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>
51 lines
1.5 KiB
TypeScript
51 lines
1.5 KiB
TypeScript
import {
|
|
createUser,
|
|
insertDump,
|
|
insertPlaylist,
|
|
stubNetwork,
|
|
test,
|
|
} from "../../helpers.ts";
|
|
import { assertEquals } from "@std/assert";
|
|
import { fetchRichContent } from "../../../services/rich-content-service.ts";
|
|
|
|
test("self provider: our own dump, user and playlist pages come from the DB", async () => {
|
|
const user = await createUser();
|
|
const dump = insertDump({
|
|
userId: user.id,
|
|
title: "Local dump",
|
|
comment: "About it",
|
|
richContent: { type: "generic", url: "x", thumbnailUrl: "https://t/x.jpg" },
|
|
});
|
|
using net = stubNetwork(() => {
|
|
throw new Error("no network for self links");
|
|
});
|
|
const content = await fetchRichContent(
|
|
`http://gerbeur.test/dumps/${dump.slug}`,
|
|
);
|
|
assertEquals(content?.title, "Local dump");
|
|
assertEquals(content?.description, "About it");
|
|
assertEquals(content?.thumbnailUrl, "https://t/x.jpg");
|
|
assertEquals(content?.siteName, "gerbeur");
|
|
const profile = await fetchRichContent(
|
|
`http://gerbeur.test/users/${user.username}`,
|
|
);
|
|
assertEquals(profile?.title, user.username);
|
|
assertEquals(net.calls.length, 0);
|
|
});
|
|
|
|
test("self provider: a private playlist gets no preview", async () => {
|
|
const user = await createUser();
|
|
const playlist = insertPlaylist({
|
|
userId: user.id,
|
|
title: "Secret mix",
|
|
isPublic: false,
|
|
});
|
|
using _net = stubNetwork(() => {
|
|
throw new Error("no network for self links");
|
|
});
|
|
const content = await fetchRichContent(
|
|
`http://gerbeur.test/playlists/${playlist.id}`,
|
|
);
|
|
assertEquals(content?.title, undefined);
|
|
});
|