diff --git a/.changeset/flat-owls-sniff.md b/.changeset/flat-owls-sniff.md new file mode 100644 index 00000000000..67d5c289c09 --- /dev/null +++ b/.changeset/flat-owls-sniff.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +You can now use custom auth headers in graphiql dev mode panel. diff --git a/src/components/DevModePanel/DevModePanel.tsx b/src/components/DevModePanel/DevModePanel.tsx index 4cb3a9e8ff3..6f854418d29 100644 --- a/src/components/DevModePanel/DevModePanel.tsx +++ b/src/components/DevModePanel/DevModePanel.tsx @@ -2,8 +2,7 @@ import { useDashboardTheme } from "@dashboard/components/GraphiQL/styles"; import { DashboardModal } from "@dashboard/components/Modal"; import { useOnboarding } from "@dashboard/welcomePage/WelcomePageOnboarding/onboardingContext"; -import { createGraphiQLFetcher, FetcherOpts, FetcherParams } from "@graphiql/toolkit"; -import { createFetch } from "@saleor/sdk"; +import { FetcherOpts, FetcherParams } from "@graphiql/toolkit"; import React from "react"; import { useIntl } from "react-intl"; @@ -12,8 +11,7 @@ import { useContextualLink } from "../AppLayout/ContextualLinks/useContextualLin import PlainGraphiQL from "../GraphiQLPlain"; import { useDevModeContext } from "./hooks"; import { messages } from "./messages"; - -const authorizedFetch = createFetch(); +import { getFetcher } from "./utils"; export const DevModePanel: React.FC = () => { const intl = useIntl(); @@ -21,15 +19,13 @@ export const DevModePanel: React.FC = () => { const { rootStyle } = useDashboardTheme(); const { markOnboardingStepAsCompleted } = useOnboarding(); const { isDevModeVisible, variables, devModeContent, setDevModeVisibility } = useDevModeContext(); - const baseFetcher = createGraphiQLFetcher({ - url: process.env.API_URL, - fetch: authorizedFetch, - }); const fetcher = async (graphQLParams: FetcherParams, opts: FetcherOpts) => { if (graphQLParams.operationName !== "IntrospectionQuery") { markOnboardingStepAsCompleted("graphql-playground"); } + const baseFetcher = getFetcher(opts); + const result = await baseFetcher(graphQLParams, opts); // Call the base fetcher return result; diff --git a/src/components/DevModePanel/utils.test.ts b/src/components/DevModePanel/utils.test.ts new file mode 100644 index 00000000000..66f6721d1ea --- /dev/null +++ b/src/components/DevModePanel/utils.test.ts @@ -0,0 +1,95 @@ +import { createGraphiQLFetcher, FetcherOpts } from "@graphiql/toolkit"; +import { createFetch } from "@saleor/sdk"; + +import { getFetcher } from "./utils"; + +jest.mock("@graphiql/toolkit", () => ({ + createGraphiQLFetcher: jest.fn(), +})); + +jest.mock("@saleor/sdk", () => ({ + createFetch: jest.fn().mockReturnValue(jest.fn()), +})); + +const mockCreateGraphiQLFetcher = createGraphiQLFetcher as jest.Mock; +const authorizedFetch = createFetch as jest.Mock; + +describe("getFetcher", () => { + const mockApiUrl = "http://test-api.com"; + let originalFetch: typeof fetch; + + beforeEach(() => { + process.env.API_URL = mockApiUrl; + originalFetch = global.fetch; + }); + + afterEach(() => { + jest.resetAllMocks(); + global.fetch = originalFetch; + }); + + it("should return fetcher with authorizedFetch when no auth headers", () => { + // Arrange + const opts: FetcherOpts = { headers: {} }; + + // Act + getFetcher(opts); + + // Assert + expect(authorizedFetch).toHaveBeenCalled(); + // 'toHaveBeenCalledWith' can't properly compare mock functions + expect(mockCreateGraphiQLFetcher).toHaveBeenCalledWith( + expect.objectContaining({ + url: mockApiUrl, + }), + ); + }); + + it("should return fetcher with fetch when Authorization header present", () => { + // Arrange + const opts: FetcherOpts = { + headers: { Authorization: "Bearer token" }, + }; + + // Act + getFetcher(opts); + + // Assert + expect(mockCreateGraphiQLFetcher).toHaveBeenCalledWith({ + url: mockApiUrl, + fetch: fetch, + }); + }); + + it("should return fetcher with fetch when Authorization-Bearer header present", () => { + // Arrange + const opts: FetcherOpts = { + headers: { "Authorization-Bearer": "token" }, + }; + + // Act + getFetcher(opts); + + // Assert + expect(mockCreateGraphiQLFetcher).toHaveBeenCalledWith({ + url: mockApiUrl, + fetch: fetch, + }); + }); + + it("should return fetcher with fetch when lowercase header present", () => { + // Arrange + const opts: FetcherOpts = { + headers: { "authorization-bearer": "token" }, + }; + + // Act + getFetcher(opts); + + // Assert + expect(mockCreateGraphiQLFetcher).toHaveBeenCalledWith({ + url: mockApiUrl, + fetch: fetch, + }); + }); +}); diff --git a/src/components/DevModePanel/utils.ts b/src/components/DevModePanel/utils.ts new file mode 100644 index 00000000000..321ba6b4813 --- /dev/null +++ b/src/components/DevModePanel/utils.ts @@ -0,0 +1,23 @@ +import { createGraphiQLFetcher, FetcherOpts } from "@graphiql/toolkit"; +import { createFetch } from "@saleor/sdk"; + +const authHeaders = ["Authorization", "Authorization-Bearer"]; + +const authorizedFetch = createFetch(); + +export const getFetcher = (opts: FetcherOpts) => { + let httpFetch = authorizedFetch; + + const hasAuthorizationHeaders = + opts.headers && + authHeaders.some(header => opts.headers![header] || opts.headers![header.toLowerCase()]); + + if (hasAuthorizationHeaders) { + httpFetch = fetch; + } + + return createGraphiQLFetcher({ + url: process.env.API_URL as string, + fetch: httpFetch as typeof fetch, + }); +};