diff --git a/frontend/e2e/_targets.ts b/frontend/e2e/_targets.ts new file mode 100644 index 0000000000..c596d8e130 --- /dev/null +++ b/frontend/e2e/_targets.ts @@ -0,0 +1,45 @@ +// Shared helpers for building target-instance mock payloads in e2e tests. +// +// Mirrors the frontend `makeTarget` fixture (src/test-utils/targetFixtures.ts): +// folds flat identity scalars into the embedded `identifier` so route mocks +// match the current nested `TargetInstance` wire model. Without this, the +// target table crashes reading `target.identifier.class_name` and no rows +// (and no "Set Active" button) render. + +export interface FlatTarget { + target_registry_name: string; + target_type?: string; + endpoint?: string | null; + model_name?: string | null; + capabilities?: unknown; + target_specific_params?: unknown; + inner_targets?: FlatTarget[] | null; +} + +interface TargetInstanceMock { + target_registry_name: string; + identifier: { + class_name: string; + hash: string; + endpoint: string | null; + model_name: string | null; + }; + capabilities: unknown; + target_specific_params: unknown; + inner_targets: TargetInstanceMock[] | null; +} + +export function makeTarget(flat: FlatTarget): TargetInstanceMock { + return { + target_registry_name: flat.target_registry_name, + identifier: { + class_name: flat.target_type ?? "TextTarget", + hash: `${flat.target_registry_name}-hash`, + endpoint: flat.endpoint ?? null, + model_name: flat.model_name ?? null, + }, + capabilities: flat.capabilities ?? null, + target_specific_params: flat.target_specific_params ?? null, + inner_targets: flat.inner_targets ? flat.inner_targets.map(makeTarget) : null, + }; +} diff --git a/frontend/e2e/accessibility.spec.ts b/frontend/e2e/accessibility.spec.ts index d6b5ce576d..20dfb864c7 100644 --- a/frontend/e2e/accessibility.spec.ts +++ b/frontend/e2e/accessibility.spec.ts @@ -1,4 +1,5 @@ import { test, expect } from "@playwright/test"; +import { makeTarget } from "./_targets"; test.describe("Accessibility", () => { test.beforeEach(async ({ page }) => { @@ -13,12 +14,12 @@ test.describe("Accessibility", () => { contentType: "application/json", body: JSON.stringify({ items: [ - { + makeTarget({ target_registry_name: "a11y-form-target", target_type: "OpenAIChatTarget", endpoint: "https://test.com", model_name: "gpt-4o", - }, + }), ], pagination: { limit: 200, has_more: false, next_cursor: null, prev_cursor: null }, }), @@ -84,12 +85,12 @@ test.describe("Accessibility", () => { contentType: "application/json", body: JSON.stringify({ items: [ - { + makeTarget({ target_registry_name: "a11y-focus-target", target_type: "OpenAIChatTarget", endpoint: "https://test.com", model_name: "gpt-4o", - }, + }), ], pagination: { limit: 200, has_more: false, next_cursor: null, prev_cursor: null }, }), @@ -124,12 +125,12 @@ test.describe("Accessibility", () => { contentType: "application/json", body: JSON.stringify({ items: [ - { + makeTarget({ target_registry_name: "a11y-test-target", target_type: "OpenAIChatTarget", endpoint: "https://test.com", model_name: "gpt-4o", - }, + }), ], pagination: { limit: 200, has_more: false, next_cursor: null, prev_cursor: null }, }), diff --git a/frontend/e2e/chat.spec.ts b/frontend/e2e/chat.spec.ts index 197be11045..05f75a44b8 100644 --- a/frontend/e2e/chat.spec.ts +++ b/frontend/e2e/chat.spec.ts @@ -1,4 +1,5 @@ import { test, expect, type Page } from "@playwright/test"; +import { makeTarget } from "./_targets"; // --------------------------------------------------------------------------- // Helpers – mock backend API responses so tests don't require an OpenAI key @@ -19,12 +20,12 @@ async function mockBackendAPIs(page: Page) { contentType: "application/json", body: JSON.stringify({ items: [ - { + makeTarget({ target_registry_name: "mock-openai-chat", target_type: "OpenAIChatTarget", endpoint: "https://mock.openai.com", model_name: "gpt-4o-mock", - }, + }), ], }), }); @@ -327,12 +328,12 @@ function buildModalityMock( contentType: "application/json", body: JSON.stringify({ items: [ - { + makeTarget({ target_registry_name: "mock-target", target_type: "OpenAIChatTarget", endpoint: "https://mock.endpoint.com", model_name: "test-model", - }, + }), ], }), }); @@ -677,7 +678,7 @@ test.describe("Target type scenarios", () => { endpoint: "https://api.openai.com", model_name: "tts-1-hd", }, - ]; + ].map(makeTarget); test("should list multiple target types on config page", async ({ page }) => { await page.route(/\/api\/targets/, async (route) => { diff --git a/frontend/e2e/config.spec.ts b/frontend/e2e/config.spec.ts index 0e9956b8a9..5c2e4d8828 100644 --- a/frontend/e2e/config.spec.ts +++ b/frontend/e2e/config.spec.ts @@ -1,16 +1,17 @@ import { test, expect, type Page } from "@playwright/test"; +import { makeTarget, type FlatTarget } from "./_targets"; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- /** Return a mock targets list response. */ -function mockTargetsList(items: Record[] = []) { +function mockTargetsList(items: FlatTarget[] = []) { return { status: 200, contentType: "application/json", body: JSON.stringify({ - items, + items: items.map(makeTarget), pagination: { limit: 200, has_more: false, next_cursor: null, prev_cursor: null }, }), }; @@ -138,7 +139,7 @@ test.describe("Target Configuration Page", () => { test.describe("Create Target Dialog", () => { test("should create a target through the dialog", async ({ page }) => { - let createdTarget: Record | null = null; + let createdTarget: FlatTarget | null = null; await page.route(/\/api\/targets/, async (route) => { if (route.request().method() === "POST") { diff --git a/frontend/e2e/converters.spec.ts b/frontend/e2e/converters.spec.ts index 74dfb91148..b64ced7e7e 100644 --- a/frontend/e2e/converters.spec.ts +++ b/frontend/e2e/converters.spec.ts @@ -1,4 +1,5 @@ import { test, expect, type Page } from "@playwright/test"; +import { makeTarget } from "./_targets"; // --------------------------------------------------------------------------- // Mock data @@ -186,7 +187,7 @@ async function mockBackendAPIs(page: Page) { contentType: "application/json", body: JSON.stringify({ items: [ - { + makeTarget({ target_registry_name: "mock-openai-chat", target_type: "OpenAIChatTarget", endpoint: "https://mock.openai.com", @@ -201,7 +202,7 @@ async function mockBackendAPIs(page: Page) { supported_input_data_types: ["text"], supported_output_data_types: ["text"], }, - }, + }), ], pagination: { limit: 50, has_more: false }, }), diff --git a/frontend/e2e/errors.spec.ts b/frontend/e2e/errors.spec.ts index 73560c3d6e..84afa9953c 100644 --- a/frontend/e2e/errors.spec.ts +++ b/frontend/e2e/errors.spec.ts @@ -1,4 +1,5 @@ import { test, expect, type Page, type Route } from "@playwright/test"; +import { makeTarget } from "./_targets"; // --------------------------------------------------------------------------- // Helpers @@ -67,12 +68,12 @@ async function mockAllAPIs( contentType: "application/json", body: JSON.stringify({ items: [ - { + makeTarget({ target_registry_name: "mock-target", target_type: "OpenAIChatTarget", endpoint: "https://mock.endpoint.com", model_name: "gpt-4o-mock", - }, + }), ], }), }); @@ -438,12 +439,12 @@ test.describe("Error: create attack fails", () => { contentType: "application/json", body: JSON.stringify({ items: [ - { + makeTarget({ target_registry_name: "mock-target", target_type: "OpenAIChatTarget", endpoint: "https://mock.endpoint.com", model_name: "gpt-4o-mock", - }, + }), ], }), }); diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index 5088924cb2..7663291a4d 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -167,6 +167,7 @@ jest.mock("./components/Chat/ChatWindow", () => { }); jest.mock("./components/Config/TargetConfig", () => { + const { makeTarget } = jest.requireActual("@/test-utils/targetFixtures") as typeof import("@/test-utils/targetFixtures"); const MockTargetConfig = ({ activeTarget, onSetActiveTarget, @@ -181,12 +182,10 @@ jest.mock("./components/Config/TargetConfig", () => {