From eacf6bbd0342b638c52c549d0a9324b188941b83 Mon Sep 17 00:00:00 2001 From: lenkan Date: Sat, 9 Dec 2023 11:11:41 +0100 Subject: [PATCH] remove endless loops from tests Improves wait/retry function with exponential back-off for faster test run --- .../integration-scripts/challenge.test.ts | 13 +- .../integration-scripts/credentials.test.ts | 175 ++++-------------- .../delegation-multisig.test.ts | 94 ++++------ .../integration-scripts/delegation.test.ts | 7 +- examples/integration-scripts/multisig.test.ts | 78 +++----- examples/integration-scripts/randy.test.ts | 9 +- .../request-present.test.ts | 34 +--- examples/integration-scripts/salty.test.ts | 14 +- .../single-issuer-chained-credential.test.ts | 69 +++---- .../single-issuer-holder.test.ts | 142 +++----------- examples/integration-scripts/utils/retry.ts | 53 ++++++ .../integration-scripts/utils/test-util.ts | 68 ++++--- examples/integration-scripts/witness.test.ts | 34 +--- 13 files changed, 265 insertions(+), 525 deletions(-) create mode 100644 examples/integration-scripts/utils/retry.ts diff --git a/examples/integration-scripts/challenge.test.ts b/examples/integration-scripts/challenge.test.ts index ef1a4b31..99a0f7c3 100644 --- a/examples/integration-scripts/challenge.test.ts +++ b/examples/integration-scripts/challenge.test.ts @@ -55,7 +55,7 @@ test('challenge', async () => { 'BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX', ], }); - const { response: aid1 } = await waitOperation<{ i: string }>( + const { response: aid1 } = await waitOperation( client1, await icpResult1.op() ); @@ -72,14 +72,11 @@ test('challenge', async () => { 'BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX', ], }); - let op2 = await icpResult2.op(); - while (!op2['done']) { - op2 = await client2.operations().get(op2.name); - await new Promise((resolve) => setTimeout(resolve, 1000)); - } - const aid2 = op2['response']; + const { response: aid2 } = await waitOperation( + client2, + await icpResult2.op() + ); await client2.identifiers().addEndRole('bob', 'agent', client2!.agent!.pre); - console.log("Bob's AID:", aid2.i); // Exchenge OOBIs const oobi1 = await client1.oobis().get('alice', 'agent'); diff --git a/examples/integration-scripts/credentials.test.ts b/examples/integration-scripts/credentials.test.ts index 35a60116..65b521c1 100644 --- a/examples/integration-scripts/credentials.test.ts +++ b/examples/integration-scripts/credentials.test.ts @@ -1,18 +1,18 @@ import { strict as assert } from 'assert'; import signify from 'signify-ts'; import { resolveEnvironment } from './utils/resolve-env'; -const { url, bootUrl, vleiServerUrl, witnessIds, witnessUrls } = - resolveEnvironment(); +import { + resolveOobi, + waitForNotifications, + waitOperation, +} from './utils/test-util'; +import { retry } from './utils/retry'; +const { url, bootUrl, vleiServerUrl, witnessIds } = resolveEnvironment(); const boot_url = bootUrl; const WAN_WITNESS_AID = witnessIds[0]; const WIL_WITNESS_AID = witnessIds[1]; const WES_WITNESS_AID = witnessIds[2]; -const WITNESS_OOBIS = [ - `${witnessUrls[0]}/oobi/${WAN_WITNESS_AID}/controller?name=Wan&tag=witness`, - `${witnessUrls[1]}/oobi/${WIL_WITNESS_AID}/controller?name=Wil&tag=witness`, - `${witnessUrls[2]}/oobi/${WES_WITNESS_AID}/controller?name=Wes&tag=witness`, -]; const KLI_WITNESS_DEMO_PREFIXES = [ WAN_WITNESS_AID, @@ -43,40 +43,6 @@ function createTimestamp() { return new Date().toISOString().replace('Z', '000+00:00'); } -// typed interface for Notification -interface Notification { - i: string; - dt: string; - r: boolean; - a: { r: string; d?: string; m?: string }; -} - -// checks for notifications on a route and returns them as an array -async function waitForNotifications( - client: signify.SignifyClient, - route: string -): Promise { - const awaitedNotifications = []; - while (true) { - const notifications = await client.notifications().list(); - for (const note of notifications.notes) { - if (note.a.r === route) { - awaitedNotifications.push(note); - } - } - if (awaitedNotifications.length > 0) break; - - await new Promise((resolve) => setTimeout(resolve, 250)); - } - return awaitedNotifications; -} - -async function resolveWitnesses(client: signify.SignifyClient) { - await Promise.all( - WITNESS_OOBIS.map((oobi) => client.oobis().resolve(oobi)) - ); -} - test('credentials', async () => { await signify.ready(); // Boot three clients one each for issuer, holder, and verifier @@ -117,11 +83,7 @@ test('credentials', async () => { toad: 3, wits: [...KLI_WITNESS_DEMO_PREFIXES], }); - let issOp = await issuerIcpRes.op(); - while (!issOp['done']) { - issOp = await issuerClient.operations().get(issOp.name); - await new Promise((resolve) => setTimeout(resolve, 250)); - } + await waitOperation(issuerClient, await issuerIcpRes.op()); const issAidResp = await issuerClient.identifiers().get(issuerAidName); const issuerAID = issAidResp.prefix; await issuerClient @@ -135,11 +97,7 @@ test('credentials', async () => { toad: 3, wits: [...KLI_WITNESS_DEMO_PREFIXES], }); - let hldOp = await holderIcpRes.op(); - while (!hldOp['done']) { - hldOp = await holderClient.operations().get(hldOp.name); - await new Promise((resolve) => setTimeout(resolve, 250)); - } + await waitOperation(holderClient, await holderIcpRes.op()); const hldAidResp = await holderClient.identifiers().get(holderAidName); const holderAID = hldAidResp.prefix; await holderClient @@ -153,11 +111,7 @@ test('credentials', async () => { toad: 3, wits: [...KLI_WITNESS_DEMO_PREFIXES], }); - let vfyOp = await verifierIcpRes.op(); - while (!vfyOp['done']) { - vfyOp = await verifierClient.operations().get(vfyOp.name); - await new Promise((resolve) => setTimeout(resolve, 250)); - } + await waitOperation(verifierClient, await verifierIcpRes.op()); const vfyAidResp = await verifierClient.identifiers().get(verifierAidName); const verifierAID = vfyAidResp.prefix; await verifierClient @@ -171,21 +125,9 @@ test('credentials', async () => { console.log('Resolving Schema and Agent OOBIs...'); console.log(`Resolving schema OOBIs with ${schemaOobiUrl}`); - issOp = await issuerClient.oobis().resolve(schemaOobiUrl, 'schema'); - while (!issOp['done']) { - issOp = await issuerClient.operations().get(issOp.name); - await new Promise((resolve) => setTimeout(resolve, 250)); - } - hldOp = await holderClient.oobis().resolve(schemaOobiUrl, 'schema'); - while (!hldOp['done']) { - hldOp = await holderClient.operations().get(hldOp.name); - await new Promise((resolve) => setTimeout(resolve, 250)); - } - vfyOp = await verifierClient.oobis().resolve(schemaOobiUrl, 'schema'); - while (!vfyOp['done']) { - vfyOp = await verifierClient.operations().get(vfyOp.name); - await new Promise((resolve) => setTimeout(resolve, 250)); - } + await resolveOobi(issuerClient, schemaOobiUrl, 'schema'); + await resolveOobi(holderClient, schemaOobiUrl, 'schema'); + await resolveOobi(verifierClient, schemaOobiUrl, 'schema'); const issSchema = await issuerClient.schemas().get(schemaSAID); assert.equal(issSchema.$id, schemaSAID); @@ -203,54 +145,18 @@ test('credentials', async () => { .get(verifierAidName, 'agent'); // issuer -> holder, verifier - issOp = await issuerClient - .oobis() - .resolve(hldAgentOOBI.oobis[0], holderAidName); - while (!issOp['done']) { - issOp = await issuerClient.operations().get(issOp.name); - await new Promise((resolve) => setTimeout(resolve, 250)); - } - issOp = await issuerClient - .oobis() - .resolve(vfyAgentOOBI.oobis[0], verifierAidName); - while (!issOp['done']) { - issOp = await issuerClient.operations().get(issOp.name); - await new Promise((resolve) => setTimeout(resolve, 250)); - } + await resolveOobi(issuerClient, hldAgentOOBI.oobis[0], holderAidName); + await resolveOobi(issuerClient, vfyAgentOOBI.oobis[0], verifierAidName); console.log('Issuer resolved 2 OOBIs: [holder, verifier]'); // holder -> issuer, verifier - hldOp = await holderClient - .oobis() - .resolve(issAgentOOBI.oobis[0], issuerAidName); - while (!hldOp['done']) { - hldOp = await holderClient.operations().get(hldOp.name); - await new Promise((resolve) => setTimeout(resolve, 250)); - } - hldOp = await holderClient - .oobis() - .resolve(vfyAgentOOBI.oobis[0], verifierAidName); - while (!hldOp['done']) { - hldOp = await holderClient.operations().get(hldOp.name); - await new Promise((resolve) => setTimeout(resolve, 250)); - } + await resolveOobi(holderClient, issAgentOOBI.oobis[0], issuerAidName); + await resolveOobi(holderClient, vfyAgentOOBI.oobis[0], verifierAidName); console.log('Holder resolved 2 OOBIs: [issuer, verifier]'); // verifier -> issuer, holder - vfyOp = await verifierClient - .oobis() - .resolve(issAgentOOBI.oobis[0], issuerAidName); - while (!vfyOp['done']) { - vfyOp = await verifierClient.operations().get(vfyOp.name); - await new Promise((resolve) => setTimeout(resolve, 250)); - } - vfyOp = await verifierClient - .oobis() - .resolve(hldAgentOOBI.oobis[0], holderAidName); - while (!vfyOp['done']) { - vfyOp = await verifierClient.operations().get(vfyOp.name); - await new Promise((resolve) => setTimeout(resolve, 250)); - } + await resolveOobi(verifierClient, issAgentOOBI.oobis[0], issuerAidName); + await resolveOobi(verifierClient, hldAgentOOBI.oobis[0], holderAidName); console.log('Verifier resolved 2 OOBIs: [issuer, holder]'); // Create registry for issuer @@ -258,11 +164,7 @@ test('credentials', async () => { const regResult = await issuerClient .registries() .create({ name: issuerAidName, registryName: registryName }); - issOp = await regResult.op(); - while (!issOp['done']) { - issOp = await issuerClient.operations().get(issOp.name); - await new Promise((resolve) => setTimeout(resolve, 250)); - } + await waitOperation(issuerClient, await regResult.op()); const registries = await issuerClient.registries().list(issuerAidName); const registry = registries[0]; assert.equal(registries.length, 1); @@ -285,11 +187,7 @@ test('credentials', async () => { recipient: holderAID, data: vcdata, }); - issOp = issResult.op; - while (!issOp['done']) { - issOp = await issuerClient.operations().get(issOp.name); - await new Promise((resolve) => setTimeout(resolve, 250)); - } + await waitOperation(issuerClient, issResult.op); let issCreds = await issuerClient.credentials().list(); assert.equal(issCreds.length, 1); assert.equal(issCreds[0].sad.s, schemaSAID); @@ -339,13 +237,12 @@ test('credentials', async () => { console.log('Holder: IPEX GRANT notification marked'); // list credentials to show new credential received through IPEX GRANT+ADMIT - let holderCreds = await holderClient.credentials().list(); - while (holderCreds.length < 1) { - console.log('Holder: No credentials yet...'); - await new Promise((resolve) => setTimeout(resolve, 250)); - holderCreds = await holderClient.credentials().list(); - } - const hldVleiAcdc: any = holderCreds[0]; + const holderCreds = await retry(async () => { + const result = await holderClient.credentials().list(); + expect(result.length).toBeGreaterThanOrEqual(1); + return result; + }); + const hldVleiAcdc = holderCreds[0]; assert.equal(holderCreds.length, 1); assert.equal(hldVleiAcdc.sad.s, schemaSAID); assert.equal(hldVleiAcdc.sad.i, issuerAID); @@ -387,12 +284,11 @@ test('credentials', async () => { console.log('Verifier: Notification marked for presentation'); // list credentials for verifier - let verifierCreds = await verifierClient.credentials().list(); - while (verifierCreds.length < 1) { - console.log('No credentials yet...'); - await new Promise((resolve) => setTimeout(resolve, 250)); - verifierCreds = await verifierClient.credentials().list(); - } + const verifierCreds = await retry(async () => { + const result = await verifierClient.credentials().list(); + expect(result.length).toBeGreaterThanOrEqual(1); + return result; + }); assert.equal(verifierCreds.length, 1); assert.equal(verifierCreds[0].sad.s, schemaSAID); assert.equal(verifierCreds[0].sad.i, issuerAID); @@ -400,13 +296,10 @@ test('credentials', async () => { console.log('Credential presented and received by verifier'); // Revoke credential - issOp = await issuerClient + const revokeOperation = await issuerClient .credentials() .revoke(issuerAidName, issCreds[0].sad.d); - while (!issOp['done']) { - issOp = await issuerClient.operations().get(issOp.name); - await new Promise((resolve) => setTimeout(resolve, 250)); - } + await waitOperation(issuerClient, revokeOperation); issCreds = await issuerClient.credentials().list(); const issVleiAcdc = issCreds[0]; assert.equal(issCreds.length, 1); diff --git a/examples/integration-scripts/delegation-multisig.test.ts b/examples/integration-scripts/delegation-multisig.test.ts index b0b1ea2b..35042450 100644 --- a/examples/integration-scripts/delegation-multisig.test.ts +++ b/examples/integration-scripts/delegation-multisig.test.ts @@ -1,6 +1,7 @@ import { strict as assert } from 'assert'; import signify from 'signify-ts'; import { resolveEnvironment } from './utils/resolve-env'; +import { waitForNotifications, waitOperation } from './utils/test-util'; const { url, bootUrl } = resolveEnvironment(); @@ -12,26 +13,26 @@ test('delegation-multisig', async () => { const client2 = await bootClient(); // Create four identifiers, one for each client - let aid0 = await createAID(client0, 'delegator'); - let aid1 = await createAID(client1, 'member1'); - let aid2 = await createAID(client2, 'member2'); + const aid0 = await createAID(client0, 'delegator'); + const aid1 = await createAID(client1, 'member1'); + const aid2 = await createAID(client2, 'member2'); // Exchange OOBIs console.log('Resolving OOBIs'); - let oobi0 = await client0.oobis().get('delegator', 'agent'); - let oobi1 = await client1.oobis().get('member1', 'agent'); - let oobi2 = await client2.oobis().get('member2', 'agent'); + const oobi0 = await client0.oobis().get('delegator', 'agent'); + const oobi1 = await client1.oobis().get('member1', 'agent'); + const oobi2 = await client2.oobis().get('member2', 'agent'); let op1 = await client1.oobis().resolve(oobi0.oobis[0], 'delegator'); - op1 = await waitForOp(client1, op1); + op1 = await waitOperation(client1, op1); op1 = await client1.oobis().resolve(oobi2.oobis[0], 'member2'); - op1 = await waitForOp(client1, op1); + op1 = await waitOperation(client1, op1); console.log('Member1 resolved 2 OOBIs'); let op2 = await client2.oobis().resolve(oobi0.oobis[0], 'delegator'); - op1 = await waitForOp(client2, op1); - op1 = await client2.oobis().resolve(oobi1.oobis[0], 'member1'); - op1 = await waitForOp(client2, op1); + op2 = await waitOperation(client2, op2); + op2 = await client2.oobis().resolve(oobi1.oobis[0], 'member1'); + op2 = await waitOperation(client2, op2); console.log('Member2 resolved 2 OOBIs'); // First member challenge the other members with a random list of words @@ -44,18 +45,18 @@ test('delegation-multisig', async () => { console.log('Member2 responded challenge with signed words'); op1 = await client1.challenges().verify('member1', aid2.prefix, words); - op1 = await waitForOp(client1, op1); + op1 = await waitOperation(client1, op1); console.log('Member1 verified challenge response from member2'); - let exnwords = new signify.Serder(op1.response.exn); + const exnwords = new signify.Serder(op1.response.exn); op1 = await client1 .challenges() .responded('member1', aid2.prefix, exnwords.ked.d); console.log('Member1 marked challenge response as accepted'); // First member start the creation of a multisig identifier - let rstates = [aid1['state'], aid2['state']]; - let states = rstates; - let icpResult1 = await client1.identifiers().create('multisig', { + const rstates = [aid1['state'], aid2['state']]; + const states = rstates; + const icpResult1 = await client1.identifiers().create('multisig', { algo: signify.Algos.group, mhab: aid1, isith: 2, @@ -74,7 +75,7 @@ test('delegation-multisig', async () => { let serder = icpResult1.serder; let sigs = icpResult1.sigs; - let sigers = sigs.map((sig: any) => new signify.Siger({ qb64: sig })); + let sigers = sigs.map((sig) => new signify.Siger({ qb64: sig })); let ims = signify.d(signify.messagize(serder, sigers)); let atc = ims.substring(serder.size); let embeds = { @@ -98,14 +99,19 @@ test('delegation-multisig', async () => { console.log('Member1 initiated multisig, waiting for others to join...'); // Second member check notifications and join the multisig - let msgSaid = await waitForMessage(client2, '/multisig/icp'); + const notifications = await waitForNotifications(client2, '/multisig/icp'); + await Promise.all( + notifications.map((note) => client2.notifications().mark(note.i)) + ); + const msgSaid = notifications[notifications.length - 1].a.d; + assert(msgSaid !== undefined); console.log('Member2 received exchange message to join multisig'); - let res = await client2.groups().getRequest(msgSaid); - let exn = res[0].exn; - let icp = exn.e.icp; + const res = await client2.groups().getRequest(msgSaid); + const exn = res[0].exn; + const icp = exn.e.icp; - let icpResult2 = await client2.identifiers().create('multisig', { + const icpResult2 = await client2.identifiers().create('multisig', { algo: signify.Algos.group, mhab: aid2, isith: icp.kt, @@ -119,7 +125,7 @@ test('delegation-multisig', async () => { op2 = await icpResult2.op(); serder = icpResult2.serder; sigs = icpResult2.sigs; - sigers = sigs.map((sig: any) => new signify.Siger({ qb64: sig })); + sigers = sigs.map((sig) => new signify.Siger({ qb64: sig })); ims = signify.d(signify.messagize(serder, sigers)); atc = ims.substring(serder.size); @@ -143,7 +149,7 @@ test('delegation-multisig', async () => { ); console.log('Member2 joined multisig, waiting for others...'); - let delegatePrefix = op1.name.split('.')[1]; + const delegatePrefix = op1.name.split('.')[1]; assert.equal(op2.name.split('.')[1], delegatePrefix); console.log("Delegate's prefix:", delegatePrefix); console.log('Delegate waiting for approval...'); @@ -158,40 +164,17 @@ test('delegation-multisig', async () => { console.log('Delegator approved delegation'); // Check for completion - op1 = await waitForOp(client1, op1); - op2 = await waitForOp(client2, op2); + op1 = await waitOperation(client1, op1); + op2 = await waitOperation(client2, op2); console.log('Delegated multisig created!'); const aid_delegate = await client1.identifiers().get('multisig'); assert.equal(aid_delegate.prefix, delegatePrefix); }, 30000); -async function waitForOp(client: signify.SignifyClient, op: any) { - while (!op['done']) { - op = await client.operations().get(op.name); - await new Promise((resolve) => setTimeout(resolve, 1000)); - } - return op; -} - -async function waitForMessage(client: signify.SignifyClient, route: string) { - let msgSaid = ''; - while (msgSaid == '') { - let notifications = await client.notifications().list(); - for (let notif of notifications.notes) { - if (notif.a.r == route) { - msgSaid = notif.a.d; - await client.notifications().mark(notif.i); - } - } - await new Promise((resolve) => setTimeout(resolve, 1000)); - } - return msgSaid; -} - async function bootClient(): Promise { - let bran = signify.randomPasscode(); - let client = new signify.SignifyClient( + const bran = signify.randomPasscode(); + const client = new signify.SignifyClient( url, bran, signify.Tier.low, @@ -199,7 +182,7 @@ async function bootClient(): Promise { ); await client.boot(); await client.connect(); - let state = await client.state(); + const state = await client.state(); console.log( 'Client AID:', state.controller.state.i, @@ -210,7 +193,7 @@ async function bootClient(): Promise { } async function createAID(client: signify.SignifyClient, name: string) { - let icpResult1 = await client.identifiers().create(name, { + const icpResult1 = await client.identifiers().create(name, { toad: 3, wits: [ 'BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha', @@ -218,9 +201,8 @@ async function createAID(client: signify.SignifyClient, name: string) { 'BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX', ], }); - let op = await icpResult1.op(); - op = await waitForOp(client, op); - let aid = await client.identifiers().get(name); + await waitOperation(client, await icpResult1.op()); + const aid = await client.identifiers().get(name); await client.identifiers().addEndRole(name, 'agent', client!.agent!.pre); console.log(name, 'AID:', aid.prefix); return aid; diff --git a/examples/integration-scripts/delegation.test.ts b/examples/integration-scripts/delegation.test.ts index f626f3fa..7dbc70eb 100644 --- a/examples/integration-scripts/delegation.test.ts +++ b/examples/integration-scripts/delegation.test.ts @@ -50,8 +50,7 @@ test('delegation', async () => { 'BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX', ], }); - let op1 = await icpResult1.op(); - await waitOperation(client1, op1); + await waitOperation(client1, await icpResult1.op()); const aid1 = await client1.identifiers().get('delegator'); await client1 .identifiers() @@ -68,7 +67,7 @@ test('delegation', async () => { const icpResult2 = await client2 .identifiers() .create('delegate', { delpre: aid1.prefix }); - let op2 = await icpResult2.op(); + const op2 = await icpResult2.op(); const delegatePrefix = op2.name.split('.')[1]; console.log("Delegate's prefix:", delegatePrefix); console.log('Delegate waiting for approval...'); @@ -79,7 +78,7 @@ test('delegation', async () => { s: 0, d: delegatePrefix, }; - op1 = await client1.identifiers().interact('delegator', anchor); + await client1.identifiers().interact('delegator', anchor); console.log('Delegator approved delegation'); // Client 2 check approval diff --git a/examples/integration-scripts/multisig.test.ts b/examples/integration-scripts/multisig.test.ts index 60f49539..dae67d0f 100644 --- a/examples/integration-scripts/multisig.test.ts +++ b/examples/integration-scripts/multisig.test.ts @@ -5,9 +5,10 @@ import signify, { IssueCredentialResult, } from 'signify-ts'; import { resolveEnvironment } from './utils/resolve-env'; -import { waitOperation } from './utils/test-util'; +import { waitForNotifications, waitOperation } from './utils/test-util'; +import { getOrCreateClient } from './utils/test-setup'; -const { url, bootUrl, vleiServerUrl } = resolveEnvironment(); +const { vleiServerUrl } = resolveEnvironment(); const WITNESS_AIDS = [ 'BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha', 'BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM', @@ -21,10 +22,10 @@ test('multisig', async function run() { await signify.ready(); // Boot Four clients const [client1, client2, client3, client4] = await Promise.all([ - bootClient(), - bootClient(), - bootClient(), - bootClient(), + getOrCreateClient(), + getOrCreateClient(), + getOrCreateClient(), + getOrCreateClient(), ]); // Create four identifiers, one for each client @@ -159,7 +160,7 @@ test('multisig', async function run() { // Second member check notifications and join the multisig - let msgSaid = await waitForMessage(client2, '/multisig/icp'); + let msgSaid = await waitAndMarkNotification(client2, '/multisig/icp'); console.log('Member2 received exchange message to join multisig'); let res = await client2.groups().getRequest(msgSaid); @@ -204,7 +205,7 @@ test('multisig', async function run() { console.log('Member2 joined multisig, waiting for others...'); // Third member check notifications and join the multisig - msgSaid = await waitForMessage(client3, '/multisig/icp'); + msgSaid = await waitAndMarkNotification(client3, '/multisig/icp'); console.log('Member3 received exchange message to join multisig'); res = await client3.groups().getRequest(msgSaid); @@ -345,7 +346,7 @@ test('multisig', async function run() { ); //Member2 check for notifications and join the authorization - msgSaid = await waitForMessage(client2, '/multisig/rpy'); + msgSaid = await waitAndMarkNotification(client2, '/multisig/rpy'); console.log( 'Member2 received exchange message to join the end role authorization' ); @@ -393,7 +394,7 @@ test('multisig', async function run() { ); //Member3 check for notifications and join the authorization - msgSaid = await waitForMessage(client3, '/multisig/rpy'); + msgSaid = await waitAndMarkNotification(client3, '/multisig/rpy'); console.log( 'Member3 received exchange message to join the end role authorization' ); @@ -490,7 +491,7 @@ test('multisig', async function run() { ); // Member2 check for notifications and join the interaction event - msgSaid = await waitForMessage(client2, '/multisig/ixn'); + msgSaid = await waitAndMarkNotification(client2, '/multisig/ixn'); console.log( 'Member2 received exchange message to join the interaction event' ); @@ -528,7 +529,7 @@ test('multisig', async function run() { console.log('Member2 joins interaction event, waiting for others...'); // Member3 check for notifications and join the interaction event - msgSaid = await waitForMessage(client3, '/multisig/ixn'); + msgSaid = await waitAndMarkNotification(client3, '/multisig/ixn'); console.log( 'Member3 received exchange message to join the interaction event' ); @@ -655,7 +656,7 @@ test('multisig', async function run() { ); // Member2 check for notifications and join the rotation event - msgSaid = await waitForMessage(client2, '/multisig/rot'); + msgSaid = await waitAndMarkNotification(client2, '/multisig/rot'); console.log('Member2 received exchange message to join the rotation event'); await new Promise((resolve) => setTimeout(resolve, 5000)); @@ -693,7 +694,7 @@ test('multisig', async function run() { console.log('Member2 joins rotation event, waiting for others...'); // Member3 check for notifications and join the rotation event - msgSaid = await waitForMessage(client3, '/multisig/rot'); + msgSaid = await waitAndMarkNotification(client3, '/multisig/rot'); console.log('Member3 received exchange message to join the rotation event'); res = await client3.groups().getRequest(msgSaid); exn = res[0].exn; @@ -780,7 +781,7 @@ test('multisig', async function run() { console.log('Member1 initiated registry, waiting for others to join...'); // Member2 check for notifications and join the create registry event - msgSaid = await waitForMessage(client2, '/multisig/vcp'); + msgSaid = await waitAndMarkNotification(client2, '/multisig/vcp'); console.log( 'Member2 received exchange message to join the create registry event' ); @@ -822,7 +823,7 @@ test('multisig', async function run() { console.log('Member2 joins registry event, waiting for others...'); // Member3 check for notifications and join the create registry event - msgSaid = await waitForMessage(client3, '/multisig/vcp'); + msgSaid = await waitAndMarkNotification(client3, '/multisig/vcp'); console.log( 'Member3 received exchange message to join the create registry event' ); @@ -894,7 +895,7 @@ test('multisig', async function run() { ); // Member2 check for notifications and join the credential create event - msgSaid = await waitForMessage(client2, '/multisig/iss'); + msgSaid = await waitAndMarkNotification(client2, '/multisig/iss'); console.log( 'Member2 received exchange message to join the credential create event' ); @@ -915,7 +916,7 @@ test('multisig', async function run() { console.log('Member2 joins credential create event, waiting for others...'); // Member3 check for notifications and join the create registry event - msgSaid = await waitForMessage(client3, '/multisig/iss'); + msgSaid = await waitAndMarkNotification(client3, '/multisig/iss'); console.log( 'Member3 received exchange message to join the credential create event' ); @@ -1000,7 +1001,7 @@ test('multisig', async function run() { 'Member1 initiated grant message, waiting for others to join...' ); - msgSaid = await waitForMessage(client2, '/multisig/exn'); + msgSaid = await waitAndMarkNotification(client2, '/multisig/exn'); console.log('Member2 received exchange message to join the grant message'); res = await client2.groups().getRequest(msgSaid); exn = res[0].exn; @@ -1044,7 +1045,7 @@ test('multisig', async function run() { console.log('Member2 joined grant message, waiting for others to join...'); - msgSaid = await waitForMessage(client3, '/multisig/exn'); + msgSaid = await waitAndMarkNotification(client3, '/multisig/exn'); console.log('Member3 received exchange message to join the grant message'); res = await client3.groups().getRequest(msgSaid); exn = res[0].exn; @@ -1088,7 +1089,7 @@ test('multisig', async function run() { console.log('Member3 joined grant message, waiting for others to join...'); - msgSaid = await waitForMessage(client4, '/exn/ipex/grant'); + msgSaid = await waitAndMarkNotification(client4, '/exn/ipex/grant'); console.log('Holder received exchange message with the grant message'); res = await client4.exchanges().get(msgSaid); @@ -1102,41 +1103,22 @@ test('multisig', async function run() { console.log('Holder creates and sends admit message'); - msgSaid = await waitForMessage(client1, '/exn/ipex/admit'); + msgSaid = await waitAndMarkNotification(client1, '/exn/ipex/admit'); console.log('Member1 received exchange message with the admit response'); const creds = await client4.credentials().list(); console.log(`Holder holds ${creds.length} credential`); }, 360000); -async function waitForMessage(client: SignifyClient, route: string) { - let msgSaid = ''; - while (msgSaid == '') { - const notifications = await client.notifications().list(); - for (const notif of notifications.notes) { - if (notif.a.r == route) { - msgSaid = notif.a.d; - await client.notifications().mark(notif.i); - } - } - await new Promise((resolve) => setTimeout(resolve, 1000)); - } - return msgSaid; -} +async function waitAndMarkNotification(client: SignifyClient, route: string) { + const notes = await waitForNotifications(client, route); -async function bootClient(): Promise { - const bran = signify.randomPasscode(); - const client = new SignifyClient(url, bran, signify.Tier.low, bootUrl); - await client.boot(); - await client.connect(); - const state = await client.state(); - console.log( - 'Client AID:', - state.controller.state.i, - 'Agent AID: ', - state.agent.i + await Promise.all( + notes.map(async (note) => { + await client.notifications().mark(note.i); + }) ); - return client; + return notes[notes.length - 1]?.a.d ?? ''; } async function createAID(client: SignifyClient, name: string, wits: string[]) { diff --git a/examples/integration-scripts/randy.test.ts b/examples/integration-scripts/randy.test.ts index f8f396ca..3c6ef978 100644 --- a/examples/integration-scripts/randy.test.ts +++ b/examples/integration-scripts/randy.test.ts @@ -1,6 +1,7 @@ import { strict as assert } from 'assert'; import signify from 'signify-ts'; import { resolveEnvironment } from './utils/resolve-env'; +import { waitOperation } from './utils/test-util'; const { url, bootUrl } = resolveEnvironment(); @@ -21,7 +22,7 @@ test('randy', async () => { let icpResult = await client1 .identifiers() .create('aid1', { algo: signify.Algos.randy }); - let op = await icpResult.op(); + let op = await waitOperation(client1, await icpResult.op()); assert.equal(op['done'], true); let aid = op['response']; const icp = new signify.Serder(aid); @@ -37,8 +38,7 @@ test('randy', async () => { assert.equal(aid.prefix, icp.pre); icpResult = await client1.identifiers().interact('aid1', [icp.pre]); - op = await icpResult.op(); - assert.equal(op['done'], true); + op = await waitOperation(client1, await icpResult.op()); let ked = op['response']; const ixn = new signify.Serder(ked); assert.equal(ixn.ked['s'], '1'); @@ -53,8 +53,7 @@ test('randy', async () => { assert.equal(log.length, 2); icpResult = await client1.identifiers().rotate('aid1'); - op = await icpResult.op(); - assert.equal(op['done'], true); + op = await waitOperation(client1, await icpResult.op()); ked = op['response']; const rot = new signify.Serder(ked); assert.equal(rot.ked['s'], '2'); diff --git a/examples/integration-scripts/request-present.test.ts b/examples/integration-scripts/request-present.test.ts index 47a66b72..54166b3f 100644 --- a/examples/integration-scripts/request-present.test.ts +++ b/examples/integration-scripts/request-present.test.ts @@ -1,6 +1,10 @@ import { strict as assert } from 'assert'; import signify from 'signify-ts'; -import { resolveOobi, waitOperation } from './utils/test-util'; +import { + resolveOobi, + waitForNotifications, + waitOperation, +} from './utils/test-util'; import { resolveEnvironment } from './utils/resolve-env'; const { url, bootUrl, vleiServerUrl } = resolveEnvironment(); @@ -191,18 +195,8 @@ test.skip('request-present', async () => { await client3.credentials().request('verifier', aid2.prefix, schemaSAID); // Recipient checks for a presentation request notification - let requestReceived = false; - while (!requestReceived) { - const notifications = await client2.notifications().list(); - for (const notif of notifications) { - if (notif.a.r == '/presentation/request') { - assert.equal(notif.a.schema.n, schemaSAID); - requestReceived = true; - await client2.notifications().mark(notif.i); - } - } - await new Promise((resolve) => setTimeout(resolve, 1000)); - } + const notes2 = await waitForNotifications(client2, '/presentation/request'); + await Promise.all(notes2.map((n) => client2.notifications().mark(n.i))); // Recipient present credential to verifier await client1 @@ -210,18 +204,8 @@ test.skip('request-present', async () => { .present('issuer', creds1[0].sad.d, 'verifier', true); // Verifier checks for a presentation notification - requestReceived = false; - while (!requestReceived) { - const notifications = await client3.notifications().list(); - for (const notif of notifications) { - if (notif.a.r == '/presentation') { - assert.equal(notif.a.schema.n, schemaSAID); - requestReceived = true; - await client3.notifications().mark(notif.i); - } - } - await new Promise((resolve) => setTimeout(resolve, 1000)); - } + const notes3 = await waitForNotifications(client3, '/presentation'); + await Promise.all(notes3.map((n) => client3.notifications().mark(n.i))); const creds3 = await client3 .credentials() diff --git a/examples/integration-scripts/salty.test.ts b/examples/integration-scripts/salty.test.ts index c0b78d80..6a84f044 100644 --- a/examples/integration-scripts/salty.test.ts +++ b/examples/integration-scripts/salty.test.ts @@ -1,6 +1,7 @@ import { strict as assert } from 'assert'; import signify from 'signify-ts'; import { resolveEnvironment } from './utils/resolve-env'; +import { waitOperation } from './utils/test-util'; const { url, bootUrl } = resolveEnvironment(); @@ -27,8 +28,8 @@ test('salty', async () => { let icpResult = await client1 .identifiers() .create('aid1', { bran: '0123456789abcdefghijk' }); - let op = await icpResult.op(); - assert.equal(op['done'], true); + let op = await waitOperation(client1, await icpResult.op()); + const aid1 = op['response']; const icp = new signify.Serder(aid1); assert.equal(icp.pre, 'ELUvZ8aJEHAQE-0nsevyYTP98rBbGJUrTj5an-pCmwrK'); @@ -60,8 +61,7 @@ test('salty', async () => { nsith: '2', bran: '0123456789lmnopqrstuv', }); - op = await icpResult.op(); - assert.equal(op['done'], true); + op = await waitOperation(client1, await icpResult.op()); const aid2 = op['response']; const icp2 = new signify.Serder(aid2); assert.equal(icp2.pre, 'EP10ooRj0DJF0HWZePEYMLPl-arMV-MAoTKK-o3DXbgX'); @@ -119,8 +119,7 @@ test('salty', async () => { assert.equal(aid.name, 'aid3'); icpResult = await client1.identifiers().rotate('aid1'); - op = await icpResult.op(); - assert.equal(op['done'], true); + op = await waitOperation(client1, await icpResult.op()); let ked = op['response']; const rot = new signify.Serder(ked); assert.equal(rot.ked['d'], 'EBQABdRgaxJONrSLcgrdtbASflkvLxJkiDO0H-XmuhGg'); @@ -137,8 +136,7 @@ test('salty', async () => { ); icpResult = await client1.identifiers().interact('aid1', [icp.pre]); - op = await icpResult.op(); - assert.equal(op['done'], true); + op = await waitOperation(client1, await icpResult.op()); ked = op['response']; const ixn = new signify.Serder(ked); assert.equal(ixn.ked['d'], 'ENsmRAg_oM7Hl1S-GTRMA7s4y760lQMjzl0aqOQ2iTce'); diff --git a/examples/integration-scripts/single-issuer-chained-credential.test.ts b/examples/integration-scripts/single-issuer-chained-credential.test.ts index 170f112c..092f76a1 100644 --- a/examples/integration-scripts/single-issuer-chained-credential.test.ts +++ b/examples/integration-scripts/single-issuer-chained-credential.test.ts @@ -1,7 +1,12 @@ import assert from 'node:assert'; import signify, { SignifyClient, Saider } from 'signify-ts'; import { resolveEnvironment } from './utils/resolve-env'; -import { resolveOobi, wait, waitOperation } from './utils/test-util'; +import { + resolveOobi, + waitForNotifications, + waitOperation, +} from './utils/test-util'; +import { retry } from './utils/retry'; const { bootUrl, url, vleiServerUrl } = resolveEnvironment(); @@ -40,8 +45,7 @@ async function createIdentifier( toad: witnesses.length, wits: witnesses, }); - const op = await icpResult1.op(); - await waitOperation(client, op, 15000); + await waitOperation(client, await icpResult1.op()); const aid = await client.identifiers().get(name); if (!client.agent) { @@ -67,8 +71,7 @@ async function createRegistry( registryName: string ) { const result = await client.registries().create({ name, registryName }); - const op = await result.op(); - await waitOperation(client, op, 5000); + await waitOperation(client, await result.op()); const registries = await client.registries().list(name); assert.equal(registries.length, 1); @@ -99,7 +102,7 @@ async function issueCredential( source: args.source && Saider.saidify({ d: '', ...args.source })[1], }); - await waitOperation(client, result.op, 10000); + await waitOperation(client, result.op); const creds = await client.credentials().list(); const dt = createTimestamp(); @@ -117,29 +120,6 @@ async function issueCredential( return creds[0]; } -interface Notification { - i: string; - dt: string; - r: boolean; - a: { r: string; d?: string; m?: string }; -} - -async function waitForNotification( - client: SignifyClient, - route: string -): Promise { - return wait(async () => { - const notifications = await client.notifications().list(); - for (const notif of notifications.notes) { - if (notif.a.r == route) { - return notif; - } - } - - throw new Error('No notification'); - }); -} - async function admitCredential( client: SignifyClient, name: string, @@ -194,8 +174,8 @@ test('single issuer chained credentials', async () => { }, }); - await waitOperation(issuerClient, result.op, 5); - const sourceCredential = await wait(async () => { + await waitOperation(issuerClient, result.op); + const sourceCredential = await retry(async () => { const result = await issuerClient.credentials().list(); assert(result.length >= 1); return result[0]; @@ -225,7 +205,7 @@ test('single issuer chained credentials', async () => { }, }); - const grantNotification = await waitForNotification( + const grantNotifications = await waitForNotifications( holderClient, '/exn/ipex/grant' ); @@ -233,25 +213,22 @@ test('single issuer chained credentials', async () => { await admitCredential( holderClient, 'holder', - grantNotification.a.d!, + grantNotifications[0].a.d!, issuerPrefix ); - await holderClient.notifications().mark(grantNotification.i); + await holderClient.notifications().mark(grantNotifications[0].i); - const holderCredential = await wait( - async () => { - const creds = await holderClient.credentials().list(); - const lei = creds.find( - (cred: { schema: { $id: string } }) => - cred.schema.$id === LE_SCHEMA_SAID - ); + const holderCredential = await retry(async () => { + const creds = await holderClient.credentials().list(); + const lei = creds.find( + (cred: { schema: { $id: string } }) => + cred.schema.$id === LE_SCHEMA_SAID + ); - expect(lei).toBeDefined(); - return lei; - }, - { timeout: 10000 } - ); + expect(lei).toBeDefined(); + return lei; + }); expect(holderCredential).toMatchObject({ sad: { a: { LEI: '5493001KJTIIGC8Y1R17' } }, diff --git a/examples/integration-scripts/single-issuer-holder.test.ts b/examples/integration-scripts/single-issuer-holder.test.ts index e65b45ee..b9b5e877 100644 --- a/examples/integration-scripts/single-issuer-holder.test.ts +++ b/examples/integration-scripts/single-issuer-holder.test.ts @@ -2,13 +2,14 @@ import assert from 'node:assert'; import signify, { SignifyClient, IssueCredentialArgs, - Operation, Serder, } from 'signify-ts'; import { resolveEnvironment } from './utils/resolve-env'; +import { waitForNotifications, waitOperation } from './utils/test-util'; +import { retry } from './utils/retry'; const SCHEMA_SAID = 'EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao'; -const { bootUrl, url, vleiServerUrl, witnessUrls } = resolveEnvironment(); +const { bootUrl, url, vleiServerUrl, witnessIds } = resolveEnvironment(); function createTimestamp() { const dt = new Date().toISOString().replace('Z', '000+00:00'); @@ -39,7 +40,7 @@ async function createIdentifier( wits: witnesses, }); const op = await icpResult1.op(); - await waitOperation(client, op, 5000); + await waitOperation(client, op); const aid = await client.identifiers().get(name); if (!client.agent) { @@ -62,7 +63,7 @@ async function getAgentOobi( async function resolveOobi(client: SignifyClient, oobi: string, alias: string) { console.log(`Resolve ${alias} -> ${oobi}`); const op = await client.oobis().resolve(oobi, alias); - const result = await waitOperation<{ i: string }>(client, op, 5000); + const result = await waitOperation<{ i: string }>(client, op); return result.response; } @@ -73,7 +74,7 @@ async function createRegistry( ) { const result = await client.registries().create({ name, registryName }); const op = await result.op(); - await waitOperation(client, op, 5000); + await waitOperation(client, op); const registries = await client.registries().list(name); assert.equal(registries.length, 1); @@ -88,7 +89,7 @@ async function issueCredential( ) { const result = await client.credentials().issue(args); - await waitOperation(client, result.op, 5000); + await waitOperation(client, result.op); const creds = await client.credentials().list(); assert.equal(creds.length, 1); @@ -147,30 +148,6 @@ async function grantCredential( await client.ipex().submitGrant(issuerName, grant, gsigs, end, [recipient]); } -interface Notification { - i: string; - dt: string; - r: boolean; - a: { r: string; d?: string; m?: string }; -} - -async function waitForNotification( - client: SignifyClient, - route: string -): Promise { - // eslint-disable-next-line no-constant-condition - while (true) { - const notifications = await client.notifications().list(); - for (const notif of notifications.notes) { - if (notif.a.r == route) { - return notif; - } - } - - await new Promise((resolve) => setTimeout(resolve, 1000)); - } -} - async function admitCredential( client: SignifyClient, name: string, @@ -184,52 +161,6 @@ async function admitCredential( await client.ipex().submitAdmit(name, admit, sigs, end, [recipient]); } -async function wait(fn: () => Promise, timeout: number = 10000) { - const start = Date.now(); - const errors: Error[] = []; - while (Date.now() - start < timeout) { - try { - const result = await fn(); - return result; - } catch (error) { - errors.push(error as Error); - await new Promise((resolve) => setTimeout(resolve, 1000)); - } - } - - throw new RetryError(`Retry failed after ${Date.now() - start} ms`, errors); -} - -async function waitOperation( - client: SignifyClient, - op: Operation, - timeout: number = 30000 -): Promise> { - const start = Date.now(); - while (Date.now() - start < timeout) { - const current = (await client - .operations() - .get(op.name)) as Operation; - - if (current.done) { - return current; - } - - await new Promise((resolve) => setTimeout(resolve, 1000)); - } - - throw new Error(`Operation timed out after ${Date.now() - start}ms`); -} - -class RetryError extends Error { - constructor( - message: string, - public errors: Error[] - ) { - super(message); - } -} - test( 'Single issuer holder', async () => { @@ -242,54 +173,21 @@ test( await holderClient.state(); await verifierClient.state(); - const issuerWits = await Promise.all( - witnessUrls.map(async (url, i) => { - const result = await resolveOobi( - issuerClient, - url + '/oobi', - `witness-${i}` - ); - return result.i; - }) - ); - - const holderWits = await Promise.all( - witnessUrls.map(async (url, i) => { - const result = await resolveOobi( - holderClient, - url + '/oobi', - `witness-${i}` - ); - return result.i; - }) - ); - - const verifierWits = await Promise.all( - witnessUrls.map(async (url, i) => { - const result = await resolveOobi( - verifierClient, - url + '/oobi', - `witness-${i}` - ); - return result.i; - }) - ); - // Create two identifiers, one for each client const issuerPrefix = await createIdentifier( issuerClient, 'issuer', - issuerWits + witnessIds ); const holderPrefix = await createIdentifier( holderClient, 'holder', - holderWits + witnessIds ); const verifierPrefix = await createIdentifier( verifierClient, 'verifier', - verifierWits + witnessIds ); // Exchange OOBIs @@ -329,7 +227,7 @@ test( }, }); - const grantNotification = await waitForNotification( + const grantNotifications = await waitForNotifications( holderClient, '/exn/ipex/grant' ); @@ -337,13 +235,13 @@ test( await admitCredential( holderClient, 'holder', - grantNotification.a.d!, + grantNotifications[0].a.d!, issuerPrefix ); - await holderClient.notifications().mark(grantNotification.i); + await holderClient.notifications().mark(grantNotifications[0].i); - const c = await wait(async () => { + const c = await retry(async () => { const creds = await holderClient.credentials().list(); assert(creds.length >= 1); return creds[0]; @@ -371,25 +269,27 @@ test( cred['issatc'] ); - const verifierGrantNotification = await waitForNotification( + const verifierGrantNotifications = await waitForNotifications( verifierClient, '/exn/ipex/grant' ); console.log( - `Notifcation of grant received by verifier ${verifierGrantNotification.a.d}` + `Notifcation of grant received by verifier ${verifierGrantNotifications[0].a.d}` ); await admitCredential( verifierClient, 'verifier', - verifierGrantNotification.a.d!, + verifierGrantNotifications[0].a.d!, holderPrefix ); - await verifierClient.notifications().mark(verifierGrantNotification.i); + await verifierClient + .notifications() + .mark(verifierGrantNotifications[0].i); console.log('Checking for credential'); - const p = await wait(async () => { + const p = await retry(async () => { const creds = await verifierClient.credentials().list(); assert(creds.length >= 1); return creds[0]; diff --git a/examples/integration-scripts/utils/retry.ts b/examples/integration-scripts/utils/retry.ts new file mode 100644 index 00000000..4af84450 --- /dev/null +++ b/examples/integration-scripts/utils/retry.ts @@ -0,0 +1,53 @@ +import { setTimeout } from 'timers/promises'; + +export interface RetryOptions { + maxSleep?: number; + minSleep?: number; + maxRetries?: number; + timeout?: number; + signal?: AbortSignal; +} + +export async function retry( + fn: () => Promise, + options: RetryOptions = {} +): Promise { + const { + maxSleep = 1000, + minSleep = 10, + maxRetries, + timeout = 10000, + } = options; + + const increaseFactor = 50; + + let retries = 0; + let cause: Error | null = null; + const start = Date.now(); + + while ( + (options.signal === undefined || options.signal.aborted === false) && + Date.now() - start < timeout && + (maxRetries === undefined || retries < maxRetries) + ) { + try { + const result = await fn(); + return result; + } catch (err) { + cause = err as Error; + const delay = Math.max( + minSleep, + Math.min(maxSleep, 2 ** retries * increaseFactor) + ); + retries++; + await setTimeout(delay, undefined, { signal: options.signal }); + } + } + + if (!cause) { + cause = new Error(`Failed after ${retries} attempts`); + } + + Object.assign(cause, { retries, maxAttempts: maxRetries }); + throw cause; +} diff --git a/examples/integration-scripts/utils/test-util.ts b/examples/integration-scripts/utils/test-util.ts index a7952798..354bf2ba 100644 --- a/examples/integration-scripts/utils/test-util.ts +++ b/examples/integration-scripts/utils/test-util.ts @@ -1,4 +1,5 @@ import { Operation, SignifyClient } from 'signify-ts'; +import { RetryOptions, retry } from './retry'; export function sleep(ms: number): Promise { return new Promise((resolve) => { @@ -9,18 +10,20 @@ export function sleep(ms: number): Promise { /** * Poll for operation to become completed */ -export async function waitOperation( +export async function waitOperation( client: SignifyClient, op: Operation, - retries: number = 10 + options: RetryOptions = {} ): Promise> { - const WAIT = 500; // 0.5 seconds - while (retries-- > 0) { + return retry(async () => { op = await client.operations().get(op.name); - if (op.done === true) return op; - await sleep(WAIT); - } - throw new Error(`Timeout: operation ${op.name}`); + + if (op.done !== true) { + throw new Error(`Operation ${op.name} not done`); + } + + return op; + }, options); } export async function resolveOobi( @@ -32,36 +35,31 @@ export async function resolveOobi( await waitOperation(client, op); } -export interface WaitOptions { - timeout?: number; +export interface Notification { + i: string; + dt: string; + r: boolean; + a: { r: string; d?: string; m?: string }; } -/** - * Retry fn until timeout, throws the last caught excetion after timeout passed - */ -export async function wait( - fn: () => Promise, - options: WaitOptions = {} -): Promise { - const start = Date.now(); - const timeout = options.timeout ?? 10000; - let error: Error | null = null; +export async function waitForNotifications( + client: SignifyClient, + route: string, + options: RetryOptions = {} +): Promise { + return retry(async () => { + const response: { notes: Notification[] } = await client + .notifications() + .list(); + + const notes = response.notes.filter( + (note) => note.a.r === route && note.r === false + ); - while (Date.now() - start < timeout) { - try { - const result = await fn(); - return result; - } catch (err) { - error = err as Error; - if (Date.now() - start < timeout) { - await new Promise((resolve) => setTimeout(resolve, 1000)); - } + if (!notes.length) { + throw new Error(`No notifications with route ${route}`); } - } - if (error) { - throw error; - } else { - throw new Error(`Timeout after ${Date.now() - start}ms`); - } + return notes; + }, options); } diff --git a/examples/integration-scripts/witness.test.ts b/examples/integration-scripts/witness.test.ts index 996d5fdf..b7b39a3e 100644 --- a/examples/integration-scripts/witness.test.ts +++ b/examples/integration-scripts/witness.test.ts @@ -2,6 +2,7 @@ import { strict as assert } from 'assert'; import signify from 'signify-ts'; import { resolveEnvironment } from './utils/resolve-env'; +import { resolveOobi, waitOperation } from './utils/test-util'; const WITNESS_AID = 'BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha'; const { url, bootUrl, witnessUrls } = resolveEnvironment(); @@ -27,13 +28,7 @@ test('test witness', async () => { ); // Client 1 resolves witness OOBI - let op1 = await client1 - .oobis() - .resolve(witnessUrls[0] + `/oobi/${WITNESS_AID}`, 'wit'); - while (!op1['done']) { - op1 = await client1.operations().get(op1.name); - await new Promise((resolve) => setTimeout(resolve, 1000)); - } + await resolveOobi(client1, witnessUrls[0] + `/oobi/${WITNESS_AID}`, 'wit'); console.log('Witness OOBI resolved'); // Client 1 creates AID with 1 witness @@ -41,22 +36,14 @@ test('test witness', async () => { toad: 1, wits: [WITNESS_AID], }); - op1 = await icpResult1.op(); - while (!op1['done']) { - op1 = await client1.operations().get(op1.name); - await new Promise((resolve) => setTimeout(resolve, 1000)); - } + await waitOperation(client1, await icpResult1.op()); let aid1 = await client1.identifiers().get('aid1'); console.log('AID:', aid1.prefix); assert.equal(aid1.state.b.length, 1); assert.equal(aid1.state.b[0], WITNESS_AID); icpResult1 = await client1.identifiers().rotate('aid1'); - op1 = await icpResult1.op(); - while (!op1['done']) { - op1 = await client1.operations().get(op1.name); - await new Promise((resolve) => setTimeout(resolve, 1000)); - } + await waitOperation(client1, await icpResult1.op()); aid1 = await client1.identifiers().get('aid1'); assert.equal(aid1.state.b.length, 1); assert.equal(aid1.state.b[0], WITNESS_AID); @@ -65,12 +52,7 @@ test('test witness', async () => { icpResult1 = await client1 .identifiers() .rotate('aid1', { cuts: [WITNESS_AID] }); - - op1 = await icpResult1.op(); - while (!op1['done']) { - op1 = await client1.operations().get(op1.name); - await new Promise((resolve) => setTimeout(resolve, 1000)); - } + await waitOperation(client1, await icpResult1.op()); aid1 = await client1.identifiers().get('aid1'); assert.equal(aid1.state.b.length, 0); @@ -80,11 +62,7 @@ test('test witness', async () => { .identifiers() .rotate('aid1', { adds: [WITNESS_AID] }); - op1 = await icpResult1.op(); - while (!op1['done']) { - op1 = await client1.operations().get(op1.name); - await new Promise((resolve) => setTimeout(resolve, 1000)); - } + await waitOperation(client1, await icpResult1.op()); aid1 = await client1.identifiers().get('aid1'); assert.equal(aid1.state.b.length, 1); assert.equal(aid1.state.b.length, 1);