Add project transfer controls

This commit is contained in:
2026-07-28 19:36:26 +02:00
parent da8115fe1d
commit feec814dc5
5 changed files with 353 additions and 8 deletions
@@ -22,9 +22,9 @@ import { ProjectDeviceStructureProjectCommandRepository } from "../src/db/reposi
import { ProjectHistoryRepository } from "../src/db/repositories/project-history.repository.js";
import { ProjectLocationStructureProjectCommandRepository } from "../src/db/repositories/project-location-structure-project-command.repository.js";
import { ProjectSnapshotRepository } from "../src/db/repositories/project-snapshot.repository.js";
import { ProjectTransferRepository } from "../src/db/repositories/project-transfer.repository.js";
import { ProjectStateRestoreCommandRepository } from "../src/db/repositories/project-state-restore-command.repository.js";
import { ProjectSettingsProjectCommandRepository } from "../src/db/repositories/project-settings-project-command.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";
@@ -37,6 +37,12 @@ import { projectDevices } from "../src/db/schema/project-devices.js";
import { projectRevisions } from "../src/db/schema/project-revisions.js";
import { projectSnapshots } from "../src/db/schema/project-snapshots.js";
import { projects } from "../src/db/schema/projects.js";
import { projectChangeSets } from "../src/db/schema/project-change-sets.js";
import {
hashProjectStatePayload,
readProjectStateSnapshot,
} from "../src/db/repositories/project-state-snapshot.persistence.js";
import { serializeProjectStateSnapshot } from "../src/domain/models/project-state-snapshot.model.js";
import { rooms } from "../src/db/schema/rooms.js";
import { ProjectStateConflictError } from "../src/domain/errors/project-state-conflict.error.js";
import { ProjectCommandService } from "../src/domain/services/project-command.service.js";
@@ -253,6 +259,63 @@ function seedUpgradeOnlyData(context: DatabaseContext) {
}
describe("project state restore command", () => {
it("undoes a remapped cross-project import using the persisted target hash", () => {
const context = createTestDatabase();
try {
const transfers = new ProjectTransferRepository(context.db);
const transfer = transfers.exportProject("project-1");
assert.ok(transfer);
const duplicate = transfers.importDuplicate(transfer);
const before = readProjectStateSnapshot(
context.db,
duplicate.projectId
);
assert.ok(before);
const prepared = transfers.prepareReplace(
duplicate.projectId,
transfer
);
assert.ok(prepared);
const service = createService(context);
const restored = service.executeRestore({
projectId: duplicate.projectId,
expectedRevision: 0,
description: "Projektimport wiederherstellen",
command: prepared.command,
});
const legacyExpectedHash = hashProjectStatePayload(
serializeProjectStateSnapshot(
prepared.command.payload.targetState
)
);
const storedChangeSet = context.db
.select()
.from(projectChangeSets)
.where(eq(projectChangeSets.id, restored.revision.changeSetId))
.get();
assert.ok(storedChangeSet);
const inverse = JSON.parse(storedChangeSet.inversePayloadJson);
inverse.payload.expectedStateSha256 = legacyExpectedHash;
context.db
.update(projectChangeSets)
.set({ inversePayloadJson: JSON.stringify(inverse) })
.where(eq(projectChangeSets.id, storedChangeSet.id))
.run();
const undone = service.undo({
projectId: duplicate.projectId,
expectedRevision: 1,
});
assert.equal(undone.revision.revisionNumber, 2);
assert.equal(
readProjectStateSnapshot(context.db, duplicate.projectId)
?.payloadSha256,
before.payloadSha256
);
} finally {
context.close();
}
});
it("restores a complete snapshot as a revision and supports undo and redo", () => {
const context = createTestDatabase();
try {