metis-desk/apps/server/src/mail.integration.ts

224 lines
8.8 KiB
TypeScript
Raw Normal View History

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<Response>;
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
);
}