metis-desk/apps/server/src/global-operating-systems-closure.integration.test.ts

1192 lines
42 KiB
TypeScript
Raw Normal View History

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<Response> | 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<Response> | 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<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: "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<void>((resolve, reject) =>
server.close((error) => (error ? reject(error) : resolve()))
);
runtime.stop();
await database.close();
}
});