feat(housekeeping): deliver content vertical
This commit is contained in:
1 parent
bdab924bdf
commit
fd68819d9b
67 files changed
+5742
-1916
No files matched your search
@@ -0,0 +1,169 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { AuditEntry } from "@/lib/services/audit";
|
||||
import type { HousekeepingCapabilityContext } from "../../../foundation/contracts";
|
||||
import { CONTENT_MUTATION_OPERATIONS } from "./mutations";
|
||||
import {
|
||||
CONTENT_DATABASE_OPERATIONS,
|
||||
CONTENT_EXTERNAL_OPERATIONS,
|
||||
CONTENT_MIXED_OPERATIONS,
|
||||
ContentCommittedExternalFailure,
|
||||
createContentProductionMutationAdapter,
|
||||
} from "./mutations-production";
|
||||
|
||||
const capability: HousekeepingCapabilityContext = {
|
||||
actor: { id: 42, username: "operator", rank: 7 },
|
||||
isSuperAdmin: false,
|
||||
has: () => true,
|
||||
hasAny: () => true,
|
||||
hasAll: () => true,
|
||||
};
|
||||
const mutationContext = {
|
||||
capability,
|
||||
correlationId: "production-matrix",
|
||||
legacy: false,
|
||||
};
|
||||
|
||||
function dependencies() {
|
||||
const transactionToken = { transaction: true };
|
||||
const writeAudit = vi.fn(
|
||||
async (_entry: AuditEntry, _transaction?: unknown) => undefined,
|
||||
);
|
||||
const executeOperation = vi.fn(async (operation: string) => ({
|
||||
before: { operation, state: "before" },
|
||||
after: { operation, state: "after" },
|
||||
output: { id: "18446744073709551615" },
|
||||
}));
|
||||
const transaction = vi.fn(async (run) => run(transactionToken));
|
||||
return {
|
||||
transactionToken,
|
||||
writeAudit,
|
||||
executeOperation,
|
||||
transaction,
|
||||
adapter: createContentProductionMutationAdapter({
|
||||
transaction,
|
||||
writeAudit,
|
||||
executeOperation,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
describe("Content production mutation adapter", () => {
|
||||
it("classifies every operation exactly once", () => {
|
||||
const classified = [
|
||||
...CONTENT_DATABASE_OPERATIONS,
|
||||
...CONTENT_EXTERNAL_OPERATIONS,
|
||||
...CONTENT_MIXED_OPERATIONS,
|
||||
];
|
||||
expect([...classified].sort()).toEqual(
|
||||
[...CONTENT_MUTATION_OPERATIONS].sort(),
|
||||
);
|
||||
expect(new Set(classified).size).toBe(classified.length);
|
||||
});
|
||||
|
||||
it("executes every production operation and serializes identifiers", async () => {
|
||||
const deps = dependencies();
|
||||
for (const operation of CONTENT_MUTATION_OPERATIONS) {
|
||||
const snapshot = await deps.adapter.execute(
|
||||
operation,
|
||||
{ action: "update" },
|
||||
mutationContext,
|
||||
);
|
||||
expect(snapshot.output?.id, operation).toBe("18446744073709551615");
|
||||
}
|
||||
expect(deps.executeOperation).toHaveBeenCalledTimes(
|
||||
CONTENT_MUTATION_OPERATIONS.length,
|
||||
);
|
||||
});
|
||||
|
||||
it("commits database mutation and canonical success audit in one transaction", async () => {
|
||||
const deps = dependencies();
|
||||
await deps.adapter.execute(
|
||||
"article.change",
|
||||
{ action: "update", id: "18446744073709551615" },
|
||||
mutationContext,
|
||||
);
|
||||
|
||||
expect(deps.transaction).toHaveBeenCalledTimes(1);
|
||||
expect(deps.executeOperation).toHaveBeenCalledWith(
|
||||
"article.change",
|
||||
expect.anything(),
|
||||
mutationContext,
|
||||
deps.transactionToken,
|
||||
);
|
||||
expect(deps.writeAudit).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "content.article.change",
|
||||
outcome: "success",
|
||||
domain: "content",
|
||||
correlationId: "production-matrix",
|
||||
}),
|
||||
deps.transactionToken,
|
||||
);
|
||||
});
|
||||
|
||||
it("persists correlated intent and truthful outcome around external storage writes", async () => {
|
||||
const deps = dependencies();
|
||||
await deps.adapter.execute(
|
||||
"translation.cms.save",
|
||||
{ locale: "en", data: { welcome: "Hello" } },
|
||||
mutationContext,
|
||||
);
|
||||
|
||||
expect(deps.transaction).not.toHaveBeenCalled();
|
||||
expect(deps.writeAudit.mock.calls.map(([entry]) => entry.outcome)).toEqual([
|
||||
"intent",
|
||||
"success",
|
||||
]);
|
||||
expect(
|
||||
deps.writeAudit.mock.calls.every(
|
||||
([entry]) => entry.correlationId === "production-matrix",
|
||||
),
|
||||
).toBe(true);
|
||||
expect(JSON.stringify(deps.writeAudit.mock.calls)).not.toContain("Hello");
|
||||
});
|
||||
|
||||
it("returns typed partial when database committed but the external effect failed", async () => {
|
||||
const deps = dependencies();
|
||||
deps.executeOperation.mockRejectedValueOnce(
|
||||
new ContentCommittedExternalFailure({
|
||||
before: { value: "/old.ico" },
|
||||
after: { value: "/new.ico" },
|
||||
}),
|
||||
);
|
||||
const snapshot = await deps.adapter.execute(
|
||||
"favicon.save",
|
||||
{ file: { name: "favicon.ico" } },
|
||||
mutationContext,
|
||||
);
|
||||
|
||||
expect(snapshot).toMatchObject({
|
||||
before: { value: "/old.ico" },
|
||||
after: { value: "/new.ico" },
|
||||
completion: {
|
||||
status: "partial",
|
||||
external: "failed",
|
||||
audit: "persisted",
|
||||
},
|
||||
});
|
||||
expect(deps.writeAudit.mock.calls.at(-1)?.[0]).toMatchObject({
|
||||
outcome: "partial",
|
||||
});
|
||||
});
|
||||
|
||||
it("records failure without optimistic after-state when external storage fails before commit", async () => {
|
||||
const deps = dependencies();
|
||||
deps.executeOperation.mockRejectedValueOnce(new Error("disk offline"));
|
||||
await expect(
|
||||
deps.adapter.execute(
|
||||
"media.upload",
|
||||
{ file: { name: "image.png" } },
|
||||
mutationContext,
|
||||
),
|
||||
).rejects.toThrow("disk offline");
|
||||
expect(deps.writeAudit.mock.calls.at(-1)?.[0]).toMatchObject({
|
||||
outcome: "failure",
|
||||
before: undefined,
|
||||
after: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user