Skip to content

Commit c82b74e

Browse files
committed
Add, improve, and pass interaction tests.
1 parent a6e1f9c commit c82b74e

File tree

5 files changed

+181
-75
lines changed

5 files changed

+181
-75
lines changed

lib/interactions.js

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
6262

6363
// compute callback URL
6464
const {localInteractionId} = definition;
65-
const callbackUrl =
65+
const pushCallbackUrl =
6666
`${baseUri}${interactionsPath}/${localInteractionId}` +
6767
`/callbacks/${token}`;
6868

@@ -74,18 +74,16 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
7474
// template variables
7575
variables: {
7676
...variables,
77-
callback: {
78-
url: callbackUrl
79-
},
77+
pushCallbackUrl,
8078
accountId
8179
}
8280
};
8381
const capability = definition.zcaps.get('readWriteExchanges');
8482
const response = await zcapClient.write({json: exchange, capability});
8583
const exchangeId = response.headers.get('location');
8684
// reuse `localExchangeId` in path
87-
const localExchangeId = exchangeId.slice(exchangeId.lastIndexOf('/'));
88-
const id = `${config.server.baseUri}/${routes.interactions}/` +
85+
const localExchangeId = exchangeId.slice(exchangeId.lastIndexOf('/') + 1);
86+
const id = `${config.server.baseUri}${routes.interactions}/` +
8987
`${localInteractionId}/${localExchangeId}`;
9088
res.json({interactionId: id, exchangeId});
9189
}));
@@ -94,7 +92,7 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
9492
app.get(
9593
routes.interaction,
9694
ensureAuthenticated,
97-
// FIXME: add URL query validator that requires no query or `iuv=1` only
95+
validate({querySchema: schemas.getInteractionQuery}),
9896
asyncHandler(async (req, res) => {
9997
const {id: accountId} = req.user.account || {};
10098
const {
@@ -139,7 +137,7 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
139137
ttl: POLL_TTL
140138
});
141139

142-
res.json(result);
140+
res.json(result.value);
143141
}));
144142

145143
// push event handler
@@ -226,13 +224,10 @@ function _createExchangePoller({accountId, capability}) {
226224
}
227225
// return only information that should be accessible to client
228226
return {
229-
exchange
230-
// FIXME: filter info once final step name and info is determined
231-
/*
232227
exchange: {
233228
state: exchange.state,
234229
result: exchange.variables.results?.finish
235-
}*/
230+
}
236231
};
237232
}
238233
});

schemas/bedrock-profile-http.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,11 +198,24 @@ const createInteraction = {
198198
}
199199
};
200200

201+
const getInteractionQuery = {
202+
title: 'Interaction Query',
203+
type: 'object',
204+
additionalProperties: false,
205+
properties: {
206+
iuv: {
207+
title: 'Interaction URL version',
208+
const: '1'
209+
}
210+
}
211+
};
212+
201213
export {
202214
profileAgent,
203215
profileAgents,
204216
accountQuery,
205217
delegateCapability,
206218
createInteraction,
219+
getInteractionQuery,
207220
zcaps
208221
};

