修复画布历史资源空来源修复
允许资源 remap 精确匹配显式 null 的 sourceResourceId 修正 SpacetimeDB CLI procedure 的 SHA 字段与 Option wire 编码 补充 Rust 与 Node 回归测试并重新生成 Rust bindings 更新画布迁移方案和共享排障约定
This commit is contained in:
@@ -7,6 +7,7 @@ import { fileURLToPath } from 'node:url';
|
||||
|
||||
import {
|
||||
callSpacetimeProcedureViaCli,
|
||||
encodeSpacetimeCliOption,
|
||||
} from './spacetime-migration-common.mjs';
|
||||
|
||||
const PROCEDURE_NAME = 'repair_editor_canvas_resources_and_return';
|
||||
@@ -304,11 +305,6 @@ function validateRepairAction(action, label) {
|
||||
`${label}.replacement_resource_id 为复用资源动作的必填项。`,
|
||||
);
|
||||
}
|
||||
if (!normalized.expected_layout_source_resource_id) {
|
||||
throw new Error(
|
||||
`${label}.expected_layout_source_resource_id 为复用资源动作的必填项。`,
|
||||
);
|
||||
}
|
||||
if (
|
||||
normalized.replacement_resource_id === normalized.expected_resource_id
|
||||
) {
|
||||
@@ -327,7 +323,7 @@ function validateRepairAction(action, label) {
|
||||
`${label} 的补资源动作不能携带 replacement_resource_id。`,
|
||||
);
|
||||
}
|
||||
if (normalized.expected_layout_source_resource_id) {
|
||||
if (normalized.expected_layout_source_resource_id !== null) {
|
||||
throw new Error(
|
||||
`${label} 的补资源动作不能携带 expected_layout_source_resource_id。`,
|
||||
);
|
||||
@@ -415,16 +411,17 @@ export function buildProcedureInput(canvas, updatedAtMicros, dryRun) {
|
||||
project_id: canvas.project_id,
|
||||
owner_user_id: canvas.owner_user_id,
|
||||
expected_revision: canvas.expected_revision,
|
||||
expected_canvas_layout_sha256: canvas.expected_canvas_layout_sha256,
|
||||
expected_project_layout_sha256: canvas.expected_project_layout_sha256,
|
||||
expected_canvas_layout_sha_256: canvas.expected_canvas_layout_sha256,
|
||||
expected_project_layout_sha_256: canvas.expected_project_layout_sha256,
|
||||
remaps: canvas.actions
|
||||
.filter((action) => action.action_kind === 'reuse_project_resource')
|
||||
.map((action) => ({
|
||||
layer_id: action.layer_id,
|
||||
expected_missing_resource_id: action.expected_resource_id,
|
||||
replacement_resource_id: action.replacement_resource_id,
|
||||
expected_layout_source_resource_id:
|
||||
expected_layout_source_resource_id: encodeSpacetimeCliOption(
|
||||
action.expected_layout_source_resource_id,
|
||||
),
|
||||
})),
|
||||
restores: canvas.actions
|
||||
.filter((action) => action.action_kind === 'restore_project_resource')
|
||||
|
||||
@@ -166,6 +166,39 @@ describe('spacetime editor canvas resource repair plan', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('accepts an explicit null layout source only for reuse actions', () => {
|
||||
const plan = validateRepairPlan(
|
||||
planValue([
|
||||
canvas({
|
||||
actions: [
|
||||
action({ expected_layout_source_resource_id: null }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(
|
||||
plan.canvases[0].actions[0].expected_layout_source_resource_id,
|
||||
).toBeNull();
|
||||
expect(() =>
|
||||
validateRepairPlan(
|
||||
planValue([
|
||||
canvas({
|
||||
actions: [
|
||||
action({
|
||||
action_kind: 'restore_project_resource',
|
||||
replacement_resource_id: null,
|
||||
expected_layout_source_resource_id: 'unexpected-source',
|
||||
asset_object_id: 'private-asset-object-id',
|
||||
asset_kind: 'sound-effect',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
),
|
||||
).toThrow('不能携带 expected_layout_source_resource_id');
|
||||
});
|
||||
|
||||
it('rejects count mismatches, duplicate layers, and invalid kind-specific fields', () => {
|
||||
expect(() =>
|
||||
validateRepairPlan({ ...planValue(), expected_action_count: 2 }),
|
||||
@@ -263,7 +296,10 @@ describe('spacetime editor canvas resource repair execution', () => {
|
||||
layer_id: 'private-layer-id',
|
||||
expected_missing_resource_id: 'missing-private-resource-id',
|
||||
replacement_resource_id: 'replacement-private-resource-id',
|
||||
expected_layout_source_resource_id: 'source-private-resource-id',
|
||||
expected_layout_source_resource_id: [
|
||||
0,
|
||||
'source-private-resource-id',
|
||||
],
|
||||
},
|
||||
]);
|
||||
expect(input.restores).toEqual([
|
||||
@@ -275,10 +311,37 @@ describe('spacetime editor canvas resource repair execution', () => {
|
||||
},
|
||||
]);
|
||||
expect(input.canvas_id).toBe('private-canvas-id');
|
||||
expect(input.expected_canvas_layout_sha_256).toBe(SHA_A);
|
||||
expect(input.expected_project_layout_sha_256).toBe(SHA_B);
|
||||
expect(input).not.toHaveProperty('expected_canvas_layout_sha256');
|
||||
expect(input).not.toHaveProperty('expected_project_layout_sha256');
|
||||
expect(input.updated_at_micros).toBe(123);
|
||||
expect(input.dry_run).toBe(true);
|
||||
});
|
||||
|
||||
it('encodes an explicit null expected layout source as a CLI Option null', () => {
|
||||
const plan = validateRepairPlan(
|
||||
planValue([
|
||||
canvas({
|
||||
actions: [
|
||||
action({ expected_layout_source_resource_id: null }),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const input = buildProcedureInput(plan.canvases[0], 123, true);
|
||||
|
||||
expect(input.remaps).toEqual([
|
||||
{
|
||||
layer_id: 'private-layer-id',
|
||||
expected_missing_resource_id: 'missing-private-resource-id',
|
||||
replacement_resource_id: 'replacement-private-resource-id',
|
||||
expected_layout_source_resource_id: null,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('calls one procedure per canvas and emits only anonymous aggregate output', async () => {
|
||||
const plan = validateRepairPlan(
|
||||
planValue([
|
||||
|
||||
Reference in New Issue
Block a user