2026-10-04 19:04:14 +00:00
import assert from "node:assert/strict" ;
import { InventoryResponseSchema , WorkstationResponseSchema } from "@metis/assets/common" ;
import { CompanyResponseSchema } from "@metis/companies/common" ;
import type { Sequelize } from "sequelize" ;
2026-10-10 11:32:40 +00:00
import { QueryTypes } from "sequelize" ;
2026-10-04 19:04:14 +00:00
import { z } from "zod/v4" ;
2026-10-10 11:32:40 +00:00
import { normalizeWorkstations } from "../../../modules/assets/src/api/infrastructure/persistence/workstation-relational" ;
2026-10-04 19:04:14 +00:00
type Request = ( path : string , body? : unknown , origin? : string , token? : string ) = > Promise < Response > ;
2026-10-10 10:01:58 +00:00
2026-10-04 19:04:14 +00:00
export async function verifyWorkstations (
request : Request ,
companyId : string ,
otherId : string ,
database : Sequelize
) {
const path = ` /assets/ ${ companyId } /workstations ` ;
const own = CompanyResponseSchema . parse (
await ( await request ( ` /companies/ ${ companyId } ` ) ) . json ( )
) . company ;
const other = CompanyResponseSchema . parse (
await ( await request ( ` /companies/ ${ otherId } ` ) ) . json ( )
) . company ;
const inventory = InventoryResponseSchema . parse (
await ( await request ( ` /assets/ ${ companyId } ` ) ) . json ( )
) ;
const foreign = InventoryResponseSchema . parse ( await ( await request ( ` /assets/ ${ otherId } ` ) ) . json ( ) )
. assets [ 0 ] ;
const first = inventory . assets [ 0 ] ;
const second = inventory . assets [ 1 ] ;
2026-10-10 10:01:58 +00:00
2026-10-04 19:04:14 +00:00
assert . ok ( first && second && foreign ) ;
const personFields = {
name : "Persona puesto" ,
position : null ,
email : null ,
phone : null ,
active : true ,
isContact : false ,
} ;
const personResponse = await request ( ` /companies/ ${ companyId } /people ` , personFields ) ;
2026-10-10 10:01:58 +00:00
2026-10-04 19:04:14 +00:00
assert . equal ( personResponse . status , 201 ) ;
const person = CompanyResponseSchema . parse ( await personResponse . json ( ) ) . company . people . at ( - 1 ) ;
2026-10-10 10:01:58 +00:00
2026-10-04 19:04:14 +00:00
assert . ok ( person ) ;
2026-10-09 23:37:10 +00:00
const otherPersonResponse = await request ( ` /companies/ ${ otherId } /people ` , personFields ) ;
2026-10-10 10:01:58 +00:00
2026-10-09 23:37:10 +00:00
assert . equal ( otherPersonResponse . status , 201 ) ;
2026-10-10 09:09:34 +00:00
const otherPerson = CompanyResponseSchema . parse (
await otherPersonResponse . json ( )
) . company . people . at ( - 1 ) ;
2026-10-10 10:01:58 +00:00
2026-10-09 23:37:10 +00:00
assert . ok ( otherPerson ) ;
2026-10-04 19:04:14 +00:00
const input = {
name : "Puesto Ana" ,
active : true ,
siteId : own.sites [ 0 ] ? . id ? ? null ,
personId : person.id ,
assetIds : [ first . id , second . id ] ,
2026-10-05 07:53:29 +00:00
sharedAssetIds : [ ] ,
2026-10-04 19:04:14 +00:00
} ;
2026-10-10 10:01:58 +00:00
2026-10-04 19:04:14 +00:00
const save = async ( values : unknown , id? : string ) = > {
const response = await request ( id ? ` ${ path } / ${ id } /update ` : path , { values } ) ;
2026-10-10 10:01:58 +00:00
2026-10-04 19:04:14 +00:00
assert . equal ( response . status , id ? 200 : 201 , await response . clone ( ) . text ( ) ) ;
2026-10-10 10:01:58 +00:00
2026-10-04 19:04:14 +00:00
return WorkstationResponseSchema . parse ( await response . json ( ) ) . workstation ;
} ;
2026-10-10 10:01:58 +00:00
2026-10-04 19:04:14 +00:00
const error = async ( values : unknown , code : string , id? : string , status = 409 ) = > {
const response = await request ( id ? ` ${ path } / ${ id } /update ` : path , { values } ) ;
2026-10-10 10:01:58 +00:00
2026-10-04 19:04:14 +00:00
assert . equal ( response . status , status ) ;
assert . equal ( z . object ( { code : z.string ( ) } ) . parse ( await response . json ( ) ) . code , code ) ;
} ;
2026-10-10 10:01:58 +00:00
2026-10-04 19:04:14 +00:00
await error ( { . . . input , assetIds : [ foreign . id ] } , "ASSET_NOT_FOUND" , undefined , 404 ) ;
await error ( { . . . input , siteId : other.sites [ 0 ] ? . id } , "SITE_NOT_FOUND" , undefined , 404 ) ;
2026-10-09 23:37:10 +00:00
await error ( { . . . input , personId : otherPerson.id } , "PERSON_UNAVAILABLE" ) ;
2026-10-04 19:04:14 +00:00
await error ( { . . . input , assetIds : [ first . id , first . id ] } , "INVALID_MEMBERS" ) ;
await error ( { . . . input , actorId : first.id } , "INVALID_REQUEST" , undefined , 400 ) ;
assert . equal ( ( await request ( path , { values : input } , "https://other.example" ) ) . status , 403 ) ;
assert . equal ( ( await request ( path , { values : input } , undefined , "a" . repeat ( 64 ) ) ) . status , 403 ) ;
const [ a , b ] = await Promise . all ( [
request ( path , { values : input } ) ,
request ( path , { values : { . . . input , name : "Puesto conflictivo" } } ) ,
] ) ;
2026-10-10 10:01:58 +00:00
2026-10-04 19:04:14 +00:00
assert . deepEqual ( [ a . status , b . status ] . sort ( ) , [ 201 , 409 ] ) ;
const success = a . status === 201 ? a : b ;
const created = WorkstationResponseSchema . parse ( await success . json ( ) ) . workstation ;
2026-10-10 10:01:58 +00:00
2026-10-04 19:04:14 +00:00
assert . equal ( created . id . charAt ( 14 ) , "7" ) ;
assert . equal ( created . history [ 0 ] ? . id . charAt ( 14 ) , "7" ) ;
assert . ok ( created . history [ 0 ] ? . actorName ) ;
const unchanged = await save (
{ . . . created . values , assetIds : [ . . . created . values . assetIds ] . reverse ( ) } ,
created . id
) ;
2026-10-10 10:01:58 +00:00
2026-10-04 19:04:14 +00:00
assert . deepEqual ( unchanged , created ) ;
const empty = await save ( { . . . input , name : "Puesto vacío" , siteId : null , assetIds : [ ] } ) ;
2026-10-10 10:01:58 +00:00
2026-10-04 19:04:14 +00:00
assert . equal (
(
await request ( ` /companies/ ${ companyId } /people/ ${ person . id } /update ` , {
. . . personFields ,
active : false ,
} )
) . status ,
200
) ;
await error ( { . . . empty . values , name : "Nueva asignación inactiva" } , "PERSON_UNAVAILABLE" ) ;
assert . deepEqual ( await save ( created . values , created . id ) , created ) ;
await error ( { . . . empty . values , assetIds : [ first . id ] } , "ASSET_IN_WORKSTATION" , empty . id ) ;
await error ( { . . . empty . values , assetIds : [ created . id ] } , "ASSET_NOT_FOUND" , empty . id , 404 ) ;
const inactive = await save ( { . . . created . values , active : false } , created . id ) ;
2026-10-10 10:01:58 +00:00
2026-10-04 19:04:14 +00:00
assert . equal ( inactive . history . length , 2 ) ;
const receiver = await save ( { . . . empty . values , assetIds : [ first . id ] } , empty . id ) ;
2026-10-10 10:01:58 +00:00
2026-10-04 19:04:14 +00:00
await error ( { . . . created . values , active : true } , "ASSET_IN_WORKSTATION" , created . id ) ;
const removed = await save ( { . . . receiver . values , assetIds : [ ] } , receiver . id ) ;
2026-10-10 10:01:58 +00:00
2026-10-04 19:04:14 +00:00
assert . equal ( removed . history . length , 3 ) ;
const restored = await save ( created . values , created . id ) ;
2026-10-10 10:01:58 +00:00
2026-10-04 19:04:14 +00:00
assert . equal ( restored . history . length , 3 ) ;
assert . deepEqual ( restored . history [ 1 ] ? . values . assetIds , created . values . assetIds ) ;
const foreignUpdate = await request ( ` /assets/ ${ otherId } /workstations/ ${ created . id } /update ` , {
values : created.values ,
} ) ;
2026-10-10 10:01:58 +00:00
2026-10-04 19:04:14 +00:00
assert . equal ( foreignUpdate . status , 404 ) ;
await assert . rejects (
database . query ( "UPDATE workstation_assets SET assetId=:asset WHERE workstationId=:id" , {
replacements : { asset : foreign.id , id : created.id } ,
} )
) ;
2026-10-10 11:32:40 +00:00
await database . query ( "UPDATE workstation_members SET kind='invalid' WHERE workstationId=:id" , {
replacements : { id : restored.id } ,
2026-10-04 19:04:14 +00:00
} ) ;
try {
assert . equal ( ( await request ( ` /assets/ ${ companyId } ` ) ) . status , 500 ) ;
} finally {
2026-10-10 11:32:40 +00:00
await database . query (
"UPDATE workstation_members SET kind='exclusive' WHERE workstationId=:id" ,
{
replacements : { id : restored.id } ,
}
) ;
2026-10-04 19:04:14 +00:00
}
const current = InventoryResponseSchema . parse (
await ( await request ( ` /assets/ ${ companyId } ` ) ) . json ( )
) ;
2026-10-10 10:01:58 +00:00
2026-10-04 19:04:14 +00:00
assert . deepEqual (
current . workstations . find ( ( item ) = > item . id === restored . id ) ,
restored
) ;
assert . equal (
InventoryResponseSchema . parse ( await ( await request ( ` /assets/ ${ otherId } ` ) ) . json ( ) ) . workstations
. length ,
0
) ;
2026-10-10 11:32:40 +00:00
// Migrar instantáneas antiguas sin activos compartidos conserva identidades e historial.
const beforeMigration = InventoryResponseSchema . parse (
await ( await request ( ` /assets/ ${ companyId } ` ) ) . json ( )
2026-10-05 07:53:29 +00:00
) ;
2026-10-10 11:32:40 +00:00
for ( const table of [ "workstations" , "workstation_revisions" ] ) {
await database . query ( ` ALTER TABLE ${ table } ADD COLUMN \` values \` JSON NULL ` ) ;
}
for ( const station of beforeMigration . workstations ) {
const { sharedAssetIds : _shared , . . . legacyValues } = station . values ;
await database . query ( "UPDATE workstations SET `values`=:values WHERE id=:id" , {
replacements : { id : station.id , values : JSON.stringify ( legacyValues ) } ,
} ) ;
for ( const revision of station . history ) {
const { sharedAssetIds : _revisionShared , . . . legacyRevision } = revision . values ;
2026-10-05 07:53:29 +00:00
await database . query ( "UPDATE workstation_revisions SET `values`=:values WHERE id=:id" , {
2026-10-10 11:32:40 +00:00
replacements : { id : revision.id , values : JSON.stringify ( legacyRevision ) } ,
2026-10-05 07:53:29 +00:00
} ) ;
}
}
2026-10-10 11:32:40 +00:00
await normalizeWorkstations ( database ) ;
await normalizeWorkstations ( database ) ;
assert . deepEqual (
InventoryResponseSchema . parse ( await ( await request ( ` /assets/ ${ companyId } ` ) ) . json ( ) ) ,
beforeMigration
) ;
const jsonColumns = await database . query < { name : string } > (
"SELECT COLUMN_NAME AS name FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME IN ('workstations','workstation_revisions') AND COLUMN_NAME='values'" ,
{ type : QueryTypes . SELECT }
) ;
assert . equal ( jsonColumns . length , 0 ) ;
2026-10-05 07:53:29 +00:00
await error (
{ . . . input , personId : null , assetIds : [ ] , sharedAssetIds : [ first . id ] } ,
"ASSET_IN_WORKSTATION"
) ;
await error (
{ . . . input , personId : null , assetIds : [ ] , sharedAssetIds : [ foreign . id ] } ,
"ASSET_NOT_FOUND" ,
undefined ,
404
) ;
await error ( { . . . input , sharedAssetIds : [ first . id ] } , "INVALID_MEMBERS" ) ;
await save ( { . . . restored . values , active : false } , restored . id ) ;
const sharedInput = { . . . input , personId : null , assetIds : [ ] , sharedAssetIds : [ first . id ] } ;
const sharedOne = await save ( { . . . sharedInput , name : "Compartido uno" } ) ;
const sharedTwo = await save ( { . . . sharedInput , name : "Compartido dos" } ) ;
2026-10-10 10:01:58 +00:00
2026-10-05 07:53:29 +00:00
await error ( { . . . sharedInput , assetIds : [ first . id ] , sharedAssetIds : [ ] } , "ASSET_IN_WORKSTATION" ) ;
await save ( { . . . sharedOne . values , sharedAssetIds : [ ] } , sharedOne . id ) ;
await error ( { . . . sharedInput , assetIds : [ first . id ] , sharedAssetIds : [ ] } , "ASSET_IN_WORKSTATION" ) ;
await save ( { . . . sharedTwo . values , active : false } , sharedTwo . id ) ;
const exclusive = await save ( { . . . sharedInput , assetIds : [ first . id ] , sharedAssetIds : [ ] } ) ;
2026-10-10 10:01:58 +00:00
2026-10-05 07:53:29 +00:00
await error ( { . . . sharedTwo . values , active : true } , "ASSET_IN_WORKSTATION" , sharedTwo . id ) ;
await save ( { . . . exclusive . values , active : false } , exclusive . id ) ;
const race = await Promise . all ( [
request ( path , {
values : {
. . . sharedInput ,
name : "Reserva concurrente" ,
assetIds : [ second . id ] ,
sharedAssetIds : [ ] ,
} ,
} ) ,
request ( path , {
values : { . . . sharedInput , name : "Uso compartido concurrente" , sharedAssetIds : [ second . id ] } ,
} ) ,
] ) ;
2026-10-10 10:01:58 +00:00
2026-10-05 07:53:29 +00:00
assert . deepEqual ( race . map ( ( response ) = > response . status ) . sort ( ) , [ 201 , 409 ] ) ;
2026-10-04 19:04:14 +00:00
}