metis-desk/apps/server/src/assets.integration.ts
2026-10-04 21:04:14 +02:00

212 lines
7.9 KiB
TypeScript

import assert from "node:assert/strict";
import { CatalogResponseSchema, SaveDefinitionResponseSchema } from "@metis/assets/common";
import { createAssetModule } from "@metis/assets/module";
import type { Sequelize } from "sequelize";
import { z } from "zod/v4";
export async function verifyAssets(
request: (path: string, body?: unknown, origin?: string, token?: string) => Promise<Response>,
companyId: string,
otherId: string,
database: Sequelize
) {
const global = "/asset-catalog/global";
const company = `/asset-catalog/${companyId}`;
const other = `/asset-catalog/${otherId}`;
async function read(path: string) {
const response = await request(path);
assert.equal(response.status, 200);
return CatalogResponseSchema.parse(await response.json());
}
async function save(path: string, values: unknown) {
const response = await request(path, { values });
assert.ok([200, 201].includes(response.status), await response.clone().text());
return SaveDefinitionResponseSchema.parse(await response.json());
}
async function error(path: string, body: unknown, code: string, status = 409) {
const response = await request(path, body);
assert.equal(response.status, status);
const data = z.object({ code: z.string() }).parse(await response.json());
assert.equal(data.code, code);
}
const initial = await read(global);
assert.equal(initial.items.length, 47);
assert.equal(initial.items.filter((item) => item.values.kind === "template").length, 12);
assert.ok(initial.items.every((item) => item.builtin && item.id.charAt(14) === "7"));
assert.ok(
initial.items.every((item) =>
item.bindings.every((binding) => !binding.required && binding.id.charAt(14) === "7")
)
);
const stock = initial.items.find((item) => item.values.name === "Procesador");
assert.ok(stock);
await error(
`${global}/${stock.id}/update`,
{ values: { ...stock.values, name: "Renombrado" } },
"BUILTIN_PROTECTED"
);
assert.equal(
(await request(`${global}/${stock.id}/status`, { active: false }, "https://other.example"))
.status,
403
);
assert.equal(
(await request(`${global}/${stock.id}/status`, { active: false }, undefined, "a".repeat(64)))
.status,
403
);
assert.equal((await request(`${global}/${stock.id}/status`, { active: false })).status, 200);
await createAssetModule(database, (_request, _response, next) => next());
const restarted = await read(global);
assert.equal(restarted.items.find((item) => item.id === stock.id)?.effectiveActive, false);
assert.deepEqual(
restarted.items.map((item) => item.id).sort(),
initial.items.map((item) => item.id).sort()
);
assert.equal((await request(`${global}/${stock.id}/status`, { active: true })).status, 200);
assert.equal((await request(`${company}/${stock.id}/status`, { active: false })).status, 200);
assert.equal(
(await read(company)).items.find((item) => item.id === stock.id)?.effectiveActive,
false
);
assert.equal(
(await read(other)).items.find((item) => item.id === stock.id)?.effectiveActive,
true
);
assert.equal((await read(global)).items.find((item) => item.id === stock.id)?.active, true);
assert.equal((await request(`${company}/${stock.id}/status`, { active: null })).status, 200);
assert.equal((await read(company)).items.find((item) => item.id === stock.id)?.overridden, false);
const localValues = {
kind: "field",
name: "Código interno",
type: "text",
description: "",
unit: "",
choices: [],
};
const local = await save(company, localValues);
const field = local.items.find((item) => item.id === local.savedId);
assert.ok(field);
assert.equal(field.companyId, companyId);
assert.equal(field.builtin, false);
assert.ok(!(await read(other)).items.some((item) => item.id === field.id));
assert.ok(!(await read(global)).items.some((item) => item.id === field.id));
await error(company, { values: { ...localValues, name: " código INTERNO " } }, "NAME_EXISTS");
await error(`${other}/${field.id}/update`, { values: localValues }, "DEFINITION_NOT_FOUND", 404);
await error(
`${company}/${field.id}/update`,
{ values: { ...localValues, type: "number" } },
"FIELD_TYPE_FIXED"
);
const templateValues = {
kind: "template",
name: "Equipo especial",
description: "",
fields: [
{ fieldId: field.id, required: false },
{ fieldId: stock.id, required: false },
],
};
await error(global, { values: templateValues }, "FIELD_UNAVAILABLE");
await error(other, { values: templateValues }, "FIELD_UNAVAILABLE");
const template = await save(company, templateValues);
const saved = template.items.find((item) => item.id === template.savedId);
assert.ok(saved);
const edited = await save(`${company}/${saved.id}/update`, {
...templateValues,
name: "Equipo especial actualizado",
});
assert.equal(edited.savedId, saved.id);
assert.deepEqual(edited.items.find((item) => item.id === saved.id)?.bindings, saved.bindings);
await error(
company,
{
values: {
...templateValues,
name: "Duplicado",
fields: [templateValues.fields[0], templateValues.fields[0]],
},
},
"INVALID_FIELDS"
);
const status = await request(`${company}/${field.id}/status`, { active: false });
assert.equal(status.status, 200);
await error(
company,
{ values: { ...templateValues, name: "Campo inactivo" } },
"FIELD_UNAVAILABLE"
);
assert.ok((await read(company)).items.some((item) => item.id === saved.id));
await save(`${company}/${saved.id}/update`, {
...templateValues,
name: "Equipo especial actualizado",
});
const list = {
kind: "field",
name: "Estado batería",
type: "choice",
unit: "",
description: "",
choices: ["Buena", "Sustituir"],
};
const custom = await save(global, list);
await error(
`${global}/${custom.savedId}/update`,
{ values: { ...list, choices: ["Buena"] } },
"FIELD_TYPE_FIXED"
);
await save(`${global}/${custom.savedId}/update`, {
...list,
choices: [...list.choices, "Desconocida"],
});
await error(global, { values: { ...list, name: "Sin valores", choices: [] } }, "INVALID_CHOICES");
const monitor = initial.items.find((item) => item.values.name === "Monitor");
assert.ok(monitor && monitor.values.kind === "template");
const binding = monitor.bindings[0];
assert.ok(binding);
try {
await database.query("UPDATE asset_definitions SET `values`=:values WHERE id=:id", {
replacements: {
id: monitor.id,
values: JSON.stringify({
...monitor.values,
fields: monitor.values.fields.map((entry, index) =>
index === 0 ? { ...entry, fieldId: field.id } : entry
),
}),
},
});
await database.query("UPDATE asset_template_fields SET fieldId=:fieldId WHERE id=:id", {
replacements: { fieldId: field.id, id: binding.id },
});
const corrupt = await request(global);
assert.equal(corrupt.status, 500);
const hidden = z.object({ code: z.string() }).parse(await corrupt.json());
assert.equal(hidden.code, "INTERNAL_ERROR");
} finally {
await database.query("UPDATE asset_template_fields SET fieldId=:fieldId WHERE id=:id", {
replacements: { fieldId: binding.fieldId, id: binding.id },
});
await database.query("UPDATE asset_definitions SET `values`=:values WHERE id=:id", {
replacements: { values: JSON.stringify(monitor.values), id: monitor.id },
});
}
const concurrent = await Promise.all([
request(company, { values: { ...localValues, name: "Concurrente" } }),
request(company, { values: { ...localValues, name: "Concurrente" } }),
]);
assert.deepEqual(concurrent.map((response) => response.status).sort(), [201, 409]);
await error(
`/asset-catalog/${"82be74bb-894f-4cca-ac7b-dedc96c15689"}`,
undefined,
"COMPANY_NOT_FOUND",
404
);
await error(
global,
{ values: { ...localValues, name: "Injection", builtin: true } },
"INVALID_REQUEST",
400
);
}