fix: keep tierlist saves consistent
Build and Deploy Next Preview / build-deploy (push) Successful in 2m52s
Build and Deploy Next Preview / build-deploy (push) Successful in 2m52s
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import { describe, expect, spyOn, test } from "bun:test";
|
||||
import { PgDialect } from "drizzle-orm/pg-core";
|
||||
import type { tierlistStates } from "./db/schema";
|
||||
import {
|
||||
finishTierlistWrite,
|
||||
lockTierlistList,
|
||||
normalizeTierlistPlacements,
|
||||
resolveTierlistStateTarget,
|
||||
validateTierlistStateInput,
|
||||
} from "./tierlist-write";
|
||||
|
||||
type StateRow = typeof tierlistStates.$inferSelect;
|
||||
|
||||
const stateRow = (uuid: string, list: string, ref = "char-1") =>
|
||||
({ uuid, list, ref }) as StateRow;
|
||||
|
||||
describe("tierlist write consistency helpers", () => {
|
||||
test("validates list, ref, and optional UUID before writing", () => {
|
||||
expect(
|
||||
validateTierlistStateInput({
|
||||
list: "v1",
|
||||
ref: "char-1",
|
||||
uuid: undefined,
|
||||
}),
|
||||
).toEqual({ list: "v1", ref: "char-1" });
|
||||
expect(() =>
|
||||
validateTierlistStateInput({ list: "", ref: "char-1" }),
|
||||
).toThrow();
|
||||
expect(() => validateTierlistStateInput({ list: "v1", ref: "" })).toThrow();
|
||||
expect(() =>
|
||||
validateTierlistStateInput({ list: "v1", ref: "char-1", uuid: "bad" }),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
test("rejects cross-list UUIDs and canonicalizes duplicate refs", () => {
|
||||
const uuidMatch = stateRow("uuid-1", "v1");
|
||||
const canonicalRefMatch = stateRow("uuid-0", "v1");
|
||||
expect(() =>
|
||||
resolveTierlistStateTarget(stateRow("uuid-2", "v2"), uuidMatch, "v1"),
|
||||
).toThrow("belongs to another version");
|
||||
expect(resolveTierlistStateTarget(uuidMatch, undefined, "v1")).toBe(
|
||||
uuidMatch,
|
||||
);
|
||||
expect(resolveTierlistStateTarget(uuidMatch, canonicalRefMatch, "v1")).toBe(
|
||||
canonicalRefMatch,
|
||||
);
|
||||
expect(resolveTierlistStateTarget(undefined, uuidMatch, "v1")).toBe(
|
||||
uuidMatch,
|
||||
);
|
||||
});
|
||||
|
||||
test("uses one transaction-scoped advisory lock per list before mutations", async () => {
|
||||
const order: string[] = [];
|
||||
const queries: unknown[] = [];
|
||||
await lockTierlistList(
|
||||
{
|
||||
execute: async (query) => {
|
||||
order.push("lock");
|
||||
queries.push(query);
|
||||
},
|
||||
},
|
||||
"v1",
|
||||
);
|
||||
order.push("write");
|
||||
const compiled = new PgDialect().sqlToQuery(queries[0] as never);
|
||||
expect(compiled.sql).toContain("pg_advisory_xact_lock");
|
||||
expect(compiled.sql).toContain("hashtextextended");
|
||||
expect(compiled.params).toContain("v1");
|
||||
expect(order).toEqual(["lock", "write"]);
|
||||
});
|
||||
|
||||
test("normalizes placements without mutating the caller's untiered data", () => {
|
||||
const placements = { S: ["a"], untiered: ["b"] };
|
||||
expect(normalizeTierlistPlacements(placements)).toEqual({ S: ["a"] });
|
||||
expect(placements).toEqual({ S: ["a"], untiered: ["b"] });
|
||||
});
|
||||
|
||||
test("returns persisted success when publication fails after commit", async () => {
|
||||
const errorLog = spyOn(console, "error").mockImplementation(() => {});
|
||||
try {
|
||||
const result = await finishTierlistWrite({
|
||||
list: "v1",
|
||||
operation: "state",
|
||||
data: [{ uuid: "state-1" }],
|
||||
audit: async () => {},
|
||||
revalidate: () => {},
|
||||
publish: async () => {
|
||||
throw new Error("redis unavailable");
|
||||
},
|
||||
});
|
||||
expect(result).toEqual({
|
||||
persisted: true,
|
||||
data: [{ uuid: "state-1" }],
|
||||
publication: "failed",
|
||||
});
|
||||
expect(errorLog).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
errorLog.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
test("reports sent when post-commit publication succeeds", async () => {
|
||||
const result = await finishTierlistWrite({
|
||||
list: "v1",
|
||||
operation: "placements",
|
||||
data: { S: ["char-1"] },
|
||||
audit: async () => {},
|
||||
revalidate: () => {},
|
||||
publish: async () => 1,
|
||||
});
|
||||
expect(result).toEqual({
|
||||
persisted: true,
|
||||
data: { S: ["char-1"] },
|
||||
publication: "sent",
|
||||
});
|
||||
});
|
||||
|
||||
test("does not turn a precommit transaction failure into persisted success", async () => {
|
||||
const failure = new Error("transaction failed");
|
||||
await expect(
|
||||
lockTierlistList({ execute: async () => Promise.reject(failure) }, "v1"),
|
||||
).rejects.toBe(failure);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user