-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Web API platform handlers (#403)
* Add lambda and web api handlers Add exports Update JSDocs Add tests for register handler Remove `saleorDomain` from type Rename aws-lambda file Throw errors in adapters, instead of resolving to null Add AWS lambda tests Fix lambda types Fix lambda headers Add lambda manifest test, fix headers resolution Fetch API: add tests for protected handler, fix types Add tests for lambda protected handler, fix types Simplify Fetch API adapter logic, add tests for adapter Add lambda tests Refactor sync webhook names, refactor types Add web api sync webhook tests Add async webhook tests Add lambda sync webhook test Fix types Remove aws-lambda handlers (separate PR) Remove lambda Removed unused functions * Add changeset
- Loading branch information
1 parent
51caa77
commit 4fa8271
Showing
28 changed files
with
1,251 additions
and
147 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@saleor/app-sdk": minor | ||
--- | ||
|
||
Added handlers for Web API: Request and Response |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
184 changes: 184 additions & 0 deletions
184
src/handlers/platforms/fetch-api/create-app-register-handler.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
import { beforeEach, describe, expect, it, vi } from "vitest"; | ||
|
||
import { AuthData } from "@/APL"; | ||
import { SALEOR_API_URL_HEADER } from "@/const"; | ||
import * as fetchRemoteJwksModule from "@/fetch-remote-jwks"; | ||
import * as getAppIdModule from "@/get-app-id"; | ||
import { MockAPL } from "@/test-utils/mock-apl"; | ||
|
||
import { | ||
createAppRegisterHandler, | ||
CreateAppRegisterHandlerOptions, | ||
} from "./create-app-register-handler"; | ||
|
||
describe("Fetch API createAppRegisterHandler", () => { | ||
const mockJwksValue = "{}"; | ||
const mockAppId = "42"; | ||
const saleorApiUrl = "https://mock-saleor-domain.saleor.cloud/graphql/"; | ||
const authToken = "mock-auth-token"; | ||
|
||
vi.spyOn(fetchRemoteJwksModule, "fetchRemoteJwks").mockResolvedValue(mockJwksValue); | ||
vi.spyOn(getAppIdModule, "getAppId").mockResolvedValue(mockAppId); | ||
let mockApl: MockAPL; | ||
let request: Request; | ||
|
||
beforeEach(() => { | ||
mockApl = new MockAPL(); | ||
request = new Request("https://example.com", { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
Host: "mock-slaeor-domain.saleor.cloud", | ||
"X-Forwarded-Proto": "https", | ||
[SALEOR_API_URL_HEADER]: saleorApiUrl, | ||
}, | ||
body: JSON.stringify({ auth_token: authToken }), | ||
}); | ||
}); | ||
|
||
it("Sets auth data for correct request", async () => { | ||
const handler = createAppRegisterHandler({ apl: mockApl }); | ||
const response = await handler(request); | ||
|
||
expect(response.status).toBe(200); | ||
expect(mockApl.set).toHaveBeenCalledWith({ | ||
saleorApiUrl, | ||
token: authToken, | ||
appId: mockAppId, | ||
jwks: mockJwksValue, | ||
}); | ||
}); | ||
|
||
it("Returns 403 for prohibited Saleor URLs", async () => { | ||
request.headers.set(SALEOR_API_URL_HEADER, "https://wrong-domain.saleor.cloud/graphql/"); | ||
const handler = createAppRegisterHandler({ | ||
apl: mockApl, | ||
allowedSaleorUrls: [saleorApiUrl], | ||
}); | ||
|
||
const response = await handler(request); | ||
const data = await response.json(); | ||
|
||
expect(response.status).toBe(403); | ||
expect(data.success).toBe(false); | ||
}); | ||
|
||
it("Handles invalid JSON bodies", async () => { | ||
const brokenRequest = new Request("https://example.com", { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
Host: "mock-slaeor-domain.saleor.cloud", | ||
"X-Forwarded-Proto": "https", | ||
[SALEOR_API_URL_HEADER]: saleorApiUrl, | ||
}, | ||
body: "{ ", | ||
}); | ||
const handler = createAppRegisterHandler({ | ||
apl: mockApl, | ||
allowedSaleorUrls: [saleorApiUrl], | ||
}); | ||
|
||
const response = await handler(brokenRequest); | ||
|
||
expect(response.status).toBe(400); | ||
await expect(response.text()).resolves.toBe("Invalid request json."); | ||
}); | ||
|
||
describe("Callback hooks", () => { | ||
const expectedAuthData: AuthData = { | ||
token: authToken, | ||
saleorApiUrl, | ||
jwks: mockJwksValue, | ||
appId: mockAppId, | ||
}; | ||
|
||
it("Triggers success callbacks when APL save succeeds", async () => { | ||
const mockOnRequestStart = vi.fn(); | ||
const mockOnRequestVerified = vi.fn(); | ||
const mockOnAuthAplFailed = vi.fn(); | ||
const mockOnAuthAplSaved = vi.fn(); | ||
|
||
const handler = createAppRegisterHandler({ | ||
apl: mockApl, | ||
onRequestStart: mockOnRequestStart, | ||
onRequestVerified: mockOnRequestVerified, | ||
onAplSetFailed: mockOnAuthAplFailed, | ||
onAuthAplSaved: mockOnAuthAplSaved, | ||
}); | ||
|
||
await handler(request); | ||
|
||
expect(mockOnRequestStart).toHaveBeenCalledWith( | ||
request, | ||
expect.objectContaining({ | ||
authToken, | ||
saleorApiUrl, | ||
}) | ||
); | ||
expect(mockOnRequestVerified).toHaveBeenCalledWith( | ||
request, | ||
expect.objectContaining({ | ||
authData: expectedAuthData, | ||
}) | ||
); | ||
expect(mockOnAuthAplSaved).toHaveBeenCalledWith( | ||
request, | ||
expect.objectContaining({ | ||
authData: expectedAuthData, | ||
}) | ||
); | ||
expect(mockOnAuthAplFailed).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it("Triggers failure callback when APL save fails", async () => { | ||
const mockOnAuthAplFailed = vi.fn(); | ||
const mockOnAuthAplSaved = vi.fn(); | ||
|
||
mockApl.set.mockRejectedValueOnce(new Error("Save failed")); | ||
|
||
const handler = createAppRegisterHandler({ | ||
apl: mockApl, | ||
onAplSetFailed: mockOnAuthAplFailed, | ||
onAuthAplSaved: mockOnAuthAplSaved, | ||
}); | ||
|
||
await handler(request); | ||
|
||
expect(mockOnAuthAplFailed).toHaveBeenCalledWith( | ||
request, | ||
expect.objectContaining({ | ||
error: expect.any(Error), | ||
authData: expectedAuthData, | ||
}) | ||
); | ||
}); | ||
|
||
it("Allows custom error responses via hooks", async () => { | ||
const mockOnRequestStart = vi | ||
.fn<NonNullable<CreateAppRegisterHandlerOptions["onRequestStart"]>>() | ||
.mockImplementation((_req, context) => | ||
context.respondWithError({ | ||
status: 401, | ||
message: "test message", | ||
}) | ||
); | ||
const handler = createAppRegisterHandler({ | ||
apl: mockApl, | ||
onRequestStart: mockOnRequestStart, | ||
}); | ||
|
||
const response = await handler(request); | ||
|
||
expect(response.status).toBe(401); | ||
await expect(response.json()).resolves.toStrictEqual({ | ||
error: { | ||
code: "REGISTER_HANDLER_HOOK_ERROR", | ||
message: "test message", | ||
}, | ||
success: false, | ||
}); | ||
expect(mockOnRequestStart).toHaveBeenCalled(); | ||
}); | ||
}); | ||
}); |
36 changes: 36 additions & 0 deletions
36
src/handlers/platforms/fetch-api/create-app-register-handler.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { RegisterActionHandler } from "@/handlers/actions/register-action-handler"; | ||
import { GenericCreateAppRegisterHandlerOptions } from "@/handlers/shared/create-app-register-handler-types"; | ||
|
||
import { WebApiAdapter, WebApiHandler, WebApiHandlerInput } from "./platform-adapter"; | ||
|
||
export type CreateAppRegisterHandlerOptions = | ||
GenericCreateAppRegisterHandlerOptions<WebApiHandlerInput>; | ||
|
||
/** | ||
* Returns API route handler for Web API compatible request handlers | ||
* (examples: Next.js app router, hono, deno, etc.) | ||
* that use signature: (req: Request) => Response | ||
* where Request and Response are Fetch API objects | ||
* | ||
* Handler is for register endpoint that is called by Saleor when installing the app | ||
* | ||
* It verifies the request and stores `app_token` from Saleor | ||
* in APL and along with all required AuthData fields (jwks, saleorApiUrl, ...) | ||
* | ||
* **Recommended path**: `/api/register` | ||
* (configured in manifest handler) | ||
* | ||
* To learn more check Saleor docs | ||
* @see {@link https://docs.saleor.io/developer/extending/apps/architecture/app-requirements#register-url} | ||
* | ||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Response} | ||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Request} | ||
* */ | ||
export const createAppRegisterHandler = | ||
(config: CreateAppRegisterHandlerOptions): WebApiHandler => | ||
async (req) => { | ||
const adapter = new WebApiAdapter(req); | ||
const useCase = new RegisterActionHandler(adapter); | ||
const result = await useCase.handleAction(config); | ||
return adapter.send(result); | ||
}; |
Oops, something went wrong.