From a16fa88a2d66a2d6eb13fb07726929fdf8b5521d Mon Sep 17 00:00:00 2001 From: ilia Date: Sun, 26 Jul 2026 15:54:12 -0400 Subject: [PATCH] fix: repair tests and formatting that Gitea CI never checked - mock the job-listing probe in discover-jobs tests (they hit the real network via example.com fixtures) - update JobDetailPanel skip test to the runJobAction API introduced by the undo feature - fix jobActionUndo test action type and apply Biome formatting that drifted while Gitea CI had no lint lane --- .../discovered-panel/DiscoveredPanel.tsx | 5 ++- .../src/client/lib/jobActionUndo.test.ts | 7 ++-- orchestrator/src/client/lib/jobActionUndo.ts | 6 +++- orchestrator/src/client/pages/JobPage.tsx | 2 +- .../client/pages/OrchestratorPage.test.tsx | 36 ++++++++++--------- .../orchestrator/JobDetailPanel.test.tsx | 20 +++++++++-- .../pages/orchestrator/JobDetailPanel.tsx | 9 +++-- .../orchestrator/useJobSelectionActions.tsx | 5 ++- .../orchestrator/useKeyboardShortcuts.ts | 6 +++- orchestrator/src/server/api/routes/jobs.ts | 10 +++--- .../pipeline/steps/discover-jobs.test.ts | 8 +++++ 11 files changed, 78 insertions(+), 36 deletions(-) diff --git a/orchestrator/src/client/components/discovered-panel/DiscoveredPanel.tsx b/orchestrator/src/client/components/discovered-panel/DiscoveredPanel.tsx index 8b89b7c8..6af22f5b 100644 --- a/orchestrator/src/client/components/discovered-panel/DiscoveredPanel.tsx +++ b/orchestrator/src/client/components/discovered-panel/DiscoveredPanel.tsx @@ -6,7 +6,10 @@ import type { Job } from "@shared/types.js"; import type React from "react"; import { useEffect, useRef, useState } from "react"; import { toast } from "sonner"; -import { toastMoveToReadyUndo, toastWithUndo } from "@/client/lib/jobActionUndo"; +import { + toastMoveToReadyUndo, + toastWithUndo, +} from "@/client/lib/jobActionUndo"; import { trackProductEvent } from "@/lib/analytics"; import { JobDetailsEditDrawer } from "../JobDetailsEditDrawer"; import { DecideMode } from "./DecideMode"; diff --git a/orchestrator/src/client/lib/jobActionUndo.test.ts b/orchestrator/src/client/lib/jobActionUndo.test.ts index 0020e291..7ee174d4 100644 --- a/orchestrator/src/client/lib/jobActionUndo.test.ts +++ b/orchestrator/src/client/lib/jobActionUndo.test.ts @@ -14,10 +14,7 @@ vi.mock("@client/api", () => ({ import * as api from "@client/api"; import { toast } from "sonner"; -import { - restoresFromSkipResults, - toastWithUndo, -} from "./jobActionUndo"; +import { restoresFromSkipResults, toastWithUndo } from "./jobActionUndo"; describe("restoresFromSkipResults", () => { it("includes primary and also-skipped restores", () => { @@ -49,7 +46,7 @@ describe("toastWithUndo", () => { expect(toast.message).toHaveBeenCalled(); const call = vi.mocked(toast.message).mock.calls[0]; const opts = call?.[1] as { - action?: { onClick: () => void }; + action?: { label?: string; onClick: () => void }; }; expect(opts?.action?.label ?? opts?.action).toBeTruthy(); opts?.action?.onClick(); diff --git a/orchestrator/src/client/lib/jobActionUndo.ts b/orchestrator/src/client/lib/jobActionUndo.ts index 6c4d367e..e653aa94 100644 --- a/orchestrator/src/client/lib/jobActionUndo.ts +++ b/orchestrator/src/client/lib/jobActionUndo.ts @@ -1,5 +1,9 @@ import * as api from "@client/api"; -import type { JobActionResult, JobStatus, JobStatusRestore } from "@shared/types"; +import type { + JobActionResult, + JobStatus, + JobStatusRestore, +} from "@shared/types"; import { toast } from "sonner"; const DEFAULT_UNDO_MS = 8_000; diff --git a/orchestrator/src/client/pages/JobPage.tsx b/orchestrator/src/client/pages/JobPage.tsx index 57e0a2b6..465306e6 100644 --- a/orchestrator/src/client/pages/JobPage.tsx +++ b/orchestrator/src/client/pages/JobPage.tsx @@ -27,7 +27,6 @@ import { import React from "react"; import { useNavigate, useParams } from "react-router-dom"; import { toast } from "sonner"; -import { toastAppliedUndo, toastWithUndo } from "@/client/lib/jobActionUndo"; import { invalidateJobData } from "@/client/hooks/queries/invalidate"; import { useCheckSponsorMutation, @@ -38,6 +37,7 @@ import { useUpdateJobMutation, } from "@/client/hooks/queries/useJobMutations"; import { useQueryErrorToast } from "@/client/hooks/useQueryErrorToast"; +import { toastAppliedUndo, toastWithUndo } from "@/client/lib/jobActionUndo"; import { queryKeys } from "@/client/lib/queryKeys"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; diff --git a/orchestrator/src/client/pages/OrchestratorPage.test.tsx b/orchestrator/src/client/pages/OrchestratorPage.test.tsx index b8c01fe4..89cfc5f0 100644 --- a/orchestrator/src/client/pages/OrchestratorPage.test.tsx +++ b/orchestrator/src/client/pages/OrchestratorPage.test.tsx @@ -34,23 +34,25 @@ vi.mock("../api", () => ({ }), getProfile: vi.fn().mockResolvedValue({ personName: "Test User" }), skipJob: vi.fn().mockResolvedValue({}), - runJobAction: vi.fn().mockImplementation(async (input: { action: string; jobIds: string[] }) => { - const jobId = input.jobIds[0] ?? "job-1"; - return { - action: input.action, - requested: 1, - succeeded: 1, - failed: 0, - results: [ - { - jobId, - ok: true, - job: { id: jobId, status: "skipped" }, - undo: { previousStatus: "ready", alsoSkipped: [] }, - }, - ], - }; - }), + runJobAction: vi + .fn() + .mockImplementation(async (input: { action: string; jobIds: string[] }) => { + const jobId = input.jobIds[0] ?? "job-1"; + return { + action: input.action, + requested: 1, + succeeded: 1, + failed: 0, + results: [ + { + jobId, + ok: true, + job: { id: jobId, status: "skipped" }, + undo: { previousStatus: "ready", alsoSkipped: [] }, + }, + ], + }; + }), restoreJobStatuses: vi.fn().mockResolvedValue({ restored: 1, jobs: [] }), markAsApplied: vi.fn().mockResolvedValue({}), processJob: vi.fn().mockResolvedValue({}), diff --git a/orchestrator/src/client/pages/orchestrator/JobDetailPanel.test.tsx b/orchestrator/src/client/pages/orchestrator/JobDetailPanel.test.tsx index 04e0770e..906dcfef 100644 --- a/orchestrator/src/client/pages/orchestrator/JobDetailPanel.test.tsx +++ b/orchestrator/src/client/pages/orchestrator/JobDetailPanel.test.tsx @@ -136,7 +136,7 @@ vi.mock("@client/api", () => ({ processJob: vi.fn(), generateJobPdf: vi.fn(), markAsApplied: vi.fn(), - skipJob: vi.fn(), + runJobAction: vi.fn(), getProfile: vi.fn().mockResolvedValue({}), })); @@ -346,7 +346,16 @@ describe("JobDetailPanel", () => { it("skips a job from the menu", async () => { const onJobUpdated = vi.fn().mockResolvedValue(undefined); - vi.mocked(api.skipJob).mockResolvedValue(undefined as any); + vi.mocked(api.runJobAction).mockResolvedValue({ + results: [ + { + jobId: "job-1", + ok: true, + job: { id: "job-1", status: "skipped" }, + undo: { previousStatus: "ready" }, + }, + ], + } as any); await renderJobDetailPanel({ activeTab: "all", @@ -362,7 +371,12 @@ describe("JobDetailPanel", () => { const skipItem = await screen.findByRole("menuitem", { name: /skip job/i }); fireEvent.click(skipItem); - await waitFor(() => expect(api.skipJob).toHaveBeenCalledWith("job-1")); + await waitFor(() => + expect(api.runJobAction).toHaveBeenCalledWith({ + action: "skip", + jobIds: ["job-1"], + }), + ); expect(onJobUpdated).toHaveBeenCalled(); }); diff --git a/orchestrator/src/client/pages/orchestrator/JobDetailPanel.tsx b/orchestrator/src/client/pages/orchestrator/JobDetailPanel.tsx index a9c6dbe3..2841469c 100644 --- a/orchestrator/src/client/pages/orchestrator/JobDetailPanel.tsx +++ b/orchestrator/src/client/pages/orchestrator/JobDetailPanel.tsx @@ -33,8 +33,8 @@ import { import type React from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; -import { toastAppliedUndo, toastWithUndo } from "@/client/lib/jobActionUndo"; import { JobDescriptionMarkdown } from "@/client/components/JobDescriptionMarkdown"; +import { toastAppliedUndo, toastWithUndo } from "@/client/lib/jobActionUndo"; import { getRenderableJobDescription } from "@/client/lib/jobDescription"; import { Button } from "@/components/ui/button"; import { @@ -324,7 +324,12 @@ export const JobDetailPanel: React.FC = ({ }); toastWithUndo({ title: "Moved to in progress", - restores: [{ jobId: selectedJob.id, status: fromStatus === "applied" ? "applied" : "applied" }], + restores: [ + { + jobId: selectedJob.id, + status: fromStatus === "applied" ? "applied" : "applied", + }, + ], variant: "success", onRestored: onJobUpdated, successMessage: "Reverted to Applied", diff --git a/orchestrator/src/client/pages/orchestrator/useJobSelectionActions.tsx b/orchestrator/src/client/pages/orchestrator/useJobSelectionActions.tsx index be3e1b74..9e1d2b43 100644 --- a/orchestrator/src/client/pages/orchestrator/useJobSelectionActions.tsx +++ b/orchestrator/src/client/pages/orchestrator/useJobSelectionActions.tsx @@ -7,7 +7,10 @@ import { } from "@shared/types.js"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; -import { restoresFromSkipResults, toastWithUndo } from "@/client/lib/jobActionUndo"; +import { + restoresFromSkipResults, + toastWithUndo, +} from "@/client/lib/jobActionUndo"; import { trackProductEvent } from "@/lib/analytics"; import type { FilterTab } from "./constants"; import { JobActionProgressToast } from "./JobActionProgressToast"; diff --git a/orchestrator/src/client/pages/orchestrator/useKeyboardShortcuts.ts b/orchestrator/src/client/pages/orchestrator/useKeyboardShortcuts.ts index 69e6dcae..e10890e6 100644 --- a/orchestrator/src/client/pages/orchestrator/useKeyboardShortcuts.ts +++ b/orchestrator/src/client/pages/orchestrator/useKeyboardShortcuts.ts @@ -9,7 +9,11 @@ import { SHORTCUTS } from "@client/lib/shortcut-map"; import type { JobAction, JobListItem } from "@shared/types.js"; import { useCallback, useRef } from "react"; import { toast } from "sonner"; -import { toastAppliedUndo, toastMoveToReadyUndo, toastWithUndo } from "@/client/lib/jobActionUndo"; +import { + toastAppliedUndo, + toastMoveToReadyUndo, + toastWithUndo, +} from "@/client/lib/jobActionUndo"; import { safeFilenamePart } from "@/lib/utils"; import type { FilterTab } from "./constants"; import { tabs } from "./constants"; diff --git a/orchestrator/src/server/api/routes/jobs.ts b/orchestrator/src/server/api/routes/jobs.ts index cd742545..4b24157f 100644 --- a/orchestrator/src/server/api/routes/jobs.ts +++ b/orchestrator/src/server/api/routes/jobs.ts @@ -1004,9 +1004,12 @@ jobsRouter.post("/restore-status", async (req: Request, res: Response) => { try { const parsed = restoreStatusSchema.safeParse(req.body); if (!parsed.success) { - return fail(res, badRequest("Invalid restore payload", { - issues: parsed.error.issues, - })); + return fail( + res, + badRequest("Invalid restore payload", { + issues: parsed.error.issues, + }), + ); } const restored: Job[] = []; @@ -1470,7 +1473,6 @@ jobsRouter.post("/:id/generate-pdf", async (req: Request, res: Response) => { } }); - /** * POST /api/jobs/:id/apply - Mark a job as applied */ diff --git a/orchestrator/src/server/pipeline/steps/discover-jobs.test.ts b/orchestrator/src/server/pipeline/steps/discover-jobs.test.ts index 07d552bd..2f48b808 100644 --- a/orchestrator/src/server/pipeline/steps/discover-jobs.test.ts +++ b/orchestrator/src/server/pipeline/steps/discover-jobs.test.ts @@ -23,6 +23,14 @@ vi.mock("@server/extractors/registry", () => ({ getExtractorRegistry: vi.fn(), })); +// Keep tests hermetic: the real probe fetches listing URLs over the network. +vi.mock("@shared/job-listing-probe.js", () => ({ + filterExpiredJobListings: vi.fn(async (jobs: readonly T[]) => ({ + kept: [...jobs], + dropped: 0, + })), +})); + const baseConfig: PipelineConfig = { topN: 10, minSuitabilityScore: 50,