134 lines
7.9 KiB
TypeScript
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/);
|
|
});
|