import assert from 'node:assert/strict'; import test from 'node:test'; import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; const read = (path: string) => readFileSync(resolve(process.cwd(), path), 'utf8'); const migration = read('src/database/migrations/1788804000000-phase-d5-3-24-asset-context-history.ts'); const assets = read('src/asset-master/assets.service.ts'); const controller = read('src/asset-master/assets.controller.ts'); const temporal = read('src/asset-master/asset-temporal.service.ts'); const versionEntity = read('src/database/entities/asset-version.entity.ts'); test('D5.3.24 stores parent area and operator as explicit non-overlapping temporal context', () => { assert.match(migration, /CREATE TABLE asset_context_history/); assert.match(migration, /valid_from timestamptz NOT NULL/); assert.match(migration, /valid_until timestamptz/); assert.match(migration, /uq_asset_context_history_active/); assert.match(migration, /operational_area_id IS NULL AND operator_company_id IS NULL/); assert.match(migration, /REVOKE DELETE ON TABLE asset_context_history/); }); test('D5.3.24 reconstructs previous context from immutable asset versions during migration', () => { assert.match(migration, /FROM asset_versions version/); assert.match(migration, /LAG\(NULLIF\(version\.snapshot #>> '\{parent,id\}'/); assert.match(migration, /LEAD\(changes\.occurred_at\)/); assert.match(migration, /Cambio de contexto reconstruido desde el historial versionado/); }); test('D5.3.24 separates ordinary editing from an audited context transfer', () => { assert.match(controller, /@Get\(':id\/context-history'\)/); assert.match(controller, /@Post\(':id\/context'\)/); assert.match(controller, /@RequirePermissions\('assets\.manage_context'\)/); assert.match(assets, /ASSET_CONTEXT_CHANGE_REQUIRES_TRANSFER/); assert.match(assets, /AssetVersionChangeType\.CONTEXT_CHANGED/); assert.match(versionEntity, /CONTEXT_CHANGED = 'CONTEXT_CHANGED'/); assert.match(assets, /AuditAction\.ASSET_CONTEXT_CHANGED/); }); test('D5.3.24 closes the previous interval and appends the replacement without deleting history', () => { assert.match(assets, /UPDATE asset_context_history[\s\S]*SET valid_until=\$2/); assert.match(assets, /INSERT INTO asset_context_history/); assert.match(assets, /change_reason/); assert.doesNotMatch(assets, /DELETE FROM asset_context_history/); }); test('D5.3.24 temporal reconstruction resolves context by effective validity at the requested instant', () => { assert.match(temporal, /FROM asset_context_history history/); assert.match(temporal, /history\.valid_from <= \$2/); assert.match(temporal, /history\.valid_until IS NULL OR history\.valid_until > \$2/); assert.match(temporal, /operatorCompany: context\.operatorCompany/); });