common deduped request rejection test
This commit is contained in:
parent
0df23134a1
commit
3c22db0a2c
@ -826,7 +826,7 @@ describe('fetchAndVerifyIfNeeded tests', () => {
|
|||||||
expect(verifyFunc).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 () => {
|
test('primary null, unverify during first trusted fetch, second trusted with value - cache miss, unverify during fetch, return from server', async () => {
|
||||||
const {
|
const {
|
||||||
nextPrimary, nextTrusted, nextVerify,
|
nextPrimary, nextTrusted, nextVerify,
|
||||||
primaryFunc, trustedFunc, verifyFunc
|
primaryFunc, trustedFunc, verifyFunc
|
||||||
@ -855,19 +855,19 @@ describe('fetchAndVerifyIfNeeded tests', () => {
|
|||||||
expect(av.trustedPromise).toBe(trusted1.promise);
|
expect(av.trustedPromise).toBe(trusted1.promise);
|
||||||
expect(av.trustedStatus).toBe('verifying');
|
expect(av.trustedStatus).toBe('verifying');
|
||||||
|
|
||||||
av.unverify();
|
av.unverify();
|
||||||
|
|
||||||
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');
|
||||||
|
|
||||||
const cwSpy = jest.spyOn(console, 'warn').mockImplementationOnce(() => {}); // suppress the warning
|
const cwSpy = jest.spyOn(console, 'warn').mockImplementationOnce(() => {}); // suppress the warning
|
||||||
trusted1.resolve(new BasicWE(2));
|
trusted1.resolve(new BasicWE(2));
|
||||||
await disjoint();
|
await disjoint();
|
||||||
|
|
||||||
expect(cwSpy).toHaveBeenCalled();
|
expect(cwSpy).toHaveBeenCalled();
|
||||||
expect(trustedFunc).toHaveBeenCalledTimes(2);
|
expect(trustedFunc).toHaveBeenCalledTimes(2);
|
||||||
expect(av.primaryPromise).toBe(null);
|
expect(av.primaryPromise).toBe(null);
|
||||||
expect(av.trustedPromise).toBe(trusted2.promise);
|
expect(av.trustedPromise).toBe(trusted2.promise);
|
||||||
expect(av.trustedStatus).toBe('verifying');
|
expect(av.trustedStatus).toBe('verifying');
|
||||||
|
|
||||||
@ -895,8 +895,8 @@ describe('fetchAndVerifyIfNeeded tests', () => {
|
|||||||
expect(trustedFunc).toHaveBeenCalledTimes(2);
|
expect(trustedFunc).toHaveBeenCalledTimes(2);
|
||||||
expect(verifyFunc).toHaveBeenCalledTimes(1);
|
expect(verifyFunc).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('primary null, unverify during verify, result is original trusted - cache miss, unverify during verify, return from server', async () => {
|
test('primary null, unverify during verify, result is original trusted - cache miss, unverify during verify, return from server', async () => {
|
||||||
const {
|
const {
|
||||||
nextPrimary, nextTrusted, nextVerify,
|
nextPrimary, nextTrusted, nextVerify,
|
||||||
primaryFunc, trustedFunc, verifyFunc
|
primaryFunc, trustedFunc, verifyFunc
|
||||||
@ -925,17 +925,17 @@ describe('fetchAndVerifyIfNeeded tests', () => {
|
|||||||
expect(av.trustedStatus).toBe('verifying');
|
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();
|
||||||
|
|
||||||
expect(verifyFunc).toHaveBeenCalledWith(null, new BasicWE(2));
|
expect(verifyFunc).toHaveBeenCalledWith(null, 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');
|
||||||
|
|
||||||
av.unverify(); // effectively during the verify function call
|
av.unverify(); // during the verify function call
|
||||||
|
|
||||||
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');
|
||||||
|
|
||||||
@ -943,7 +943,7 @@ describe('fetchAndVerifyIfNeeded tests', () => {
|
|||||||
verify.resolve(true);
|
verify.resolve(true);
|
||||||
await disjoint();
|
await disjoint();
|
||||||
|
|
||||||
// result is the value of the trusted promise, but the trusted will be re-called on next fetch
|
// 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(result).toEqual(new BasicWE(2));
|
||||||
expect(av.primaryPromise).toBe(null);
|
expect(av.primaryPromise).toBe(null);
|
||||||
expect(av.trustedPromise).toBe(null);
|
expect(av.trustedPromise).toBe(null);
|
||||||
@ -956,9 +956,78 @@ describe('fetchAndVerifyIfNeeded tests', () => {
|
|||||||
expect(verifyFunc).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('rejected'));
|
||||||
|
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(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);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Why not have fetcher and a simple dedup wrapper to remove some complexity from this function?
|
||||||
|
// This would likely make it possible to get rid of the "else" block in tryResolveTrustedPromise
|
||||||
|
// Make sure it would work properly for retry functionality
|
||||||
|
// can't because of primary promise early resolution
|
||||||
|
|
||||||
// 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
|
||||||
// Fetching after verified doesn't re-fetch trusted
|
// Fetching after verified doesn't re-fetch trusted
|
||||||
// Unverify during verify w/ dedups
|
// Unverify during verify w/ dedups
|
||||||
});
|
});
|
||||||
|
@ -231,7 +231,22 @@ export class AutoVerifier<T> {
|
|||||||
|
|
||||||
// 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;
|
let trustedResult = undefined;
|
||||||
|
try {
|
||||||
|
trustedResult = await origTrustedPromise;
|
||||||
|
} catch (e: unknown) {
|
||||||
|
if (this.trustedPromise == origTrustedPromise) {
|
||||||
|
throw e;
|
||||||
|
} else {
|
||||||
|
console.warn('trusted promise rejected after unverify', e);
|
||||||
|
if (this.trustedPromise === null) {
|
||||||
|
this.trustedStatus = 'fetching';
|
||||||
|
this.trustedPromise = this.trustedFunc();
|
||||||
|
}
|
||||||
|
await tryResolveTrustedPromise();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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!
|
||||||
@ -281,7 +296,18 @@ export class AutoVerifier<T> {
|
|||||||
|
|
||||||
// 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;
|
let trustedResult = undefined;
|
||||||
|
try {
|
||||||
|
trustedResult = await origTrustedPromise;
|
||||||
|
} catch (e: unknown) {
|
||||||
|
if (this.trustedPromise == origTrustedPromise) {
|
||||||
|
throw e;
|
||||||
|
} else {
|
||||||
|
console.warn('trusted promise from another path rejected after unverify', e);
|
||||||
|
await tryResolveTrustedPromise();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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!
|
||||||
@ -321,9 +347,9 @@ export class AutoVerifier<T> {
|
|||||||
await tryResolveTrustedPromise();
|
await tryResolveTrustedPromise();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
if (!resolved) {
|
if (!resolved) {
|
||||||
this.trustedPromise = null; // suppress warnings
|
this.trustedPromise = null; // suppress warnings about uncaught rejections
|
||||||
this.primaryPromise = null; // suppress warnings
|
this.primaryPromise = null; // suppress warnings about uncaught rejections
|
||||||
this.verifyPromise = null; // suppress warnings
|
this.verifyPromise = null; // suppress warnings about uncaught rejections
|
||||||
this.unverify();
|
this.unverify();
|
||||||
// eslint-disable-next-line prefer-promise-reject-errors
|
// eslint-disable-next-line prefer-promise-reject-errors
|
||||||
reject(e as Error);
|
reject(e as Error);
|
||||||
|
Loading…
Reference in New Issue
Block a user