import assert from "node:assert/strict"; import { randomUUID } from "node:crypto"; import { InventoryResponseSchema as AssetsSchema } from "@metis/assets/common"; import type { ApplicationConfigurationInput } from "@metis/companies/common"; import { ApplicationConfigurationResponseSchema, ApplicationConfigurationsResponseSchema, CompanyResponseSchema, WorkProfileResponseSchema, } from "@metis/companies/common"; import { CatalogResponseSchema, ResourceResponseSchema } from "@metis/resources/common"; import type { Sequelize } from "sequelize"; import { z } from "zod/v4"; type Request = (path: string, body?: unknown, origin?: string, token?: string) => Promise; export async function verifyApplications( request: Request, companyId: string, otherId: string, database: Sequelize ) { const personResponse = await request(`/companies/${companyId}/people`, { name: "Persona aplicaciones", position: null, email: null, phone: null, active: true, isContact: false, }); assert.equal(personResponse.status, 201); const person = CompanyResponseSchema.parse(await personResponse.json()).company.people.at(-1); assert.ok(person); const assets = AssetsSchema.parse(await (await request(`/assets/${companyId}`)).json()); const host = assets.assets.find((item) => item.values.status !== "retired"); assert.ok(host); const profileValues = { name: "Perfil aplicaciones", personId: person.id, assetId: host.id, accountId: null, active: true, notes: null, }; const profileResponse = await request(`/work-profiles/${companyId}`, { values: profileValues }); assert.equal(profileResponse.status, 201); const profile = WorkProfileResponseSchema.parse(await profileResponse.json()).profile; const catalog = CatalogResponseSchema.parse( await (await request(`/resource-catalog/${companyId}`)).json() ); const template = catalog.items.find((item) => item.builtinKey === "template:application"); assert.ok(template); assert.equal(template.values.kind, "template"); const add = async (templateId: string, name: string) => { const response = await request(`/resources/${companyId}`, { values: { name, templateId, active: true, notes: "", characteristics: {} }, }); assert.equal(response.status, 201); return ResourceResponseSchema.parse(await response.json()).resource; }; const app = await add(template.id, "Thunderbird prueba"); const certificateTemplate = catalog.items.find( (item) => item.builtinKey === "template:certificate" ); assert.ok(certificateTemplate); const certificate = await add(certificateTemplate.id, "Certificado prueba aplicaciones"); assert.ok(template.values.kind === "template"); const customValues = { ...template.values, name: "Software personalizado", usage: "application" as const, }; const customResponse = await request(`/resource-catalog/${companyId}`, { values: customValues }); assert.equal(customResponse.status, 201); const custom = CatalogResponseSchema.parse( await (await request(`/resource-catalog/${companyId}`)).json() ).items.find((item) => item.values.name === customValues.name); assert.ok(custom); const customApp = await add(custom.id, "ERP navegador prueba"); const immutable = await request(`/resource-catalog/${companyId}/${custom.id}/update`, { values: { ...customValues, usage: "certificate" }, }); assert.equal(immutable.status, 409); const path = `/application-configurations/${companyId}`; const input: ApplicationConfigurationInput = { profileId: profile.id, resourceId: app.id, delivery: "installed", version: null, state: "pending", notes: null, }; const save = async (values: ApplicationConfigurationInput, id?: string) => { const response = await request(path + (id ? `/${id}/update` : ""), { values }); assert.equal(response.status, id ? 200 : 201); return ApplicationConfigurationResponseSchema.parse(await response.json()).configuration; }; const error = async (values: unknown, code: string, id?: string, status = 409) => { const response = await request(path + (id ? `/${id}/update` : ""), { values }); assert.equal(response.status, status); assert.equal(z.object({ code: z.string() }).parse(await response.json()).code, code); }; await error({ ...input, actorId: person.id }, "INVALID_REQUEST", undefined, 400); await error( { ...input, resourceId: randomUUID() }, "APPLICATION_REFERENCE_NOT_FOUND", undefined, 404 ); await error( { ...input, resourceId: certificate.id }, "APPLICATION_REFERENCE_NOT_FOUND", undefined, 404 ); await error({ ...input, delivery: "web", version: "1" }, "INVALID_REQUEST", undefined, 400); assert.equal((await request(path, { values: input }, "https://other.example")).status, 403); assert.equal((await request(path, { values: input }, undefined, "a".repeat(64))).status, 403); const attempts = await Promise.all([ request(path, { values: input }), request(path, { values: input }), ]); assert.deepEqual(attempts.map((r) => r.status).sort(), [201, 409]); const response = attempts.find((r) => r.status === 201); assert.ok(response); const created = ApplicationConfigurationResponseSchema.parse(await response.json()).configuration; assert.equal(created.id[14], "7"); assert.equal(created.history[0]?.id[14], "7"); assert.deepEqual(await save(input, created.id), created); const configured = await save( { ...input, state: "configured", version: "128", notes: "Solicitado por Ana" }, created.id ); assert.equal(configured.history.length, 2); assert.equal(configured.history[0]?.values.version, null); await save({ ...input, resourceId: customApp.id, delivery: "web", state: "configured" }); await error({ ...input, resourceId: customApp.id }, "APPLICATION_TARGET_IMMUTABLE", created.id); await error({ ...input, state: "removed" }, "INVALID_APPLICATION_TRANSITION", created.id); await database.query("UPDATE application_configurations SET currentKey=NULL WHERE id=:id", { replacements: { id: created.id }, }); try { assert.equal((await request(path)).status, 500); } finally { await database.query("UPDATE application_configurations SET currentKey=:key WHERE id=:id", { replacements: { id: created.id, key: `${profile.id}:${app.id}` }, }); } const deactivate = await request(`/resources/${companyId}/${app.id}/update`, { values: { ...app.values, active: false }, }); assert.equal(deactivate.status, 200); const list = ApplicationConfigurationsResponseSchema.parse( await (await request(path)).json() ).configurations; assert.equal(list.find((item) => item.id === created.id)?.needsReview, true); await error( { ...configured.values, version: "129" }, "APPLICATION_REFERENCE_UNAVAILABLE", created.id ); const noted = await save({ ...configured.values, notes: "Retirar al salir" }, created.id); assert.equal(noted.history.length, 3); const removal = await save({ ...noted.values, state: "removal_pending" }, created.id); const removed = await save({ ...removal.values, state: "removed" }, created.id); assert.equal(removed.needsReview, false); await error({ ...removed.values, notes: "Otra" }, "INVALID_APPLICATION_TRANSITION", created.id); assert.equal( ( await request(`/application-configurations/${otherId}/${created.id}/update`, { values: input, }) ).status, 404 ); assert.equal( ApplicationConfigurationsResponseSchema.parse( await (await request(`/application-configurations/${otherId}`)).json() ).configurations.length, 0 ); // Un recurso compartido por perfiles mantiene configuraciones independientes. const secondPersonResponse = await request(`/companies/${companyId}/people`, { name: "Otra persona aplicaciones", position: null, email: null, phone: null, active: true, isContact: false, }); const secondPerson = CompanyResponseSchema.parse( await secondPersonResponse.json() ).company.people.at(-1); assert.ok(secondPerson); const secondProfile = await request(`/work-profiles/${companyId}`, { values: { ...profileValues, name: "Otro perfil aplicaciones", personId: secondPerson.id }, }); assert.equal(secondProfile.status, 201); const secondId = WorkProfileResponseSchema.parse(await secondProfile.json()).profile.id; await save({ ...input, profileId: secondId, resourceId: customApp.id, delivery: "web" }); const archive = await request(`/work-profiles/${companyId}/${secondId}/update`, { values: { ...profileValues, name: "Otro perfil aplicaciones", personId: secondPerson.id, active: false, }, }); assert.equal(archive.status, 200); const reviewed = ApplicationConfigurationsResponseSchema.parse( await (await request(path)).json() ).configurations; assert.equal(reviewed.find((item) => item.values.profileId === secondId)?.needsReview, true); }