import assert from "node:assert/strict"; import { InventoryResponseSchema } from "@metis/assets/common"; import type { SoftwareInstallationInput } from "@metis/companies/common"; import { SoftwareInstallationResponseSchema, SoftwareInstallationsResponseSchema, } from "@metis/companies/common"; import { CatalogResponseSchema, ResourceResponseSchema } from "@metis/resources/common"; import type { Sequelize } from "sequelize"; import { z } from "zod/v4"; import { migrateSoftwareInstallations } from "../../../modules/companies/src/api/infrastructure/persistence/software-installation-migration"; type Request = (path: string, body?: unknown, origin?: string, token?: string) => Promise; export async function verifySoftwareInstallations( request: Request, companyId: string, otherId: string, database: Sequelize ) { await migrateSoftwareInstallations(database); const inventory = InventoryResponseSchema.parse( await (await request(`/assets/${companyId}`)).json() ); const host = inventory.assets.find((asset) => asset.values.status !== "retired"); const otherHost = InventoryResponseSchema.parse( await (await request(`/assets/${otherId}`)).json() ).assets[0]; const catalog = CatalogResponseSchema.parse( await (await request(`/resource-catalog/${companyId}`)).json() ); const template = catalog.items.find((item) => item.builtinKey === "template:os"); const application = catalog.items.find((item) => item.builtinKey === "template:application"); const licenseTemplate = catalog.items.find( (item) => item.builtinKey === "template:software_license" ); const certificate = catalog.items.find((item) => item.builtinKey === "template:certificate"); assert.ok(host && otherHost && template && application && certificate && licenseTemplate); assert.ok(template.values.kind === "template" && template.values.usage === "os"); assert.ok( licenseTemplate.values.kind === "template" && licenseTemplate.values.usage === "license" ); const addResource = async (templateId: string, name: string) => { const response = await request(`/resources/${companyId}`, { values: { name, templateId, active: true, notes: "", characteristics: {} }, }); assert.equal(response.status, 201, await response.clone().text()); return ResourceResponseSchema.parse(await response.json()).resource; }; const license = await addResource(licenseTemplate.id, "Licencia equipo test"); const os = await addResource(template.id, "Windows 11 Pro test"); const app = await addResource(application.id, "Office equipo test"); const cert = await addResource(certificate.id, "Certificado no instalable como software"); const path = `/software-installations/${companyId}`; const values: SoftwareInstallationInput = { assetId: host.id, resourceId: os.id, licenseResourceId: null, version: "24H2", state: "configured", notes: null, }; const foreign = await request(path, { values: { ...values, assetId: otherHost.id } }); assert.equal(foreign.status, 404); assert.equal((await request(path, { values: { ...values, resourceId: cert.id } })).status, 404); assert.equal( (await request(path, { values: { ...values, licenseResourceId: cert.id } })).status, 404 ); const foreignLicenseResponse = await request(`/resources/${otherId}`, { values: { name: "Licencia ajena", templateId: licenseTemplate.id, active: true, notes: "", characteristics: {}, }, }); assert.equal(foreignLicenseResponse.status, 201); const foreignLicense = ResourceResponseSchema.parse(await foreignLicenseResponse.json()).resource; assert.equal( (await request(path, { values: { ...values, licenseResourceId: foreignLicense.id } })).status, 404 ); assert.equal((await request(path, { values: { ...values, profileId: host.id } })).status, 400); assert.equal((await request(path, { values }, "https://other.example")).status, 403); assert.equal((await request(path, { values }, undefined, "a".repeat(64))).status, 403); const responses = await Promise.all([request(path, { values }), request(path, { values })]); assert.deepEqual(responses.map((response) => response.status).sort(), [201, 409]); const success = responses.find((response) => response.status === 201); assert.ok(success); const created = SoftwareInstallationResponseSchema.parse(await success.json()).configuration; const save = async (data: typeof values) => { const response = await request(`${path}/${created.id}/update`, { values: data }); assert.equal(response.status, 200, await response.clone().text()); return SoftwareInstallationResponseSchema.parse(await response.json()).configuration; }; assert.deepEqual(await save(values), created); const updated = await save({ ...values, version: "25H2" }); assert.equal(updated.history.length, 2); assert.equal(updated.history[0]?.values.version, "24H2"); assert.equal(updated.history[1]?.values.version, "25H2"); const linkedValues = { ...values, version: "25H2", licenseResourceId: license.id }; const linked = await save(linkedValues); assert.equal(linked.history.length, 3); assert.equal(linked.history[0]?.values.licenseResourceId, null); assert.equal(linked.history[2]?.values.licenseResourceId, license.id); assert.deepEqual(await save(linkedValues), linked); const deactivateLicense = await request(`/resources/${companyId}/${license.id}/update`, { values: { ...license.values, active: false }, }); assert.equal(deactivateLicense.status, 200); const licenseReview = SoftwareInstallationsResponseSchema.parse( await (await request(path)).json() ); assert.equal( licenseReview.configurations.find((item) => item.id === created.id)?.needsReview, true ); assert.equal( ( await request(`${path}/${created.id}/update`, { values: { ...linkedValues, version: "26H2" }, }) ).status, 409 ); await save({ ...linkedValues, notes: "Revisar licencia" }); const detached = await save({ ...values, version: "25H2" }); assert.equal(detached.needsReview, false); assert.equal(detached.values.licenseResourceId, null); assert.equal(detached.history[2]?.values.licenseResourceId, license.id); assert.equal((await request(path, { values: { ...values, resourceId: app.id } })).status, 201); assert.equal( (await request(`/software-installations/${otherId}/${created.id}/update`, { values })).status, 404 ); assert.equal( SoftwareInstallationsResponseSchema.parse( await (await request(`/software-installations/${otherId}`)).json() ).configurations.length, 0 ); const expiryField = catalog.items.find((item) => item.builtinKey === "field:expires"); assert.ok(expiryField); const expiredResponse = await request(`/resources/${companyId}`, { values: { name: "Licencia caducada", templateId: licenseTemplate.id, active: true, notes: "", characteristics: { [expiryField.id]: "2000-01-01" }, }, }); assert.equal(expiredResponse.status, 201); const expired = ResourceResponseSchema.parse(await expiredResponse.json()).resource; const rejectedExpiry = await request(`${path}/${created.id}/update`, { values: { ...values, version: "25H2", licenseResourceId: expired.id }, }); assert.equal(rejectedExpiry.status, 409); assert.equal( z.object({ code: z.string() }).parse(await rejectedExpiry.json()).code, "SOFTWARE_REFERENCE_UNAVAILABLE" ); await migrateSoftwareInstallations(database); const persisted = SoftwareInstallationsResponseSchema.parse( await (await request(path)).json() ).configurations.find((item) => item.id === created.id); assert.deepEqual(persisted, detached); const deactivate = await request(`/resources/${companyId}/${os.id}/update`, { values: { ...os.values, active: false }, }); assert.equal(deactivate.status, 200); const listed = SoftwareInstallationsResponseSchema.parse(await (await request(path)).json()); assert.equal(listed.configurations.find((item) => item.id === created.id)?.needsReview, true); await save({ ...values, version: "25H2", state: "removal_pending" }); await save({ ...values, version: "25H2", state: "removed" }); assert.equal((await request(`${path}/${created.id}/delete`, {})).status, 200); assert.equal((await request(`${path}/${created.id}/delete`, {})).status, 404); }