import assert from "node:assert/strict"; import { InventoryResponseSchema } from "@metis/assets/common"; import { type AccessFieldsDTO, AccessResponseSchema, CompanyResponseSchema, type MailConfigurationInput, MailConfigurationResponseSchema, MailConfigurationsResponseSchema, } from "@metis/companies/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 verifyMail( request: Request, companyId: string, otherId: string, database: Sequelize ) { const path = `/mail-configurations/${companyId}`; const accessPath = `/access/${companyId}`; const company = CompanyResponseSchema.parse( await (await request(`/companies/${companyId}`)).json() ).company; const other = CompanyResponseSchema.parse( await (await request(`/companies/${otherId}`)).json() ).company; const foreignAsset = InventoryResponseSchema.parse( await (await request(`/assets/${otherId}`)).json() ).assets[0]; assert.ok(foreignAsset); const inventory = InventoryResponseSchema.parse( await (await request(`/assets/${companyId}`)).json() ); const assets = inventory.assets; for (const item of assets.slice(0, 2)) { if (item.values.status === "retired") { const response = await request(`/assets/${companyId}/${item.id}/update`, { values: { ...item.values, status: "available" }, }); assert.equal(response.status, 200, await response.clone().text()); } } const first = assets[0]; const second = assets[1]; const foreignPerson = other.people[0]; assert.ok(first && second && foreignPerson); const personFields = { name: "Persona correo", position: null, email: null, phone: null, active: true, isContact: false, }; const peopleResponse = await request(`/companies/${companyId}/people`, personFields); assert.equal(peopleResponse.status, 201); const person = CompanyResponseSchema.parse(await peopleResponse.json()).company.people.at(-1); assert.ok(person); const otherPerson = company.people.find((item) => item.active); assert.ok(otherPerson); const createAccess = async (values: AccessFieldsDTO) => { const response = await request(accessPath, { values }); assert.equal(response.status, 201, await response.clone().text()); const item = AccessResponseSchema.parse(await response.json()).items.at(-1); assert.ok(item); return item; }; const system = await createAccess({ kind: "system", name: "Correo pruebas", active: true }); const accountFields = { kind: "account" as const, systemId: system.id, environmentId: null, usage: "shared" as const, active: true, expiresOn: null, notes: null, }; const account = await createAccess({ ...accountFields, login: "ventas@example.test" }); const anotherAccount = await createAccess({ ...accountFields, login: "ana@example.test", usage: "personal", }); const today = new Date().toISOString().slice(0, 10); const authorization = { kind: "account_person" as const, personId: person.id, startDate: today, endDate: null, configuration: "pending" as const, }; const relation = await createAccess({ ...authorization, accountId: account.id }); await createAccess({ ...authorization, accountId: anotherAccount.id }); await createAccess({ ...authorization, personId: otherPerson.id, accountId: account.id }); const input: MailConfigurationInput = { personId: person.id, accountId: account.id, assetId: first.id, address: "ventas@example.test", method: "imap", state: "configured", notes: null, }; const save = async (values: MailConfigurationInput, id?: string) => { const response = await request(id ? `${path}/${id}/update` : path, { values }); assert.equal(response.status, id ? 200 : 201, await response.clone().text()); return MailConfigurationResponseSchema.parse(await response.json()).configuration; }; const error = async (values: unknown, code: string, id?: string, status = 409) => { const response = await request(id ? `${path}/${id}/update` : path, { values }); assert.equal(response.status, status, await response.clone().text()); assert.equal(z.object({ code: z.string() }).parse(await response.json()).code, code); }; await error({ ...input, personId: foreignPerson.id }, "MAIL_SCOPE_MISMATCH"); await error({ ...input, accountId: system.id }, "MAIL_SCOPE_MISMATCH"); await error({ ...input, assetId: foreignAsset.id }, "MAIL_SCOPE_MISMATCH"); await error({ ...input, personId: other.people[0]?.id }, "MAIL_SCOPE_MISMATCH"); await error({ ...input, address: "invalid" }, "INVALID_REQUEST", undefined, 400); await error({ ...input, actorId: person.id }, "INVALID_REQUEST", undefined, 400); await error({ ...input, state: "removed" }, "INVALID_MAIL_TRANSITION"); assert.equal((await request(path, { values: input }, "https://other.example")).status, 403); assert.equal((await request(path, { values: input }, undefined, "bad")).status, 403); const [a, b] = await Promise.all([ request(path, { values: input }), request(path, { values: input }), ]); assert.deepEqual([a.status, b.status].sort(), [201, 409]); const created = MailConfigurationResponseSchema.parse( await (a.status === 201 ? a : b).json() ).configuration; assert.equal(created.id[14], "7"); assert.equal(created.history[0]?.id[14], "7"); assert.ok(created.history[0]?.actorName); assert.equal(created.needsReview, false); assert.deepEqual(await save(input, created.id), created); const personal = await save({ ...input, accountId: anotherAccount.id, address: "ana@example.test", state: "pending", }); const mobile = await save({ ...input, assetId: second.id, method: "exchange" }); const shared = await save({ ...input, personId: otherPerson.id, state: "pending", method: "pop", }); assert.notEqual(mobile.id, created.id); assert.notEqual(personal.id, created.id); assert.notEqual(shared.id, created.id); await error({ ...input, assetId: second.id }, "MAIL_TARGET_IMMUTABLE", created.id); await error({ ...input, state: "removed" }, "INVALID_MAIL_TRANSITION", created.id); const foreignUpdate = await request(`/mail-configurations/${otherId}/${created.id}/update`, { values: input, }); assert.equal(foreignUpdate.status, 404); const closedAccess = await request(`${accessPath}/${relation.id}/update`, { values: { ...authorization, accountId: account.id, endDate: today, configuration: "never_configured", }, }); assert.equal(closedAccess.status, 200); const reviewed = MailConfigurationsResponseSchema.parse( await (await request(path)).json() ).configurations; assert.equal(reviewed.find((item) => item.id === created.id)?.needsReview, true); assert.equal(reviewed.find((item) => item.id === personal.id)?.needsReview, false); await error({ ...input, method: "pop" }, "MAIL_NOT_AUTHORIZED", created.id); const pending = await save({ ...input, state: "removal_pending" }, created.id); const removed = await save({ ...input, state: "removed" }, created.id); assert.equal(pending.history.length, 2); assert.equal(removed.history.length, 3); assert.equal(removed.history[0]?.values.state, "configured"); assert.equal(removed.needsReview, false); await error(input, "INVALID_MAIL_TRANSITION", removed.id); assert.equal( ( await request(`/companies/${companyId}/people/${person.id}/update`, { ...personFields, active: false, }) ).status, 200 ); const disabledPerson = MailConfigurationsResponseSchema.parse( await (await request(path)).json() ).configurations; assert.equal(disabledPerson.find((item) => item.id === mobile.id)?.needsReview, true); const never = await save({ ...personal.values, state: "never_configured" }, personal.id); assert.equal(never.history.length, 2); // No borrar configuraciones ni sus revisiones al terminar un permiso. const reloaded = MailConfigurationsResponseSchema.parse( await (await request(path)).json() ).configurations; assert.equal(reloaded.length, 4); assert.equal(reloaded.find((item) => item.id === removed.id)?.history.length, 3); await database.query("UPDATE mail_configurations SET currentKey='invalid' WHERE id=:id", { replacements: { id: mobile.id }, }); try { assert.equal((await request(path)).status, 500); } finally { await database.query("UPDATE mail_configurations SET currentKey=:key WHERE id=:id", { replacements: { id: mobile.id, key: [mobile.values.personId, mobile.values.accountId, mobile.values.assetId].join(":"), }, }); } assert.equal( MailConfigurationsResponseSchema.parse( await (await request(`/mail-configurations/${otherId}`)).json() ).configurations.length, 0 ); }