import { count, createUser, db, get, insertDump, post, put, test, } from "../helpers.ts"; import type { DumpCommentedData, DumpUpvotedData, 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 put(`/api/me/follow/users/${me.id}`, { auth: other.auth }); await post(`/api/dumps/${dumpId}/comments`, { auth: other.auth, json: { body: "hey" }, }); await put(`/api/me/follow/users/${other.id}`, { auth: me.auth }); const list = await get>("/api/me/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/me/notifications/read", { 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/me/notifications")).status, 401); }); test("notifications: names and item paths are joined when read, so they follow renames", async () => { const me = await createUser(); const fan = await createUser(); const { id: dumpId } = insertDump({ userId: me.id, title: "Mine" }); await post(`/api/dumps/${dumpId}/comments`, { auth: fan.auth, json: { body: "nice" }, }); db.prepare(`UPDATE users SET username = 'fan_renamed' WHERE id = ?;`) .run(fan.id); db.prepare(`UPDATE dumps SET slug = 'mine-now' WHERE id = ?;`).run(dumpId); const [note] = (await get>( "/api/me/notifications", { auth: me.auth }, )).data.items; assertEquals(note.type, "dump_commented"); const data = note.data as DumpCommentedData; assertEquals(data.commenterUsername, "fan_renamed"); assertEquals([data.dumpOwnerUsername, data.dumpSlug], [ me.username, "mine-now", ]); // Once the dump is gone, the notification has no path to it. db.prepare(`DELETE FROM dumps WHERE id = ?;`).run(dumpId); db.prepare( `INSERT INTO notifications (id, user_id, type, data, created_at) VALUES (?, ?, 'dump_upvoted', ?, ?);`, ).run( crypto.randomUUID(), me.id, JSON.stringify({ voterId: fan.id, voterUsername: "x", dumpId, dumpTitle: "Mine", }), new Date().toISOString(), ); const [upvote] = (await get>( "/api/me/notifications", { auth: me.auth }, )).data.items; assertEquals((upvote.data as DumpUpvotedData).dumpSlug, undefined); });