Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions frontend/e2e/errors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,9 @@ test.describe("Error: backend 500 on send message", () => {

// Error message should appear in chat
await expect(
page.getByText(/Internal server error/i),
page.getByText(/The server could not complete the request\. Please try again\./i),
).toBeVisible({ timeout: 10000 });
await expect(page.getByText(/Internal server error/i)).toHaveCount(0);

// The failed text should be restored in the input for easy re-send
await expect(input).toHaveValue("This should fail", { timeout: 5000 });
Expand All @@ -282,12 +283,42 @@ test.describe("Error: backend 500 on send message", () => {
await input.fill("First send fails");
await page.getByRole("button", { name: /send/i }).click();

await expect(page.getByText(/Internal server error/i)).toBeVisible({
timeout: 10000,
});
await expect(
page.getByText(/The server could not complete the request\. Please try again\./i),
).toBeVisible({ timeout: 10000 });
await expect(page.getByText(/Internal server error/i)).toHaveCount(0);
await expect(page.getByTestId("loading-state")).toHaveCount(0);
await expect(input).toHaveValue("First send fails", { timeout: 5000 });
});

test("should show an explicitly categorized server error", async ({ page }) => {
await mockAllAPIs(page, async (route) => {
await route.fulfill({
status: 503,
contentType: "application/json",
body: JSON.stringify({
type: "/errors/target-unavailable",
title: "Target Unavailable",
status: 503,
detail: "The target is temporarily unavailable. Please try again later.",
}),
});
});

await page.goto("/");
await activateMockTarget(page);

const input = page.getByRole("textbox");
await input.fill("Unavailable target");
await page.getByRole("button", { name: /send/i }).click();

await expect(
page.getByText("The target is temporarily unavailable. Please try again later."),
).toBeVisible({ timeout: 10000 });
await expect(
page.getByText("The server could not complete the request. Please try again."),
).toHaveCount(0);
});
});

// ---------------------------------------------------------------------------
Expand Down
11 changes: 7 additions & 4 deletions frontend/src/auth/AuthProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,11 @@ describe("AuthProvider", () => {
});
});

