2022-07-14 00:46:46 +00:00
|
|
|
// Copyright 2022 Signal Messenger, LLC
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
|
|
|
import { assert } from 'chai';
|
2023-04-10 21:30:33 +00:00
|
|
|
import { sleep } from '../../util/sleep';
|
2022-07-14 00:46:46 +00:00
|
|
|
import { MINUTE } from '../../util/durations';
|
2022-12-21 18:41:48 +00:00
|
|
|
import { drop } from '../../util/drop';
|
2022-07-14 00:46:46 +00:00
|
|
|
|
|
|
|
import { ProfileService } from '../../services/profiles';
|
2023-08-10 16:43:33 +00:00
|
|
|
import { generateAci } from '../../types/ServiceId';
|
2022-07-14 00:46:46 +00:00
|
|
|
import { HTTPError } from '../../textsecure/Errors';
|
|
|
|
|
|
|
|
describe('util/profiles', () => {
|
2023-08-16 20:54:39 +00:00
|
|
|
const SERVICE_ID_1 = generateAci();
|
|
|
|
const SERVICE_ID_2 = generateAci();
|
|
|
|
const SERVICE_ID_3 = generateAci();
|
|
|
|
const SERVICE_ID_4 = generateAci();
|
|
|
|
const SERVICE_ID_5 = generateAci();
|
2022-07-14 00:46:46 +00:00
|
|
|
|
|
|
|
beforeEach(async () => {
|
2023-08-16 20:54:39 +00:00
|
|
|
await window.ConversationController.getOrCreateAndWait(
|
|
|
|
SERVICE_ID_1,
|
|
|
|
'private'
|
|
|
|
);
|
|
|
|
await window.ConversationController.getOrCreateAndWait(
|
|
|
|
SERVICE_ID_2,
|
|
|
|
'private'
|
|
|
|
);
|
|
|
|
await window.ConversationController.getOrCreateAndWait(
|
|
|
|
SERVICE_ID_3,
|
|
|
|
'private'
|
|
|
|
);
|
|
|
|
await window.ConversationController.getOrCreateAndWait(
|
|
|
|
SERVICE_ID_4,
|
|
|
|
'private'
|
|
|
|
);
|
|
|
|
await window.ConversationController.getOrCreateAndWait(
|
|
|
|
SERVICE_ID_5,
|
|
|
|
'private'
|
|
|
|
);
|
2022-07-14 00:46:46 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
describe('clearAll', () => {
|
|
|
|
it('Cancels all in-flight requests', async () => {
|
|
|
|
const getProfileWithLongDelay = async () => {
|
|
|
|
await sleep(MINUTE);
|
|
|
|
};
|
|
|
|
const service = new ProfileService(getProfileWithLongDelay);
|
|
|
|
|
2024-10-10 17:57:22 +00:00
|
|
|
const promise1 = service.get(SERVICE_ID_1, null);
|
|
|
|
const promise2 = service.get(SERVICE_ID_2, null);
|
|
|
|
const promise3 = service.get(SERVICE_ID_3, null);
|
|
|
|
const promise4 = service.get(SERVICE_ID_4, null);
|
2022-07-14 00:46:46 +00:00
|
|
|
|
|
|
|
service.clearAll('testing');
|
|
|
|
|
|
|
|
await assert.isRejected(promise1, 'job cancelled');
|
|
|
|
await assert.isRejected(promise2, 'job cancelled');
|
|
|
|
await assert.isRejected(promise3, 'job cancelled');
|
|
|
|
await assert.isRejected(promise4, 'job cancelled');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('pause', () => {
|
|
|
|
it('pauses the queue', async () => {
|
|
|
|
let runCount = 0;
|
|
|
|
const getProfileWithIncrement = () => {
|
|
|
|
runCount += 1;
|
|
|
|
return Promise.resolve();
|
|
|
|
};
|
|
|
|
const service = new ProfileService(getProfileWithIncrement);
|
|
|
|
|
|
|
|
// Queued and immediately started due to concurrency = 3
|
2024-10-10 17:57:22 +00:00
|
|
|
drop(service.get(SERVICE_ID_1, null));
|
|
|
|
drop(service.get(SERVICE_ID_2, null));
|
|
|
|
drop(service.get(SERVICE_ID_3, null));
|
2022-07-14 00:46:46 +00:00
|
|
|
|
|
|
|
// Queued but only run after paused queue restarts
|
2024-10-10 17:57:22 +00:00
|
|
|
const lastPromise = service.get(SERVICE_ID_4, null);
|
2022-07-14 00:46:46 +00:00
|
|
|
|
|
|
|
const pausePromise = service.pause(5);
|
|
|
|
|
|
|
|
assert.strictEqual(runCount, 3, 'as pause starts');
|
|
|
|
|
|
|
|
await pausePromise;
|
|
|
|
await lastPromise;
|
|
|
|
|
|
|
|
assert.strictEqual(runCount, 4, 'after last promise');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('get', () => {
|
|
|
|
it('throws if we are currently paused', async () => {
|
|
|
|
let runCount = 0;
|
|
|
|
const getProfileWithIncrement = () => {
|
|
|
|
runCount += 1;
|
|
|
|
return Promise.resolve();
|
|
|
|
};
|
|
|
|
const service = new ProfileService(getProfileWithIncrement);
|
|
|
|
|
|
|
|
const pausePromise = service.pause(5);
|
|
|
|
|
|
|
|
// None of these are even queued
|
2024-10-10 17:57:22 +00:00
|
|
|
const promise1 = service.get(SERVICE_ID_1, null);
|
|
|
|
const promise2 = service.get(SERVICE_ID_2, null);
|
|
|
|
const promise3 = service.get(SERVICE_ID_3, null);
|
|
|
|
const promise4 = service.get(SERVICE_ID_4, null);
|
2022-07-14 00:46:46 +00:00
|
|
|
|
|
|
|
await assert.isRejected(promise1, 'paused queue');
|
|
|
|
await assert.isRejected(promise2, 'paused queue');
|
|
|
|
await assert.isRejected(promise3, 'paused queue');
|
|
|
|
await assert.isRejected(promise4, 'paused queue');
|
|
|
|
|
|
|
|
await pausePromise;
|
|
|
|
|
|
|
|
assert.strictEqual(runCount, 0);
|
|
|
|
});
|
|
|
|
|
2023-01-05 22:29:02 +00:00
|
|
|
for (const code of [413, 429] as const) {
|
|
|
|
it(`clears all outstanding jobs if we get a ${code}, then pauses`, async () => {
|
|
|
|
let runCount = 0;
|
|
|
|
const getProfileWhichThrows = async () => {
|
|
|
|
runCount += 1;
|
|
|
|
const error = new HTTPError(`fake ${code}`, {
|
|
|
|
code,
|
|
|
|
headers: {
|
|
|
|
'retry-after': '1',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
throw error;
|
|
|
|
};
|
|
|
|
const service = new ProfileService(getProfileWhichThrows);
|
|
|
|
|
|
|
|
// Queued and immediately started due to concurrency = 3
|
2024-10-10 17:57:22 +00:00
|
|
|
const promise1 = service.get(SERVICE_ID_1, null);
|
|
|
|
const promise2 = service.get(SERVICE_ID_2, null);
|
|
|
|
const promise3 = service.get(SERVICE_ID_3, null);
|
2023-01-05 22:29:02 +00:00
|
|
|
|
|
|
|
// Never started, but queued
|
2024-10-10 17:57:22 +00:00
|
|
|
const promise4 = service.get(SERVICE_ID_4, null);
|
2023-01-05 22:29:02 +00:00
|
|
|
|
|
|
|
assert.strictEqual(runCount, 3, 'before await');
|
|
|
|
|
|
|
|
await assert.isRejected(promise1, `fake ${code}`);
|
|
|
|
|
|
|
|
// Never queued
|
2024-10-10 17:57:22 +00:00
|
|
|
const promise5 = service.get(SERVICE_ID_5, null);
|
2023-01-05 22:29:02 +00:00
|
|
|
|
|
|
|
await assert.isRejected(promise2, 'job cancelled');
|
|
|
|
await assert.isRejected(promise3, 'job cancelled');
|
|
|
|
await assert.isRejected(promise4, 'job cancelled');
|
|
|
|
await assert.isRejected(promise5, 'paused queue');
|
|
|
|
|
|
|
|
assert.strictEqual(runCount, 3, 'after await');
|
|
|
|
});
|
|
|
|
}
|
2024-02-16 20:40:38 +00:00
|
|
|
|
|
|
|
it('clears all outstanding jobs if we get a -1', async () => {
|
|
|
|
let runCount = 0;
|
|
|
|
const getProfileWhichThrows = async () => {
|
|
|
|
runCount += 1;
|
|
|
|
const error = new HTTPError('fake -1', {
|
|
|
|
code: -1,
|
|
|
|
headers: {},
|
|
|
|
});
|
|
|
|
throw error;
|
|
|
|
};
|
|
|
|
const service = new ProfileService(getProfileWhichThrows);
|
|
|
|
|
|
|
|
// Queued and immediately started due to concurrency = 3
|
2024-10-10 17:57:22 +00:00
|
|
|
const promise1 = service.get(SERVICE_ID_1, null);
|
|
|
|
const promise2 = service.get(SERVICE_ID_2, null);
|
|
|
|
const promise3 = service.get(SERVICE_ID_3, null);
|
2024-02-16 20:40:38 +00:00
|
|
|
|
|
|
|
// Never started, but queued
|
2024-10-10 17:57:22 +00:00
|
|
|
const promise4 = service.get(SERVICE_ID_4, null);
|
2024-02-16 20:40:38 +00:00
|
|
|
|
|
|
|
assert.strictEqual(runCount, 3, 'before await');
|
|
|
|
|
|
|
|
await assert.isRejected(promise1, 'fake -1');
|
|
|
|
|
|
|
|
// Queued, because we aren't pausing
|
2024-10-10 17:57:22 +00:00
|
|
|
const promise5 = service.get(SERVICE_ID_5, null);
|
2024-02-16 20:40:38 +00:00
|
|
|
|
|
|
|
await assert.isRejected(promise2, 'job cancelled');
|
|
|
|
await assert.isRejected(promise3, 'job cancelled');
|
|
|
|
await assert.isRejected(promise4, 'job cancelled');
|
|
|
|
await assert.isRejected(promise5, 'fake -1');
|
|
|
|
|
|
|
|
assert.strictEqual(runCount, 4, 'after await');
|
|
|
|
});
|
2022-07-14 00:46:46 +00:00
|
|
|
});
|
|
|
|
});
|