Phase 1 of the Protocol 2.0/2.1 integration: the read/ingest backend for the four
new uo-link boards, following the established champs/pages pattern (ingest → our
MariaDB + snapshot-on-reconnect + public SSE + token-free public endpoint).
- Schema: shard_guilds, shard_governors, shard_governor_terms, shard_presence;
extend shard_houses with the house.update registry columns (owner_name,
co_owners, friends, price, decay, in_registry) so the decay-transition and
registry feeds share one house row without clobbering each other.
- Ingest: route guild.update/remove, city.update, presence.online,
house.update/remove; log guild.join (real-time joins feed); region.enter is
broadcast-only. All new public kinds added to the SSE allowlist.
- Governor term history captured from day one: on every observed governor CHANGE
the open term is closed and a new one opened, idempotent so backfill/duplicate
city.update never spawn spurious terms. votes stays NULL (the feed carries only
candidate count, not tallies) — we never fabricate vote numbers.
- Client + backfill: getGuilds/getGovernors/getHouses/getPresence; snapshot each
board on every WS (re)connect, independently guarded so an empty/failed board
(e.g. no City Loyalty) never wipes another.
- Public endpoints: /shard/{guilds,governors,governors/:city/history,presence,houses}.
- Tests: ingest routing for all new kinds + governor term-capture idempotency
(15 new; full suite 179/179). Swagger regenerated.
Refs .plans/protocol2-integration.md (Phase 1).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
78 lines
3.3 KiB
JavaScript
78 lines
3.3 KiB
JavaScript
const { test, beforeEach } = require('node:test')
|
|
const assert = require('node:assert/strict')
|
|
|
|
// Term capture lives in the model (shardState.model.upsertGovernor →
|
|
// recordGovernorTransition) and talks to the db module. We exercise the real
|
|
// logic against an in-memory fake by monkeypatching the shared db module object
|
|
// (same instance the model require()s) — no DB, no mocking library.
|
|
const db = require('../src/model/shardState/shardState.db')
|
|
const model = require('../src/model/shardState/shardState.model')
|
|
|
|
let terms // in-memory shard_governor_terms
|
|
let nextId
|
|
const saved = {}
|
|
|
|
beforeEach(() => {
|
|
terms = []
|
|
nextId = 1
|
|
for (const k of ['currentGovernorTerm', 'closeGovernorTerm', 'openGovernorTerm', 'upsertGovernor']) {
|
|
saved[k] = db[k]
|
|
}
|
|
db.currentGovernorTerm = async (city) =>
|
|
terms.find((t) => t.city === city && t.ended_at === null) || null
|
|
db.closeGovernorTerm = async (id, endedAt) => {
|
|
const row = terms.find((t) => t.id === id)
|
|
if (row) row.ended_at = endedAt
|
|
}
|
|
db.openGovernorTerm = async ({ city, serial, name, acct, webId, startedAt }) => {
|
|
terms.push({ id: nextId++, city, governor_serial: serial, governor_name: name,
|
|
governor_acct: acct, governor_web_id: webId, started_at: startedAt, ended_at: null })
|
|
}
|
|
db.upsertGovernor = async () => {} // snapshot write — irrelevant to term capture
|
|
})
|
|
|
|
function restore() {
|
|
for (const k of Object.keys(saved)) db[k] = saved[k]
|
|
}
|
|
|
|
test('a repeated city.update with the same governor does NOT open a second term', async () => {
|
|
await model.upsertGovernor({ city: 'Britain', governor: { serial: '0x1', name: 'Darrow' }, t: 100 })
|
|
await model.upsertGovernor({ city: 'Britain', governor: { serial: '0x1', name: 'Darrow' }, t: 200 })
|
|
const open = terms.filter((t) => t.ended_at === null)
|
|
assert.equal(terms.length, 1)
|
|
assert.equal(open.length, 1)
|
|
assert.equal(open[0].governor_serial, '0x1')
|
|
assert.equal(open[0].started_at, 100)
|
|
restore()
|
|
})
|
|
|
|
test('a governor change closes the old term and opens a new one', async () => {
|
|
await model.upsertGovernor({ city: 'Britain', governor: { serial: '0x1', name: 'Darrow' }, t: 100 })
|
|
await model.upsertGovernor({ city: 'Britain', governor: { serial: '0x2', name: 'Mira' }, t: 300 })
|
|
assert.equal(terms.length, 2)
|
|
const [first, second] = terms
|
|
assert.equal(first.governor_serial, '0x1')
|
|
assert.equal(first.ended_at, 300) // closed at the transition time
|
|
assert.equal(second.governor_serial, '0x2')
|
|
assert.equal(second.ended_at, null) // now current
|
|
assert.equal(second.started_at, 300)
|
|
restore()
|
|
})
|
|
|
|
test('a seat going vacant closes the term without opening a new one', async () => {
|
|
await model.upsertGovernor({ city: 'Britain', governor: { serial: '0x1', name: 'Darrow' }, t: 100 })
|
|
await model.upsertGovernor({ city: 'Britain', governor: null, t: 400 })
|
|
assert.equal(terms.length, 1)
|
|
assert.equal(terms[0].ended_at, 400)
|
|
restore()
|
|
})
|
|
|
|
test('terms are tracked independently per city', async () => {
|
|
await model.upsertGovernor({ city: 'Britain', governor: { serial: '0x1' }, t: 100 })
|
|
await model.upsertGovernor({ city: 'Minoc', governor: { serial: '0x9' }, t: 120 })
|
|
await model.upsertGovernor({ city: 'Britain', governor: { serial: '0x1' }, t: 200 }) // dup, no-op
|
|
assert.equal(terms.length, 2)
|
|
assert.equal(terms.filter((t) => t.ended_at === null).length, 2)
|
|
restore()
|
|
})
|