1
0
This repository has been archived on 2026-08-25. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
tera/src/test/journey.test.ts
T

298 lines
11 KiB
TypeScript

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<string, string>();
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");
});
});