2026-10-24 23:19:17 +00:00
import assert from "node:assert/strict" ;
import { randomBytes } from "node:crypto" ;
import { once } from "node:events" ;
import { test } from "node:test" ;
import {
SoftwareInstallationResponseSchema ,
SoftwareInstallationsResponseSchema ,
} from "@metis/companies/common" ;
import {
ApplicationReferenceCapabilitiesSchema ,
GetGlobalApplicationAdminResponseSchema ,
GlobalApplicationTemplateResponseSchema ,
InventoryResponseSchema ,
ListApplicationCandidatesResponseSchema ,
ResolveApplicationReferencesResponseSchema ,
} from "@metis/resources/common" ;
import { migrateResourceScope } from "@metis/resources/module" ;
import { QueryTypes } from "sequelize" ;
import { z } from "zod/v4" ;
import { UniqueID } from "../../../packages/rdx-ddd/src/index" ;
import { createApp } from "./app" ;
import { prepareMembershipFixture } from "./asset-directory-memberships.fixture" ;
import { createDatabase , readDatabaseEnvironment } from "./config/database" ;
import { createIdentityRuntime } from "./infrastructure/http/identity-runtime" ;
import { disposableScopeEnvironment , writeScopeEvidence } from "./resource-scope.preflight" ;
import { scopeApiClient } from "./resource-scope-api.client" ;
import { latch , waitBarrier , waitCatalogLock } from "./resource-scope-concurrency.fixture" ;
import { scopeHttpFixture , seedScopeVolume } from "./resource-scope-http.fixture" ;
test ( "R2.4: software global, licencia local, negociación y serialización HTTP/MariaDB" , async ( t ) = > {
const environment = disposableScopeEnvironment ( ) ;
const database = createDatabase ( environment ) ;
const companies = Array . from ( { length : 15 } , ( ) = > UniqueID . generateNewID ( ) . toPrimitive ( ) ) ;
const company = companies [ 0 ] ;
const other = companies [ 1 ] ;
const actor = UniqueID . generateNewID ( ) . toPrimitive ( ) ;
assert . ok ( company && other ) ;
const fixture = await prepareMembershipFixture (
database ,
environment . APP_ORIGIN ,
actor ,
companies
) ;
await migrateResourceScope ( database ) ;
const volumeHttp = await scopeHttpFixture ( database , fixture ) ;
const volume = await seedScopeVolume ( fixture , volumeHttp , company ) ;
await volumeHttp . close ( ) ;
const catalog = await fixture . resourceUseCases . get ( company ) ;
assert . ok ( catalog . isSuccess ) ;
const template = ( key : string ) = > {
const item = catalog . data . catalog . definitions . find (
( item ) = > item . stockKey . getOrUndefined ( ) === key
) ;
assert . ok ( item ) ;
return item . id . toPrimitive ( ) ;
} ;
const local = async ( owner : string , key : string , name : string ) = > {
const saved = await fixture . resourceUseCases . save (
owner ,
undefined ,
{ name , templateId : template ( key ) , active : true , notes : "" , characteristics : { } } ,
actor
) ;
assert . ok ( saved . isSuccess ) ;
return saved . data . resource . id . toPrimitive ( ) ;
} ;
const localIds : string [ ] = [ ] ;
for ( const owner of companies ) {
for ( let index = 0 ; index < ( owner === company ? 50 : 1 ) ; index += 1 ) {
const id = await local ( owner , "template:application" , ` Ágil local ${ index + 1 } ` ) ;
if ( owner === company ) {
localIds . push ( id ) ;
}
}
}
const foreignInventory = await fixture . resourceUseCases . get ( other ) ;
assert . ok ( foreignInventory . isSuccess ) ;
const foreignId = foreignInventory . data . resources [ 0 ] ? . id . toPrimitive ( ) ;
const license = await local ( company , "template:software_license" , "Licencia propia" ) ;
const foreignLicense = await local ( other , "template:software_license" , "Licencia ajena" ) ;
const os = await local ( company , "template:os" , "SO particular" ) ;
const certificate = await local ( company , "template:certificate" , "No instalable" ) ;
const otherAsset = await fixture . asset ( other , "laptop" ) ;
const firstAsset = volume . assetIds [ 0 ] ;
const secondAsset = volume . assetIds [ 1 ] ;
assert . ok ( firstAsset && secondAsset && foreignId && localIds [ 0 ] ) ;
let runtime = await createIdentityRuntime (
database ,
readDatabaseEnvironment ( {
. . . process . env ,
RESOURCE_GLOBAL_CATALOG_ENABLED : "true" ,
RESOURCE_GLOBAL_SOFTWARE_ENABLED : "true" ,
RESOURCE_GLOBAL_SOFTWARE_CANDIDATES_ENABLED : "true" ,
} )
) ;
let server = createApp ( ( ) = > undefined , runtime ) . listen ( 0 , "127.0.0.1" ) ;
await once ( server , "listening" ) ;
const address = server . address ( ) ;
assert . ok ( address && typeof address !== "string" ) ;
const base = ` http://127.0.0.1: ${ address . port } ` ;
const admin = await scopeApiClient ( base ) ;
const ordinary = await scopeApiClient ( base ) ;
const password = randomBytes ( 24 ) . toString ( "hex" ) ;
try {
assert . equal (
(
await admin . request ( "/auth/bootstrap" , {
name : "Preparación R2.4" ,
email : "r24-admin@example.invalid" ,
password ,
} )
) . status ,
201
) ;
await admin . refresh ( ) ;
assert . equal (
(
await admin . request ( "/users" , {
name : "Soporte ordinario" ,
email : "r24-user@example.invalid" ,
password ,
} )
) . status ,
201
) ;
assert . equal (
( await ordinary . request ( "/auth/login" , { email : "r24-user@example.invalid" , password } ) )
. status ,
200
) ;
await ordinary . refresh ( ) ;
const specification = GlobalApplicationTemplateResponseSchema . parse (
await ( await admin . request ( "/resource-global-applications/template" ) ) . json ( )
) . specification ;
const product = async ( name : string , active = true ) = > {
const response = await admin . request ( "/resource-global-applications" , {
creationId : UniqueID.generateNewID ( ) . toPrimitive ( ) ,
values : {
name ,
templateId : specification.templateId ,
active ,
notes : "Nota privada global" ,
characteristics : { } ,
} ,
} ) ;
assert . equal ( response . status , 201 ) ;
return GetGlobalApplicationAdminResponseSchema . parse ( await response . json ( ) ) . resource ;
} ;
const global = await product ( "Ágil global — Producto sintético" ) ;
const inactive = await product ( "Global inactivo" , false ) ;
const input = {
assetId : firstAsset ,
resourceId : global.id ,
licenseResourceId : license ,
version : "1.0" ,
state : "configured" ,
notes : "Instalación particular" ,
} ;
const path = ` /software-installations/ ${ company } ` ;
const saved = await ordinary . request ( path , { values : input } ) ;
assert . equal ( saved . status , 201 , await saved . clone ( ) . text ( ) ) ;
let installation = SoftwareInstallationResponseSchema . parse ( await saved . json ( ) ) . configuration ;
const originalId = installation . id ;
const originalRevision = installation . history [ 0 ] ? . id ;
const races : string [ ] = [ ] ;
await t . test (
"mismo producto, instalaciones, versiones, estados, activos y licencias independientes" ,
async ( ) = > {
const inOther = await ordinary . request ( ` /software-installations/ ${ other } ` , {
values : {
. . . input ,
assetId : otherAsset.id.toPrimitive ( ) ,
version : "99" ,
state : "pending" ,
licenseResourceId : foreignLicense ,
} ,
} ) ;
assert . equal ( inOther . status , 201 ) ;
const otherInstallation = SoftwareInstallationResponseSchema . parse (
await inOther . json ( )
) . configuration ;
assert . notEqual ( otherInstallation . id , installation . id ) ;
assert . equal ( otherInstallation . companyId , other ) ;
assert . equal ( otherInstallation . values . resourceId , global . id ) ;
assert . equal ( otherInstallation . values . licenseResourceId , foreignLicense ) ;
assert . equal ( installation . companyId , company ) ;
assert . equal ( installation . values . version , "1.0" ) ;
const raw = await database . query < { amount : number } > (
"SELECT COUNT(*) AS amount FROM resources WHERE id=:id" ,
{ replacements : { id : global.id } , type : QueryTypes . SELECT }
) ;
assert . equal ( Number ( raw [ 0 ] ? . amount ) , 1 ) ;
assert . equal (
InventoryResponseSchema . parse (
await ( await ordinary . request ( ` /resources/ ${ company } ` ) ) . json ( )
) . resources . some ( ( item ) = > item . id === global . id ) ,
false
) ;
}
) ;
await t . test ( "matriz negativa, destino inmutable, CSRF y autenticación reales" , async ( ) = > {
for ( const [ resourceId , licenseResourceId , assetId , status ] of [
[ foreignId , null , firstAsset , 404 ] ,
[ inactive . id , null , firstAsset , 409 ] ,
[ certificate , null , firstAsset , 404 ] ,
[ global . id , foreignLicense , secondAsset , 404 ] ,
[ global . id , global . id , secondAsset , 404 ] ,
[ global . id , null , otherAsset . id . toPrimitive ( ) , 404 ] ,
[ UniqueID . generateNewID ( ) . toPrimitive ( ) , null , firstAsset , 404 ] ,
] as const ) {
assert . equal (
(
await admin . request ( path , {
values : { . . . input , resourceId , licenseResourceId , assetId } ,
} )
) . status ,
status
) ;
}
assert . equal (
(
await ordinary . request ( ` ${ path } / ${ installation . id } /update ` , {
values : { . . . input , resourceId : localIds [ 0 ] } ,
} )
) . status ,
409
) ;
assert . equal (
(
await ordinary . request (
path ,
{ values : { . . . input , assetId : secondAsset } } ,
{ token : "invalid" }
)
) . status ,
403
) ;
assert . equal (
( await ordinary . request ( path , { values : input } , { origin : "https://invalid.example" } ) )
. status ,
403
) ;
const anonymous = await scopeApiClient ( base ) ;
assert . equal ( ( await anonymous . request ( path ) ) . status , 401 ) ;
assert . equal ( ( await anonymous . request ( path , { values : input } ) ) . status , 401 ) ;
assert . equal (
( await ordinary . request ( path , { values : { . . . input , scope : "global" } } ) ) . status ,
400
) ;
assert . equal (
( await ordinary . request ( ` ${ path } ?purpose=application-configuration ` ) ) . status ,
400
) ;
const unused = companies [ 2 ] ;
assert . ok ( unused ) ;
assert . equal (
( await ordinary . request ( ` /software-installations/ ${ unused } ` , undefined , { contract : null } ) )
. status ,
200
) ;
assert . equal (
(
await admin . request ( "/resource-global-applications" , {
creationId : UniqueID.generateNewID ( ) . toPrimitive ( ) ,
values : {
name : "OS global prohibido" ,
templateId : template ( "template:os" ) ,
active : true ,
notes : "" ,
characteristics : { } ,
} ,
} )
) . status ,
409
) ;
} ) ;
await t . test (
"compatibilidad v2: locales legacy y respuesta completa global sin downgrade silencioso" ,
async ( ) = > {
assert . equal ( ( await ordinary . request ( path , undefined , { contract : null } ) ) . status , 409 ) ;
2026-10-25 19:39:06 +00:00
assert . equal ( ( await ordinary . request ( path , undefined , { contract : "3" } ) ) . status , 200 ) ;
assert . equal ( ( await ordinary . request ( path , undefined , { contract : "4" } ) ) . status , 400 ) ;
2026-10-24 23:19:17 +00:00
const uppercase = await ordinary . request (
path ,
{ values : { . . . input , assetId : secondAsset , resourceId : global.id.toUpperCase ( ) } } ,
{ contract : null }
) ;
assert . equal ( uppercase . status , 409 ) ;
assert . equal (
z
. object ( { code : z.literal ( "RESOURCE_CLIENT_UPGRADE_REQUIRED" ) } )
. safeParse ( await uppercase . json ( ) ) . success ,
true
) ;
assert . equal (
(
await ordinary . request ( ` ${ path } / ${ installation . id . toUpperCase ( ) } /update ` , {
values : { . . . input , resourceId : global.id.toUpperCase ( ) } ,
} )
) . status ,
200
) ;
assert . equal (
(
await ordinary . request (
` ${ path } / ${ installation . id } /update ` ,
{ values : input } ,
{ contract : null }
)
) . status ,
409
) ;
assert . equal (
( await ordinary . request ( ` ${ path } / ${ installation . id } /delete ` , { } , { contract : null } ) )
. status ,
409
) ;
const response = SoftwareInstallationsResponseSchema . parse (
await ( await ordinary . request ( path ) ) . json ( )
) ;
assert . equal ( response . configurations . length , 1 ) ;
assert . equal ( Object . hasOwn ( response . configurations [ 0 ] ? ? { } , "scope" ) , false ) ;
}
) ;
await t . test ( "instalación local de SO mantiene contrato y validación de licencia" , async ( ) = > {
assert . equal (
(
await ordinary . request (
path ,
{ values : { . . . input , resourceId : os , licenseResourceId : null } } ,
{ contract : null }
)
) . status ,
201
) ;
} ) ;
await t . test (
"candidatos mixtos paginados, proyección mínima, búsqueda, homónimos y perfiles cerrados" ,
async ( ) = > {
for ( let index = 0 ; index < 30 ; index += 1 ) {
await product ( ` Ágil global ${ index % 7 } ` , index % 5 !== 0 ) ;
}
let cursor : string | null = null ;
const ids = new Set < string > ( ) ;
const scopes = new Set < string > ( ) ;
do {
const response = await ordinary . request (
` /resource-application-candidates/ ${ company } ?purpose=software-installation&search=agil&pageSize=7 ${ cursor ? ` &cursor= ${ encodeURIComponent ( cursor ) } ` : "" } `
) ;
const page = ListApplicationCandidatesResponseSchema . parse ( await response . json ( ) ) ;
assert . equal ( response . status , 200 ) ;
for ( const item of page . items ) {
assert . equal ( ids . has ( item . id ) , false ) ;
ids . add ( item . id ) ;
scopes . add ( item . scope ) ;
assert . equal ( item . active , true ) ;
for ( const key of [ "notes" , "history" , "characteristics" , "actorId" , "installations" ] ) {
assert . equal ( Object . hasOwn ( item , key ) , false ) ;
}
}
cursor = page . nextCursor ;
} while ( cursor ) ;
assert . deepEqual ( [ . . . scopes ] . sort ( ) , [ "company" , "global" ] ) ;
assert . equal ( ids . size , 75 ) ;
const profiles = ListApplicationCandidatesResponseSchema . parse (
await (
await ordinary . request (
` /resource-application-candidates/ ${ company } ?purpose=application-configuration `
)
) . json ( )
) ;
assert . ok ( profiles . items . every ( ( item ) = > item . scope === "company" ) ) ;
assert . equal (
(
await ordinary . request ( ` /application-configurations/ ${ company } ` , {
values : {
profileId : volume.profileIds [ 0 ] ,
resourceId : global.id ,
delivery : "installed" ,
version : null ,
state : "configured" ,
notes : null ,
} ,
} )
) . status ,
409
) ;
}
) ;
await t . test (
"edición idempotente, licencia propia, renombrado e inactivo histórico con needsReview derivado" ,
async ( ) = > {
const repeated = SoftwareInstallationResponseSchema . parse (
await (
await ordinary . request ( ` ${ path } / ${ installation . id } /update ` , { values : input } )
) . json ( )
) . configuration ;
assert . equal ( repeated . history . length , 1 ) ;
installation = SoftwareInstallationResponseSchema . parse (
await (
await ordinary . request ( ` ${ path } / ${ installation . id } /update ` , {
values : { . . . input , version : "2.0" } ,
} )
) . json ( )
) . configuration ;
assert . equal ( installation . history . length , 2 ) ;
assert . equal ( installation . history [ 0 ] ? . values . version , "1.0" ) ;
assert . equal (
(
await admin . request ( ` /resource-global-applications/ ${ global . id } /update ` , {
values : { . . . global . values , name : "Producto renombrado" } ,
} )
) . status ,
200
) ;
assert . equal (
(
await admin . request ( ` /resource-global-applications/ ${ global . id } /status ` , {
active : false ,
} )
) . status ,
200
) ;
const before = await database . query (
"SELECT * FROM software_installation_revisions WHERE configurationId=:id ORDER BY position" ,
{ replacements : { id : installation.id } , type : QueryTypes . SELECT }
) ;
const all = SoftwareInstallationsResponseSchema . parse (
await ( await ordinary . request ( path ) ) . json ( )
) ;
assert . equal (
all . configurations . find ( ( item ) = > item . id === installation . id ) ? . needsReview ,
true
) ;
const reference = ResolveApplicationReferencesResponseSchema . parse (
await (
await ordinary . request ( ` /resource-application-references/ ${ company } /resolve ` , {
purpose : "software-installation" ,
ids : [ global . id ] ,
} )
) . json ( )
) . items [ 0 ] ;
assert . ok ( reference ? . status === "resolved" ) ;
assert . equal ( reference . reference . name , "Producto renombrado" ) ;
assert . equal ( reference . reference . active , false ) ;
assert . equal (
(
await ordinary . request ( ` ${ path } / ${ installation . id } /update ` , {
values : { . . . installation . values , version : "3.0" } ,
} )
) . status ,
409
) ;
const note = await ordinary . request ( ` ${ path } / ${ installation . id } /update ` , {
values : { . . . installation . values , notes : "Revisar producto" } ,
} ) ;
assert . equal ( note . status , 200 ) ;
installation = SoftwareInstallationResponseSchema . parse ( await note . json ( ) ) . configuration ;
assert . equal ( installation . id , originalId ) ;
assert . equal ( installation . history [ 0 ] ? . id , originalRevision ) ;
assert . equal ( installation . values . resourceId , global . id ) ;
const unchanged = await database . query (
"SELECT * FROM software_installation_revisions WHERE configurationId=:id AND position<2 ORDER BY position" ,
{ replacements : { id : installation.id } , type : QueryTypes . SELECT }
) ;
assert . deepEqual ( unchanged , before ) ;
}
) ;
await t . test (
"carrera: instalación primero, desactivación espera el commit y deriva revisión" ,
async ( ) = > {
const target = await product ( "Carrera instalación primero" ) ;
const arrived = latch ( ) ;
const release = latch ( ) ;
database . addHook ( "afterQuery" , "r24-hold-install" , async ( _options , query ) = > {
const sql : unknown = Reflect . get ( query , "sql" ) ;
if (
typeof sql === "string" &&
sql . startsWith ( "INSERT INTO software_installations" ) &&
sql . includes ( target . id )
) {
arrived . release ( ) ;
await release . promise ;
}
} ) ;
const installing = ordinary . request ( path , {
values : { . . . input , resourceId : target.id , licenseResourceId : null } ,
} ) ;
let disabling : Promise < Response > | undefined ;
try {
await waitBarrier ( arrived . promise ) ;
disabling = admin . request ( ` /resource-global-applications/ ${ target . id } /status ` , {
active : false ,
} ) ;
await waitCatalogLock ( process . env . METIS_RESOURCE_SCOPE_DISPOSABLE ? ? "" ) ;
release . release ( ) ;
assert . equal ( ( await installing ) . status , 201 ) ;
assert . equal ( ( await disabling ) . status , 200 ) ;
const installed = SoftwareInstallationsResponseSchema . parse (
await ( await ordinary . request ( path ) ) . json ( )
) . configurations . find ( ( item ) = > item . values . resourceId === target . id ) ;
assert . equal ( installed ? . needsReview , true ) ;
races . push ( "installation-commit-before-deactivation" ) ;
} finally {
release . release ( ) ;
database . removeHook ( "afterQuery" , "r24-hold-install" ) ;
await Promise . allSettled ( [ installing , . . . ( disabling ? [ disabling ] : [ ] ) ] ) ;
}
}
) ;
await t . test (
"carrera: desactivación primero, instalación espera y se rechaza al releer" ,
async ( ) = > {
const target = await product ( "Carrera desactivación primero" ) ;
const arrived = latch ( ) ;
const release = latch ( ) ;
database . addHook ( "afterQuery" , "r24-hold-status" , async ( _options , query ) = > {
const sql : unknown = Reflect . get ( query , "sql" ) ;
if (
typeof sql === "string" &&
sql . startsWith ( "INSERT INTO `resources`" ) &&
sql . includes ( target . id )
) {
arrived . release ( ) ;
await release . promise ;
}
} ) ;
const disabling = admin . request ( ` /resource-global-applications/ ${ target . id } /status ` , {
active : false ,
} ) ;
let installing : Promise < Response > | undefined ;
try {
await waitBarrier ( arrived . promise ) ;
installing = ordinary . request ( path , {
values : { . . . input , resourceId : target.id , licenseResourceId : null } ,
} ) ;
await waitCatalogLock ( process . env . METIS_RESOURCE_SCOPE_DISPOSABLE ? ? "" ) ;
release . release ( ) ;
assert . equal ( ( await disabling ) . status , 200 ) ;
assert . equal ( ( await installing ) . status , 409 ) ;
const rows = await database . query (
"SELECT id FROM software_installations WHERE resourceId=:id" ,
{ replacements : { id : target.id } , type : QueryTypes . SELECT }
) ;
assert . equal ( rows . length , 0 ) ;
races . push ( "deactivation-commit-before-installation-revalidation" ) ;
} finally {
release . release ( ) ;
database . removeHook ( "afterQuery" , "r24-hold-status" ) ;
await Promise . allSettled ( [ disabling , . . . ( installing ? [ installing ] : [ ] ) ] ) ;
}
}
) ;
await t . test ( "dos compañías instalan a la vez sin lock exclusivo de catálogo" , async ( ) = > {
const target = await product ( "Paralelismo entre compañías" ) ;
const first = latch ( ) ;
const second = latch ( ) ;
const release = latch ( ) ;
let arrived = 0 ;
database . addHook ( "afterQuery" , "r24-parallel" , async ( _options , query ) = > {
const sql : unknown = Reflect . get ( query , "sql" ) ;
if (
typeof sql === "string" &&
sql . startsWith ( "INSERT INTO software_installations" ) &&
sql . includes ( target . id )
) {
arrived += 1 ;
if ( arrived === 1 ) {
first . release ( ) ;
} else {
second . release ( ) ;
}
await release . promise ;
}
} ) ;
const attempts = [
ordinary . request ( path , {
values : { . . . input , resourceId : target.id , licenseResourceId : null } ,
} ) ,
ordinary . request ( ` /software-installations/ ${ other } ` , {
values : {
. . . input ,
assetId : otherAsset.id.toPrimitive ( ) ,
resourceId : target.id ,
licenseResourceId : null ,
} ,
} ) ,
] ;
try {
await waitBarrier ( Promise . all ( [ first . promise , second . promise ] ) . then ( ( ) = > undefined ) ) ;
release . release ( ) ;
assert . deepEqual (
( await Promise . all ( attempts ) ) . map ( ( response ) = > response . status ) ,
[ 201 , 201 ]
) ;
races . push ( "two-company-inserts-before-either-commit" ) ;
} finally {
release . release ( ) ;
database . removeHook ( "afterQuery" , "r24-parallel" ) ;
await Promise . allSettled ( attempts ) ;
}
} ) ;
await t . test (
"volumen operativo: cincuenta instalaciones particulares con productos locales distintos" ,
async ( ) = > {
for ( let index = 0 ; index < 50 ; index += 1 ) {
const assetId = volume . assetIds [ index ] ;
const resourceId = localIds [ index ] ;
assert . ok ( assetId && resourceId ) ;
assert . equal (
(
await ordinary . request ( path , {
values : {
. . . input ,
assetId ,
resourceId ,
licenseResourceId : null ,
version : ` Edición ${ index + 1 } ` ,
state : index % 3 ? "configured" : "pending" ,
} ,
} )
) . status ,
201
) ;
}
const listed = SoftwareInstallationsResponseSchema . parse (
await ( await ordinary . request ( path ) ) . json ( )
) ;
assert . ok ( listed . configurations . length >= 50 ) ;
assert . ok ( listed . configurations . every ( ( item ) = > item . companyId === company ) ) ;
}
) ;
await t . test ( "gates independientes de candidatos y asociación, sin abrir perfil" , async ( ) = > {
for ( const flags of [
{ software : false , candidates : true } ,
{ software : true , candidates : false } ,
] ) {
const isolated = await createIdentityRuntime (
database ,
readDatabaseEnvironment ( {
. . . process . env ,
RESOURCE_GLOBAL_CATALOG_ENABLED : "true" ,
RESOURCE_GLOBAL_SOFTWARE_ENABLED : String ( flags . software ) ,
RESOURCE_GLOBAL_SOFTWARE_CANDIDATES_ENABLED : String ( flags . candidates ) ,
} )
) ;
const listener = createApp ( ( ) = > undefined , isolated ) . listen ( 0 , "127.0.0.1" ) ;
await once ( listener , "listening" ) ;
try {
const endpoint = listener . address ( ) ;
assert . ok ( endpoint && typeof endpoint !== "string" ) ;
const client = await scopeApiClient ( ` http://127.0.0.1: ${ endpoint . port } ` ) ;
assert . equal (
( await client . request ( "/auth/login" , { email : "r24-user@example.invalid" , password } ) )
. status ,
200
) ;
await client . refresh ( ) ;
const capabilities = ApplicationReferenceCapabilitiesSchema . parse (
await (
await client . request ( ` /resource-application-candidates/ ${ company } /capabilities ` )
) . json ( )
) ;
assert . equal ( capabilities . softwareSelection , flags . candidates ) ;
assert . equal ( capabilities . softwareAssociation , flags . software ) ;
const options = ListApplicationCandidatesResponseSchema . parse (
await (
await client . request (
` /resource-application-candidates/ ${ company } ?purpose=software-installation&search=global `
)
) . json ( )
) ;
assert . equal (
options . items . some ( ( item ) = > item . scope === "global" ) ,
flags . candidates
) ;
const existing = await database . query < { id : string } > (
"SELECT id FROM resources WHERE companyId IS NULL AND name LIKE 'Ágil global%' AND active=1 ORDER BY id LIMIT 1" ,
{ type : QueryTypes . SELECT }
) ;
assert . ok ( existing [ 0 ] ) ;
const attempt = await client . request ( path , {
values : { . . . input , resourceId : existing [ 0 ] . id , licenseResourceId : null } ,
} ) ;
assert . equal ( attempt . status , flags . software ? 201 : 409 ) ;
if ( ! flags . software ) {
assert . equal (
z
. object ( { code : z.literal ( "RESOURCE_GLOBAL_FEATURE_DISABLED" ) } )
. safeParse ( await attempt . json ( ) ) . success ,
true
) ;
}
} finally {
await new Promise < void > ( ( resolve , reject ) = >
listener . close ( ( error ) = > ( error ? reject ( error ) : resolve ( ) ) )
) ;
isolated . stop ( ) ;
}
}
} ) ;
await t . test (
"gate cerrado tras reinicio: lectura, notas y retirada siguen; nuevas dependencias no" ,
async ( ) = > {
await new Promise < void > ( ( resolve , reject ) = >
server . close ( ( error ) = > ( error ? reject ( error ) : resolve ( ) ) )
) ;
runtime . stop ( ) ;
runtime = await createIdentityRuntime (
database ,
readDatabaseEnvironment ( {
. . . process . env ,
RESOURCE_GLOBAL_CATALOG_ENABLED : "false" ,
RESOURCE_GLOBAL_SOFTWARE_ENABLED : "true" ,
RESOURCE_GLOBAL_SOFTWARE_CANDIDATES_ENABLED : "true" ,
} )
) ;
server = createApp ( ( ) = > undefined , runtime ) . listen ( 0 , "127.0.0.1" ) ;
await once ( server , "listening" ) ;
const restarted = server . address ( ) ;
assert . ok ( restarted && typeof restarted !== "string" ) ;
const client = await scopeApiClient ( ` http://127.0.0.1: ${ restarted . port } ` ) ;
assert . equal (
( await client . request ( "/auth/login" , { email : "r24-user@example.invalid" , password } ) )
. status ,
200
) ;
await client . refresh ( ) ;
assert . equal ( ( await client . request ( path ) ) . status , 200 ) ;
assert . equal (
(
await client . request (
path ,
{
values : {
. . . input ,
assetId : secondAsset ,
resourceId : localIds [ 0 ] ,
licenseResourceId : null ,
} ,
} ,
{ contract : null }
)
) . status ,
201
) ;
assert . equal (
( await client . request ( path , { values : { . . . input , assetId : secondAsset } } ) ) . status ,
409
) ;
assert . equal (
(
await client . request ( ` ${ path } / ${ installation . id } /update ` , {
values : { . . . installation . values , notes : "Puerta cerrada, conservar borrador" } ,
} )
) . status ,
200
) ;
assert . equal (
(
await client . request ( ` ${ path } / ${ installation . id } /update ` , {
values : { . . . installation . values , state : "removal_pending" } ,
} )
) . status ,
200
) ;
const candidates = ListApplicationCandidatesResponseSchema . parse (
await (
await client . request (
` /resource-application-candidates/ ${ company } ?purpose=software-installation `
)
) . json ( )
) ;
assert . ok ( candidates . items . every ( ( item ) = > item . scope === "company" ) ) ;
assert . equal ( ( await client . request ( ` ${ path } / ${ installation . id } /delete ` , { } ) ) . status , 200 ) ;
}
) ;
await writeScopeEvidence ( "software-results.json" , {
companies : 15 ,
principal : {
people : 50 ,
assets : 50 ,
profiles : 50 ,
workstations : 50 ,
localApplications : 50 ,
softwareInstallationsAtLeast : 50 ,
} ,
races ,
profileGlobalAssociations : false ,
migrationsAdded : false ,
} ) ;
const foreignKeys = await database . query (
"SELECT TABLE_NAME,COLUMN_NAME,REFERENCED_TABLE_NAME,REFERENCED_COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME IN ('software_installations','software_installation_revisions') AND REFERENCED_TABLE_NAME IS NOT NULL" ,
{ type : QueryTypes . SELECT }
) ;
await writeScopeEvidence ( "software-foreign-keys.json" , foreignKeys ) ;
} finally {
await new Promise < void > ( ( resolve , reject ) = >
server . close ( ( error ) = > ( error ? reject ( error ) : resolve ( ) ) )
) ;
runtime . stop ( ) ;
await database . close ( ) ;
}
} ) ;