Validation: the dump validators refuse the nulls their shared types don't allow (a null title or note on creation, a null URL on edit); a playlist title of spaces is refused and titles are stored trimmed; usernames and emails are checked by the same USERNAME_RE and EMAIL_RE in shared/validation.ts on sign-up, on PATCH /api/users/me (which checked other limits) and in the sign-up form. Mentions are read by shared/mention.ts's mentionedUsernames, which skips code and URLs as Markdown shows them: https://mastodon.social/@bob or a `@bob` in code notified bob; the editor's suggestions derive their trigger from the same pattern instead of a copy. Following an unknown user is a 404 rather than a foreign-key 500, and following a private playlist a 404 rather than a 403 that confirmed it exists. `ws` and `emojibase` are reserved category slugs, since the server answers them before the app. GET /api/users/me no longer turns every error into a 500. Dead code goes: adminOnlyMiddleware, verifyEmailTransport, listUsers, deleteUser with disconnectUser and the force_logout message only it sent, PATCH /api/notifications/:id/read (with markOneRead), isSlugAvailable, the unreachable 500 in fetchComment, and the `if (!ctx.state.user)` checks behind authMiddleware. 214 backend and 627 frontend tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
44 lines
1.4 KiB
TypeScript
44 lines
1.4 KiB
TypeScript
import { count, createUser, get, insertDump, post, test } from "../helpers.ts";
|
|
import type { Notification } from "../../../shared/notification.ts";
|
|
import type { PaginatedData } from "../../../shared/api.ts";
|
|
import { assertEquals } from "@std/assert";
|
|
|
|
test("notifications: listed newest first, marked read one or all, never someone else's", async () => {
|
|
const me = await createUser();
|
|
const other = await createUser();
|
|
const { id: dumpId } = insertDump({ userId: me.id, title: "Mine" });
|
|
// Two notifications for me, one for other.
|
|
await post(`/api/follows/users/${me.id}`, { auth: other.auth });
|
|
await post(`/api/dumps/${dumpId}/comments`, {
|
|
auth: other.auth,
|
|
json: { body: "hey" },
|
|
});
|
|
await post(`/api/follows/users/${other.id}`, { auth: me.auth });
|
|
|
|
const list = await get<PaginatedData<Notification>>("/api/notifications", {
|
|
auth: me.auth,
|
|
});
|
|
assertEquals(list.data.items.length, 2);
|
|
assertEquals(
|
|
list.data.items.map((n) => n.type),
|
|
["dump_commented", "user_followed"],
|
|
);
|
|
assertEquals(
|
|
list.data.items.every((n) => !n.read),
|
|
true,
|
|
);
|
|
|
|
assertEquals(
|
|
(await post("/api/notifications/read-all", { auth: me.auth })).status,
|
|
204,
|
|
);
|
|
assertEquals(
|
|
count(
|
|
`SELECT COUNT(*) as n FROM notifications WHERE user_id = ? AND read = 0;`,
|
|
me.id,
|
|
),
|
|
0,
|
|
);
|
|
assertEquals((await get("/api/notifications")).status, 401);
|
|
});
|