/* Migration 8 — the roadmap loses its 'now' rows; chat turns get ids that two devices can share. A database as the previous build left it, including the state an earlier sync could produce: two units both marked 'now'. */ import { describe, it, expect, beforeEach, afterEach } from "vitest"; import { SqliteWasmDb } from "@app/db/sqlite-wasm-core.js"; import { migrate } from "@app/db/migrate.js"; import { readProgress } from "@app/domain/progress.js"; import type { Db } from "@app/db/types.js"; let db: Db; beforeEach(async () => { db = await SqliteWasmDb.open({ memory: true }); await migrate(db, 7); await db.exec(` INSERT INTO progress (unit_id, state, confidence, updated_at) VALUES ('1.1', 'done', 87, 100), ('1.2', 'now', 40, 300), ('1.3', 'now', 10, 200), ('1.4', 'todo', 0, 0); INSERT INTO chat (id, role, body, created_at, updated_at) VALUES (1, 'user', 'Start unit 1.1.', 0, 0), (2, 'assistant', 'first', 0, 0), (10, 'assistant', 'tenth', 0, 0), (11, 'user', 'later', 500, 500); INSERT INTO tombstone (tbl, pk, updated_at) VALUES ('chat', '9', 400); `); await migrate(db); }); afterEach(async () => { await db.close(); }); describe("migration 8", () => { it("keeps what is true of each unit, and nothing about being current", async () => { expect(await db.all("SELECT unit_id, done, confidence, updated_at FROM progress ORDER BY unit_id")).toEqual([ { unit_id: "1.1", done: 1, confidence: 87, updated_at: 100 }, { unit_id: "1.2", done: 0, confidence: 40, updated_at: 300 }, { unit_id: "1.3", done: 0, confidence: 10, updated_at: 200 }, { unit_id: "1.4", done: 0, confidence: 0, updated_at: 0 }, ]); }); it("puts him where he most recently was, as exactly as new as that was", async () => { expect(await db.get("SELECT v, updated_at FROM meta WHERE k = 'road.unit'")).toEqual({ v: "1.2", updated_at: 300, }); expect((await readProgress(db)).current).toBe("1.2"); }); it("gives every turn a text id and keeps the transcript in the order it was written", async () => { const turns = await db.all<{ id: string; body: string }>("SELECT id, body FROM chat ORDER BY created_at, id"); expect(turns.map((t) => t.body)).toEqual(["Start unit 1.1.", "first", "tenth", "later"]); for (const t of turns) expect(t.id).toMatch(/^legacy:[0-9a-f-]{36}:\d{10}$/); }); it("renames a chat tombstone the same way", async () => { const device = (await db.get<{ v: string }>("SELECT v FROM meta WHERE k = 'sync.device'"))!.v; expect(await db.all("SELECT pk, updated_at FROM tombstone WHERE tbl = 'chat'")).toEqual([ // As JSON, once migration 9 has written keys in protocol 2's form. { pk: JSON.stringify([`legacy:${device}:0000000009`]), updated_at: 400 }, ]); }); it("names this install without stamping it", async () => { expect(await db.get("SELECT updated_at FROM meta WHERE k = 'sync.device'")).toEqual({ updated_at: 0 }); }); it("creates the learner-model tables empty", async () => { for (const tbl of ["evidence", "confusion", "phase_ledger"]) { expect(await db.get(`SELECT count(*) AS n FROM ${tbl}`), tbl).toEqual({ n: 0 }); } }); }); describe("the new roadmap model", () => { it("does not un-finish a unit that is revisited", async () => { const { goToUnit } = await import("@app/domain/progress.js"); await goToUnit(db, await readProgress(db), "1.1"); const p = await readProgress(db); expect(p.current).toBe("1.1"); expect(p.done["1.1"]).toBe(true); }); it("finishes the current unit and moves on in one step", async () => { const { advanceUnit } = await import("@app/domain/progress.js"); const next = await advanceUnit(db, await readProgress(db)); const p = await readProgress(db); expect(next).toBe("1.3"); expect(p.current).toBe("1.3"); expect(p.done["1.2"]).toBe(true); }); });