fix(news): persist publication requests and retry cache delivery
CI / check (push) Successful in 3m12s
CI / deploy (push) Successful in 1m13s
CI / publish-container (push) Successful in 42s

This commit is contained in:
Simo committed 2026-09-13 18:33:45 +02:00
1 parent c977fe95ba
commit 52f6d1491f
40 files changed
+733 -134

No files matched your search

+4 -1
View File
@@ -53,7 +53,10 @@ export function operationHash(value: unknown) {
export function retryDelay(attempt: number) {
return Math.min(3600, 60 * 2 ** Math.max(0, attempt - 1));
}
export type EffectTopic = "catalog.refresh" | "catalog.export.request";
export type EffectTopic =
| "catalog.refresh"
| "catalog.export.request"
| "news.refresh";
export interface EffectClaim {
id: string;
token: string;
+56
View File
@@ -0,0 +1,56 @@
import { beforeEach, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
claim: vi.fn(),
complete: vi.fn(),
fail: vi.fn(),
news: vi.fn(),
catalog: vi.fn(),
exportEnabled: vi.fn(),
request: vi.fn(),
error: vi.fn(),
}));
vi.mock("./server", () => ({
effectRepository: {
claim: mocks.claim,
complete: mocks.complete,
fail: mocks.fail,
},
}));
vi.mock("@/lib/services/news-cache", () => ({
refreshNewsCacheForDelivery: mocks.news,
}));
vi.mock("@/features/catalog/server/sync-status", () => ({
sendCatalogUpdate: mocks.catalog,
}));
vi.mock("@/lib/services/catalog-git-queue", () => ({
catalogExportEnabled: mocks.exportEnabled,
catalogExportQueue: () => ({ request: mocks.request }),
}));
vi.mock("@/lib/logger", () => ({ logger: { error: mocks.error } }));
import { drainOperationEffects } from "./worker";
const effect = {
id: "one",
token: "lease",
attempts: 1,
topic: "news.refresh",
};
beforeEach(() => {
vi.resetAllMocks();
mocks.claim.mockResolvedValueOnce(effect).mockResolvedValue(null);
mocks.news.mockResolvedValue(undefined);
});
it("invalidates the shared news cache before acknowledging delivery", async () => {
await drainOperationEffects();
expect(mocks.news).toHaveBeenCalledTimes(1);
expect(mocks.complete).toHaveBeenCalledWith(effect);
expect(mocks.fail).not.toHaveBeenCalled();
});
it("keeps a failed news invalidation retryable", async () => {
mocks.news.mockRejectedValue(new Error("Redis unavailable"));
await drainOperationEffects();
expect(mocks.fail).toHaveBeenCalledWith(effect);
expect(mocks.complete).not.toHaveBeenCalled();
});
+3
View File
@@ -5,6 +5,7 @@ import {
catalogExportEnabled,
catalogExportQueue,
} from "@/lib/services/catalog-git-queue";
import { refreshNewsCacheForDelivery } from "@/lib/services/news-cache";
import { dispatchEffects } from "./dispatcher";
import { effectRepository } from "./server";
@@ -19,6 +20,8 @@ export async function drainOperationEffects() {
throw new Error("Hotel update not delivered");
} else if (claim.topic === "catalog.export.request") {
if (catalogExportEnabled()) await catalogExportQueue().request();
} else if (claim.topic === "news.refresh") {
await refreshNewsCacheForDelivery();
} else throw new Error("Unknown delivery topic");
});
} catch (error) {