Fix race conditions in challenge test

This commit is contained in:
Fedor Indutny 2021-05-07 13:59:46 -07:00 committed by GitHub
parent 17a051a5b7
commit 0ccc607100
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -17,14 +17,21 @@ type CreateMessageOptions = {
}; };
type CreateHandlerOptions = { type CreateHandlerOptions = {
readonly challenge?: boolean; readonly autoSolve?: boolean;
readonly challengeError?: Error; readonly challengeError?: Error;
readonly expireAfter?: number; readonly expireAfter?: number;
readonly onChallengeSolved?: () => void; readonly onChallengeSolved?: () => void;
readonly onChallengeFailed?: (retryAfter?: number) => void; readonly onChallengeFailed?: (retryAfter?: number) => void;
}; };
const NEVER_RETRY = Date.now() + 365 * 24 * 3600 * 1000; const ONE_DAY = 24 * 3600 * 1000;
const NEVER_RETRY = Date.now() + ONE_DAY;
const IMMEDIATE_RETRY = Date.now() - ONE_DAY;
// Various timeouts in milliseconds
const DEFAULT_RETRY_AFTER = 25;
const SOLVE_AFTER = 5;
const LEEWAY = 25;
describe('ChallengeHandler', () => { describe('ChallengeHandler', () => {
const storage = new Map<string, any>(); const storage = new Map<string, any>();
@ -46,7 +53,7 @@ describe('ChallengeHandler', () => {
const { const {
sentAt = 0, sentAt = 0,
isNormalBubble = true, isNormalBubble = true,
retryAfter = Date.now() + 25, retryAfter = Date.now() + DEFAULT_RETRY_AFTER,
} = options; } = options;
const testLocalSent = sent; const testLocalSent = sent;
@ -82,7 +89,6 @@ describe('ChallengeHandler', () => {
events.delete(name); events.delete(name);
}, },
async retrySend() { async retrySend() {
await sleep(5);
const handler = events.get('sent'); const handler = events.get('sent');
if (!handler) { if (!handler) {
throw new Error('Expected handler'); throw new Error('Expected handler');
@ -94,7 +100,7 @@ describe('ChallengeHandler', () => {
}; };
const createHandler = async ({ const createHandler = async ({
challenge = false, autoSolve = false,
challengeError, challengeError,
expireAfter, expireAfter,
onChallengeSolved = noop, onChallengeSolved = noop,
@ -116,7 +122,7 @@ describe('ChallengeHandler', () => {
onChallengeFailed, onChallengeFailed,
requestChallenge(request) { requestChallenge(request) {
if (!challenge) { if (!autoSolve) {
return; return;
} }
@ -125,7 +131,7 @@ describe('ChallengeHandler', () => {
seq: request.seq, seq: request.seq,
data: { captcha: 'captcha' }, data: { captcha: 'captcha' },
}); });
}, 5); }, SOLVE_AFTER);
}, },
async getMessageById(messageId) { async getMessageById(messageId) {
@ -165,7 +171,7 @@ describe('ChallengeHandler', () => {
assert.isTrue(isInStorage(one.id)); assert.isTrue(isInStorage(one.id));
assert.equal(challengeStatus, 'required'); assert.equal(challengeStatus, 'required');
await sleep(50); await sleep(DEFAULT_RETRY_AFTER + LEEWAY);
assert.deepEqual(sent, ['1']); assert.deepEqual(sent, ['1']);
assert.equal(challengeStatus, 'idle'); assert.equal(challengeStatus, 'idle');
@ -173,7 +179,7 @@ describe('ChallengeHandler', () => {
}); });
it('should send challenge response', async () => { it('should send challenge response', async () => {
const handler = await createHandler({ challenge: true }); const handler = await createHandler({ autoSolve: true });
const one = createMessage('1', { retryAfter: NEVER_RETRY }); const one = createMessage('1', { retryAfter: NEVER_RETRY });
messageStorage.set('1', one); messageStorage.set('1', one);
@ -181,7 +187,7 @@ describe('ChallengeHandler', () => {
await handler.register(one); await handler.register(one);
assert.equal(challengeStatus, 'required'); assert.equal(challengeStatus, 'required');
await sleep(50); await sleep(DEFAULT_RETRY_AFTER + SOLVE_AFTER + LEEWAY);
assert.deepEqual(sent, ['1']); assert.deepEqual(sent, ['1']);
assert.isFalse(isInStorage(one.id)); assert.isFalse(isInStorage(one.id));
@ -191,13 +197,11 @@ describe('ChallengeHandler', () => {
it('should send old messages', async () => { it('should send old messages', async () => {
const handler = await createHandler(); const handler = await createHandler();
const retryAfter = Date.now() + 50;
// Put messages in reverse order to validate that the send order is correct // Put messages in reverse order to validate that the send order is correct
const messages = [ const messages = [
createMessage('3', { sentAt: 3, retryAfter }), createMessage('3', { sentAt: 3 }),
createMessage('2', { sentAt: 2, retryAfter }), createMessage('2', { sentAt: 2 }),
createMessage('1', { sentAt: 1, retryAfter }), createMessage('1', { sentAt: 1 }),
]; ];
for (const message of messages) { for (const message of messages) {
messageStorage.set(message.id, message); messageStorage.set(message.id, message);
@ -218,7 +222,7 @@ describe('ChallengeHandler', () => {
await handler.onOffline(); await handler.onOffline();
// Wait for messages to mature // Wait for messages to mature
await sleep(50); await sleep(DEFAULT_RETRY_AFTER + LEEWAY);
// Create new handler to load old messages from storage // Create new handler to load old messages from storage
await createHandler(); await createHandler();
@ -241,7 +245,7 @@ describe('ChallengeHandler', () => {
it('should send message immediately if it is ready', async () => { it('should send message immediately if it is ready', async () => {
const handler = await createHandler(); const handler = await createHandler();
const one = createMessage('1', { retryAfter: Date.now() - 100 }); const one = createMessage('1', { retryAfter: IMMEDIATE_RETRY });
await handler.register(one); await handler.register(one);
assert.equal(challengeStatus, 'idle'); assert.equal(challengeStatus, 'idle');
@ -251,13 +255,15 @@ describe('ChallengeHandler', () => {
it('should not change challenge status on non-bubble messages', async () => { it('should not change challenge status on non-bubble messages', async () => {
const handler = await createHandler(); const handler = await createHandler();
const one = createMessage('1', { isNormalBubble: false }); const one = createMessage('1', {
isNormalBubble: false,
});
await handler.register(one); await handler.register(one);
assert.equal(challengeStatus, 'idle'); assert.equal(challengeStatus, 'idle');
assert.deepEqual(sent, []); assert.deepEqual(sent, []);
await sleep(50); await sleep(DEFAULT_RETRY_AFTER + LEEWAY);
assert.deepEqual(sent, ['1']); assert.deepEqual(sent, ['1']);
}); });
@ -270,7 +276,7 @@ describe('ChallengeHandler', () => {
assert.isTrue(isInStorage(bubble.id)); assert.isTrue(isInStorage(bubble.id));
const newHandler = await createHandler({ const newHandler = await createHandler({
challenge: true, autoSolve: true,
expireAfter: -1, expireAfter: -1,
}); });
await handler.unregister(bubble); await handler.unregister(bubble);
@ -281,7 +287,7 @@ describe('ChallengeHandler', () => {
assert.equal(challengeStatus, 'idle'); assert.equal(challengeStatus, 'idle');
assert.deepEqual(sent, []); assert.deepEqual(sent, []);
await sleep(25); await sleep(DEFAULT_RETRY_AFTER + SOLVE_AFTER + LEEWAY);
assert.equal(challengeStatus, 'idle'); assert.equal(challengeStatus, 'idle');
assert.deepEqual(sent, []); assert.deepEqual(sent, []);
@ -302,7 +308,7 @@ describe('ChallengeHandler', () => {
await handler.onOffline(); await handler.onOffline();
// Let messages mature // Let messages mature
await sleep(50); await sleep(DEFAULT_RETRY_AFTER + LEEWAY);
assert.isTrue(isInStorage(one.id)); assert.isTrue(isInStorage(one.id));
assert.deepEqual(sent, []); assert.deepEqual(sent, []);
@ -319,9 +325,7 @@ describe('ChallengeHandler', () => {
it('should not retry more than 5 times', async () => { it('should not retry more than 5 times', async () => {
const handler = await createHandler(); const handler = await createHandler();
const one = createMessage('1', { const one = createMessage('1');
retryAfter: Date.now() + 50,
});
messageStorage.set('1', one); messageStorage.set('1', one);
await handler.register(one); await handler.register(one);
@ -331,8 +335,8 @@ describe('ChallengeHandler', () => {
assert.deepEqual(sent, []); assert.deepEqual(sent, []);
assert.equal(challengeStatus, 'required'); assert.equal(challengeStatus, 'required');
// Let it spam the server // Let it spam the server.
await sleep(100); await sleep(DEFAULT_RETRY_AFTER + LEEWAY);
assert.isTrue(isInStorage(one.id)); assert.isTrue(isInStorage(one.id));
assert.deepEqual(sent, []); assert.deepEqual(sent, []);
@ -345,18 +349,16 @@ describe('ChallengeHandler', () => {
const onChallengeSolved = sinon.stub(); const onChallengeSolved = sinon.stub();
const handler = await createHandler({ const handler = await createHandler({
challenge: true, autoSolve: true,
onChallengeSolved, onChallengeSolved,
}); });
const one = createMessage('1', { const one = createMessage('1', { retryAfter: NEVER_RETRY });
retryAfter: NEVER_RETRY,
});
messageStorage.set('1', one); messageStorage.set('1', one);
await handler.register(one); await handler.register(one);
// Let the challenge go through // Let the challenge go through
await sleep(50); await sleep(SOLVE_AFTER + LEEWAY);
sinon.assert.calledOnce(onChallengeSolved); sinon.assert.calledOnce(onChallengeSolved);
}); });
@ -365,19 +367,17 @@ describe('ChallengeHandler', () => {
const onChallengeFailed = sinon.stub(); const onChallengeFailed = sinon.stub();
const handler = await createHandler({ const handler = await createHandler({
challenge: true, autoSolve: true,
challengeError: new Error('custom failure'), challengeError: new Error('custom failure'),
onChallengeFailed, onChallengeFailed,
}); });
const one = createMessage('1', { const one = createMessage('1', { retryAfter: NEVER_RETRY });
retryAfter: NEVER_RETRY,
});
messageStorage.set('1', one); messageStorage.set('1', one);
await handler.register(one); await handler.register(one);
// Let the challenge go through // Let the challenge go through
await sleep(50); await sleep(SOLVE_AFTER + LEEWAY);
sinon.assert.calledOnce(onChallengeFailed); sinon.assert.calledOnce(onChallengeFailed);
}); });