All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 46s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
187 lines
6.2 KiB
TypeScript
187 lines
6.2 KiB
TypeScript
import {
|
|
createUser,
|
|
db,
|
|
del,
|
|
get,
|
|
insertCategory,
|
|
insertDump,
|
|
insertPlaylist,
|
|
post,
|
|
put,
|
|
test,
|
|
} from "./helpers.ts";
|
|
import { assertEquals, assertStringIncludes } from "@std/assert";
|
|
|
|
test("categories: listed by position, then slug; found by slug", async () => {
|
|
insertCategory({ slug: "zeta", position: 0 });
|
|
insertCategory({ slug: "alpha", position: 1 });
|
|
insertCategory({ slug: "beta", position: 0 });
|
|
const list = await get("/api/categories");
|
|
assertEquals(list.body.data.map((c: { slug: string }) => c.slug), [
|
|
"beta",
|
|
"zeta",
|
|
"alpha",
|
|
]);
|
|
assertEquals((await get("/api/categories/alpha")).body.data.slug, "alpha");
|
|
assertEquals((await get("/api/categories/nope")).status, 404);
|
|
});
|
|
|
|
test("categories: only category:manage may create, update or delete", async () => {
|
|
const admin = await createUser({ role: "admin" });
|
|
const mod = await createUser({ role: "moderator" });
|
|
const json = { slug: "music", name: "Music" };
|
|
assertEquals((await post("/api/categories", { json })).status, 401);
|
|
assertEquals(
|
|
(await post("/api/categories", { auth: mod.auth, json })).status,
|
|
403,
|
|
);
|
|
const created = await post("/api/categories", { auth: admin.auth, json });
|
|
assertEquals(created.status, 201);
|
|
const id = created.body.data.id;
|
|
assertEquals(
|
|
(await put(`/api/categories/${id}`, {
|
|
auth: mod.auth,
|
|
json: { name: "X" },
|
|
})).status,
|
|
403,
|
|
);
|
|
assertEquals(
|
|
(await del(`/api/categories/${id}`, { auth: mod.auth })).status,
|
|
403,
|
|
);
|
|
assertEquals(
|
|
(await del(`/api/categories/${id}`, { auth: admin.auth })).status,
|
|
200,
|
|
);
|
|
assertEquals(
|
|
(await del(`/api/categories/${id}`, { auth: admin.auth })).status,
|
|
404,
|
|
);
|
|
});
|
|
|
|
test("categories: slug and name rules, and friendly conflicts", async () => {
|
|
const admin = await createUser({ role: "admin" });
|
|
const create = (json: unknown) =>
|
|
post("/api/categories", { auth: admin.auth, json });
|
|
for (
|
|
const [json, message] of [
|
|
[{ slug: "Bad Slug", name: "x" }, "lowercase"],
|
|
[{ slug: "users", name: "x" }, "reserved"],
|
|
[{ slug: "a".repeat(51), name: "x" }, "at most 50"],
|
|
[{ slug: "ok", name: " " }, "Name is required"],
|
|
[{ slug: "ok", name: "x".repeat(51) }, "at most 50"],
|
|
] as const
|
|
) {
|
|
const res = await create(json);
|
|
assertEquals(res.status, 400, JSON.stringify(json));
|
|
assertStringIncludes(res.body.error.message, message);
|
|
}
|
|
assertEquals((await create({ slug: "ok" })).status, 400);
|
|
|
|
const first = await create({ slug: "music", name: " Music " });
|
|
assertEquals(first.body.data.name, "Music");
|
|
const dupSlug = await create({ slug: "music", name: "Other" });
|
|
assertEquals(dupSlug.status, 409);
|
|
assertStringIncludes(dupSlug.body.error.message, "Slug");
|
|
const dupName = await create({ slug: "other", name: "Music" });
|
|
assertEquals(dupName.status, 409);
|
|
assertStringIncludes(dupName.body.error.message, "Name");
|
|
|
|
const second = await create({ slug: "films", name: "Films" });
|
|
const rename = await put(`/api/categories/${second.body.data.id}`, {
|
|
auth: admin.auth,
|
|
json: { slug: "music" },
|
|
});
|
|
assertEquals(rename.status, 409);
|
|
const ok = await put(`/api/categories/${second.body.data.id}`, {
|
|
auth: admin.auth,
|
|
json: { name: "Movies", position: 3 },
|
|
});
|
|
assertEquals(ok.body.data.slug, "films");
|
|
assertEquals(ok.body.data.name, "Movies");
|
|
assertEquals(ok.body.data.position, 3);
|
|
});
|
|
|
|
test("GET /api/categories/:slug/dumps: members only, private for the owner", async () => {
|
|
const owner = await createUser();
|
|
const cat = insertCategory({ slug: "music" });
|
|
const inCat = insertDump({ userId: owner.id, title: "in" });
|
|
const priv = insertDump({
|
|
userId: owner.id,
|
|
title: "private",
|
|
isPrivate: true,
|
|
});
|
|
insertDump({ userId: owner.id, title: "out" });
|
|
for (const d of [inCat, priv]) {
|
|
db.prepare(
|
|
`INSERT INTO dump_categories (dump_id, category_id) VALUES (?, ?);`,
|
|
)
|
|
.run(d.id, cat);
|
|
}
|
|
const anon = await get("/api/categories/music/dumps");
|
|
assertEquals(anon.body.data.items.map((d: { title: string }) => d.title), [
|
|
"in",
|
|
]);
|
|
assertEquals(anon.body.data.items[0].categoryIds, [cat]);
|
|
assertEquals(
|
|
(await get("/api/categories/music/dumps", { auth: owner.auth })).body.data
|
|
.total,
|
|
2,
|
|
);
|
|
assertEquals((await get("/api/categories/nope/dumps")).status, 404);
|
|
|
|
// Deleting the category unlinks its dumps without deleting them.
|
|
const admin = await createUser({ role: "admin" });
|
|
await del(`/api/categories/${cat}`, { auth: admin.auth });
|
|
assertEquals((await get(`/api/dumps/${inCat.id}`)).body.data.categoryIds, []);
|
|
});
|
|
|
|
test("GET /api/search: dumps, users and playlists, privacy-filtered", async () => {
|
|
const owner = await createUser({ username: "searcher_a" });
|
|
const other = await createUser();
|
|
db.prepare(
|
|
`UPDATE users SET description = 'loves banana bread' WHERE id = ?;`,
|
|
).run(other.id);
|
|
insertDump({ userId: owner.id, title: "Banana split" });
|
|
insertDump({
|
|
userId: owner.id,
|
|
title: "x",
|
|
comment: "a banana in the comment",
|
|
});
|
|
insertDump({
|
|
userId: owner.id,
|
|
title: "y",
|
|
richContent: { type: "generic", url: "u", description: "banana page" },
|
|
});
|
|
insertDump({ userId: owner.id, title: "Secret banana", isPrivate: true });
|
|
insertDump({ userId: owner.id, title: "Apple" });
|
|
insertPlaylist({ userId: owner.id, title: "Banana hits" });
|
|
insertPlaylist({
|
|
userId: owner.id,
|
|
title: "Private bananas",
|
|
isPublic: false,
|
|
});
|
|
|
|
const anon = (await get("/api/search?q=banana")).body.data;
|
|
assertEquals(anon.dumps.total, 3);
|
|
assertEquals(anon.users.map((u: { id: string }) => u.id), [other.id]);
|
|
assertEquals(anon.playlists.map((p: { title: string }) => p.title), [
|
|
"Banana hits",
|
|
]);
|
|
|
|
const mine =
|
|
(await get("/api/search?q=banana", { auth: owner.auth })).body.data;
|
|
assertEquals(mine.dumps.total, 4);
|
|
assertEquals(mine.playlists.length, 2);
|
|
|
|
const paged = (await get("/api/search?q=banana&limit=2")).body.data.dumps;
|
|
assertEquals(paged.items.length, 2);
|
|
assertEquals(paged.hasMore, true);
|
|
|
|
assertEquals((await get("/api/search?q=%20%20")).body.data, {
|
|
dumps: { items: [], total: 0, hasMore: false },
|
|
users: [],
|
|
playlists: [],
|
|
});
|
|
});
|