import { describe, expect, it } from 'vitest'; import { buildSpacetimeCallArgs, encodeSpacetimeCliOption, parseProcedureResult, } from './spacetime-migration-common.mjs'; describe('SpacetimeDB CLI SATS option encoding', () => { it('keeps absent options null and wraps present values as Some', () => { expect(encodeSpacetimeCliOption(null)).toBeNull(); expect(encodeSpacetimeCliOption(undefined)).toBeNull(); expect(encodeSpacetimeCliOption('job-2')).toEqual([0, 'job-2']); expect(encodeSpacetimeCliOption(123)).toEqual([0, 123]); }); it('serializes non-empty maintenance cursors in the CLI procedure input', () => { const args = buildSpacetimeCallArgs( { database: 'genarrative-prod', passthrough: [], serverUrl: 'http://127.0.0.1:3311', }, 'compact_external_generation_job_payloads_and_return', { dry_run: true, limit: 1, cursor_job_id: encodeSpacetimeCliOption('job-2'), completed_before_micros: encodeSpacetimeCliOption(123), }, ); const input = JSON.parse(args.at(-2) ?? 'null'); expect(input.cursor_job_id).toEqual([0, 'job-2']); expect(input.completed_before_micros).toEqual([0, 123]); }); it('normalizes editor canvas migration procedure results', () => { const result = parseProcedureResult( JSON.stringify([ true, [ 0, [ 'canvas-1', 'project-1', 'source-hash', 'structured-hash', 7, 2, 1, 'resource-hash', 1, 'backfilled', 7, ], ], [1], ]), 'backfill_editor_canvas_layout_and_return', ); expect(result).toEqual({ ok: true, migration: { canvas_id: 'canvas-1', project_id: 'project-1', source_layout_sha256: 'source-hash', structured_layout_sha256: 'structured-hash', verified_revision: 7, layer_count: 2, dialog_count: 1, resource_refs_sha256: 'resource-hash', migration_version: 1, status: 'backfilled', revision: 7, }, error_message: null, }); }); it('normalizes editor canvas resource repair procedure results', () => { const result = parseProcedureResult( JSON.stringify([true, [0, [true, false, 2, 0, 0, 0, 7]], [1]]), 'repair_editor_canvas_resources_and_return', ); expect(result).toEqual({ ok: true, repair: { dry_run: true, already_repaired: false, matched_layer_count: 2, remapped_layer_count: 0, restored_resource_count: 0, removed_source_resource_id_count: 0, revision: 7, }, error_message: null, }); }); it('normalizes character action metadata normalization results', () => { const result = parseProcedureResult( JSON.stringify([ true, 'asset', true, 25, 3, 0, 2, 1, 0, 3, 2, 0, 0, 1, ['asset-blocked'], [0, 'asset-25'], true, 'a'.repeat(64), [1], ]), 'normalize_editor_character_animation_metadata_and_return', ); expect(result).toEqual({ ok: true, scope: 'asset', dry_run: true, scanned_count: 25, matched_count: 3, updated_count: 0, backfilled_frames_count: 2, backfilled_duration_count: 1, reclassified_preview_count: 0, cleaned_generation_inputs_count: 3, cleaned_frame_index_count: 2, cleaned_canvas_layer_count: 0, materialized_resource_count: 0, blocker_count: 1, blocker_ids: ['asset-blocked'], next_cursor: 'asset-25', has_more: true, batch_sha256: 'a'.repeat(64), error_message: null, }); }); it('normalizes object-shaped character action procedure results', () => { const result = parseProcedureResult( JSON.stringify({ ok: true, scope: 'asset', dry_run: true, scanned_count: 25, matched_count: 3, updated_count: 0, blocker_count: 0, blocker_ids: [], next_cursor: 'asset-25', has_more: true, batch_sha_256: 'b'.repeat(64), error_message: null, }), 'normalize_editor_character_animation_metadata_and_return', ); expect(result).toMatchObject({ batch_sha256: 'b'.repeat(64), next_cursor: 'asset-25', }); expect(result).not.toHaveProperty('batch_sha_256'); }); it('normalizes ordinary image assetKind cleanup results', () => { const tupleResult = parseProcedureResult( JSON.stringify([ true, 'canvas', true, 5, 2, 0, 4, 0, [], [0, 'canvas-5'], true, 'c'.repeat(64), [1], ]), 'clean_editor_image_asset_kind_and_return', ); expect(tupleResult).toMatchObject({ scope: 'canvas', cleaned_field_count: 4, next_cursor: 'canvas-5', batch_sha256: 'c'.repeat(64), error_message: null, }); const objectResult = parseProcedureResult( JSON.stringify({ ok: true, scope: 'asset', dry_run: true, scanned_count: 1, matched_count: 1, updated_count: 0, cleaned_field_count: 1, blocker_count: 0, blocker_samples: [], next_cursor: null, has_more: false, batch_sha_256: 'd'.repeat(64), error_message: null, }), 'clean_editor_image_asset_kind_and_return', ); expect(objectResult.batch_sha256).toBe('d'.repeat(64)); expect(objectResult).not.toHaveProperty('batch_sha_256'); }); it('normalizes external generation history prune tuple results', () => { const result = parseProcedureResult( JSON.stringify([ true, false, 25, 2, 2, 2, 10, [0, 'job-25'], true, [0, '清理失败'], ]), 'prune_external_generation_job_history_and_return', ); expect(result).toEqual({ ok: true, dry_run: false, scanned_count: 25, selected_count: 2, deleted_job_count: 2, deleted_summary_count: 2, deleted_event_count: 10, next_cursor_job_id: 'job-25', has_more: true, error_message: '清理失败', }); }); });