修复 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
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:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user