Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,145 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import { TWOPC_CODES, TwoPcError } from "../error-codes/error-codes.js";
import { createTwoPcInstruments } from "../instruments/instruments.js";
import type { RegisteredTwoPcParticipant } from "../types/types.types.js";
import type { ParticipantPrepareOutcome } from "./prepare.js";
import { isUnknownPreparedTransaction, rollbackPhaseTwo } from "./rollback.js";
const INSTRUMENTS = createTwoPcInstruments({
outstandingPreparedSnapshot: () => 0,
});
const OUTCOME: ParticipantPrepareOutcome = {
participantId: "billing",
vote: "commit",
prepared: true,
gid: "2pc-h-t-billing",
};
function makeParticipant(opts: { fail?: unknown } = {}) {
const calls: string[] = [];
const p: RegisteredTwoPcParticipant = {
id: "billing",
voterMode: "writer",
connection: {
engine: "postgres",
run: async (sql: string) => {
calls.push(sql);
if (opts.fail !== undefined) throw opts.fail;
return undefined;
},
},
prepare: async () => ({ vote: "commit", writeIntent: true }),
};
return { participant: p, calls };
}
describe("rollbackPhaseTwo", () => {
it("issues ROLLBACK PREPARED against a prepared writer", {
timeout: 5000,
}, async () => {
const { participant, calls } = makeParticipant();
await rollbackPhaseTwo({
txId: "t",
participant,
outcome: OUTCOME,
instruments: INSTRUMENTS,
});
expect(calls[0]).toBe("ROLLBACK PREPARED '2pc-h-t-billing'");
});
it("is a no-op when outcome.prepared === false", {
timeout: 5000,
}, async () => {
const { participant, calls } = makeParticipant();
await rollbackPhaseTwo({
txId: "t",
participant,
outcome: { ...OUTCOME, prepared: false, gid: "" },
instruments: INSTRUMENTS,
});
expect(calls).toEqual([]);
});
it("treats 42704 (undefined_object) as a no-op (idempotency)", {
timeout: 5000,
}, async () => {
const cause = Object.assign(new Error("does not exist"), {
code: "42704",
});
const { participant } = makeParticipant({ fail: cause });
await expect(
rollbackPhaseTwo({
txId: "t",
participant,
outcome: OUTCOME,
instruments: INSTRUMENTS,
})
).resolves.toBeUndefined();
});
it("treats 'does not exist' message-only errors as a no-op", {
timeout: 5000,
}, async () => {
const cause = new Error(
"prepared transaction with identifier 'x' does not exist"
);
const { participant } = makeParticipant({ fail: cause });
await expect(
rollbackPhaseTwo({
txId: "t",
participant,
outcome: OUTCOME,
instruments: INSTRUMENTS,
})
).resolves.toBeUndefined();
});
it("wraps a non-idempotency SQL throw into TWOPC.ROLLBACK_FAILED", {
timeout: 5000,
}, async () => {
const cause = new Error("connection broken");
const { participant } = makeParticipant({ fail: cause });
try {
await rollbackPhaseTwo({
txId: "t",
participant,
outcome: OUTCOME,
instruments: INSTRUMENTS,
});
throw new Error("should throw");
} catch (err) {
expect(err).toBeInstanceOf(TwoPcError);
expect((err as TwoPcError).twoPcCode).toBe(
TWOPC_CODES.ROLLBACK_FAILED
);
expect((err as TwoPcError).cause).toBe(cause);
}
});
});
describe("isUnknownPreparedTransaction", () => {
it("returns true for SQLSTATE 42704", { timeout: 5000 }, () => {
expect(isUnknownPreparedTransaction({ code: "42704" })).toBe(true);
});
it("returns true for messages containing 'does not exist'", {
timeout: 5000,
}, () => {
expect(
isUnknownPreparedTransaction({ message: "tx X does not exist" })
).toBe(true);
});
it("returns false for null / undefined / primitives", {
timeout: 5000,
}, () => {
expect(isUnknownPreparedTransaction(null)).toBe(false);
expect(isUnknownPreparedTransaction(undefined)).toBe(false);
expect(isUnknownPreparedTransaction(42)).toBe(false);
});
it("returns false for unrelated errors", { timeout: 5000 }, () => {
expect(isUnknownPreparedTransaction(new Error("other"))).toBe(false);
});
});