Add Prettier and ESLint tooling (Refs #77)
This commit is contained in:
@@ -18,7 +18,8 @@ afterEach(async () => {
|
||||
});
|
||||
|
||||
async function diagnostics(name = server) {
|
||||
const { GET } = await import('../../src/routes/api/servers/[name]/telemetry-requests/+server.js');
|
||||
const { GET } =
|
||||
await import('../../src/routes/api/servers/[name]/telemetry-requests/+server.js');
|
||||
return GET({ params: { name } });
|
||||
}
|
||||
|
||||
@@ -27,7 +28,9 @@ it('returns uncached, empty diagnostics for a registered server before any reque
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get('cache-control')).toContain('no-store');
|
||||
expect(await response.json()).toEqual({
|
||||
ok: true, requests: [], retention: { maxPerServer: 200, maxAgeDays: 7, maxTotal: 10000 }
|
||||
ok: true,
|
||||
requests: [],
|
||||
retention: { maxPerServer: 200, maxAgeDays: 7, maxTotal: 10000 }
|
||||
});
|
||||
expect((await diagnostics('unknown')).status).toBe(404);
|
||||
});
|
||||
@@ -44,9 +47,18 @@ it('persists the received payload and response, isolates each server, and lists
|
||||
const body = await (await diagnostics()).json();
|
||||
expect(body.requests).toHaveLength(2);
|
||||
expect(body.requests[0]).toEqual({
|
||||
id: expect.any(Number), server, receivedAt: expect.any(String), status: 200, error: null,
|
||||
event: 'round_change', map: 'zombie_theater', round: 3, playerCount: 4,
|
||||
contentType: 'application/json', payload: JSON.stringify(latest), payloadTruncated: false
|
||||
id: expect.any(Number),
|
||||
server,
|
||||
receivedAt: expect.any(String),
|
||||
status: 200,
|
||||
error: null,
|
||||
event: 'round_change',
|
||||
map: 'zombie_theater',
|
||||
round: 3,
|
||||
playerCount: 4,
|
||||
contentType: 'application/json',
|
||||
payload: JSON.stringify(latest),
|
||||
payloadTruncated: false
|
||||
});
|
||||
expect(body.requests[0].id).toBeGreaterThan(body.requests[1].id);
|
||||
expect(body.requests[1].payload).toBe(JSON.stringify(first));
|
||||
@@ -62,10 +74,18 @@ it('records rejected player data with the same validation reason returned to the
|
||||
const result = await response.json();
|
||||
expect(result.error).toContain('players[0].kills');
|
||||
const { requests } = await (await diagnostics()).json();
|
||||
expect(requests).toEqual([expect.objectContaining({
|
||||
server, status: 400, error: result.error, event: 'round_change', round: 5,
|
||||
playerCount: 1, payload: JSON.stringify(invalid), payloadTruncated: false
|
||||
})]);
|
||||
expect(requests).toEqual([
|
||||
expect.objectContaining({
|
||||
server,
|
||||
status: 400,
|
||||
error: result.error,
|
||||
event: 'round_change',
|
||||
round: 5,
|
||||
playerCount: 1,
|
||||
payload: JSON.stringify(invalid),
|
||||
payloadTruncated: false
|
||||
})
|
||||
]);
|
||||
const { listTelemetry } = await import('../../src/lib/server/fleet/telemetry.js');
|
||||
expect(listTelemetry()).toEqual([]);
|
||||
});
|
||||
@@ -76,14 +96,24 @@ it('records identifiable JSON sent with an unsupported or missing Content-Type',
|
||||
const { POST } = await import('../../src/routes/api/telemetry/+server.js');
|
||||
// A byte body avoids Request automatically assigning text/plain to a string body.
|
||||
const request = new Request('http://localhost/api/telemetry', {
|
||||
method: 'POST', headers: telemetryAuthHeaders(), body: new TextEncoder().encode(JSON.stringify(payload))
|
||||
method: 'POST',
|
||||
headers: telemetryAuthHeaders(),
|
||||
body: new TextEncoder().encode(JSON.stringify(payload))
|
||||
});
|
||||
expect(request.headers.get('content-type')).toBeNull();
|
||||
expect((await POST({ request })).status).toBe(415);
|
||||
const { requests } = await (await diagnostics()).json();
|
||||
expect(requests).toEqual([
|
||||
expect.objectContaining({ status: 415, contentType: null, error: 'Content-Type must be application/json' }),
|
||||
expect.objectContaining({ status: 415, contentType: 'text/plain', payload: JSON.stringify(payload) })
|
||||
expect.objectContaining({
|
||||
status: 415,
|
||||
contentType: null,
|
||||
error: 'Content-Type must be application/json'
|
||||
}),
|
||||
expect.objectContaining({
|
||||
status: 415,
|
||||
contentType: 'text/plain',
|
||||
payload: JSON.stringify(payload)
|
||||
})
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -93,7 +123,13 @@ it('distinguishes a zero round and an empty scoreboard from absent or invalid fi
|
||||
await post({ server, players: 4 });
|
||||
const { requests } = await (await diagnostics()).json();
|
||||
expect(requests).toEqual([
|
||||
expect.objectContaining({ status: 400, event: null, map: null, round: null, playerCount: null }),
|
||||
expect.objectContaining({
|
||||
status: 400,
|
||||
event: null,
|
||||
map: null,
|
||||
round: null,
|
||||
playerCount: null
|
||||
}),
|
||||
expect.objectContaining({ status: 200, event: 'game_start', round: null, playerCount: 0 }),
|
||||
expect.objectContaining({ status: 200, event: 'round_change', round: 0, playerCount: 0 })
|
||||
]);
|
||||
@@ -109,14 +145,23 @@ it('keeps unknown and destroyed server rejections and matches the submitted serv
|
||||
setServerField(server, 'destroyed_at', new Date().toISOString());
|
||||
expect((await post(gameEvent('game_end', 1))).status).toBe(404);
|
||||
|
||||
const { listTelemetryRequests } = await import('../../src/lib/server/fleet/telemetry-requests.js');
|
||||
expect(listTelemetryRequests(unknown)).toEqual([expect.objectContaining({ server: unknown, status: 404 })]);
|
||||
expect(listTelemetryRequests(spaced)).toEqual([expect.objectContaining({ server: spaced, status: 404 })]);
|
||||
const { listTelemetryRequests } =
|
||||
await import('../../src/lib/server/fleet/telemetry-requests.js');
|
||||
expect(listTelemetryRequests(unknown)).toEqual([
|
||||
expect.objectContaining({ server: unknown, status: 404 })
|
||||
]);
|
||||
expect(listTelemetryRequests(spaced)).toEqual([
|
||||
expect.objectContaining({ server: spaced, status: 404 })
|
||||
]);
|
||||
expect((await diagnostics(unknown)).status).toBe(404);
|
||||
const response = await diagnostics();
|
||||
expect(response.status).toBe(200);
|
||||
expect((await response.json()).requests).toEqual([
|
||||
expect.objectContaining({ server, status: 404, error: 'Server is not registered or has been destroyed' }),
|
||||
expect.objectContaining({
|
||||
server,
|
||||
status: 404,
|
||||
error: 'Server is not registered or has been destroyed'
|
||||
}),
|
||||
expect.objectContaining({ server, status: 200 })
|
||||
]);
|
||||
});
|
||||
@@ -124,21 +169,30 @@ it('keeps unknown and destroyed server rejections and matches the submitted serv
|
||||
it('does not attribute malformed JSON or unusable server names to a server', async () => {
|
||||
const { POST } = await import('../../src/routes/api/telemetry/+server.js');
|
||||
const malformed = new Request('http://localhost/api/telemetry', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json', ...telemetryAuthHeaders() }, body: `{"server":"${server}",`
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', ...telemetryAuthHeaders() },
|
||||
body: `{"server":"${server}",`
|
||||
});
|
||||
expect((await POST({ request: malformed })).status).toBe(400);
|
||||
for (const value of [undefined, null, '', ' ', 123, 's'.repeat(256)]) {
|
||||
expect((await post({ ...gameEvent('game_start'), server: value })).status).toBe(400);
|
||||
}
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
expect(db().prepare('SELECT COUNT(*) AS count FROM telemetry_requests').get()).toEqual({ count: 0 });
|
||||
expect(db().prepare('SELECT COUNT(*) AS count FROM telemetry_requests').get()).toEqual({
|
||||
count: 0
|
||||
});
|
||||
});
|
||||
|
||||
it('bounds stored payloads and marks truncation without rejecting a valid event', async () => {
|
||||
const payload = { ...gameEvent('round_change', 4), diagnostic: 'x'.repeat(20_000) };
|
||||
expect((await post(payload)).status).toBe(200);
|
||||
const { requests } = await (await diagnostics()).json();
|
||||
expect(requests[0]).toMatchObject({ status: 200, round: 4, playerCount: 4, payloadTruncated: true });
|
||||
expect(requests[0]).toMatchObject({
|
||||
status: 200,
|
||||
round: 4,
|
||||
playerCount: 4,
|
||||
payloadTruncated: true
|
||||
});
|
||||
expect(requests[0].payload).toBe(JSON.stringify(payload).slice(0, 16 * 1024));
|
||||
});
|
||||
|
||||
@@ -154,7 +208,9 @@ it('records processing failures after rollback without changing the scoreboard,
|
||||
const matches = db().prepare('SELECT * FROM leaderboard_matches ORDER BY id').all();
|
||||
const listener = vi.fn();
|
||||
const unsubscribe = subscribe(listener);
|
||||
vi.spyOn(beta, 'recordGameEvent').mockImplementationOnce(() => { throw new Error('Injected beta processing failure'); });
|
||||
vi.spyOn(beta, 'recordGameEvent').mockImplementationOnce(() => {
|
||||
throw new Error('Injected beta processing failure');
|
||||
});
|
||||
const response = await post(gameEvent('game_end', 99));
|
||||
unsubscribe();
|
||||
|
||||
@@ -165,7 +221,12 @@ it('records processing failures after rollback without changing the scoreboard,
|
||||
expect(db().prepare('SELECT * FROM leaderboard_matches ORDER BY id').all()).toEqual(matches);
|
||||
expect(listener).not.toHaveBeenCalled();
|
||||
expect((await (await diagnostics()).json()).requests).toEqual([
|
||||
expect.objectContaining({ status: 500, event: 'game_end', round: 99, error: 'Could not process telemetry: Injected beta processing failure' }),
|
||||
expect.objectContaining({
|
||||
status: 500,
|
||||
event: 'game_end',
|
||||
round: 99,
|
||||
error: 'Could not process telemetry: Injected beta processing failure'
|
||||
}),
|
||||
expect.objectContaining({ status: 200, round: 3 })
|
||||
]);
|
||||
});
|
||||
@@ -183,15 +244,19 @@ it('still accepts and broadcasts telemetry if storing diagnostics fails', async
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
const { listTelemetry } = await import('../../src/lib/server/fleet/telemetry.js');
|
||||
expect(listTelemetry()).toEqual([expect.objectContaining({ ...payload, receivedAt: expect.any(String) })]);
|
||||
expect(listTelemetry()).toEqual([
|
||||
expect.objectContaining({ ...payload, receivedAt: expect.any(String) })
|
||||
]);
|
||||
expect(listener).toHaveBeenCalledWith({
|
||||
type: 'server-telemetry', entries: [expect.objectContaining({ ...payload, receivedAt: expect.any(String) })]
|
||||
type: 'server-telemetry',
|
||||
entries: [expect.objectContaining({ ...payload, receivedAt: expect.any(String) })]
|
||||
});
|
||||
});
|
||||
|
||||
it('retains only the latest 200 requests for a server and removes requests older than seven days', async () => {
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
const { recordTelemetryRequest, listTelemetryRequests } = await import('../../src/lib/server/fleet/telemetry-requests.js');
|
||||
const { recordTelemetryRequest, listTelemetryRequests } =
|
||||
await import('../../src/lib/server/fleet/telemetry-requests.js');
|
||||
const now = new Date().toISOString();
|
||||
const old = new Date(Date.now() - 8 * 86_400_000).toISOString();
|
||||
const insert = db().prepare(`INSERT INTO telemetry_requests
|
||||
@@ -201,27 +266,52 @@ it('retains only the latest 200 requests for a server and removes requests older
|
||||
insert.run(otherServer, now);
|
||||
for (let i = 0; i < 205; i++) insert.run(server, now);
|
||||
})();
|
||||
recordTelemetryRequest({ value: gameEvent('round_change', 9), receivedAt: now, status: 200, error: null, contentType: 'application/json' });
|
||||
recordTelemetryRequest({
|
||||
value: gameEvent('round_change', 9),
|
||||
receivedAt: now,
|
||||
status: 200,
|
||||
error: null,
|
||||
contentType: 'application/json'
|
||||
});
|
||||
|
||||
const requests = listTelemetryRequests(server);
|
||||
expect(requests).toHaveLength(200);
|
||||
expect(requests[0]).toMatchObject({ round: 9 });
|
||||
expect(requests.map(entry => entry.id)).toEqual([...requests.map(entry => entry.id)].sort((a, b) => b - a));
|
||||
expect(requests.map((entry) => entry.id)).toEqual(
|
||||
[...requests.map((entry) => entry.id)].sort((a, b) => b - a)
|
||||
);
|
||||
expect(listTelemetryRequests(otherServer)).toHaveLength(1);
|
||||
expect(db().prepare('SELECT COUNT(*) AS count FROM telemetry_requests WHERE received_at = ?').get(old)).toEqual({ count: 0 });
|
||||
expect(
|
||||
db()
|
||||
.prepare('SELECT COUNT(*) AS count FROM telemetry_requests WHERE received_at = ?')
|
||||
.get(old)
|
||||
).toEqual({ count: 0 });
|
||||
});
|
||||
|
||||
it('caps global diagnostic history even when rejected requests name many unknown servers', async () => {
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
const { recordTelemetryRequest } = await import('../../src/lib/server/fleet/telemetry-requests.js');
|
||||
const { recordTelemetryRequest } =
|
||||
await import('../../src/lib/server/fleet/telemetry-requests.js');
|
||||
const now = new Date().toISOString();
|
||||
const insert = db().prepare(`INSERT INTO telemetry_requests
|
||||
(server, received_at, status, payload, payload_truncated) VALUES (?, ?, 404, '{}', 0)`);
|
||||
db().transaction(() => {
|
||||
for (let i = 0; i < 10_005; i++) insert.run(`unknown-${i}`, now);
|
||||
})();
|
||||
recordTelemetryRequest({ value: gameEvent('game_start'), receivedAt: now, status: 200, error: null, contentType: 'application/json' });
|
||||
expect(db().prepare('SELECT COUNT(*) AS count FROM telemetry_requests').get()).toEqual({ count: 10_000 });
|
||||
expect(db().prepare('SELECT server FROM telemetry_requests ORDER BY id DESC LIMIT 1').get()).toEqual({ server });
|
||||
expect(db().prepare("SELECT id FROM telemetry_requests WHERE server = 'unknown-0'").get()).toBeUndefined();
|
||||
recordTelemetryRequest({
|
||||
value: gameEvent('game_start'),
|
||||
receivedAt: now,
|
||||
status: 200,
|
||||
error: null,
|
||||
contentType: 'application/json'
|
||||
});
|
||||
expect(db().prepare('SELECT COUNT(*) AS count FROM telemetry_requests').get()).toEqual({
|
||||
count: 10_000
|
||||
});
|
||||
expect(
|
||||
db().prepare('SELECT server FROM telemetry_requests ORDER BY id DESC LIMIT 1').get()
|
||||
).toEqual({ server });
|
||||
expect(
|
||||
db().prepare("SELECT id FROM telemetry_requests WHERE server = 'unknown-0'").get()
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user