Isolated module (mirrors src/external-model/ dependency direction) that adds an on-demand, DIN VDE 0298-4 referenced cable cross-section calculator: a click-on-a-circuit input mask for laying method, insulation, ambient temperature, grouping and voltage-drop limit that suggests a cross-section for the circuit cableCrossSection/cableLength fields. Applying a suggestion goes through the existing circuit.update command (expectedRevision, undo/redo) unchanged - nothing here writes to circuits directly or touches the revision/command system. See docs/cable-sizing-module.md for the full rationale, API contract, verification status and maintenance plan. Proposal, not yet reviewed by the project owner - see the docs file. 423 existing + 11 new tests pass, build:api/build:web/typecheck:scripts clean.
152 lines
4.9 KiB
TypeScript
152 lines
4.9 KiB
TypeScript
import path from "node:path";
|
|
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
import { eq } from "drizzle-orm";
|
|
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
|
|
import { createDatabaseContext, type DatabaseContext } from "../src/db/database-context.js";
|
|
import { CableSizingCalculationRepository } from "../src/db/repositories/cable-sizing-calculation.repository.js";
|
|
import { circuitLists } from "../src/db/schema/circuit-lists.js";
|
|
import { circuitSections } from "../src/db/schema/circuit-sections.js";
|
|
import { circuits } from "../src/db/schema/circuits.js";
|
|
import { projects } from "../src/db/schema/projects.js";
|
|
import { DistributionBoardFixtureRepository } from "./support/distribution-board-fixture.js";
|
|
|
|
function createRepository() {
|
|
const context = createDatabaseContext(":memory:");
|
|
migrate(context.db, { migrationsFolder: path.resolve("src", "db", "migrations") });
|
|
return new CableSizingCalculationRepository(context.db);
|
|
}
|
|
|
|
function createRepositoryWithCircuits(): {
|
|
repository: CableSizingCalculationRepository;
|
|
context: DatabaseContext;
|
|
circuitOneId: string;
|
|
circuitTwoId: string;
|
|
} {
|
|
const context = createDatabaseContext(":memory:");
|
|
migrate(context.db, { migrationsFolder: path.resolve("src", "db", "migrations") });
|
|
context.db.insert(projects).values({ id: "project-1", name: "Test project" }).run();
|
|
const board = new DistributionBoardFixtureRepository(context.db).createWithCircuitListAndDefaultSections(
|
|
"project-1",
|
|
"UV-01"
|
|
);
|
|
const circuitList = context.db
|
|
.select()
|
|
.from(circuitLists)
|
|
.where(eq(circuitLists.distributionBoardId, board.id))
|
|
.get();
|
|
if (!circuitList) {
|
|
throw new Error("fixture did not create a circuit list");
|
|
}
|
|
const section = context.db
|
|
.select()
|
|
.from(circuitSections)
|
|
.where(eq(circuitSections.circuitListId, circuitList.id))
|
|
.get();
|
|
if (!section) {
|
|
throw new Error("fixture did not create a section");
|
|
}
|
|
const circuitOneId = "circuit-1";
|
|
const circuitTwoId = "circuit-2";
|
|
context.db
|
|
.insert(circuits)
|
|
.values([
|
|
{
|
|
id: circuitOneId,
|
|
circuitListId: section.circuitListId,
|
|
sectionId: section.id,
|
|
equipmentIdentifier: "-1F1.1",
|
|
},
|
|
{
|
|
id: circuitTwoId,
|
|
circuitListId: section.circuitListId,
|
|
sectionId: section.id,
|
|
equipmentIdentifier: "-1F1.2",
|
|
},
|
|
])
|
|
.run();
|
|
return {
|
|
repository: new CableSizingCalculationRepository(context.db),
|
|
context,
|
|
circuitOneId,
|
|
circuitTwoId,
|
|
};
|
|
}
|
|
|
|
describe("CableSizingCalculationRepository", () => {
|
|
it("creates an entry with input/result JSON and defaults appliedToCircuit to false", () => {
|
|
const repository = createRepository();
|
|
const entry = repository.create({
|
|
id: "calc-1",
|
|
projectId: null,
|
|
circuitId: null,
|
|
equipmentIdentifier: "-1F1.1",
|
|
input: { layingMethod: "C" },
|
|
result: { recommendedCrossSectionMm2: 4 },
|
|
appliedToCircuit: 0,
|
|
});
|
|
assert.equal(entry.id, "calc-1");
|
|
assert.equal(entry.appliedToCircuit, 0);
|
|
assert.deepEqual(entry.input, { layingMethod: "C" });
|
|
assert.deepEqual(entry.result, { recommendedCrossSectionMm2: 4 });
|
|
});
|
|
|
|
it("lists entries by circuitId, most recent first", () => {
|
|
// createdAt defaults to SQLite's unixepoch() (second resolution), so two
|
|
// inserts in the same test can tie - pass explicit, distinct timestamps
|
|
// instead of racing the clock. circuitId has a real foreign key into
|
|
// circuits (foreign_keys = ON), so this needs actual circuit rows, not
|
|
// arbitrary strings.
|
|
const { repository, circuitOneId, circuitTwoId } = createRepositoryWithCircuits();
|
|
repository.create({
|
|
id: "calc-a",
|
|
projectId: null,
|
|
circuitId: circuitOneId,
|
|
equipmentIdentifier: null,
|
|
input: {},
|
|
result: {},
|
|
appliedToCircuit: 0,
|
|
createdAt: new Date(1000),
|
|
});
|
|
repository.create({
|
|
id: "calc-b",
|
|
projectId: null,
|
|
circuitId: circuitOneId,
|
|
equipmentIdentifier: null,
|
|
input: {},
|
|
result: {},
|
|
appliedToCircuit: 0,
|
|
createdAt: new Date(2000),
|
|
});
|
|
repository.create({
|
|
id: "calc-other-circuit",
|
|
projectId: null,
|
|
circuitId: circuitTwoId,
|
|
equipmentIdentifier: null,
|
|
input: {},
|
|
result: {},
|
|
appliedToCircuit: 0,
|
|
createdAt: new Date(3000),
|
|
});
|
|
|
|
const rows = repository.listByCircuit(circuitOneId);
|
|
assert.equal(rows.length, 2);
|
|
assert.equal(rows[0].id, "calc-b");
|
|
assert.equal(rows[1].id, "calc-a");
|
|
});
|
|
|
|
it("marks an entry as applied", () => {
|
|
const repository = createRepository();
|
|
repository.create({
|
|
id: "calc-1",
|
|
projectId: null,
|
|
circuitId: null,
|
|
equipmentIdentifier: null,
|
|
input: {},
|
|
result: {},
|
|
appliedToCircuit: 0,
|
|
});
|
|
const updated = repository.markApplied("calc-1");
|
|
assert.equal(updated?.appliedToCircuit, 1);
|
|
});
|
|
});
|