Make device row moves atomic

This commit is contained in:
2026-07-23 18:52:40 +02:00
parent a6837d7242
commit cf356bbedc
7 changed files with 440 additions and 247 deletions
+15 -93
View File
@@ -414,7 +414,9 @@ describe("circuit write service rules", () => {
async findById() {
return { id: "r1", circuitId: "c1" } as never;
},
moveRowsTransactional(input: typeof transactionalMove) {
} as never,
deviceRowTransactionStore: {
moveRows(input: typeof transactionalMove) {
transactionalMove = input;
return {
movedRowIds: input!.rows.map((row) => row.id),
@@ -469,7 +471,9 @@ describe("circuit write service rules", () => {
async findById() {
return { id: "r1", circuitId: "c1" } as never;
},
moveRowsTransactional(input: { rows: Array<{ id: string }>; createTargetCircuit?: typeof preparedTarget }) {
} as never,
deviceRowTransactionStore: {
moveRows(input: { rows: Array<{ id: string }>; createTargetCircuit?: typeof preparedTarget }) {
preparedTarget = input.createTargetCircuit;
return {
movedRowIds: input.rows.map((row) => row.id),
@@ -525,7 +529,9 @@ describe("circuit write service rules", () => {
async findById() {
return { id: "r1", circuitId: "c1" } as never;
},
moveRowsTransactional() {
} as never,
deviceRowTransactionStore: {
moveRows() {
transactionalMoveCount += 1;
throw new Error("same-circuit move must not write");
},
@@ -565,7 +571,9 @@ describe("circuit write service rules", () => {
}
return { id: "r2", circuitId: "c2" } as never;
},
moveRowsTransactional(input: typeof transactionalMove) {
} as never,
deviceRowTransactionStore: {
moveRows(input: typeof transactionalMove) {
transactionalMove = input;
return {
movedRowIds: input!.rows.map((row) => row.id),
@@ -617,7 +625,9 @@ describe("circuit write service rules", () => {
async findById(rowId: string) {
return { id: rowId, circuitId: rowId === "r1" ? "c1" : "c2" } as never;
},
moveRowsTransactional(input: { rows: Array<{ id: string }>; createTargetCircuit?: typeof preparedTarget }) {
} as never,
deviceRowTransactionStore: {
moveRows(input: { rows: Array<{ id: string }>; createTargetCircuit?: typeof preparedTarget }) {
transactionCount += 1;
preparedTarget = input.createTargetCircuit;
return {
@@ -839,94 +849,6 @@ describe("circuit write service rules", () => {
}
});
it("bulk device row move uses one synchronous transaction callback", () => {
const repository = new CircuitDeviceRowRepository();
const originalTransaction = (db as unknown as { transaction: unknown }).transaction;
let callbackReturnedPromise = false;
let selectCall = 0;
let rowUpdateCount = 0;
const selectedRows = [
[
{ id: "r1", circuitId: "c1" },
{ id: "r2", circuitId: "c2" },
],
[{ id: "c3", circuitListId: "l1" }],
[
{ id: "c1", circuitListId: "l1" },
{ id: "c2", circuitListId: "l1" },
],
[{ id: "existing-target-row", sortOrder: 10 }],
[],
[],
];
(db as unknown as { transaction: (cb: (tx: unknown) => unknown) => void }).transaction = (cb) => {
const fakeTx = {
select() {
const rows = selectedRows[selectCall++] ?? [];
const query = {
from() {
return query;
},
where() {
return query;
},
limit() {
return query;
},
all() {
return rows;
},
};
return query;
},
update() {
return {
set() {
return {
where() {
return {
run() {
rowUpdateCount += 1;
return { changes: 1 };
},
};
},
};
},
};
},
};
const callbackResult = cb(fakeTx);
callbackReturnedPromise = Boolean(
callbackResult && typeof (callbackResult as Promise<unknown>).then === "function"
);
if (callbackReturnedPromise) {
throw new Error("Transaction function cannot return a promise");
}
};
try {
const result = repository.moveRowsTransactional({
rows: [
{ id: "r1", expectedCircuitId: "c1" },
{ id: "r2", expectedCircuitId: "c2" },
],
targetCircuitId: "c3",
});
assert.equal(callbackReturnedPromise, false);
assert.equal(rowUpdateCount, 5);
assert.deepEqual(result, {
movedRowIds: ["r1", "r2"],
targetCircuitId: "c3",
createdCircuitId: undefined,
});
} finally {
(db as unknown as { transaction: unknown }).transaction = originalTransaction;
}
});
it("tracks local edits on linked device rows as overridden fields", async () => {
let updatePayload: Record<string, unknown> = {};
const current = {