fix: enhance error handling for custom headers and improve test coverage for header management
This commit is contained in:
parent
fde55efc2f
commit
90ab419d12
3 changed files with 163 additions and 41 deletions
|
|
@ -137,7 +137,11 @@ const App = () => {
|
||||||
if (savedHeaders) {
|
if (savedHeaders) {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(savedHeaders);
|
return JSON.parse(savedHeaders);
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
console.warn(
|
||||||
|
`Failed to parse custom headers: "${savedHeaders}", will try legacy migration`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
// Fall back to migration if JSON parsing fails
|
// Fall back to migration if JSON parsing fails
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,7 @@ const CustomHeaders = ({
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={addHeader}
|
onClick={addHeader}
|
||||||
className="text-xs px-2"
|
className="text-xs px-2"
|
||||||
|
data-testid="add-header-button"
|
||||||
>
|
>
|
||||||
<Plus className="w-3 h-3 mr-1" />
|
<Plus className="w-3 h-3 mr-1" />
|
||||||
Add
|
Add
|
||||||
|
|
@ -185,6 +186,7 @@ const CustomHeaders = ({
|
||||||
value={header.name}
|
value={header.name}
|
||||||
onChange={(e) => updateHeader(index, "name", e.target.value)}
|
onChange={(e) => updateHeader(index, "name", e.target.value)}
|
||||||
className="font-mono text-xs"
|
className="font-mono text-xs"
|
||||||
|
data-testid={`header-name-input-${index}`}
|
||||||
/>
|
/>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Input
|
<Input
|
||||||
|
|
@ -195,6 +197,7 @@ const CustomHeaders = ({
|
||||||
}
|
}
|
||||||
type={visibleValues.has(index) ? "text" : "password"}
|
type={visibleValues.has(index) ? "text" : "password"}
|
||||||
className="font-mono text-xs pr-8"
|
className="font-mono text-xs pr-8"
|
||||||
|
data-testid={`header-value-input-${index}`}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,10 @@
|
||||||
import { render, screen, fireEvent, act } from "@testing-library/react";
|
import {
|
||||||
|
render,
|
||||||
|
screen,
|
||||||
|
fireEvent,
|
||||||
|
act,
|
||||||
|
waitFor,
|
||||||
|
} from "@testing-library/react";
|
||||||
import "@testing-library/jest-dom";
|
import "@testing-library/jest-dom";
|
||||||
import { describe, it, beforeEach, jest } from "@jest/globals";
|
import { describe, it, beforeEach, jest } from "@jest/globals";
|
||||||
import Sidebar from "../Sidebar";
|
import Sidebar from "../Sidebar";
|
||||||
|
|
@ -623,117 +629,179 @@ describe("Sidebar", () => {
|
||||||
fireEvent.click(button);
|
fireEvent.click(button);
|
||||||
};
|
};
|
||||||
|
|
||||||
it("should update bearer token", () => {
|
it("should update bearer token via custom headers", async () => {
|
||||||
const setBearerToken = jest.fn();
|
const setCustomHeaders = jest.fn();
|
||||||
renderSidebar({
|
renderSidebar({
|
||||||
bearerToken: "",
|
customHeaders: [],
|
||||||
setBearerToken,
|
setCustomHeaders,
|
||||||
transportType: "sse", // Set transport type to SSE
|
transportType: "sse", // Set transport type to SSE
|
||||||
});
|
});
|
||||||
|
|
||||||
openAuthSection();
|
openAuthSection();
|
||||||
|
|
||||||
const tokenInput = screen.getByTestId("bearer-token-input");
|
// Add a new header
|
||||||
fireEvent.change(tokenInput, { target: { value: "new_token" } });
|
const addButton = screen.getByTestId("add-header-button");
|
||||||
|
fireEvent.click(addButton);
|
||||||
|
|
||||||
expect(setBearerToken).toHaveBeenCalledWith("new_token");
|
// Verify that setCustomHeaders was called to add an empty header
|
||||||
|
expect(setCustomHeaders).toHaveBeenCalledWith([
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
value: "",
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should update header name", () => {
|
it("should update header name via custom headers", () => {
|
||||||
const setHeaderName = jest.fn();
|
const setCustomHeaders = jest.fn();
|
||||||
renderSidebar({
|
renderSidebar({
|
||||||
headerName: "Authorization",
|
customHeaders: [
|
||||||
setHeaderName,
|
{
|
||||||
|
name: "Authorization",
|
||||||
|
value: "Bearer token123",
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
setCustomHeaders,
|
||||||
transportType: "sse",
|
transportType: "sse",
|
||||||
});
|
});
|
||||||
|
|
||||||
openAuthSection();
|
openAuthSection();
|
||||||
|
|
||||||
const headerInput = screen.getByTestId("header-input");
|
const headerNameInput = screen.getByTestId("header-name-input-0");
|
||||||
fireEvent.change(headerInput, { target: { value: "X-Custom-Auth" } });
|
fireEvent.change(headerNameInput, { target: { value: "X-Custom-Auth" } });
|
||||||
|
|
||||||
expect(setHeaderName).toHaveBeenCalledWith("X-Custom-Auth");
|
expect(setCustomHeaders).toHaveBeenCalledWith([
|
||||||
|
{
|
||||||
|
name: "X-Custom-Auth",
|
||||||
|
value: "Bearer token123",
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should clear bearer token", () => {
|
it("should clear bearer token via custom headers", () => {
|
||||||
const setBearerToken = jest.fn();
|
const setCustomHeaders = jest.fn();
|
||||||
renderSidebar({
|
renderSidebar({
|
||||||
bearerToken: "existing_token",
|
customHeaders: [
|
||||||
setBearerToken,
|
{
|
||||||
|
name: "Authorization",
|
||||||
|
value: "Bearer existing_token",
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
setCustomHeaders,
|
||||||
transportType: "sse", // Set transport type to SSE
|
transportType: "sse", // Set transport type to SSE
|
||||||
});
|
});
|
||||||
|
|
||||||
openAuthSection();
|
openAuthSection();
|
||||||
|
|
||||||
const tokenInput = screen.getByTestId("bearer-token-input");
|
const headerValueInput = screen.getByTestId("header-value-input-0");
|
||||||
fireEvent.change(tokenInput, { target: { value: "" } });
|
fireEvent.change(headerValueInput, { target: { value: "" } });
|
||||||
|
|
||||||
expect(setBearerToken).toHaveBeenCalledWith("");
|
expect(setCustomHeaders).toHaveBeenCalledWith([
|
||||||
|
{
|
||||||
|
name: "Authorization",
|
||||||
|
value: "",
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should properly render bearer token input", () => {
|
it("should properly render header value input as password field", () => {
|
||||||
const { rerender } = renderSidebar({
|
const { rerender } = renderSidebar({
|
||||||
bearerToken: "existing_token",
|
customHeaders: [
|
||||||
|
{
|
||||||
|
name: "Authorization",
|
||||||
|
value: "Bearer existing_token",
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
transportType: "sse", // Set transport type to SSE
|
transportType: "sse", // Set transport type to SSE
|
||||||
});
|
});
|
||||||
|
|
||||||
openAuthSection();
|
openAuthSection();
|
||||||
|
|
||||||
// Token input should be a password field
|
// Token input should be a password field
|
||||||
const tokenInput = screen.getByTestId("bearer-token-input");
|
const tokenInput = screen.getByTestId("header-value-input-0");
|
||||||
expect(tokenInput).toHaveProperty("type", "password");
|
expect(tokenInput).toHaveProperty("type", "password");
|
||||||
|
|
||||||
// Update the token
|
// Update the token
|
||||||
fireEvent.change(tokenInput, { target: { value: "new_token" } });
|
fireEvent.change(tokenInput, { target: { value: "Bearer new_token" } });
|
||||||
|
|
||||||
// Rerender with updated token
|
// Rerender with updated token
|
||||||
rerender(
|
rerender(
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Sidebar
|
<Sidebar
|
||||||
{...defaultProps}
|
{...defaultProps}
|
||||||
bearerToken="new_token"
|
customHeaders={[
|
||||||
|
{
|
||||||
|
name: "Authorization",
|
||||||
|
value: "Bearer new_token",
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
transportType="sse"
|
transportType="sse"
|
||||||
/>
|
/>
|
||||||
</TooltipProvider>,
|
</TooltipProvider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Token input should still exist after update
|
// Token input should still exist after update
|
||||||
expect(screen.getByTestId("bearer-token-input")).toBeInTheDocument();
|
expect(screen.getByTestId("header-value-input-0")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should maintain token visibility state after update", () => {
|
it("should maintain token visibility state after update", () => {
|
||||||
const { rerender } = renderSidebar({
|
const { rerender } = renderSidebar({
|
||||||
bearerToken: "existing_token",
|
customHeaders: [
|
||||||
|
{
|
||||||
|
name: "Authorization",
|
||||||
|
value: "Bearer existing_token",
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
transportType: "sse", // Set transport type to SSE
|
transportType: "sse", // Set transport type to SSE
|
||||||
});
|
});
|
||||||
|
|
||||||
openAuthSection();
|
openAuthSection();
|
||||||
|
|
||||||
// Token input should be a password field
|
// Token input should be a password field
|
||||||
const tokenInput = screen.getByTestId("bearer-token-input");
|
const tokenInput = screen.getByTestId("header-value-input-0");
|
||||||
expect(tokenInput).toHaveProperty("type", "password");
|
expect(tokenInput).toHaveProperty("type", "password");
|
||||||
|
|
||||||
// Update the token
|
// Update the token
|
||||||
fireEvent.change(tokenInput, { target: { value: "new_token" } });
|
fireEvent.change(tokenInput, { target: { value: "Bearer new_token" } });
|
||||||
|
|
||||||
// Rerender with updated token
|
// Rerender with updated token
|
||||||
rerender(
|
rerender(
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Sidebar
|
<Sidebar
|
||||||
{...defaultProps}
|
{...defaultProps}
|
||||||
bearerToken="new_token"
|
customHeaders={[
|
||||||
|
{
|
||||||
|
name: "Authorization",
|
||||||
|
value: "Bearer new_token",
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
transportType="sse"
|
transportType="sse"
|
||||||
/>
|
/>
|
||||||
</TooltipProvider>,
|
</TooltipProvider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Token input should still exist after update
|
// Token input should still exist after update
|
||||||
expect(screen.getByTestId("bearer-token-input")).toBeInTheDocument();
|
expect(screen.getByTestId("header-value-input-0")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should maintain header name when toggling auth section", () => {
|
it("should maintain header name when toggling auth section", () => {
|
||||||
renderSidebar({
|
renderSidebar({
|
||||||
headerName: "X-API-Key",
|
customHeaders: [
|
||||||
|
{
|
||||||
|
name: "X-API-Key",
|
||||||
|
value: "api-key-123",
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
transportType: "sse",
|
transportType: "sse",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -741,7 +809,7 @@ describe("Sidebar", () => {
|
||||||
openAuthSection();
|
openAuthSection();
|
||||||
|
|
||||||
// Verify header name is displayed
|
// Verify header name is displayed
|
||||||
const headerInput = screen.getByTestId("header-input");
|
const headerInput = screen.getByTestId("header-name-input-0");
|
||||||
expect(headerInput).toHaveValue("X-API-Key");
|
expect(headerInput).toHaveValue("X-API-Key");
|
||||||
|
|
||||||
// Close auth section
|
// Close auth section
|
||||||
|
|
@ -752,19 +820,66 @@ describe("Sidebar", () => {
|
||||||
fireEvent.click(authButton);
|
fireEvent.click(authButton);
|
||||||
|
|
||||||
// Verify header name is still preserved
|
// Verify header name is still preserved
|
||||||
expect(screen.getByTestId("header-input")).toHaveValue("X-API-Key");
|
expect(screen.getByTestId("header-name-input-0")).toHaveValue(
|
||||||
|
"X-API-Key",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should display default header name when not specified", () => {
|
it("should display placeholder for header name when no headers exist", () => {
|
||||||
renderSidebar({
|
renderSidebar({
|
||||||
headerName: undefined,
|
customHeaders: [],
|
||||||
transportType: "sse",
|
transportType: "sse",
|
||||||
});
|
});
|
||||||
|
|
||||||
openAuthSection();
|
openAuthSection();
|
||||||
|
|
||||||
const headerInput = screen.getByTestId("header-input");
|
// Verify that the "No custom headers configured" message is shown
|
||||||
expect(headerInput).toHaveAttribute("placeholder", "Authorization");
|
expect(
|
||||||
|
screen.getByText("No custom headers configured"),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText('Click "Add" to get started'),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Verify the Add button is present
|
||||||
|
const addButton = screen.getByTestId("add-header-button");
|
||||||
|
expect(addButton).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should allow editing existing headers", () => {
|
||||||
|
const setCustomHeaders = jest.fn();
|
||||||
|
renderSidebar({
|
||||||
|
customHeaders: [
|
||||||
|
{
|
||||||
|
name: "Authorization",
|
||||||
|
value: "Bearer token123",
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
setCustomHeaders,
|
||||||
|
transportType: "sse",
|
||||||
|
});
|
||||||
|
|
||||||
|
openAuthSection();
|
||||||
|
|
||||||
|
// Verify header inputs are rendered
|
||||||
|
const headerNameInput = screen.getByTestId("header-name-input-0");
|
||||||
|
const headerValueInput = screen.getByTestId("header-value-input-0");
|
||||||
|
|
||||||
|
expect(headerNameInput).toHaveValue("Authorization");
|
||||||
|
expect(headerValueInput).toHaveValue("Bearer token123");
|
||||||
|
expect(headerNameInput).toHaveAttribute("placeholder", "Header Name");
|
||||||
|
expect(headerValueInput).toHaveAttribute("placeholder", "Header Value");
|
||||||
|
|
||||||
|
// Update header name
|
||||||
|
fireEvent.change(headerNameInput, { target: { value: "X-API-Key" } });
|
||||||
|
expect(setCustomHeaders).toHaveBeenCalledWith([
|
||||||
|
{
|
||||||
|
name: "X-API-Key",
|
||||||
|
value: "Bearer token123",
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue