diff --git a/archive/auto-verifier.test.ts b/archive/auto-verifier.test.ts new file mode 100644 index 0000000..8eab864 --- /dev/null +++ b/archive/auto-verifier.test.ts @@ -0,0 +1,729 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +import { AutoVerifier } from '../../webapp/auto-verifier'; + +function sleep(ms: number): Promise { + if (ms === 0) return; + return new Promise(resolve => { + setTimeout(resolve, ms); + }); +} + +// disjoint the current async function to allow other promises to be handled before the next lines are run +async function disjoint() { + await sleep(0); +} + +type QueryablePromise = Promise & { isPending: () => boolean; isRejected: () => boolean; isFulfilled: () => boolean }; + +/** + * this function allow you to modify a JS Promise by adding some status properties. + * Based on: http://stackoverflow.com/questions/21485545/is-there-a-way-to-tell-if-an-es6-promise-is-fulfilled-rejected-resolved + * But modified according to the specs of promises : https://promisesaplus.com/ + */ +function makeQuerablePromise(promise: Promise) { + // set initial state + let isPending = true; + let isRejected = false; + let isFulfilled = false; + + // observe the promise, saving the fulfillment in a closure scope. + const result = promise.then( + v => { + isFulfilled = true; + isPending = false; + return v; + }, + e => { + isRejected = true; + isPending = false; + throw e; + }, + ); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (result as any).isFulfilled = function () { + return isFulfilled; + }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (result as any).isPending = function () { + return isPending; + }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (result as any).isRejected = function () { + return isRejected; + }; + return result as QueryablePromise; +} + +class AutoVerifierTest { + static createSimple(primaryResult: T, trustedResult: T): AutoVerifier { + return new AutoVerifier( + async () => primaryResult, + async () => trustedResult, + async (_primaryResult: T | null, _trustedResult: T | null) => true, + ); + } + + static createPromises( + primaryResult: T | null, + trustedResult: T | null, + primaryDelay: number, + trustedDelay: number, + verifyDelay: number, + primaryFail = false, + trustedFail = false, + ): { + primaryPromise: QueryablePromise; // resolves BEFORE the function returns + trustedPromise: QueryablePromise; // resolves BEFORE the function returns + verifyPromise: QueryablePromise<{ primaryResult: T | null; trustedResult: T | null }>; // resolves AFTER function returns + verifier: AutoVerifier; + } { + let primaryResolve = (value: T | null) => { + /* */ + }; + let trustedResolve = (value: T | null) => { + /* */ + }; + let verifyResolve = (value: { primaryResult: T | null; trustedResult: T | null }) => { + /* */ + }; + const primaryPromise = new Promise((resolve, reject) => { + primaryResolve = resolve; + }); + const trustedPromise = new Promise((resolve, reject) => { + trustedResolve = resolve; + }); + const verifyPromise = new Promise<{ primaryResult: T | null; trustedResult: T | null }>(resolve => { + verifyResolve = resolve; + }); + const verifier = new AutoVerifier( + async () => { + await sleep(primaryDelay); + if (primaryFail) { + primaryResolve(null); + throw new Error('primary reject'); + } else { + const result = primaryResult; + primaryResolve(result); + await disjoint(); // if you need to know what this does you will not be happy :) + return result; + } + }, + async () => { + await sleep(trustedDelay); + if (trustedFail) { + trustedResolve(null); + throw new Error('trusted reject'); + } else { + const result = trustedResult; + trustedResolve(result); + await disjoint(); + return result; + } + }, + async (primaryResult: T | null, trustedResult: T | null) => { + await sleep(verifyDelay); + if (primaryResult !== null && trustedResult !== null && primaryResult !== trustedResult) verifier.unverify(); // auto-unverify if not equal + (async () => { + await disjoint(); + verifyResolve({ primaryResult, trustedResult }); + })(); + }, + ); + return { + primaryPromise: makeQuerablePromise(primaryPromise), + trustedPromise: makeQuerablePromise(trustedPromise), + verifyPromise: makeQuerablePromise(verifyPromise), + verifier, + }; + } +} + +describe('basic functionality', () => { + test('basic functionality', async () => { + const verifier = AutoVerifierTest.createSimple(2, 2); + expect(verifier.trustedPromise === null); + expect(verifier.trustedStatus === 'none'); + const result = await verifier.fetchAndVerifyIfNeeded(); + expect(result).toBe(2); + }); +}); + +describe('promise order tests', () => { + test('normal verification', async () => { + const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 35, 65, 15); + + expect(primaryPromise.isFulfilled()).toBe(false); + expect(trustedPromise.isFulfilled()).toBe(false); + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedPromise).toBe(null); + expect(verifier.trustedStatus).toBe('none'); + + const fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); + + expect(verifier.trustedPromise !== null); + expect(verifier.trustedStatus === 'fetching'); + + const primaryResult = await primaryPromise; + expect(primaryResult).toBe(2); + + expect(fetchPromise.isFulfilled()).toBe(false); + expect(primaryPromise.isFulfilled()).toBe(true); + expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('fetching'); + + await fetchPromise; + + expect(fetchPromise.isFulfilled()).toBe(true); + expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('verifying'); + + const trustedResult = await trustedPromise; + + expect(trustedResult).toBe(2); + + expect(trustedPromise.isFulfilled()).toBe(true); + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('verifying'); + + const verification = await verifyPromise; + + expect(verification.primaryResult).toBe(2); + expect(verification.trustedResult).toBe(2); + + expect(verifyPromise.isFulfilled()).toBe(true); + expect(verifier.trustedStatus).toBe('verified'); + }); + + test('odd-ordered verification', async () => { + const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 65, 35, 15); + + expect(primaryPromise.isFulfilled()).toBe(false); + expect(trustedPromise.isFulfilled()).toBe(false); + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedPromise).toBe(null); + expect(verifier.trustedStatus).toBe('none'); + + const fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); + + expect(verifier.trustedPromise !== null); + expect(verifier.trustedStatus === 'fetching'); + + const primaryResult = await primaryPromise; + expect(primaryResult).toBe(2); + + expect(primaryPromise.isFulfilled()).toBe(true); + expect(trustedPromise.isFulfilled()).toBe(true); // since 65ms > 35ms + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('fetching'); + + await fetchPromise; + + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('verifying'); + + const trustedResult = await trustedPromise; // note: this is already fulfilled + expect(trustedResult).toBe(2); + + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('verifying'); + + const verification = await verifyPromise; + + expect(verification.primaryResult).toBe(2); + expect(verification.trustedResult).toBe(2); + + expect(verifyPromise.isFulfilled()).toBe(true); + expect(verifier.trustedStatus).toBe('verified'); + }); + + test('normal trusted override', async () => { + const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(null, 3, 35, 65, 15); + + expect(primaryPromise.isFulfilled()).toBe(false); + expect(trustedPromise.isFulfilled()).toBe(false); + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedPromise).toBe(null); + expect(verifier.trustedStatus).toBe('none'); + + const fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); + + expect(verifier.trustedPromise !== null); + expect(verifier.trustedStatus === 'fetching'); + + const primaryResult = await primaryPromise; + expect(primaryResult).toBe(null); + + expect(fetchPromise.isFulfilled()).toBe(false); + expect(primaryPromise.isFulfilled()).toBe(true); + expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('fetching'); + + const trustedResult = await trustedPromise; + expect(trustedResult).toBe(3); + + expect(fetchPromise.isFulfilled()).toBe(false); + expect(trustedPromise.isFulfilled()).toBe(true); + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('verifying'); + + const verification = await verifyPromise; + + expect(verification.primaryResult).toBe(null); + expect(verification.trustedResult).toBe(3); + + expect(fetchPromise.isFulfilled()).toBe(true); // since verifyPromise is called AFTER + expect(verifyPromise.isFulfilled()).toBe(true); + expect(verifier.trustedStatus).toBe('verified'); // does not get auto unverified + + const result = await fetchPromise; + expect(result).toBe(3); + }); + + test('normal invalidation', async () => { + const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 3, 35, 65, 15); + + expect(primaryPromise.isFulfilled()).toBe(false); + expect(trustedPromise.isFulfilled()).toBe(false); + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedPromise).toBe(null); + expect(verifier.trustedStatus).toBe('none'); + + const fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); + + expect(verifier.trustedPromise !== null); + expect(verifier.trustedStatus === 'fetching'); + + const primaryResult = await primaryPromise; + expect(primaryResult).toBe(2); + + expect(fetchPromise.isFulfilled()).toBe(false); + expect(primaryPromise.isFulfilled()).toBe(true); + expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('fetching'); + + await fetchPromise; + + expect(fetchPromise.isFulfilled()).toBe(true); + expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('verifying'); + + const trustedResult = await trustedPromise; + expect(trustedResult).toBe(3); + + expect(trustedPromise.isFulfilled()).toBe(true); + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('verifying'); + + const verification = await verifyPromise; + + expect(verification.primaryResult).toBe(2); + expect(verification.trustedResult).toBe(3); + + expect(verifyPromise.isFulfilled()).toBe(true); + expect(verifier.trustedStatus).toBe('none'); // gets auto unverified + }); + + test('odd-ordered invalidation', async () => { + const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 3, 65, 35, 15); + + expect(primaryPromise.isFulfilled()).toBe(false); + expect(trustedPromise.isFulfilled()).toBe(false); + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedPromise).toBe(null); + expect(verifier.trustedStatus).toBe('none'); + + const fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); + + expect(verifier.trustedPromise !== null); + expect(verifier.trustedStatus === 'fetching'); + + const primaryResult = await primaryPromise; + expect(primaryResult).toBe(2); + + expect(fetchPromise.isFulfilled()).toBe(false); + expect(primaryPromise.isFulfilled()).toBe(true); + expect(trustedPromise.isFulfilled()).toBe(true); // since 65ms > 35ms + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('fetching'); + + await fetchPromise; + + expect(fetchPromise.isFulfilled()).toBe(true); + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('verifying'); + + const trustedResult = await trustedPromise; // note: this is already fulfilled + expect(trustedResult).toBe(3); + + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('verifying'); + + const verification = await verifyPromise; + + expect(verification.primaryResult).toBe(2); + expect(verification.trustedResult).toBe(3); + + expect(verifyPromise.isFulfilled()).toBe(true); + expect(verifier.trustedStatus).toBe('none'); // gets auto unverified + }); + + test('normal unverification during primary func', async () => { + const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 35, 65, 15); + + expect(primaryPromise.isFulfilled()).toBe(false); + expect(trustedPromise.isFulfilled()).toBe(false); + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedPromise).toBe(null); + expect(verifier.trustedStatus).toBe('none'); + + const fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); + + expect(verifier.trustedPromise !== null); + expect(verifier.trustedStatus === 'fetching'); + + const primaryResult = await primaryPromise; + expect(primaryResult).toBe(2); + + expect(fetchPromise.isFulfilled()).toBe(false); + expect(primaryPromise.isFulfilled()).toBe(true); + expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('fetching'); + + verifier.unverify(); // keep in mind this is still during the primary func since this is before we await the fetchPromise + expect(verifier.trustedPromise).toBe(null); + expect(verifier.trustedStatus).toBe('none'); + + await fetchPromise; + + expect(fetchPromise.isFulfilled()).toBe(true); + expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('verifying'); + + const trustedResult = await trustedPromise; + expect(trustedResult).toBe(2); + + expect(trustedPromise.isFulfilled()).toBe(true); + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('verifying'); + + const verification = await verifyPromise; + + expect(verification.primaryResult).toBe(2); + expect(verification.trustedResult).toBe(2); + + expect(verifyPromise.isFulfilled()).toBe(true); + expect(verifier.trustedStatus).toBe('verified'); + }); + + test('odd-ordered unverification during primary func', async () => { + const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 65, 35, 15); + + expect(primaryPromise.isFulfilled()).toBe(false); + expect(trustedPromise.isFulfilled()).toBe(false); + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedPromise).toBe(null); + expect(verifier.trustedStatus).toBe('none'); + + const fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); + + expect(verifier.trustedPromise !== null); + expect(verifier.trustedStatus === 'fetching'); + + const primaryResult = await primaryPromise; + expect(primaryResult).toBe(2); + + expect(fetchPromise.isFulfilled()).toBe(false); + expect(primaryPromise.isFulfilled()).toBe(true); + expect(trustedPromise.isFulfilled()).toBe(true); // since 65ms > 35ms + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('fetching'); + + verifier.unverify(); + expect(verifier.trustedPromise).toBe(null); + expect(verifier.trustedStatus).toBe('none'); + + await fetchPromise; + + expect(fetchPromise.isFulfilled()).toBe(true); + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('verifying'); + + const trustedResult = await trustedPromise; // note: this is already fulfilled + expect(trustedResult).toBe(2); + + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('verifying'); + + const verification = await verifyPromise; + + expect(verification.primaryResult).toBe(2); + expect(verification.trustedResult).toBe(2); + + expect(verifyPromise.isFulfilled()).toBe(true); + expect(verifier.trustedStatus).toBe('verified'); + }); + + test('normal unverification during trusted func', async () => { + const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 35, 65, 15); + + expect(primaryPromise.isFulfilled()).toBe(false); + expect(trustedPromise.isFulfilled()).toBe(false); + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedPromise).toBe(null); + expect(verifier.trustedStatus).toBe('none'); + + const fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); + + expect(verifier.trustedPromise !== null); + expect(verifier.trustedStatus === 'fetching'); + + const primaryResult = await primaryPromise; + expect(primaryResult).toBe(2); + + expect(fetchPromise.isFulfilled()).toBe(false); + expect(primaryPromise.isFulfilled()).toBe(true); + expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('fetching'); + + await fetchPromise; + + expect(fetchPromise.isFulfilled()).toBe(true); + expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('verifying'); + + verifier.unverify(); + expect(verifier.trustedPromise).toBe(null); + expect(verifier.trustedStatus).toBe('none'); + + const trustedResult = await trustedPromise; + expect(trustedResult).toBe(2); + + expect(trustedPromise.isFulfilled()).toBe(true); + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('none'); + + // verification will not happen since the verifier.trustedPromise is null and we already returned + }); + + test('normal unverification during primary func with trusted override', async () => { + const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(null, 2, 35, 65, 15); + + expect(primaryPromise.isFulfilled()).toBe(false); + expect(trustedPromise.isFulfilled()).toBe(false); + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedPromise).toBe(null); + expect(verifier.trustedStatus).toBe('none'); + + const fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); + + expect(verifier.trustedPromise !== null); + expect(verifier.trustedStatus === 'fetching'); + + const primaryResult = await primaryPromise; + expect(primaryResult).toBe(null); + + expect(fetchPromise.isFulfilled()).toBe(false); + expect(primaryPromise.isFulfilled()).toBe(true); + expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('fetching'); + + verifier.unverify(); // keep in mind this is still during the primary func since this is before we await the fetchPromise + expect(verifier.trustedPromise).toBe(null); + expect(verifier.trustedStatus).toBe('none'); + + const trustedResult = await trustedPromise; + expect(trustedResult).toBe(2); + + expect(fetchPromise.isFulfilled()).toBe(false); + expect(trustedPromise.isFulfilled()).toBe(true); + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('verifying'); + + const verification = await verifyPromise; + expect(verification.primaryResult).toBe(null); + expect(verification.trustedResult).toBe(2); + + expect(fetchPromise.isFulfilled()).toBe(true); // since verifyPromise occurs AFTER it returns + expect(verifyPromise.isFulfilled()).toBe(true); + expect(verifier.trustedStatus).toBe('verified'); + + const fetchResult = await fetchPromise; + expect(fetchResult).toBe(2); + expect(fetchPromise.isFulfilled()).toBe(true); + }); + + test('normal unverification during trusted func with trusted override', async () => { + const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(null, 2, 35, 65, 15); + + expect(primaryPromise.isFulfilled()).toBe(false); + expect(trustedPromise.isFulfilled()).toBe(false); + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedPromise).toBe(null); + expect(verifier.trustedStatus).toBe('none'); + + const fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); + + expect(verifier.trustedPromise !== null); + expect(verifier.trustedStatus === 'fetching'); + + const primaryResult = await primaryPromise; + expect(primaryResult).toBe(null); + + expect(fetchPromise.isFulfilled()).toBe(false); + expect(primaryPromise.isFulfilled()).toBe(true); + expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms + expect(verifyPromise.isFulfilled()).toBe(false); + expect(verifier.trustedStatus).toBe('fetching'); + + const trustedResult = await trustedPromise; + expect(trustedResult).toBe(2); + + verifier.unverify(); // keep in mind this is still during the trusted func since this is before we await the fetchPromise + expect(verifier.trustedPromise).toBe(null); + expect(verifier.trustedStatus).toBe('none'); + + expect(fetchPromise.isFulfilled()).toBe(false); + expect(trustedPromise.isFulfilled()).toBe(true); + expect(verifyPromise.isFulfilled()).toBe(false); + + // this will resolve with null since we got unverified during the fetch + + const fetchResult = await fetchPromise; + expect(fetchResult).toBe(null); + expect(fetchPromise.isFulfilled()).toBe(true); + }); +}); + +describe('error tests', () => { + test('primary error', async () => { + const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 35, 65, 15, true, false); + + expect.assertions(3); + try { + await verifier.fetchAndVerifyIfNeeded(); + } catch (e) { + expect(e).toBeDefined(); + } + await primaryPromise; + await trustedPromise; + await disjoint(); + + expect(verifier.trustedPromise).toBe(null); + expect(verifier.trustedStatus).toBe('none'); + }); + + test('trusted error thrown out', async () => { + const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 35, 65, 15, false, true); + + await verifier.fetchAndVerifyIfNeeded(); + await primaryPromise; + await trustedPromise; + await disjoint(); + + await disjoint(); // :D since trustedPromise happens BEFORE + + expect(verifier.trustedPromise).toBe(null); + expect(verifier.trustedStatus).toBe('none'); + }); + + test('trusted error considered since null primary result', async () => { + const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(null, 2, 35, 65, 15, false, true); + + expect.assertions(3); + try { + await verifier.fetchAndVerifyIfNeeded(); + } catch (e) { + expect(e).toBeDefined(); + } + await primaryPromise; + await trustedPromise; + await disjoint(); + + expect(verifier.trustedPromise).toBe(null); + expect(verifier.trustedStatus).toBe('none'); + }); + + test('both error only one exception', async () => { + const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 35, 65, 15, true, true); + + expect.assertions(3); + try { + await verifier.fetchAndVerifyIfNeeded(); + } catch (e) { + expect(e).toBeDefined(); + } + await primaryPromise; + await trustedPromise; + await disjoint(); + + expect(verifier.trustedPromise).toBe(null); + expect(verifier.trustedStatus).toBe('none'); + }); +}); + +describe('deduplication', () => { + test('primary deduplication', async () => { + const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 3, 35, 65, 15); + + await Promise.all([ + (async () => { + const fetchResult = await verifier.fetchAndVerifyIfNeeded(); + expect(fetchResult).toBe(2); + })(), + (async () => { + const fetchResult = await verifier.fetchAndVerifyIfNeeded(); + expect(fetchResult).toBe(2); + })(), + ]); + }); + + test('trusted deduplication', async () => { + const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(null, 3, 35, 65, 15); + + await Promise.all([ + (async () => { + const fetchResult = await verifier.fetchAndVerifyIfNeeded(); + expect(fetchResult).toBe(3); + })(), + (async () => { + const fetchResult = await verifier.fetchAndVerifyIfNeeded(); + expect(fetchResult).toBe(3); + })(), + ]); + + // doesn't really test it but it gets the code coverage B) + }); + + test('trusted deduplication with unverification', async () => { + const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(null, 3, 35, 65, 15); + + await Promise.all([ + (async () => { + const fetchPromise = verifier.fetchAndVerifyIfNeeded(); + const fetchResult = await fetchPromise; + expect(fetchResult).toBe(null); + })(), + (async () => { + const fetchPromise = verifier.fetchAndVerifyIfNeeded(); + await trustedPromise; + verifier.unverify(); + const fetchResult = await fetchPromise; + expect(fetchResult).toBe(null); + })(), + ]); + }); +}); + +// TODO: changesFunc +// TODO: Standard autoverifiers? diff --git a/src/client/tests/webapp/auto-verifier.test.ts b/src/client/tests/webapp/auto-verifier.test.ts index 8eab864..e69de29 100644 --- a/src/client/tests/webapp/auto-verifier.test.ts +++ b/src/client/tests/webapp/auto-verifier.test.ts @@ -1,729 +0,0 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ -import { AutoVerifier } from '../../webapp/auto-verifier'; - -function sleep(ms: number): Promise { - if (ms === 0) return; - return new Promise(resolve => { - setTimeout(resolve, ms); - }); -} - -// disjoint the current async function to allow other promises to be handled before the next lines are run -async function disjoint() { - await sleep(0); -} - -type QueryablePromise = Promise & { isPending: () => boolean; isRejected: () => boolean; isFulfilled: () => boolean }; - -/** - * this function allow you to modify a JS Promise by adding some status properties. - * Based on: http://stackoverflow.com/questions/21485545/is-there-a-way-to-tell-if-an-es6-promise-is-fulfilled-rejected-resolved - * But modified according to the specs of promises : https://promisesaplus.com/ - */ -function makeQuerablePromise(promise: Promise) { - // set initial state - let isPending = true; - let isRejected = false; - let isFulfilled = false; - - // observe the promise, saving the fulfillment in a closure scope. - const result = promise.then( - v => { - isFulfilled = true; - isPending = false; - return v; - }, - e => { - isRejected = true; - isPending = false; - throw e; - }, - ); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (result as any).isFulfilled = function () { - return isFulfilled; - }; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (result as any).isPending = function () { - return isPending; - }; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (result as any).isRejected = function () { - return isRejected; - }; - return result as QueryablePromise; -} - -class AutoVerifierTest { - static createSimple(primaryResult: T, trustedResult: T): AutoVerifier { - return new AutoVerifier( - async () => primaryResult, - async () => trustedResult, - async (_primaryResult: T | null, _trustedResult: T | null) => true, - ); - } - - static createPromises( - primaryResult: T | null, - trustedResult: T | null, - primaryDelay: number, - trustedDelay: number, - verifyDelay: number, - primaryFail = false, - trustedFail = false, - ): { - primaryPromise: QueryablePromise; // resolves BEFORE the function returns - trustedPromise: QueryablePromise; // resolves BEFORE the function returns - verifyPromise: QueryablePromise<{ primaryResult: T | null; trustedResult: T | null }>; // resolves AFTER function returns - verifier: AutoVerifier; - } { - let primaryResolve = (value: T | null) => { - /* */ - }; - let trustedResolve = (value: T | null) => { - /* */ - }; - let verifyResolve = (value: { primaryResult: T | null; trustedResult: T | null }) => { - /* */ - }; - const primaryPromise = new Promise((resolve, reject) => { - primaryResolve = resolve; - }); - const trustedPromise = new Promise((resolve, reject) => { - trustedResolve = resolve; - }); - const verifyPromise = new Promise<{ primaryResult: T | null; trustedResult: T | null }>(resolve => { - verifyResolve = resolve; - }); - const verifier = new AutoVerifier( - async () => { - await sleep(primaryDelay); - if (primaryFail) { - primaryResolve(null); - throw new Error('primary reject'); - } else { - const result = primaryResult; - primaryResolve(result); - await disjoint(); // if you need to know what this does you will not be happy :) - return result; - } - }, - async () => { - await sleep(trustedDelay); - if (trustedFail) { - trustedResolve(null); - throw new Error('trusted reject'); - } else { - const result = trustedResult; - trustedResolve(result); - await disjoint(); - return result; - } - }, - async (primaryResult: T | null, trustedResult: T | null) => { - await sleep(verifyDelay); - if (primaryResult !== null && trustedResult !== null && primaryResult !== trustedResult) verifier.unverify(); // auto-unverify if not equal - (async () => { - await disjoint(); - verifyResolve({ primaryResult, trustedResult }); - })(); - }, - ); - return { - primaryPromise: makeQuerablePromise(primaryPromise), - trustedPromise: makeQuerablePromise(trustedPromise), - verifyPromise: makeQuerablePromise(verifyPromise), - verifier, - }; - } -} - -describe('basic functionality', () => { - test('basic functionality', async () => { - const verifier = AutoVerifierTest.createSimple(2, 2); - expect(verifier.trustedPromise === null); - expect(verifier.trustedStatus === 'none'); - const result = await verifier.fetchAndVerifyIfNeeded(); - expect(result).toBe(2); - }); -}); - -describe('promise order tests', () => { - test('normal verification', async () => { - const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 35, 65, 15); - - expect(primaryPromise.isFulfilled()).toBe(false); - expect(trustedPromise.isFulfilled()).toBe(false); - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedPromise).toBe(null); - expect(verifier.trustedStatus).toBe('none'); - - const fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); - - expect(verifier.trustedPromise !== null); - expect(verifier.trustedStatus === 'fetching'); - - const primaryResult = await primaryPromise; - expect(primaryResult).toBe(2); - - expect(fetchPromise.isFulfilled()).toBe(false); - expect(primaryPromise.isFulfilled()).toBe(true); - expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('fetching'); - - await fetchPromise; - - expect(fetchPromise.isFulfilled()).toBe(true); - expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('verifying'); - - const trustedResult = await trustedPromise; - - expect(trustedResult).toBe(2); - - expect(trustedPromise.isFulfilled()).toBe(true); - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('verifying'); - - const verification = await verifyPromise; - - expect(verification.primaryResult).toBe(2); - expect(verification.trustedResult).toBe(2); - - expect(verifyPromise.isFulfilled()).toBe(true); - expect(verifier.trustedStatus).toBe('verified'); - }); - - test('odd-ordered verification', async () => { - const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 65, 35, 15); - - expect(primaryPromise.isFulfilled()).toBe(false); - expect(trustedPromise.isFulfilled()).toBe(false); - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedPromise).toBe(null); - expect(verifier.trustedStatus).toBe('none'); - - const fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); - - expect(verifier.trustedPromise !== null); - expect(verifier.trustedStatus === 'fetching'); - - const primaryResult = await primaryPromise; - expect(primaryResult).toBe(2); - - expect(primaryPromise.isFulfilled()).toBe(true); - expect(trustedPromise.isFulfilled()).toBe(true); // since 65ms > 35ms - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('fetching'); - - await fetchPromise; - - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('verifying'); - - const trustedResult = await trustedPromise; // note: this is already fulfilled - expect(trustedResult).toBe(2); - - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('verifying'); - - const verification = await verifyPromise; - - expect(verification.primaryResult).toBe(2); - expect(verification.trustedResult).toBe(2); - - expect(verifyPromise.isFulfilled()).toBe(true); - expect(verifier.trustedStatus).toBe('verified'); - }); - - test('normal trusted override', async () => { - const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(null, 3, 35, 65, 15); - - expect(primaryPromise.isFulfilled()).toBe(false); - expect(trustedPromise.isFulfilled()).toBe(false); - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedPromise).toBe(null); - expect(verifier.trustedStatus).toBe('none'); - - const fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); - - expect(verifier.trustedPromise !== null); - expect(verifier.trustedStatus === 'fetching'); - - const primaryResult = await primaryPromise; - expect(primaryResult).toBe(null); - - expect(fetchPromise.isFulfilled()).toBe(false); - expect(primaryPromise.isFulfilled()).toBe(true); - expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('fetching'); - - const trustedResult = await trustedPromise; - expect(trustedResult).toBe(3); - - expect(fetchPromise.isFulfilled()).toBe(false); - expect(trustedPromise.isFulfilled()).toBe(true); - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('verifying'); - - const verification = await verifyPromise; - - expect(verification.primaryResult).toBe(null); - expect(verification.trustedResult).toBe(3); - - expect(fetchPromise.isFulfilled()).toBe(true); // since verifyPromise is called AFTER - expect(verifyPromise.isFulfilled()).toBe(true); - expect(verifier.trustedStatus).toBe('verified'); // does not get auto unverified - - const result = await fetchPromise; - expect(result).toBe(3); - }); - - test('normal invalidation', async () => { - const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 3, 35, 65, 15); - - expect(primaryPromise.isFulfilled()).toBe(false); - expect(trustedPromise.isFulfilled()).toBe(false); - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedPromise).toBe(null); - expect(verifier.trustedStatus).toBe('none'); - - const fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); - - expect(verifier.trustedPromise !== null); - expect(verifier.trustedStatus === 'fetching'); - - const primaryResult = await primaryPromise; - expect(primaryResult).toBe(2); - - expect(fetchPromise.isFulfilled()).toBe(false); - expect(primaryPromise.isFulfilled()).toBe(true); - expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('fetching'); - - await fetchPromise; - - expect(fetchPromise.isFulfilled()).toBe(true); - expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('verifying'); - - const trustedResult = await trustedPromise; - expect(trustedResult).toBe(3); - - expect(trustedPromise.isFulfilled()).toBe(true); - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('verifying'); - - const verification = await verifyPromise; - - expect(verification.primaryResult).toBe(2); - expect(verification.trustedResult).toBe(3); - - expect(verifyPromise.isFulfilled()).toBe(true); - expect(verifier.trustedStatus).toBe('none'); // gets auto unverified - }); - - test('odd-ordered invalidation', async () => { - const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 3, 65, 35, 15); - - expect(primaryPromise.isFulfilled()).toBe(false); - expect(trustedPromise.isFulfilled()).toBe(false); - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedPromise).toBe(null); - expect(verifier.trustedStatus).toBe('none'); - - const fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); - - expect(verifier.trustedPromise !== null); - expect(verifier.trustedStatus === 'fetching'); - - const primaryResult = await primaryPromise; - expect(primaryResult).toBe(2); - - expect(fetchPromise.isFulfilled()).toBe(false); - expect(primaryPromise.isFulfilled()).toBe(true); - expect(trustedPromise.isFulfilled()).toBe(true); // since 65ms > 35ms - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('fetching'); - - await fetchPromise; - - expect(fetchPromise.isFulfilled()).toBe(true); - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('verifying'); - - const trustedResult = await trustedPromise; // note: this is already fulfilled - expect(trustedResult).toBe(3); - - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('verifying'); - - const verification = await verifyPromise; - - expect(verification.primaryResult).toBe(2); - expect(verification.trustedResult).toBe(3); - - expect(verifyPromise.isFulfilled()).toBe(true); - expect(verifier.trustedStatus).toBe('none'); // gets auto unverified - }); - - test('normal unverification during primary func', async () => { - const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 35, 65, 15); - - expect(primaryPromise.isFulfilled()).toBe(false); - expect(trustedPromise.isFulfilled()).toBe(false); - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedPromise).toBe(null); - expect(verifier.trustedStatus).toBe('none'); - - const fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); - - expect(verifier.trustedPromise !== null); - expect(verifier.trustedStatus === 'fetching'); - - const primaryResult = await primaryPromise; - expect(primaryResult).toBe(2); - - expect(fetchPromise.isFulfilled()).toBe(false); - expect(primaryPromise.isFulfilled()).toBe(true); - expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('fetching'); - - verifier.unverify(); // keep in mind this is still during the primary func since this is before we await the fetchPromise - expect(verifier.trustedPromise).toBe(null); - expect(verifier.trustedStatus).toBe('none'); - - await fetchPromise; - - expect(fetchPromise.isFulfilled()).toBe(true); - expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('verifying'); - - const trustedResult = await trustedPromise; - expect(trustedResult).toBe(2); - - expect(trustedPromise.isFulfilled()).toBe(true); - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('verifying'); - - const verification = await verifyPromise; - - expect(verification.primaryResult).toBe(2); - expect(verification.trustedResult).toBe(2); - - expect(verifyPromise.isFulfilled()).toBe(true); - expect(verifier.trustedStatus).toBe('verified'); - }); - - test('odd-ordered unverification during primary func', async () => { - const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 65, 35, 15); - - expect(primaryPromise.isFulfilled()).toBe(false); - expect(trustedPromise.isFulfilled()).toBe(false); - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedPromise).toBe(null); - expect(verifier.trustedStatus).toBe('none'); - - const fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); - - expect(verifier.trustedPromise !== null); - expect(verifier.trustedStatus === 'fetching'); - - const primaryResult = await primaryPromise; - expect(primaryResult).toBe(2); - - expect(fetchPromise.isFulfilled()).toBe(false); - expect(primaryPromise.isFulfilled()).toBe(true); - expect(trustedPromise.isFulfilled()).toBe(true); // since 65ms > 35ms - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('fetching'); - - verifier.unverify(); - expect(verifier.trustedPromise).toBe(null); - expect(verifier.trustedStatus).toBe('none'); - - await fetchPromise; - - expect(fetchPromise.isFulfilled()).toBe(true); - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('verifying'); - - const trustedResult = await trustedPromise; // note: this is already fulfilled - expect(trustedResult).toBe(2); - - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('verifying'); - - const verification = await verifyPromise; - - expect(verification.primaryResult).toBe(2); - expect(verification.trustedResult).toBe(2); - - expect(verifyPromise.isFulfilled()).toBe(true); - expect(verifier.trustedStatus).toBe('verified'); - }); - - test('normal unverification during trusted func', async () => { - const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 35, 65, 15); - - expect(primaryPromise.isFulfilled()).toBe(false); - expect(trustedPromise.isFulfilled()).toBe(false); - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedPromise).toBe(null); - expect(verifier.trustedStatus).toBe('none'); - - const fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); - - expect(verifier.trustedPromise !== null); - expect(verifier.trustedStatus === 'fetching'); - - const primaryResult = await primaryPromise; - expect(primaryResult).toBe(2); - - expect(fetchPromise.isFulfilled()).toBe(false); - expect(primaryPromise.isFulfilled()).toBe(true); - expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('fetching'); - - await fetchPromise; - - expect(fetchPromise.isFulfilled()).toBe(true); - expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('verifying'); - - verifier.unverify(); - expect(verifier.trustedPromise).toBe(null); - expect(verifier.trustedStatus).toBe('none'); - - const trustedResult = await trustedPromise; - expect(trustedResult).toBe(2); - - expect(trustedPromise.isFulfilled()).toBe(true); - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('none'); - - // verification will not happen since the verifier.trustedPromise is null and we already returned - }); - - test('normal unverification during primary func with trusted override', async () => { - const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(null, 2, 35, 65, 15); - - expect(primaryPromise.isFulfilled()).toBe(false); - expect(trustedPromise.isFulfilled()).toBe(false); - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedPromise).toBe(null); - expect(verifier.trustedStatus).toBe('none'); - - const fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); - - expect(verifier.trustedPromise !== null); - expect(verifier.trustedStatus === 'fetching'); - - const primaryResult = await primaryPromise; - expect(primaryResult).toBe(null); - - expect(fetchPromise.isFulfilled()).toBe(false); - expect(primaryPromise.isFulfilled()).toBe(true); - expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('fetching'); - - verifier.unverify(); // keep in mind this is still during the primary func since this is before we await the fetchPromise - expect(verifier.trustedPromise).toBe(null); - expect(verifier.trustedStatus).toBe('none'); - - const trustedResult = await trustedPromise; - expect(trustedResult).toBe(2); - - expect(fetchPromise.isFulfilled()).toBe(false); - expect(trustedPromise.isFulfilled()).toBe(true); - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('verifying'); - - const verification = await verifyPromise; - expect(verification.primaryResult).toBe(null); - expect(verification.trustedResult).toBe(2); - - expect(fetchPromise.isFulfilled()).toBe(true); // since verifyPromise occurs AFTER it returns - expect(verifyPromise.isFulfilled()).toBe(true); - expect(verifier.trustedStatus).toBe('verified'); - - const fetchResult = await fetchPromise; - expect(fetchResult).toBe(2); - expect(fetchPromise.isFulfilled()).toBe(true); - }); - - test('normal unverification during trusted func with trusted override', async () => { - const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(null, 2, 35, 65, 15); - - expect(primaryPromise.isFulfilled()).toBe(false); - expect(trustedPromise.isFulfilled()).toBe(false); - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedPromise).toBe(null); - expect(verifier.trustedStatus).toBe('none'); - - const fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); - - expect(verifier.trustedPromise !== null); - expect(verifier.trustedStatus === 'fetching'); - - const primaryResult = await primaryPromise; - expect(primaryResult).toBe(null); - - expect(fetchPromise.isFulfilled()).toBe(false); - expect(primaryPromise.isFulfilled()).toBe(true); - expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms - expect(verifyPromise.isFulfilled()).toBe(false); - expect(verifier.trustedStatus).toBe('fetching'); - - const trustedResult = await trustedPromise; - expect(trustedResult).toBe(2); - - verifier.unverify(); // keep in mind this is still during the trusted func since this is before we await the fetchPromise - expect(verifier.trustedPromise).toBe(null); - expect(verifier.trustedStatus).toBe('none'); - - expect(fetchPromise.isFulfilled()).toBe(false); - expect(trustedPromise.isFulfilled()).toBe(true); - expect(verifyPromise.isFulfilled()).toBe(false); - - // this will resolve with null since we got unverified during the fetch - - const fetchResult = await fetchPromise; - expect(fetchResult).toBe(null); - expect(fetchPromise.isFulfilled()).toBe(true); - }); -}); - -describe('error tests', () => { - test('primary error', async () => { - const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 35, 65, 15, true, false); - - expect.assertions(3); - try { - await verifier.fetchAndVerifyIfNeeded(); - } catch (e) { - expect(e).toBeDefined(); - } - await primaryPromise; - await trustedPromise; - await disjoint(); - - expect(verifier.trustedPromise).toBe(null); - expect(verifier.trustedStatus).toBe('none'); - }); - - test('trusted error thrown out', async () => { - const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 35, 65, 15, false, true); - - await verifier.fetchAndVerifyIfNeeded(); - await primaryPromise; - await trustedPromise; - await disjoint(); - - await disjoint(); // :D since trustedPromise happens BEFORE - - expect(verifier.trustedPromise).toBe(null); - expect(verifier.trustedStatus).toBe('none'); - }); - - test('trusted error considered since null primary result', async () => { - const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(null, 2, 35, 65, 15, false, true); - - expect.assertions(3); - try { - await verifier.fetchAndVerifyIfNeeded(); - } catch (e) { - expect(e).toBeDefined(); - } - await primaryPromise; - await trustedPromise; - await disjoint(); - - expect(verifier.trustedPromise).toBe(null); - expect(verifier.trustedStatus).toBe('none'); - }); - - test('both error only one exception', async () => { - const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 35, 65, 15, true, true); - - expect.assertions(3); - try { - await verifier.fetchAndVerifyIfNeeded(); - } catch (e) { - expect(e).toBeDefined(); - } - await primaryPromise; - await trustedPromise; - await disjoint(); - - expect(verifier.trustedPromise).toBe(null); - expect(verifier.trustedStatus).toBe('none'); - }); -}); - -describe('deduplication', () => { - test('primary deduplication', async () => { - const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 3, 35, 65, 15); - - await Promise.all([ - (async () => { - const fetchResult = await verifier.fetchAndVerifyIfNeeded(); - expect(fetchResult).toBe(2); - })(), - (async () => { - const fetchResult = await verifier.fetchAndVerifyIfNeeded(); - expect(fetchResult).toBe(2); - })(), - ]); - }); - - test('trusted deduplication', async () => { - const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(null, 3, 35, 65, 15); - - await Promise.all([ - (async () => { - const fetchResult = await verifier.fetchAndVerifyIfNeeded(); - expect(fetchResult).toBe(3); - })(), - (async () => { - const fetchResult = await verifier.fetchAndVerifyIfNeeded(); - expect(fetchResult).toBe(3); - })(), - ]); - - // doesn't really test it but it gets the code coverage B) - }); - - test('trusted deduplication with unverification', async () => { - const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(null, 3, 35, 65, 15); - - await Promise.all([ - (async () => { - const fetchPromise = verifier.fetchAndVerifyIfNeeded(); - const fetchResult = await fetchPromise; - expect(fetchResult).toBe(null); - })(), - (async () => { - const fetchPromise = verifier.fetchAndVerifyIfNeeded(); - await trustedPromise; - verifier.unverify(); - const fetchResult = await fetchPromise; - expect(fetchResult).toBe(null); - })(), - ]); - }); -}); - -// TODO: changesFunc -// TODO: Standard autoverifiers?