213 lines
6.7 KiB
TypeScript
213 lines
6.7 KiB
TypeScript
import {
|
|
createUser,
|
|
db,
|
|
insertComment,
|
|
insertDump,
|
|
insertPlaylist,
|
|
test,
|
|
} from "../helpers.ts";
|
|
import { assertEquals } from "@std/assert";
|
|
import {
|
|
communityStatsOf,
|
|
getCommunityFeed,
|
|
getCommunityStats,
|
|
listCommunityEvents,
|
|
refreshCommunityStats,
|
|
} from "../../services/community-service.ts";
|
|
|
|
const DAY = 86_400_000;
|
|
const ago = (ms: number) => new Date(Date.now() - ms).toISOString();
|
|
const week = () =>
|
|
[new Date(Date.now() - 7 * DAY), new Date(Date.now() + 60_000)] as const;
|
|
|
|
function vote(dumpId: string, n: number) {
|
|
db.prepare(`UPDATE dumps SET vote_count = ? WHERE id = ?;`).run(n, dumpId);
|
|
}
|
|
|
|
test("communityStatsOf: the period's top public dumps, members, public playlists and totals", async () => {
|
|
const ann = await createUser({ createdAt: ago(20 * DAY) });
|
|
const bob = await createUser({ invitedBy: ann.id, createdAt: ago(2 * DAY) });
|
|
const cy = await createUser({ createdAt: ago(DAY) });
|
|
const hit = insertDump({ userId: bob.id, title: "Hit" });
|
|
vote(hit.id, 5);
|
|
const tie = insertDump({ userId: ann.id, title: "Tie", createdAt: ago(DAY) });
|
|
vote(tie.id, 2);
|
|
const newer = insertDump({ userId: ann.id, title: "Newer" });
|
|
vote(newer.id, 2);
|
|
insertDump({ userId: ann.id, title: "No votes" });
|
|
vote(insertDump({ userId: ann.id, title: "Private", isPrivate: true }).id, 9);
|
|
vote(
|
|
insertDump({ userId: ann.id, title: "Too old", createdAt: ago(10 * DAY) })
|
|
.id,
|
|
9,
|
|
);
|
|
insertComment({ dumpId: hit.id, userId: cy.id, body: "nice" });
|
|
insertPlaylist({ userId: ann.id, title: "Small" });
|
|
const full = insertPlaylist({ userId: bob.id, title: "Full" });
|
|
insertPlaylist({ userId: bob.id, title: "Hidden", isPublic: false });
|
|
// Bob's private dump isn't counted: every reader gets the same figures.
|
|
const secret = insertDump({
|
|
userId: bob.id,
|
|
title: "Secret",
|
|
isPrivate: true,
|
|
});
|
|
db.prepare(
|
|
`INSERT INTO playlist_dumps (playlist_id, dump_id, position, added_at) VALUES (?, ?, 0, ?), (?, ?, 1, ?), (?, ?, 2, ?);`,
|
|
).run(
|
|
full.id,
|
|
hit.id,
|
|
ago(0),
|
|
full.id,
|
|
tie.id,
|
|
ago(0),
|
|
full.id,
|
|
secret.id,
|
|
ago(0),
|
|
);
|
|
|
|
const stats = communityStatsOf(...week());
|
|
assertEquals(stats.top.map((d) => d.title), ["Hit", "Newer", "Tie"]);
|
|
assertEquals(stats.top[0].ownerUsername, bob.username);
|
|
assertEquals(
|
|
stats.members.map((m) => [m.username, m.invitedByUsername]),
|
|
[[bob.username, ann.username], [cy.username, undefined]],
|
|
);
|
|
assertEquals(
|
|
stats.playlists.map((p) => [p.title, p.dumpCount]),
|
|
[["Full", 2], ["Small", 0]],
|
|
);
|
|
assertEquals(stats.totals, { dumps: 4, upvotes: 0, comments: 1 });
|
|
assertEquals(stats.chatMessages, 0);
|
|
});
|
|
|
|
test("getCommunityStats: serves the last refresh, of the week or the month", async () => {
|
|
const ann = await createUser({ createdAt: ago(20 * DAY) });
|
|
vote(
|
|
insertDump({ userId: ann.id, title: "Month", createdAt: ago(20 * DAY) })
|
|
.id,
|
|
1,
|
|
);
|
|
refreshCommunityStats();
|
|
assertEquals(getCommunityStats("weekly").top, []);
|
|
assertEquals(getCommunityStats("monthly").top.map((d) => d.title), [
|
|
"Month",
|
|
]);
|
|
assertEquals(getCommunityStats("monthly").members.length, 1);
|
|
|
|
vote(
|
|
insertDump({ userId: ann.id, title: "Later", createdAt: ago(1000) }).id,
|
|
1,
|
|
);
|
|
assertEquals(getCommunityStats("weekly").top, []);
|
|
refreshCommunityStats();
|
|
assertEquals(getCommunityStats("weekly").top.map((d) => d.title), [
|
|
"Later",
|
|
]);
|
|
});
|
|
|
|
function castVote(dumpId: string, userId: string, at: string) {
|
|
db.prepare(
|
|
`INSERT INTO votes (dump_id, user_id, created_at) VALUES (?, ?, ?);`,
|
|
).run(dumpId, userId, at);
|
|
}
|
|
|
|
test("listCommunityEvents: what was done in public, newest first, a dump's latest comment and upvote alone", async () => {
|
|
const ann = await createUser({ createdAt: ago(9 * DAY) });
|
|
const bob = await createUser({ invitedBy: ann.id, createdAt: ago(8 * DAY) });
|
|
const hit = insertDump({
|
|
userId: ann.id,
|
|
title: "Hit",
|
|
createdAt: ago(7 * DAY),
|
|
});
|
|
const secret = insertDump({
|
|
userId: ann.id,
|
|
title: "Secret",
|
|
isPrivate: true,
|
|
});
|
|
insertComment({ dumpId: hit.id, userId: ann.id, createdAt: ago(6 * DAY) });
|
|
const last = insertComment({
|
|
dumpId: hit.id,
|
|
userId: bob.id,
|
|
createdAt: ago(5 * DAY),
|
|
});
|
|
const gone = insertComment({ dumpId: hit.id, userId: ann.id });
|
|
db.prepare(`UPDATE comments SET deleted = 1 WHERE id = ?;`).run(gone);
|
|
insertComment({ dumpId: secret.id, userId: ann.id });
|
|
castVote(hit.id, bob.id, ago(4 * DAY));
|
|
castVote(hit.id, ann.id, ago(3 * DAY));
|
|
castVote(secret.id, ann.id, ago(0));
|
|
const mix = insertPlaylist({ userId: bob.id, title: "Mix" });
|
|
insertPlaylist({ userId: bob.id, title: "Hidden", isPublic: false });
|
|
|
|
const events = listCommunityEvents();
|
|
assertEquals(
|
|
events.map((e) => [e.type, e.username]),
|
|
[
|
|
["playlist_created", bob.username],
|
|
["dump_upvoted", ann.username],
|
|
["dump_commented", bob.username],
|
|
["dump_posted", ann.username],
|
|
["user_joined", bob.username],
|
|
["user_joined", ann.username],
|
|
],
|
|
);
|
|
const [playlist, upvote, comment, , joined] = events;
|
|
assertEquals(playlist.type === "playlist_created" && playlist.playlist, {
|
|
title: "Mix",
|
|
slug: mix.slug,
|
|
ownerUsername: bob.username,
|
|
});
|
|
assertEquals(upvote.type === "dump_upvoted" && upvote.dump, {
|
|
title: "Hit",
|
|
slug: hit.slug,
|
|
ownerUsername: ann.username,
|
|
});
|
|
assertEquals(upvote.id, `dump_upvoted:${hit.id}`);
|
|
assertEquals(comment.type === "dump_commented" && comment.commentId, last);
|
|
assertEquals(
|
|
joined.type === "user_joined" && joined.invitedByUsername,
|
|
ann.username,
|
|
);
|
|
});
|
|
|
|
test("listCommunityEvents: keeps the 12 latest", async () => {
|
|
const ann = await createUser({ createdAt: ago(DAY) });
|
|
for (let i = 0; i < 14; i++) {
|
|
insertDump({
|
|
userId: ann.id,
|
|
title: `Dump ${i}`,
|
|
createdAt: ago(i * 1000),
|
|
});
|
|
}
|
|
const events = listCommunityEvents();
|
|
assertEquals(events.length, 12);
|
|
assertEquals(
|
|
events.map((e) => e.type === "dump_posted" && e.dump.title),
|
|
Array.from({ length: 12 }, (_, i) => `Dump ${i}`),
|
|
);
|
|
});
|
|
|
|
test("getCommunityFeed: the events as they are now, the week's figures as of the last refresh", async () => {
|
|
const ann = await createUser({ createdAt: ago(20 * DAY) });
|
|
const hit = insertDump({
|
|
userId: ann.id,
|
|
title: "Hit",
|
|
createdAt: ago(1000),
|
|
});
|
|
vote(hit.id, 3);
|
|
refreshCommunityStats();
|
|
insertDump({ userId: ann.id, title: "Later" });
|
|
|
|
const feed = getCommunityFeed();
|
|
assertEquals(
|
|
feed.events.map((e) => e.type === "dump_posted" && e.dump.title),
|
|
["Later", "Hit", false],
|
|
);
|
|
assertEquals(feed.week.totals.dumps, 1);
|
|
assertEquals(feed.week.top, {
|
|
title: "Hit",
|
|
slug: hit.slug,
|
|
ownerUsername: ann.username,
|
|
});
|
|
});
|