// Test 13: fetchAuthConfig rejects with Error → err.message shown
it("shows error when initialization fails with Error", async () => {
mockFetchAuthConfig.mockRejectedValue(new Error("Config fetch failed"));
// Test 13: fetchAuthConfig rejects with Error → generic message shown
it("shows a generic error when initialization fails with Error", async () => {
mockFetchAuthConfig.mockRejectedValue(
new Error("token secret=sk-test failed at C:\\internal\\auth.ts")
);

render(
<AuthProvider>
Expand All @@ -158,7 +160,8 @@ describe("AuthProvider", () => {

await waitFor(() => {
expect(screen.getByText("Authentication Error")).toBeVisible();
expect(screen.getByText("Config fetch failed")).toBeVisible();
expect(screen.getByText("Failed to initialize authentication")).toBeVisible();
expect(screen.queryByText(/sk-test|internal\\auth/i)).not.toBeInTheDocument();
});
});

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/auth/AuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,9 @@ export function AuthProvider({ children }: AuthProviderProps) {
setMsalInstance(instance)
setAuthConfig(config)
}
} catch (err) {
} catch {
if (!cancelled) {
setError(err instanceof Error ? err.message : 'Failed to initialize authentication')
setError('Failed to initialize authentication')
}
}
}
Expand Down
12 changes: 6 additions & 6 deletions frontend/src/components/Chat/ChatWindow.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -844,7 +844,7 @@ describe("ChatWindow Integration", () => {
await user.click(screen.getByRole("button", { name: /send/i }));

await waitFor(() => {
expect(screen.getByText(/Network error/)).toBeInTheDocument();
expect(screen.getByText(/An unexpected error occurred\./)).toBeInTheDocument();
});
});

Expand Down Expand Up @@ -877,11 +877,11 @@ describe("ChatWindow Integration", () => {
await user.click(screen.getByRole("button", { name: /send/i }));

await waitFor(() => {
expect(screen.getByText(/Request failed with status code 404/)).toBeInTheDocument();
expect(screen.getByText(/An unexpected error occurred\./)).toBeInTheDocument();
});
});

it("should extract detail from axios-style error response", async () => {
it("should sanitize detail from a 500 axios-style error response", async () => {
const user = userEvent.setup();

mockedMapper.buildMessagePieces.mockResolvedValue([
Expand Down Expand Up @@ -911,7 +911,7 @@ describe("ChatWindow Integration", () => {
await user.click(screen.getByRole("button", { name: /send/i }));

await waitFor(() => {
expect(screen.getByText(/Failed to add message/)).toBeInTheDocument();
expect(screen.getByText(/The server could not complete the request\. Please try again\./)).toBeInTheDocument();
});
});

Expand Down Expand Up @@ -945,7 +945,7 @@ describe("ChatWindow Integration", () => {
await user.click(screen.getByRole("button", { name: /send/i }));

await waitFor(() => {
expect(screen.getByText(/Internal Server Error/)).toBeInTheDocument();
expect(screen.getByText(/The server could not complete the request\. Please try again\./)).toBeInTheDocument();
});
});

Expand All @@ -971,7 +971,7 @@ describe("ChatWindow Integration", () => {
await user.click(screen.getByRole("button", { name: /send/i }));

await waitFor(() => {
expect(screen.getByText(/string error/)).toBeInTheDocument();
expect(screen.getByText(/An unexpected error occurred\./)).toBeInTheDocument();
});
});

Expand Down
6 changes: 4 additions & 2 deletions frontend/src/components/Chat/ConversationPanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ describe("ConversationPanel", () => {
await waitFor(() => {
expect(screen.getByTestId("conversation-error")).toBeInTheDocument();
});
expect(screen.getByText("Network error")).toBeInTheDocument();
expect(screen.getByText("An unexpected error occurred.")).toBeInTheDocument();
});

// -----------------------------------------------------------------------
Expand Down Expand Up @@ -428,7 +428,9 @@ describe("ConversationPanel", () => {
await waitFor(() => {
expect(screen.getByTestId("conversation-error")).toBeInTheDocument();
});
expect(screen.getByText("Server exploded")).toBeInTheDocument();
expect(
screen.getByText("The server could not complete the request. Please try again.")
).toBeInTheDocument();
expect(screen.getByTestId("conversation-retry-btn")).toBeInTheDocument();
});

Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/Config/CreateTargetDialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ describe("CreateTargetDialog", () => {
await user.click(screen.getByText("Create Target"));

await waitFor(() => {
expect(screen.getByText("Invalid API key")).toBeInTheDocument();
expect(screen.getByText("An unexpected error occurred.")).toBeInTheDocument();
});
});

Expand Down Expand Up @@ -454,7 +454,7 @@ describe("CreateTargetDialog", () => {
});
});

it("should surface string throws verbatim via toApiError", async () => {
it("should sanitize string throws via toApiError", async () => {
const user = userEvent.setup();
mockedTargetsApi.createTarget.mockRejectedValue("string error");

Expand All @@ -474,7 +474,7 @@ describe("CreateTargetDialog", () => {
await user.click(screen.getByText("Create Target"));

await waitFor(() => {
expect(screen.getByText("string error")).toBeInTheDocument();
expect(screen.getByText("An unexpected error occurred.")).toBeInTheDocument();
});
});

Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/Config/TargetConfig.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,9 @@ describe("TargetConfig", () => {

await waitFor(
() => {
expect(screen.getByText(/Connection refused/)).toBeInTheDocument();
expect(
screen.getByText(/An unexpected error occurred\./)
).toBeInTheDocument();
},
{ timeout: 15000 }
);
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/components/ErrorBoundary.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ErrorBoundary } from './ErrorBoundary'
// Component that throws on render
function ThrowingChild({ shouldThrow }: { shouldThrow: boolean }) {
if (shouldThrow) {
throw new Error('Test crash')
throw new Error('secret=sk-test at C:\\internal\\component.tsx')
}
return <div data-testid="child-content">OK</div>
}
Expand All @@ -31,15 +31,16 @@ describe('ErrorBoundary', () => {
expect(screen.queryByTestId('error-boundary-fallback')).toBeNull()
})

it('catches render error and shows fallback', () => {
it('catches render error without showing internal details', () => {
render(
<ErrorBoundary>
<ThrowingChild shouldThrow={true} />
</ErrorBoundary>
)

expect(screen.getByTestId('error-boundary-fallback')).toBeInTheDocument()
expect(screen.getByText(/test crash/i)).toBeInTheDocument()
expect(screen.getByText(/something went wrong\. please try again\./i)).toBeInTheDocument()
expect(screen.queryByText(/sk-test|internal\\component/i)).not.toBeInTheDocument()
expect(screen.getByText('Try again')).toBeInTheDocument()
})

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { FallbackProps } from 'react-error-boundary'
import type { ReactNode } from 'react'
import { Button, MessageBar, MessageBarBody, tokens } from '@fluentui/react-components'

function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
function ErrorFallback({ resetErrorBoundary }: FallbackProps) {
const [crashCount, setCrashCount] = useState(1)

const handleRetry = () => {
Expand All @@ -31,7 +31,7 @@ function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
>
<MessageBar intent="error">
<MessageBarBody>
Something went wrong: {error instanceof Error ? error.message : 'Unknown error'}
Something went wrong. Please try again.
</MessageBarBody>
</MessageBar>

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/History/AttackHistory.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ describe('AttackHistory', () => {
await waitFor(() => {
expect(screen.getByTestId('error-state')).toBeInTheDocument()
})
expect(screen.getByText('Internal server error')).toBeInTheDocument()
expect(screen.getByText('The server could not complete the request. Please try again.')).toBeInTheDocument()
expect(screen.getByTestId('retry-btn')).toBeInTheDocument()
})

Expand Down
Loading
Loading