98 lines
6.1 KiB
JavaScript
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');
|