lib/account-transfer.test.ts
Rama main · réplica de lectura del repositorio original.
import { describe, expect, it } from "vitest";
import {
ACCOUNT_TRANSFER_FORMAT,
ACCOUNT_TRANSFER_VERSION,
CURRENT_ACCOUNT_EXPORT_VERSION,
RETIRED_ACCOUNT_TRANSFER_SECTIONS,
validateAccountTransfer,
} from "@/lib/account-transfer";
const emptyDocument = {
format_version: 2,
exported_at: "2026-08-20T00:00:00.000Z",
account: { id: "source-user" },
preferences: null,
owned_projects: [],
memberships: [],
issues: [],
comments: [],
attachments: [],
page_files: [],
pages: [],
objectives: [],
views: [],
cycles: [],
scratchpad: null,
assistant_conversations: [],
code_agent_conversations: [],
notifications: [],
push_devices: [],
statistics: [],
billing: null,
ai_usage: [],
api_keys: [],
connected_apps: [],
git_connections: [],
git_user_identities: [],
model_keys: [],
};
describe("account transfer compatibility", () => {
it("retains personal Numo defaults and rejects malformed preference arrays", () => {
const rows = [{ provider: "openrouter", model: "test/model" }];
const result = validateAccountTransfer({ ...emptyDocument, numo_preferences: rows });
expect(result.ok && result.document.numo_preferences).toEqual(rows);
expect(validateAccountTransfer({ ...emptyDocument, numo_preferences: "bad" }))
.toMatchObject({ ok: false, field: "numo_preferences" });
});
it("accepts legacy v2 exports and normalizes the current envelope", () => {
const result = validateAccountTransfer(emptyDocument);
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.document.transfer_format).toBe(ACCOUNT_TRANSFER_FORMAT);
expect(result.document.transfer_version).toBe(ACCOUNT_TRANSFER_VERSION);
expect(result.document.categories).toEqual([]);
expect(result.document.issue_categories).toEqual([]);
});
it("normalizes the collections added after v2 for legacy exports", () => {
const result = validateAccountTransfer(emptyDocument);
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.document.agent_routines).toEqual([]);
expect(result.document.numo_preferences).toEqual([]);
});
it("accepts the explicit current transfer envelope without the retired sections", () => {
const { preferences: _preferences, code_agent_conversations: _code, ...current } =
emptyDocument;
const result = validateAccountTransfer({
...current,
format_version: CURRENT_ACCOUNT_EXPORT_VERSION,
transfer_format: ACCOUNT_TRANSFER_FORMAT,
transfer_version: ACCOUNT_TRANSFER_VERSION,
categories: [],
issue_categories: [],
});
expect(result.ok).toBe(true);
});
it("accepts a v3 file with code-agent sections and leaves them out of the document", () => {
const result = validateAccountTransfer({
...emptyDocument,
format_version: 3,
preferences: { default_model: "legacy/model", branch_prefix: "agent/" },
code_agent_conversations: [{ id: "conversation", turns: [], messages: [] }],
});
expect(result.ok).toBe(true);
if (!result.ok) return;
for (const section of RETIRED_ACCOUNT_TRANSFER_SECTIONS) {
expect(result.document).not.toHaveProperty(section);
}
});
it("accepts a v4 file with palette bookmarks and leaves them out of the document", () => {
const result = validateAccountTransfer({
...emptyDocument,
format_version: CURRENT_ACCOUNT_EXPORT_VERSION,
saved_views: [{ id: "bookmark", name: "Mine", href: "/all?assignee=me" }],
});
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.document).not.toHaveProperty("saved_views");
});
it("ignores malformed retired sections instead of rejecting the file", () => {
expect(validateAccountTransfer({
...emptyDocument,
format_version: 3,
preferences: "legacy",
code_agent_conversations: "legacy",
saved_views: "legacy",
})).toMatchObject({ ok: true });
});
it("rejects unknown versions and malformed collections", () => {
expect(validateAccountTransfer({ ...emptyDocument, format_version: 99 })).toMatchObject({
ok: false,
error: "unsupportedVersion",
});
expect(validateAccountTransfer({ ...emptyDocument, format_version: 1 })).toMatchObject({
ok: false,
error: "unsupportedVersion",
field: "format_version",
});
expect(validateAccountTransfer({ ...emptyDocument, issues: "not-an-array" })).toMatchObject({
ok: false,
error: "missingField",
field: "issues",
});
});
});