tests
This commit is contained in:
parent
90e4feecf5
commit
a14814291e
168
orchestrator/src/client/components/ManualImportSheet.test.tsx
Normal file
168
orchestrator/src/client/components/ManualImportSheet.test.tsx
Normal file
@ -0,0 +1,168 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
||||
|
||||
import { ManualImportSheet } from "./ManualImportSheet";
|
||||
import * as api from "../api";
|
||||
import { toast } from "sonner";
|
||||
|
||||
vi.mock("../api", () => ({
|
||||
inferManualJob: vi.fn(),
|
||||
importManualJob: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("sonner", () => ({
|
||||
toast: {
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe("ManualImportSheet", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("runs analyze -> review -> import on the happy path", async () => {
|
||||
const rawDescription = " Backend Engineer role in London. ";
|
||||
const onOpenChange = vi.fn();
|
||||
const onImported = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
vi.mocked(api.inferManualJob).mockResolvedValue({
|
||||
job: {
|
||||
title: "Backend Engineer",
|
||||
employer: "Acme Labs",
|
||||
location: "London, UK",
|
||||
},
|
||||
});
|
||||
vi.mocked(api.importManualJob).mockResolvedValue({ id: "job-1" } as any);
|
||||
|
||||
render(
|
||||
<ManualImportSheet open onOpenChange={onOpenChange} onImported={onImported} />
|
||||
);
|
||||
|
||||
fireEvent.change(
|
||||
screen.getByPlaceholderText("Paste the full job description here..."),
|
||||
{ target: { value: rawDescription } }
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: /analyze jd/i }));
|
||||
|
||||
const titleInput = await screen.findByPlaceholderText("e.g. Junior Backend Engineer");
|
||||
expect(titleInput).toHaveValue("Backend Engineer");
|
||||
|
||||
const jdTextarea = screen.getByPlaceholderText("Paste the job description...") as HTMLTextAreaElement;
|
||||
expect(jdTextarea.value).toBe(rawDescription.trim());
|
||||
|
||||
fireEvent.change(screen.getByPlaceholderText("e.g. GBP 45k-55k"), {
|
||||
target: { value: " 120k " },
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /import job/i }));
|
||||
|
||||
await waitFor(() => expect(api.importManualJob).toHaveBeenCalled());
|
||||
expect(api.importManualJob).toHaveBeenCalledWith({
|
||||
job: expect.objectContaining({
|
||||
title: "Backend Engineer",
|
||||
employer: "Acme Labs",
|
||||
location: "London, UK",
|
||||
salary: "120k",
|
||||
jobDescription: rawDescription.trim(),
|
||||
}),
|
||||
});
|
||||
|
||||
await waitFor(() => expect(onImported).toHaveBeenCalledWith("job-1"));
|
||||
expect(onOpenChange).toHaveBeenCalledWith(false);
|
||||
expect(toast.success).toHaveBeenCalledWith(
|
||||
"Job imported",
|
||||
expect.objectContaining({
|
||||
description: expect.any(String),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("shows warnings and requires required fields before import", async () => {
|
||||
const rawDescription = "Manual QA Engineer role.";
|
||||
|
||||
vi.mocked(api.inferManualJob).mockResolvedValue({
|
||||
job: {},
|
||||
warning: "AI inference failed. Fill details manually.",
|
||||
});
|
||||
|
||||
render(
|
||||
<ManualImportSheet open onOpenChange={vi.fn()} onImported={vi.fn()} />
|
||||
);
|
||||
|
||||
fireEvent.change(
|
||||
screen.getByPlaceholderText("Paste the full job description here..."),
|
||||
{ target: { value: rawDescription } }
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: /analyze jd/i }));
|
||||
|
||||
await screen.findByText("AI inference failed. Fill details manually.");
|
||||
|
||||
const importButton = screen.getByRole("button", { name: /import job/i });
|
||||
expect(importButton).toBeDisabled();
|
||||
|
||||
fireEvent.change(screen.getByPlaceholderText("e.g. Junior Backend Engineer"), {
|
||||
target: { value: "QA Engineer" },
|
||||
});
|
||||
fireEvent.change(screen.getByPlaceholderText("e.g. Acme Labs"), {
|
||||
target: { value: "Acme Labs" },
|
||||
});
|
||||
|
||||
await waitFor(() => expect(importButton).toBeEnabled());
|
||||
});
|
||||
|
||||
it("returns to the paste step when inference fails", async () => {
|
||||
const rawDescription = "Backend role description.";
|
||||
|
||||
vi.mocked(api.inferManualJob).mockRejectedValue(new Error("Inference failed"));
|
||||
|
||||
render(
|
||||
<ManualImportSheet open onOpenChange={vi.fn()} onImported={vi.fn()} />
|
||||
);
|
||||
|
||||
fireEvent.change(
|
||||
screen.getByPlaceholderText("Paste the full job description here..."),
|
||||
{ target: { value: rawDescription } }
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: /analyze jd/i }));
|
||||
|
||||
await screen.findByText("Inference failed");
|
||||
expect(screen.getByRole("button", { name: /analyze jd/i })).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByPlaceholderText("e.g. Junior Backend Engineer")
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows a toast error and keeps the sheet open when import fails", async () => {
|
||||
vi.mocked(api.inferManualJob).mockResolvedValue({
|
||||
job: {
|
||||
title: "Backend Engineer",
|
||||
employer: "Acme Labs",
|
||||
},
|
||||
});
|
||||
vi.mocked(api.importManualJob).mockRejectedValue(new Error("Import failed"));
|
||||
|
||||
const onOpenChange = vi.fn();
|
||||
|
||||
render(
|
||||
<ManualImportSheet open onOpenChange={onOpenChange} onImported={vi.fn()} />
|
||||
);
|
||||
|
||||
fireEvent.change(
|
||||
screen.getByPlaceholderText("Paste the full job description here..."),
|
||||
{ target: { value: "Backend Engineer role." } }
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: /analyze jd/i }));
|
||||
|
||||
await screen.findByPlaceholderText("e.g. Junior Backend Engineer");
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /import job/i }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(toast.error).toHaveBeenCalledWith("Import failed")
|
||||
);
|
||||
expect(onOpenChange).not.toHaveBeenCalled();
|
||||
expect(screen.getByRole("button", { name: /import job/i })).toBeEnabled();
|
||||
});
|
||||
});
|
||||
74
orchestrator/src/server/services/manualJob.test.ts
Normal file
74
orchestrator/src/server/services/manualJob.test.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import * as settingsRepo from "../repositories/settings.js";
|
||||
import { inferManualJobDetails } from "./manualJob.js";
|
||||
|
||||
vi.mock("../repositories/settings.js", () => ({
|
||||
getSetting: vi.fn(),
|
||||
}));
|
||||
|
||||
const originalEnv = process.env;
|
||||
const originalFetch = global.fetch;
|
||||
|
||||
describe("manual job inference", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
process.env = { ...originalEnv, OPENROUTER_API_KEY: "test-key" };
|
||||
global.fetch = vi.fn();
|
||||
vi.mocked(settingsRepo.getSetting).mockResolvedValue(null);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
global.fetch = originalFetch;
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("returns a warning when the API key is missing", async () => {
|
||||
delete process.env.OPENROUTER_API_KEY;
|
||||
|
||||
const result = await inferManualJobDetails("JD text");
|
||||
|
||||
expect(result.job).toEqual({});
|
||||
expect(result.warning).toContain("OPENROUTER_API_KEY not set");
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("parses JSON even when wrapped in markdown fences", async () => {
|
||||
vi.mocked(global.fetch).mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content:
|
||||
"Here is the data: ```json\n{ \"title\": \"Backend Engineer\", \"employer\": \"Acme\", \"salary\": \" 100k \" }\n```",
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
} as any);
|
||||
|
||||
const result = await inferManualJobDetails("JD text");
|
||||
|
||||
expect(result.warning).toBeUndefined();
|
||||
expect(result.job).toMatchObject({
|
||||
title: "Backend Engineer",
|
||||
employer: "Acme",
|
||||
salary: "100k",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns a warning when the API response fails", async () => {
|
||||
vi.mocked(global.fetch).mockResolvedValue({
|
||||
ok: false,
|
||||
status: 500,
|
||||
} as any);
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
|
||||
const result = await inferManualJobDetails("JD text");
|
||||
|
||||
expect(result.job).toEqual({});
|
||||
expect(result.warning).toContain("AI inference failed");
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user