Files
website/client/test/teamAdmin.test.js
wtclaude 8012a457fc
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 39s
PR Checks / client-build (pull_request) Successful in 58s
PR Checks / server-tests (pull_request) Successful in 12m43s
fix(teams): say "game server", not "shard", on the never-synced Teams panel
Admin → Teams told an operator that an unsynced projection "is not a
confirmed empty shard". Teams is a core primitive and core's word for the
host is the game server; "shard" is Ultima Online's. The detail now reads
"What is shown below does not confirm that the game server has no Teams."

The matching test, and the four comments that said "shard" about the same
state, follow it. Found while making runicgateway.com's core docs
game-neutral (runicgateway.com#34), which had to paraphrase this line
rather than quote it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-22 14:43:37 -05:00

141 lines
6.3 KiB
JavaScript

// 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 a game server with no Teams', () => {
// 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, /does not confirm that the game server has no Teams/)
})
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/)
})