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 type { OperatingSystemIdentity } from "@metis/resources/common"; import { ApplicationReferenceCapabilitiesSchema, CatalogResponseSchema, GetGlobalApplicationAdminResponseSchema, GetGlobalOperatingSystemResponseSchema, GlobalApplicationTemplateResponseSchema, InventoryResponseSchema, ListApplicationCandidatesResponseSchema, ListGlobalApplicationHistoryResponseSchema, ListGlobalApplicationsResponseSchema, ListGlobalOperatingSystemsResponseSchema, ResolveApplicationReferencesResponseSchema, ResourceResponseSchema, SaveDefinitionResponseSchema, } from "@metis/resources/common"; import { migrateResourceScope } from "@metis/resources/module"; import { QueryTypes } from "sequelize"; import { z } from "zod/v4"; import { inspectResourceScopeSchema } from "../../../modules/resources/src/api/infrastructure/persistence/resource-scope-schema"; 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 { resourceSqlSnapshot, verifyLegacyOperatingSystemEvolution, } from "./global-operating-systems-legacy.fixture"; import { createIdentityRuntime } from "./infrastructure/http/identity-runtime"; import { disposableScopeEnvironment, scopeSchemaEvidence, 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"; const uuid = () => UniqueID.generateNewID().toPrimitive(); const v3 = { contract: "3" }; const ErrorSchema = z.object({ code: z.string() }); test("Cierre SO: matriz DB-SO, evolución, integridad y concurrencia real", async (t) => { const environment = disposableScopeEnvironment(); const database = createDatabase(environment); const companies = Array.from({ length: 15 }, uuid); const company = companies[0]; const other = companies[1]; const actor = uuid(); assert.ok(company && other); const fixture = await prepareMembershipFixture( database, environment.APP_ORIGIN, actor, companies ); const migration = await migrateResourceScope(database); const http = await scopeHttpFixture(database, fixture); const volume = await seedScopeVolume(fixture, http, company); await http.close(); const assetA = volume.assetIds[0]; const assetA2 = volume.assetIds[1]; const raceAsset = volume.assetIds[2]; const assetB = await fixture.asset(other, "laptop"); assert.ok(assetA && assetA2 && raceAsset); const legacy = await verifyLegacyOperatingSystemEvolution( database, fixture.resourceUseCases, company, actor ); await writeScopeEvidence("legacy-evolution.json", legacy); const schema = await scopeSchemaEvidence(database); const settings = z .array(z.object({ version: z.string(), checkConstraints: z.number(), foreignKeys: z.number() })) .parse(schema.version); const currentSettings = settings[0]; assert.ok(currentSettings); assert.ok(currentSettings.version.includes("MariaDB")); assert.equal(currentSettings.checkConstraints, 1); assert.equal(currentSettings.foreignKeys, 1); assert.equal((await inspectResourceScopeSchema(database)).mode, "scoped"); assert.equal((await migrateResourceScope(database)).completed.length, 0); await writeScopeEvidence("schema.json", { ...schema, migration, repeatedMigration: "no changes", connection: { database: environment.DB_NAME, host: environment.DB_HOST, disposable: true }, engines: await database.query( "SELECT TABLE_NAME,ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() ORDER BY TABLE_NAME", { type: QueryTypes.SELECT } ), }); const openEnvironment = readDatabaseEnvironment({ ...process.env, RESOURCE_GLOBAL_CATALOG_ENABLED: "true", RESOURCE_GLOBAL_SOFTWARE_ENABLED: "true", RESOURCE_GLOBAL_SOFTWARE_CANDIDATES_ENABLED: "true", RESOURCE_GLOBAL_PROFILE_ENABLED: "true", RESOURCE_GLOBAL_OS_CATALOG_ENABLED: "true", RESOURCE_GLOBAL_OS_SOFTWARE_ENABLED: "true", }); let runtime = await createIdentityRuntime(database, openEnvironment); 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"); const root = "/resource-global-operating-systems"; const productId = uuid(); const applicationId = uuid(); const results: { id: string; result: string; http?: number[] }[] = []; try { assert.equal( ( await admin.request("/auth/bootstrap", { name: "Cierre SO", email: "closure-admin@example.invalid", password, }) ).status, 201 ); await admin.refresh(); assert.equal( ( await admin.request("/users", { name: "Operador de cierre", email: "closure-user@example.invalid", password, }) ).status, 201 ); assert.equal( (await ordinary.request("/auth/login", { email: "closure-user@example.invalid", password })) .status, 200 ); await ordinary.refresh(); const catalog = CatalogResponseSchema.parse( await (await admin.request("/resource-catalog/global")).json() ); const template = (key: string) => { const item = catalog.items.find((item) => item.builtinKey === `template:${key}`); assert.ok(item); return item; }; const field = (key: string) => { const item = catalog.items.find((item) => item.builtinKey === `field:${key}`); assert.ok(item); return item.id; }; const specification = GlobalApplicationTemplateResponseSchema.parse( await (await admin.request(`${root}/template`)).json() ).specification; const identity: OperatingSystemIdentity = { provider: "Microsoft", family: "Windows", productVersion: "11", editionState: "known", edition: "Pro", }; const values = (item: OperatingSystemIdentity) => ({ name: `${item.family} ${item.productVersion} ${item.edition}`.trim(), templateId: specification.templateId, active: true, notes: "Datos sintéticos de cierre", characteristics: { [field("provider")]: item.provider, [field("os_family")]: item.family, [field("os_product_version")]: item.productVersion, [field("os_edition_state")]: item.editionState, [field("software_edition")]: item.edition, }, }); const createProduct = async (version: string) => { const item = { ...identity, productVersion: version }; const response = await admin.request(root, { creationId: uuid(), values: values(item), reviewedIdentity: item, }); assert.equal(response.status, 201); return GetGlobalApplicationAdminResponseSchema.parse(await response.json()).resource; }; const adminProduct = async () => GetGlobalApplicationAdminResponseSchema.parse( await (await admin.request(`${root}/${productId}/admin`)).json() ).resource; const local = async (owner: string, key: string, name: string) => { const response = await ordinary.request(`/resources/${owner}`, { values: { name, templateId: template(key).id, active: true, notes: "", characteristics: {}, }, }); assert.equal(response.status, 201); return ResourceResponseSchema.parse(await response.json()).resource; }; for (let index = 0; index < 50; index += 1) { await local(company, "application", `Cierre sintético Álvarez ${index + 1}`); } const licenseA = await local(company, "software_license", "Licencia A"); const licenseB = await local(other, "software_license", "Licencia B"); const localOs = await local(company, "os", "Windows 11 Pro"); const input = { assetId: assetA, resourceId: productId, licenseResourceId: licenseA.id, version: "23H2", state: "configured", notes: null, }; const path = `/software-installations/${company}`; const readInstallation = async (owner: string, assetId: string, resourceId: string) => { const payload = SoftwareInstallationsResponseSchema.parse( await (await ordinary.request(`/software-installations/${owner}`, undefined, v3)).json() ); const item = payload.configurations.find( (entry) => entry.values.assetId === assetId && entry.values.resourceId === resourceId ); assert.ok(item); return item; }; const rootRows = (id: string) => database.query( "SELECT id,companyId,scope,templateId,name,active FROM resources WHERE id=:id", { replacements: { id }, type: QueryTypes.SELECT } ); const installationRows = (assetId: string, resourceId: string) => database.query( "SELECT * FROM software_installations WHERE assetId=:assetId AND resourceId=:resourceId", { replacements: { assetId, resourceId }, type: QueryTypes.SELECT } ); await t.test( "B3 — evolución SQL doble conserva campos, valores, personalizaciones y revisiones", () => { assert.equal(legacy.evolutionRuns, 2); assert.equal(legacy.originalResourcesUnchanged, true); assert.equal(legacy.revisionsPreserved, 2); results.push({ id: "B3", result: "pass: two persisted evolutions; legacy root and two revisions unchanged", }); } ); await t.test("DB-SO-01 crear SO global", async () => { const response = await admin.request(root, { creationId: productId, values: values(identity), reviewedIdentity: identity, }); assert.equal(response.status, 201); const item = GetGlobalApplicationAdminResponseSchema.parse(await response.json()).resource; assert.equal(item.id, productId); assert.equal(item.specification.templateId, specification.templateId); assert.deepEqual(await rootRows(productId), [ { id: productId, companyId: null, scope: "global", templateId: specification.templateId, name: "Windows 11 Pro", active: 1, }, ]); results.push({ id: "DB-SO-01", result: "pass", http: [201] }); }); await t.test("DB-SO-02 crear aplicación global mantiene su tipo", async () => { const response = await admin.request("/resource-global-applications", { creationId: applicationId, values: { name: "Editor global", templateId: template("application").id, active: true, notes: "", characteristics: {}, }, }); assert.equal(response.status, 201); assert.equal((await rootRows(applicationId)).length, 1); const list = ListGlobalApplicationsResponseSchema.parse( await (await ordinary.request("/resource-global-applications")).json() ); assert.ok(list.items.some((item) => item.id === applicationId)); assert.ok(list.items.every((item) => item.kind === "application" && item.id !== productId)); results.push({ id: "DB-SO-02", result: "pass", http: [201] }); }); await t.test("DB-SO-03 licencia global rechazada sin raíz persistida", async () => { const id = uuid(); assert.equal( ( await admin.request(root, { creationId: id, values: { ...values(identity), templateId: template("software_license").id }, reviewedIdentity: identity, }) ).status, 409 ); assert.equal((await rootRows(id)).length, 0); results.push({ id: "DB-SO-03", result: "pass", http: [409] }); }); await t.test("DB-SO-04 instalación válida desde A", async () => { const response = await ordinary.request(path, { values: input }, v3); assert.equal(response.status, 201); const item = SoftwareInstallationResponseSchema.parse(await response.json()).configuration; assert.equal(item.values.resourceId, productId); assert.equal((await installationRows(assetA, productId)).length, 1); results.push({ id: "DB-SO-04", result: "pass", http: [201] }); }); await t.test( "DB-SO-05 mismo producto desde B, instalación y licencia independientes", async () => { const response = await ordinary.request( `/software-installations/${other}`, { values: { ...input, assetId: assetB.id.toPrimitive(), licenseResourceId: licenseB.id, version: "24H2", }, }, v3 ); assert.equal(response.status, 201); const a = await readInstallation(company, assetA, productId); const b = SoftwareInstallationResponseSchema.parse(await response.json()).configuration; assert.notEqual(a.id, b.id); assert.equal(b.companyId, other); assert.equal(b.values.version, "24H2"); assert.equal((await installationRows(assetB.id.toPrimitive(), productId)).length, 1); results.push({ id: "DB-SO-05", result: "pass", http: [201] }); } ); await t.test("DB-SO-06 dos activos de A usan el mismo SO global", async () => { assert.equal( (await ordinary.request(path, { values: { ...input, assetId: assetA2 } }, v3)).status, 201 ); assert.notEqual( (await readInstallation(company, assetA, productId)).id, (await readInstallation(company, assetA2, productId)).id ); assert.equal( ( await database.query( "SELECT id FROM software_installations WHERE companyId=:company AND resourceId=:product", { replacements: { company, product: productId }, type: QueryTypes.SELECT } ) ).length, 2 ); results.push({ id: "DB-SO-06", result: "pass", http: [201] }); }); await t.test( "DB-SO-07 particulares y globales equivalentes conservan UUID e instalaciones", async () => { assert.notEqual(localOs.id, productId); assert.equal( (await ordinary.request(path, { values: { ...input, resourceId: localOs.id } }, v3)) .status, 201 ); assert.equal((await installationRows(assetA, localOs.id)).length, 1); assert.equal((await installationRows(assetA, productId)).length, 1); const list = ListApplicationCandidatesResponseSchema.parse( await ( await ordinary.request( `/resource-application-candidates/${company}?purpose=software-installation&search=Windows%2011`, undefined, v3 ) ).json() ); assert.ok(list.items.some((item) => item.id === productId && item.scope === "global")); assert.ok(list.items.some((item) => item.id === localOs.id && item.scope === "company")); results.push({ id: "DB-SO-07", result: "pass", http: [201, 200] }); } ); await t.test("DB-SO-08 licencia de A válida y particular", async () => { const item = await readInstallation(company, assetA, productId); assert.equal(item.values.licenseResourceId, licenseA.id); const rows = await database.query( "SELECT r.scope,r.companyId FROM resources r JOIN software_installations s ON s.licenseResourceId=r.id WHERE s.id=:id", { replacements: { id: item.id }, type: QueryTypes.SELECT } ); assert.deepEqual(rows, [{ scope: "company", companyId: company }]); results.push({ id: "DB-SO-08", result: "pass", http: [200] }); }); await t.test("DB-SO-09 licencia de A no puede vincularse desde B", async () => { const b = await readInstallation(other, assetB.id.toPrimitive(), productId); assert.equal( ( await ordinary.request( `/software-installations/${other}/${b.id}/update`, { values: { ...b.values, licenseResourceId: licenseA.id } }, v3 ) ).status, 404 ); assert.equal( (await readInstallation(other, assetB.id.toPrimitive(), productId)).values .licenseResourceId, licenseB.id ); results.push({ id: "DB-SO-09", result: "pass", http: [404] }); }); await t.test("DB-SO-10 SO excluido de ApplicationConfiguration", async () => { const response = await ordinary.request( `/application-configurations/${company}`, { values: { profileId: volume.profileIds[0], resourceId: productId, delivery: "installed", version: null, state: "configured", notes: null, }, }, v3 ); assert.equal(response.status, 404); assert.equal( ( await database.query("SELECT id FROM application_configurations WHERE resourceId=:id", { replacements: { id: productId }, type: QueryTypes.SELECT, }) ).length, 0 ); results.push({ id: "DB-SO-10", result: "pass", http: [404] }); }); await t.test("DB-SO-11 desactivar conserva instalaciones usadas", async () => { const before = await database.query( "SELECT * FROM software_installations WHERE resourceId=:id ORDER BY id", { replacements: { id: productId }, type: QueryTypes.SELECT } ); assert.equal( (await admin.request(`${root}/${productId}/status`, { active: false })).status, 200 ); assert.deepEqual( await database.query( "SELECT * FROM software_installations WHERE resourceId=:id ORDER BY id", { replacements: { id: productId }, type: QueryTypes.SELECT } ), before ); results.push({ id: "DB-SO-11", result: "pass", http: [200] }); }); await t.test("DB-SO-12 inactivo bloquea nueva instalación", async () => { assert.equal( (await ordinary.request(path, { values: { ...input, assetId: raceAsset } }, v3)).status, 409 ); assert.equal((await installationRows(raceAsset, productId)).length, 0); results.push({ id: "DB-SO-12", result: "pass", http: [409] }); }); await t.test("DB-SO-13 historial y resolver conservan SO inactivo", async () => { const history = ListGlobalApplicationHistoryResponseSchema.parse( await (await admin.request(`${root}/${productId}/history`)).json() ); assert.equal(history.items.length, 2); const resolved = ResolveApplicationReferencesResponseSchema.parse( await ( await ordinary.request( `/resource-application-references/${company}/resolve`, { purpose: "software-installation", ids: [productId, localOs.id] }, v3 ) ).json() ); assert.ok(resolved.items[0]?.status === "resolved" && !resolved.items[0].reference.active); assert.equal( ( await database.query("SELECT id FROM resource_revisions WHERE resourceId=:id", { replacements: { id: productId }, type: QueryTypes.SELECT, }) ).length, 2 ); results.push({ id: "DB-SO-13", result: "pass", http: [200, 200] }); }); await t.test("DB-SO-14 versión instalada genera revisión sin cambiar producto", async () => { assert.equal( ( await admin.request(`${root}/${productId}/status`, { active: true, reviewedIdentity: identity, }) ).status, 200 ); const current = await readInstallation(company, assetA, productId); assert.equal( ( await ordinary.request( `${path}/${current.id}/update`, { values: { ...current.values, version: "24H2" } }, v3 ) ).status, 200 ); const changed = await readInstallation(company, assetA, productId); assert.equal(changed.id, current.id); assert.equal(changed.values.resourceId, productId); assert.equal(changed.history.length, current.history.length + 1); assert.equal( ( await database.query( "SELECT id FROM software_installation_revisions WHERE configurationId=:id", { replacements: { id: current.id }, type: QueryTypes.SELECT } ) ).length, changed.history.length ); results.push({ id: "DB-SO-14", result: "pass", http: [200, 200] }); }); await t.test("DB-SO-15 destino de instalación inmutable", async () => { const current = await readInstallation(company, assetA, productId); const before = await database.query( "SELECT * FROM software_installation_revisions WHERE configurationId=:id ORDER BY position", { replacements: { id: current.id }, type: QueryTypes.SELECT } ); const response = await ordinary.request( `${path}/${current.id}/update`, { values: { ...current.values, resourceId: localOs.id } }, v3 ); assert.equal(response.status, 409); assert.equal(ErrorSchema.parse(await response.json()).code, "SOFTWARE_TARGET_IMMUTABLE"); assert.deepEqual( await database.query( "SELECT * FROM software_installation_revisions WHERE configurationId=:id ORDER BY position", { replacements: { id: current.id }, type: QueryTypes.SELECT } ), before ); results.push({ id: "DB-SO-15", result: "pass", http: [409] }); }); await t.test("DB-SO-16 retirada de instalación de producto inactivo", async () => { assert.equal( (await admin.request(`${root}/${productId}/status`, { active: false })).status, 200 ); const current = await readInstallation(company, assetA, productId); for (const state of ["removal_pending", "removed"]) { assert.equal( ( await ordinary.request( `${path}/${current.id}/update`, { values: { ...current.values, state } }, v3 ) ).status, 200 ); } const removed = await readInstallation(company, assetA, productId); assert.equal(removed.id, current.id); assert.equal(removed.values.state, "removed"); assert.equal(removed.history.length, current.history.length + 2); results.push({ id: "DB-SO-16", result: "pass", http: [200, 200] }); }); await t.test("DB-SO-17 replay de creación no duplica ni reactiva", async () => { const before = await resourceSqlSnapshot(database, productId); assert.equal( ( await admin.request(root, { creationId: productId, values: values(identity), reviewedIdentity: identity, }) ).status, 200 ); assert.deepEqual(await resourceSqlSnapshot(database, productId), before); results.push({ id: "DB-SO-17", result: "pass", http: [200] }); }); await t.test("DB-SO-18 activar/desactivar preserva UUID, historial y referencias", async () => { const before = await adminProduct(); assert.equal( ( await admin.request(`${root}/${productId}/status`, { active: true, reviewedIdentity: identity, }) ).status, 200 ); assert.equal( (await admin.request(`${root}/${productId}/status`, { active: false })).status, 200 ); const after = await adminProduct(); assert.equal(after.id, before.id); const history = ListGlobalApplicationHistoryResponseSchema.parse( await (await admin.request(`${root}/${productId}/history`)).json() ); assert.equal(history.items.length, 6); assert.equal((await installationRows(assetA2, productId)).length, 1); results.push({ id: "DB-SO-18", result: "pass", http: [200, 200] }); }); await t.test("DB-SO-19 solo stock: copia y otros tipos no adquieren globalidad", async () => { const os = template("os"); assert.ok(os.values.kind === "template"); const response = await admin.request("/resource-catalog/global", { values: { ...os.values, name: "Copia de SO para cierre" }, }); assert.equal(response.status, 201); const copy = SaveDefinitionResponseSchema.parse(await response.json()); const ids = [ copy.savedId, ...["software_license", "support", "shared_folder", "access", "certificate"].map( (key) => template(key).id ), ]; for (const templateId of ids) { for (const endpoint of [root, "/resource-global-applications"]) { const id = uuid(); assert.equal( ( await admin.request(endpoint, { creationId: id, values: { name: "Global no autorizado", templateId, active: false, notes: "", characteristics: {}, }, }) ).status, 409 ); assert.equal((await rootRows(id)).length, 0); } } results.push({ id: "DB-SO-19", result: "pass: stock accepted; copied/support/SMB/access/certificate/license rejected", http: [201, 409], }); }); await t.test( "DB-SO-20 particular anterior incompleto sigue siendo editable y local", async () => { const inventory = InventoryResponseSchema.parse( await (await ordinary.request(`/resources/${company}`)).json() ); const current = inventory.resources.find((item) => item.id === legacy.resourceId); assert.ok(current); assert.equal( ( await ordinary.request(`/resources/${company}/${current.id}/update`, { values: { ...current.values, notes: "Corrección posterior legítima" }, }) ).status, 200 ); const after = InventoryResponseSchema.parse( await (await ordinary.request(`/resources/${company}`)).json() ).resources.find((item) => item.id === current.id); assert.ok(after); assert.deepEqual(after.values.characteristics, current.values.characteristics); assert.equal(after.history.length, 3); assert.equal(after.companyId, company); results.push({ id: "DB-SO-20", result: "pass", http: [200] }); } ); await t.test( "C1 — equivalentes concurrentes esperan el lock de catálogo y solo uno persiste", async () => { const item = { ...identity, productVersion: "30" }; const firstId = uuid(); const secondId = uuid(); const arrived = latch(); const release = latch(); database.addHook("afterQuery", "closure-duplicate", async (_options, query) => { const sql: unknown = Reflect.get(query, "sql"); if ( typeof sql === "string" && sql.startsWith("INSERT INTO `resources`") && sql.includes(firstId) ) { arrived.release(); await release.promise; } }); const first = admin.request(root, { creationId: firstId, values: values(item), reviewedIdentity: item, }); let second: Promise | undefined; try { await waitBarrier(arrived.promise); second = admin.request(root, { creationId: secondId, values: values(item), reviewedIdentity: item, }); await waitCatalogLock(process.env.METIS_RESOURCE_SCOPE_DISPOSABLE ?? ""); release.release(); assert.equal((await first).status, 201); assert.equal((await second).status, 409); assert.equal((await rootRows(firstId)).length, 1); assert.equal((await rootRows(secondId)).length, 0); results.push({ id: "C1", result: "pass: observed InnoDB catalog wait; one equivalent persisted", http: [201, 409], }); } finally { release.release(); database.removeHook("afterQuery", "closure-duplicate"); await Promise.allSettled([first, ...(second ? [second] : [])]); } } ); await t.test( "C2 — desactivación confirma primero y asociación revalida después de esperar", async () => { const target = await createProduct("31"); const arrived = latch(); const release = latch(); database.addHook("afterQuery", "closure-disable-first", 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(`${root}/${target.id}/status`, { active: false }); let installing: Promise | undefined; try { await waitBarrier(arrived.promise); installing = ordinary.request( path, { values: { ...input, assetId: raceAsset, resourceId: target.id } }, v3 ); await waitCatalogLock(process.env.METIS_RESOURCE_SCOPE_DISPOSABLE ?? ""); release.release(); assert.equal((await disabling).status, 200); assert.equal((await installing).status, 409); assert.equal((await installationRows(raceAsset, target.id)).length, 0); results.push({ id: "C2", result: "pass: deactivation first, waiting installation rejected", http: [200, 409], }); } finally { release.release(); database.removeHook("afterQuery", "closure-disable-first"); await Promise.allSettled([disabling, ...(installing ? [installing] : [])]); } } ); await t.test( "C3 — identidad, instalación, metadatos y desactivación concurrentes", async () => { assert.equal( ( await admin.request(`${root}/${productId}/status`, { active: true, reviewedIdentity: identity, }) ).status, 200 ); const current = await adminProduct(); const beforeHistory = await database.query( "SELECT id FROM resource_revisions WHERE resourceId=:id", { replacements: { id: productId }, type: QueryTypes.SELECT } ); const changed = { ...identity, productVersion: "99" }; const responses = await Promise.all([ admin.request(`${root}/${productId}/update`, { values: values(changed), reviewedIdentity: changed, }), ordinary.request(path, { values: { ...input, assetId: raceAsset } }, v3), admin.request(`${root}/${productId}/update`, { values: { ...current.values, notes: "Metadatos concurrentes" }, reviewedIdentity: identity, expectedRevisionId: current.revisionId, }), admin.request(`${root}/${productId}/status`, { active: false, expectedRevisionId: current.revisionId, }), ]); const identityResult = responses[0]; const installationResult = responses[1]; const metadataResult = responses[2]; const statusResult = responses[3]; assert.ok(identityResult && installationResult && metadataResult && statusResult); assert.equal(identityResult.status, 409); assert.equal(ErrorSchema.parse(await identityResult.json()).code, "OS_IDENTITY_FIXED"); assert.ok([201, 409].includes(installationResult.status)); assert.deepEqual([metadataResult.status, statusResult.status].sort(), [200, 409]); const publicProduct = GetGlobalOperatingSystemResponseSchema.parse( await (await ordinary.request(`${root}/${productId}`)).json() ).resource; assert.equal(publicProduct.productVersion, "11"); assert.equal(publicProduct.name, "Windows 11 Pro"); assert.equal( ( await database.query("SELECT id FROM resource_revisions WHERE resourceId=:id", { replacements: { id: productId }, type: QueryTypes.SELECT, }) ).length, beforeHistory.length + 1 ); assert.equal( (await installationRows(raceAsset, productId)).length, installationResult.status === 201 ? 1 : 0 ); results.push({ id: "C3", result: "pass: commercial identity fixed; exactly one optimistic admin update", http: responses.map((response) => response.status), }); } ); await t.test( "C4a — fallo tras escribir raíz global revierte raíz, características y revisiones", async () => { const id = uuid(); const item = { ...identity, productVersion: "40" }; let faultTriggered = false; database.addHook("afterQuery", "closure-fail-resource", async (_options, query) => { const sql: unknown = Reflect.get(query, "sql"); if ( typeof sql === "string" && sql.startsWith("INSERT INTO `resource_revisions`") && sql.includes(id) ) { faultTriggered = true; throw new Error("CONTROLLED_CLOSURE_RESOURCE_FAILURE"); } }); try { const response = await admin.request(root, { creationId: id, values: values(item), reviewedIdentity: item, }); assert.equal(response.status, 500); assert.equal(faultTriggered, true); const body = await response.text(); assert.ok( !(body.includes("CONTROLLED_") || body.includes("INSERT") || body.includes("stack")) ); const snapshot = await resourceSqlSnapshot(database, id); assert.ok( Object.values(snapshot).every((rows) => Array.isArray(rows) && rows.length === 0) ); } finally { database.removeHook("afterQuery", "closure-fail-resource"); } assert.equal( ( await admin.request(root, { creationId: id, values: values(item), reviewedIdentity: item, }) ).status, 201 ); results.push({ id: "C4a", result: "pass: rollback of root/values/revisions, same creation UUID retry succeeds", http: [500, 201], }); } ); await t.test( "C4b — fallo en revisión de instalación revierte instalación completa", async () => { const target = await createProduct("41"); const previousProduct = await resourceSqlSnapshot(database, target.id); let faultTriggered = false; database.addHook("afterQuery", "closure-fail-installation", async (_options, query) => { const sql: unknown = Reflect.get(query, "sql"); if ( typeof sql === "string" && sql.startsWith("INSERT INTO software_installation_revisions") && sql.includes(target.id) ) { faultTriggered = true; throw new Error("CONTROLLED_CLOSURE_INSTALLATION_FAILURE"); } }); try { assert.equal( ( await ordinary.request( path, { values: { ...input, assetId: raceAsset, resourceId: target.id } }, v3 ) ).status, 500 ); assert.equal((await installationRows(raceAsset, target.id)).length, 0); assert.equal(faultTriggered, true); assert.equal( ( await database.query( "SELECT id FROM software_installation_revisions WHERE resourceId=:id", { replacements: { id: target.id }, type: QueryTypes.SELECT } ) ).length, 0 ); assert.deepEqual(await resourceSqlSnapshot(database, target.id), previousProduct); } finally { database.removeHook("afterQuery", "closure-fail-installation"); } results.push({ id: "C4b", result: "pass: no partial installation or orphan revision; Resource unchanged", http: [500], }); } ); await t.test("D — contratos v2/v3, propósito, capabilities y autorización", async () => { const capabilities2 = await ( await ordinary.request(`/resource-application-candidates/${company}/capabilities`) ).json(); assert.deepEqual(Object.keys(z.record(z.string(), z.unknown()).parse(capabilities2)).sort(), [ "profileAssociation", "profileSelection", "softwareAssociation", "softwareSelection", ]); const capabilities3 = ApplicationReferenceCapabilitiesSchema.parse( await ( await ordinary.request( `/resource-application-candidates/${company}/capabilities`, undefined, v3 ) ).json() ); assert.equal(capabilities3.operatingSystemAssociation, true); assert.equal((await ordinary.request(path)).status, 409); assert.equal((await ordinary.request(path, undefined, { contract: null })).status, 409); assert.equal((await ordinary.request(path, undefined, { contract: "4" })).status, 400); assert.equal((await ordinary.request(`${root}/${productId}/admin`)).status, 403); assert.equal( ( await ordinary.request(root, { creationId: uuid(), values: values(identity), reviewedIdentity: identity, }) ).status, 403 ); assert.equal( ( await admin.request( `${root}/${productId}/status`, { active: false }, { token: "invalid" } ) ).status, 403 ); const osList = ListGlobalOperatingSystemsResponseSchema.parse( await (await ordinary.request(root)).json() ); assert.ok(osList.items.every((item) => item.kind === "os" && item.id !== applicationId)); results.push({ id: "D", result: "pass: exact capabilities v2, v3 OS, incompatible reads and unauthorized writes rejected", http: [200, 409, 400, 403], }); }); await t.test("F — cerrar gates conserva lectura, historial y retirada legítima", async () => { const current = await readInstallation(company, assetA2, productId); await new Promise((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: "false", RESOURCE_GLOBAL_SOFTWARE_CANDIDATES_ENABLED: "false", RESOURCE_GLOBAL_PROFILE_ENABLED: "false", RESOURCE_GLOBAL_OS_CATALOG_ENABLED: "false", RESOURCE_GLOBAL_OS_SOFTWARE_ENABLED: "false", }) ); server = createApp(() => undefined, runtime).listen(0, "127.0.0.1"); await once(server, "listening"); const next = server.address(); assert.ok(next && typeof next !== "string"); const manager = await scopeApiClient(`http://127.0.0.1:${next.port}`); const client = await scopeApiClient(`http://127.0.0.1:${next.port}`); assert.equal( (await manager.request("/auth/login", { email: "closure-admin@example.invalid", password })) .status, 200 ); await manager.refresh(); assert.equal( (await client.request("/auth/login", { email: "closure-user@example.invalid", password })) .status, 200 ); await client.refresh(); assert.equal((await client.request(`${root}/${productId}`)).status, 200); assert.equal((await manager.request(`${root}/${productId}/history`)).status, 200); assert.equal((await client.request(path, undefined, v3)).status, 200); assert.equal( ( await manager.request(root, { creationId: uuid(), values: values({ ...identity, productVersion: "50" }), reviewedIdentity: { ...identity, productVersion: "50" }, }) ).status, 409 ); for (const state of ["removal_pending", "removed"]) { assert.equal( ( await client.request( `${path}/${current.id}/update`, { values: { ...current.values, state } }, v3 ) ).status, 200 ); } results.push({ id: "F", result: "pass: all six gates closed; read/history/removal preserved", http: [200, 409], }); }); const orphans = await database.query( "SELECT r.id FROM software_installation_revisions r LEFT JOIN software_installations s ON s.id=r.configurationId WHERE s.id IS NULL UNION ALL SELECT r.id FROM resource_revisions r LEFT JOIN resources s ON s.id=r.resourceId WHERE s.id IS NULL", { type: QueryTypes.SELECT } ); assert.equal(orphans.length, 0); await writeScopeEvidence("closure-results.json", { results, databaseCases: results.filter((item) => item.id.startsWith("DB-SO-")).length, companies: 15, principal: { people: 50, assets: 50, workstations: 50, profiles: 50, resourcesAtLeast: 53 }, orphanRevisions: orphans.length, semanticSqlUnique: false, productionActivated: false, }); } finally { await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())) ); runtime.stop(); await database.close(); } });