import { readFileSync } from 'node:fs'; import { beforeEach, describe, expect, it } from 'vitest'; import { setCurrentLanguage } from '../i18n'; import { detectConnectionImportKind, isConnectionPackagePasswordRequiredError, isConnectionPackageExportCanceled, resolveConnectionPackageExportResult, normalizeConnectionPackagePassword, } from './connectionExport'; const source = readFileSync(new URL('./connectionExport.ts', import.meta.url), 'utf8'); describe('connectionExport', () => { beforeEach(() => { setCurrentLanguage('en-US'); }); it('detects v2 app-managed packages', () => { expect(detectConnectionImportKind(JSON.stringify({ v: 2, kind: 'gonavi_connection_package', p: 1, exportedAt: '2026-04-11T21:00:00Z', connections: [], }))).toBe('app-managed-package'); }); it('detects v2 encrypted packages', () => { expect(detectConnectionImportKind(JSON.stringify({ v: 2, kind: 'gonavi_connection_package', p: 2, kdf: { n: 'a2id', m: 65536, t: 3, l: 4, s: 'c2FsdA==', }, nc: 'bm9uY2Utbm9uY2U=', d: 'encrypted-data', }))).toBe('encrypted-package'); }); it('rejects malformed v2 app-managed packages without connections array', () => { expect(detectConnectionImportKind(JSON.stringify({ v: 2, kind: 'gonavi_connection_package', p: 1, exportedAt: '2026-04-11T21:00:00Z', }))).toBe('invalid'); }); it('rejects malformed v2 encrypted packages without protected payload fields', () => { expect(detectConnectionImportKind(JSON.stringify({ v: 2, kind: 'gonavi_connection_package', p: 2, kdf: { n: 'a2id', m: 65536, t: 3, l: 4, }, }))).toBe('invalid'); }); it('detects v1 encrypted packages by gonavi envelope kind', () => { expect(detectConnectionImportKind(JSON.stringify({ schemaVersion: 1, kind: 'gonavi_connection_package', cipher: 'AES-256-GCM', kdf: { name: 'Argon2id', memoryKiB: 65536, timeCost: 3, parallelism: 4, salt: 'c2FsdA==', }, nonce: 'bm9uY2Utbm9uY2U=', payload: 'encrypted-data', }))).toBe('encrypted-package'); }); it('detects legacy imports from historical json arrays', () => { expect(detectConnectionImportKind(JSON.stringify([ { id: 'conn-1', name: 'Primary', config: { type: 'postgres', }, }, ]))).toBe('legacy-json'); }); it('detects Navicat NCX xml exports', () => { expect(detectConnectionImportKind(` `)).toBe('navicat-ncx'); }); it('returns invalid for malformed or unsupported content', () => { expect(detectConnectionImportKind('{not-json}')).toBe('invalid'); expect(detectConnectionImportKind(JSON.stringify({ v: 2, kind: 'gonavi_connection_package', p: 0, }))).toBe('invalid'); expect(detectConnectionImportKind(JSON.stringify({ v: 2, kind: 'gonavi_connection_package', }))).toBe('invalid'); expect(detectConnectionImportKind(JSON.stringify({ kind: 'gonavi_connection_package', payload: 'encrypted-data', }))).toBe('invalid'); expect(detectConnectionImportKind(JSON.stringify([ { foo: 'bar', }, ]))).toBe('invalid'); expect(detectConnectionImportKind(JSON.stringify({ kind: 'other_package', payload: 'encrypted-data', }))).toBe('invalid'); expect(detectConnectionImportKind('null')).toBe('invalid'); }); it('trims package passwords before use', () => { expect(normalizeConnectionPackagePassword(' secret-pass ')).toBe('secret-pass'); expect(normalizeConnectionPackagePassword('\n\t \t')).toBe(''); }); it('recognizes backend password-required errors for protected packages', () => { expect(isConnectionPackagePasswordRequiredError(new Error('恢复包密码不能为空'))).toBe(true); expect(isConnectionPackagePasswordRequiredError({ message: '恢复包密码不能为空' })).toBe(true); expect(isConnectionPackagePasswordRequiredError('恢复包密码不能为空')).toBe(true); expect(isConnectionPackagePasswordRequiredError(new Error('文件密码错误或文件已损坏'))).toBe(false); expect(isConnectionPackagePasswordRequiredError(undefined)).toBe(false); }); it('keeps the backend password-required sentinel keyed instead of hard-coded in source', () => { expect(source).not.toContain('恢复包密码不能为空'); expect(source).toContain('file.backend.error.connection_package_password_required'); }); it('treats export cancel as a non-error backend result', () => { expect(isConnectionPackageExportCanceled({ success: false, message: '已取消' })).toBe(true); expect(isConnectionPackageExportCanceled({ success: false, message: '导出失败' })).toBe(false); expect(isConnectionPackageExportCanceled({ success: true, message: '已取消' })).toBe(false); expect(isConnectionPackageExportCanceled(undefined)).toBe(false); }); it('maps export results to dialog state transitions', () => { const staleDialog = { open: true, mode: 'export' as const, includeSecrets: true, useFilePassword: false, password: ' secret-pass ', error: '上一次失败', confirmLoading: false, }; const canceledResult = resolveConnectionPackageExportResult(staleDialog, { success: false, message: '已取消' }); expect(canceledResult.kind).toBe('canceled'); if (canceledResult.kind === 'canceled') { expect(typeof canceledResult.nextDialog).toBe('function'); expect((canceledResult.nextDialog as (current: typeof staleDialog) => typeof staleDialog)({ open: false, mode: 'export', includeSecrets: true, useFilePassword: false, password: 'secret-pass', error: '更新后的错误', confirmLoading: true, })).toEqual({ open: false, mode: 'export', includeSecrets: true, useFilePassword: false, password: 'secret-pass', error: '', confirmLoading: false, }); } expect(resolveConnectionPackageExportResult(staleDialog, { success: true, message: '导出完成' })).toEqual({ kind: 'succeeded', }); expect(resolveConnectionPackageExportResult(staleDialog, { success: false, message: '磁盘已满' })).toEqual({ kind: 'failed', error: 'Export failed: 磁盘已满', }); expect(resolveConnectionPackageExportResult(staleDialog, undefined)).toEqual({ kind: 'failed', error: 'Export failed', }); }); });