diff --git a/src/client/tests/webapp/auto-verifier.test.ts b/src/client/tests/webapp/auto-verifier.test.ts index 46ff1ee..23efe40 100644 --- a/src/client/tests/webapp/auto-verifier.test.ts +++ b/src/client/tests/webapp/auto-verifier.test.ts @@ -199,17 +199,18 @@ describe('fetchAndVerifyIfNeeded tests', () => { verify: ManualPromise, result: T | null | undefined, rejection: Error | undefined, + resultPromise: Promise, } = { primary: nextPrimary(), trusted: nextTrusted(), verify: nextVerify(), result: undefined, rejection: undefined, + resultPromise: av.fetchAndVerifyIfNeeded(lazyVerify), } - const resultPromise = av.fetchAndVerifyIfNeeded(lazyVerify); - resultPromise.then((value) => { state.result = value; }); - resultPromise.catch((value) => { state.rejection = value; }); + state.resultPromise.then((value: T | null) => { state.result = value; }); + state.resultPromise.catch((value: Error) => { console.log('caught', value); state.rejection = value; }); return state; } @@ -588,7 +589,7 @@ describe('fetchAndVerifyIfNeeded tests', () => { expect(av.trustedStatus).toBe('verifying'); expect(rejection).toBeUndefined(); - trusted.reject(new Error()); + trusted.reject(new Error('rejection')); await disjoint(); expect(rejection).toBeDefined(); @@ -635,7 +636,7 @@ describe('fetchAndVerifyIfNeeded tests', () => { // suppress the warning about trusted rejecting after primary hit const cwSpy = jest.spyOn(console, 'warn').mockImplementationOnce(() => {}).mockReset(); // suppress the warning - trusted.reject(new Error()); + trusted.reject(new Error('rejection')); await disjoint(); expect(cwSpy).toHaveBeenCalledTimes(1); @@ -763,6 +764,60 @@ describe('fetchAndVerifyIfNeeded tests', () => { expect(trustedFunc).toHaveBeenCalledTimes(1); expect(verifyFunc).toHaveBeenCalledTimes(1); }); + + test('ab, a: primary with null, b: primary dedup, ab: trusted rejects', async () => { + const { + nextPrimary, nextTrusted, + primaryFunc, trustedFunc, verifyFunc + } = getManualsAndMocks(); + + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); + + const primary = nextPrimary(); + const trusted = nextTrusted(); + + const resultPromise1 = av.fetchAndVerifyIfNeeded(); + let rejection1: Error | undefined = undefined; + resultPromise1.catch(value => { rejection1 = value }); + + expect(av.primaryPromise).toBe(primary.promise); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('fetching'); + expect(primaryFunc).toHaveBeenCalled(); + expect(trustedFunc).toHaveBeenCalled(); + + const resultPromise2 = av.fetchAndVerifyIfNeeded(); + let rejection2: Error | undefined = undefined; + resultPromise2.catch(value => { rejection2 = value }); + + expect(av.primaryPromise).toBe(primary.promise); // doesn't change + expect(av.trustedPromise).toBe(trusted.promise); // doesn't change + expect(av.trustedStatus).toBe('fetching'); + + primary.resolve(null); + await disjoint(); + + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('verifying'); + + trusted.reject(new Error('rejection')); + await disjoint(); + + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(null); + expect(av.trustedStatus).toBe('none'); + expect(rejection1).toBeDefined(); + expect(rejection2).toBeDefined(); + expect(rejection1).toBe(rejection2); + + 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(0); + }); test('a: primary with null, b: primary re-fetch null, a: trusted with value, b: dedup', async () => { const { diff --git a/src/client/webapp/auto-verifier.ts b/src/client/webapp/auto-verifier.ts index d18c834..2a9133a 100644 --- a/src/client/webapp/auto-verifier.ts +++ b/src/client/webapp/auto-verifier.ts @@ -297,9 +297,13 @@ export class AutoVerifier { trustedResult = await origTrustedPromise; } catch (e: unknown) { if (this.trustedPromise === origTrustedPromise) { + // TODO: decide if can unverify here rather than in the "catch-all" + // maybe this will let us get rid of the catch all? throw e; } else { console.warn('trusted promise from another path rejected after unverify', e); + // TODO: only retry if there is another trusted promise available... + // OR always reject? await tryResolveTrustedPromise(); return; }