Files
dh-inspeccion-v2/api-v3/test/unit/asset-imports-d5-3-7-2.test.ts
T

134 lines
7.9 KiB
TypeScript

import 'reflect-metadata';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import test from 'node:test';
import { REQUIRED_PERMISSIONS_KEY } from '../../src/authorization/decorators/require-permissions.decorator';
import { AssetImportsController } from '../../src/asset-imports/asset-imports.controller';
import { departmentCode, normalizedLegalRightType, organizationImportKey } from '../../src/asset-imports/asset-import-plan';
function source(relative: string): string { return readFileSync(resolve(process.cwd(), relative), 'utf8'); }
function permissionFor(method: string): string[] {
const controller = AssetImportsController.prototype;
return Reflect.getMetadata(REQUIRED_PERMISSIONS_KEY, controller[method as keyof typeof controller]) as string[];
}
test('D5.3.7.2 normalizes Departments and legal-right source values', () => {
assert.equal(departmentCode('Luján de Cuyo'), 'LUJAN-DE-CUYO');
assert.equal(departmentCode('San Rafael'), 'SAN-RAFAEL');
assert.equal(normalizedLegalRightType('Explotación'), 'EXPLOITATION_CONCESSION');
assert.equal(normalizedLegalRightType('Exploración'), 'EXPLORATION_PERMIT');
assert.equal(normalizedLegalRightType('Concesión de Transporte'), 'TRANSPORT_CONCESSION');
});
test('D5.3.7.2 exposes paginated plan details as read-only import information', () => {
assert.deepEqual(permissionFor('planItems'), ['asset_imports.read']);
const controller = source('src/asset-imports/asset-imports.controller.ts');
assert.match(controller, /@Get\(':id\/plan\/items'\)/);
const service = source('src/asset-imports/asset-imports.service.ts');
assert.match(service, /async planItems\(/);
assert.match(service, /entity_kind=\$\$\{params\.length\}/);
});
test('D5.3.7.2 migration creates a normalized Department catalog and Area relation', () => {
const migration = source('src/database/migrations/1787767200000-phase-d5-3-7-2-relational-territory-plan.ts');
assert.match(migration, /CREATE TABLE administrative_departments/);
assert.match(migration, /CREATE TABLE area_department_relations/);
assert.match(migration, /uq_administrative_department_name/);
assert.match(migration, /uq_area_department_relation_active_pair/);
assert.match(migration, /'DEPARTMENT'.*'AREA_DEPARTMENT_RELATION'.*'LEGAL_RIGHT'.*'LEGAL_RIGHT_ORGANIZATION'/s);
assert.match(migration, /REVOKE DELETE ON TABLE administrative_departments, area_department_relations/);
});
test('D5.3.7.2 territory planning deduplicates catalogs before relations and fields', () => {
const service = source('src/asset-imports/asset-imports.service.ts');
const territory = service.slice(service.indexOf('private async buildTerritoryPlan'), service.indexOf('private async buildInventoryPlan'));
assert.match(territory, /const departments = new Map/);
assert.match(territory, /entityKind: 'DEPARTMENT'|entityKind:'DEPARTMENT'/);
assert.match(territory, /entityKind:'AREA_DEPARTMENT_RELATION'|entityKind: 'AREA_DEPARTMENT_RELATION'/);
assert.match(territory, /entityKind:'LEGAL_RIGHT'|entityKind: 'LEGAL_RIGHT'/);
assert.match(territory, /entityKind:'LEGAL_RIGHT_ORGANIZATION'|entityKind: 'LEGAL_RIGHT_ORGANIZATION'/);
assert.match(territory, /matchedDepartmentId/);
assert.match(territory, /matchedLegalRightId/);
assert.match(territory, /departmentEntityKeys/);
assert.match(territory, /legalRightEntityKeys/);
});
test('D5.3.7.2 applies relational objects transactionally without duplicating Department attributes', () => {
const service = source('src/asset-imports/asset-imports.service.ts');
const apply = service.slice(service.indexOf('private async applyPlanItems'), service.indexOf('private async insertAssetItems'));
assert.match(apply, /INSERT INTO administrative_departments/);
assert.match(apply, /INSERT INTO area_department_relations/);
assert.match(apply, /INSERT INTO area_legal_rights/);
assert.match(apply, /INSERT INTO area_legal_right_organizations/);
assert.match(apply, /'PENDING'/);
assert.match(apply, /no se infiere titularidad/i);
const attributes = service.slice(service.indexOf('private async insertKnownAttributes'), service.indexOf('private async insertImportVersions'));
assert.doesNotMatch(attributes, /add\('departamento'/);
assert.match(service, /return `\(\$\$\{base\+1\}::integer,\$\$\{base\+2\}::uuid\)`/);
assert.match(service, /row\.row_number=v\.row_number/);
});
test('D5.3.7.2 rollback is logical for relational territory objects', () => {
const service = source('src/asset-imports/asset-imports.service.ts');
const rollback = service.slice(service.indexOf('async rollbackPlan'), service.indexOf('async upload'));
assert.match(rollback, /area_department_relations SET valid_until=CURRENT_DATE/);
assert.match(rollback, /area_legal_rights SET status='REVOKED'/);
assert.match(rollback, /administrative_departments SET is_active=false/);
assert.doesNotMatch(rollback, /DELETE FROM administrative_departments|DELETE FROM area_department_relations|DELETE FROM area_legal_rights/);
});
test('D5.3.7.2.3 stores an applied asset id without PostgreSQL parameter type ambiguity', () => {
const service = source('src/asset-imports/asset-imports.service.ts');
assert.match(service, /applied_asset_id=\$2::uuid,applied_object_id=\(\$2::uuid\)::text/);
assert.doesNotMatch(service, /applied_asset_id=\$2,applied_object_id=\$2/);
});
test('D5.3.7.2.4 snapshots the Maestro and blocks stale READY plans before any write', () => {
const service = source('src/asset-imports/asset-imports.service.ts');
const generate = service.slice(service.indexOf('async generatePlan'), service.indexOf('async resolvePlanItem'));
const apply = service.slice(service.indexOf('async applyPlan'), service.indexOf('async rollbackPlan'));
assert.match(service, /private async masterStateFingerprint/);
assert.match(generate, /masterStateBefore = await this\.masterStateFingerprint/);
assert.match(generate, /masterStateHash = await this\.masterStateFingerprint/);
assert.match(generate, /ASSET_IMPORT_MASTER_CHANGED_DURING_PLAN/);
assert.match(generate, /masterStateHash,/);
assert.match(apply, /plannedMasterStateHash/);
assert.match(apply, /currentMasterStateHash/);
assert.match(apply, /El Maestro cambió desde que se generó este plan/);
});
test('D5.3.8 preserves operator-local hierarchy instead of inventing universal installation names', () => {
const service = source('src/asset-imports/asset-imports.service.ts');
const planner = service.slice(service.indexOf('private async buildInventoryPlan'), service.indexOf('private async assetNameMatches'));
assert.match(planner, /sourceLocalStructureSuggestion/);
assert.match(planner, /entityKind:'LOCAL_STRUCTURE'/);
assert.match(planner, /assetTypeCode:'estructura_local'/);
assert.match(planner, /localNomenclature:true/);
assert.match(planner, /classificationStatus:'SOURCE_PRESERVED'/);
assert.doesNotMatch(planner, /\[A VALIDAR\]/);
});
test('D5.3.8 collapses territory ambiguity into one explicit context decision', () => {
const service = source('src/asset-imports/asset-imports.service.ts');
assert.match(service, /contextDecisionItems=new Map/);
assert.match(service, /inventoryContextDecision:true/);
assert.match(service, /candidateOptions/);
assert.match(service, /PLAN_CONTEXT_DECISION_REQUIRED/);
assert.match(service, /inventoryContextOverrides/);
assert.match(service, /ASSET_IMPORT_CONTEXT_MATCH_INVALID/);
});
test('D5.3.8 migration adds the provisional local structure type and hierarchy rules', () => {
const migration = source('src/database/migrations/1787853600000-phase-d5-3-8-local-inventory-structure.ts');
assert.match(migration, /'LOCAL_STRUCTURE'/);
assert.match(migration, /'estructura_local'/);
assert.match(migration, /Estructura local \(fuente\)/);
assert.match(migration, /nivel_fuente/);
assert.match(migration, /clasificacion_fuente/);
assert.match(migration, /ruta_fuente/);
assert.match(migration, /asset_type_parent_rules/);
});