mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
127 lines
4.0 KiB
JavaScript
127 lines
4.0 KiB
JavaScript
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
import assert from 'node:assert/strict';
|
|
import {Buffer} from 'node:buffer';
|
|
import {PassThrough, Readable} from 'node:stream';
|
|
import {describe, mock, test} from 'node:test';
|
|
import {installElectronStub} from './LocalAppTestSupport.test.mjs';
|
|
|
|
installElectronStub();
|
|
|
|
const {DesktopLocalAppProxyClient, LocalAppProxyTraffic} = await import('./LocalAppProxyClient.ts');
|
|
|
|
const RENDERED_AVATAR_COUNT = 120;
|
|
const PARKED_MEDIA_BODY_COUNT = 200;
|
|
const RESOURCE_HEADERS_DEADLINE_MS = 30_000;
|
|
|
|
function tick() {
|
|
return new Promise((resolve) => {
|
|
setImmediate(resolve);
|
|
});
|
|
}
|
|
|
|
function avatarRequest(index, signal) {
|
|
return {
|
|
targetURL: `https://media.fluxer.app/avatars/${index}.webp`,
|
|
method: 'GET',
|
|
headers: new Headers(),
|
|
body: null,
|
|
signal,
|
|
acceptEncoding: null,
|
|
uploadId: null,
|
|
uploadTotalBytes: null,
|
|
};
|
|
}
|
|
|
|
function createUpstream({streamBodies}) {
|
|
const state = {started: [], aborted: 0};
|
|
const outboundHTTP = {
|
|
request: (request) =>
|
|
new Promise((resolve, reject) => {
|
|
const entry = {
|
|
url: request.url,
|
|
answer: () =>
|
|
resolve({
|
|
headers: {'content-type': 'image/webp'},
|
|
message: streamBodies ? new PassThrough() : Readable.from([Buffer.from('avatar-bytes')]),
|
|
status: 200,
|
|
statusText: 'OK',
|
|
url: new URL(request.url),
|
|
}),
|
|
};
|
|
state.started.push(entry);
|
|
request.signal.addEventListener(
|
|
'abort',
|
|
() => {
|
|
state.aborted += 1;
|
|
reject(request.signal.reason);
|
|
},
|
|
{once: true},
|
|
);
|
|
}),
|
|
};
|
|
return {outboundHTTP, state};
|
|
}
|
|
|
|
describe('the local app proxy never queues a request behind another', () => {
|
|
test('every avatar of a fully rendered chat starts at once', async () => {
|
|
const {outboundHTTP, state} = createUpstream({streamBodies: false});
|
|
const client = new DesktopLocalAppProxyClient({outboundHTTP, traffic: LocalAppProxyTraffic.REMOTE_RESOURCE});
|
|
const renderer = new AbortController();
|
|
|
|
const fetches = [];
|
|
for (let index = 0; index < RENDERED_AVATAR_COUNT; index += 1) {
|
|
fetches.push(client.fetch(avatarRequest(index, renderer.signal)));
|
|
}
|
|
await tick();
|
|
assert.equal(state.started.length, RENDERED_AVATAR_COUNT);
|
|
|
|
for (const entry of state.started) {
|
|
entry.answer();
|
|
}
|
|
const texts = await Promise.all(fetches.map(async (fetch) => await (await fetch).text()));
|
|
assert.deepEqual(texts, new Array(RENDERED_AVATAR_COUNT).fill('avatar-bytes'));
|
|
});
|
|
|
|
test('a media request starts at once while earlier response bodies sit unread', async () => {
|
|
const {outboundHTTP, state} = createUpstream({streamBodies: true});
|
|
const client = new DesktopLocalAppProxyClient({outboundHTTP, traffic: LocalAppProxyTraffic.REMOTE_RESOURCE});
|
|
const renderer = new AbortController();
|
|
|
|
for (let index = 0; index < PARKED_MEDIA_BODY_COUNT; index += 1) {
|
|
const fetch = client.fetch(avatarRequest(index, renderer.signal));
|
|
await tick();
|
|
state.started.at(-1).answer();
|
|
assert.equal((await fetch).status, 200);
|
|
}
|
|
|
|
const next = client.fetch(avatarRequest(PARKED_MEDIA_BODY_COUNT, renderer.signal));
|
|
await tick();
|
|
assert.equal(state.started.length, PARKED_MEDIA_BODY_COUNT + 1);
|
|
state.started.at(-1).answer();
|
|
assert.equal((await next).status, 200);
|
|
renderer.abort();
|
|
});
|
|
});
|
|
|
|
describe('media that a slow or silent self-host never answers is cut off', () => {
|
|
test('a silent upstream is aborted at the headers deadline', async () => {
|
|
mock.timers.enable({apis: ['setTimeout']});
|
|
try {
|
|
const {outboundHTTP, state} = createUpstream({streamBodies: false});
|
|
const client = new DesktopLocalAppProxyClient({outboundHTTP, traffic: LocalAppProxyTraffic.REMOTE_RESOURCE});
|
|
const renderer = new AbortController();
|
|
const silent = client.fetch(avatarRequest(0, renderer.signal));
|
|
silent.catch(() => {});
|
|
await tick();
|
|
|
|
mock.timers.tick(RESOURCE_HEADERS_DEADLINE_MS);
|
|
|
|
await assert.rejects(silent, (error) => error.name === 'LocalAppProxyResponseHeadersDeadlineError');
|
|
assert.equal(state.aborted, 1);
|
|
} finally {
|
|
mock.timers.reset();
|
|
}
|
|
});
|
|
});
|