Error statuses follow one rule: APIException takes a status and derives the code from it (BAD_REQUEST 400, UNAUTHORIZED 401, FORBIDDEN 403, NOT_FOUND 404, CONFLICT 409, RATE_LIMITED 429, SERVER_ERROR 500, BAD_GATEWAY 502), so a code can't contradict its status. That fixed real bugs: a wrong current password on change-password answered 401, on which authFetch signs the user out; editing or deleting someone else's comment or chat message answered 401 rather than 403; one invalid dump edit answered 422. A taken username is 409, like a taken email. Along the way: the database is created from schema.sql by connection.ts when the file doesn't exist, so every task works on a fresh checkout (dev and serve never ran db/init.ts, which is gone); the startup purge compared ISO dates with SQLite's datetime() as text and kept anything from the current UTC day, and now compares ISO cutoffs; the seeded admin's hash comes from lib/password.ts (split out of jwt.ts, which needs the JWT secret) rather than a copy of its format; avatar, thumbnail and playlist-cover uploads go through replaceUploadedFile, which writes beside the old file and renames over it before the record announces it (a failed avatar write lost the old avatar; a thumbnail was announced before it existed); upload.ts derives its size message from MAX_IMAGE_SIZE_BYTES and stops re-exporting the upload folders; ALLOWED_IMAGE_MIMES, which duplicated detectImageMime, is gone; the SPA fallback no longer calls next() after answering; a reply must answer a comment on the same dump (a foreign or unknown parent was a 500 or a cross-dump thread). 211 backend and 628 frontend tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
42 lines
1.1 KiB
TypeScript
42 lines
1.1 KiB
TypeScript
import "../setup.ts";
|
|
import { assertEquals } from "@std/assert";
|
|
import { DatabaseSync } from "node:sqlite";
|
|
import { runMigrations } from "../../db/migrate.ts";
|
|
|
|
const SCHEMA = Deno.readTextFileSync(
|
|
new URL("../../db/schema.sql", import.meta.url),
|
|
);
|
|
|
|
function freshDb(): DatabaseSync {
|
|
const db = new DatabaseSync(":memory:");
|
|
db.exec(SCHEMA);
|
|
return db;
|
|
}
|
|
|
|
function schemaOf(db: DatabaseSync): string[] {
|
|
return (db.prepare(
|
|
`SELECT sql FROM sqlite_master WHERE sql IS NOT NULL AND name != 'schema_migrations' ORDER BY name;`,
|
|
).all() as { sql: string }[]).map((r) => r.sql);
|
|
}
|
|
|
|
Deno.test("runMigrations: a fresh database is left as schema.sql made it", () => {
|
|
const db = freshDb();
|
|
const before = schemaOf(db);
|
|
runMigrations(db);
|
|
assertEquals(schemaOf(db), before);
|
|
const applied = db.prepare(`SELECT COUNT(*) as n FROM schema_migrations;`)
|
|
.get() as {
|
|
n: number;
|
|
};
|
|
assertEquals(applied.n, 18);
|
|
// Running again applies nothing.
|
|
runMigrations(db);
|
|
assertEquals(
|
|
(db.prepare(`SELECT COUNT(*) as n FROM schema_migrations;`).get() as {
|
|
n: number;
|
|
}).n,
|
|
18,
|
|
);
|
|
db.close();
|
|
});
|