metis-desk/modules/resources/src/api/domain/template-sections.test.ts

294 lines
9.9 KiB
TypeScript
Raw Normal View History

import assert from "node:assert/strict";
import { test } from "node:test";
import { UniqueID } from "@repo/rdx-ddd";
import { Maybe } from "@repo/rdx-utils";
import { mapResourceInput } from "../application/resource-input.mapper";
import { ResourceCatalogPersistenceMapper } from "../infrastructure/persistence/resource-catalog-persistence.mapper";
import { Resource } from "./resource.aggregate";
import { ResourceCatalog } from "./resource-catalog.aggregate";
import { seedResourceCatalog } from "./resource-catalog-seed";
import { ResourceDefinitionSchema } from "./resource-definition";
import { ResourceDefinition } from "./resource-definition.entity";
import type { ResourceInput } from "./resource-values";
import { ResourceDefinitionValues } from "./value-objects/resource-definition-values.vo";
import { ResourceSpecification, ResourceValues } from "./value-objects/resource-values.vo";
const company = UniqueID.generateNewID();
const actor = UniqueID.generateNewID();
const fieldId = UniqueID.generateNewID().toPrimitive();
const section = {
id: UniqueID.generateNewID().toPrimitive(),
name: "Hardware",
description: "Datos técnicos",
};
const otherSection = {
id: UniqueID.generateNewID().toPrimitive(),
name: "Compra",
description: "",
};
const template = {
kind: "template" as const,
name: "Plantilla",
description: "",
sections: [section, otherSection],
fields: [{ fieldId, required: false, sectionId: section.id }],
};
2026-10-10 10:01:58 +00:00
function valid(input = template) {
const result = ResourceDefinitionValues.create(input);
2026-10-10 10:01:58 +00:00
assert.ok(result.isSuccess);
2026-10-10 10:01:58 +00:00
return result.data;
}
test("secciones: nombres, identidades, referencias y un solo nivel", () => {
assert.ok(ResourceDefinitionSchema.safeParse(template).success);
for (const candidate of [
{ ...template, sections: [section, { ...otherSection, name: " hardware " }] },
{ ...template, sections: [section, { ...otherSection, id: section.id.toUpperCase() }] },
{ ...template, sections: [{ ...section, name: " " }] },
{ ...template, sections: [], fields: template.fields },
{ ...template, sections: [{ ...section, sections: [] }] },
{
...template,
sections: Array.from({ length: 31 }, (_, index) => ({
...section,
id: UniqueID.generateNewID().toPrimitive(),
name: `Sección ${index}`,
})),
},
]) {
assert.equal(ResourceDefinitionSchema.safeParse(candidate).success, false);
}
assert.ok(
ResourceDefinitionSchema.safeParse({
...template,
sections: [],
fields: [{ fieldId, required: false }],
}).success
);
});
test("secciones: igualdad, orden, identidad y persistencia JSON", () => {
const first = valid();
2026-10-10 10:01:58 +00:00
assert.ok(first.equals(valid()));
assert.equal(first.equals(valid({ ...template, sections: [otherSection, section] })), false);
assert.equal(
first.equals(
valid({
...template,
fields: [{ ...template.fields[0], fieldId, required: false, sectionId: otherSection.id }],
})
),
false
);
const definition = ResourceDefinition.create(Maybe.none(), first, true);
2026-10-10 10:01:58 +00:00
assert.equal(definition.sections[0]?.id.toPrimitive(), section.id);
assert.equal(definition.sections[0]?.name.toPrimitive(), "Hardware");
const parsed = ResourceDefinitionSchema.parse(JSON.parse(JSON.stringify(first.data)));
const restored = ResourceDefinitionValues.create(parsed);
2026-10-10 10:01:58 +00:00
assert.ok(restored.isSuccess && first.equals(restored.data));
assert.ok(Object.isFrozen(first.data.kind === "template" ? first.data.sections : []));
});
test("secciones: plantillas globales y locales conservan separación y UUID al editar", () => {
const field = ResourceDefinitionValues.create({
kind: "field",
name: "Campo",
description: "",
type: "text",
unit: "",
choices: [],
});
2026-10-10 10:01:58 +00:00
assert.ok(field.isSuccess);
const root = ResourceCatalog.create().save(Maybe.none(), Maybe.none(), field.data);
2026-10-10 10:01:58 +00:00
assert.ok(root.isSuccess);
const id = root.data.definitions[0]?.id.toPrimitive();
2026-10-10 10:01:58 +00:00
assert.ok(id);
const grouped = valid({
...template,
fields: [{ fieldId: id, required: false, sectionId: section.id }],
});
2026-10-10 10:01:58 +00:00
for (const scope of [Maybe.none<UniqueID>(), Maybe.some(company)]) {
const saved = root.data.save(scope, Maybe.none(), grouped);
2026-10-10 10:01:58 +00:00
assert.ok(saved.isSuccess);
const definition = saved.data.definitions.at(-1);
2026-10-10 10:01:58 +00:00
assert.ok(definition);
assert.equal(definition.sections[0]?.id.toPrimitive(), section.id);
const edited = ResourceDefinitionValues.create({ ...grouped.data, name: "Editada" });
2026-10-10 10:01:58 +00:00
assert.ok(edited.isSuccess);
const updated = saved.data.save(scope, Maybe.some(definition.id), edited.data);
2026-10-10 10:01:58 +00:00
assert.ok(updated.isSuccess);
const next = updated.data.definitions.at(-1);
2026-10-10 10:01:58 +00:00
assert.ok(next);
assert.equal(next.id.toPrimitive(), definition.id.toPrimitive());
assert.equal(next.sections[0]?.id.toPrimitive(), section.id);
assert.equal(next.bindings[0]?.id.toPrimitive(), definition.bindings[0]?.id.toPrimitive());
const rows = new ResourceCatalogPersistenceMapper().toPersistence(updated.data);
const serialized: (typeof rows.definitions)[number] | undefined = rows.definitions.find(
(item) => item.id === definition.id.toPrimitive()
);
2026-10-10 10:01:58 +00:00
assert.ok(serialized?.values.kind === "template");
assert.deepEqual(serialized.values.sections, template.sections);
}
const local = root.data.save(Maybe.some(company), Maybe.none(), field.data);
2026-10-10 10:01:58 +00:00
assert.ok(!local.isSuccess); // nombre visible duplicado
const unrelated = root.data.save(Maybe.some(company), Maybe.none(), valid());
2026-10-10 10:01:58 +00:00
assert.ok(!unrelated.isSuccess); // no admite campos de otro catálogo o compañía
});
test("secciones de serie: el arranque conserva una plantilla sin grupos y su disponibilidad", () => {
const seeded = seedResourceCatalog(ResourceCatalog.create());
const grouped = seeded.definitions.find(
(item) => item.values.data.kind === "template" && (item.values.data.sections?.length ?? 0) > 0
);
2026-10-10 10:01:58 +00:00
assert.ok(grouped && grouped.values.data.kind === "template");
const { sections: omitted, ...flat } = grouped.values.data;
2026-10-10 10:01:58 +00:00
assert.ok(omitted);
const oldValues = ResourceDefinitionValues.create({
...flat,
fields: flat.fields.map(({ fieldId, required }) => ({ fieldId, required })),
});
2026-10-10 10:01:58 +00:00
assert.ok(oldValues.isSuccess);
const old = ResourceDefinition.rehydrate(
{
values: oldValues.data,
active: false,
companyId: grouped.companyId,
stockKey: grouped.stockKey,
bindings: grouped.bindings,
},
grouped.id
);
const catalog = ResourceCatalog.rehydrate(
{
definitions: seeded.definitions.map((item) => (item.id.equals(old.id) ? old : item)),
availability: seeded.availability,
},
seeded.id
);
const disabled = catalog.setActive(Maybe.some(company), old.id, false);
2026-10-10 10:01:58 +00:00
assert.ok(disabled.isSuccess);
const upgraded = seedResourceCatalog(disabled.data);
const next = upgraded.definitions.find((item) => item.id.equals(old.id));
2026-10-10 10:01:58 +00:00
assert.ok(next);
assert.equal(next.active, false);
assert.equal(upgraded.active(next, Maybe.some(company)), false);
assert.deepEqual(
next.bindings.map((item) => item.id.toPrimitive()),
old.bindings.map((item) => item.id.toPrimitive())
);
assert.equal(next.sections.length, 0);
assert.equal(next, old);
assert.equal(seedResourceCatalog(upgraded), upgraded);
assert.ok(next.sections.every((item) => item.id.toPrimitive()[14] === "7"));
});
test("especificación: secciones capturadas, edición posterior y obligatoriedad independiente", () => {
const catalog = seedResourceCatalog(ResourceCatalog.create());
const selected = catalog.definitions.find(
(item) => item.values.data.kind === "template" && (item.values.data.sections?.length ?? 0) > 0
);
2026-10-10 10:01:58 +00:00
assert.ok(selected && selected.values.data.kind === "template");
const input: ResourceInput = {
name: "Registro",
templateId: selected.id.toPrimitive(),
active: true,
notes: "",
characteristics: {},
};
const context = { catalog, resources: [], references: [], actorNames: new Map() };
const mapped = mapResourceInput(input, company, context);
2026-10-10 10:01:58 +00:00
assert.ok(mapped.isSuccess);
assert.deepEqual(mapped.data.specification.toPrimitive().sections, selected.values.data.sections);
assert.deepEqual(
mapped.data.specification.toPrimitive().fields.map((item) => item.sectionId),
selected.values.data.fields.map((item) => item.sectionId)
);
const created = Resource.create(
company,
mapped.data.specification,
mapped.data.values,
actor,
"2026-10-09T12:00:00.000Z"
);
2026-10-10 10:01:58 +00:00
assert.ok(created.isSuccess);
const flatSpec = ResourceSpecification.create({
templateId: selected.id.toPrimitive(),
templateName: "Anterior",
fields: mapped.data.specification
.toPrimitive()
.fields.map(({ sectionId: omitted, ...field }) => {
assert.ok(omitted);
2026-10-10 10:01:58 +00:00
return field;
}),
});
2026-10-10 10:01:58 +00:00
assert.ok(flatSpec.isSuccess);
const previousValues = ResourceValues.create(input, flatSpec.data);
2026-10-10 10:01:58 +00:00
assert.ok(previousValues.isSuccess);
const previous = Resource.create(
company,
flatSpec.data,
previousValues.data,
actor,
"2026-10-09T11:00:00.000Z"
);
2026-10-10 10:01:58 +00:00
assert.ok(previous.isSuccess);
const edit = mapResourceInput({ ...input, name: "Cambio" }, company, context, previous.data);
2026-10-10 10:01:58 +00:00
assert.ok(edit.isSuccess);
assert.equal(edit.data.specification, previous.data.specification);
assert.equal(edit.data.specification.toPrimitive().sections, undefined);
const spec = mapped.data.specification.toPrimitive();
const firstField = spec.fields[0];
2026-10-10 10:01:58 +00:00
assert.ok(firstField);
const required = ResourceSpecification.create({
...spec,
fields: spec.fields.map((field) => ({
...field,
required: field.fieldId === firstField.fieldId,
})),
});
2026-10-10 10:01:58 +00:00
assert.ok(required.isSuccess);
assert.equal(ResourceValues.create(input, required.data).isSuccess, false);
assert.ok(
ResourceValues.create(
{ ...input, characteristics: { [firstField.fieldId]: "Dato" } },
required.data
).isSuccess
);
});