修复 AGC 生成准备依赖全量素材库的问题
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m33s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 4m41s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 3m41s
Project CI / Backend tests (pull_request) Failing after 3m38s
Project CI / Frontend tests (pull_request) Successful in 2m27s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m56s
Project CI / Repository checks (pull_request) Successful in 3m1s
Project CI / Native shell tests (pull_request) Successful in 6m12s

新增站内与 External v1 单目录读取接口并校验账号归属,保持既有 API 兼容
将已有绑定与 partial 恢复改为按目录 ID 确认,首次建绑不再读取全库
目录缺失或查询失败时停止并保留恢复证据,保持生成幂等与账号隔离
同步 OpenAPI、生成绑定、技术文档及目录读取和生成恢复回归测试
This commit is contained in:
2026-10-02 22:25:17 +08:00
parent db0332ea09
commit 0afd0b0b10
19 changed files with 842 additions and 184 deletions
+96 -1
View File
@@ -384,6 +384,28 @@ function assertDeletedConflict(result, recordLabel) {
}
async function runSmoke(configPath, serverUrl) {
const readFolder = (folderId, ownerUserId = 'receipt-owner') =>
callProcedure(configPath, serverUrl, 'get_editor_asset_folder_and_return', {
folder_id: folderId,
owner_user_id: ownerUserId,
});
const missingBeforeCreate = await readFolder('missing-folder', 'empty-owner');
assert(
missingBeforeCreate.ok === false &&
missingBeforeCreate.errorMessage === '素材文件夹不存在',
'Missing folder read did not return the missing-folder error.',
);
assert(
(
await sqlRows(
configPath,
serverUrl,
'SELECT folder_id FROM editor_asset_folder',
'folders after missing read',
)
).length === 0,
'Missing folder read created a default folder.',
);
const projectA = projectInput('project-a', 'First');
assertOk(
await callProcedure(
@@ -471,6 +493,73 @@ async function runSmoke(configPath, serverUrl) {
'asset folder drift replay',
);
const folderRowsBeforeRead = await sqlRows(
configPath,
serverUrl,
'SELECT * FROM editor_asset_folder',
'folders before read',
);
const receiptsBeforeRead = await sqlRows(
configPath,
serverUrl,
'SELECT * FROM editor_idempotent_create_receipt',
'receipts before read',
);
assert(
(
await sqlRows(
configPath,
serverUrl,
'SELECT asset_id FROM editor_asset',
'assets before folder read',
)
).length === 0,
'Folder read fixture must have no assets.',
);
const emptyFolder = await readFolder('folder-a');
assertOk(emptyFolder, 'empty asset folder read');
assert(
emptyFolder.record[0] === 'folder-a' &&
emptyFolder.record[1] === 'Folder A',
'Folder read returned a different folder.',
);
const foreignFolder = await readFolder('folder-a', 'other-owner');
assert(
foreignFolder.ok === false &&
foreignFolder.record === null &&
foreignFolder.errorMessage === '无权访问该素材文件夹',
'Foreign-owner folder read did not reject access.',
);
const missingFolder = await readFolder('missing-folder');
assert(
missingFolder.ok === false &&
missingFolder.record === null &&
missingFolder.errorMessage === '素材文件夹不存在',
'Missing folder read did not fail closed.',
);
assert(
JSON.stringify(
await sqlRows(
configPath,
serverUrl,
'SELECT * FROM editor_asset_folder',
'folders after read',
),
) === JSON.stringify(folderRowsBeforeRead),
'Folder reads changed folder rows or timestamps.',
);
assert(
JSON.stringify(
await sqlRows(
configPath,
serverUrl,
'SELECT * FROM editor_idempotent_create_receipt',
'receipts after read',
),
) === JSON.stringify(receiptsBeforeRead),
'Folder reads changed durable receipts.',
);
const sameRaceInput = projectInput('project-same-race', 'Same Race');
const sameRace = await Promise.all([
callProcedure(
@@ -641,6 +730,12 @@ async function runSmoke(configPath, serverUrl) {
},
);
assertOk(deleteFolderResult, 'asset folder delete');
const deletedFolder = await readFolder('folder-a');
assert(
deletedFolder.ok === false &&
deletedFolder.errorMessage === '素材文件夹不存在',
'Deleted folder read did not return the missing-folder error.',
);
assertDeletedConflict(
await callProcedure(
configPath,
@@ -757,7 +852,7 @@ export async function main() {
await runSmoke(configPath, serverUrl);
console.log(
'[editor-idempotency-smoke] Passed exact replay, body conflicts, deletion fail-close, concurrent calls, and orphan checks.',
'[editor-idempotency-smoke] Passed folder reads (empty, missing, foreign owner, no writes), exact replay, body conflicts, deletion fail-close, concurrent calls, and orphan checks.',
);
} catch (error) {
const standaloneOutput = sanitizeDiagnostic(