test: testing overhaul -- CI e2e gates, parallel suites, generated matrices, mutation testing (#215)

Closes the "e2e never runs in CI" hole. Adds per-PR e2e smoke gate,
nightly full-suite workflows, parallel vitest forks (per-fork DBs),
Playwright parallel/serial/visual projects against production builds,
metadata-generated test suites (drift guards, hostile inputs, format
matrix, pairwise settings, property-based fuzz), Stryker mutation
testing, Schemathesis API fuzz, coverage ratchet, and fixes for three
session-poisoning bugs that caused 200+ serial-bucket failures.

Bug fix included: favicon/split/bulk-rename could hang clients forever
when ZIP streaming failed after reply.hijack().
This commit is contained in:
SnapOtter
2026-06-10 22:01:13 +08:00
committed by GitHub
parent 3b8d529b44
commit 4ec39c556f
62 changed files with 2888 additions and 298 deletions
+9
View File
@@ -10,6 +10,7 @@ import { eq } from "drizzle-orm";
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import { z } from "zod";
import { db, schema } from "../db/index.js";
import { auditLog } from "../lib/audit.js";
import { requirePermission } from "../permissions.js";
import { requireAuth } from "../plugins/auth.js";
@@ -86,6 +87,14 @@ export async function settingsRoutes(app: FastifyInstance): Promise<void> {
}
}
if (entries.length > 0) {
auditLog(request.log, "SETTINGS_UPDATED", {
adminId: admin.id,
username: admin.username,
keys: entries.map((e) => e.key),
});
}
return reply.send({ ok: true, updatedCount: entries.length });
});
+3
View File
@@ -103,6 +103,9 @@ export function registerBulkRename(app: FastifyInstance) {
details: err instanceof Error ? err.message : "Unknown error",
});
}
// The ZIP stream already started; end the connection so clients see a
// truncated transfer instead of hanging forever.
reply.raw.destroy(err instanceof Error ? err : new Error(String(err)));
}
});
}
+10
View File
@@ -106,6 +106,13 @@ export function registerFavicon(app: FastifyInstance) {
buf = await autoOrient(buf);
}
// Force a full pixel decode now. Header validation alone lets
// truncated files through, and a decode failure after reply.hijack()
// cannot be turned into an error response anymore.
if (validation.format !== "svg") {
await sharp(buf).stats();
}
decodedFiles.push({ buffer: buf, filename: file.filename });
} catch (err) {
const reason = err instanceof Error ? err.message : "Unknown decode error";
@@ -206,6 +213,9 @@ export function registerFavicon(app: FastifyInstance) {
details: err instanceof Error ? err.message : "Unknown error",
});
}
// The ZIP stream already started; end the connection so clients see a
// truncated transfer instead of hanging forever.
reply.raw.destroy(err instanceof Error ? err : new Error(String(err)));
}
});
}
+10
View File
@@ -139,6 +139,13 @@ export function registerSplit(app: FastifyInstance) {
const fullW = metadata.width ?? 0;
const fullH = metadata.height ?? 0;
// Force a full pixel decode before streaming starts. Metadata alone
// lets truncated files through, and a decode failure after
// reply.hijack() cannot become an error response anymore.
if (validation.format !== "svg") {
await sharp(fileBuffer).stats();
}
let cols = settings.columns;
let rows = settings.rows;
if (settings.tileWidth && settings.tileHeight) {
@@ -221,6 +228,9 @@ export function registerSplit(app: FastifyInstance) {
details: err instanceof Error ? err.message : "Unknown error",
});
}
// The ZIP stream already started; end the connection so clients see a
// truncated transfer instead of hanging forever.
reply.raw.destroy(err instanceof Error ? err : new Error(String(err)));
}
});