From 1b5e79fab3083456a3b5b24f5d1bf729f7365ee1 Mon Sep 17 00:00:00 2001 From: Konsti Wohlwend Date: Fri, 19 Jun 2026 15:52:56 -0700 Subject: [PATCH] fix: changelog endpoint returns 502 silently without Sentry logging (#1618) --- .../api/latest/internal/changelog/route.tsx | 12 +++---- .../api/v1/internal/changelog.test.ts | 36 +++++++++++++++++++ 2 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 apps/e2e/tests/backend/endpoints/api/v1/internal/changelog.test.ts diff --git a/apps/backend/src/app/api/latest/internal/changelog/route.tsx b/apps/backend/src/app/api/latest/internal/changelog/route.tsx index 3ef9948a4..6698d0f9c 100644 --- a/apps/backend/src/app/api/latest/internal/changelog/route.tsx +++ b/apps/backend/src/app/api/latest/internal/changelog/route.tsx @@ -1,6 +1,7 @@ import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler"; import { yupArray, yupBoolean, yupNumber, yupObject, yupString } from "@hexclave/shared/dist/schema-fields"; import { getEnvVariable, getNodeEnvironment } from "@hexclave/shared/dist/utils/env"; +import { HexclaveAssertionError } from "@hexclave/shared/dist/utils/errors"; import * as fs from "fs/promises"; import * as path from "path"; @@ -131,11 +132,10 @@ export const GET = createSmartRouteHandler({ method: yupString().oneOf(["GET"]).defined(), }), response: yupObject({ - statusCode: yupNumber().oneOf([200, 502]).defined(), + statusCode: yupNumber().oneOf([200]).defined(), bodyType: yupString().oneOf(["json"]).defined(), body: yupObject({ - entries: yupArray(changelogEntrySchema).optional(), - error: yupString().optional(), + entries: yupArray(changelogEntrySchema).defined(), }).defined(), }), handler: async () => { @@ -179,11 +179,7 @@ export const GET = createSmartRouteHandler({ }); if (!response.ok) { - return { - statusCode: 502, - bodyType: "json", - body: { error: "Failed to download changelog" }, - } as const; + throw new HexclaveAssertionError(`Changelog fetch failed with status ${response.status}`, { changelogUrl }); } const content = await response.text(); diff --git a/apps/e2e/tests/backend/endpoints/api/v1/internal/changelog.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/internal/changelog.test.ts new file mode 100644 index 000000000..49a965183 --- /dev/null +++ b/apps/e2e/tests/backend/endpoints/api/v1/internal/changelog.test.ts @@ -0,0 +1,36 @@ +import { describe } from "vitest"; +import { it } from "../../../../../helpers"; +import { niceBackendFetch } from "../../../../backend-helpers"; + +describe("GET /api/v1/internal/changelog", () => { + it("should return changelog entries with expected structure", async ({ expect }) => { + const response = await niceBackendFetch("/api/v1/internal/changelog", { + method: "GET", + }); + + expect(response.status).toBe(200); + expect(response.body).toHaveProperty("entries"); + expect(Array.isArray(response.body.entries)).toBe(true); + expect(response.body.entries.length).toBeGreaterThan(0); + + const entry = response.body.entries[0]; + expect(entry).toHaveProperty("version"); + expect(entry).toHaveProperty("type"); + expect(entry).toHaveProperty("markdown"); + expect(entry).toHaveProperty("bulletCount"); + expect(typeof entry.version).toBe("string"); + expect(["major", "minor", "patch"]).toContain(entry.type); + expect(typeof entry.markdown).toBe("string"); + expect(typeof entry.bulletCount).toBe("number"); + expect(entry.bulletCount).toBeGreaterThanOrEqual(0); + }); + + it("should return at most 8 entries", async ({ expect }) => { + const response = await niceBackendFetch("/api/v1/internal/changelog", { + method: "GET", + }); + + expect(response.status).toBe(200); + expect(response.body.entries.length).toBeLessThanOrEqual(8); + }); +});