Files
2fa/tests/scripts/emitted-scripts-parse.test.js
wuzf 3b590c8fae feat(import): chunked bulk import with backup continuity
Split bulk/Google-migration imports into 100-item chunks (shared via
LIMITS.BULK_IMPORT_CHUNK_SIZE) so large batches stay within rate limits
and avoid per-request timeouts.

Backend:
- batch.js accepts chunkIndex/chunkCount; only full, non-last chunks
  skip the event-driven backup (prevents forged metadata from bypassing)
- shared.js skipBackup branch stages pending_backup_hash so cron can
  compensate via hash comparison; on stage failure falls back to an
  immediate backup so middle chunks are never left without coverage
- data-hash.js saveDataHash accepts backupStartedAt and clears pending
  entries superseded by the just-committed backup, avoiding KV residue
  from multi-chunk imports
- worker.js/backup.js thread backupStartedAt through both event-driven
  and scheduled paths

Frontend:
- import/core.js + googleMigration.js chunk uploads and accumulate
  success/failure across retries; resume picks up from remaining items
- Google migration failures re-open the preview modal (including the
  first-round zero-success case) so users always have a retry entry
- New regression test (emitted-scripts-parse.test.js) parses every
  generated UI script through new Function to catch template-literal
  issues like real newlines in .join('\n')
2026-05-02 21:18:02 +08:00

90 lines
3.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 防回归:模板字符串生成的前端脚本必须语法合法。
*
* 历史问题src/ui/scripts/googleMigration.js 某处 `.join('\n')` 写在普通模板字面量里,
* `\n` 被提前解释成真换行,最终产物是非法 JS字符串字面量里夹真换行
* 该 bug 只在运行时载入脚本时才会暴露import-code.test.js 只解析 getImportCode()
* 覆盖不到 Google Migration 和整包脚本。
*
* 这组测试对每个生成脚本的 module getter 和 index.js 的组合入口做 new Function() parse。
*/
import { describe, expect, it } from 'vitest';
import { getAuthCode } from '../../src/ui/scripts/auth.js';
import { getBackupCode } from '../../src/ui/scripts/backup.js';
import { getCoreCode } from '../../src/ui/scripts/core.js';
import { getExportCode } from '../../src/ui/scripts/export.js';
import { getGoogleMigrationCode } from '../../src/ui/scripts/googleMigration.js';
import { getImportCode } from '../../src/ui/scripts/import/index.js';
import { getCoreScripts, getModuleCode, getScripts } from '../../src/ui/scripts/index.js';
import { getModuleLoaderCode } from '../../src/ui/scripts/moduleLoader.js';
import { getOTPCode } from '../../src/ui/scripts/otp.js';
import { getPWACode } from '../../src/ui/scripts/pwa.js';
import { getQRCodeCode } from '../../src/ui/scripts/qrcode.js';
import { getSearchCode } from '../../src/ui/scripts/search.js';
import { getSettingsCode } from '../../src/ui/scripts/settings.js';
import { getStateCode } from '../../src/ui/scripts/state.js';
import { getToolsCode } from '../../src/ui/scripts/tools.js';
import { getUICode } from '../../src/ui/scripts/ui.js';
import { getUtilsCode } from '../../src/ui/scripts/utils.js';
function assertParses(label, code) {
expect(typeof code).toBe('string');
expect(code.length).toBeGreaterThan(0);
try {
// eslint-disable-next-line no-new-func
new Function(code);
} catch (error) {
throw new Error(`${label} emits invalid JS: ${error.message}`);
}
}
describe('emitted script modules parse as valid JavaScript', () => {
it.each([
['state', getStateCode],
['auth', getAuthCode],
['otp', getOTPCode],
['ui', getUICode],
['search', getSearchCode],
['export', getExportCode],
['qrcode', getQRCodeCode],
['import', getImportCode],
['backup', getBackupCode],
['tools', getToolsCode],
['settings', getSettingsCode],
['googleMigration', getGoogleMigrationCode],
['core', getCoreCode],
['utils', getUtilsCode],
['pwa', getPWACode],
['moduleLoader', getModuleLoaderCode],
])('%s', (name, getter) => {
assertParses(name, getter());
});
});
describe('emitted script aggregators parse as valid JavaScript', () => {
it('getScripts (full inline bundle)', () => {
assertParses('getScripts', getScripts());
});
it('getCoreScripts (core-only bundle for lazy-loaded mode)', () => {
assertParses('getCoreScripts', getCoreScripts());
});
it.each(['import', 'export', 'backup', 'qrcode', 'tools', 'googleMigration'])('lazy-loaded module %s via getModuleCode', (name) => {
assertParses(`getModuleCode(${name})`, getModuleCode(name));
});
});
describe('googleMigration specific regression guards', () => {
it('does not emit a raw LF inside a single-quoted JS string literal', () => {
// 典型症状:}).join('
// '); ← 模板字面量里的 \n 被提前展开成真换行
const code = getGoogleMigrationCode();
expect(/'\n'/.test(code)).toBe(false);
// 期望是字面的 backslash+n两个字符JS 运行时才解析为换行
expect(code).toContain("join('\\n')");
});
});