From a6a512617c0daff8653bac8b60aa9f90eee045c1 Mon Sep 17 00:00:00 2001 From: Michael Peters Date: Wed, 19 Oct 2022 23:51:36 -0700 Subject: [PATCH] prettier/eslint auto-verifier.test.ts --- src/.eslintrc.js | 1 + src/client/tests/webapp/auto-verifier.test.ts | 2672 ++++++++--------- 2 files changed, 1334 insertions(+), 1339 deletions(-) diff --git a/src/.eslintrc.js b/src/.eslintrc.js index 9c59873..663ae87 100644 --- a/src/.eslintrc.js +++ b/src/.eslintrc.js @@ -132,6 +132,7 @@ module.exports = { // typescript/react/jsx // 'jsx-quotes': [ 'warn', 'prefer-double' ], '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', 'unused-imports/no-unused-imports': 'warn', 'react/self-closing-comp': 'warn', 'react/jsx-boolean-value': ['warn', 'always'], diff --git a/src/client/tests/webapp/auto-verifier.test.ts b/src/client/tests/webapp/auto-verifier.test.ts index c4aec9e..d2a4495 100644 --- a/src/client/tests/webapp/auto-verifier.test.ts +++ b/src/client/tests/webapp/auto-verifier.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-lines */ import { AutoVerifier } from '../../webapp/auto-verifier'; import { WithEquals } from '../../webapp/data-types'; import Util from '../../webapp/util'; @@ -5,1514 +6,1507 @@ import Util from '../../webapp/util'; jest.mock('@electron/remote', () => ({ getGlobal: jest.fn() })); class BasicWE implements WithEquals { - static fromIds(ids: number[]): BasicWE[] { - return ids.map(id => new BasicWE(id)); - } + static fromIds(ids: number[]): BasicWE[] { + return ids.map(id => new BasicWE(id)); + } - static fromCombos(combos: { id: string | number, extra: string | null }[]): BasicWE[] { - return combos.map(({ id, extra }) => new BasicWE(id, extra)); - } + static fromCombos(combos: { id: string | number; extra: string | null }[]): BasicWE[] { + return combos.map(({ id, extra }) => new BasicWE(id, extra)); + } - id: string; - constructor(public id_: string | number, public readonly extra: string | null = null) { this.id = id_ + '' } + id: string; + constructor(public id_: string | number, public readonly extra: string | null = null) { + this.id = id_ + ''; + } - equals(other: BasicWE) { - return this.id == other.id && this.extra == other.extra; - } + equals(other: BasicWE) { + return this.id === other.id && this.extra === other.extra; + } } describe('getChanges tests', () => { - test('both null', () => { - const primary = null; - const trusted = null; - const changes = AutoVerifier.getChanges(primary, trusted); - expect(changes).toEqual({ - added: [], - updated: [], - deleted: [], - }); - }); + test('both null', () => { + const primary = null; + const trusted = null; + const changes = AutoVerifier.getChanges(primary, trusted); + expect(changes).toEqual({ + added: [], + updated: [], + deleted: [], + }); + }); - test('null primary gives trusted as added', () => { - const primary = null; - const trusted = BasicWE.fromIds([2, 3, 4]); - const changes = AutoVerifier.getChanges(primary, trusted); - expect(changes).toEqual({ - added: BasicWE.fromIds([2, 3, 4]), - updated: [], - deleted: [], - }); - }); - - test('null trusted gives primary as deleted', () => { - const primary = BasicWE.fromIds([2, 3, 4]); - const trusted = null; - const changes = AutoVerifier.getChanges(primary, trusted); - expect(changes).toEqual({ - added: [], - updated: [], - deleted: BasicWE.fromIds([2, 3, 4]), - }); - }); + test('null primary gives trusted as added', () => { + const primary = null; + const trusted = BasicWE.fromIds([2, 3, 4]); + const changes = AutoVerifier.getChanges(primary, trusted); + expect(changes).toEqual({ + added: BasicWE.fromIds([2, 3, 4]), + updated: [], + deleted: [], + }); + }); - test('equal', () => { - const primary = BasicWE.fromIds([2, 3, 4]); - const trusted = BasicWE.fromIds([2, 3, 4]); - const changes = AutoVerifier.getChanges(primary, trusted); - expect(changes).toEqual({ - added: [], - updated: [], - deleted: [], - }); - }); + test('null trusted gives primary as deleted', () => { + const primary = BasicWE.fromIds([2, 3, 4]); + const trusted = null; + const changes = AutoVerifier.getChanges(primary, trusted); + expect(changes).toEqual({ + added: [], + updated: [], + deleted: BasicWE.fromIds([2, 3, 4]), + }); + }); - test('added', () => { - const primary = BasicWE.fromIds([2, 3, 4]); - const trusted = BasicWE.fromIds([2, 3, 4, 5, 6]); - const changes = AutoVerifier.getChanges(primary, trusted); - expect(changes).toEqual({ - added: BasicWE.fromIds([5, 6]), - updated: [], - deleted: [], - }); - }); + test('equal', () => { + const primary = BasicWE.fromIds([2, 3, 4]); + const trusted = BasicWE.fromIds([2, 3, 4]); + const changes = AutoVerifier.getChanges(primary, trusted); + expect(changes).toEqual({ + added: [], + updated: [], + deleted: [], + }); + }); - test('changed', () => { - const primary = BasicWE.fromCombos([ { id: 2, extra: 'a' }, { id: 3, extra: 'b' }, { id: 4, extra: 'c' }, { id: 5, extra: 'd' } ]); - const trusted = BasicWE.fromCombos([ { id: 2, extra: '*' }, { id: 3, extra: 'b' }, { id: 4, extra: '*' }, { id: 5, extra: 'd' } ]); - const changes = AutoVerifier.getChanges(primary, trusted); - expect(changes).toEqual({ - added: [], - updated: [ - { oldDataPoint: new BasicWE(2, 'a'), newDataPoint: new BasicWE(2, '*') }, - { oldDataPoint: new BasicWE(4, 'c'), newDataPoint: new BasicWE(4, '*') }, - ], - deleted: [], - }); - }); + test('added', () => { + const primary = BasicWE.fromIds([2, 3, 4]); + const trusted = BasicWE.fromIds([2, 3, 4, 5, 6]); + const changes = AutoVerifier.getChanges(primary, trusted); + expect(changes).toEqual({ + added: BasicWE.fromIds([5, 6]), + updated: [], + deleted: [], + }); + }); - test('deleted', () => { - const primary = BasicWE.fromIds([2, 3, 4]); - const trusted = BasicWE.fromIds([2]); - const changes = AutoVerifier.getChanges(primary, trusted); - expect(changes).toEqual({ - added: [], - updated: [], - deleted: BasicWE.fromIds([3, 4]), - }); - }); + test('changed', () => { + const primary = BasicWE.fromCombos([ + { id: 2, extra: 'a' }, + { id: 3, extra: 'b' }, + { id: 4, extra: 'c' }, + { id: 5, extra: 'd' }, + ]); + const trusted = BasicWE.fromCombos([ + { id: 2, extra: '*' }, + { id: 3, extra: 'b' }, + { id: 4, extra: '*' }, + { id: 5, extra: 'd' }, + ]); + const changes = AutoVerifier.getChanges(primary, trusted); + expect(changes).toEqual({ + added: [], + updated: [ + { oldDataPoint: new BasicWE(2, 'a'), newDataPoint: new BasicWE(2, '*') }, + { oldDataPoint: new BasicWE(4, 'c'), newDataPoint: new BasicWE(4, '*') }, + ], + deleted: [], + }); + }); + + test('deleted', () => { + const primary = BasicWE.fromIds([2, 3, 4]); + const trusted = BasicWE.fromIds([2]); + const changes = AutoVerifier.getChanges(primary, trusted); + expect(changes).toEqual({ + added: [], + updated: [], + deleted: BasicWE.fromIds([3, 4]), + }); + }); }); describe('constructor/factory tests', () => { - test('constructor', () => { - 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('constructor', () => { + 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()); - 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()); + 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()); - 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()); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(null); + expect(av.trustedStatus).toBe('none'); + }); }); describe('fetchAndVerifyIfNeeded tests', () => { - class ManualPromise { - promise: Promise; - resolve: (value: T) => void; - reject: (reason?: any) => void; + class ManualPromise { + promise: Promise; + resolve: (value: T) => void; + reject: (reason: unknown) => void; - constructor() { - this.resolve = undefined as unknown as (value: T) => void; - this.reject = undefined as unknown as (reason?: any) => void; - this.promise = new Promise((resolve, reject) => { - this.resolve = resolve; - this.reject = reject; - }); - } - } + constructor() { + this.resolve = undefined as unknown as (value: T) => void; + this.reject = undefined as unknown as (reason: unknown) => void; + this.promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + } + } - function getManualsAndMocks(): { - nextPrimary: () => ManualPromise, - nextTrusted: () => ManualPromise, - nextVerify: () => ManualPromise, - primaryFunc: jest.Mock, []>, - trustedFunc: jest.Mock, []>, - verifyFunc: jest.Mock, [primaryResult: BasicWE | null, trustedResult: BasicWE | null]>, - } { - const manuals: { - primary: ManualPromise[], - trusted: ManualPromise[], - verify: ManualPromise[], - } = { - primary: [], - trusted: [], - verify: [], - }; + function getManualsAndMocks(): { + nextPrimary: () => ManualPromise; + nextTrusted: () => ManualPromise; + nextVerify: () => ManualPromise; + primaryFunc: jest.Mock, []>; + trustedFunc: jest.Mock, []>; + verifyFunc: jest.Mock, [primaryResult: BasicWE | null, trustedResult: BasicWE | null]>; + } { + const manuals: { + primary: ManualPromise[]; + trusted: ManualPromise[]; + verify: ManualPromise[]; + } = { + primary: [], + trusted: [], + verify: [], + }; - const calls: { - primary: number, - trusted: number, - verify: number, - } = { - primary: 0, - trusted: 0, - verify: 0 - }; + const calls: { + primary: number; + trusted: number; + verify: number; + } = { + primary: 0, + trusted: 0, + verify: 0, + }; - function getNext(manualList: ManualPromise[]) { - const next = new ManualPromise(); - manualList.push(next); - return next; - } + function getNext(manualList: ManualPromise[]) { + const next = new ManualPromise(); + manualList.push(next); + return next; + } - return { - nextPrimary: () => getNext(manuals.primary), - nextTrusted: () => getNext(manuals.trusted), - nextVerify: () => getNext(manuals.verify), - primaryFunc: jest.fn(() => manuals.primary[calls.primary++]!.promise), - trustedFunc: jest.fn(() => manuals.trusted[calls.trusted++]!.promise), - verifyFunc: jest.fn((_primaryResult: BasicWE | null, _trustedResult: BasicWE | null) => manuals.verify[calls.verify++]!.promise), - } - } + return { + nextPrimary: () => getNext(manuals.primary), + nextTrusted: () => getNext(manuals.trusted), + nextVerify: () => getNext(manuals.verify), + primaryFunc: jest.fn(() => manuals.primary[calls.primary++]!.promise), + trustedFunc: jest.fn(() => manuals.trusted[calls.trusted++]!.promise), + verifyFunc: jest.fn( + (_primaryResult: BasicWE | null, _trustedResult: BasicWE | null) => + manuals.verify[calls.verify++]!.promise, + ), + }; + } // this function makes it easier to do concurrent request // although its naming can make stuff a bit confusing depending on the situation // a means the 1st function call for each func AND the promise of the 1st fetchAndVerifyIfNeeded // b means the 2nd function call for each func AND the promise of the 2nd fetchAndVerifyIfNeeded - function fetchAndVerifyIfNeededManually( - av: AutoVerifier, - nextPrimary: () => ManualPromise, - nextTrusted: () => ManualPromise, - nextVerify: () => ManualPromise, - lazyVerify: boolean = false, - ) { - const state: { - primary: ManualPromise, - trusted: ManualPromise, - verify: ManualPromise, - result: T | null | undefined, - rejection: Error | undefined, - resultPromise: Promise, - } = { - primary: nextPrimary(), - trusted: nextTrusted(), - verify: nextVerify(), - result: undefined, - rejection: undefined, + function fetchAndVerifyIfNeededManually( + av: AutoVerifier, + nextPrimary: () => ManualPromise, + nextTrusted: () => ManualPromise, + nextVerify: () => ManualPromise, + lazyVerify = false, + ) { + const state: { + primary: ManualPromise; + trusted: ManualPromise; + 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), - } + }; - state.resultPromise - .then((value: T | null) => { state.result = value; }) - .catch((value: Error) => { state.rejection = value; }); + state.resultPromise + .then((value: T | null) => { + state.result = value; + }) + .catch((value: Error) => { + state.rejection = value; + }); - return state; - } + return state; + } - async function disjoint() { - await Util.sleep(0); - } + async function disjoint() { + await Util.sleep(0); + } - test('primary null, then trusted with value - cache miss, return from server', async () => { - const { - nextPrimary, nextTrusted, nextVerify, - primaryFunc, trustedFunc, verifyFunc - } = getManualsAndMocks(); + test('primary null, then trusted with value - cache miss, return from server', async () => { + const { nextPrimary, nextTrusted, nextVerify, primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); - const primary = nextPrimary(); - const trusted = nextTrusted(); - const verify = nextVerify(); + const primary = nextPrimary(); + const trusted = nextTrusted(); + const verify = nextVerify(); - const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); - const resultPromise = av.fetchAndVerifyIfNeeded(); - let result: BasicWE | null | undefined = undefined; - resultPromise.then(value => { result = value; }); + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); + const resultPromise = av.fetchAndVerifyIfNeeded(); + let result: BasicWE | null | undefined = undefined; + resultPromise.then(value => { + result = value; + }); - expect(av.primaryPromise).toBe(primary.promise); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('fetching'); - expect(primaryFunc).toHaveBeenCalled(); - expect(trustedFunc).toHaveBeenCalled(); + expect(av.primaryPromise).toBe(primary.promise); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('fetching'); + expect(primaryFunc).toHaveBeenCalled(); + expect(trustedFunc).toHaveBeenCalled(); - primary.resolve(null); - await disjoint(); + primary.resolve(null); + await disjoint(); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verifying'); + 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(); + expect(verifyFunc).toHaveBeenCalledTimes(0); + trusted.resolve(new BasicWE(2)); + await disjoint(); - expect(verifyFunc).toHaveBeenCalledWith(null, new BasicWE(2)); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verifying'); + expect(verifyFunc).toHaveBeenCalledWith(null, new BasicWE(2)); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('verifying'); - expect(result).toBeUndefined(); - verify.resolve(true); - await disjoint(); + expect(result).toBeUndefined(); + verify.resolve(true); + await disjoint(); - expect(result).toEqual(new BasicWE(2)); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verified'); + expect(result).toEqual(new BasicWE(2)); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('verified'); - await disjoint(); // sanity check + await disjoint(); // sanity check - expect(primaryFunc).toHaveBeenCalledTimes(1); - expect(trustedFunc).toHaveBeenCalledTimes(1); - expect(verifyFunc).toHaveBeenCalledTimes(1); - }); + expect(primaryFunc).toHaveBeenCalledTimes(1); + expect(trustedFunc).toHaveBeenCalledTimes(1); + expect(verifyFunc).toHaveBeenCalledTimes(1); + }); test('primary null, then trusted with value, then refetch, primary with value - primary use case', async () => { // the main use case! - const { - nextPrimary, nextTrusted, nextVerify, - primaryFunc, trustedFunc, verifyFunc - } = getManualsAndMocks(); + const { nextPrimary, nextTrusted, nextVerify, primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); - const primary1 = nextPrimary(); - const trusted = nextTrusted(); - const verify = nextVerify(); + const primary1 = nextPrimary(); + const trusted = nextTrusted(); + const verify = nextVerify(); - const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); - const result1Promise = av.fetchAndVerifyIfNeeded(); - let result1: BasicWE | null | undefined = undefined; - result1Promise.then(value => { result1 = value; }); + const result1Promise = av.fetchAndVerifyIfNeeded(); + let result1: BasicWE | null | undefined = undefined; + result1Promise.then(value => { + result1 = value; + }); - expect(av.primaryPromise).toBe(primary1.promise); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('fetching'); - expect(primaryFunc).toHaveBeenCalled(); - expect(trustedFunc).toHaveBeenCalled(); + expect(av.primaryPromise).toBe(primary1.promise); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('fetching'); + expect(primaryFunc).toHaveBeenCalled(); + expect(trustedFunc).toHaveBeenCalled(); - primary1.resolve(null); - await disjoint(); + primary1.resolve(null); + await disjoint(); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verifying'); + 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(); + expect(verifyFunc).toHaveBeenCalledTimes(0); + trusted.resolve(new BasicWE(2)); + await disjoint(); - expect(verifyFunc).toHaveBeenCalledWith(null, new BasicWE(2)); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verifying'); + expect(verifyFunc).toHaveBeenCalledWith(null, new BasicWE(2)); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('verifying'); - expect(result1).toBeUndefined(); - verify.resolve(true); - await disjoint(); + expect(result1).toBeUndefined(); + verify.resolve(true); + await disjoint(); - expect(result1).toEqual(new BasicWE(2)); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verified'); + expect(result1).toEqual(new BasicWE(2)); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('verified'); - const primary2 = nextPrimary(); + const primary2 = nextPrimary(); - const result2Promise = av.fetchAndVerifyIfNeeded(); - let result2: BasicWE | null | undefined = undefined; - result2Promise.then(value => { result2 = value; }); + const result2Promise = av.fetchAndVerifyIfNeeded(); + let result2: BasicWE | null | undefined = undefined; + result2Promise.then(value => { + result2 = value; + }); - expect(av.primaryPromise).toBe(primary2.promise); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verified'); - expect(primaryFunc).toHaveBeenCalledTimes(2); + expect(av.primaryPromise).toBe(primary2.promise); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('verified'); + expect(primaryFunc).toHaveBeenCalledTimes(2); expect(result2).toBeUndefined(); primary2.resolve(new BasicWE(2)); await disjoint(); expect(result2).toEqual(new BasicWE(2)); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verified'); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('verified'); - await disjoint(); // sanity check - - expect(primaryFunc).toHaveBeenCalledTimes(2); - expect(trustedFunc).toHaveBeenCalledTimes(1); - expect(verifyFunc).toHaveBeenCalledTimes(1); + await disjoint(); // sanity check + expect(primaryFunc).toHaveBeenCalledTimes(2); + expect(trustedFunc).toHaveBeenCalledTimes(1); + expect(verifyFunc).toHaveBeenCalledTimes(1); }); - test('primary with value, then trusted with value - cache hit, verify with server', async () => { - const { - nextPrimary, nextTrusted, nextVerify, - primaryFunc, trustedFunc, verifyFunc - } = getManualsAndMocks(); - - const primary = nextPrimary(); - const trusted = nextTrusted(); - const verify = nextVerify(); - - const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); - const resultPromise = av.fetchAndVerifyIfNeeded(); - let result: BasicWE | null | undefined = undefined; - resultPromise.then(value => { result = value; }); - - expect(av.primaryPromise).toBe(primary.promise); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('fetching'); - expect(primaryFunc).toHaveBeenCalled(); - 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(); - - primary.resolve(new BasicWE(2)); - await disjoint(); - - expect(result).toEqual(new BasicWE(2)); - 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(); - - expect(verifyFunc).toHaveBeenCalledWith(new BasicWE(2), new BasicWE(2)); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verifying'); - - verify.resolve(true); - await disjoint(); - - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verified'); - - await disjoint(); // sanity check - - expect(primaryFunc).toHaveBeenCalledTimes(1); - expect(trustedFunc).toHaveBeenCalledTimes(1); - expect(verifyFunc).toHaveBeenCalledTimes(1); - }); - - test('primary null, then trusted with null - cache miss, server confirms miss', async () => { - const { - nextPrimary, nextTrusted, nextVerify, - primaryFunc, trustedFunc, verifyFunc - } = getManualsAndMocks(); - - const primary = nextPrimary(); - const trusted = nextTrusted(); - const verify = nextVerify(); - - const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); - const resultPromise = av.fetchAndVerifyIfNeeded(); - let result: BasicWE | null | undefined = undefined; - resultPromise.then(value => { result = value; }); - - expect(av.primaryPromise).toBe(primary.promise); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('fetching'); - expect(primaryFunc).toHaveBeenCalled(); - expect(trustedFunc).toHaveBeenCalled(); - - primary.resolve(null); - 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(); - - expect(verifyFunc).toHaveBeenCalledWith(null, null); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verifying'); - - expect(result).toBeUndefined(); - verify.resolve(true); - await disjoint(); - - expect(result).toBeNull(); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(null); - expect(av.trustedStatus).toBe('none'); - - await disjoint(); // sanity check - - expect(primaryFunc).toHaveBeenCalledTimes(1); - expect(trustedFunc).toHaveBeenCalledTimes(1); - expect(verifyFunc).toHaveBeenCalledTimes(1); - }); - - test('primary with value, then trusted with null - cache hit, server deleted', async () => { - const { - nextPrimary, nextTrusted, nextVerify, - primaryFunc, trustedFunc, verifyFunc - } = getManualsAndMocks(); - - const primary = nextPrimary(); - const trusted = nextTrusted(); - const verify = nextVerify(); - - const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); - const resultPromise = av.fetchAndVerifyIfNeeded(); - let result: BasicWE | null | undefined = undefined; - resultPromise.then(value => { result = value; }); - - expect(av.primaryPromise).toBe(primary.promise); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('fetching'); - expect(primaryFunc).toHaveBeenCalled(); - expect(trustedFunc).toHaveBeenCalled(); - - expect(result).toBeUndefined(); - primary.resolve(new BasicWE(2)); - await disjoint(); - - expect(result).toEqual(new BasicWE(2)); - 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(); - - expect(verifyFunc).toHaveBeenCalledWith(new BasicWE(2), null); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verifying'); - - verify.resolve(true); - await disjoint(); - - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(null); - expect(av.trustedStatus).toBe('none'); - - await disjoint(); // sanity check - - expect(primaryFunc).toHaveBeenCalledTimes(1); - expect(trustedFunc).toHaveBeenCalledTimes(1); - expect(verifyFunc).toHaveBeenCalledTimes(1); - }); - - test('primary with null, lazy - lazy cache miss, need to ping server', async () => { - const { - nextPrimary, nextTrusted, nextVerify, - primaryFunc, trustedFunc, verifyFunc - } = getManualsAndMocks(); - - const primary = nextPrimary(); - const trusted = nextTrusted(); - const verify = nextVerify(); - - const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); - const resultPromise = av.fetchAndVerifyIfNeeded(true); - let result: BasicWE | null | undefined = undefined; - resultPromise.then(value => { result = value; }); - - expect(av.primaryPromise).toBe(primary.promise); - expect(av.trustedPromise).toBe(null); // trustedFunc will never be called - expect(av.trustedStatus).toBe('none'); - expect(primaryFunc).toHaveBeenCalled(); - - expect(trustedFunc).toHaveBeenCalledTimes(0); - primary.resolve(null); - await disjoint(); - - expect(trustedFunc).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' - - expect(verifyFunc).toHaveBeenCalledTimes(0); - trusted.resolve(new BasicWE(2)); - await disjoint(); - - expect(verifyFunc).toHaveBeenCalledWith(null, new BasicWE(2)); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verifying'); - - expect(primaryFunc).toHaveBeenCalledTimes(1); - expect(trustedFunc).toHaveBeenCalledTimes(1); - expect(verifyFunc).toHaveBeenCalledTimes(1); - - expect(result).toBeUndefined(); - verify.resolve(true); - await disjoint(); - - expect(result).toEqual(new BasicWE(2)); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verified'); - - await disjoint(); // sanity check - - expect(primaryFunc).toHaveBeenCalledTimes(1); - expect(trustedFunc).toHaveBeenCalledTimes(1); - expect(verifyFunc).toHaveBeenCalledTimes(1); - }); - - test('primary with value, lazy - lazy cache hit, no need to ping server', async () => { - const { - nextPrimary, - primaryFunc, trustedFunc, verifyFunc - } = getManualsAndMocks(); - - const primary = nextPrimary(); - - const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); - const resultPromise = av.fetchAndVerifyIfNeeded(true); - let result: BasicWE | null | undefined = undefined; - resultPromise.then(value => { result = value; }); - - expect(av.primaryPromise).toBe(primary.promise); - expect(av.trustedPromise).toBe(null); // trustedFunc will never be called - expect(av.trustedStatus).toBe('none'); - expect(primaryFunc).toHaveBeenCalled(); - - expect(result).toBeUndefined(); - primary.resolve(new BasicWE(2)); - await disjoint(); - - expect(result).toEqual(new BasicWE(2)); - expect(av.trustedStatus).toBe('none'); - - await disjoint(); // sanity check - - expect(primaryFunc).toHaveBeenCalledTimes(1); - expect(trustedFunc).toHaveBeenCalledTimes(0); - expect(verifyFunc).toHaveBeenCalledTimes(0); - }); - - test('primary rejects - cache fail', async () => { - // expecting the promise to reject, the server to succeed, but the verify function to never be called - const { - nextPrimary, nextTrusted, - primaryFunc, trustedFunc, verifyFunc - } = getManualsAndMocks(); - - const primary = nextPrimary(); - const trusted = nextTrusted(); - - const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); - const resultPromise = av.fetchAndVerifyIfNeeded(); - let rejection: Error | undefined = undefined; - resultPromise.catch(value => { rejection = value; }); - - expect(av.primaryPromise).toBe(primary.promise); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('fetching'); - expect(primaryFunc).toHaveBeenCalled(); - expect(trustedFunc).toHaveBeenCalled(); - expect(rejection).toBeUndefined(); - - primary.reject(new Error('rejection')); // this will also 'unverify' the AutoVerifier - await disjoint(); - - expect(rejection).toBeDefined(); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(null); - expect(av.trustedStatus).toBe('none'); - - await disjoint(); // sanity check - - expect(primaryFunc).toHaveBeenCalledTimes(1); - expect(trustedFunc).toHaveBeenCalledTimes(1); // notably, this server response will be thrown out - expect(verifyFunc).toHaveBeenCalledTimes(0); - - // TODO: Make sure the trusted rejecting later doesn't throw an uncaught promise rejection error - }); - - 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, - primaryFunc, trustedFunc, verifyFunc - } = getManualsAndMocks(); - - const primary = nextPrimary(); - const trusted = nextTrusted(); - - const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); - const resultPromise = av.fetchAndVerifyIfNeeded(); - let rejection: Error | undefined = undefined; - resultPromise.catch(value => { rejection = value; }); - - expect(av.primaryPromise).toBe(primary.promise); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('fetching'); - expect(primaryFunc).toHaveBeenCalled(); - expect(trustedFunc).toHaveBeenCalled(); - - primary.resolve(null); - await disjoint(); - - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verifying'); - - expect(rejection).toBeUndefined(); - trusted.reject(new Error('rejection')); - await disjoint(); - - expect(rejection).toBeDefined(); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(null); - expect(av.trustedStatus).toBe('none'); - - await disjoint(); // sanity check - - expect(primaryFunc).toHaveBeenCalledTimes(1); - expect(trustedFunc).toHaveBeenCalledTimes(1); // notably, this server response will be thrown out - 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, - primaryFunc, trustedFunc, verifyFunc - } = getManualsAndMocks(); - - const primary = nextPrimary(); - const trusted = nextTrusted(); - - const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); - const resultPromise = av.fetchAndVerifyIfNeeded(); - let result: BasicWE | null | undefined = undefined; - resultPromise.then(value => { result = value; }); - - expect(av.primaryPromise).toBe(primary.promise); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('fetching'); - expect(primaryFunc).toHaveBeenCalled(); - expect(trustedFunc).toHaveBeenCalled(); - - expect(result).toBeUndefined(); - primary.resolve(new BasicWE(2)); - await disjoint(); - - expect(result).toEqual(new BasicWE(2)); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verifying'); - - // suppress the warning about trusted rejecting after primary hit - const cwSpy = jest.spyOn(console, 'warn').mockImplementationOnce(() => {}).mockReset(); // suppress the warning - trusted.reject(new Error('rejection')); - await disjoint(); - - expect(cwSpy).toHaveBeenCalledTimes(1); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(null); - expect(av.trustedStatus).toBe('none'); - - await disjoint(); // sanity check - - expect(primaryFunc).toHaveBeenCalledTimes(1); - expect(trustedFunc).toHaveBeenCalledTimes(1); // notably, this server response will be thrown out - expect(verifyFunc).toHaveBeenCalledTimes(0); - }); - - test('primary null, then trusted with value, verifyFunc rejects - cache miss, return from server, verify failed', async () => { - const { - nextPrimary, nextTrusted, nextVerify, - primaryFunc, trustedFunc, verifyFunc - } = getManualsAndMocks(); - - const primary = nextPrimary(); - const trusted = nextTrusted(); - const verify = nextVerify(); - - const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); - const resultPromise = av.fetchAndVerifyIfNeeded(); - let result: BasicWE | null | undefined = undefined; - let rejection: unknown | undefined = undefined; - resultPromise - .then(value => { result = value; }) - .catch(value => { rejection = value; }); - - expect(av.primaryPromise).toBe(primary.promise); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('fetching'); - expect(primaryFunc).toHaveBeenCalled(); - expect(trustedFunc).toHaveBeenCalled(); - - primary.resolve(null); - 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(); - - expect(verifyFunc).toHaveBeenCalledWith(null, new BasicWE(2)); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verifying'); - - expect(result).toBeUndefined(); + test('primary with value, then trusted with value - cache hit, verify with server', async () => { + const { nextPrimary, nextTrusted, nextVerify, primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); + + const primary = nextPrimary(); + const trusted = nextTrusted(); + const verify = nextVerify(); + + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); + const resultPromise = av.fetchAndVerifyIfNeeded(); + let result: BasicWE | null | undefined = undefined; + resultPromise.then(value => { + result = value; + }); + + expect(av.primaryPromise).toBe(primary.promise); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('fetching'); + expect(primaryFunc).toHaveBeenCalled(); + 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(); + + primary.resolve(new BasicWE(2)); + await disjoint(); + + expect(result).toEqual(new BasicWE(2)); + 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(); + + expect(verifyFunc).toHaveBeenCalledWith(new BasicWE(2), new BasicWE(2)); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('verifying'); + + verify.resolve(true); + await disjoint(); + + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('verified'); + + await disjoint(); // sanity check + + expect(primaryFunc).toHaveBeenCalledTimes(1); + expect(trustedFunc).toHaveBeenCalledTimes(1); + expect(verifyFunc).toHaveBeenCalledTimes(1); + }); + + test('primary null, then trusted with null - cache miss, server confirms miss', async () => { + const { nextPrimary, nextTrusted, nextVerify, primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); + + const primary = nextPrimary(); + const trusted = nextTrusted(); + const verify = nextVerify(); + + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); + const resultPromise = av.fetchAndVerifyIfNeeded(); + let result: BasicWE | null | undefined = undefined; + resultPromise.then(value => { + result = value; + }); + + expect(av.primaryPromise).toBe(primary.promise); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('fetching'); + expect(primaryFunc).toHaveBeenCalled(); + expect(trustedFunc).toHaveBeenCalled(); + + primary.resolve(null); + 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(); + + expect(verifyFunc).toHaveBeenCalledWith(null, null); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('verifying'); + + expect(result).toBeUndefined(); + verify.resolve(true); + await disjoint(); + + expect(result).toBeNull(); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(null); + expect(av.trustedStatus).toBe('none'); + + await disjoint(); // sanity check + + expect(primaryFunc).toHaveBeenCalledTimes(1); + expect(trustedFunc).toHaveBeenCalledTimes(1); + expect(verifyFunc).toHaveBeenCalledTimes(1); + }); + + test('primary with value, then trusted with null - cache hit, server deleted', async () => { + const { nextPrimary, nextTrusted, nextVerify, primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); + + const primary = nextPrimary(); + const trusted = nextTrusted(); + const verify = nextVerify(); + + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); + const resultPromise = av.fetchAndVerifyIfNeeded(); + let result: BasicWE | null | undefined = undefined; + resultPromise.then(value => { + result = value; + }); + + expect(av.primaryPromise).toBe(primary.promise); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('fetching'); + expect(primaryFunc).toHaveBeenCalled(); + expect(trustedFunc).toHaveBeenCalled(); + + expect(result).toBeUndefined(); + primary.resolve(new BasicWE(2)); + await disjoint(); + + expect(result).toEqual(new BasicWE(2)); + 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(); + + expect(verifyFunc).toHaveBeenCalledWith(new BasicWE(2), null); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('verifying'); + + verify.resolve(true); + await disjoint(); + + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(null); + expect(av.trustedStatus).toBe('none'); + + await disjoint(); // sanity check + + expect(primaryFunc).toHaveBeenCalledTimes(1); + expect(trustedFunc).toHaveBeenCalledTimes(1); + expect(verifyFunc).toHaveBeenCalledTimes(1); + }); + + test('primary with null, lazy - lazy cache miss, need to ping server', async () => { + const { nextPrimary, nextTrusted, nextVerify, primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); + + const primary = nextPrimary(); + const trusted = nextTrusted(); + const verify = nextVerify(); + + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); + const resultPromise = av.fetchAndVerifyIfNeeded(true); + let result: BasicWE | null | undefined = undefined; + resultPromise.then(value => { + result = value; + }); + + expect(av.primaryPromise).toBe(primary.promise); + expect(av.trustedPromise).toBe(null); // trustedFunc will never be called + expect(av.trustedStatus).toBe('none'); + expect(primaryFunc).toHaveBeenCalled(); + + expect(trustedFunc).toHaveBeenCalledTimes(0); + primary.resolve(null); + await disjoint(); + + expect(trustedFunc).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' + + expect(verifyFunc).toHaveBeenCalledTimes(0); + trusted.resolve(new BasicWE(2)); + await disjoint(); + + expect(verifyFunc).toHaveBeenCalledWith(null, new BasicWE(2)); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('verifying'); + + expect(primaryFunc).toHaveBeenCalledTimes(1); + expect(trustedFunc).toHaveBeenCalledTimes(1); + expect(verifyFunc).toHaveBeenCalledTimes(1); + + expect(result).toBeUndefined(); + verify.resolve(true); + await disjoint(); + + expect(result).toEqual(new BasicWE(2)); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('verified'); + + await disjoint(); // sanity check + + expect(primaryFunc).toHaveBeenCalledTimes(1); + expect(trustedFunc).toHaveBeenCalledTimes(1); + expect(verifyFunc).toHaveBeenCalledTimes(1); + }); + + test('primary with value, lazy - lazy cache hit, no need to ping server', async () => { + const { nextPrimary, primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); + + const primary = nextPrimary(); + + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); + const resultPromise = av.fetchAndVerifyIfNeeded(true); + let result: BasicWE | null | undefined = undefined; + resultPromise.then(value => { + result = value; + }); + + expect(av.primaryPromise).toBe(primary.promise); + expect(av.trustedPromise).toBe(null); // trustedFunc will never be called + expect(av.trustedStatus).toBe('none'); + expect(primaryFunc).toHaveBeenCalled(); + + expect(result).toBeUndefined(); + primary.resolve(new BasicWE(2)); + await disjoint(); + + expect(result).toEqual(new BasicWE(2)); + expect(av.trustedStatus).toBe('none'); + + await disjoint(); // sanity check + + expect(primaryFunc).toHaveBeenCalledTimes(1); + expect(trustedFunc).toHaveBeenCalledTimes(0); + expect(verifyFunc).toHaveBeenCalledTimes(0); + }); + + test('primary rejects - cache fail', async () => { + // expecting the promise to reject, the server to succeed, but the verify function to never be called + const { nextPrimary, nextTrusted, primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); + + const primary = nextPrimary(); + const trusted = nextTrusted(); + + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); + const resultPromise = av.fetchAndVerifyIfNeeded(); + let rejection: Error | undefined = undefined; + resultPromise.catch(value => { + rejection = value; + }); + + expect(av.primaryPromise).toBe(primary.promise); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('fetching'); + expect(primaryFunc).toHaveBeenCalled(); + expect(trustedFunc).toHaveBeenCalled(); expect(rejection).toBeUndefined(); - verify.reject(new Error('rejection')) - await disjoint(); + + primary.reject(new Error('rejection')); // this will also 'unverify' the AutoVerifier + await disjoint(); + + expect(rejection).toBeDefined(); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(null); + expect(av.trustedStatus).toBe('none'); + + await disjoint(); // sanity check + + expect(primaryFunc).toHaveBeenCalledTimes(1); + expect(trustedFunc).toHaveBeenCalledTimes(1); // notably, this server response will be thrown out + expect(verifyFunc).toHaveBeenCalledTimes(0); + + // TODO: Make sure the trusted rejecting later doesn't throw an uncaught promise rejection error + }); + + 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, primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); + + const primary = nextPrimary(); + const trusted = nextTrusted(); + + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); + const resultPromise = av.fetchAndVerifyIfNeeded(); + let rejection: Error | undefined = undefined; + resultPromise.catch(value => { + rejection = value; + }); + + expect(av.primaryPromise).toBe(primary.promise); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('fetching'); + expect(primaryFunc).toHaveBeenCalled(); + expect(trustedFunc).toHaveBeenCalled(); + + primary.resolve(null); + await disjoint(); + + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('verifying'); + + expect(rejection).toBeUndefined(); + trusted.reject(new Error('rejection')); + await disjoint(); + + expect(rejection).toBeDefined(); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(null); + expect(av.trustedStatus).toBe('none'); + + await disjoint(); // sanity check + + expect(primaryFunc).toHaveBeenCalledTimes(1); + expect(trustedFunc).toHaveBeenCalledTimes(1); // notably, this server response will be thrown out + 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, primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); + + const primary = nextPrimary(); + const trusted = nextTrusted(); + + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); + const resultPromise = av.fetchAndVerifyIfNeeded(); + let result: BasicWE | null | undefined = undefined; + resultPromise.then(value => { + result = value; + }); + + expect(av.primaryPromise).toBe(primary.promise); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('fetching'); + expect(primaryFunc).toHaveBeenCalled(); + expect(trustedFunc).toHaveBeenCalled(); + + expect(result).toBeUndefined(); + primary.resolve(new BasicWE(2)); + await disjoint(); + + expect(result).toEqual(new BasicWE(2)); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('verifying'); + + // suppress the warning about trusted rejecting after primary hit + const cwSpy = jest + .spyOn(console, 'warn') + .mockImplementationOnce(() => undefined) + .mockReset(); // suppress the warning + trusted.reject(new Error('rejection')); + await disjoint(); + + expect(cwSpy).toHaveBeenCalledTimes(1); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(null); + expect(av.trustedStatus).toBe('none'); + + await disjoint(); // sanity check + + expect(primaryFunc).toHaveBeenCalledTimes(1); + expect(trustedFunc).toHaveBeenCalledTimes(1); // notably, this server response will be thrown out + expect(verifyFunc).toHaveBeenCalledTimes(0); + }); + + test('primary null, then trusted with value, verifyFunc rejects - cache miss, return from server, verify failed', async () => { + const { nextPrimary, nextTrusted, nextVerify, primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); + + const primary = nextPrimary(); + const trusted = nextTrusted(); + const verify = nextVerify(); + + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); + const resultPromise = av.fetchAndVerifyIfNeeded(); + let result: BasicWE | null | undefined = undefined; + let rejection: unknown | undefined = undefined; + resultPromise + .then(value => { + result = value; + }) + .catch(value => { + rejection = value; + }); + + expect(av.primaryPromise).toBe(primary.promise); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('fetching'); + expect(primaryFunc).toHaveBeenCalled(); + expect(trustedFunc).toHaveBeenCalled(); + + primary.resolve(null); + 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(); + + expect(verifyFunc).toHaveBeenCalledWith(null, new BasicWE(2)); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('verifying'); + + expect(result).toBeUndefined(); + expect(rejection).toBeUndefined(); + verify.reject(new Error('rejection')); + await disjoint(); expect(result).toBeUndefined(); expect(rejection).toBeDefined(); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(null); - expect(av.trustedStatus).toBe('none'); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(null); + expect(av.trustedStatus).toBe('none'); - await disjoint(); // sanity check + await disjoint(); // sanity check - expect(primaryFunc).toHaveBeenCalledTimes(1); - expect(trustedFunc).toHaveBeenCalledTimes(1); - expect(verifyFunc).toHaveBeenCalledTimes(1); - }); + expect(primaryFunc).toHaveBeenCalledTimes(1); + expect(trustedFunc).toHaveBeenCalledTimes(1); + expect(verifyFunc).toHaveBeenCalledTimes(1); + }); - test('ab, a: primary with value, b: primary dedup, ab: trusted dedup with value', async () => { - const { - nextPrimary, nextTrusted, nextVerify, - primaryFunc, trustedFunc, verifyFunc - } = getManualsAndMocks(); + test('ab, a: primary with value, 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 av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); - const a = fetchAndVerifyIfNeededManually(av, nextPrimary, nextTrusted, nextVerify); + 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(); + 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'); - - expect(a.result).toBeUndefined(); - expect(b.result).toBeUndefined(); - a.primary.resolve(new BasicWE(2)); - await disjoint(); + const b = fetchAndVerifyIfNeededManually(av, nextPrimary, nextTrusted, nextVerify); - 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'); + 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(verifyFunc).toHaveBeenCalledTimes(0); - a.trusted.resolve(new BasicWE(2)); - await disjoint(); + expect(a.result).toBeUndefined(); + expect(b.result).toBeUndefined(); + a.primary.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'); + 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.verify.resolve(true); - await disjoint(); + expect(verifyFunc).toHaveBeenCalledTimes(0); + a.trusted.resolve(new BasicWE(2)); + await disjoint(); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(a.trusted.promise); - expect(av.trustedStatus).toBe('verified'); + 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'); - await disjoint(); // sanity check + a.verify.resolve(true); + await disjoint(); - // 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); - }); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(a.trusted.promise); + expect(av.trustedStatus).toBe('verified'); - 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); + await disjoint(); // sanity check - const a = fetchAndVerifyIfNeededManually(av, nextPrimary, nextTrusted, nextVerify); + // 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); + }); - 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(); + test('ab, a: primary with null, b: primary dedup, ab: trusted dedup with value', async () => { + const { nextPrimary, nextTrusted, nextVerify, primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); - 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(); + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(a.trusted.promise); - expect(av.trustedStatus).toBe('verifying'); + const a = fetchAndVerifyIfNeededManually(av, nextPrimary, nextTrusted, nextVerify); - expect(verifyFunc).toHaveBeenCalledTimes(0); - a.trusted.resolve(new BasicWE(2)); - await disjoint(); + 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(); - expect(verifyFunc).toHaveBeenCalledWith(null, new BasicWE(2)); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(a.trusted.promise); - expect(av.trustedStatus).toBe('verifying'); + const b = fetchAndVerifyIfNeededManually(av, nextPrimary, nextTrusted, nextVerify); - 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'); + 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'); - await disjoint(); // sanity check + a.primary.resolve(null); + await disjoint(); - // 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); - }); + 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); + }); 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 { 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 }); + 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(); + 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(); + resultPromise2.catch(value => { + rejection2 = value; + }); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verifying'); + expect(av.primaryPromise).toBe(primary.promise); // doesn't change + expect(av.trustedPromise).toBe(trusted.promise); // doesn't change + expect(av.trustedStatus).toBe('fetching'); - trusted.reject(new Error('rejection')); - await disjoint(); + primary.resolve(null); + await disjoint(); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(null); - expect(av.trustedStatus).toBe('none'); + 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 + 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); - }); + // 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('ab, a: primary with null, b: primary dedup, ab: trusted dedup with value, verifyFunc false', 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(false); - 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(null); - expect(av.trustedStatus).toBe('none'); - - await disjoint(); // sanity check - - expect(primaryFunc).toHaveBeenCalledTimes(1); - expect(trustedFunc).toHaveBeenCalledTimes(1); - expect(verifyFunc).toHaveBeenCalledTimes(1); - }); - - test('a: primary with null, b: primary re-fetch null, a: trusted with value, b: dedup', 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(); - - a.primary.resolve(null); - await disjoint(); - - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(a.trusted.promise); - expect(av.trustedStatus).toBe('verifying'); - - const b = fetchAndVerifyIfNeededManually(av, nextPrimary, nextTrusted, nextVerify); - - expect(av.primaryPromise).toBe(b.primary.promise); // notably, primaryFunc gets called again - expect(av.trustedPromise).toBe(a.trusted.promise); - expect(av.trustedStatus).toBe('verifying'); - - b.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 - - expect(primaryFunc).toHaveBeenCalledTimes(2); - expect(trustedFunc).toHaveBeenCalledTimes(1); - expect(verifyFunc).toHaveBeenCalledTimes(1); - }); - - test('primary null, unverify during first trusted fetch, second trusted with value - cache miss, unverify during fetch, return from server', async () => { - const { - nextPrimary, nextTrusted, nextVerify, - primaryFunc, trustedFunc, verifyFunc - } = getManualsAndMocks(); - - const primary = nextPrimary(); - const trusted1 = nextTrusted(); - const trusted2 = nextTrusted(); - const verify = nextVerify(); - - const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); - const resultPromise = av.fetchAndVerifyIfNeeded(); - let result: BasicWE | null | undefined = undefined; - resultPromise.then(value => { result = value; }); - - expect(av.primaryPromise).toBe(primary.promise); - expect(av.trustedPromise).toBe(trusted1.promise); - expect(av.trustedStatus).toBe('fetching'); - expect(primaryFunc).toHaveBeenCalled(); - expect(trustedFunc).toHaveBeenCalledTimes(1); - - primary.resolve(null); - await disjoint(); - - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted1.promise); - expect(av.trustedStatus).toBe('verifying'); - - av.unverify(); - - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(null); - expect(av.trustedStatus).toBe('none'); - - const cwSpy = jest.spyOn(console, 'warn').mockImplementationOnce(() => {}).mockReset(); // suppress the warning - trusted1.resolve(new BasicWE(2)); - await disjoint(); - - expect(cwSpy).toHaveBeenCalledTimes(1); - expect(trustedFunc).toHaveBeenCalledTimes(2); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted2.promise); - expect(av.trustedStatus).toBe('verifying'); - - expect(verifyFunc).toHaveBeenCalledTimes(0); - trusted2.resolve(new BasicWE(3)); - await disjoint(); - - expect(verifyFunc).toHaveBeenCalledWith(null, new BasicWE(3)); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted2.promise); - expect(av.trustedStatus).toBe('verifying'); - - expect(result).toBeUndefined(); - verify.resolve(true); - await disjoint(); - - expect(result).toEqual(new BasicWE(3)); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted2.promise); - expect(av.trustedStatus).toBe('verified'); - - await disjoint(); // sanity check - - expect(primaryFunc).toHaveBeenCalledTimes(1); - expect(trustedFunc).toHaveBeenCalledTimes(2); - expect(verifyFunc).toHaveBeenCalledTimes(1); - }); - - test('primary null, unverify during verify, result is original trusted - cache miss, unverify during verify, return from server', async () => { - const { - nextPrimary, nextTrusted, nextVerify, - primaryFunc, trustedFunc, verifyFunc - } = getManualsAndMocks(); - - const primary = nextPrimary(); - const trusted = nextTrusted(); - const verify = nextVerify(); - - const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); - const resultPromise = av.fetchAndVerifyIfNeeded(); - let result: BasicWE | null | undefined = undefined; - resultPromise.then(value => { result = value; }); - - expect(av.primaryPromise).toBe(primary.promise); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('fetching'); - expect(primaryFunc).toHaveBeenCalled(); - expect(trustedFunc).toHaveBeenCalledTimes(1); - - primary.resolve(null); - 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(); - - expect(verifyFunc).toHaveBeenCalledWith(null, new BasicWE(2)); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(trusted.promise); - expect(av.trustedStatus).toBe('verifying'); - - av.unverify(); // during the verify function call - - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(null); - expect(av.trustedStatus).toBe('none'); - - expect(result).toBeUndefined(); - verify.resolve(true); - await disjoint(); - - // result is the value of the trusted promise, but the trusted will be re-called on next fetch - expect(result).toEqual(new BasicWE(2)); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(null); - expect(av.trustedStatus).toBe('none'); - - await disjoint(); // sanity check - - expect(primaryFunc).toHaveBeenCalledTimes(1); - expect(trustedFunc).toHaveBeenCalledTimes(1); - expect(verifyFunc).toHaveBeenCalledTimes(1); - }); - - test('ab: a: primary with null, b: primary dedup, ab: reject trusted fetch and unverify, second trusted 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); - const b = 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(); - - a.primary.resolve(null); - await disjoint(); - - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(a.trusted.promise); - expect(av.trustedStatus).toBe('verifying'); - - a.trusted.reject(new Error('rejection')); - av.unverify() - - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(null); - expect(av.trustedStatus).toBe('none'); - - const cwSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}).mockReset(); // suppress the warning - await disjoint(); - - expect(cwSpy).toHaveBeenCalledTimes(2); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(b.trusted.promise); - expect(av.trustedStatus).toBe('verifying'); - - expect(verifyFunc).toHaveBeenCalledTimes(0); - b.trusted.resolve(new BasicWE(2)); - await disjoint(); - - expect(verifyFunc).toHaveBeenCalledWith(null, new BasicWE(2)); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(b.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(b.trusted.promise); - expect(av.trustedStatus).toBe('verified'); - - await disjoint(); - - expect(primaryFunc).toHaveBeenCalledTimes(1); - expect(trustedFunc).toHaveBeenCalledTimes(2); - expect(verifyFunc).toHaveBeenCalledTimes(1); - }); - - - test('ab: a: primary with null, b: primary dedup, ab: resolve trusted fetch and unverify, second trusted 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); - const b = 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(); - - a.primary.resolve(null); - await disjoint(); - - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(a.trusted.promise); - expect(av.trustedStatus).toBe('verifying'); - - a.trusted.resolve(new BasicWE(2)); - av.unverify() - - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(null); - expect(av.trustedStatus).toBe('none'); - - await disjoint(); - - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(b.trusted.promise); - expect(av.trustedStatus).toBe('verifying'); - - expect(verifyFunc).toHaveBeenCalledTimes(0); - b.trusted.resolve(new BasicWE(3)); - await disjoint(); - - expect(verifyFunc).toHaveBeenCalledWith(null, new BasicWE(3)); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(b.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(3)); - expect(b.result).toEqual(new BasicWE(3)); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(b.trusted.promise); - expect(av.trustedStatus).toBe('verified'); - - await disjoint(); - - expect(primaryFunc).toHaveBeenCalledTimes(1); - expect(trustedFunc).toHaveBeenCalledTimes(2); - expect(verifyFunc).toHaveBeenCalledTimes(1); - }); + 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(false); + 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(null); + expect(av.trustedStatus).toBe('none'); + + await disjoint(); // sanity check + + expect(primaryFunc).toHaveBeenCalledTimes(1); + expect(trustedFunc).toHaveBeenCalledTimes(1); + expect(verifyFunc).toHaveBeenCalledTimes(1); + }); + + test('a: primary with null, b: primary re-fetch null, a: trusted with value, b: dedup', 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(); + + a.primary.resolve(null); + await disjoint(); + + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(a.trusted.promise); + expect(av.trustedStatus).toBe('verifying'); + + const b = fetchAndVerifyIfNeededManually(av, nextPrimary, nextTrusted, nextVerify); + + expect(av.primaryPromise).toBe(b.primary.promise); // notably, primaryFunc gets called again + expect(av.trustedPromise).toBe(a.trusted.promise); + expect(av.trustedStatus).toBe('verifying'); + + b.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 + + expect(primaryFunc).toHaveBeenCalledTimes(2); + expect(trustedFunc).toHaveBeenCalledTimes(1); + expect(verifyFunc).toHaveBeenCalledTimes(1); + }); + + test('primary null, unverify during first trusted fetch, second trusted with value - cache miss, unverify during fetch, return from server', async () => { + const { nextPrimary, nextTrusted, nextVerify, primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); + + const primary = nextPrimary(); + const trusted1 = nextTrusted(); + const trusted2 = nextTrusted(); + const verify = nextVerify(); + + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); + const resultPromise = av.fetchAndVerifyIfNeeded(); + let result: BasicWE | null | undefined = undefined; + resultPromise.then(value => { + result = value; + }); + + expect(av.primaryPromise).toBe(primary.promise); + expect(av.trustedPromise).toBe(trusted1.promise); + expect(av.trustedStatus).toBe('fetching'); + expect(primaryFunc).toHaveBeenCalled(); + expect(trustedFunc).toHaveBeenCalledTimes(1); + + primary.resolve(null); + await disjoint(); + + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(trusted1.promise); + expect(av.trustedStatus).toBe('verifying'); + + av.unverify(); + + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(null); + expect(av.trustedStatus).toBe('none'); + + // eslint-disable-next-line no-empty-function + // eslint-disable-next-line no-empty + const cwSpy = jest + .spyOn(console, 'warn') + .mockImplementationOnce(() => undefined) + .mockReset(); // suppress the warning + trusted1.resolve(new BasicWE(2)); + await disjoint(); + + expect(cwSpy).toHaveBeenCalledTimes(1); + expect(trustedFunc).toHaveBeenCalledTimes(2); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(trusted2.promise); + expect(av.trustedStatus).toBe('verifying'); + + expect(verifyFunc).toHaveBeenCalledTimes(0); + trusted2.resolve(new BasicWE(3)); + await disjoint(); + + expect(verifyFunc).toHaveBeenCalledWith(null, new BasicWE(3)); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(trusted2.promise); + expect(av.trustedStatus).toBe('verifying'); + + expect(result).toBeUndefined(); + verify.resolve(true); + await disjoint(); + + expect(result).toEqual(new BasicWE(3)); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(trusted2.promise); + expect(av.trustedStatus).toBe('verified'); + + await disjoint(); // sanity check + + expect(primaryFunc).toHaveBeenCalledTimes(1); + expect(trustedFunc).toHaveBeenCalledTimes(2); + expect(verifyFunc).toHaveBeenCalledTimes(1); + }); + + test('primary null, unverify during verify, result is original trusted - cache miss, unverify during verify, return from server', async () => { + const { nextPrimary, nextTrusted, nextVerify, primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); + + const primary = nextPrimary(); + const trusted = nextTrusted(); + const verify = nextVerify(); + + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); + const resultPromise = av.fetchAndVerifyIfNeeded(); + let result: BasicWE | null | undefined = undefined; + resultPromise.then(value => { + result = value; + }); + + expect(av.primaryPromise).toBe(primary.promise); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('fetching'); + expect(primaryFunc).toHaveBeenCalled(); + expect(trustedFunc).toHaveBeenCalledTimes(1); + + primary.resolve(null); + 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(); + + expect(verifyFunc).toHaveBeenCalledWith(null, new BasicWE(2)); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(trusted.promise); + expect(av.trustedStatus).toBe('verifying'); + + av.unverify(); // during the verify function call + + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(null); + expect(av.trustedStatus).toBe('none'); + + expect(result).toBeUndefined(); + verify.resolve(true); + await disjoint(); + + // result is the value of the trusted promise, but the trusted will be re-called on next fetch + expect(result).toEqual(new BasicWE(2)); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(null); + expect(av.trustedStatus).toBe('none'); + + await disjoint(); // sanity check + + expect(primaryFunc).toHaveBeenCalledTimes(1); + expect(trustedFunc).toHaveBeenCalledTimes(1); + expect(verifyFunc).toHaveBeenCalledTimes(1); + }); + + test('ab: a: primary with null, b: primary dedup, ab: reject trusted fetch and unverify, second trusted 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); + const b = 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(); + + a.primary.resolve(null); + await disjoint(); + + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(a.trusted.promise); + expect(av.trustedStatus).toBe('verifying'); + + a.trusted.reject(new Error('rejection')); + av.unverify(); + + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(null); + expect(av.trustedStatus).toBe('none'); + + const cwSpy = jest + .spyOn(console, 'warn') + .mockImplementation(() => undefined) + .mockReset(); // suppress the warning + await disjoint(); + + expect(cwSpy).toHaveBeenCalledTimes(2); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(b.trusted.promise); + expect(av.trustedStatus).toBe('verifying'); + + expect(verifyFunc).toHaveBeenCalledTimes(0); + b.trusted.resolve(new BasicWE(2)); + await disjoint(); + + expect(verifyFunc).toHaveBeenCalledWith(null, new BasicWE(2)); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(b.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(b.trusted.promise); + expect(av.trustedStatus).toBe('verified'); + + await disjoint(); + + expect(primaryFunc).toHaveBeenCalledTimes(1); + expect(trustedFunc).toHaveBeenCalledTimes(2); + expect(verifyFunc).toHaveBeenCalledTimes(1); + }); + + test('ab: a: primary with null, b: primary dedup, ab: resolve trusted fetch and unverify, second trusted 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); + const b = 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(); + + a.primary.resolve(null); + await disjoint(); + + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(a.trusted.promise); + expect(av.trustedStatus).toBe('verifying'); + + a.trusted.resolve(new BasicWE(2)); + av.unverify(); + + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(null); + expect(av.trustedStatus).toBe('none'); + + await disjoint(); + + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(b.trusted.promise); + expect(av.trustedStatus).toBe('verifying'); + + expect(verifyFunc).toHaveBeenCalledTimes(0); + b.trusted.resolve(new BasicWE(3)); + await disjoint(); + + expect(verifyFunc).toHaveBeenCalledWith(null, new BasicWE(3)); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(b.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(3)); + expect(b.result).toEqual(new BasicWE(3)); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(b.trusted.promise); + expect(av.trustedStatus).toBe('verified'); + + await disjoint(); + + expect(primaryFunc).toHaveBeenCalledTimes(1); + expect(trustedFunc).toHaveBeenCalledTimes(2); + expect(verifyFunc).toHaveBeenCalledTimes(1); + }); test('ab: a: primary with null, b: primary dedup, ab: unverify during verify, ab: resolve with trusted anyway', async () => { - const { - nextPrimary, nextTrusted, nextVerify, - primaryFunc, trustedFunc, verifyFunc - } = getManualsAndMocks(); - - const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); + const { nextPrimary, nextTrusted, nextVerify, primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); - const a = fetchAndVerifyIfNeededManually(av, nextPrimary, nextTrusted, nextVerify); + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); - 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 a = fetchAndVerifyIfNeededManually(av, nextPrimary, nextTrusted, 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 - expect(av.trustedStatus).toBe('fetching'); - - a.primary.resolve(null); - await disjoint(); + 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(); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(a.trusted.promise); - expect(av.trustedStatus).toBe('verifying'); + const b = fetchAndVerifyIfNeededManually(av, nextPrimary, nextTrusted, nextVerify); - expect(verifyFunc).toHaveBeenCalledTimes(0); - a.trusted.resolve(new BasicWE(2)); - await disjoint(); + 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(verifyFunc).toHaveBeenCalledWith(null, new BasicWE(2)); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(a.trusted.promise); - expect(av.trustedStatus).toBe('verifying'); + 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(); - expect(a.result).toBeUndefined(); - expect(b.result).toBeUndefined(); - av.unverify(); - 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(null); - expect(av.trustedStatus).toBe('none'); + a.verify.resolve(true); + await disjoint(); - await disjoint(); // sanity check + expect(a.result).toEqual(new BasicWE(2)); + expect(b.result).toEqual(new BasicWE(2)); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(null); + expect(av.trustedStatus).toBe('none'); - // 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); + 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); }); test('ab: a: primary with null, b: primary dedup, ab: verifyFunc false, ab: resolve with trusted', async () => { - const { - nextPrimary, nextTrusted, nextVerify, - primaryFunc, trustedFunc, verifyFunc - } = getManualsAndMocks(); + const { nextPrimary, nextTrusted, nextVerify, primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); - const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); - const a = fetchAndVerifyIfNeededManually(av, nextPrimary, nextTrusted, nextVerify); + 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(); + 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); + 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'); + 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(); + a.primary.resolve(null); + await disjoint(); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(a.trusted.promise); - expect(av.trustedStatus).toBe('verifying'); + 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).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(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(); + expect(a.result).toBeUndefined(); + expect(b.result).toBeUndefined(); - a.verify.resolve(false); - await disjoint(); + a.verify.resolve(false); + 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(null); - expect(av.trustedStatus).toBe('none'); + expect(a.result).toEqual(new BasicWE(2)); + expect(b.result).toEqual(new BasicWE(2)); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(null); + expect(av.trustedStatus).toBe('none'); - await disjoint(); // sanity check + 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); + // 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); }); test('ab: a: primary with null, b: primary dedup, ab: verifyFunc false, ab: resolve with trusted', async () => { - const { - nextPrimary, nextTrusted, nextVerify, - primaryFunc, trustedFunc, verifyFunc - } = getManualsAndMocks(); + const { nextPrimary, nextTrusted, nextVerify, primaryFunc, trustedFunc, verifyFunc } = getManualsAndMocks(); - const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); + const av = new AutoVerifier(primaryFunc, trustedFunc, verifyFunc); - const a = fetchAndVerifyIfNeededManually(av, nextPrimary, nextTrusted, nextVerify); + 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(); + 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); + 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'); + 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(); + a.primary.resolve(null); + await disjoint(); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(a.trusted.promise); - expect(av.trustedStatus).toBe('verifying'); + 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).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(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(); + expect(a.result).toBeUndefined(); + expect(b.result).toBeUndefined(); - a.verify.reject(new Error('rejection')) - await disjoint(); + a.verify.reject(new Error('rejection')); + await disjoint(); - expect(a.result).toBeUndefined(); - expect(b.result).toBeUndefined(); + expect(a.result).toBeUndefined(); + expect(b.result).toBeUndefined(); expect(a.rejection).toBeDefined(); expect(b.rejection).toBeDefined(); - expect(av.primaryPromise).toBe(null); - expect(av.trustedPromise).toBe(null); - expect(av.trustedStatus).toBe('none'); + expect(av.primaryPromise).toBe(null); + expect(av.trustedPromise).toBe(null); + expect(av.trustedStatus).toBe('none'); - await disjoint(); // sanity check + 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); + // 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); }); // not possible (and therefore not tested) list