import { test } from 'node:test' import assert from 'node:assert/strict' import { http } from '../src/shared/api/http.ts' import { clearApiSecretCache, getStoredApiSecret, getStoredApiSecretSnapshot, loadApiSecrets, saveApiSecret, } from '../src/shared/utils/api-secret-store.ts' type RequestConfig = { url?: string; method?: string; data?: unknown } type MockTest = Parameters[1] extends (t: infer T) => unknown ? T : never function createStorage() { const store = new Map() return { getItem: (key: string) => (store.has(key) ? (store.get(key) as string) : null), setItem: (key: string, value: string) => { store.set(key, String(value)) }, removeItem: (key: string) => { store.delete(key) }, } } function setupWindow() { const localStorage = createStorage() const sessionStorage = createStorage() // 密钥按登录用户隔离:uid 必须存在(与真实前端 localStorage.uid 一致) localStorage.setItem('uid', '42') ;(globalThis as Record).window = { localStorage, sessionStorage, location: { origin: 'http://localhost' }, } return { localStorage, sessionStorage } } function mockRequest(t: MockTest, impl: (config: RequestConfig) => Promise) { t.mock.method(http, 'request', impl as never) } const okResponse = (data: unknown) => Promise.resolve({ data: { success: true, message: 'ok', data } }) function serverItem(moduleKey: string, overrides: Record = {}) { return { moduleKey, moduleLabel: moduleKey === 'appearance-patent' ? '外观专利密钥' : '货源查询密钥', masked: '', exists: false, checkStatus: 'unknown', checkCode: '', checkMessage: '', checkLatencyMs: null, checkedAt: null, updatedAt: null, ...overrides, } } function bundleResponse(items: unknown[]) { return okResponse({ items, requiredModules: ['appearance-patent', 'similar-asin'], complete: false, }) } const LEGACY_MIRROR_KEY = 'brand:api-secret:42:appearance-patent' test('test_secret_store_reads_legacy_value_before_load', () => { const { localStorage } = setupWindow() clearApiSecretCache() localStorage.setItem(LEGACY_MIRROR_KEY, JSON.stringify({ value: 'legacy-key-1', retention: 'forever' })) assert.equal(getStoredApiSecret('appearance-patent'), 'legacy-key-1') }) test('test_secret_store_load_failure_returns_unknown_and_keeps_cache', async (t) => { const { localStorage } = setupWindow() clearApiSecretCache() localStorage.setItem(LEGACY_MIRROR_KEY, JSON.stringify({ value: 'legacy-key-2' })) mockRequest(t, () => Promise.reject(new Error('服务不可用'))) const state = await loadApiSecrets({ force: true }) assert.equal(state, 'unknown') assert.equal(getStoredApiSecret('appearance-patent'), 'legacy-key-2') }) test('test_secret_store_migrates_legacy_value_once_and_clears_local', async (t) => { const { localStorage } = setupWindow() clearApiSecretCache() localStorage.setItem(LEGACY_MIRROR_KEY, JSON.stringify({ value: 'legacy-key-3' })) const calls: string[] = [] mockRequest(t, (config) => { calls.push(config.url || '') if ((config.url || '').includes('/migrate')) { return okResponse({ migrated: 1 }) } return bundleResponse([serverItem('appearance-patent'), serverItem('similar-asin')]) }) const state = await loadApiSecrets({ force: true }) assert.equal(state, 'incomplete') assert.ok(calls.some((url) => url.includes('/api/user-secrets/migrate')), '应调用迁移接口') assert.equal(localStorage.getItem(LEGACY_MIRROR_KEY), null, '迁移后应清理旧记录') }) test('test_secret_store_save_keeps_plain_value_locally', async (t) => { setupWindow() clearApiSecretCache() mockRequest(t, () => okResponse(serverItem('appearance-patent', { masked: 'sk-a****1234', exists: true })), ) await saveApiSecret('appearance-patent', 'sk-abc123456') assert.equal(getStoredApiSecret('appearance-patent'), 'sk-abc123456') const snapshot = getStoredApiSecretSnapshot('appearance-patent') assert.equal(snapshot.masked, 'sk-a****1234') assert.equal(snapshot.exists, true) }) test('test_secret_store_save_rejects_empty_value', async () => { setupWindow() clearApiSecretCache() await assert.rejects(() => saveApiSecret('similar-asin', ' '), /密钥不能为空/) })