Make device row writes atomic
This commit is contained in:
@@ -117,17 +117,14 @@ describe("circuit write service rules", () => {
|
||||
});
|
||||
|
||||
it("deleting last device row keeps circuit and sets reserve", async () => {
|
||||
let reserveFlag = false;
|
||||
let transactionalDelete: { rowId: string; circuitId: string } | undefined;
|
||||
const service = new CircuitWriteService({
|
||||
deviceRowRepository: {
|
||||
async findById() {
|
||||
return { id: "r1", circuitId: "c1" } as never;
|
||||
},
|
||||
async delete() {
|
||||
return;
|
||||
},
|
||||
async countByCircuit() {
|
||||
return 0;
|
||||
deleteFromCircuitTransactional(rowId: string, circuitId: string) {
|
||||
transactionalDelete = { rowId, circuitId };
|
||||
},
|
||||
} as never,
|
||||
circuitRepository: {
|
||||
@@ -141,18 +138,22 @@ describe("circuit write service rules", () => {
|
||||
isReserve: 0,
|
||||
} as never;
|
||||
},
|
||||
async update(_id: string, payload: { isReserve: boolean }) {
|
||||
reserveFlag = payload.isReserve;
|
||||
},
|
||||
} as never,
|
||||
});
|
||||
|
||||
await service.deleteDeviceRow("r1");
|
||||
assert.equal(reserveFlag, true);
|
||||
assert.deepEqual(transactionalDelete, { rowId: "r1", circuitId: "c1" });
|
||||
});
|
||||
|
||||
it("creating device row in reserve circuit clears reserve status", async () => {
|
||||
let reserveFlag = true;
|
||||
let transactionalCreate:
|
||||
| {
|
||||
circuitId: string;
|
||||
sortOrder?: number;
|
||||
name: string;
|
||||
displayName: string;
|
||||
}
|
||||
| undefined;
|
||||
const service = new CircuitWriteService({
|
||||
circuitRepository: {
|
||||
async findById() {
|
||||
@@ -165,15 +166,10 @@ describe("circuit write service rules", () => {
|
||||
isReserve: 1,
|
||||
} as never;
|
||||
},
|
||||
async update(_id: string, payload: { isReserve: boolean }) {
|
||||
reserveFlag = payload.isReserve;
|
||||
},
|
||||
} as never,
|
||||
deviceRowRepository: {
|
||||
async countByCircuit() {
|
||||
return 0;
|
||||
},
|
||||
async create() {
|
||||
createInCircuitTransactional(input: typeof transactionalCreate) {
|
||||
transactionalCreate = input;
|
||||
return "row1";
|
||||
},
|
||||
async findById() {
|
||||
@@ -196,7 +192,27 @@ describe("circuit write service rules", () => {
|
||||
powerPerUnit: 1,
|
||||
simultaneityFactor: 1,
|
||||
});
|
||||
assert.equal(reserveFlag, false);
|
||||
assert.deepEqual(transactionalCreate, {
|
||||
circuitId: "c1",
|
||||
linkedProjectDeviceId: undefined,
|
||||
sortOrder: undefined,
|
||||
name: "Load",
|
||||
displayName: "Load",
|
||||
phaseType: undefined,
|
||||
connectionKind: undefined,
|
||||
costGroup: undefined,
|
||||
category: undefined,
|
||||
level: undefined,
|
||||
roomId: undefined,
|
||||
roomNumberSnapshot: undefined,
|
||||
roomNameSnapshot: undefined,
|
||||
quantity: 1,
|
||||
powerPerUnit: 1,
|
||||
simultaneityFactor: 1,
|
||||
cosPhi: undefined,
|
||||
remark: undefined,
|
||||
overriddenFields: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("renumber uses safe bulk identifier update for swapped identifiers", async () => {
|
||||
@@ -770,6 +786,163 @@ describe("circuit write service rules", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("device row creation and reserve activation share one synchronous transaction", () => {
|
||||
const repository = new CircuitDeviceRowRepository();
|
||||
const originalTransaction = (db as unknown as { transaction: unknown }).transaction;
|
||||
|
||||
let callbackReturnedPromise = false;
|
||||
let insertedValues: { id?: string; circuitId?: string; sortOrder?: number } | undefined;
|
||||
let reserveValue: number | undefined;
|
||||
let selectCall = 0;
|
||||
|
||||
(db as unknown as { transaction: (cb: (tx: unknown) => unknown) => void }).transaction = (cb) => {
|
||||
const fakeTx = {
|
||||
select() {
|
||||
const rows = selectCall++ === 0 ? [{ id: "c1" }] : [{ sortOrder: 20 }];
|
||||
const query = {
|
||||
from() {
|
||||
return query;
|
||||
},
|
||||
where() {
|
||||
return query;
|
||||
},
|
||||
limit() {
|
||||
return query;
|
||||
},
|
||||
all() {
|
||||
return rows;
|
||||
},
|
||||
};
|
||||
return query;
|
||||
},
|
||||
insert() {
|
||||
return {
|
||||
values(values: typeof insertedValues) {
|
||||
insertedValues = values;
|
||||
return {
|
||||
run() {
|
||||
return { changes: 1 };
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
},
|
||||
update() {
|
||||
return {
|
||||
set(values: { isReserve: number }) {
|
||||
reserveValue = values.isReserve;
|
||||
return {
|
||||
where() {
|
||||
return {
|
||||
run() {
|
||||
return { changes: 1 };
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
const callbackResult = cb(fakeTx);
|
||||
callbackReturnedPromise = Boolean(
|
||||
callbackResult && typeof (callbackResult as Promise<unknown>).then === "function"
|
||||
);
|
||||
};
|
||||
|
||||
try {
|
||||
const rowId = repository.createInCircuitTransactional({
|
||||
circuitId: "c1",
|
||||
name: "Load",
|
||||
displayName: "Load",
|
||||
quantity: 1,
|
||||
powerPerUnit: 1,
|
||||
simultaneityFactor: 1,
|
||||
});
|
||||
assert.equal(callbackReturnedPromise, false);
|
||||
assert.equal(insertedValues?.id, rowId);
|
||||
assert.equal(insertedValues?.circuitId, "c1");
|
||||
assert.equal(insertedValues?.sortOrder, 30);
|
||||
assert.equal(reserveValue, 0);
|
||||
} finally {
|
||||
(db as unknown as { transaction: unknown }).transaction = originalTransaction;
|
||||
}
|
||||
});
|
||||
|
||||
it("device row deletion and reserve update share one synchronous transaction", () => {
|
||||
const repository = new CircuitDeviceRowRepository();
|
||||
const originalTransaction = (db as unknown as { transaction: unknown }).transaction;
|
||||
|
||||
let callbackReturnedPromise = false;
|
||||
let deleteCount = 0;
|
||||
let reserveValue: number | undefined;
|
||||
let selectCall = 0;
|
||||
|
||||
(db as unknown as { transaction: (cb: (tx: unknown) => unknown) => void }).transaction = (cb) => {
|
||||
const fakeTx = {
|
||||
select() {
|
||||
const rows = selectCall++ === 0 ? [{ id: "r1", circuitId: "c1" }] : [];
|
||||
const query = {
|
||||
from() {
|
||||
return query;
|
||||
},
|
||||
where() {
|
||||
return query;
|
||||
},
|
||||
limit() {
|
||||
return query;
|
||||
},
|
||||
all() {
|
||||
return rows;
|
||||
},
|
||||
};
|
||||
return query;
|
||||
},
|
||||
delete() {
|
||||
return {
|
||||
where() {
|
||||
return {
|
||||
run() {
|
||||
deleteCount += 1;
|
||||
return { changes: 1 };
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
},
|
||||
update() {
|
||||
return {
|
||||
set(values: { isReserve: number }) {
|
||||
reserveValue = values.isReserve;
|
||||
return {
|
||||
where() {
|
||||
return {
|
||||
run() {
|
||||
return { changes: 1 };
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
const callbackResult = cb(fakeTx);
|
||||
callbackReturnedPromise = Boolean(
|
||||
callbackResult && typeof (callbackResult as Promise<unknown>).then === "function"
|
||||
);
|
||||
};
|
||||
|
||||
try {
|
||||
repository.deleteFromCircuitTransactional("r1", "c1");
|
||||
assert.equal(callbackReturnedPromise, false);
|
||||
assert.equal(deleteCount, 1);
|
||||
assert.equal(reserveValue, 1);
|
||||
} finally {
|
||||
(db as unknown as { transaction: unknown }).transaction = originalTransaction;
|
||||
}
|
||||
});
|
||||
|
||||
it("tracks local edits on linked device rows as overridden fields", async () => {
|
||||
let savedOverrides: string | undefined;
|
||||
const current = {
|
||||
|
||||
Reference in New Issue
Block a user