// What Admin → Teams says (client/src/lib/teamAdmin.js). // // The test that earns this file: "no Teams" and "core has not been able to ask" // must never read the same. They produce almost identical screens — an empty // table — and one is fine while the other is an outage an operator needs to act // on. Everything else here is in service of that distinction. import { test } from 'node:test' import assert from 'node:assert/strict' import { freshnessOf, ago, statusOf, gateLabelFor, describeRequest, parsePayload, leadershipOf, TONE, } from '../src/lib/teamAdmin.js' const minutesAgo = (n) => new Date(Date.now() - n * 60_000).toISOString() // ── Freshness: four states that must not be confused ─────────────────────── test('no provider is idle, not a fault', () => { const f = freshnessOf({ configured: false }) assert.equal(f.tone, TONE.idle) assert.match(f.label, /No Team provider/) }) test('never synced is reported as never synced, not as an empty shard', () => { // The failure this prevents: an empty projection core has never confirmed, // rendered as though the game genuinely has no Teams. const f = freshnessOf({ configured: true, lastSyncAt: null }) assert.equal(f.tone, TONE.bad) assert.equal(f.label, 'Never synced') assert.match(f.detail, /not a confirmed empty shard/) }) test('stale says how old it is', () => { const f = freshnessOf({ configured: true, stale: true, lastSyncAt: minutesAgo(14) }) assert.equal(f.tone, TONE.warn) assert.equal(f.label, 'Stale') assert.match(f.detail, /14 minutes ago/) }) test('current says so plainly', () => { const f = freshnessOf({ configured: true, stale: false, lastSyncAt: minutesAgo(2) }) assert.equal(f.tone, TONE.ok) assert.equal(f.label, 'Current') }) test('ago is deliberately coarse', () => { // Second-level precision would be false comfort about a projection whose poll // interval is fifteen minutes. assert.equal(ago(null), 'never') assert.equal(ago(new Date().toISOString()), 'just now') assert.equal(ago(minutesAgo(14)), '14 minutes ago') assert.equal(ago(minutesAgo(60)), '1 hour ago') assert.equal(ago(minutesAgo(180)), '3 hours ago') assert.equal(ago(minutesAgo(60 * 72)), '3 days ago') }) // ── Status ───────────────────────────────────────────────────────────────── test('the four Team statuses are distinguishable', () => { assert.equal(statusOf({ status: 'active' }).label, 'Public') assert.equal(statusOf({ status: 'active', hidden: 1, hiddenReason: 'reserved_name' }).label, 'Hidden — reserved name') assert.equal(statusOf({ status: 'active', hidden: 1, hiddenReason: 'staff' }).label, 'Hidden by staff') assert.equal(statusOf({ status: 'archived', archivedReason: 'disbanded' }).label, 'Archived') assert.equal(statusOf({ status: 'archived', archivedReason: 'renamed' }).label, 'Renamed') }) test('a reserved-name hide is the loudest tone', () => { assert.equal(statusOf({ status: 'active', hidden: 1, hiddenReason: 'reserved_name' }).tone, TONE.bad) assert.equal(statusOf({ status: 'active', hidden: 1, hiddenReason: 'staff' }).tone, TONE.warn) }) // ── The gate, described honestly ─────────────────────────────────────────── test('the button says what will actually happen for this role', () => { // The server decides from the live role; this only describes it. Saying // "Publish" to a moderator would make the pending result a surprise. assert.equal(gateLabelFor('admin', 'Publish'), 'Publish') assert.equal(gateLabelFor('moderator', 'Publish'), 'Request publish') }) // ── The approval queue ───────────────────────────────────────────────────── test('a request describes itself, including the name being published', () => { assert.equal( describeRequest({ action: 'unhide', requested_username: 'mod1', team_name: 'Admin' }), 'mod1 asks to publish “Admin”', ) assert.equal( describeRequest({ action: 'display_name_override', requested_username: 'mod1', team_name: 'Admin', payload: { displayName: 'The Old Guard' }, }), 'mod1 asks to display “Admin” as “The Old Guard”', ) assert.equal( describeRequest({ action: 'clear_display_name_override', requested_username: 'mod1', team_name: 'X' }), 'mod1 asks to clear the display name on “X”', ) }) test('a deleted requester still reads as a sentence', () => { // §2.10 sets requested_by to NULL and keeps the username snapshot; when even // that is gone the queue must not render "null asks to publish". assert.match(describeRequest({ action: 'unhide', team_name: 'Admin' }), /^a deleted user asks/) }) test('a payload arrives parsed or as a string, and both work', () => { assert.deepEqual(parsePayload({ displayName: 'X' }), { displayName: 'X' }) assert.deepEqual(parsePayload('{"displayName":"X"}'), { displayName: 'X' }) assert.deepEqual(parsePayload(null), {}) assert.deepEqual(parsePayload('not json'), {}) }) // ── Leadership shows the decision, not just the answer ───────────────────── test('an unoverridden member reads straight from the projection', () => { const l = leadershipOf({ isLeader: true, isLeaderSynced: true }) assert.equal(l.isLeader, true) assert.equal(l.overridden, false) assert.equal(l.note, null) }) test('an override is shown AS an override, with what the game says', () => { // Staff looking at a roster need to see that a decision was made, not a fact // that looks like the game's. const l = leadershipOf({ isLeaderSynced: true, leaderOverride: { effect: 'deny', by: 'mod1', reason: 'harassment' }, }) assert.equal(l.isLeader, false) assert.equal(l.overridden, true) assert.match(l.note, /Denied by mod1 — harassment/) assert.match(l.note, /the game says leader/) }) test('a grant override says the game disagrees', () => { const l = leadershipOf({ isLeaderSynced: false, leaderOverride: { effect: 'grant', by: 'root' } }) assert.equal(l.isLeader, true) assert.match(l.note, /the game says not a leader/) })