diff --git a/src/client/tests/webapp/auto-verifier.test.ts b/src/client/tests/webapp/auto-verifier.test.ts index b0d77b9..3e3a872 100644 --- a/src/client/tests/webapp/auto-verifier.test.ts +++ b/src/client/tests/webapp/auto-verifier.test.ts @@ -105,21 +105,21 @@ describe('getChanges tests', () => { describe('constructor/factory tests', () => { test('constructor', () => { - const av = new AutoVerifier(jest.fn(), jest.fn(), jest.fn(), jest.fn()); + const av = new AutoVerifier(jest.fn(), jest.fn(), jest.fn()); expect(av.primaryPromise).toBe(null); expect(av.trustedPromise).toBe(null); expect(av.trustedStatus).toBe('none') }); test('createStandardListAutoVerifier', () => { - const av = AutoVerifier.createStandardListAutoVerifier(jest.fn(), jest.fn(), jest.fn(), jest.fn()); + const av = AutoVerifier.createStandardListAutoVerifier(jest.fn(), jest.fn(), jest.fn()); expect(av.primaryPromise).toBe(null); expect(av.trustedPromise).toBe(null); expect(av.trustedStatus).toBe('none') }); test('createStandardSingleAutoVerifier', () => { - const av = AutoVerifier.createStandardListAutoVerifier(jest.fn(), jest.fn(), jest.fn(), jest.fn()); + const av = AutoVerifier.createStandardListAutoVerifier(jest.fn(), jest.fn(), jest.fn()); expect(av.primaryPromise).toBe(null); expect(av.trustedPromise).toBe(null); expect(av.trustedStatus).toBe('none') @@ -145,34 +145,28 @@ describe('fetchAndVerifyIfNeeded tests', () => { function getManualsAndMocks(): { nextPrimary: () => ManualPromise, nextTrusted: () => ManualPromise, - nextEnsureTrustedFuncReady: () => ManualPromise, nextVerify: () => ManualPromise, primaryFunc: jest.Mock, []>, trustedFunc: jest.Mock, []>, - ensureTrustedFuncReadyFunc: jest.Mock, []>, verifyFunc: jest.Mock, [primaryResult: BasicWE | null, trustedResult: BasicWE | null]>, } { const manuals: { primary: ManualPromise[], trusted: ManualPromise[], - ensureTrustedFuncReady: ManualPromise[], verify: ManualPromise[], } = { primary: [], trusted: [], - ensureTrustedFuncReady: [], verify: [], }; const calls: { primary: number, trusted: number, - ensureTrustedFuncReady: number, verify: number, } = { primary: 0, trusted: 0, - ensureTrustedFuncReady: 0, verify: 0 }; @@ -185,11 +179,9 @@ describe('fetchAndVerifyIfNeeded tests', () => { return { nextPrimary: () => getNext(manuals.primary), nextTrusted: () => getNext(manuals.trusted), - nextEnsureTrustedFuncReady: () => getNext(manuals.ensureTrustedFuncReady), nextVerify: () => getNext(manuals.verify), primaryFunc: jest.fn(() => manuals.primary[calls.primary++]!.promise), trustedFunc: jest.fn(() => manuals.trusted[calls.trusted++]!.promise), - ensureTrustedFuncReadyFunc: jest.fn(() => manuals.ensureTrustedFuncReady[calls.ensureTrustedFuncReady++]!.promise), verifyFunc: jest.fn((_primaryResult: BasicWE | null, _trustedResult: BasicWE | null) => manuals.verify[calls.verify++]!.promise), } } @@ -198,21 +190,18 @@ describe('fetchAndVerifyIfNeeded tests', () => { av: AutoVerifier, nextPrimary: () => ManualPromise, nextTrusted: () => ManualPromise, - nextEnsureTrustedFuncReady: () => ManualPromise, nextVerify: () => ManualPromise, lazyVerify: boolean = false, ) { const state: { primary: ManualPromise, trusted: ManualPromise, - ensureTrustedFuncReady: ManualPromise, verify: ManualPromise, result: T | null | undefined, rejection: Error | undefined, } = { primary: nextPrimary(), trusted: nextTrusted(), - ensureTrustedFuncReady: nextEnsureTrustedFuncReady(), verify: nextVerify(), result: undefined, rejection: undefined, @@ -231,16 +220,15 @@ describe('fetchAndVerifyIfNeeded tests', () => { test('primary null, then trusted with value - cache miss, return from server', async () => { const { - nextPrimary, nextTrusted, nextEnsureTrustedFuncReady, nextVerify, - primaryFunc, trustedFunc, ensureTrustedFuncReadyFunc, verifyFunc + nextPrimary, nextTrusted, nextVerify, + primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); const primary = nextPrimary(); const trusted = nextTrusted(); - const ensureTrustedFuncReady = nextEnsureTrustedFuncReady(); const verify = nextVerify(); - const av = new AutoVerifier(primaryFunc, trustedFunc, ensureTrustedFuncReadyFunc, verifyFunc); + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); const resultPromise = av.fetchAndVerifyIfNeeded(); let result: BasicWE | null | undefined = undefined; resultPromise.then(value => { result = value; }); @@ -251,18 +239,9 @@ describe('fetchAndVerifyIfNeeded tests', () => { expect(primaryFunc).toHaveBeenCalled(); expect(trustedFunc).toHaveBeenCalled(); - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalledTimes(0); primary.resolve(null); await disjoint(); - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalled(); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verifying'); - - ensureTrustedFuncReady.resolve(); - await disjoint(); - expect(av.primaryPromise).toBe(null); expect(av.trustedPromise).toBe(trusted.promise); expect(av.trustedStatus).toBe('verifying'); @@ -289,22 +268,20 @@ describe('fetchAndVerifyIfNeeded tests', () => { expect(primaryFunc).toHaveBeenCalledTimes(1); expect(trustedFunc).toHaveBeenCalledTimes(1); - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalledTimes(1); expect(verifyFunc).toHaveBeenCalledTimes(1); }); test('primary with value, then trusted with value - cache hit, verify with server', async () => { const { - nextPrimary, nextTrusted, nextEnsureTrustedFuncReady, nextVerify, - primaryFunc, trustedFunc, ensureTrustedFuncReadyFunc, verifyFunc + nextPrimary, nextTrusted, nextVerify, + primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); const primary = nextPrimary(); const trusted = nextTrusted(); - const ensureTrustedFuncReady = nextEnsureTrustedFuncReady(); const verify = nextVerify(); - const av = new AutoVerifier(primaryFunc, trustedFunc, ensureTrustedFuncReadyFunc, verifyFunc); + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); const resultPromise = av.fetchAndVerifyIfNeeded(); let result: BasicWE | null | undefined = undefined; resultPromise.then(value => { result = value; }); @@ -316,23 +293,14 @@ describe('fetchAndVerifyIfNeeded tests', () => { expect(trustedFunc).toHaveBeenCalled(); // TODO: Is this the source of the problem? - trustedFunc needs to wait to be called until ensureTrustedFuncReadyFunc is resolved expect(result).toBeUndefined(); - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalledTimes(0); primary.resolve(new BasicWE(2)); await disjoint(); expect(result).toEqual(new BasicWE(2)); - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalled(); expect(av.primaryPromise).toBe(null); expect(av.trustedPromise).toBe(trusted.promise); expect(av.trustedStatus).toBe('verifying'); - ensureTrustedFuncReady.resolve(); - await disjoint(); - - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verifying'); - expect(verifyFunc).toHaveBeenCalledTimes(0); trusted.resolve(new BasicWE(2)); await disjoint(); @@ -353,22 +321,20 @@ describe('fetchAndVerifyIfNeeded tests', () => { expect(primaryFunc).toHaveBeenCalledTimes(1); expect(trustedFunc).toHaveBeenCalledTimes(1); - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalledTimes(1); expect(verifyFunc).toHaveBeenCalledTimes(1); }); test('primary null, then trusted with null - cache miss, server confirms miss', async () => { const { - nextPrimary, nextTrusted, nextEnsureTrustedFuncReady, nextVerify, - primaryFunc, trustedFunc, ensureTrustedFuncReadyFunc, verifyFunc + nextPrimary, nextTrusted, nextVerify, + primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); const primary = nextPrimary(); const trusted = nextTrusted(); - const ensureTrustedFuncReady = nextEnsureTrustedFuncReady(); const verify = nextVerify(); - const av = new AutoVerifier(primaryFunc, trustedFunc, ensureTrustedFuncReadyFunc, verifyFunc); + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); const resultPromise = av.fetchAndVerifyIfNeeded(); let result: BasicWE | null | undefined = undefined; resultPromise.then(value => { result = value; }); @@ -379,22 +345,13 @@ describe('fetchAndVerifyIfNeeded tests', () => { expect(primaryFunc).toHaveBeenCalled(); expect(trustedFunc).toHaveBeenCalled(); - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalledTimes(0); primary.resolve(null); await disjoint(); - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalled(); expect(av.primaryPromise).toBe(null); expect(av.trustedPromise).toBe(trusted.promise); expect(av.trustedStatus).toBe('verifying'); - ensureTrustedFuncReady.resolve(); - await disjoint(); - - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verifying'); - expect(verifyFunc).toHaveBeenCalledTimes(0); trusted.resolve(null); await disjoint(); @@ -407,7 +364,7 @@ describe('fetchAndVerifyIfNeeded tests', () => { expect(result).toBeUndefined(); verify.resolve(true); await disjoint(); - + expect(result).toBeNull(); expect(av.primaryPromise).toBe(null); expect(av.trustedPromise).toBe(trusted.promise); @@ -417,22 +374,20 @@ describe('fetchAndVerifyIfNeeded tests', () => { expect(primaryFunc).toHaveBeenCalledTimes(1); expect(trustedFunc).toHaveBeenCalledTimes(1); - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalledTimes(1); expect(verifyFunc).toHaveBeenCalledTimes(1); }); test('primary with value, then trusted with null - cache hit, server deleted', async () => { const { - nextPrimary, nextTrusted, nextEnsureTrustedFuncReady, nextVerify, - primaryFunc, trustedFunc, ensureTrustedFuncReadyFunc, verifyFunc + nextPrimary, nextTrusted, nextVerify, + primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); const primary = nextPrimary(); const trusted = nextTrusted(); - const ensureTrustedFuncReady = nextEnsureTrustedFuncReady(); const verify = nextVerify(); - const av = new AutoVerifier(primaryFunc, trustedFunc, ensureTrustedFuncReadyFunc, verifyFunc); + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); const resultPromise = av.fetchAndVerifyIfNeeded(); let result: BasicWE | null | undefined = undefined; resultPromise.then(value => { result = value; }); @@ -443,24 +398,15 @@ describe('fetchAndVerifyIfNeeded tests', () => { expect(primaryFunc).toHaveBeenCalled(); expect(trustedFunc).toHaveBeenCalled(); - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalledTimes(0); expect(result).toBeUndefined(); primary.resolve(new BasicWE(2)); await disjoint(); - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalled(); expect(result).toEqual(new BasicWE(2)); expect(av.primaryPromise).toBe(null); expect(av.trustedPromise).toBe(trusted.promise); expect(av.trustedStatus).toBe('verifying'); - ensureTrustedFuncReady.resolve(); - await disjoint(); - - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verifying'); - expect(verifyFunc).toHaveBeenCalledTimes(0); trusted.resolve(null); await disjoint(); @@ -481,22 +427,20 @@ describe('fetchAndVerifyIfNeeded tests', () => { expect(primaryFunc).toHaveBeenCalledTimes(1); expect(trustedFunc).toHaveBeenCalledTimes(1); - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalledTimes(1); expect(verifyFunc).toHaveBeenCalledTimes(1); }); test('primary with null, lazy - lazy cache miss, need to ping server', async () => { const { - nextPrimary, nextTrusted, nextEnsureTrustedFuncReady, nextVerify, - primaryFunc, trustedFunc, ensureTrustedFuncReadyFunc, verifyFunc + nextPrimary, nextTrusted, nextVerify, + primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); const primary = nextPrimary(); const trusted = nextTrusted(); - const ensureTrustedFuncReady = nextEnsureTrustedFuncReady(); const verify = nextVerify(); - const av = new AutoVerifier(primaryFunc, trustedFunc, ensureTrustedFuncReadyFunc, verifyFunc); + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); const resultPromise = av.fetchAndVerifyIfNeeded(true); let result: BasicWE | null | undefined = undefined; resultPromise.then(value => { result = value; }); @@ -507,23 +451,14 @@ describe('fetchAndVerifyIfNeeded tests', () => { expect(primaryFunc).toHaveBeenCalled(); expect(trustedFunc).toHaveBeenCalledTimes(0); - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalledTimes(0); primary.resolve(null); await disjoint(); expect(trustedFunc).toHaveBeenCalled(); - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalled(); expect(av.primaryPromise).toBe(null); expect(av.trustedPromise).toBe(trusted.promise); expect(av.trustedStatus).toBe('verifying'); // notably, we will never publicly see it in 'fetching' - ensureTrustedFuncReady.resolve(); - await disjoint(); - - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verifying'); - expect(verifyFunc).toHaveBeenCalledTimes(0); trusted.resolve(new BasicWE(2)); await disjoint(); @@ -535,7 +470,6 @@ describe('fetchAndVerifyIfNeeded tests', () => { expect(primaryFunc).toHaveBeenCalledTimes(1); expect(trustedFunc).toHaveBeenCalledTimes(1); - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalledTimes(1); expect(verifyFunc).toHaveBeenCalledTimes(1); expect(result).toBeUndefined(); @@ -551,19 +485,18 @@ describe('fetchAndVerifyIfNeeded tests', () => { expect(primaryFunc).toHaveBeenCalledTimes(1); expect(trustedFunc).toHaveBeenCalledTimes(1); - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalledTimes(1); expect(verifyFunc).toHaveBeenCalledTimes(1); }); test('primary with value, lazy - lazy cache hit, no need to ping server', async () => { const { nextPrimary, - primaryFunc, trustedFunc, ensureTrustedFuncReadyFunc, verifyFunc + primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); const primary = nextPrimary(); - const av = new AutoVerifier(primaryFunc, trustedFunc, ensureTrustedFuncReadyFunc, verifyFunc); + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); const resultPromise = av.fetchAndVerifyIfNeeded(true); let result: BasicWE | null | undefined = undefined; resultPromise.then(value => { result = value; }); @@ -584,7 +517,6 @@ describe('fetchAndVerifyIfNeeded tests', () => { expect(primaryFunc).toHaveBeenCalledTimes(1); expect(trustedFunc).toHaveBeenCalledTimes(0); - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalledTimes(0); expect(verifyFunc).toHaveBeenCalledTimes(0); }); @@ -592,13 +524,13 @@ describe('fetchAndVerifyIfNeeded tests', () => { // expecting the promise to reject, the server to succeed, but the verify function to never be called const { nextPrimary, nextTrusted, - primaryFunc, trustedFunc, ensureTrustedFuncReadyFunc, verifyFunc + primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); const primary = nextPrimary(); const trusted = nextTrusted(); - const av = new AutoVerifier(primaryFunc, trustedFunc, ensureTrustedFuncReadyFunc, verifyFunc); + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); const resultPromise = av.fetchAndVerifyIfNeeded(); let rejection: Error | undefined = undefined; resultPromise.catch(value => { rejection = value; }); @@ -623,22 +555,20 @@ describe('fetchAndVerifyIfNeeded tests', () => { expect(primaryFunc).toHaveBeenCalledTimes(1); expect(trustedFunc).toHaveBeenCalledTimes(1); // notably, this server response will be thrown out - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalledTimes(0); expect(verifyFunc).toHaveBeenCalledTimes(0); }); test('primary with null, trusted rejects - cache miss, failed to ping server', async () => { // expect the promise to reject, but the verify function to never be called const { - nextPrimary, nextTrusted, nextEnsureTrustedFuncReady, - primaryFunc, trustedFunc, ensureTrustedFuncReadyFunc, verifyFunc + nextPrimary, nextTrusted, + primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); const primary = nextPrimary(); const trusted = nextTrusted(); - const ensureTrustedFuncReady = nextEnsureTrustedFuncReady(); - const av = new AutoVerifier(primaryFunc, trustedFunc, ensureTrustedFuncReadyFunc, verifyFunc); + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); const resultPromise = av.fetchAndVerifyIfNeeded(); let rejection: Error | undefined = undefined; resultPromise.catch(value => { rejection = value; }); @@ -649,18 +579,9 @@ describe('fetchAndVerifyIfNeeded tests', () => { expect(primaryFunc).toHaveBeenCalled(); expect(trustedFunc).toHaveBeenCalled(); - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalledTimes(0); primary.resolve(null); await disjoint(); - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalled(); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verifying'); - - ensureTrustedFuncReady.resolve(); - await disjoint(); - expect(av.primaryPromise).toBe(null); expect(av.trustedPromise).toBe(trusted.promise); expect(av.trustedStatus).toBe('verifying'); @@ -678,22 +599,20 @@ describe('fetchAndVerifyIfNeeded tests', () => { expect(primaryFunc).toHaveBeenCalledTimes(1); expect(trustedFunc).toHaveBeenCalledTimes(1); // notably, this server response will be thrown out - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalledTimes(1); expect(verifyFunc).toHaveBeenCalledTimes(0); }); test('primary with value, trusted rejects - cache hit, failed to ping server', async () => { // expect the promise to reject, but the verify function to never be called const { - nextPrimary, nextTrusted, nextEnsureTrustedFuncReady, - primaryFunc, trustedFunc, ensureTrustedFuncReadyFunc, verifyFunc + nextPrimary, nextTrusted, + primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); const primary = nextPrimary(); const trusted = nextTrusted(); - const ensureTrustedFuncReady = nextEnsureTrustedFuncReady(); - const av = new AutoVerifier(primaryFunc, trustedFunc, ensureTrustedFuncReadyFunc, verifyFunc); + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); const resultPromise = av.fetchAndVerifyIfNeeded(); let result: BasicWE | null | undefined = undefined; resultPromise.then(value => { result = value; }); @@ -705,19 +624,10 @@ describe('fetchAndVerifyIfNeeded tests', () => { expect(trustedFunc).toHaveBeenCalled(); expect(result).toBeUndefined(); - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalledTimes(0); primary.resolve(new BasicWE(2)); await disjoint(); expect(result).toEqual(new BasicWE(2)); - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalled(); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verifying'); - - ensureTrustedFuncReady.resolve(); - await disjoint(); - expect(av.primaryPromise).toBe(null); expect(av.trustedPromise).toBe(trusted.promise); expect(av.trustedStatus).toBe('verifying'); @@ -736,19 +646,18 @@ describe('fetchAndVerifyIfNeeded tests', () => { expect(primaryFunc).toHaveBeenCalledTimes(1); expect(trustedFunc).toHaveBeenCalledTimes(1); // notably, this server response will be thrown out - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalledTimes(1); expect(verifyFunc).toHaveBeenCalledTimes(0); }); test('ab, a: primary with value, b: primary dedup, ab: trusted dedup with value', async () => { const { - nextPrimary, nextTrusted, nextEnsureTrustedFuncReady, nextVerify, - primaryFunc, trustedFunc, ensureTrustedFuncReadyFunc, verifyFunc + nextPrimary, nextTrusted, nextVerify, + primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); - - const av = new AutoVerifier(primaryFunc, trustedFunc, ensureTrustedFuncReadyFunc, verifyFunc); - const a = fetchAndVerifyIfNeededManually(av, nextPrimary, nextTrusted, nextEnsureTrustedFuncReady, nextVerify); + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); + + const a = fetchAndVerifyIfNeededManually(av, nextPrimary, nextTrusted, nextVerify); expect(av.primaryPromise).toBe(a.primary.promise); expect(av.trustedPromise).toBe(a.trusted.promise); @@ -756,7 +665,7 @@ describe('fetchAndVerifyIfNeeded tests', () => { expect(primaryFunc).toHaveBeenCalled(); expect(trustedFunc).toHaveBeenCalled(); - const b = fetchAndVerifyIfNeededManually(av, nextPrimary, nextTrusted, nextEnsureTrustedFuncReady, nextVerify); + const b = fetchAndVerifyIfNeededManually(av, nextPrimary, nextTrusted, nextVerify); expect(av.primaryPromise).toBe(a.primary.promise); // doesn't change from a expect(av.trustedPromise).toBe(a.trusted.promise); // doesn't change from a @@ -773,13 +682,6 @@ describe('fetchAndVerifyIfNeeded tests', () => { expect(av.trustedPromise).toBe(a.trusted.promise); expect(av.trustedStatus).toBe('verifying'); - a.ensureTrustedFuncReady.resolve(); - await disjoint(); - - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(a.trusted.promise); - expect(av.trustedStatus).toBe('verifying'); - expect(verifyFunc).toHaveBeenCalledTimes(0); a.trusted.resolve(new BasicWE(2)); await disjoint(); @@ -791,7 +693,7 @@ describe('fetchAndVerifyIfNeeded tests', () => { a.verify.resolve(true); await disjoint(); - + expect(av.primaryPromise).toBe(null); expect(av.trustedPromise).toBe(a.trusted.promise); expect(av.trustedStatus).toBe('verified'); @@ -801,10 +703,65 @@ describe('fetchAndVerifyIfNeeded tests', () => { // Even though there were two fetchAnd... calls, they were deduped such that each func was only called once expect(primaryFunc).toHaveBeenCalledTimes(1); expect(trustedFunc).toHaveBeenCalledTimes(1); - expect(ensureTrustedFuncReadyFunc).toHaveBeenCalledTimes(1); expect(verifyFunc).toHaveBeenCalledTimes(1); }); + test('ab, a: primary with null, b: primary dedup, ab: trusted dedup with value', async () => { + const { + nextPrimary, nextTrusted, nextVerify, + primaryFunc, trustedFunc, verifyFunc + } = getManualsAndMocks(); + + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); + + const a = fetchAndVerifyIfNeededManually(av, nextPrimary, nextTrusted, nextVerify); + + expect(av.primaryPromise).toBe(a.primary.promise); + expect(av.trustedPromise).toBe(a.trusted.promise); + expect(av.trustedStatus).toBe('fetching'); + expect(primaryFunc).toHaveBeenCalled(); + expect(trustedFunc).toHaveBeenCalled(); + + const b = fetchAndVerifyIfNeededManually(av, nextPrimary, nextTrusted, nextVerify); + + expect(av.primaryPromise).toBe(a.primary.promise); // doesn't change from a + expect(av.trustedPromise).toBe(a.trusted.promise); // doesn't change from a + expect(av.trustedStatus).toBe('fetching'); + + a.primary.resolve(null); + await disjoint(); + + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(a.trusted.promise); + expect(av.trustedStatus).toBe('verifying'); + + expect(verifyFunc).toHaveBeenCalledTimes(0); + a.trusted.resolve(new BasicWE(2)); + await disjoint(); + + expect(verifyFunc).toHaveBeenCalledWith(null, new BasicWE(2)); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(a.trusted.promise); + expect(av.trustedStatus).toBe('verifying'); + + expect(a.result).toBeUndefined(); + expect(b.result).toBeUndefined(); + a.verify.resolve(true); + await disjoint(); + + expect(a.result).toEqual(new BasicWE(2)); + expect(b.result).toEqual(new BasicWE(2)); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(a.trusted.promise); + expect(av.trustedStatus).toBe('verified'); + + await disjoint(); // sanity check + + // Even though there were two fetchAnd... calls, they were deduped such that each func was only called once + expect(primaryFunc).toHaveBeenCalledTimes(1); + expect(trustedFunc).toHaveBeenCalledTimes(1); + expect(verifyFunc).toHaveBeenCalledTimes(1); + }); // Make sure to do try/catch errors/rejections, verification failures, unverifies in the middle // Multiple tryResolveTrustedPromises - multiple fetchAndVerifyIfNeeded at the same time // Trusted resolves *BEFORE* ensureTrustedFuncReady diff --git a/src/client/webapp/auto-verifier.ts b/src/client/webapp/auto-verifier.ts index f58973e..e004e25 100644 --- a/src/client/webapp/auto-verifier.ts +++ b/src/client/webapp/auto-verifier.ts @@ -26,6 +26,7 @@ export enum AutoVerifierChangesType { export class AutoVerifier { public primaryPromise: Promise | null = null; public trustedPromise: Promise | null = null; + public verifyPromise: Promise | null = null; public trustedStatus: 'none' | 'fetching' | 'verifying' | 'verified' = 'none'; private verifierId: string; @@ -39,7 +40,6 @@ export class AutoVerifier { constructor( private primaryFunc: () => Promise, private trustedFunc: () => Promise, - private ensureTrustedFuncReady: () => Promise, private verifyFunc: (primaryResult: T | null, trustedResult: T | null) => Promise, ) { this.verifierId = uuid.v4().slice(undefined, 4); @@ -123,13 +123,11 @@ export class AutoVerifier { static createStandardListAutoVerifier & { id: string }>( primaryFunc: () => Promise, trustedFunc: () => Promise, - ensureTrustedFuncReady: () => Promise, changesFunc: (changesType: AutoVerifierChangesType, changes: Changes) => Promise, ) { return new AutoVerifier( primaryFunc, trustedFunc, - ensureTrustedFuncReady, async (primaryResult: T[] | null, trustedResult: T[] | null) => { const changes = AutoVerifier.getChanges(primaryResult, trustedResult); const changesType = AutoVerifier.getListChangesType(primaryResult, trustedResult, changes); @@ -141,7 +139,6 @@ export class AutoVerifier { static createStandardSingleAutoVerifier>( primaryFunc: () => Promise, trustedFunc: () => Promise, - ensureTrustedFuncReady: () => Promise, changesFunc: ( changesType: AutoVerifierChangesType, primaryResult: T | null, @@ -151,7 +148,6 @@ export class AutoVerifier { return new AutoVerifier( primaryFunc, trustedFunc, - ensureTrustedFuncReady, async (primaryResult: T | null, trustedResult: T | null) => { const changesType = AutoVerifier.getSingleChangesType(primaryResult, trustedResult); return await changesFunc(changesType, primaryResult, trustedResult); @@ -172,8 +168,14 @@ export class AutoVerifier { console.warn('caught unverified trusted promise', e); }); } + if (this.verifyPromise) { + this.verifyPromise.catch(e => { + console.warn('caught unverified verify promise', e); + }); + } this.primaryPromise = null; this.trustedPromise = null; + this.verifyPromise = null; this.trustedStatus = 'none'; } @@ -181,62 +183,39 @@ export class AutoVerifier { // if the primary fetchable returns null but has not been verified yet, this will return the result of the trusted fetchable // if the trusted fetchable has not been used to verify the primary fetchable yet, this queries the trusted fetchable and calls verify // @param lazyVerify: set to true to only verify if primaryResult returns null (useful for fetching resources since they can never change) - // @param debug: print debug messages. This is useful if you (unfortunately) think there is a bug in this - async fetchAndVerifyIfNeeded(lazyVerify = false, debug = false): Promise { + async fetchAndVerifyIfNeeded(lazyVerify = false): Promise { return await new Promise( // eslint-disable-next-line no-async-promise-executor async (resolve: (result: T | null) => void, reject: (error: Error) => void) => { let resolved = false; - /* istanbul ignore next */ - const fetchId = debug && `v#${this.verifierId} f#${uuid.v4().slice(undefined, 4)}`; try { if (this.primaryPromise === null) { - /* istanbul ignore next */ - if (debug) LOG.debug(fetchId + ': created primary promise'); this.primaryPromise = this.primaryFunc(); } const origPrimaryPromise = this.primaryPromise; // pre-fetch the trusted result while we fetch the primary result if (!lazyVerify && this.trustedStatus === 'none') { - /* istanbul ignore next */ - if (debug) LOG.debug(fetchId + ": created trusted promise, set to 'fetching'"); this.trustedStatus = 'fetching'; - // TODO: The solution may be to merge the ensureTrustedFuncReady into the trustedPromise this.trustedPromise = this.trustedFunc(); } const primaryResult = await this.primaryPromise; if (this.primaryPromise === origPrimaryPromise) { // reset the primary promise so we create a new one next time - /* istanbul ignore next */ - if (debug) LOG.debug(fetchId + ': reset the primary promise for next time'); this.primaryPromise = null; } if (primaryResult) { - /* istanbul ignore next */ - if (debug) LOG.debug(fetchId + ': resolving with primary result'); resolve(primaryResult); resolved = true; if (lazyVerify || this.trustedStatus === 'verified') { - /* istanbul ignore next */ - if (debug) - LOG.debug(fetchId + ': not waiting on trusted promise', { - lazyVerify, - trustedStatus: this.trustedStatus, - }); return; } - } else { - /* istanbul ignore next */ - if (debug) LOG.debug(fetchId + ': waiting for trusted to resolve'); } if (this.trustedStatus === 'none') { // try to re-fetch the trusted result - /* istanbul ignore next */ - if (debug) LOG.debug(fetchId + ": creating trusted result (since status is 'none'"); this.trustedStatus = 'fetching'; this.trustedPromise = this.trustedFunc(); } @@ -249,12 +228,6 @@ export class AutoVerifier { // no one has started verifying the trusted yet this.trustedStatus = 'verifying'; - // TODO: I want to remove the ensureTrustedFuncReady func in favor - // of just including it yourself in the trustedFunc - /* istanbul ignore next */ - if (debug) LOG.debug(fetchId + ': verifying... (awaiting trustedPromise)'); - await this.ensureTrustedFuncReady(); - // if (debug) LOG.debug(fetchId + ': verifying...'); // note: Promises that have already resolved will return the same value when awaited again :) const origTrustedPromise: Promise | null = this.trustedPromise; @@ -263,17 +236,11 @@ export class AutoVerifier { if (this.trustedPromise !== origTrustedPromise) { // we've been invalidated while we were waiting for the trusted result! // TODO: This happens when a socket fetch is sent before the socket is connected to. - /* istanbul ignore next */ - if (debug) LOG.debug(fetchId + ': unverified during fetch!', new Error()); - /* istanbul ignore next */ - if (debug) LOG.debug(fetchId + ': trustedPromise now: ', { trustedPromise: this.trustedPromise }); console.warn( 'RARE ALERT: we got unverified while trying to fetch a trusted promise for verification!', new Error(), ); if (this.trustedPromise === null) { - /* istanbul ignore next */ - if (debug) LOG.debug(fetchId + ': re-fetching since trustedPromise was null'); this.trustedStatus = 'fetching'; this.trustedPromise = this.trustedFunc(); } @@ -283,19 +250,16 @@ export class AutoVerifier { // make sure to verify BEFORE potentially resolving // this way the conflicts can be resolved before the result is returned - const primaryUpToDate = await this.verifyFunc(primaryResult, trustedResult); + this.verifyPromise = this.verifyFunc(primaryResult, trustedResult); + const primaryUpToDate = await this.verifyPromise; if (this.trustedPromise === origTrustedPromise) { if (trustedResult !== null && primaryUpToDate) { // we got a good trusted result and the primary data has been updated // to reflect the trusted data (or already reflects it). - /* istanbul ignore next */ - if (debug) LOG.debug(fetchId + ': verified successfully.'); this.trustedStatus = 'verified'; } else { // we have to re-fetch the trusted promise again next fetch - /* istanbul ignore next */ - if (debug) LOG.debug(fetchId + ': needs trusted promise re-fetched next time'); this.trustedStatus = 'none'; } } else { @@ -307,8 +271,6 @@ export class AutoVerifier { if (!resolved) { // removed 01/09/2021 pretty sure should not be here... && trustedResult - /* istanbul ignore next */ - if (debug) LOG.debug(fetchId + ': resolving with trusted result'); resolve(trustedResult); resolved = true; } @@ -317,32 +279,33 @@ export class AutoVerifier { // await the same trusted promise and return its result if we didn't get a result // from the primary source. - /* istanbul ignore next */ - if (debug) LOG.debug(fetchId + ': waiting on result of a different verifier...'); - // note: Promises that have already resolved will return the same value when awaited again :) const origTrustedPromise: Promise | null = this.trustedPromise; const trustedResult = await origTrustedPromise; if (this.trustedPromise !== origTrustedPromise) { // we've been invalidated while we were waiting for the trusted result! - /* istanbul ignore next */ - if (debug) - LOG.debug( - fetchId + - ': got unverified while waiting on the result of a different verifier!', - new Error(), - ); - console.warn( - 'ULTRA RARE ALERT: we got unverified while awaiting a trusted promise another path was verifying!', - ); + console.warn( + 'ULTRA RARE ALERT: we got unverified while awaiting a trusted promise another path was verifying!', + ); + await tryResolveTrustedPromise(); + return; + } + + const origVerifyPromise: Promise | null = this.verifyPromise; + await origVerifyPromise; // we don't care about the result, just that we wait for the verification to finish + if (this.verifyPromise !== origVerifyPromise) { + // we've been invalidated while we were waiting for the trusted result! + console.warn( + 'ULTRA RARE ALERT: we got unverified while awaiting a verify promise another path was calling!', + origVerifyPromise, + this.verifyPromise, + ); await tryResolveTrustedPromise(); return; } if (!resolved) { - /* istanbul ignore next */ - if (debug) LOG.debug(fetchId + ': resolving with trusted result of different verifier'); resolve(trustedResult); resolved = true; } @@ -350,8 +313,6 @@ export class AutoVerifier { } else { // we are all up to date, make sure to resolve if primaryResult is null if (!resolved) { - /* istanbul ignore next */ - if (debug) LOG.debug(fetchId + ': no trusted promise, resolving with null'); resolve(null); resolved = true; } @@ -362,21 +323,13 @@ export class AutoVerifier { if (!resolved) { this.trustedPromise = null; // suppress warnings this.primaryPromise = null; // suppress warnings + this.verifyPromise = null; // suppress warnings this.unverify(); - /* istanbul ignore next */ - if (debug) LOG.debug(fetchId + ': error during fetch', e); // eslint-disable-next-line prefer-promise-reject-errors reject(e as Error); resolved = true; } else { this.unverify() - /* istanbul ignore next */ - if (debug) - LOG.debug( - fetchId + - ': server request failed after returning cache value (or we already rejected)', - e, - ); console.warn('server request failed after returning cache value (or when already rejected)', e); } }