From 88ac5ac1ba82a7dac230c3572f1162f30ed29cc5 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sun, 2 Aug 2026 14:56:12 +0200 Subject: [PATCH] feat: add recent furni resync client contract --- src/lib/admin/recent-furni-resync.test.ts | 46 +++++++++++++++++++++ src/lib/admin/recent-furni-resync.ts | 49 +++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 src/lib/admin/recent-furni-resync.test.ts create mode 100644 src/lib/admin/recent-furni-resync.ts diff --git a/src/lib/admin/recent-furni-resync.test.ts b/src/lib/admin/recent-furni-resync.test.ts new file mode 100644 index 00000000..5be6e324 --- /dev/null +++ b/src/lib/admin/recent-furni-resync.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, it, vi } from "vitest"; +import { + RECENT_FURNI_RESYNC_URL, + requestRecentFurniResync, +} from "./recent-furni-resync"; + +describe("requestRecentFurniResync", () => { + it("posts to the fixed seven-day resync endpoint and normalizes the result", async () => { + const fetcher = vi.fn(async () => + Response.json({ + ok: true, + mode: "days", + days: 7, + examined: 4, + resynced: 3, + failed: 1, + rconOk: false, + errors: [{ classname: "chair", message: "invalid entry" }], + }), + ); + + await expect(requestRecentFurniResync(fetcher)).resolves.toEqual({ + examined: 4, + resynced: 3, + failed: 1, + rconOk: false, + errors: [{ classname: "chair", message: "invalid entry" }], + }); + expect(RECENT_FURNI_RESYNC_URL).toBe( + "/api/admin/import/furni/resync?days=7", + ); + expect(fetcher).toHaveBeenCalledWith(RECENT_FURNI_RESYNC_URL, { + method: "POST", + }); + }); + + it("throws the API error when the request fails", async () => { + const fetcher = vi.fn(async () => + Response.json({ error: "Forbidden" }, { status: 403 }), + ); + + await expect(requestRecentFurniResync(fetcher)).rejects.toThrow( + "Forbidden", + ); + }); +}); diff --git a/src/lib/admin/recent-furni-resync.ts b/src/lib/admin/recent-furni-resync.ts new file mode 100644 index 00000000..f22bca97 --- /dev/null +++ b/src/lib/admin/recent-furni-resync.ts @@ -0,0 +1,49 @@ +export const RECENT_FURNI_RESYNC_URL = "/api/admin/import/furni/resync?days=7"; + +export interface RecentFurniResyncError { + classname: string; + message: string; +} + +export interface RecentFurniResyncResult { + examined: number; + resynced: number; + failed: number; + rconOk: boolean; + errors: RecentFurniResyncError[]; +} + +type AdminFetcher = ( + input: RequestInfo | URL, + init?: RequestInit, +) => Promise; + +export async function requestRecentFurniResync( + fetcher: AdminFetcher, +): Promise { + const response = await fetcher(RECENT_FURNI_RESYNC_URL, { method: "POST" }); + const payload = (await response.json()) as Record; + if (!response.ok) { + throw new Error( + typeof payload.error === "string" ? payload.error : "Furni resync failed", + ); + } + + const errors = Array.isArray(payload.errors) + ? payload.errors.filter( + (value): value is RecentFurniResyncError => + typeof value === "object" && + value !== null && + typeof (value as RecentFurniResyncError).classname === "string" && + typeof (value as RecentFurniResyncError).message === "string", + ) + : []; + + return { + examined: Number(payload.examined) || 0, + resynced: Number(payload.resynced) || 0, + failed: Number(payload.failed) || 0, + rconOk: payload.rconOk === true, + errors, + }; +}