From 2142331fc095bdc9f391b2adb38b05b83304c4ae Mon Sep 17 00:00:00 2001 From: enlineawork Date: Sat, 5 Sep 2026 18:05:08 -0300 Subject: [PATCH] F2.1: probar contrato de Inventario nacido en campo --- api-v3/test/unit/field-inventory-f2-1.test.ts | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 api-v3/test/unit/field-inventory-f2-1.test.ts diff --git a/api-v3/test/unit/field-inventory-f2-1.test.ts b/api-v3/test/unit/field-inventory-f2-1.test.ts new file mode 100644 index 0000000..20e6586 --- /dev/null +++ b/api-v3/test/unit/field-inventory-f2-1.test.ts @@ -0,0 +1,67 @@ +import 'reflect-metadata'; +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { readFileSync } from 'node:fs'; +import { validate } from 'class-validator'; +import { plainToInstance } from 'class-transformer'; +import { FieldInventoryController } from '../../src/inspection-visits/field-inventory.controller'; +import { CreateFieldInventoryDto } from '../../src/inspection-visits/dto/create-field-inventory.dto'; +import { UploadFieldInventoryPhotoDto } from '../../src/inspection-visits/dto/upload-field-inventory-photo.dto'; +import { REQUIRED_PERMISSIONS_KEY } from '../../src/authorization/decorators/require-permissions.decorator'; + +function permissions(method: string): string[] { + const controller = FieldInventoryController.prototype; + const handler = controller[method as keyof typeof controller]; + return Reflect.getMetadata(REQUIRED_PERMISSIONS_KEY, handler) as string[]; +} + +test('F2.1 mantiene Inventario de campo bajo política móvil y permisos explícitos', () => { + assert.deepEqual(permissions('list'), ['assets.read', 'inspections.execute']); + assert.deepEqual(permissions('types'), ['assets.read', 'inspections.execute']); + assert.deepEqual(permissions('selectExisting'), ['assets.read', 'inspections.execute']); + assert.deepEqual(permissions('create'), ['assets.create', 'inspections.execute']); + assert.deepEqual(permissions('uploadPhoto'), ['assets.create', 'inspections.execute']); +}); + +test('F2.1 exige GPS del dispositivo al crear Inventario en campo', async () => { + const dto = plainToInstance(CreateFieldInventoryDto, { + typeId: '11111111-1111-4111-8111-111111111111', + name: 'Batería 28', + attributes: {}, + deviceCapturedAt: '2026-09-05T17:30:00-03:00', + }); + const errors = await validate(dto); + const properties = new Set(errors.map((error) => error.property)); + assert.equal(properties.has('deviceLatitude'), true); + assert.equal(properties.has('deviceLongitude'), true); +}); + +test('F2.1 exige GPS del dispositivo en cada fotografía de campo', async () => { + const dto = plainToInstance(UploadFieldInventoryPhotoDto, { + deviceCapturedAt: '2026-09-05T17:30:00-03:00', + }); + const errors = await validate(dto); + const properties = new Set(errors.map((error) => error.property)); + assert.equal(properties.has('deviceLatitude'), true); + assert.equal(properties.has('deviceLongitude'), true); +}); + +test('F2.1 deriva Área y Operadora desde la Inspección y usa el asset anidado del alta provisional', () => { + const source = readFileSync('src/inspection-visits/field-inventory.service.ts', 'utf8'); + assert.match(source, /visit\.operational_area_id AS \"areaId\"/); + assert.match(source, /visit\.operator_company_id AS \"companyId\"/); + assert.match(source, /operationalAreaId: context\.areaId/); + assert.match(source, /operatorCompanyId: context\.companyId/); + assert.match(source, /const assetId = discovery\.asset\?\.id/); +}); + +test('F2.1 bloquea hallazgos sobre altas de campo hasta tener GPS y foto', () => { + const migration = readFileSync( + 'src/database/migrations/1789668000000-phase-f2-1-field-inventory.ts', + 'utf8', + ); + assert.match(migration, /enforce_field_inventory_capture_before_finding/); + assert.match(migration, /FIELD_INVENTORY_GPS_REQUIRED/); + assert.match(migration, /FIELD_INVENTORY_PHOTO_REQUIRED/); + assert.match(migration, /REVOKE UPDATE, DELETE ON TABLE asset_field_capture_events/); +});