224 lines
8.8 KiB
TypeScript
224 lines
8.8 KiB
TypeScript
|
|
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
|
||
|
|
);
|
||
|
|
}
|