subscriptionQueryAst or query argument has to be specified for the event subscription (#107)

This commit is contained in:
Krzysztof Wolski 2022-11-05 10:35:01 +01:00 committed by GitHub
parent 33d7666c7d
commit 83ed6719d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 74 additions and 25 deletions

View file

@ -21,7 +21,8 @@ export type SaleorWebhookError =
| "NOT_REGISTERED"
| "SIGNATURE_VERIFICATION_FAILED"
| "WRONG_METHOD"
| "CANT_BE_PARSED";
| "CANT_BE_PARSED"
| "CONFIGURATION_ERROR";
export class WebhookError extends Error {
errorType: SaleorWebhookError = "OTHER";
@ -116,7 +117,7 @@ export const processAsyncSaleorWebhook: ProcessAsyncSaleorWebhook = async <T>({
parsedBody = JSON.parse(rawBody);
} catch {
debug("Request body cannot be parsed");
throw new WebhookError("Request body cant be parsed", "CANT_BE_PARSED");
throw new WebhookError("Request body can't be parsed", "CANT_BE_PARSED");
}
// Check if domain is installed in the app

View file

@ -1,7 +1,9 @@
import { ASTNode } from "graphql";
import { createMocks } from "node-mocks-http";
import { describe, expect, it, vi } from "vitest";
import { afterEach, describe, expect, it, vi } from "vitest";
import { APL } from "../../APL";
import { processAsyncSaleorWebhook } from "./process-async-saleor-webhook";
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "./saleor-async-webhook";
const webhookPath = "api/webhooks/product-updated";
@ -23,10 +25,40 @@ describe("SaleorAsyncWebhook", () => {
isConfigured: vi.fn(),
};
const saleorAsyncWebhook = new SaleorAsyncWebhook({
afterEach(async () => {
vi.restoreAllMocks();
});
const validAsyncWebhookConfiguration = {
apl: mockAPL,
asyncEvent: "PRODUCT_UPDATED",
webhookPath,
query: "subscription { event { ... on ProductUpdated { product { id }}}}",
};
const saleorAsyncWebhook = new SaleorAsyncWebhook(validAsyncWebhookConfiguration);
it("throw CONFIGURATION_ERROR if query and subscriptionQueryAst are both absent", async () => {
expect(() => {
// eslint-disable-next-line no-new
new SaleorAsyncWebhook({
...validAsyncWebhookConfiguration,
// @ts-ignore: We make type error for test purpose
query: undefined,
subscriptionQueryAst: undefined,
});
}).toThrowError();
});
it("constructor passes if subscriptionQueryAst is provided", async () => {
expect(() => {
// eslint-disable-next-line no-new
new SaleorAsyncWebhook({
...validAsyncWebhookConfiguration,
query: undefined,
subscriptionQueryAst: {} as ASTNode,
});
}).not.toThrowError();
});
it("targetUrl should return full path to the webhook route based on given baseUrl", async () => {
@ -39,18 +71,19 @@ describe("SaleorAsyncWebhook", () => {
isActive: true,
name: "PRODUCT_UPDATED webhook",
targetUrl: "http://example.com/api/webhooks/product-updated",
query: "subscription { event { ... on ProductUpdated { product { id }}}}",
});
});
it("Test createHandler which return success", async () => {
// prepare mocked context returned by mocked process function
vi.mock("./process-async-saleor-webhook", () => ({
processAsyncSaleorWebhook: vi.fn().mockResolvedValue({
baseUrl: "example.com",
event: "product_updated",
payload: { data: "test_payload" },
authData: { domain: "example.com", token: "token" },
}),
vi.mock("./process-async-saleor-webhook");
vi.mocked(processAsyncSaleorWebhook).mockImplementationOnce(async () => ({
baseUrl: "example.com",
event: "product_updated",
payload: { data: "test_payload" },
authData: { domain: "example.com", token: "token" },
}));
// Test handler - will throw error if mocked context is not passed to it

View file

@ -15,16 +15,26 @@ import {
const debug = createDebug("SaleorAsyncWebhook");
export interface WebhookManifestConfiguration {
interface WebhookManifestConfigurationBase {
name?: string;
webhookPath: string;
subscriptionQueryAst?: ASTNode;
query?: string;
asyncEvent: WebhookEvent;
isActive?: boolean;
apl: APL;
}
interface WebhookManifestConfigurationWithAst extends WebhookManifestConfigurationBase {
subscriptionQueryAst: ASTNode;
}
interface WebhookManifestConfigurationWithQuery extends WebhookManifestConfigurationBase {
query: string;
}
type WebhookManifestConfiguration =
| WebhookManifestConfigurationWithAst
| WebhookManifestConfigurationWithQuery;
export const ErrorCodeMap: Record<SaleorWebhookError, number> = {
OTHER: 500,
MISSING_HOST_HEADER: 400,
@ -38,6 +48,7 @@ export const ErrorCodeMap: Record<SaleorWebhookError, number> = {
SIGNATURE_VERIFICATION_FAILED: 401,
WRONG_METHOD: 405,
CANT_BE_PARSED: 400,
CONFIGURATION_ERROR: 500,
};
export type NextWebhookApiHandler<TPayload = unknown, TResp = unknown> = (
@ -61,18 +72,22 @@ export class SaleorAsyncWebhook<TPayload = unknown> {
apl: APL;
constructor({
name,
webhookPath,
subscriptionQueryAst,
query,
asyncEvent,
apl,
isActive = true,
}: WebhookManifestConfiguration) {
constructor(configuration: WebhookManifestConfiguration) {
const { name, webhookPath, asyncEvent, apl, isActive = true } = configuration;
this.name = name || `${asyncEvent} webhook`;
this.subscriptionQueryAst = subscriptionQueryAst;
this.query = query;
if ("query" in configuration) {
this.query = configuration.query;
}
if ("subscriptionQueryAst" in configuration) {
this.subscriptionQueryAst = configuration.subscriptionQueryAst;
}
if (!this.subscriptionQueryAst && !this.query) {
throw new WebhookError(
"Need to specify `subscriptionQueryAst` or `query` to create webhook subscription",
"CONFIGURATION_ERROR"
);
}
this.webhookPath = webhookPath;
this.asyncEvent = asyncEvent;
this.isActive = isActive;