Skip to content

Commit 90ab419

Browse files
fix: enhance error handling for custom headers and improve test coverage for header management
1 parent fde55ef commit 90ab419

File tree

3 files changed

+163
-41
lines changed

3 files changed

+163
-41
lines changed

client/src/App.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,11 @@ const App = () => {
137137
if (savedHeaders) {
138138
try {
139139
return JSON.parse(savedHeaders);
140-
} catch {
140+
} catch (error) {
141+
console.warn(
142+
`Failed to parse custom headers: "${savedHeaders}", will try legacy migration`,
143+
error,
144+
);
141145
// Fall back to migration if JSON parsing fails
142146
}
143147
}

client/src/components/CustomHeaders.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ const CustomHeaders = ({
153153
size="sm"
154154
onClick={addHeader}
155155
className="text-xs px-2"
156+
data-testid="add-header-button"
156157
>
157158
<Plus className="w-3 h-3 mr-1" />
158159
Add
@@ -185,6 +186,7 @@ const CustomHeaders = ({
185186
value={header.name}
186187
onChange={(e) => updateHeader(index, "name", e.target.value)}
187188
className="font-mono text-xs"
189+
data-testid={`header-name-input-${index}`}
188190
/>
189191
<div className="relative">
190192
<Input
@@ -195,6 +197,7 @@ const CustomHeaders = ({
195197
}
196198
type={visibleValues.has(index) ? "text" : "password"}
197199
className="font-mono text-xs pr-8"
200+
data-testid={`header-value-input-${index}`}
198201
/>
199202
<Button
200203
type="button"

client/src/components/__tests__/Sidebar.test.tsx

Lines changed: 155 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { render, screen, fireEvent, act } from "@testing-library/react";
1+
import {
2+
render,
3+
screen,
4+
fireEvent,
5+
act,
6+
waitFor,
7+
} from "@testing-library/react";
28
import "@testing-library/jest-dom";
39
import { describe, it, beforeEach, jest } from "@jest/globals";
410
import Sidebar from "../Sidebar";
@@ -623,125 +629,187 @@ describe("Sidebar", () => {
623629
fireEvent.click(button);
624630
};
625631

626-
it("should update bearer token", () => {
627-
const setBearerToken = jest.fn();
632+
it("should update bearer token via custom headers", async () => {
633+
const setCustomHeaders = jest.fn();
628634
renderSidebar({
629-
bearerToken: "",
630-
setBearerToken,
635+
customHeaders: [],
636+
setCustomHeaders,
631637
transportType: "sse", // Set transport type to SSE
632638
});
633639

634640
openAuthSection();
635641

636-
const tokenInput = screen.getByTestId("bearer-token-input");
637-
fireEvent.change(tokenInput, { target: { value: "new_token" } });
642+
// Add a new header
643+
const addButton = screen.getByTestId("add-header-button");
644+
fireEvent.click(addButton);
638645

639-
expect(setBearerToken).toHaveBeenCalledWith("new_token");
646+
// Verify that setCustomHeaders was called to add an empty header
647+
expect(setCustomHeaders).toHaveBeenCalledWith([
648+
{
649+
name: "",
650+
value: "",
651+
enabled: true,
652+
},
653+
]);
640654
});
641655

642-
it("should update header name", () => {
643-
const setHeaderName = jest.fn();
656+
it("should update header name via custom headers", () => {
657+
const setCustomHeaders = jest.fn();
644658
renderSidebar({
645-
headerName: "Authorization",
646-
setHeaderName,
659+
customHeaders: [
660+
{
661+
name: "Authorization",
662+
value: "Bearer token123",
663+
enabled: true,
664+
},
665+
],
666+
setCustomHeaders,
647667
transportType: "sse",
648668
});
649669

650670
openAuthSection();
651671

652-
const headerInput = screen.getByTestId("header-input");
653-
fireEvent.change(headerInput, { target: { value: "X-Custom-Auth" } });
672+
const headerNameInput = screen.getByTestId("header-name-input-0");
673+
fireEvent.change(headerNameInput, { target: { value: "X-Custom-Auth" } });
654674

655-
expect(setHeaderName).toHaveBeenCalledWith("X-Custom-Auth");
675+
expect(setCustomHeaders).toHaveBeenCalledWith([
676+
{
677+
name: "X-Custom-Auth",
678+
value: "Bearer token123",
679+
enabled: true,
680+
},
681+
]);
656682
});
657683

658-
it("should clear bearer token", () => {
659-
const setBearerToken = jest.fn();
684+
it("should clear bearer token via custom headers", () => {
685+
const setCustomHeaders = jest.fn();
660686
renderSidebar({
661-
bearerToken: "existing_token",
662-
setBearerToken,
687+
customHeaders: [
688+
{
689+
name: "Authorization",
690+
value: "Bearer existing_token",
691+
enabled: true,
692+
},
693+
],
694+
setCustomHeaders,
663695
transportType: "sse", // Set transport type to SSE
664696
});
665697

666698
openAuthSection();
667699

668-
const tokenInput = screen.getByTestId("bearer-token-input");
669-
fireEvent.change(tokenInput, { target: { value: "" } });
700+
const headerValueInput = screen.getByTestId("header-value-input-0");
701+
fireEvent.change(headerValueInput, { target: { value: "" } });
670702

671-
expect(setBearerToken).toHaveBeenCalledWith("");
703+
expect(setCustomHeaders).toHaveBeenCalledWith([
704+
{
705+
name: "Authorization",
706+
value: "",
707+
enabled: true,
708+
},
709+
]);
672710
});
673711

674-
it("should properly render bearer token input", () => {
712+
it("should properly render header value input as password field", () => {
675713
const { rerender } = renderSidebar({
676-
bearerToken: "existing_token",
714+
customHeaders: [
715+
{
716+
name: "Authorization",
717+
value: "Bearer existing_token",
718+
enabled: true,
719+
},
720+
],
677721
transportType: "sse", // Set transport type to SSE
678722
});
679723

680724
openAuthSection();
681725

682726
// Token input should be a password field
683-
const tokenInput = screen.getByTestId("bearer-token-input");
727+
const tokenInput = screen.getByTestId("header-value-input-0");
684728
expect(tokenInput).toHaveProperty("type", "password");
685729

686730
// Update the token
687-
fireEvent.change(tokenInput, { target: { value: "new_token" } });
731+
fireEvent.change(tokenInput, { target: { value: "Bearer new_token" } });
688732

689733
// Rerender with updated token
690734
rerender(
691735
<TooltipProvider>
692736
<Sidebar
693737
{...defaultProps}
694-
bearerToken="new_token"
738+
customHeaders={[
739+
{
740+
name: "Authorization",
741+
value: "Bearer new_token",
742+
enabled: true,
743+
},
744+
]}
695745
transportType="sse"
696746
/>
697747
</TooltipProvider>,
698748
);
699749

700750
// Token input should still exist after update
701-
expect(screen.getByTestId("bearer-token-input")).toBeInTheDocument();
751+
expect(screen.getByTestId("header-value-input-0")).toBeInTheDocument();
702752
});
703753

704754
it("should maintain token visibility state after update", () => {
705755
const { rerender } = renderSidebar({
706-
bearerToken: "existing_token",
756+
customHeaders: [
757+
{
758+
name: "Authorization",
759+
value: "Bearer existing_token",
760+
enabled: true,
761+
},
762+
],
707763
transportType: "sse", // Set transport type to SSE
708764
});
709765

710766
openAuthSection();
711767

712768
// Token input should be a password field
713-
const tokenInput = screen.getByTestId("bearer-token-input");
769+
const tokenInput = screen.getByTestId("header-value-input-0");
714770
expect(tokenInput).toHaveProperty("type", "password");
715771

716772
// Update the token
717-
fireEvent.change(tokenInput, { target: { value: "new_token" } });
773+
fireEvent.change(tokenInput, { target: { value: "Bearer new_token" } });
718774

719775
// Rerender with updated token
720776
rerender(
721777
<TooltipProvider>
722778
<Sidebar
723779
{...defaultProps}
724-
bearerToken="new_token"
780+
customHeaders={[
781+
{
782+
name: "Authorization",
783+
value: "Bearer new_token",
784+
enabled: true,
785+
},
786+
]}
725787
transportType="sse"
726788
/>
727789
</TooltipProvider>,
728790
);
729791

730792
// Token input should still exist after update
731-
expect(screen.getByTestId("bearer-token-input")).toBeInTheDocument();
793+
expect(screen.getByTestId("header-value-input-0")).toBeInTheDocument();
732794
});
733795

734796
it("should maintain header name when toggling auth section", () => {
735797
renderSidebar({
736-
headerName: "X-API-Key",
798+
customHeaders: [
799+
{
800+
name: "X-API-Key",
801+
value: "api-key-123",
802+
enabled: true,
803+
},
804+
],
737805
transportType: "sse",
738806
});
739807

740808
// Open auth section
741809
openAuthSection();
742810

743811
// Verify header name is displayed
744-
const headerInput = screen.getByTestId("header-input");
812+
const headerInput = screen.getByTestId("header-name-input-0");
745813
expect(headerInput).toHaveValue("X-API-Key");
746814

747815
// Close auth section
@@ -752,19 +820,66 @@ describe("Sidebar", () => {
752820
fireEvent.click(authButton);
753821

754822
// Verify header name is still preserved
755-
expect(screen.getByTestId("header-input")).toHaveValue("X-API-Key");
823+
expect(screen.getByTestId("header-name-input-0")).toHaveValue(
824+
"X-API-Key",
825+
);
756826
});
757827

758-
it("should display default header name when not specified", () => {
828+
it("should display placeholder for header name when no headers exist", () => {
759829
renderSidebar({
760-
headerName: undefined,
830+
customHeaders: [],
761831
transportType: "sse",
762832
});
763833

764834
openAuthSection();
765835

766-
const headerInput = screen.getByTestId("header-input");
767-
expect(headerInput).toHaveAttribute("placeholder", "Authorization");
836+
// Verify that the "No custom headers configured" message is shown
837+
expect(
838+
screen.getByText("No custom headers configured"),
839+
).toBeInTheDocument();
840+
expect(
841+
screen.getByText('Click "Add" to get started'),
842+
).toBeInTheDocument();
843+
844+
// Verify the Add button is present
845+
const addButton = screen.getByTestId("add-header-button");
846+
expect(addButton).toBeInTheDocument();
847+
});
848+
849+
it("should allow editing existing headers", () => {
850+
const setCustomHeaders = jest.fn();
851+
renderSidebar({
852+
customHeaders: [
853+
{
854+
name: "Authorization",
855+
value: "Bearer token123",
856+
enabled: true,
857+
},
858+
],
859+
setCustomHeaders,
860+
transportType: "sse",
861+
});
862+
863+
openAuthSection();
864+
865+
// Verify header inputs are rendered
866+
const headerNameInput = screen.getByTestId("header-name-input-0");
867+
const headerValueInput = screen.getByTestId("header-value-input-0");
868+
869+
expect(headerNameInput).toHaveValue("Authorization");
870+
expect(headerValueInput).toHaveValue("Bearer token123");
871+
expect(headerNameInput).toHaveAttribute("placeholder", "Header Name");
872+
expect(headerValueInput).toHaveAttribute("placeholder", "Header Value");
873+
874+
// Update header name
875+
fireEvent.change(headerNameInput, { target: { value: "X-API-Key" } });
876+
expect(setCustomHeaders).toHaveBeenCalledWith([
877+
{
878+
name: "X-API-Key",
879+
value: "Bearer token123",
880+
enabled: true,
881+
},
882+
]);
768883
});
769884
});
770885

0 commit comments

Comments
 (0)