Files
go_bt/tests/aistudio_project_sync_deferral.test.js

98 lines
6.1 KiB
JavaScript

'use strict';
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const service = require('../src/services/aistudio_api_key_service');
const step = require('../src/pipeline/steps/aistudio_api_key');
const projectId = 'delayed-project-509408-x1';
const pending = service._private.pendingCloudProjectSync(projectId);
assert.strictEqual(pending.projectSyncPending, true,
'Cloud 项目尚未同步时必须返回可延迟重试状态,不能直接判失败');
assert.strictEqual(pending.retryAfterMs, 30000,
'项目同步等待时间必须是 30 秒');
assert.strictEqual(pending.preferredProjectId, projectId,
'延迟重试必须携带已创建的项目 ID,防止重复创建项目');
const root = path.join(__dirname, '..', 'src');
const serviceSource = fs.readFileSync(path.join(root, 'services', 'aistudio_api_key_service.js'), 'utf8');
const selectStart = serviceSource.indexOf('async function selectProjectInNewUi');
const selectEnd = serviceSource.indexOf('\nasync function classifyProjectOptions', selectStart);
const selectSource = serviceSource.slice(selectStart, selectEnd);
assert(selectSource.includes('pendingCloudProjectSync('),
'已有 Cloud 项目但导入列表为空时必须挂起,不能转去新建重复项目');
assert(selectSource.indexOf('if (preferredProjectId)')
< selectSource.indexOf('if (classified.createIndex >= 0)'),
'挂起判断必须发生在 Create project 分支之前');
const ensureStart = serviceSource.indexOf('async function ensureProjectSelected');
const ensureEnd = serviceSource.indexOf('\nasync function ', ensureStart + 20);
const ensureSource = serviceSource.slice(ensureStart, ensureEnd);
assert(ensureSource.includes('if (newUi.handled) return newUi;'),
'新版项目选择结果必须原样向上传递,否则 projectSyncPending 会被丢失并立即判失败');
assert(!ensureSource.includes('if (newUi.handled) return { ok: newUi.ok'),
'不得只挑选 ok/created/msg 字段,必须保留延迟重试元数据');
const stepSource = fs.readFileSync(path.join(root, 'pipeline', 'steps', 'aistudio_api_key.js'), 'utf8');
assert(stepSource.includes("type: 'aistudio_project_sync'"),
'AI Studio 步骤必须把项目同步状态交给批量调度器');
const runnerSource = fs.readFileSync(path.join(root, 'workers', 'task_runner.js'), 'utf8');
assert(runnerSource.includes('? [ATOM_STEPS.aistudio_api_key.step]'),
'延迟到期后必须只补跑 AI Studio Key 步骤');
assert(runnerSource.includes('__defer: true'),
'账号处理器必须返回调度器可识别的延迟重新入队标记');
const pendingStore = step._private;
const persisted = pendingStore.serializePendingProject(projectId, 1000, 'My Project 25180');
assert.strictEqual(pendingStore.parsePendingProject(persisted, 2000), projectId,
'待同步 project ID 必须能跨批次持久化并恢复');
assert.strictEqual(
pendingStore.parsePendingProject(persisted, 1000 + pendingStore.PENDING_PROJECT_MAX_AGE_MS + 1),
'',
'超过10分钟的待同步 project ID 必须失效,避免跨批次继续搜索错误项目');
assert.strictEqual(
pendingStore.pendingProjectSettingKey(' User@Example.COM '),
'aistudio_pending_project:user@example.com',
'待同步项目记录必须按规范化邮箱隔离');
const pendingRecord = pendingStore.parsePendingProjectRecord(persisted, 31000);
assert.strictEqual(pendingRecord.projectId, projectId,
'持久化记录必须恢复 project ID');
assert.strictEqual(pendingRecord.savedAt, 1000,
'跨批次重试必须保留项目最初保存时间,不能无限刷新等待窗口');
assert.strictEqual(pendingRecord.projectName, 'My Project 25180',
'待同步记录必须保存 Cloud Console 生成的项目名称,供导入搜索使用');
assert.strictEqual(pendingRecord.ageMs, 30000,
'持久化记录必须提供年龄,供旧项目失效恢复判断');
assert.strictEqual(pendingStore.PENDING_PROJECT_MAX_AGE_MS, 600000,
'待同步项目最多保留10分钟,过期后必须以页面实际状态为准');
assert.strictEqual(pendingStore.PENDING_PROJECT_RECOVERY_MIN_AGE_MS, 600000,
'30秒只是回队间隔;10分钟保护期内搜索为空也不得重复创建项目');
assert.strictEqual(pendingStore.MAX_PROJECT_SYNC_DEFERS, 4,
'项目同步应允许多轮30秒非阻塞回队,不能首次回队就重复创建项目');
assert(stepSource.includes('if (waited < MAX_PROJECT_SYNC_DEFERS)'),
'项目同步步骤必须按上限多轮挂起,不能只等一轮');
assert(stepSource.includes('loadPendingProjectRecord(dbWrite, account.email)'),
'AI Studio 步骤启动时必须恢复跨批次待同步 project ID');
assert(stepSource.includes('account._aistudioPreferredProjectSavedAt = await savePendingProjectId('),
'进入延迟队列前必须持久化 project ID,防止下轮重复创建项目');
assert(stepSource.includes('allowMissingPreferredProjectRecovery:'),
'服务层必须收到项目是否已等满同步窗口,区分新项目同步延迟和失效旧项目');
assert(runnerSource.includes('account._aistudioPreferredProjectSavedAt = Number('),
'延迟重新入队必须保留项目最初保存时间,供10分钟同步保护期判定');
assert(runnerSource.includes('account._aistudioPreferredProjectName = String('),
'延迟重新入队必须保留项目名称,继续按名称搜索');
assert(serviceSource.includes('if (!allowMissingPreferredProjectRecovery)'),
'10分钟保护期内项目搜索暂空时必须挂起,不能再次创建项目');
assert(serviceSource.includes("await search.press('Enter').catch(() => {})"),
'按项目名称填入搜索框后必须触发 Enter,避免页面保留旧的空搜索状态');
assert(serviceSource.includes('clearedPreferredProjectId'),
'确认旧项目不存在后必须把失效 project ID 传回流水线清理持久化状态');
assert(stepSource.includes("await clearPendingProjectId(dbWrite, account.email)"),
'流水线收到失效 project ID 后必须清除数据库中的待同步项目记录');
assert(stepSource.includes('account._aistudioProjectSyncWaits = 0'),
'旧项目失效后必须重置等待次数,让新项目获得完整的同步重试额度');
console.log('aistudio_project_sync_deferral.test.js: PASS');