Files
stack/apps/web/src/providers/WebSocketProvider.test.tsx
Jason Woltje dd46025d60 fix(#338): Enforce WSS in production and add connect_error handling
- Add validateWebSocketSecurity() to warn when using ws:// in production
- Add connect_error event handler to capture connection failures
- Expose connectionError state to consumers via hook and provider
- Add comprehensive tests for WSS enforcement and error handling

Refs #338

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 17:31:26 -06:00

123 lines
3.5 KiB
TypeScript

import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { WebSocketProvider, useWebSocketContext } from "./WebSocketProvider";
import * as useWebSocketModule from "../hooks/useWebSocket";
// Mock the useWebSocket hook
vi.mock("../hooks/useWebSocket");
describe("WebSocketProvider", (): void => {
it("should provide WebSocket context to children", (): void => {
const mockUseWebSocket = vi.spyOn(useWebSocketModule, "useWebSocket");
mockUseWebSocket.mockReturnValue({
isConnected: true,
socket: null,
connectionError: null,
});
function TestComponent(): React.JSX.Element {
const { isConnected } = useWebSocketContext();
return <div>{isConnected ? "Connected" : "Disconnected"}</div>;
}
render(
<WebSocketProvider workspaceId="workspace-123" token="auth-token">
<TestComponent />
</WebSocketProvider>
);
expect(screen.getByText("Connected")).toBeInTheDocument();
});
it("should pass callbacks to useWebSocket hook", (): void => {
const mockUseWebSocket = vi.spyOn(useWebSocketModule, "useWebSocket");
mockUseWebSocket.mockReturnValue({
isConnected: false,
socket: null,
connectionError: null,
});
const onTaskCreated = vi.fn();
const onTaskUpdated = vi.fn();
const onTaskDeleted = vi.fn();
render(
<WebSocketProvider
workspaceId="workspace-123"
token="auth-token"
onTaskCreated={onTaskCreated}
onTaskUpdated={onTaskUpdated}
onTaskDeleted={onTaskDeleted}
>
<div>Test</div>
</WebSocketProvider>
);
expect(mockUseWebSocket).toHaveBeenCalledWith("workspace-123", "auth-token", {
onTaskCreated,
onTaskUpdated,
onTaskDeleted,
onEventCreated: undefined,
onEventUpdated: undefined,
onEventDeleted: undefined,
onProjectUpdated: undefined,
});
});
it("should throw error when useWebSocketContext is used outside provider", (): void => {
function TestComponent(): React.JSX.Element {
useWebSocketContext();
return <div>Test</div>;
}
// Suppress console.error for this test
const originalError = console.error;
console.error = vi.fn();
expect(() => {
render(<TestComponent />);
}).toThrow("useWebSocketContext must be used within WebSocketProvider");
console.error = originalError;
});
it("should update context when connection status changes", (): void => {
const mockUseWebSocket = vi.spyOn(useWebSocketModule, "useWebSocket");
// Initially disconnected
mockUseWebSocket.mockReturnValue({
isConnected: false,
socket: null,
connectionError: null,
});
function TestComponent(): React.JSX.Element {
const { isConnected } = useWebSocketContext();
return <div data-testid="status">{isConnected ? "Connected" : "Disconnected"}</div>;
}
const { rerender } = render(
<WebSocketProvider workspaceId="workspace-123" token="auth-token">
<TestComponent />
</WebSocketProvider>
);
expect(screen.getByTestId("status")).toHaveTextContent("Disconnected");
// Update to connected
mockUseWebSocket.mockReturnValue({
isConnected: true,
socket: null,
connectionError: null,
});
rerender(
<WebSocketProvider workspaceId="workspace-123" token="auth-token">
<TestComponent />
</WebSocketProvider>
);
expect(screen.getByTestId("status")).toHaveTextContent("Connected");
});
});