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