fix(web): handle 404 and 501 gracefully in API loaders

Demo mode returns 404 for unimplemented endpoints instead of 501.
Rename isNotImplemented to isNotAvailable covering both status codes,
and use apiJsonOrNull in workflow-runs loader.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-04-15 07:54:11 -04:00
parent 549d85aaa7
commit cfd0005319
No known key found for this signature in database
3 changed files with 14 additions and 13 deletions

View file

@ -1,21 +1,21 @@
import { afterEach, describe, expect, mock, test } from "bun:test";
import { getAuthConfig, isNotImplemented, loginDevToken } from "./api";
import { getAuthConfig, isNotAvailable, loginDevToken } from "./api";
afterEach(() => {
mock.restore();
});
describe("isNotImplemented", () => {
describe("isNotAvailable", () => {
test("returns true for 501 status", () => {
expect(isNotImplemented(501)).toBe(true);
expect(isNotAvailable(501)).toBe(true);
});
test("returns true for 404 status", () => {
expect(isNotAvailable(404)).toBe(true);
});
test("returns false for 200 status", () => {
expect(isNotImplemented(200)).toBe(false);
});
test("returns false for 404 status", () => {
expect(isNotImplemented(404)).toBe(false);
expect(isNotAvailable(200)).toBe(false);
});
});

View file

@ -27,8 +27,8 @@ export async function apiJson<T>(path: string, options?: ApiOptions): Promise<T>
return response.json() as Promise<T>;
}
export function isNotImplemented(status: number): boolean {
return status === 501;
export function isNotAvailable(status: number): boolean {
return status === 404 || status === 501;
}
export async function apiJsonOrNull<T>(
@ -36,7 +36,7 @@ export async function apiJsonOrNull<T>(
options?: ApiOptions,
): Promise<T | null> {
const response = await apiFetch(path, options);
if (isNotImplemented(response.status)) {
if (isNotAvailable(response.status)) {
return null;
}
if (!response.ok) {

View file

@ -3,11 +3,12 @@ import { ChevronDownIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outlin
import { Link, useParams } from "react-router";
import { ciConfig, columnNames, deriveCiStatus, mapRunListItem, statusColors } from "../data/runs";
import type { ColumnStatus, RunWithStatus } from "../data/runs";
import { apiJson } from "../api";
import { apiJsonOrNull } from "../api";
import type { PaginatedRunList } from "@qltysh/fabro-api-client";
export async function loader({ request, params }: any) {
const { data: apiRuns } = await apiJson<PaginatedRunList>(`/workflows/${params.name}/runs`, { request });
const result = await apiJsonOrNull<PaginatedRunList>(`/workflows/${params.name}/runs`, { request });
const apiRuns = result?.data ?? [];
const runs: RunWithStatus[] = apiRuns.map((r) => ({
...mapRunListItem(r),
status: r.status as ColumnStatus,