From 54195fdf14d0ec2b63e56f2d396927f6d402a853 Mon Sep 17 00:00:00 2001 From: Michael Peters Date: Thu, 6 Oct 2022 21:23:58 -0700 Subject: [PATCH] multi-request dedups with value --- src/client/tests/webapp/auto-verifier.test.ts | 101 +++++++++++++++++- src/client/webapp/auto-verifier.ts | 2 + 2 files changed, 101 insertions(+), 2 deletions(-) diff --git a/src/client/tests/webapp/auto-verifier.test.ts b/src/client/tests/webapp/auto-verifier.test.ts index 83ef042..b0d77b9 100644 --- a/src/client/tests/webapp/auto-verifier.test.ts +++ b/src/client/tests/webapp/auto-verifier.test.ts @@ -194,6 +194,37 @@ describe('fetchAndVerifyIfNeeded tests', () => { } } + function fetchAndVerifyIfNeededManually( + 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, + } + + const resultPromise = av.fetchAndVerifyIfNeeded(lazyVerify); + resultPromise.then((value) => { state.result = value; }); + resultPromise.catch((value) => { state.rejection = value; }); + + return state; + } + async function disjoint() { await Util.sleep(0); } @@ -709,7 +740,73 @@ describe('fetchAndVerifyIfNeeded tests', () => { 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 + } = getManualsAndMocks(); + + const av = new AutoVerifier(primaryFunc, trustedFunc, ensureTrustedFuncReadyFunc, verifyFunc); + + const a = fetchAndVerifyIfNeededManually(av, nextPrimary, nextTrusted, nextEnsureTrustedFuncReady, 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, nextEnsureTrustedFuncReady, 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'); + + expect(a.result).toBeUndefined(); + expect(b.result).toBeUndefined(); + a.primary.resolve(new BasicWE(2)); + 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('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(); + + expect(verifyFunc).toHaveBeenCalledWith(new BasicWE(2), new BasicWE(2)); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(a.trusted.promise); + expect(av.trustedStatus).toBe('verifying'); + + a.verify.resolve(true); + await disjoint(); + + 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(ensureTrustedFuncReadyFunc).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 + // Multiple tryResolveTrustedPromises - multiple fetchAndVerifyIfNeeded at the same time + // Trusted resolves *BEFORE* ensureTrustedFuncReady + // Fetching after verified doesn't re-fetch trusted }); diff --git a/src/client/webapp/auto-verifier.ts b/src/client/webapp/auto-verifier.ts index 07e2062..f58973e 100644 --- a/src/client/webapp/auto-verifier.ts +++ b/src/client/webapp/auto-verifier.ts @@ -249,6 +249,8 @@ 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();