import { beforeEach, describe, expect, it, vi } from "vitest"; type StorageValue = string | number | boolean | null | undefined | Record | unknown[]; function createWxStorage(initial: Record) { const store = new Map(Object.entries(initial)); return { store, getStorageSync(key: string) { return store.get(key); }, setStorageSync(key: string, value: StorageValue) { store.set(key, value); }, getStorageInfoSync() { return { keys: Array.from(store.keys()) }; } }; } function loadStorageModule() { const modulePath = require.resolve("./storage.js"); delete require.cache[modulePath]; return require("./storage.js"); } describe("miniprogram storage bootstrap", () => { beforeEach(() => { vi.restoreAllMocks(); }); it("当前正式 key 缺失时可从稳定备份恢复服务器列表", () => { const backupServers = [ { id: "srv-backup-1", name: "backup", host: "127.0.0.1", port: 22, username: "root", authType: "password", projectPath: "~/workspace", timeoutSeconds: 20, heartbeatSeconds: 15, transportMode: "gateway", tags: ["prod"], jumpHost: {}, sortOrder: 1, lastConnectedAt: "" } ]; const wxStorage = createWxStorage({ "remoteconn.backup.servers": backupServers }); (global as typeof globalThis & { wx: typeof wxStorage }).wx = wxStorage; const storage = loadStorageModule(); const rows = storage.listServers(); expect(rows).toHaveLength(1); expect(rows[0].id).toBe("srv-backup-1"); expect(wxStorage.store.get("remoteconn.servers.v2")).toEqual(backupServers); }); it("当前正式 key 与备份都缺失时可从历史 key 迁移记录数据", () => { const legacyRecords = [ { id: "rec-legacy-1", content: "legacy note", serverId: "srv-legacy-1", createdAt: "2026-03-09T10:00:00.000Z" } ]; const wxStorage = createWxStorage({ "remoteconn.records.v1": legacyRecords }); (global as typeof globalThis & { wx: typeof wxStorage }).wx = wxStorage; const storage = loadStorageModule(); const rows = storage.listRecords(); expect(rows).toHaveLength(1); expect(rows[0]).toMatchObject({ id: "rec-legacy-1", content: "legacy note", serverId: "srv-legacy-1" }); expect(wxStorage.store.get("remoteconn.records.v2")).toEqual(rows); expect(wxStorage.store.get("remoteconn.backup.records")).toEqual(rows); }); it("闪念处理状态会归一化并按互斥规则稳定写回", () => { const wxStorage = createWxStorage({ "remoteconn.records.v2": [ { id: "rec-1", content: "待处理事项", category: "问题", createdAt: "2026-03-10T10:00:00.000Z", updatedAt: "2026-03-10T10:00:00.000Z" } ] }); (global as typeof globalThis & { wx: typeof wxStorage }).wx = wxStorage; const storage = loadStorageModule(); const normalized = storage.listRecords(); expect(normalized[0]?.processed).toBe(false); expect(normalized[0]?.discarded).toBe(false); const processed = storage.updateRecord({ id: "rec-1", content: "待处理事项", category: "问题", processed: true }); expect(processed?.processed).toBe(true); expect(processed?.discarded).toBe(false); const discarded = storage.updateRecord({ id: "rec-1", content: "待处理事项", category: "问题", discarded: true }); expect(discarded?.processed).toBe(false); expect(discarded?.discarded).toBe(true); expect(storage.listRecords()[0]?.processed).toBe(false); expect(storage.listRecords()[0]?.discarded).toBe(true); expect(wxStorage.store.get("remoteconn.records.v2")).toEqual(storage.listRecords()); }); it("写入正式 key 时会同步刷新稳定备份", () => { const wxStorage = createWxStorage({}); (global as typeof globalThis & { wx: typeof wxStorage }).wx = wxStorage; const storage = loadStorageModule(); storage.saveSettings({ uiThemeMode: "light", uiBgColor: "#f4f1de", uiTextColor: "#222222", uiBtnColor: "#111111", uiAccentColor: "#123456" }); expect(wxStorage.store.get("remoteconn.settings.v2")).toEqual( wxStorage.store.get("remoteconn.backup.settings") ); }); it("syncConfigEnabled 关闭后可稳定读回", () => { const wxStorage = createWxStorage({}); (global as typeof globalThis & { wx: typeof wxStorage }).wx = wxStorage; const storage = loadStorageModule(); storage.saveSettings({ syncConfigEnabled: false }); expect(storage.getSettings().syncConfigEnabled).toBe(false); }); it("showVoiceInputButton 关闭后可稳定读回", () => { const wxStorage = createWxStorage({}); (global as typeof globalThis & { wx: typeof wxStorage }).wx = wxStorage; const storage = loadStorageModule(); expect(storage.getSettings().showVoiceInputButton).toBe(true); storage.saveSettings({ showVoiceInputButton: false }); expect(storage.getSettings().showVoiceInputButton).toBe(false); }); it("ttsSpeakableMaxChars 会按边界归一化并稳定读回", () => { const wxStorage = createWxStorage({}); (global as typeof globalThis & { wx: typeof wxStorage }).wx = wxStorage; const storage = loadStorageModule(); expect(storage.getSettings().ttsSpeakableMaxChars).toBe(500); expect(storage.getSettings().ttsSegmentMaxChars).toBe(80); storage.saveSettings({ ttsSpeakableMaxChars: 50 }); expect(storage.getSettings().ttsSpeakableMaxChars).toBe(120); storage.saveSettings({ ttsSpeakableMaxChars: 500 }); expect(storage.getSettings().ttsSpeakableMaxChars).toBe(500); storage.saveSettings({ ttsSpeakableMaxChars: 5000 }); expect(storage.getSettings().ttsSpeakableMaxChars).toBe(1200); storage.saveSettings({ ttsSegmentMaxChars: 10 }); expect(storage.getSettings().ttsSegmentMaxChars).toBe(40); storage.saveSettings({ ttsSegmentMaxChars: 80 }); expect(storage.getSettings().ttsSegmentMaxChars).toBe(80); storage.saveSettings({ ttsSegmentMaxChars: 1000 }); expect(storage.getSettings().ttsSegmentMaxChars).toBe(200); }); it("AI 连接默认模式会稳定读回,非法值会回退到默认档位", () => { const wxStorage = createWxStorage({}); (global as typeof globalThis & { wx: typeof wxStorage }).wx = wxStorage; const storage = loadStorageModule(); expect(storage.getSettings().aiDefaultProvider).toBe("codex"); expect(storage.getSettings().aiCodexSandboxMode).toBe("workspace-write"); expect(storage.getSettings().aiCopilotPermissionMode).toBe("default"); storage.saveSettings({ aiDefaultProvider: "copilot", aiCodexSandboxMode: "read-only", aiCopilotPermissionMode: "experimental" }); expect(storage.getSettings().aiDefaultProvider).toBe("copilot"); expect(storage.getSettings().aiCodexSandboxMode).toBe("read-only"); expect(storage.getSettings().aiCopilotPermissionMode).toBe("experimental"); storage.saveSettings({ aiDefaultProvider: "invalid", aiCodexSandboxMode: "invalid", aiCopilotPermissionMode: "invalid" }); expect(storage.getSettings().aiDefaultProvider).toBe("codex"); expect(storage.getSettings().aiCodexSandboxMode).toBe("workspace-write"); expect(storage.getSettings().aiCopilotPermissionMode).toBe("default"); }); it("续接快照行数会按边界归一化后稳定读回", () => { const wxStorage = createWxStorage({}); (global as typeof globalThis & { wx: typeof wxStorage }).wx = wxStorage; const storage = loadStorageModule(); expect(storage.getSettings().shellBufferSnapshotMaxLines).toBe( storage.DEFAULT_TERMINAL_BUFFER_SNAPSHOT_MAX_LINES ); storage.saveSettings({ shellBufferSnapshotMaxLines: storage.MIN_TERMINAL_BUFFER_SNAPSHOT_MAX_LINES - 1 }); expect(storage.getSettings().shellBufferSnapshotMaxLines).toBe( storage.MIN_TERMINAL_BUFFER_SNAPSHOT_MAX_LINES ); storage.saveSettings({ shellBufferSnapshotMaxLines: storage.MAX_TERMINAL_BUFFER_SNAPSHOT_MAX_LINES + 1 }); expect(storage.getSettings().shellBufferSnapshotMaxLines).toBe( storage.MAX_TERMINAL_BUFFER_SNAPSHOT_MAX_LINES ); }); it("uiLanguage 会回退到合法值并稳定读回", () => { const wxStorage = createWxStorage({}); (global as typeof globalThis & { wx: typeof wxStorage }).wx = wxStorage; const storage = loadStorageModule(); expect(storage.getSettings().uiLanguage).toBe("zh-Hans"); storage.saveSettings({ uiLanguage: "ja" }); expect(storage.getSettings().uiLanguage).toBe("ja"); storage.saveSettings({ uiLanguage: "ko" }); expect(storage.getSettings().uiLanguage).toBe("ko"); storage.saveSettings({ uiLanguage: "invalid-language" }); expect(storage.getSettings().uiLanguage).toBe("zh-Hans"); }); it("历史服务器脏数据会先归一化,再参与同步上传", () => { const wxStorage = createWxStorage({ "remoteconn.servers.v2": [ { id: "srv-legacy-1", name: null, tags: ["prod", "", "prod"], host: " 10.0.0.8 ", port: "22", username: null, authType: "key", password: null, privateKey: null, passphrase: null, certificate: null, projectPath: null, timeoutSeconds: "30", heartbeatSeconds: "12", transportMode: null, jumpHost: { enabled: true, host: " jump.local ", port: "2222", username: null, authType: "key" }, jumpPassword: null, jumpPrivateKey: null, jumpPassphrase: null, jumpCertificate: null, sortOrder: "9", lastConnectedAt: null, updatedAt: "" } ] }); (global as typeof globalThis & { wx: typeof wxStorage }).wx = wxStorage; const storage = loadStorageModule(); const rows = storage.listServers(); expect(rows).toHaveLength(1); expect(rows[0]).toMatchObject({ id: "srv-legacy-1", name: "", tags: ["prod"], host: "10.0.0.8", port: 22, username: "", authType: "password", password: "", privateKey: "", passphrase: "", certificate: "", projectPath: "", timeoutSeconds: 30, heartbeatSeconds: 12, transportMode: "gateway", jumpHost: { enabled: true, host: "jump.local", port: 2222, username: "", authType: "password" }, jumpPassword: "", jumpPrivateKey: "", jumpPassphrase: "", jumpCertificate: "", sortOrder: 9, lastConnectedAt: "" }); expect(typeof rows[0].updatedAt).toBe("string"); expect(rows[0].updatedAt.length).toBeGreaterThan(0); }); });