test: re-point core's suite at what core still owns
25 of 82 test files left with the module. Three that core keeps needed splitting rather than moving, and the split is the boundary in each case. announceJobs.test.js keeps the announce PIPELINE -- the shared backoff schedule, the parent-status rollup, core's Discord leg -- and loses the town-crier text building and classification, which are a module's leg. pushDispatch.test.js keeps the SSRF guard and publish() delivering a content-free tickle, and loses mapShardEvent and the shard fan-out, which are a module's catalog. playerRouteAccess.test.js is the one worth explaining. It guards a real past bug -- an admin 403'd off their own characters -- and it did so through /player/shard/accounts, which is now module-owned. The guarantee it protects is CORE's, though: /player/* is role-agnostic self-service, staff are a superset of players. So it stays here and asserts that through /player/appeals, a core route with the same gate. Moving it would have left core with no test of its own tier rule, which is precisely what regressed once before. The remaining updates are core's own tests catching up: ctx has four more members, registerCore now registers only what core owns (one stream, one leg, no filled slot), and the extension-slot test asks for the DECLARED slot's router rather than the filled one, since core declares it and a module fills it. The gated-surface floor drops from >100 to >50 -- it is there so a filter matching nothing fails loudly, not to track core's exact route count. 616 core tests and 160 client tests pass; the module's own suite is 351. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -6,11 +6,9 @@ process.env.DB_PORT = '59999'
|
||||
const { test, after } = require('node:test')
|
||||
const assert = require('node:assert/strict')
|
||||
|
||||
const { mapShardEvent, createTracker } = require('../src/config/shardStreams')
|
||||
const pushDispatch = require('../src/utils/pushDispatch')
|
||||
// fromShardEvent moved out of pushDispatch in PR 4: core publishes, the shard
|
||||
// side resolves an event to a stream and an owner (MODULE_SYSTEM.md §1.8).
|
||||
const shardPush = require('../src/utils/shardPush')
|
||||
const db = require('../src/utils/db')
|
||||
|
||||
after(() => db.close())
|
||||
@@ -33,71 +31,11 @@ function withEnv(vars, fn) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── mapShardEvent ───────────────────────────────────────────────────────────
|
||||
|
||||
test('server.hello / shutdown / crashed map to the public server.status stream', () => {
|
||||
const t = createTracker()
|
||||
assert.deepEqual(mapShardEvent({ kind: 'server.hello', bootId: 'b1' }, t), [
|
||||
{ streamId: 'server.status', ref: 'up:b1' },
|
||||
])
|
||||
assert.deepEqual(mapShardEvent({ kind: 'server.shutdown' }, t), [{ streamId: 'server.status', ref: 'down' }])
|
||||
assert.deepEqual(mapShardEvent({ kind: 'server.crashed' }, t), [{ streamId: 'server.status', ref: 'down' }])
|
||||
})
|
||||
|
||||
test('house.decay INTO idoc yields the public idoc.warning AND the owner-keyed house.idoc', () => {
|
||||
const t = createTracker()
|
||||
const out = mapShardEvent({ kind: 'house.decay', to: 'IDOC', serial: '0x40', ownerAcct: 'bob' }, t)
|
||||
assert.deepEqual(out, [
|
||||
{ streamId: 'idoc.warning', ref: '0x40' },
|
||||
{ streamId: 'house.idoc', ref: '0x40', ownerAccount: 'bob' },
|
||||
])
|
||||
// A non-IDOC decay stage produces nothing.
|
||||
assert.deepEqual(mapShardEvent({ kind: 'house.decay', to: 'Fairly', serial: '0x41' }, t), [])
|
||||
})
|
||||
|
||||
test('champ.update fires champ.start only on the inactive→active transition', () => {
|
||||
const t = createTracker()
|
||||
// First sight active → start.
|
||||
assert.deepEqual(mapShardEvent({ kind: 'champ.update', serial: 'c1', active: true }, t), [
|
||||
{ streamId: 'champ.start', ref: 'c1' },
|
||||
])
|
||||
// Still active → no re-fire.
|
||||
assert.deepEqual(mapShardEvent({ kind: 'champ.update', serial: 'c1', active: true }, t), [])
|
||||
// Goes inactive, then active again → fires again.
|
||||
assert.deepEqual(mapShardEvent({ kind: 'champ.update', serial: 'c1', active: false }, t), [])
|
||||
assert.deepEqual(mapShardEvent({ kind: 'champ.update', serial: 'c1', active: true }, t), [
|
||||
{ streamId: 'champ.start', ref: 'c1' },
|
||||
])
|
||||
})
|
||||
|
||||
test('city.update fires governor.election only on a real governor change, never on first sight', () => {
|
||||
const t = createTracker()
|
||||
// First sight of the city → no election (could be a reconnect snapshot).
|
||||
assert.deepEqual(mapShardEvent({ kind: 'city.update', city: 'Britain', governor: { serial: '0x1' } }, t), [])
|
||||
// Same governor → nothing.
|
||||
assert.deepEqual(mapShardEvent({ kind: 'city.update', city: 'Britain', governor: { serial: '0x1' } }, t), [])
|
||||
// New governor → election.
|
||||
assert.deepEqual(mapShardEvent({ kind: 'city.update', city: 'Britain', governor: { serial: '0x2' } }, t), [
|
||||
{ streamId: 'governor.election', ref: 'Britain' },
|
||||
])
|
||||
})
|
||||
|
||||
test('personal streams are owner-keyed and sensitive kinds never yield a public target', () => {
|
||||
const t = createTracker()
|
||||
const sale = mapShardEvent({ kind: 'vendor.sale', ownerAcct: 'bob', t: 7 }, t)
|
||||
assert.deepEqual(sale, [{ streamId: 'vendor.sale', ref: '7', ownerAccount: 'bob' }])
|
||||
|
||||
const login = mapShardEvent({ kind: 'account.login.attempt', acct: 'bob', ip: '1.2.3.4', t: 9 }, t)
|
||||
assert.deepEqual(login, [{ streamId: 'account.login', ref: '9', ownerAccount: 'bob' }])
|
||||
|
||||
// Every personal target carries an ownerAccount (never a bare public push).
|
||||
for (const target of [...sale, ...login]) assert.ok(target.ownerAccount, 'personal target must be owner-keyed')
|
||||
|
||||
// A truly sensitive, unmapped kind produces nothing at all.
|
||||
assert.deepEqual(mapShardEvent({ kind: 'cheat.fastwalk', acct: 'bob' }, t), [])
|
||||
assert.deepEqual(mapShardEvent({ kind: 'admin.audit', actor: 'staff' }, t), [])
|
||||
})
|
||||
|
||||
// `mapShardEvent` and `fromShardEvent` left with module-uo in Phase 3: which
|
||||
// shard event becomes which stream is the module's catalog, not core's
|
||||
// infrastructure. What stays here is what core owns — the SSRF guard on the
|
||||
// configured ntfy endpoint, and `publish()` delivering a content-free tickle to
|
||||
// the right subscribers.
|
||||
// ── isAllowedEndpoint (SSRF guard) ──────────────────────────────────────────
|
||||
|
||||
test('isAllowedEndpoint pins the configured ntfy origin and rejects everything else', () => {
|
||||
@@ -188,51 +126,3 @@ test('publish skips endpoints that fail the SSRF guard', async () => {
|
||||
assert.equal(calls[0].url, 'https://relay.test/ok')
|
||||
})
|
||||
})
|
||||
|
||||
// ── fromShardEvent (owner resolution) ───────────────────────────────────────
|
||||
|
||||
test('fromShardEvent resolves a personal event to the owning user, or drops it if unlinked', async () => {
|
||||
await withEnv({ NTFY_BASE_URL: undefined, NTFY_ALLOWED_ORIGINS: undefined }, async () => {
|
||||
const { calls, fetchImpl } = captureFetch()
|
||||
const userStreamCalls = []
|
||||
const shardLinks = { getByAccount: async (acct) => (acct === 'mine' ? { userId: 42 } : null) }
|
||||
const pushDevices = {
|
||||
endpointsForStream: async () => [],
|
||||
endpointsForUserStream: async (userId, s) => {
|
||||
userStreamCalls.push([userId, s])
|
||||
return [{ endpoint: 'https://relay.test/UPc' }]
|
||||
},
|
||||
}
|
||||
const deps = { shardLinks, pushDevices, fetchImpl, tracker: createTracker() }
|
||||
|
||||
await shardPush.fromShardEvent({ kind: 'vendor.sale', ownerAcct: 'mine', t: 1 }, deps)
|
||||
assert.deepEqual(userStreamCalls, [[42, 'vendor.sale']])
|
||||
assert.equal(calls.length, 1)
|
||||
|
||||
// Unlinked account → nobody to notify → no publish.
|
||||
userStreamCalls.length = 0
|
||||
calls.length = 0
|
||||
await shardPush.fromShardEvent({ kind: 'vendor.sale', ownerAcct: 'stranger', t: 2 }, deps)
|
||||
assert.equal(userStreamCalls.length, 0)
|
||||
assert.equal(calls.length, 0)
|
||||
})
|
||||
})
|
||||
|
||||
test('fromShardEvent fans a public shard event to the stream’s subscribers', async () => {
|
||||
await withEnv({ NTFY_BASE_URL: undefined, NTFY_ALLOWED_ORIGINS: undefined }, async () => {
|
||||
const { fetchImpl } = captureFetch()
|
||||
const publicCalls = []
|
||||
const pushDevices = {
|
||||
endpointsForStream: async (s) => {
|
||||
publicCalls.push(s)
|
||||
return []
|
||||
},
|
||||
endpointsForUserStream: async () => [],
|
||||
}
|
||||
await shardPush.fromShardEvent(
|
||||
{ kind: 'server.hello', bootId: 'b1' },
|
||||
{ pushDevices, fetchImpl, tracker: createTracker() },
|
||||
)
|
||||
assert.deepEqual(publicCalls, ['server.status'])
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user