import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { createJourney, decodeJourneySnapshot, encodeJourneySnapshot, endpointCity, clearJourneySession, journeyReducer, loadJourneySession, officeCity, saveJourneySession, transitionJourney, type JourneyActor, type JourneyEvent, type JourneyState, type JourneyStorageAdapter, } from "../journey/index.ts"; const CROW: JourneyActor = { id: "anon-115", kind: "crow", signedIn: false, profile: { displayName: "Visitor", color: "midnight" }, }; const KARTI: JourneyActor = { id: "user-karti", kind: "humanoid", signedIn: true, profile: { displayName: "Karti", face: "profile:user-karti", color: "black" }, }; function playFromLosAngeles(): JourneyState { let state = createJourney({ actor: CROW, location: { scale: "socal" }, mode: "play" }); state = journeyReducer(state, { type: "select-route", routeId: "la-sf-i-5", direction: 1, }); state = journeyReducer(state, { type: "enter-vehicle", vehicleId: "model-x-hero" }); return state; } describe("journey state machine", () => { it("navigates explicitly between California and either detailed city board", () => { const california = createJourney({ actor: CROW, mode: "play" }); const actor = california.actor; const bayArea = journeyReducer(california, { type: "navigate-to-city", city: "bay-area", }); assert.deepEqual(bayArea.location, { scale: "bay-area" }); assert.equal(bayArea.actor, actor); const returned = journeyReducer(bayArea, { type: "return-to-california" }); assert.deepEqual(returned.location, { scale: "california" }); assert.equal(returned.actor, actor); const southern = journeyReducer(returned, { type: "navigate-to-city", city: "socal" }); assert.deepEqual(southern.location, { scale: "socal" }); }); it("requires leaving an office or vehicle before board navigation", () => { let driving = playFromLosAngeles(); assert.deepEqual( transitionJourney(driving, { type: "navigate-to-city", city: "bay-area" }), { accepted: false, state: driving, reason: "exit-vehicle-first" }, ); driving = journeyReducer(driving, { type: "exit-vehicle" }); const bayArea = journeyReducer(driving, { type: "navigate-to-city", city: "bay-area" }); let office = journeyReducer(bayArea, { type: "enter-office", officeId: "lumbridge-hq", }); assert.deepEqual( transitionJourney(office, { type: "return-to-california" }), { accepted: false, state: office, reason: "leave-office-first" }, ); office = journeyReducer(office, { type: "leave-office" }); assert.equal(journeyReducer(office, { type: "return-to-california" }).location.scale, "california"); }); it("maps route endpoints honestly onto the detailed city boards", () => { assert.equal(endpointCity("los-angeles"), "socal"); assert.equal(endpointCity("san-francisco"), "bay-area"); let northbound = playFromLosAngeles(); northbound = journeyReducer(northbound, { type: "update-route-progress", progress: 0.74 }); northbound = journeyReducer(northbound, { type: "reach-route-endpoint", endpoint: "san-francisco", }); assert.equal(northbound.location.scale, "bay-area"); assert.equal(northbound.route?.progress, 1); assert.equal(northbound.vehicle?.vehicleId, "model-x-hero"); let southbound = journeyReducer(northbound, { type: "exit-vehicle" }); southbound = journeyReducer(southbound, { type: "select-route", routeId: "la-sf-us-101", direction: -1, }); southbound = journeyReducer(southbound, { type: "enter-vehicle", vehicleId: "model-x-hero" }); southbound = journeyReducer(southbound, { type: "reach-route-endpoint", endpoint: "los-angeles", }); assert.equal(southbound.location.scale, "socal"); assert.equal(southbound.route?.progress, 0); }); it("preserves identity and prior city across office doors", () => { let state = createJourney({ actor: KARTI, location: { scale: "bay-area" }, mode: "play" }); const actor = state.actor; state = journeyReducer(state, { type: "enter-office", officeId: "frontier-valley" }); assert.deepEqual(state.location, { scale: "office", officeId: "frontier-valley", priorCity: "bay-area", }); assert.equal(state.actor, actor); state = journeyReducer(state, { type: "leave-office" }); assert.deepEqual(state.location, { scale: "bay-area" }); assert.equal(state.actor, actor); assert.equal(officeCity("mateo-court"), "socal"); }); it("swaps a signed-in actor without disturbing their journey", () => { const before = playFromLosAngeles(); const after = journeyReducer(before, { type: "sign-in-actor-swap", actor: KARTI }); assert.deepEqual(after.actor, KARTI); assert.notEqual(after.actor, KARTI, "the reducer owns a defensive actor copy"); assert.deepEqual(after.location, before.location); assert.deepEqual(after.vehicle, before.vehicle); assert.deepEqual(after.route, before.route); }); it("rejects invalid and out-of-order transitions as exact no-ops", () => { const state = createJourney({ actor: CROW }); const badEvents: JourneyEvent[] = [ { type: "enter-vehicle", vehicleId: "model-x-hero" }, { type: "enter-office", officeId: "lumbridge-hq" }, { type: "leave-office" }, { type: "update-route-progress", progress: 0.5 }, { type: "reach-route-endpoint", endpoint: "san-francisco" }, { type: "sign-in-actor-swap", actor: CROW }, ]; for (const event of badEvents) { const result = transitionJourney(state, event); assert.equal(result.accepted, false); assert.equal(result.state, state); assert.equal(journeyReducer(state, event), state); } }); it("requires the endpoint that agrees with the selected direction", () => { const state = playFromLosAngeles(); const rejected = transitionJourney(state, { type: "reach-route-endpoint", endpoint: "los-angeles", }); assert.deepEqual(rejected, { accepted: false, state, reason: "wrong-endpoint" }); }); it("requires play mode, a route, and exiting the car before an office", () => { const observer = createJourney({ actor: CROW, location: { scale: "socal" } }); assert.equal( transitionJourney(observer, { type: "enter-vehicle", vehicleId: "x" }).accepted, false, ); let driving = playFromLosAngeles(); driving = journeyReducer(driving, { type: "reach-route-endpoint", endpoint: "san-francisco", }); const result = transitionJourney(driving, { type: "enter-office", officeId: "lumbridge-hq", }); assert.deepEqual(result, { accepted: false, state: driving, reason: "exit-vehicle-first" }); }); it("encodes and decodes an isolated versioned reconnect snapshot", () => { const state = playFromLosAngeles(); const encoded = encodeJourneySnapshot(state); const decoded = decodeJourneySnapshot(encoded); assert.equal(decoded.ok, true); if (!decoded.ok) return; assert.equal(decoded.snapshot.version, 1); assert.deepEqual(decoded.state, state); assert.notEqual(decoded.state, state); decoded.state.actor.profile.displayName = "Changed offline"; assert.equal(state.actor.profile.displayName, "Visitor"); }); it("fails closed on corrupt, inconsistent, and future snapshots", () => { assert.deepEqual(decodeJourneySnapshot("{"), { ok: false, error: "journey: snapshot is not valid JSON", }); assert.equal(decodeJourneySnapshot({ version: 2, state: {} }).ok, false); assert.equal( decodeJourneySnapshot({ version: 1, state: { ...createJourney({ actor: CROW }), location: { scale: "office", officeId: "mateo-court", priorCity: "bay-area", }, }, }).ok, false, ); }); it("is deterministic for the same initial state and event log", () => { const initial = createJourney({ actor: CROW, location: { scale: "socal" }, mode: "play" }); const events: JourneyEvent[] = [ { type: "select-route", routeId: "la-sf-us-101", direction: 1 }, { type: "enter-vehicle", vehicleId: "model-x-hero" }, { type: "update-route-progress", progress: 0.25 }, { type: "update-route-progress", progress: 0.75 }, { type: "reach-route-endpoint", endpoint: "san-francisco" }, { type: "exit-vehicle" }, { type: "enter-office", officeId: "lumbridge-hq" }, { type: "sign-in-actor-swap", actor: KARTI }, ]; const run = (): JourneyState => events.reduce(journeyReducer, initial); assert.deepEqual(run(), run()); }); }); class MemoryStorage implements JourneyStorageAdapter { readonly values = new Map(); getItem(key: string): string | null { return this.values.get(key) ?? null; } setItem(key: string, value: string): void { this.values.set(key, value); } removeItem(key: string): void { this.values.delete(key); } } describe("journey session persistence", () => { it("round-trips through a caller-owned storage adapter and clears cleanly", () => { const storage = new MemoryStorage(); const state = playFromLosAngeles(); assert.deepEqual(saveJourneySession(storage, "tera.journey", state), { ok: true }); const loaded = loadJourneySession(storage, "tera.journey"); assert.equal(loaded.status, "loaded"); if (loaded.status !== "loaded") return; assert.deepEqual(loaded.state, state); assert.notEqual(loaded.state, state); assert.deepEqual(clearJourneySession(storage, "tera.journey"), { ok: true }); assert.deepEqual(loadJourneySession(storage, "tera.journey"), { status: "missing" }); }); it("rejects corrupt and future sessions without throwing or mutating storage", () => { const storage = new MemoryStorage(); storage.values.set("corrupt", "{"); storage.values.set("future", JSON.stringify({ version: 2, state: {} })); assert.equal(loadJourneySession(storage, "corrupt").status, "invalid"); assert.deepEqual(loadJourneySession(storage, "future"), { status: "invalid", error: "journey: unsupported snapshot version", }); assert.equal(storage.values.has("future"), true); }); it("contains adapter exceptions and invalid keys", () => { const unavailable: JourneyStorageAdapter = { getItem: () => { throw new Error("blocked"); }, setItem: () => { throw new Error("full"); }, removeItem: () => { throw new Error("blocked"); }, }; const state = playFromLosAngeles(); assert.equal(loadJourneySession(unavailable, "tera.journey").status, "unavailable"); assert.equal(saveJourneySession(unavailable, "tera.journey", state).ok, false); assert.equal(clearJourneySession(unavailable, "tera.journey").ok, false); assert.equal(loadJourneySession(unavailable, " ").status, "invalid"); }); });