import assert from "node:assert/strict"; import { randomUUID } from "node:crypto"; import { InventoryResponseSchema as AssetsSchema } from "@metis/assets/common"; import { type CertificateConfigurationInput, CertificateConfigurationResponseSchema, CertificateConfigurationsResponseSchema, 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 verifyCertificates( request: Request, companyId: string, otherId: string, database: Sequelize ) { const personResponse = await request(`/companies/${companyId}/people`, { name: "Persona certificados", 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 certificados", 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:certificate"); 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, "Certificado empresa prueba"); const certificateTemplate = catalog.items.find( (item) => item.builtinKey === "template:application" ); assert.ok(certificateTemplate); const certificate = await add(certificateTemplate.id, "Aplicación ajena al certificado"); assert.ok(template.values.kind === "template"); const customValues = { ...template.values, name: "Certificados personalizados", usage: "certificate" 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, "Certificado colaboración prueba"); const immutable = await request(`/resource-catalog/${companyId}/${custom.id}/update`, { values: { ...customValues, usage: "application" }, }); assert.equal(immutable.status, 409); const path = `/certificate-configurations/${companyId}`; const input: CertificateConfigurationInput = { profileId: profile.id, resourceId: app.id, location: null, state: "pending", notes: null, }; const save = async (values: CertificateConfigurationInput, id?: string) => { const response = await request(path + (id ? `/${id}/update` : ""), { values }); assert.equal(response.status, id ? 200 : 201); return CertificateConfigurationResponseSchema.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() }, "CERTIFICATE_REFERENCE_NOT_FOUND", undefined, 404 ); await error( { ...input, resourceId: certificate.id }, "CERTIFICATE_REFERENCE_NOT_FOUND", undefined, 404 ); await error({ ...input, privateKey: "forbidden" }, "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 = CertificateConfigurationResponseSchema.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", location: "Windows del usuario", notes: "Necesario para firmar", }, created.id ); assert.equal(configured.history.length, 2); assert.equal(configured.history[0]?.values.location, null); await save({ ...input, resourceId: customApp.id, location: "Firefox", state: "configured" }); await error({ ...input, resourceId: customApp.id }, "CERTIFICATE_TARGET_IMMUTABLE", created.id); await error({ ...input, state: "removed" }, "INVALID_CERTIFICATE_TRANSITION", created.id); await database.query("UPDATE certificate_configurations SET currentKey=NULL WHERE id=:id", { replacements: { id: created.id }, }); try { assert.equal((await request(path)).status, 500); } finally { await database.query("UPDATE certificate_configurations SET currentKey=:key WHERE id=:id", { replacements: { id: created.id, key: `${profile.id}:${app.id}` }, }); } const expiry = catalog.items.find((item) => item.builtinKey === "field:expires"); assert.ok(expiry); const expiredResource = await request(`/resources/${companyId}/${app.id}/update`, { values: { ...app.values, characteristics: { ...app.values.characteristics, [expiry.id]: "2000-01-01" }, }, }); assert.equal(expiredResource.status, 200); const list = CertificateConfigurationsResponseSchema.parse( await (await request(path)).json() ).configurations; assert.equal(list.find((item) => item.id === created.id)?.needsReview, true); assert.equal(list.find((item) => item.id === created.id)?.expired, true); assert.equal(list.find((item) => item.id === created.id)?.expiresOn, "2000-01-01"); await error( { ...configured.values, location: "Otro almacén" }, "CERTIFICATE_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_CERTIFICATE_TRANSITION", created.id); assert.equal( ( await request(`/certificate-configurations/${otherId}/${created.id}/update`, { values: input, }) ).status, 404 ); assert.equal( CertificateConfigurationsResponseSchema.parse( await (await request(`/certificate-configurations/${otherId}`)).json() ).configurations.length, 0 ); // Un recurso compartido por perfiles mantiene configuraciones independientes. const secondPersonResponse = await request(`/companies/${companyId}/people`, { name: "Otra persona certificados", 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 certificados", 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, location: "Tarjeta criptográfica", }); const archive = await request(`/work-profiles/${companyId}/${secondId}/update`, { values: { ...profileValues, name: "Otro perfil certificados", personId: secondPerson.id, active: false, }, }); assert.equal(archive.status, 200); const reviewed = CertificateConfigurationsResponseSchema.parse( await (await request(path)).json() ).configurations; assert.equal(reviewed.find((item) => item.values.profileId === secondId)?.needsReview, true); }