2026-10-04 16:36:09 +00:00
|
|
|
import assert from "node:assert/strict";
|
|
|
|
|
import { test } from "node:test";
|
|
|
|
|
|
|
|
|
|
import { UniqueID } from "@repo/rdx-ddd";
|
|
|
|
|
import { Maybe } from "@repo/rdx-utils";
|
|
|
|
|
|
|
|
|
|
import { AssetCatalog } from "./asset-catalog.aggregate";
|
|
|
|
|
import { seedAssetCatalog } from "./asset-catalog-seed";
|
|
|
|
|
import type { AssetDefinitionSpec } from "./asset-definition";
|
2026-10-10 16:34:25 +00:00
|
|
|
import { AssetDefinitionSchema } from "./asset-definition";
|
2026-10-10 20:00:45 +00:00
|
|
|
import { AssetDefinition, TemplateField } from "./asset-definition.entity";
|
2026-10-09 08:38:19 +00:00
|
|
|
import { AssetDefinitionValues } from "./value-objects/asset-definition-values.vo";
|
2026-10-04 16:36:09 +00:00
|
|
|
|
|
|
|
|
const global = Maybe.none<UniqueID>();
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
function values(spec: AssetDefinitionSpec) {
|
|
|
|
|
const result = AssetDefinitionValues.create(spec);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
assert.ok(result.isSuccess);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
return result.data;
|
|
|
|
|
}
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
function field(name: string) {
|
|
|
|
|
return values({ kind: "field", name, type: "text", unit: "", choices: [], description: "" });
|
|
|
|
|
}
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
function changed(result: ReturnType<AssetCatalog["save"]>) {
|
|
|
|
|
assert.ok(result.isSuccess);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
return result.data;
|
|
|
|
|
}
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
test("el catálogo inicial es idempotente, con UUID propios y características opcionales", () => {
|
|
|
|
|
const catalog = seedAssetCatalog(AssetCatalog.create());
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-10 20:00:45 +00:00
|
|
|
assert.equal(catalog.definitions.length, 75);
|
2026-10-04 16:36:09 +00:00
|
|
|
assert.equal(seedAssetCatalog(catalog), catalog);
|
|
|
|
|
const ids = [
|
|
|
|
|
catalog.id,
|
|
|
|
|
...catalog.definitions.map((item) => item.id),
|
|
|
|
|
...catalog.definitions.flatMap((item) => item.bindings.map((binding) => binding.id)),
|
|
|
|
|
].map((id) => id.toPrimitive());
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
assert.equal(new Set(ids).size, ids.length);
|
|
|
|
|
assert.ok(ids.every((id) => id.charAt(14) === "7"));
|
|
|
|
|
const templates = catalog.definitions.filter((item) => item.values.data.kind === "template");
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
assert.equal(templates.length, 12);
|
|
|
|
|
assert.ok(templates.every((item) => item.bindings.every((field) => !field.required)));
|
|
|
|
|
const rehydrated = AssetCatalog.rehydrate(
|
|
|
|
|
{ definitions: catalog.definitions, availability: catalog.availability },
|
|
|
|
|
catalog.id
|
|
|
|
|
);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
assert.ok(rehydrated.id.equals(catalog.id));
|
|
|
|
|
assert.ok(rehydrated.definitions[0]?.id.equals(catalog.definitions[0]?.id));
|
|
|
|
|
const stock = catalog.definitions[0];
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
assert.ok(stock);
|
|
|
|
|
const protectedResult = catalog.save(global, Maybe.some(stock.id), field("Cambio"));
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-10 20:00:45 +00:00
|
|
|
assert.ok(protectedResult.isSuccess);
|
|
|
|
|
assert.equal(
|
|
|
|
|
protectedResult.data.definitions.find((item) => item.id.equals(stock.id))?.values.data.name,
|
|
|
|
|
"Cambio"
|
|
|
|
|
);
|
2026-10-04 16:36:09 +00:00
|
|
|
});
|
|
|
|
|
test("la disponibilidad por compañía conserva el global y los campos desactivados", () => {
|
|
|
|
|
let catalog = seedAssetCatalog(AssetCatalog.create());
|
|
|
|
|
const first = Maybe.some(UniqueID.generateNewID());
|
|
|
|
|
const second = Maybe.some(UniqueID.generateNewID());
|
|
|
|
|
const stock = catalog.definitions[0];
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
assert.ok(stock);
|
|
|
|
|
catalog = changed(catalog.setActive(first, stock.id, false));
|
|
|
|
|
assert.equal(catalog.active(stock, first), false);
|
|
|
|
|
assert.equal(catalog.active(stock, second), true);
|
|
|
|
|
assert.equal(stock.active, true);
|
2026-10-10 20:00:45 +00:00
|
|
|
assert.equal(catalog.definitions.length, 75);
|
2026-10-04 16:36:09 +00:00
|
|
|
const overrideId = catalog.availability[0]?.id;
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
assert.ok(overrideId);
|
|
|
|
|
assert.equal(changed(catalog.setActive(first, stock.id, false)), catalog);
|
|
|
|
|
catalog = changed(catalog.setActive(first, stock.id, null));
|
|
|
|
|
assert.equal(catalog.active(stock, first), true);
|
|
|
|
|
assert.ok(catalog.availability[0]?.id.equals(overrideId));
|
|
|
|
|
catalog = changed(catalog.setActive(global, stock.id, false));
|
|
|
|
|
assert.equal(seedAssetCatalog(catalog), catalog);
|
|
|
|
|
assert.equal(catalog.active(catalog.definitions[0] ?? stock, first), false);
|
|
|
|
|
});
|
|
|
|
|
test("las plantillas validan ámbito, campos activos e identidad estable al editar", () => {
|
|
|
|
|
let catalog = seedAssetCatalog(AssetCatalog.create());
|
|
|
|
|
const company = Maybe.some(UniqueID.generateNewID());
|
|
|
|
|
const other = Maybe.some(UniqueID.generateNewID());
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
catalog = changed(catalog.save(company, Maybe.none(), field("Campo local")));
|
|
|
|
|
const local = catalog.definitions.at(-1);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
assert.ok(local);
|
|
|
|
|
const template = values({
|
|
|
|
|
kind: "template",
|
|
|
|
|
name: "Plantilla local",
|
|
|
|
|
description: "",
|
|
|
|
|
fields: [{ fieldId: local.id.toPrimitive(), required: false }],
|
|
|
|
|
});
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
assert.ok(!catalog.save(global, Maybe.none(), template).isSuccess);
|
|
|
|
|
assert.ok(!catalog.save(other, Maybe.none(), template).isSuccess);
|
|
|
|
|
catalog = changed(catalog.save(company, Maybe.none(), template));
|
|
|
|
|
const saved = catalog.definitions.at(-1);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
assert.ok(saved);
|
|
|
|
|
assert.equal(changed(catalog.save(company, Maybe.some(saved.id), template)), catalog);
|
|
|
|
|
const updated = values({ ...template.data, name: "Otra denominación" });
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
catalog = changed(catalog.save(company, Maybe.some(saved.id), updated));
|
|
|
|
|
const edited = catalog.definitions.at(-1);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
assert.ok(edited);
|
|
|
|
|
assert.ok(edited.id.equals(saved.id));
|
|
|
|
|
assert.ok(edited.bindings[0]?.id.equals(saved.bindings[0]?.id));
|
|
|
|
|
catalog = changed(catalog.setActive(company, local.id, false));
|
|
|
|
|
assert.ok(
|
|
|
|
|
!catalog.save(company, Maybe.none(), values({ ...template.data, name: "Nueva" })).isSuccess
|
|
|
|
|
);
|
|
|
|
|
assert.ok(catalog.save(company, Maybe.some(saved.id), updated).isSuccess);
|
|
|
|
|
assert.ok(!catalog.visible(other).some((item) => item.id.equals(local.id)));
|
|
|
|
|
});
|
|
|
|
|
test("los campos validan listas, igualdad semántica, duplicados y tipo fijo", () => {
|
|
|
|
|
const list: AssetDefinitionSpec = {
|
|
|
|
|
kind: "field",
|
|
|
|
|
name: "Batería",
|
|
|
|
|
description: "",
|
|
|
|
|
type: "choice",
|
|
|
|
|
unit: "",
|
|
|
|
|
choices: ["Buena", "Sustituir"],
|
|
|
|
|
};
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
assert.ok(!AssetDefinitionValues.create({ ...list, choices: [] }).isSuccess);
|
|
|
|
|
assert.ok(!AssetDefinitionValues.create({ ...list, choices: ["Buena", " buena "] }).isSuccess);
|
|
|
|
|
assert.ok(!AssetDefinitionValues.create({ ...list, type: "text" }).isSuccess);
|
|
|
|
|
const frozen = values(list);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
assert.ok(Object.isFrozen(frozen.data));
|
|
|
|
|
if (frozen.data.kind === "field") {
|
|
|
|
|
assert.ok(Object.isFrozen(frozen.data.choices));
|
|
|
|
|
}
|
|
|
|
|
let catalog = AssetCatalog.create();
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
catalog = changed(catalog.save(global, Maybe.none(), frozen));
|
|
|
|
|
const saved = catalog.definitions[0];
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-04 16:36:09 +00:00
|
|
|
assert.ok(saved);
|
|
|
|
|
assert.ok(!catalog.save(global, Maybe.none(), values({ ...list, name: " batería " })).isSuccess);
|
|
|
|
|
assert.ok(!catalog.save(global, Maybe.some(saved.id), field("Batería")).isSuccess);
|
|
|
|
|
assert.ok(
|
|
|
|
|
!catalog.save(global, Maybe.some(saved.id), values({ ...list, choices: ["Buena"] })).isSuccess
|
|
|
|
|
);
|
|
|
|
|
assert.ok(
|
|
|
|
|
catalog.save(
|
|
|
|
|
global,
|
|
|
|
|
Maybe.some(saved.id),
|
|
|
|
|
values({ ...list, choices: [...list.choices, "Desconocida"] })
|
|
|
|
|
).isSuccess
|
|
|
|
|
);
|
|
|
|
|
});
|
2026-10-10 16:34:25 +00:00
|
|
|
|
|
|
|
|
test("iconos opcionales: cambio y retirada conservan identidad y asociaciones", () => {
|
|
|
|
|
let catalog = seedAssetCatalog(AssetCatalog.create());
|
|
|
|
|
const fieldId = catalog.definitions[0]?.id.toPrimitive();
|
|
|
|
|
|
|
|
|
|
assert.ok(fieldId);
|
|
|
|
|
const base = {
|
|
|
|
|
kind: "template",
|
|
|
|
|
name: "Equipo con icono",
|
|
|
|
|
description: "",
|
|
|
|
|
fields: [{ fieldId, required: false }],
|
|
|
|
|
} satisfies AssetDefinitionSpec;
|
|
|
|
|
const company = Maybe.some(UniqueID.generateNewID());
|
|
|
|
|
|
|
|
|
|
for (const scope of [global, company]) {
|
|
|
|
|
const input = {
|
|
|
|
|
...base,
|
|
|
|
|
name: scope === global ? "Plantilla global con icono" : "Plantilla local con icono",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
catalog = changed(catalog.save(scope, Maybe.none(), values(input)));
|
|
|
|
|
const original = catalog.definitions.at(-1);
|
|
|
|
|
|
|
|
|
|
assert.ok(original);
|
|
|
|
|
catalog = changed(
|
|
|
|
|
catalog.save(scope, Maybe.some(original.id), values({ ...input, icon: "laptop" }))
|
|
|
|
|
);
|
|
|
|
|
const updated = catalog.definitions.find((item) => item.id.equals(original.id));
|
|
|
|
|
|
|
|
|
|
assert.ok(updated?.values.data.kind === "template");
|
|
|
|
|
assert.equal(updated.values.data.icon, "laptop");
|
|
|
|
|
assert.ok(updated.bindings[0]?.id.equals(original.bindings[0]?.id));
|
|
|
|
|
assert.equal(changed(catalog.save(scope, Maybe.some(original.id), updated.values)), catalog);
|
|
|
|
|
catalog = changed(catalog.save(scope, Maybe.some(original.id), values(input)));
|
|
|
|
|
const cleared = catalog.definitions.find((item) => item.id.equals(original.id));
|
|
|
|
|
|
|
|
|
|
assert.ok(cleared?.values.data.kind === "template");
|
|
|
|
|
assert.equal(cleared.values.data.icon, undefined);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-10-10 20:00:45 +00:00
|
|
|
test("el arranque conserva un icono de serie retirado sin cambiar UUID, campos o disponibilidad", () => {
|
2026-10-10 16:34:25 +00:00
|
|
|
const initial = seedAssetCatalog(AssetCatalog.create());
|
|
|
|
|
const template = initial.definitions.find(
|
|
|
|
|
(item) => item.stockKey.getOrUndefined() === "template:laptop"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert.ok(template?.values.data.kind === "template");
|
|
|
|
|
assert.equal(template.values.data.icon, "laptop");
|
|
|
|
|
const { icon, ...withoutIcon } = template.values.data;
|
|
|
|
|
|
|
|
|
|
assert.ok(icon);
|
|
|
|
|
const previous = AssetDefinition.rehydrate(
|
|
|
|
|
{
|
|
|
|
|
companyId: template.companyId,
|
|
|
|
|
stockKey: template.stockKey,
|
|
|
|
|
values: values(withoutIcon),
|
|
|
|
|
active: false,
|
|
|
|
|
bindings: template.bindings,
|
|
|
|
|
},
|
|
|
|
|
template.id
|
|
|
|
|
);
|
|
|
|
|
const upgraded = seedAssetCatalog(
|
|
|
|
|
AssetCatalog.rehydrate(
|
|
|
|
|
{
|
|
|
|
|
definitions: initial.definitions.map((item) =>
|
|
|
|
|
item.id.equals(template.id) ? previous : item
|
|
|
|
|
),
|
|
|
|
|
availability: initial.availability,
|
|
|
|
|
},
|
|
|
|
|
initial.id
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
const saved = upgraded.definitions.find((item) => item.id.equals(template.id));
|
|
|
|
|
|
|
|
|
|
assert.ok(saved?.values.data.kind === "template");
|
2026-10-10 20:00:45 +00:00
|
|
|
assert.equal(saved.values.data.icon, undefined);
|
2026-10-10 16:34:25 +00:00
|
|
|
assert.equal(saved.active, false);
|
|
|
|
|
assert.deepEqual(saved.values.data.fields, template.values.data.fields);
|
|
|
|
|
assert.deepEqual(saved.bindings, template.bindings);
|
|
|
|
|
assert.equal(seedAssetCatalog(upgraded), upgraded);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("los iconos desconocidos o asociados a campos se rechazan", () => {
|
|
|
|
|
const template = {
|
|
|
|
|
kind: "template",
|
|
|
|
|
name: "Equipo",
|
|
|
|
|
description: "",
|
|
|
|
|
fields: [{ fieldId: UniqueID.generateNewID().toPrimitive(), required: false }],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
assert.equal(
|
|
|
|
|
AssetDefinitionSchema.safeParse({ ...template, icon: "unknown-icon" }).success,
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
AssetDefinitionSchema.safeParse({
|
|
|
|
|
kind: "field",
|
|
|
|
|
name: "Campo",
|
|
|
|
|
description: "",
|
|
|
|
|
type: "text",
|
|
|
|
|
unit: "",
|
|
|
|
|
choices: [],
|
|
|
|
|
icon: "laptop",
|
|
|
|
|
}).success,
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
});
|
2026-10-10 20:00:45 +00:00
|
|
|
|
|
|
|
|
test("revisión explícita de Portátil conserva identidades, copias y otras plantillas", () => {
|
|
|
|
|
const initial = seedAssetCatalog(AssetCatalog.create());
|
|
|
|
|
const laptop = initial.definitions.find(
|
|
|
|
|
(item) => item.stockKey.getOrUndefined() === "template:laptop"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert.ok(laptop?.values.data.kind === "template");
|
|
|
|
|
const oldLayout: [string, string[]][] = [
|
|
|
|
|
["Identificación", ["brand", "model", "serial"]],
|
|
|
|
|
["Hardware", ["processor", "memory", "storage"]],
|
|
|
|
|
["Sistema", ["os", "hostname"]],
|
|
|
|
|
["Pantalla y conexiones", ["screen_size", "resolution", "connections"]],
|
|
|
|
|
["Red", ["ip", "mac"]],
|
|
|
|
|
["Compra y garantía", ["purchased_on", "warranty_until"]],
|
|
|
|
|
];
|
|
|
|
|
const sections = oldLayout.map(([name]) => ({
|
|
|
|
|
id: UniqueID.generateNewID().toPrimitive(),
|
|
|
|
|
name,
|
|
|
|
|
description: "",
|
|
|
|
|
}));
|
|
|
|
|
const fields = oldLayout.flatMap(([, keys], index) =>
|
|
|
|
|
keys.map((key) => {
|
|
|
|
|
const field = initial.definitions.find(
|
|
|
|
|
(item) => item.stockKey.getOrUndefined() === `field:${key}`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert.ok(field);
|
|
|
|
|
|
|
|
|
|
return { fieldId: field.id.toPrimitive(), required: false, sectionId: sections[index]?.id };
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
const oldValues = values({ ...laptop.values.data, fields, sections });
|
|
|
|
|
const bindings = fields.map((field) => {
|
|
|
|
|
const definition = initial.definitions.find((item) => item.id.toPrimitive() === field.fieldId);
|
|
|
|
|
|
|
|
|
|
assert.ok(definition);
|
|
|
|
|
|
|
|
|
|
return TemplateField.create(definition.id, false);
|
|
|
|
|
});
|
|
|
|
|
const oldLaptop = AssetDefinition.rehydrate(
|
|
|
|
|
{
|
|
|
|
|
companyId: laptop.companyId,
|
|
|
|
|
stockKey: laptop.stockKey,
|
|
|
|
|
values: oldValues,
|
|
|
|
|
active: false,
|
|
|
|
|
bindings,
|
|
|
|
|
},
|
|
|
|
|
laptop.id
|
|
|
|
|
);
|
|
|
|
|
const copy = AssetDefinition.create(
|
|
|
|
|
global,
|
|
|
|
|
values({ ...oldLaptop.values.data, name: "Copia personalizada" }),
|
|
|
|
|
true
|
|
|
|
|
);
|
|
|
|
|
const before = AssetCatalog.rehydrate(
|
|
|
|
|
{
|
|
|
|
|
definitions: [
|
|
|
|
|
...initial.definitions.filter((item) => !item.id.equals(laptop.id)),
|
|
|
|
|
oldLaptop,
|
|
|
|
|
copy,
|
|
|
|
|
],
|
|
|
|
|
availability: initial.availability,
|
|
|
|
|
},
|
|
|
|
|
initial.id
|
|
|
|
|
);
|
|
|
|
|
const revision = before.seed("template:laptop", laptop.values, { refreshTemplate: true });
|
|
|
|
|
|
|
|
|
|
assert.ok(revision.isSuccess);
|
|
|
|
|
const after = revision.data;
|
|
|
|
|
const updated = after.definitions.find((item) => item.id.equals(laptop.id));
|
|
|
|
|
|
|
|
|
|
assert.ok(updated?.values.data.kind === "template");
|
|
|
|
|
assert.equal(updated.active, false);
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
updated.values.data.sections?.map((section) => section.name),
|
|
|
|
|
[
|
|
|
|
|
"Identificación",
|
|
|
|
|
"Hardware",
|
|
|
|
|
"Memoria RAM",
|
|
|
|
|
"Almacenamiento",
|
|
|
|
|
"Pantalla",
|
|
|
|
|
"Teclado",
|
|
|
|
|
"Batería",
|
|
|
|
|
"Alimentación",
|
|
|
|
|
"Compra y garantía",
|
|
|
|
|
"Soporte y reparación",
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
const names = updated.values.data.fields.map(
|
|
|
|
|
(field) =>
|
|
|
|
|
after.definitions.find((item) => item.id.toPrimitive() === field.fieldId)?.values.data.name
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
for (const name of [
|
|
|
|
|
"Tarjeta gráfica",
|
|
|
|
|
"Webcam",
|
|
|
|
|
"Estándar Wi-Fi",
|
|
|
|
|
"Versión de Bluetooth",
|
|
|
|
|
"Teclado numérico",
|
|
|
|
|
]) {
|
|
|
|
|
assert.ok(names.includes(name));
|
|
|
|
|
}
|
|
|
|
|
for (const name of [
|
|
|
|
|
"Sistema operativo",
|
|
|
|
|
"Nombre de equipo",
|
|
|
|
|
"Dirección IP",
|
|
|
|
|
"Dirección MAC",
|
|
|
|
|
"Conexiones",
|
|
|
|
|
]) {
|
|
|
|
|
assert.equal(names.includes(name), false);
|
|
|
|
|
}
|
|
|
|
|
for (const binding of updated.bindings) {
|
|
|
|
|
const previous = oldLaptop.bindings.find((item) => item.fieldId.equals(binding.fieldId));
|
|
|
|
|
|
|
|
|
|
if (previous) {
|
|
|
|
|
assert.ok(previous.id.equals(binding.id));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
assert.equal(
|
|
|
|
|
updated.values.data.sections?.find((section) => section.name === "Hardware")?.id,
|
|
|
|
|
sections.find((section) => section.name === "Hardware")?.id
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
updated.values.data.sections?.find((section) => section.name === "Pantalla")?.id,
|
|
|
|
|
sections.find((section) => section.name === "Pantalla y conexiones")?.id
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
after.definitions.find((item) => item.id.equals(copy.id)),
|
|
|
|
|
copy
|
|
|
|
|
);
|
|
|
|
|
const desktop = before.definitions.find(
|
|
|
|
|
(item) => item.stockKey.getOrUndefined() === "template:desktop"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert.equal(
|
|
|
|
|
after.definitions.find((item) => item.stockKey.getOrUndefined() === "template:desktop"),
|
|
|
|
|
desktop
|
|
|
|
|
);
|
|
|
|
|
assert.equal(oldLaptop.values, oldValues);
|
|
|
|
|
assert.equal(updated.values.data.fields.length, 39);
|
|
|
|
|
const batteryState = after.definitions.find(
|
|
|
|
|
(item) => item.stockKey.getOrUndefined() === "field:battery_status"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert.ok(batteryState?.values.data.kind === "field");
|
|
|
|
|
assert.equal(batteryState.values.data.type, "choice");
|
|
|
|
|
assert.deepEqual(batteryState.values.data.choices, [
|
|
|
|
|
"Normal",
|
|
|
|
|
"Desgastada",
|
|
|
|
|
"Averiada",
|
|
|
|
|
"Hinchada",
|
|
|
|
|
]);
|
|
|
|
|
for (const key of ["memory", "storage", "numeric_keypad"]) {
|
|
|
|
|
const field = after.definitions.find(
|
|
|
|
|
(item) => item.stockKey.getOrUndefined() === `field:${key}`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert.ok(field);
|
|
|
|
|
assert.equal(
|
|
|
|
|
after.definitions.filter((item) => item.stockKey.getOrUndefined() === `field:${key}`).length,
|
|
|
|
|
1
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
assert.ok(updated.values.data.fields.every((field) => !field.required));
|
|
|
|
|
assert.equal(seedAssetCatalog(after), after);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("plantillas globales de serie editables: conserva identidad, asociaciones, disponibilidad y cambios al reiniciar", () => {
|
|
|
|
|
const initial = seedAssetCatalog(AssetCatalog.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(seedAssetCatalog(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(seedAssetCatalog(fieldChange.data), fieldChange.data);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("campos globales de serie editables: listas ampliables, tipo fijo y opciones existentes conservadas", () => {
|
|
|
|
|
const catalog = seedAssetCatalog(AssetCatalog.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(seedAssetCatalog(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");
|
|
|
|
|
});
|