test: add tests for path-derived IDs in json migration
Tests verify that file paths are used for IDs even when JSON contains different values - ensuring robustness against stale JSON content.
This commit is contained in:
@@ -128,6 +128,28 @@ describe("JSON to SQLite migration", () => {
|
||||
expect(projects[0].sandboxes).toEqual(["/test/sandbox"])
|
||||
})
|
||||
|
||||
test("uses filename for project id when JSON has different value", async () => {
|
||||
await Bun.write(
|
||||
path.join(storageDir, "project", "proj_filename.json"),
|
||||
JSON.stringify({
|
||||
id: "proj_different_in_json", // Stale! Should be ignored
|
||||
worktree: "/test/path",
|
||||
vcs: "git",
|
||||
name: "Test Project",
|
||||
sandboxes: [],
|
||||
}),
|
||||
)
|
||||
|
||||
const stats = await JsonMigration.run(sqlite)
|
||||
|
||||
expect(stats?.projects).toBe(1)
|
||||
|
||||
const db = drizzle({ client: sqlite })
|
||||
const projects = db.select().from(ProjectTable).all()
|
||||
expect(projects.length).toBe(1)
|
||||
expect(projects[0].id).toBe("proj_filename") // Uses filename, not JSON id
|
||||
})
|
||||
|
||||
test("migrates project with commands", async () => {
|
||||
await writeProject(storageDir, {
|
||||
id: "proj_with_commands",
|
||||
@@ -285,6 +307,74 @@ describe("JSON to SQLite migration", () => {
|
||||
expect(parts[0].data).not.toHaveProperty("sessionID")
|
||||
})
|
||||
|
||||
test("uses filename for message id when JSON has different value", async () => {
|
||||
await writeProject(storageDir, {
|
||||
id: "proj_test123abc",
|
||||
worktree: "/",
|
||||
time: { created: Date.now(), updated: Date.now() },
|
||||
sandboxes: [],
|
||||
})
|
||||
await writeSession(storageDir, "proj_test123abc", { ...fixtures.session })
|
||||
await Bun.write(
|
||||
path.join(storageDir, "message", "ses_test456def", "msg_from_filename.json"),
|
||||
JSON.stringify({
|
||||
id: "msg_different_in_json", // Stale! Should be ignored
|
||||
sessionID: "ses_test456def",
|
||||
role: "user",
|
||||
agent: "default",
|
||||
time: { created: 1700000000000 },
|
||||
}),
|
||||
)
|
||||
|
||||
const stats = await JsonMigration.run(sqlite)
|
||||
|
||||
expect(stats?.messages).toBe(1)
|
||||
|
||||
const db = drizzle({ client: sqlite })
|
||||
const messages = db.select().from(MessageTable).all()
|
||||
expect(messages.length).toBe(1)
|
||||
expect(messages[0].id).toBe("msg_from_filename") // Uses filename, not JSON id
|
||||
expect(messages[0].session_id).toBe("ses_test456def")
|
||||
})
|
||||
|
||||
test("uses paths for part id and messageID when JSON has different values", async () => {
|
||||
await writeProject(storageDir, {
|
||||
id: "proj_test123abc",
|
||||
worktree: "/",
|
||||
time: { created: Date.now(), updated: Date.now() },
|
||||
sandboxes: [],
|
||||
})
|
||||
await writeSession(storageDir, "proj_test123abc", { ...fixtures.session })
|
||||
await Bun.write(
|
||||
path.join(storageDir, "message", "ses_test456def", "msg_realmsgid.json"),
|
||||
JSON.stringify({
|
||||
role: "user",
|
||||
agent: "default",
|
||||
time: { created: 1700000000000 },
|
||||
}),
|
||||
)
|
||||
await Bun.write(
|
||||
path.join(storageDir, "part", "msg_realmsgid", "prt_from_filename.json"),
|
||||
JSON.stringify({
|
||||
id: "prt_different_in_json", // Stale! Should be ignored
|
||||
messageID: "msg_different_in_json", // Stale! Should be ignored
|
||||
sessionID: "ses_test456def",
|
||||
type: "text",
|
||||
text: "Hello",
|
||||
}),
|
||||
)
|
||||
|
||||
const stats = await JsonMigration.run(sqlite)
|
||||
|
||||
expect(stats?.parts).toBe(1)
|
||||
|
||||
const db = drizzle({ client: sqlite })
|
||||
const parts = db.select().from(PartTable).all()
|
||||
expect(parts.length).toBe(1)
|
||||
expect(parts[0].id).toBe("prt_from_filename") // Uses filename, not JSON id
|
||||
expect(parts[0].message_id).toBe("msg_realmsgid") // Uses parent dir, not JSON messageID
|
||||
})
|
||||
|
||||
test("skips orphaned sessions (no parent project)", async () => {
|
||||
await Bun.write(
|
||||
path.join(storageDir, "session", "proj_test123abc", "ses_orphan.json"),
|
||||
@@ -304,6 +394,72 @@ describe("JSON to SQLite migration", () => {
|
||||
expect(stats?.sessions).toBe(0)
|
||||
})
|
||||
|
||||
test("uses directory path for projectID when JSON has stale value", async () => {
|
||||
// Simulates the scenario where earlier migration moved sessions to new
|
||||
// git-based project directories but didn't update the projectID field
|
||||
const gitBasedProjectID = "abc123gitcommit"
|
||||
await writeProject(storageDir, {
|
||||
id: gitBasedProjectID,
|
||||
worktree: "/test/path",
|
||||
vcs: "git",
|
||||
time: { created: Date.now(), updated: Date.now() },
|
||||
sandboxes: [],
|
||||
})
|
||||
|
||||
// Session is in the git-based directory but JSON still has old projectID
|
||||
await writeSession(storageDir, gitBasedProjectID, {
|
||||
id: "ses_migrated",
|
||||
projectID: "old-project-name", // Stale! Should be ignored
|
||||
slug: "migrated-session",
|
||||
directory: "/test/path",
|
||||
title: "Migrated Session",
|
||||
version: "1.0.0",
|
||||
time: { created: 1700000000000, updated: 1700000001000 },
|
||||
})
|
||||
|
||||
const stats = await JsonMigration.run(sqlite)
|
||||
|
||||
expect(stats?.sessions).toBe(1)
|
||||
|
||||
const db = drizzle({ client: sqlite })
|
||||
const sessions = db.select().from(SessionTable).all()
|
||||
expect(sessions.length).toBe(1)
|
||||
expect(sessions[0].id).toBe("ses_migrated")
|
||||
expect(sessions[0].project_id).toBe(gitBasedProjectID) // Uses directory, not stale JSON
|
||||
})
|
||||
|
||||
test("uses filename for session id when JSON has different value", async () => {
|
||||
await writeProject(storageDir, {
|
||||
id: "proj_test123abc",
|
||||
worktree: "/test/path",
|
||||
time: { created: Date.now(), updated: Date.now() },
|
||||
sandboxes: [],
|
||||
})
|
||||
|
||||
await Bun.write(
|
||||
path.join(storageDir, "session", "proj_test123abc", "ses_from_filename.json"),
|
||||
JSON.stringify({
|
||||
id: "ses_different_in_json", // Stale! Should be ignored
|
||||
projectID: "proj_test123abc",
|
||||
slug: "test-session",
|
||||
directory: "/test/path",
|
||||
title: "Test Session",
|
||||
version: "1.0.0",
|
||||
time: { created: 1700000000000, updated: 1700000001000 },
|
||||
}),
|
||||
)
|
||||
|
||||
const stats = await JsonMigration.run(sqlite)
|
||||
|
||||
expect(stats?.sessions).toBe(1)
|
||||
|
||||
const db = drizzle({ client: sqlite })
|
||||
const sessions = db.select().from(SessionTable).all()
|
||||
expect(sessions.length).toBe(1)
|
||||
expect(sessions[0].id).toBe("ses_from_filename") // Uses filename, not JSON id
|
||||
expect(sessions[0].project_id).toBe("proj_test123abc")
|
||||
})
|
||||
|
||||
test("is idempotent (running twice doesn't duplicate)", async () => {
|
||||
await writeProject(storageDir, {
|
||||
id: "proj_test123abc",
|
||||
@@ -666,8 +822,11 @@ describe("JSON to SQLite migration", () => {
|
||||
|
||||
const stats = await JsonMigration.run(sqlite)
|
||||
|
||||
expect(stats.projects).toBe(1)
|
||||
expect(stats.sessions).toBe(1)
|
||||
// Projects: proj_test123abc (valid), proj_missing_id (now derives id from filename)
|
||||
// Sessions: ses_test456def (valid), ses_missing_project (now uses dir path),
|
||||
// ses_orphan (now uses dir path, ignores stale projectID)
|
||||
expect(stats.projects).toBe(2)
|
||||
expect(stats.sessions).toBe(3)
|
||||
expect(stats.messages).toBe(1)
|
||||
expect(stats.parts).toBe(1)
|
||||
expect(stats.todos).toBe(1)
|
||||
@@ -676,8 +835,8 @@ describe("JSON to SQLite migration", () => {
|
||||
expect(stats.errors.length).toBeGreaterThanOrEqual(6)
|
||||
|
||||
const db = drizzle({ client: sqlite })
|
||||
expect(db.select().from(ProjectTable).all().length).toBe(1)
|
||||
expect(db.select().from(SessionTable).all().length).toBe(1)
|
||||
expect(db.select().from(ProjectTable).all().length).toBe(2)
|
||||
expect(db.select().from(SessionTable).all().length).toBe(3)
|
||||
expect(db.select().from(MessageTable).all().length).toBe(1)
|
||||
expect(db.select().from(PartTable).all().length).toBe(1)
|
||||
expect(db.select().from(TodoTable).all().length).toBe(1)
|
||||
|
||||
Reference in New Issue
Block a user