test/mocha/30-interactions.js

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ describe('interactions', () => {
4343
result.data.message.should.equal(
4444
`A validation error occurred in the 'Create Interaction' validator.`);
4545
});
46+
4647
it('fails to create a new interaction with unknown type', async () => {
4748
let result;
4849
let error;
@@ -64,8 +65,10 @@ describe('interactions', () => {
6465
result.data.message.should.equal(
6566
'Interaction type "does-not-exist" not found.');
6667
});
67-
it.skip('creates a new interaction', async () => {
68+
69+
it('creates a new interaction', async () => {
6870
let interactionId;
71+
let exchangeId;
6972
{
7073
let result;
7174
let error;
@@ -86,6 +89,7 @@ describe('interactions', () => {
8689
should.exist(result.data.interactionId);
8790
should.exist(result.data.exchangeId);
8891
interactionId = result.data.interactionId;
92+
exchangeId = result.data.exchangeId;
8993
}
9094

9195
// get status of interaction
@@ -101,8 +105,100 @@ describe('interactions', () => {
101105
should.exist(result);
102106
result.status.should.equal(200);
103107
result.ok.should.equal(true);
104-
console.log('result.data', result.data);
105-
// FIXME: assert on result.data
108+
should.exist(result.data.exchange);
109+
result.data.exchange.should.include.keys(['state']);
110+
result.data.exchange.state.should.equal('pending');
111+
}
112+
113+
// get protocols for interaction
114+
{
115+
let result;
116+
let error;
117+
try {
118+
result = await api.get(`${interactionId}?iuv=1`);
119+
} catch(e) {
120+
error = e;
121+
}
122+
assertNoError(error);
123+
should.exist(result);
124+
result.status.should.equal(200);
125+
result.ok.should.equal(true);
126+
should.exist(result.data.protocols);
127+
result.data.protocols.should.include.keys(['inviteRequest']);
128+
result.data.protocols.inviteRequest.should.equal(
129+
`${exchangeId}/invite-request/response`);
130+
}
131+
});
132+
133+
it('completes an interaction', async () => {
134+
let interactionId;
135+
{
136+
let result;
137+
let error;
138+
try {
139+
result = await api.post('/interactions', {
140+
type: 'test',
141+
exchange: {
142+
variables: {}
143+
}
144+
});
145+
} catch(e) {
146+
error = e;
147+
}
148+
assertNoError(error);
149+
interactionId = result.data.interactionId;
150+
}
151+
152+
// get protocols for interaction
153+
let inviteRequestUrl;
154+
{
155+
let result;
156+
let error;
157+
try {
158+
result = await api.get(`${interactionId}?iuv=1`);
159+
} catch(e) {
160+
error = e;
161+
}
162+
assertNoError(error);
163+
inviteRequestUrl = result.data.protocols.inviteRequest;
164+
}
165+
166+
// create invite response for exchange
167+
const referenceId = crypto.randomUUID();
168+
const inviteResponse = {
169+
url: 'https://retailer.example/checkout/baskets/1',
170+
purpose: 'checkout',
171+
referenceId
172+
};
173+
174+
// complete interaction by posting invite response to exchange
175+
{
176+
const response = await api.post(inviteRequestUrl, inviteResponse);
177+
should.exist(response?.data?.referenceId);
178+
// ensure `referenceId` matches
179+
response.data.referenceId.should.equal(referenceId);
180+
}
181+
182+
// get status of interaction (exchange should have a `complete` state
183+
// and result)
184+
{
185+
let result;
186+
let error;
187+
try {
188+
result = await api.get(interactionId);
189+
} catch(e) {
190+
error = e;
191+
}
192+
assertNoError(error);
193+
should.exist(result);
194+
result.status.should.equal(200);
195+
result.ok.should.equal(true);
196+
should.exist(result.data.exchange);
197+
result.data.exchange.should.include.keys(['state', 'result']);
198+
result.data.exchange.state.should.equal('complete');
199+
should.exist(result.data.exchange.result.inviteRequest?.inviteResponse);
200+
result.data.exchange.result.inviteRequest.inviteResponse
201+
.should.deep.equal(inviteResponse);
106202
}
107203
});
108204
});

test/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"@bedrock/ssm-mongodb": "^13.0.0",
4141
"@bedrock/test": "^8.2.0",
4242
"@bedrock/validation": "^7.1.1",
43-
"@bedrock/vc-delivery": "^7.7.0",
43+
"@bedrock/vc-delivery": "^7.7.1",
4444
"@bedrock/vc-verifier": "^22.1.0",
4545
"@bedrock/veres-one-context": "^16.0.0",
4646
"@bedrock/zcap-storage": "^9.0.0",

test/test.js

Lines changed: 61 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -58,69 +58,71 @@ bedrock.events.on('bedrock.init', async () => {
5858
});
5959

6060
bedrock.events.on('bedrock.ready', async () => {
61-
// programmatically create workflow for interactions
62-
{
63-
// create some controller for the workflow
64-
const secret = '53ad64ce-8e1d-11ec-bb12-10bf48838a41';
65-
const handle = 'test';
66-
const capabilityAgent = await CapabilityAgent.fromSecret({secret, handle});
67-
const {baseUri} = bedrock.config.server;
61+
// programmatically create workflow for interactions...
6862

69-
const meter = {
70-
id: idEncoder.encode(await idGenerator.generate()),
71-
controller: capabilityAgent.id,
72-
product: {
73-
id: mockData.productIdMap.get('vc-workflow')
74-
},
75-
serviceId: bedrock.config['app-identity'].seeds.services['vc-workflow'].id
76-
};
77-
await meters.insert({meter});
78-
const meterId = `${baseUri}/meters/${meter.id}`;
79-
await meterReporting.upsert({id: meterId, serviceType: 'vc-workflow'});
63+
// create some controller for the workflow
64+
const secret = '53ad64ce-8e1d-11ec-bb12-10bf48838a41';
65+
const handle = 'test';
66+
const capabilityAgent = await CapabilityAgent.fromSecret({secret, handle});
67+
const {baseUri} = bedrock.config.server;
8068

81-
const localWorkflowId = idEncoder.encode(await idGenerator.generate());
82-
const config = {
83-
id: `${baseUri}/workflows/${localWorkflowId}`,
84-
sequence: 0,
85-
controller: capabilityAgent.id,
86-
meterId,
87-
steps: {
88-
myStep: {
89-
stepTemplate: {
90-
type: 'jsonata',
91-
template: `
92-
{
93-
"inviteRequest": inviteRequest
94-
}`
95-
}
69+
const meter = {
70+
id: idEncoder.encode(await idGenerator.generate()),
71+
controller: capabilityAgent.id,
72+
product: {
73+
id: mockData.productIdMap.get('vc-workflow')
74+
},
75+
serviceId: bedrock.config['app-identity'].seeds.services['vc-workflow'].id
76+
};
77+
await meters.insert({meter});
78+
const meterId = `${baseUri}/meters/${meter.id}`;
79+
await meterReporting.upsert({id: meterId, serviceType: 'vc-workflow'});
80+
81+
const localWorkflowId = idEncoder.encode(await idGenerator.generate());
82+
const config = {
83+
id: `${baseUri}/workflows/${localWorkflowId}`,
84+
sequence: 0,
85+
controller: capabilityAgent.id,
86+
meterId,
87+
steps: {
88+
finish: {
89+
stepTemplate: {
90+
type: 'jsonata',
91+
template: `
92+
{
93+
"inviteRequest": true,
94+
"callback": {
95+
"url": pushCallbackUrl
96+
}
97+
}`
9698
}
97-
},
98-
initialStep: 'myStep'
99-
};
100-
await workflowService.configStorage.insert({config});
99+
}
100+
},
101+
initialStep: 'finish'
102+
};
103+
await workflowService.configStorage.insert({config});
101104

102-
// delegate ability to read/write exchanges for workflow to app identity
103-
const signer = capabilityAgent.getSigner();
104-
const zcapClient = new ZcapClient({
105-
invocationSigner: signer,
106-
delegationSigner: signer,
107-
SuiteClass: Ed25519Signature2020
108-
});
109-
const readWriteExchanges = await zcapClient.delegate({
110-
capability: `urn:zcap:root:${encodeURIComponent(config.id)}`,
111-
invocationTarget: `${config.id}/exchanges`,
112-
controller: ZCAP_CLIENT.invocationSigner.id,
113-
expires: new Date(Date.now() + 1000 * 60 * 60),
114-
allowedActions: ['read', 'write']
115-
});
116-
// update interactions config
117-
const interactionsConfig = bedrock.config['profile-http'].interactions;
118-
interactionsConfig.enabled = true;
119-
interactionsConfig.types.test.zcaps
120-
.readWriteExchanges = JSON.stringify(readWriteExchanges);
121-
// re-process interactions config
122-
processInteractionConfig();
123-
}
105+
// delegate ability to read/write exchanges for workflow to app identity
106+
const signer = capabilityAgent.getSigner();
107+
const zcapClient = new ZcapClient({
108+
invocationSigner: signer,
109+
delegationSigner: signer,
110+
SuiteClass: Ed25519Signature2020
111+
});
112+
const readWriteExchanges = await zcapClient.delegate({
113+
capability: `urn:zcap:root:${encodeURIComponent(config.id)}`,
114+
invocationTarget: `${config.id}/exchanges`,
115+
controller: ZCAP_CLIENT.invocationSigner.id,
116+
expires: new Date(Date.now() + 1000 * 60 * 60),
117+
allowedActions: ['read', 'write']
118+
});
119+
// update interactions config
120+
const interactionsConfig = bedrock.config['profile-http'].interactions;
121+
interactionsConfig.enabled = true;
122+
interactionsConfig.types.test.zcaps
123+
.readWriteExchanges = JSON.stringify(readWriteExchanges);
124+
// re-process interactions config
125+
processInteractionConfig();
124126
});
125127

126128
import '@bedrock/test';

0 commit comments

Comments
 (0)