mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-07-27 16:49:51 -07:00
120 lines
3.3 KiB
TypeScript
120 lines
3.3 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import * as http from 'node:http';
|
|
import type { AddressInfo } from 'node:net';
|
|
|
|
import { animetoshoFetchJson } from './utils.js';
|
|
|
|
interface TestServer {
|
|
baseUrl: string;
|
|
close: () => Promise<void>;
|
|
}
|
|
|
|
function startServer(handler: http.RequestListener): Promise<TestServer> {
|
|
return new Promise((resolve) => {
|
|
const server = http.createServer(handler);
|
|
server.listen(0, '127.0.0.1', () => {
|
|
const { port } = server.address() as AddressInfo;
|
|
resolve({
|
|
baseUrl: `http://127.0.0.1:${port}`,
|
|
close: () =>
|
|
new Promise((done) => {
|
|
server.closeAllConnections?.();
|
|
server.close(() => done());
|
|
}),
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
test('animetoshoFetchJson gives up on a hanging response', async () => {
|
|
const server = await startServer((_req, res) => {
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
// Never end the response.
|
|
res.write('[');
|
|
});
|
|
|
|
try {
|
|
const result = await animetoshoFetchJson(
|
|
'/json',
|
|
{},
|
|
{ baseUrl: server.baseUrl, timeoutMs: 150 },
|
|
);
|
|
assert.equal(result.ok, false);
|
|
if (!result.ok) {
|
|
assert.match(result.error.error, /timed out/i);
|
|
}
|
|
} finally {
|
|
await server.close();
|
|
}
|
|
});
|
|
|
|
test('animetoshoFetchJson rejects an oversized response', async () => {
|
|
const server = await startServer((_req, res) => {
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify(Array.from({ length: 2000 }, (_, index) => ({ id: index }))));
|
|
});
|
|
|
|
try {
|
|
const result = await animetoshoFetchJson(
|
|
'/json',
|
|
{},
|
|
{ baseUrl: server.baseUrl, maxResponseBytes: 512 },
|
|
);
|
|
assert.equal(result.ok, false);
|
|
if (!result.ok) {
|
|
assert.match(result.error.error, /too large/i);
|
|
}
|
|
} finally {
|
|
await server.close();
|
|
}
|
|
});
|
|
|
|
test('animetoshoFetchJson decodes multi-byte characters split across chunks', async () => {
|
|
const title = '葬送のフリーレン';
|
|
const body = Buffer.from(JSON.stringify([{ id: 1, title }]), 'utf8');
|
|
// Split inside the first Japanese character's 3-byte UTF-8 sequence.
|
|
const splitAt = body.indexOf(Buffer.from(title, 'utf8')) + 1;
|
|
|
|
const server = await startServer((_req, res) => {
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
res.write(body.subarray(0, splitAt));
|
|
setTimeout(() => res.end(body.subarray(splitAt)), 10);
|
|
});
|
|
|
|
try {
|
|
const result = await animetoshoFetchJson<Array<{ id: number; title: string }>>(
|
|
'/json',
|
|
{},
|
|
{ baseUrl: server.baseUrl },
|
|
);
|
|
assert.equal(result.ok, true);
|
|
if (result.ok) {
|
|
assert.equal(result.data[0]!.title, title);
|
|
}
|
|
} finally {
|
|
await server.close();
|
|
}
|
|
});
|
|
|
|
test('animetoshoFetchJson still parses a normal response', async () => {
|
|
const server = await startServer((_req, res) => {
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify([{ id: 1, title: 'ok' }]));
|
|
});
|
|
|
|
try {
|
|
const result = await animetoshoFetchJson<Array<{ id: number }>>(
|
|
'/json',
|
|
{ q: 'x' },
|
|
{ baseUrl: server.baseUrl },
|
|
);
|
|
assert.equal(result.ok, true);
|
|
if (result.ok) {
|
|
assert.deepEqual(result.data, [{ id: 1, title: 'ok' }]);
|
|
}
|
|
} finally {
|
|
await server.close();
|
|
}
|
|
});
|