metis-desk/modules/resources/src/api/domain/resource-catalog.test.ts

357 lines
12 KiB
TypeScript

import assert from "node:assert/strict";
import { test } from "node:test";
import { UniqueID } from "@repo/rdx-ddd";
import { Maybe } from "@repo/rdx-utils";
import { ResourceCatalog } from "./resource-catalog.aggregate";
import { seedResourceCatalog } from "./resource-catalog-seed";
import type { ResourceDefinitionSpec } from "./resource-definition";
import { ResourceDefinition } from "./resource-definition.entity";
import { ResourceDefinitionValues } from "./value-objects/resource-definition-values.vo";
function values(input: ResourceDefinitionSpec) {
const result = ResourceDefinitionValues.create(input);
assert.ok(result.isSuccess);
return result.data;
}
const field = {
kind: "field",
name: "Marca",
description: "",
type: "text",
unit: "",
choices: [],
} as const;
test("catálogo de recursos: serie idempotente, UUID v7, características opcionales y protección", () => {
const catalog = seedResourceCatalog(ResourceCatalog.create());
assert.equal(catalog.definitions.length, 28);
assert.equal(seedResourceCatalog(catalog), catalog);
assert.ok(catalog.definitions.every((item) => item.id.toPrimitive()[14] === "7"));
assert.ok(
catalog.definitions
.filter((item) => item.values.data.kind === "field")
.every((item) => item.values.data.description.includes("Ejemplo:"))
);
const previousDescription = ResourceDefinitionValues.create({
kind: "field",
name: "Dirección de correo",
description: "Indica la dirección completa del buzón.",
type: "text",
unit: "",
choices: [],
});
assert.ok(previousDescription.isSuccess);
const email = catalog.definitions.find(
(item) => item.stockKey.getOrUndefined() === "field:email"
);
assert.ok(email);
const catalogWithoutHelp = ResourceCatalog.rehydrate(
{
definitions: catalog.definitions.map((item) =>
item.id.equals(email.id)
? ResourceDefinition.rehydrate(
{
companyId: item.companyId,
stockKey: item.stockKey,
values: previousDescription.data,
active: item.active,
bindings: item.bindings,
},
item.id
)
: item
),
availability: catalog.availability,
},
catalog.id
);
const enriched = seedResourceCatalog(catalogWithoutHelp);
assert.ok(
enriched.definitions
.find((item) => item.id.equals(email.id))
?.values.data.description.includes("Ejemplo:")
);
const template = catalog.definitions.find((item) => item.values.data.kind === "template");
assert.ok(template && template.values.data.kind === "template");
assert.ok(template.values.data.fields.every((binding) => !binding.required));
assert.ok(template.bindings.every((binding) => binding.id.toPrimitive()[14] === "7"));
const changed = catalog.save(
Maybe.none(),
Maybe.some(template.id),
values({ ...template.values.data, name: "Cambio" })
);
assert.ok(changed.isSuccess);
const edited = changed.data.definitions.find((item) => item.id.equals(template.id));
assert.equal(edited?.values.data.name, "Cambio");
assert.equal(seedResourceCatalog(changed.data), changed.data);
const disabled = catalog.setActive(Maybe.none(), template.id, false);
assert.ok(disabled.isSuccess);
assert.equal(
seedResourceCatalog(disabled.data).definitions.find((item) => item.id.equals(template.id))
?.active,
false
);
});
test("ámbitos, tipos, listas, campos externos y disponibilidad por compañía", () => {
const company = UniqueID.generateNewID();
const other = UniqueID.generateNewID();
const saved = ResourceCatalog.create().save(
Maybe.some(company),
Maybe.none(),
values({ ...field, choices: [] })
);
assert.ok(saved.isSuccess);
const local = saved.data.definitions[0];
assert.ok(local);
assert.equal(saved.data.visible(Maybe.some(other)).length, 0);
const template = (fieldId: string): ResourceDefinitionSpec => ({
kind: "template",
name: "Propia",
description: "",
fields: [{ fieldId, required: false }],
});
for (const [scope, id] of [
[Maybe.none<UniqueID>(), local.id],
[Maybe.some(other), local.id],
[Maybe.some(company), UniqueID.generateNewID()],
] as const) {
const result = saved.data.save(scope, Maybe.none(), values(template(id.toPrimitive())));
assert.ok(!result.isSuccess);
assert.equal(result.error.code, "FIELD_UNAVAILABLE");
}
const duplicate = saved.data.save(
Maybe.some(company),
Maybe.none(),
values({ ...field, choices: [], name: " marca " })
);
assert.ok(!duplicate.isSuccess);
assert.equal(duplicate.error.code, "NAME_EXISTS");
const changeType = local.update(values({ ...field, choices: [], type: "number" }));
assert.ok(!changeType.isSuccess);
assert.equal(changeType.error.code, "FIELD_TYPE_FIXED");
assert.equal(
ResourceDefinitionValues.create({ ...field, type: "choice", choices: ["A", " a "] }).isSuccess,
false
);
const choices = saved.data.save(
Maybe.none(),
Maybe.none(),
values({ ...field, name: "Lista", type: "choice", choices: ["A", "B"] })
);
assert.ok(choices.isSuccess);
const list = choices.data.definitions.find((item) => item.values.data.name === "Lista");
assert.ok(list);
assert.equal(
list.update(values({ ...field, name: "Lista", type: "choice", choices: ["A"] })).isSuccess,
false
);
const seeded = seedResourceCatalog(ResourceCatalog.create());
const stock = seeded.definitions[0];
assert.ok(stock);
const override = seeded.setActive(Maybe.some(company), stock.id, false);
assert.ok(override.isSuccess);
assert.equal(override.data.active(stock, Maybe.some(company)), false);
assert.equal(override.data.active(stock, Maybe.some(other)), true);
const reset = override.data.setActive(Maybe.some(company), stock.id, null);
assert.ok(reset.isSuccess);
assert.equal(reset.data.active(stock, Maybe.some(company)), true);
});
test("licencias: ampliar un catálogo previo conserva identidades y disponibilidad", () => {
const seeded = seedResourceCatalog(ResourceCatalog.create());
const originalKeys = new Set([
"field:email",
"field:provider",
"field:context",
"field:unc",
"field:server",
"field:description",
"field:url",
"field:holder",
"field:issuer",
"field:expires",
"field:fingerprint",
"field:purpose",
"field:reference",
"template:mailbox",
"template:access",
"template:shared_folder",
"template:application",
"template:certificate",
]);
const previous = ResourceCatalog.rehydrate(
{
definitions: seeded.definitions.filter((item) =>
originalKeys.has(item.stockKey.getOrUndefined() ?? "")
),
availability: [],
},
seeded.id
);
const original = previous.definitions[0];
assert.ok(original);
const disabled = previous.setActive(Maybe.none(), original.id, false);
assert.ok(disabled.isSuccess);
const upgraded = seedResourceCatalog(disabled.data);
assert.equal(upgraded.definitions.length, 28);
for (const existing of disabled.data.definitions) {
assert.equal(
upgraded.definitions.find((item) => item.id.equals(existing.id)),
existing
);
}
const license = upgraded.definitions.find(
(item) => item.stockKey.getOrUndefined() === "template:software_license"
);
assert.ok(license && license.values.data.kind === "template");
assert.ok(license.values.data.fields.every((binding) => !binding.required));
assert.equal(license.values.data.usage, "license");
const editedTemplate = license.update(values({ ...license.values.data, name: "Otra" }));
assert.ok(editedTemplate.isSuccess);
assert.ok(editedTemplate.data.id.equals(license.id));
const company = UniqueID.generateNewID();
const locallyDisabled = upgraded.setActive(Maybe.some(company), license.id, false);
assert.ok(locallyDisabled.isSuccess);
assert.equal(locallyDisabled.data.active(license, Maybe.some(company)), false);
assert.equal(locallyDisabled.data.active(license, Maybe.none()), true);
assert.equal(seedResourceCatalog(locallyDisabled.data), locallyDisabled.data);
});
test("plantillas globales de serie editables: conserva identidad, asociaciones, disponibilidad y cambios al reiniciar", () => {
const initial = seedResourceCatalog(ResourceCatalog.create());
const template = initial.definitions.find((item) => item.values.data.kind === "template");
assert.ok(template?.values.data.kind === "template");
const original = template.values.data;
const input = values({
...original,
name: "Plantilla revisada",
description: "Personalizada",
sections: [],
fields: original.fields
.slice(0, 1)
.map((field) => ({ fieldId: field.fieldId, required: true })),
});
const result = initial.save(Maybe.none(), Maybe.some(template.id), input);
assert.ok(result.isSuccess);
const edited = result.data.definitions.find((item) => item.id.equals(template.id));
assert.ok(edited?.values.data.kind === "template");
assert.equal(edited.values.data.name, "Plantilla revisada");
assert.equal(edited.values.data.fields[0]?.required, true);
assert.ok(edited.bindings[0]?.id.equals(template.bindings[0]?.id));
assert.equal(edited.stockKey.getOrUndefined(), template.stockKey.getOrUndefined());
assert.equal(edited.active, template.active);
assert.equal(seedResourceCatalog(result.data), result.data);
assert.deepEqual(template.values.data, original);
const repeated = result.data.save(Maybe.none(), Maybe.some(template.id), input);
assert.ok(repeated.isSuccess);
assert.equal(repeated.data, result.data);
const crossScope = result.data.save(
Maybe.some(UniqueID.generateNewID()),
Maybe.some(template.id),
input
);
assert.ok(!crossScope.isSuccess);
const protectedField = initial.definitions.find((item) => item.values.data.kind === "field");
assert.ok(protectedField);
const fieldChange = initial.save(
Maybe.none(),
Maybe.some(protectedField.id),
values({ ...protectedField.values.data, name: "Campo revisado" })
);
assert.ok(fieldChange.isSuccess);
const fieldEdited = fieldChange.data.definitions.find((item) =>
item.id.equals(protectedField.id)
);
assert.equal(fieldEdited?.values.data.name, "Campo revisado");
assert.equal(seedResourceCatalog(fieldChange.data), fieldChange.data);
});
test("campos globales de serie editables: listas ampliables, tipo fijo y opciones existentes conservadas", () => {
const catalog = seedResourceCatalog(ResourceCatalog.create());
const field = catalog.definitions.find(
(item) => item.values.data.kind === "field" && item.values.data.type === "choice"
);
assert.ok(field?.values.data.kind === "field");
const previous = field.values.data;
const added = values({
...previous,
name: "Lista revisada",
description: "Personalizada",
unit: "unidad",
choices: [...previous.choices, "Nueva opción"],
});
const result = catalog.save(Maybe.none(), Maybe.some(field.id), added);
assert.ok(result.isSuccess);
const edited = result.data.definitions.find((item) => item.id.equals(field.id));
assert.ok(edited);
assert.deepEqual(edited.values.data, added.data);
assert.equal(seedResourceCatalog(result.data), result.data);
assert.deepEqual(field.values.data, previous);
const differentType = result.data.save(
Maybe.none(),
Maybe.some(field.id),
values({ ...previous, type: "text", choices: [] })
);
assert.ok(!differentType.isSuccess);
assert.equal(differentType.error.code, "FIELD_TYPE_FIXED");
const removedOption = result.data.save(Maybe.none(), Maybe.some(field.id), values(previous));
assert.ok(!removedOption.isSuccess);
assert.equal(removedOption.error.code, "FIELD_TYPE_FIXED");
});
test("sistema operativo: plantilla con uso explícito, copia y ampliación sin sobrescribir definiciones", () => {
const catalog = seedResourceCatalog(ResourceCatalog.create());
const template = catalog.definitions.find(
(item) => item.stockKey.getOrUndefined() === "template:os"
);
assert.ok(template && template.values.data.kind === "template");
assert.equal(template.values.data.usage, "os");
assert.equal(template.values.data.fields.length, 3);
assert.equal(seedResourceCatalog(catalog), catalog);
});