feat(api): world/system/crew snapshots expose the active + pending task queue (#44)

This commit is contained in:
2026-10-02 19:42:19 +00:00
parent 178de6e4fb
commit 84d1aa8122
7 changed files with 212 additions and 24 deletions
+51 -4
View File
@@ -239,7 +239,7 @@ describe('explorePlanet', () => {
describe('getCrewState', () => {
it('seeds a starting crew of 2–3 members derived from the save seed', async () => {
const { db } = await freshFile()
const crew = await getCrewState(db) // ensureSave runs inside, so this also seeds the crew
const crew = (await getCrewState(db)).crew // ensureSave runs inside, so this also seeds the crew (the roster moved under .crew — JC-7)
expect(crew.length).toBeGreaterThanOrEqual(2)
expect(crew.length).toBeLessThanOrEqual(3)
@@ -285,7 +285,7 @@ describe('explorePlanet crew finds', () => {
await db.db.insert(schema.planets).values({ systemId: sys.id, parentId: null, name: `P${i}`, type: 'ROCKY', hazard: false })
}
const before = await getCrewState(db) // empty — this save was hand-built, no starting crew seeded
const before = (await getCrewState(db)).crew // empty — this save was hand-built, no starting crew seeded
const result = await explorePlanet(db, crewPlanetId)
expect(result.crewFound).not.toBeNull()
@@ -297,13 +297,13 @@ describe('explorePlanet crew finds', () => {
expect([result.crewFound!.traitGood, result.crewFound!.traitBad]).toEqual([expectedMember.traitGood, expectedMember.traitBad])
expect(result.crewFound!.origin).toBe(`${planetName}, Test System`)
const after = await getCrewState(db)
const after = (await getCrewState(db)).crew
expect(after.length).toBe(before.length + 1) // exactly one join
expect(after.find((c) => c.id === result.crewFound!.id)).toMatchObject({ name: expectedMember.name, origin: `${planetName}, Test System` })
// The body is already explored → a second scan is rejected and no duplicate joins.
await expect(explorePlanet(db, crewPlanetId)).rejects.toMatchObject({ status: 409 })
expect((await getCrewState(db)).length).toBe(before.length + 1)
expect((await getCrewState(db)).crew.length).toBe(before.length + 1)
})
})
@@ -540,6 +540,53 @@ describe('runTurn (tick on the DB)', () => {
})
})
// --- The GET snapshots' task queue (Task 6) ---------------------------------------------
//
// Every GET snapshot (world / system / crew) carries the active + pending queue: ACTIVE rows
// first, then PENDING, each block ordered by (queuedAtTurn, id); DONE rows never appear. A
// pure read — `remaining` is the stored value, never recomputed; crew names resolve from the
// roster by id.
describe('snapshot task queue (world / system / crew)', () => {
// buildWorldFixture (4a): a save (seed '42') + Origin (s0, current) / Outpost (s1) +
// Alder (p0) / Basalt (p1) + the route pair — no crew, no tasks; this block adds its own.
it('empty queue → every GET snapshot carries tasks: []', async () => {
const { db } = await freshFile()
await buildWorldFixture(db)
expect((await getWorldState(db)).tasks).toEqual([])
expect((await getSystemState(db)).tasks).toEqual([])
const crew = await getCrewState(db)
expect(crew.tasks).toEqual([])
expect(crew.crew).toEqual([]) // the new wrapper: the roster moves under .crew (JC-7)
})
it('one ACTIVE + one PENDING row → exactly those two, ACTIVE first, in all three snapshots', async () => {
const { db } = await freshFile()
const f = await buildWorldFixture(db)
const [c1, c2, c3] = await addCrew(db, 3)
const jumpId = await insertTaskRow(db, { type: 'JUMP', payload: { systemId: f.sys1, hops: 1 }, leadCrewId: c1, helpers: [], duration: 2, remaining: 1, status: 'ACTIVE', queuedAtTurn: 3 })
const exploreId = await insertTaskRow(db, { type: 'EXPLORE', payload: { planetId: f.p1 }, leadCrewId: c2, helpers: [c3], duration: 4, remaining: 4, status: 'PENDING', queuedAtTurn: 3 })
const expected = [
{ id: jumpId, type: 'JUMP', targetLabel: 'Outpost', leadName: 'CREW 1', helperNames: [], remaining: 1 },
{ id: exploreId, type: 'EXPLORE', targetLabel: 'Basalt', leadName: 'CREW 2', helperNames: ['CREW 3'], remaining: 4 },
]
expect((await getWorldState(db)).tasks).toEqual(expected)
expect((await getSystemState(db)).tasks).toEqual(expected)
expect((await getCrewState(db)).tasks).toEqual(expected)
})
it('DONE rows never appear — even when they sort first by (queuedAtTurn, id)', async () => {
const { db } = await freshFile()
const f = await buildWorldFixture(db)
const [c1] = await addCrew(db, 1)
await insertTaskRow(db, { type: 'JUMP', payload: { systemId: f.sys1, hops: 1 }, leadCrewId: c1, helpers: [], duration: 1, remaining: 0, status: 'DONE', queuedAtTurn: 1 }) // oldest, lowest id in the queue
await insertTaskRow(db, { type: 'EXPLORE', payload: { planetId: f.p0 }, leadCrewId: c1, helpers: [], duration: 4, remaining: 4, status: 'PENDING', queuedAtTurn: 2 })
const snap = await getWorldState(db)
expect(snap.tasks).toHaveLength(1)
expect(snap.tasks[0].type).toBe('EXPLORE') // the DONE row is filtered out, not reordered to the back
})
})
describe('backfill (old saves)', () => {
// Planets/moons of one system in insertion order, nested by parent — ids are excluded so a
// regenerated set compares equal to the original even though autoincrement moved on.