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