Add portable project transfer API
This commit is contained in:
@@ -10,6 +10,8 @@ import {
|
||||
} from "../src/db/database-context.js";
|
||||
import { DistributionBoardFixtureRepository } from "./support/distribution-board-fixture.js";
|
||||
import { ProjectSnapshotRepository } from "../src/db/repositories/project-snapshot.repository.js";
|
||||
import { ProjectTransferRepository } from "../src/db/repositories/project-transfer.repository.js";
|
||||
import { readProjectStateSnapshot } from "../src/db/repositories/project-state-snapshot.persistence.js";
|
||||
import { circuitDeviceRows } from "../src/db/schema/circuit-device-rows.js";
|
||||
import { circuitLists } from "../src/db/schema/circuit-lists.js";
|
||||
import { circuitSections } from "../src/db/schema/circuit-sections.js";
|
||||
@@ -317,3 +319,100 @@ describe("project snapshot repository", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("portable project transfer", () => {
|
||||
it("exports and atomically duplicates a complete project with new ids", () => {
|
||||
const context = createTestDatabase();
|
||||
try {
|
||||
const repository = new ProjectTransferRepository(context.db);
|
||||
const transfer = repository.exportProject("project-1");
|
||||
assert.ok(transfer);
|
||||
const duplicate = repository.importDuplicate(transfer);
|
||||
assert.notEqual(duplicate.projectId, "project-1");
|
||||
assert.equal(duplicate.name, "Snapshot-Projekt (Kopie)");
|
||||
|
||||
const source = readProjectStateSnapshot(context.db, "project-1");
|
||||
const copied = readProjectStateSnapshot(
|
||||
context.db,
|
||||
duplicate.projectId
|
||||
);
|
||||
assert.ok(source);
|
||||
assert.ok(copied);
|
||||
assert.equal(copied.currentRevision, 0);
|
||||
assert.equal(copied.state.circuits.length, source.state.circuits.length);
|
||||
assert.notEqual(
|
||||
copied.state.circuits[0].id,
|
||||
source.state.circuits[0].id
|
||||
);
|
||||
assert.equal(
|
||||
copied.state.circuits[0].deviceRows[0].linkedProjectDeviceId,
|
||||
copied.state.projectDevices[0].id
|
||||
);
|
||||
assert.equal(
|
||||
copied.state.circuits[0].deviceRows[0].roomId,
|
||||
copied.state.rooms[0].id
|
||||
);
|
||||
assert.equal(
|
||||
copied.state.circuits[0].deviceRows[0].legacyConsumerId,
|
||||
null
|
||||
);
|
||||
} finally {
|
||||
context.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("prepares same-project replacement and rejects damaged payloads", () => {
|
||||
const context = createTestDatabase();
|
||||
try {
|
||||
const repository = new ProjectTransferRepository(context.db);
|
||||
const transfer = repository.exportProject("project-1");
|
||||
assert.ok(transfer);
|
||||
const prepared = repository.prepareReplace("project-1", transfer);
|
||||
assert.ok(prepared);
|
||||
assert.equal(
|
||||
prepared.command.payload.targetState.project.id,
|
||||
"project-1"
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
repository.importDuplicate({
|
||||
...transfer,
|
||||
payloadSha256: "0".repeat(64),
|
||||
}),
|
||||
/checksum verification failed/
|
||||
);
|
||||
assert.equal(
|
||||
context.db.select().from(projects).all().length,
|
||||
1
|
||||
);
|
||||
} finally {
|
||||
context.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("rolls back a duplicate when a late project-state insert fails", () => {
|
||||
const context = createTestDatabase();
|
||||
try {
|
||||
const repository = new ProjectTransferRepository(context.db);
|
||||
const transfer = repository.exportProject("project-1");
|
||||
assert.ok(transfer);
|
||||
context.sqlite.exec(`
|
||||
CREATE TRIGGER fail_imported_device_row
|
||||
BEFORE INSERT ON circuit_device_rows
|
||||
BEGIN
|
||||
SELECT RAISE(ABORT, 'forced project transfer failure');
|
||||
END;
|
||||
`);
|
||||
assert.throws(
|
||||
() => repository.importDuplicate(transfer),
|
||||
/forced project transfer failure/
|
||||
);
|
||||
assert.equal(
|
||||
context.db.select().from(projects).all().length,
|
||||
1
|
||||
);
|
||||
} finally {
|
||||
context.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user