From 54851779fb7536af2004794b5b0813a97fd1daf5 Mon Sep 17 00:00:00 2001 From: Developer Date: Sun, 12 Jul 2026 16:30:59 +0000 Subject: [PATCH] fix(build): JellyseerStatsResponse export/import spelling + test mock type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The frontend production build (tsc -b) was failing, which blocked deployment: - api/jellyseerr.ts exported `JellyseerrStatsResponse` (double-r) while every import used `JellyseerStatsResponse` (single-r) — a mismatch TS reported as "no exported member" (with a misleading identical-name suggestion). The sibling types (JellyseerStat, JellyseerRecentRequest) are single-r, so align the export to single-r. (tsc --noEmit missed it because the root tsconfig is solution-style; tsc -b builds the app project and catches it.) - RequestsTab.test.tsx's useJellyseerrStats mock returned a partial object that didn't satisfy UseQueryResult's full shape; cast via a typed helper. `npm run build` (tsc -b && vite build) now succeeds; 184/184 tests + ESLint clean. --- frontend/src/api/.pi-map.index.md | 2 +- frontend/src/api/.pi-map.md | 6 ++-- frontend/src/api/jellyseerr.ts | 6 ++-- .../__tests__/RequestsTab.test.tsx | 30 +++++++++++++------ 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/frontend/src/api/.pi-map.index.md b/frontend/src/api/.pi-map.index.md index 1e82288..b463f4e 100644 --- a/frontend/src/api/.pi-map.index.md +++ b/frontend/src/api/.pi-map.index.md @@ -2,7 +2,7 @@ dir: frontend/src/api ## role -Centralized API client layer providing typed functions for frontend communication with various backend services and external integrations. +Typed API client layer that centralizes all backend communication for the frontend application across multiple service domains. ## parent index: frontend/src/.pi-map.index.md map: frontend/src/.pi-map.md diff --git a/frontend/src/api/.pi-map.md b/frontend/src/api/.pi-map.md index 25d5950..9347cca 100644 --- a/frontend/src/api/.pi-map.md +++ b/frontend/src/api/.pi-map.md @@ -4,18 +4,18 @@ dir: frontend/src/api index: frontend/src/api/.pi-map.index.md ## role -Centralized API client layer providing typed functions for frontend communication with various backend services and external integrations. +Typed API client layer that centralizes all backend communication for the frontend application across multiple service domains. ## files - authentik.ts | API client providing functions to fetch users, send messages, and check message status from the Authentik service. | exp: AuthentikUser, AuthentikUsersResponse, AuthentikMessageInput, AuthentikMessageResponse, func:fetchAuthentikUsers(serviceId: string, params: { search?: string; page?: number; page_size?: number }) → Promise, call:get, call:String, func:sendAuthentikMessage(serviceId: string, input: AuthentikMessageInput) → Promise, call:post, func:fetchAuthentikMessageStatus(serviceId: string) → Promise>, call:get | dep: ./shared - backups.ts | API client functions for fetching and managing backup jobs, runs, alerts, and dashboard summaries. | exp: func:fetchBackupJobs(serviceId: string) → Promise, call:get, func:fetchBackupJob(jobId: string) → Promise<{ job: BackupJob; runs: BackupRun[] }>, call:get, func:fetchBackupRuns(jobId: string, status: string, serviceId: string) → Promise, call:get, func:fetchBackupRun(runId: string) → Promise, call:get, func:fetchBackupAlerts(jobId: string, acknowledged: boolean, severity: string, serviceId: string) → Promise, call:get, call:String, func:acknowledgeBackupAlert(alertId: string) → Promise, call:post, func:fetchBackupDashboard() → Promise, call:get | dep: ./shared, ../types/backups - client.ts | Typed API client providing functions for interacting with a FastAPI backend across dashboard, monitoring, media, files, jobs, and observability endpoints. | exp: fetchCounts, fetchLibraries, fetchActivity, fetchUsers, fetchNowPlaying, fetchMonitoringMachines, fetchAppVersion, fetchDashboardShortcuts, saveDashboardShortcut, deleteDashboardShortcut, fetchMonitoringSettings, fetchSSHKeys, generateSSHKey, saveSSHKey, deleteSSHKey, fetchSavedTasks, fetchSavedTaskRuns, saveTask, deleteTask, runTask, saveMonitoringMachine, testMonitoringMachineSSH, deleteMonitoringMachine, resetLocalDatabase, fetchMediaStatus, buildMediaIndex, stopMediaIndexBuild, forceStopMediaIndexBuild, queryMedia, fetchDirectoryListing, fetchFfprobe, fetchStat, resolvePath, fetchJobTemplates, runJob, fetchUserMessageQueueStatus, sendUserMessage, fetchAlertmanagerAlerts, fetchAlertmanagerStatus, fetchPrometheusStatus, fetchPrometheusTargets | dep: ../types, ./shared - dashboards.ts | API client providing CRUD operations for named dashboards via REST endpoints. | exp: NamedDashboard, NamedDashboardInput, func:fetchDashboards() → Promise, call:get, func:fetchDashboardBySlug(slug: string) → Promise, call:get, call:encodeURIComponent, func:createDashboard(input: NamedDashboardInput) → Promise, call:post, func:updateDashboard(input: NamedDashboardInput) → Promise, call:put, func:deleteDashboard(id: string) → Promise<{ status: string }>, call:del | dep: ./shared -- jellyseerr.ts | Fetches Jellyseerr request statistics and recent requests for a Jellyfin service instance via an API endpoint. | exp: JellyseerStat, JellyseerRecentRequest, JellyseerrStatsResponse, func:fetchJellyseerrStats(jellyfinServiceId: string) → Promise, call:get | dep: ./shared, shared +- jellyseerr.ts | Fetches Jellyseerr request statistics and recent requests for a Jellyfin service instance via an API endpoint. | exp: JellyseerStat, JellyseerRecentRequest, JellyseerStatsResponse, func:fetchJellyseerrStats(jellyfinServiceId: string) → Promise, call:get | dep: ./shared - services.ts | API client functions for CRUD operations and testing of service instances. | exp: func:fetchServiceTypes() → Promise, call:get, func:fetchServiceInstances(serviceType: string) → Promise, call:get, func:createServiceInstance(input: ServiceInstanceInput) → Promise, call:post, func:updateServiceInstance(input: ServiceInstanceInput) → Promise, call:put, raise:Error, func:deleteServiceInstance(serviceId: string) → Promise<{ status: string }>, call:del, func:testServiceInstance(input: ServiceInstanceInput) → Promise, call:post | dep: ./shared, ../types - shared.ts | Provides shared API helper functions (GET, POST, PUT, DELETE, etc.) that automatically attach OIDC auth tokens and handle URL building and error parsing for backend requests. | exp: API_BASE, func:buildUrl(path: string, params: Record) → string, call:isAbsoluteUrl, call:Object.entries, call:url.searchParams.set, call:url.toString, func:readErrorDetail(response: Response) → Promise, call:response.text, call:JSON.parse, call:detail.trim, func:buildHeaders(isJsonBody: boolean) → Headers, call:getAccessToken, call:headers.set, func:get(path: string, params: Record) → Promise, call:fetch, call:buildUrl, call:buildHeaders, call:response.json, raise:Error, func:post(path: string, body: unknown) → Promise, call:fetch, call:buildUrl, call:buildHeaders, call:JSON.stringify, call:response.json, raise:Error, func:postForm(path: string, body: FormData) → Promise, call:fetch, call:buildUrl, call:buildHeaders, call:response.json, raise:Error, func:put(path: string, body: unknown) → Promise, call:fetch, call:buildUrl, call:buildHeaders, call:JSON.stringify, call:response.json, raise:Error, func:del(path: string) → Promise, call:fetch, call:buildUrl, call:buildHeaders, call:response.json, raise:Error | dep: ../auth, getAccessToken (from ../auth), fetch API, Headers API, URL API, import.meta.env - widgets.ts | API client module providing CRUD operations for widget instances, widget references, builtin widget kinds, and widget data retrieval. | exp: WidgetReference, WidgetReferenceInput, func:fetchBuiltinWidgetKinds() → Promise< BuiltinWidgetKindInfo[] >, call:get, func:fetchWidgetInstances(serviceId: string, scope: "dashboard" | "service") → Promise, call:get, func:createWidgetInstance(input: WidgetInstanceInput) → Promise, call:post, func:updateWidgetInstance(input: WidgetInstanceInput) → Promise, call:put, raise:Error, func:deleteWidgetInstance(widgetId: string) → Promise<{ status: string }>, call:del, func:fetchWidgetData(widgetId: string) → Promise, call:get, func:fetchWidgetReferences(dashboardScope: string) → Promise, call:get, func:createWidgetReference(input: WidgetReferenceInput) → Promise, call:post, func:deleteWidgetReference(referenceId: string) → Promise<{ status: string }>, call:del, func:detachWidgetReference(referenceId: string) → Promise, call:post, func:updateWidgetReference(referenceId: string, sortOrder: number) → Promise, call:put | dep: ./shared, ../types ## arch -Modular per-domain API modules (authentik, backups, dashboards, jellyseerr, services, widgets) built on a shared HTTP client that handles OIDC token injection, URL construction, and error parsing. +Modular domain-based API clients built on a shared HTTP helper that handles OIDC authentication, URL building, and error parsing, with each module exposing typed functions for specific service endpoints. ## tags fetch, call:get, widget, dashboard, call:build, authentik, delete, call:post ## symbols diff --git a/frontend/src/api/jellyseerr.ts b/frontend/src/api/jellyseerr.ts index 4abc8ee..38ac799 100644 --- a/frontend/src/api/jellyseerr.ts +++ b/frontend/src/api/jellyseerr.ts @@ -15,7 +15,7 @@ export interface JellyseerRecentRequest { created_at?: number | string; } -export interface JellyseerrStatsResponse { +export interface JellyseerStatsResponse { stats: JellyseerStat[]; recent: JellyseerRecentRequest[]; detail?: string | null; @@ -24,8 +24,8 @@ export interface JellyseerrStatsResponse { /** Fetch Jellyseerr request stats for a Jellyfin service instance. */ export async function fetchJellyseerrStats( jellyfinServiceId?: string, -): Promise { - return get( +): Promise { + return get( "/api/jellyseerr/stats", jellyfinServiceId ? { jellyfin_service_id: jellyfinServiceId } : undefined, ); diff --git a/frontend/src/pages/service-tabs/__tests__/RequestsTab.test.tsx b/frontend/src/pages/service-tabs/__tests__/RequestsTab.test.tsx index 30b0ff6..350a64e 100644 --- a/frontend/src/pages/service-tabs/__tests__/RequestsTab.test.tsx +++ b/frontend/src/pages/service-tabs/__tests__/RequestsTab.test.tsx @@ -2,6 +2,7 @@ import { describe, it, expect, vi } from "vitest"; import { render, screen } from "@testing-library/react"; import { RequestsTab } from "../RequestsTab"; import { useJellyseerrStats } from "../../../hooks/useJellyseer"; +import type { JellyseerStatsResponse } from "../../../api/jellyseerr"; import type { ServiceInstance } from "../../../types"; // Mock the stats hook so the tab renders without a QueryClientProvider and we @@ -10,7 +11,16 @@ vi.mock("../../../hooks/useJellyseer", () => ({ useJellyseerrStats: vi.fn(), })); const mockUseJellyseerrStats = vi.mocked(useJellyseerrStats); +type StatsResult = ReturnType; +function mockStats(result: { + data: JellyseerStatsResponse | undefined; + isLoading: boolean; + error: Error | null; +}) { + // UseQueryResult has many fields; cast the partial we care about. + mockUseJellyseerrStats.mockReturnValue(result as unknown as StatsResult); +} function makeInstance( config: Record, @@ -30,7 +40,7 @@ function makeInstance( describe("RequestsTab", () => { it("shows empty-state CTA when Jellyseerr is not configured", () => { - mockUseJellyseerrStats.mockReturnValue({ data: undefined, isLoading: false, error: null }); + mockStats({ data: undefined, isLoading: false, error: null }); render( { }); it("shows empty-state when only URL is set (api key secret missing)", () => { - mockUseJellyseerrStats.mockReturnValue({ data: undefined, isLoading: false, error: null }); + mockStats({ data: undefined, isLoading: false, error: null }); render( { }); it("shows the configured Jellyseerr URL and the stats grid", () => { - mockUseJellyseerrStats.mockReturnValue({ + mockStats({ data: { stats: [ { key: "pending", label: "Pending", value: 3 }, { key: "total", label: "Total", value: 42 }, ], recent: [ - { id: 1, name: "Inception", type: "movie", status: "pending", media_status: "available" }, + { + id: 1, + name: "Inception", + type: "movie", + status: "pending", + media_status: "available", + }, ], }, isLoading: false, @@ -83,11 +99,7 @@ describe("RequestsTab", () => { }); it("surfaces a fetch error", () => { - mockUseJellyseerrStats.mockReturnValue({ - data: undefined, - isLoading: false, - error: new Error("boom"), - }); + mockStats({ data: undefined, isLoading: false, error: new Error("boom") }); render(