// ── Who a deleted account's snapshots show as (ACCOUNT_DELETION.md AD5, AD6) ── // // The rule itself, then each place a member can read a snapshot: forum threads // and posts, the leader's guest list, and the Team digest mail. The Reports // queue (staff-only) is covered in contentReports.test.js. process.env.DB_HOST = '127.0.0.1' process.env.DB_PORT = '59999' const { test, afterEach, after } = require('node:test') const assert = require('node:assert/strict') const { displayAuthor, DELETED_PLAYER, seesDeletedNames, hasActor } = require('../src/utils/displayAuthor') const forum = require('../src/model/teams/teamForum.model') const grants = require('../src/model/teams/teamGrants.model') const accessDb = require('../src/model/teams/teamAccess.db') const teamsDb = require('../src/model/teams/teams.db') const db = require('../src/utils/db') after(() => db.close()) const saved = [] function patch(obj, key, value) { saved.push([obj, key, obj[key]]) obj[key] = value } afterEach(() => { while (saved.length) { const [obj, key, value] = saved.pop() obj[key] = value } }) const MEMBER = { role: 'player' } const STAFF = ['admin', 'editor', 'moderator'].map((role) => ({ role })) // ── the rule ─────────────────────────────────────────────────────────────── test('a live account shows its name to everyone', () => { for (const viewer of [null, MEMBER, ...STAFF]) { assert.deepEqual(displayAuthor({ userId: 4, username: 'Thorne' }, viewer), { name: 'Thorne', deleted: false }) } }) test('a deleted account reads "[deleted player]" to members and visitors (AD5)', () => { assert.equal(DELETED_PLAYER, '[deleted player]') for (const viewer of [null, undefined, MEMBER, { role: 'leader' }, {}]) { assert.deepEqual(displayAuthor({ userId: null, username: 'Ravenna' }, viewer), { name: DELETED_PLAYER, deleted: true }) } }) test('staff — admin, editor and moderator — see the stored name, marked deleted (AD6)', () => { for (const viewer of STAFF) { assert.equal(seesDeletedNames(viewer), true) assert.deepEqual(displayAuthor({ userId: null, username: 'Ravenna' }, viewer), { name: 'Ravenna', deleted: true }) } // With no snapshot at all there is no name to show anybody. assert.deepEqual(displayAuthor({ userId: null, username: null }, STAFF[0]), { name: DELETED_PLAYER, deleted: true }) }) test('an absent actor is not a deleted one', () => { assert.equal(hasActor(null, null), false) assert.equal(hasActor(null, ''), false) assert.equal(hasActor(null, 'OldMod'), true) assert.equal(hasActor(5, null), true) }) // ── forum threads and posts ──────────────────────────────────────────────── const threadRow = (over = {}) => ({ id: 5, type: 'discussion', title: 'Raid night', created_by: null, created_username: 'Ravenna', created_at: new Date(), last_post_at: new Date(), post_count: 2, pinned: 0, locked: 0, status: 'visible', ...over, }) const postRow = (over = {}) => ({ id: 80, thread_id: 5, author_user_id: null, author_username: 'Ravenna', body_html: '
Guide
', created_at: new Date(), edited_at: null, status: 'visible', ...over, }) test('a forum thread by a deleted account: "[deleted player]" to a member, the name to staff', () => { assert.equal(forum.publicThread(threadRow(), MEMBER).author, DELETED_PLAYER) assert.equal(forum.publicThread(threadRow()).author, DELETED_PLAYER) // no viewer said assert.equal(forum.publicThread(threadRow(), MEMBER).authorDeleted, true) for (const viewer of STAFF) { const t = forum.publicThread(threadRow(), viewer) assert.equal(t.author, 'Ravenna') assert.equal(t.authorDeleted, true) } assert.equal(forum.publicThread(threadRow({ created_by: 4 }), MEMBER).author, 'Ravenna') assert.equal(forum.publicThread(threadRow({ created_by: 4 }), MEMBER).authorDeleted, false) }) test('a forum post by a deleted account follows the same rule, and is nobody’s to edit', () => { const asMember = forum.renderPost(postRow(), 'disabled', { userId: 9, role: 'player', isStaff: false }) assert.equal(asMember.author, DELETED_PLAYER) assert.equal(asMember.authorDeleted, true) assert.equal(asMember.mine, false) assert.equal(asMember.canEdit, false) const asEditor = forum.renderPost(postRow(), 'disabled', { userId: 2, role: 'editor', isStaff: false }) assert.equal(asEditor.author, 'Ravenna') assert.equal(asEditor.authorDeleted, true) }) test('the thread list and a thread pass the viewer down to every row', async () => { const forumDb = require('../src/model/teams/teamForum.db') const forumSettings = require('../src/model/teams/teamForumSettings.model') patch(forumDb, 'threadsByTeam', async () => [threadRow()]) patch(forumDb, 'threadById', async () => ({ ...threadRow(), team_id: 1 })) patch(forumDb, 'postsByThread', async () => [postRow()]) patch(forumSettings, 'imageMode', async () => 'disabled') const [listedForMember] = await forum.listThreads(1, { viewer: MEMBER }) assert.equal(listedForMember.author, DELETED_PLAYER) const [listedForStaff] = await forum.listThreads(1, { viewer: { role: 'moderator' } }) assert.equal(listedForStaff.author, 'Ravenna') const thread = await forum.getThread(1, 5, { viewer: { userId: 9, role: 'player' } }) assert.equal(thread.author, DELETED_PLAYER) assert.equal(thread.posts[0].author, DELETED_PLAYER) const staffThread = await forum.getThread(1, 5, { canModerate: true, viewer: { userId: 2, role: 'admin', isStaff: true } }) assert.equal(staffThread.author, 'Ravenna') assert.equal(staffThread.posts[0].author, 'Ravenna') }) // ── the leader's guest list ──────────────────────────────────────────────── test('a deleted account is not a forum guest, and a deleted granter reads "[deleted player]" (AD13)', async () => { patch(accessDb, 'activeGrants', async () => [ { user_id: 21, username: 'Guest', granted_by: null, granted_username: 'OldLeader', granted_at: new Date(), reason: null }, { user_id: null, username: 'Ravenna', granted_by: 4, granted_username: 'Leader', granted_at: new Date(), reason: null }, { user_id: 22, username: 'Member', granted_by: 4, granted_username: 'Leader', granted_at: new Date(), reason: null }, ]) patch(teamsDb, 'membersByTeam', async () => [{ user_id: 22 }]) const guests = await grants.forumGuests(1, MEMBER) assert.deepEqual(guests.map((g) => g.username), ['Guest']) assert.equal(guests[0].grantedBy, DELETED_PLAYER) const forStaff = await grants.forumGuests(1, { role: 'moderator' }) assert.equal(forStaff[0].grantedBy, 'OldLeader') }) // ── the Team digest ──────────────────────────────────────────────────────── test('the digest mail names a deleted author "[deleted player]"', async () => { const worker = require('../src/utils/teamDigestWorker') const teamNotify = require('../src/model/teams/teamNotify.model') const templates = require('../src/engagement/templates') const safety = require('../src/model/safety/userSafety.model') let items = null patch(safety, 'forumFilter', async () => ({ blockedUserIds: new Set(), hidden: { team_forum_thread: new Set(), team_forum_post: new Set() }, })) patch(teamNotify, 'digestPostsSince', async () => [ { id: 1, thread_id: 5, body_html: 'a
', created_at: new Date(), author_user_id: null, author_username: 'Ravenna', title: 'Raid night', type: 'discussion' }, { id: 2, thread_id: 5, body_html: 'b
', created_at: new Date(), author_user_id: 4, author_username: 'Thorne', title: 'Raid night', type: 'discussion' }, ]) patch(templates, 'renderByKey', async (key, values) => { items = values.items return null // stop before anything is sent }) await worker.sendOne({ id: 1, slug: 'wolves', name: 'Iron Wolves' }, { user_id: 9, last_digest_at: null }, null, new Date()) assert.deepEqual(items.map((i) => i.heading), ['Raid night — [deleted player]', 'Raid night — Thorne']) })