subscriptionQueryAst or query argument has to be specified for the event subscription (#107)
This commit is contained in:
parent
33d7666c7d
commit
83ed6719d5
3 changed files with 74 additions and 25 deletions
|
@ -21,7 +21,8 @@ export type SaleorWebhookError =
|
||||||
| "NOT_REGISTERED"
|
| "NOT_REGISTERED"
|
||||||
| "SIGNATURE_VERIFICATION_FAILED"
|
| "SIGNATURE_VERIFICATION_FAILED"
|
||||||
| "WRONG_METHOD"
|
| "WRONG_METHOD"
|
||||||
| "CANT_BE_PARSED";
|
| "CANT_BE_PARSED"
|
||||||
|
| "CONFIGURATION_ERROR";
|
||||||
|
|
||||||
export class WebhookError extends Error {
|
export class WebhookError extends Error {
|
||||||
errorType: SaleorWebhookError = "OTHER";
|
errorType: SaleorWebhookError = "OTHER";
|
||||||
|
@ -116,7 +117,7 @@ export const processAsyncSaleorWebhook: ProcessAsyncSaleorWebhook = async <T>({
|
||||||
parsedBody = JSON.parse(rawBody);
|
parsedBody = JSON.parse(rawBody);
|
||||||
} catch {
|
} catch {
|
||||||
debug("Request body cannot be parsed");
|
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
|
// Check if domain is installed in the app
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
import { ASTNode } from "graphql";
|
||||||
import { createMocks } from "node-mocks-http";
|
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 { APL } from "../../APL";
|
||||||
|
import { processAsyncSaleorWebhook } from "./process-async-saleor-webhook";
|
||||||
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "./saleor-async-webhook";
|
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "./saleor-async-webhook";
|
||||||
|
|
||||||
const webhookPath = "api/webhooks/product-updated";
|
const webhookPath = "api/webhooks/product-updated";
|
||||||
|
@ -23,10 +25,40 @@ describe("SaleorAsyncWebhook", () => {
|
||||||
isConfigured: vi.fn(),
|
isConfigured: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const saleorAsyncWebhook = new SaleorAsyncWebhook({
|
afterEach(async () => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
const validAsyncWebhookConfiguration = {
|
||||||
apl: mockAPL,
|
apl: mockAPL,
|
||||||
asyncEvent: "PRODUCT_UPDATED",
|
asyncEvent: "PRODUCT_UPDATED",
|
||||||
webhookPath,
|
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 () => {
|
it("targetUrl should return full path to the webhook route based on given baseUrl", async () => {
|
||||||
|
@ -39,18 +71,19 @@ describe("SaleorAsyncWebhook", () => {
|
||||||
isActive: true,
|
isActive: true,
|
||||||
name: "PRODUCT_UPDATED webhook",
|
name: "PRODUCT_UPDATED webhook",
|
||||||
targetUrl: "http://example.com/api/webhooks/product-updated",
|
targetUrl: "http://example.com/api/webhooks/product-updated",
|
||||||
|
query: "subscription { event { ... on ProductUpdated { product { id }}}}",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Test createHandler which return success", async () => {
|
it("Test createHandler which return success", async () => {
|
||||||
// prepare mocked context returned by mocked process function
|
// prepare mocked context returned by mocked process function
|
||||||
vi.mock("./process-async-saleor-webhook", () => ({
|
vi.mock("./process-async-saleor-webhook");
|
||||||
processAsyncSaleorWebhook: vi.fn().mockResolvedValue({
|
|
||||||
baseUrl: "example.com",
|
vi.mocked(processAsyncSaleorWebhook).mockImplementationOnce(async () => ({
|
||||||
event: "product_updated",
|
baseUrl: "example.com",
|
||||||
payload: { data: "test_payload" },
|
event: "product_updated",
|
||||||
authData: { domain: "example.com", token: "token" },
|
payload: { data: "test_payload" },
|
||||||
}),
|
authData: { domain: "example.com", token: "token" },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Test handler - will throw error if mocked context is not passed to it
|
// Test handler - will throw error if mocked context is not passed to it
|
||||||
|
|
|
@ -15,16 +15,26 @@ import {
|
||||||
|
|
||||||
const debug = createDebug("SaleorAsyncWebhook");
|
const debug = createDebug("SaleorAsyncWebhook");
|
||||||
|
|
||||||
export interface WebhookManifestConfiguration {
|
interface WebhookManifestConfigurationBase {
|
||||||
name?: string;
|
name?: string;
|
||||||
webhookPath: string;
|
webhookPath: string;
|
||||||
subscriptionQueryAst?: ASTNode;
|
|
||||||
query?: string;
|
|
||||||
asyncEvent: WebhookEvent;
|
asyncEvent: WebhookEvent;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
apl: APL;
|
apl: APL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface WebhookManifestConfigurationWithAst extends WebhookManifestConfigurationBase {
|
||||||
|
subscriptionQueryAst: ASTNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WebhookManifestConfigurationWithQuery extends WebhookManifestConfigurationBase {
|
||||||
|
query: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebhookManifestConfiguration =
|
||||||
|
| WebhookManifestConfigurationWithAst
|
||||||
|
| WebhookManifestConfigurationWithQuery;
|
||||||
|
|
||||||
export const ErrorCodeMap: Record<SaleorWebhookError, number> = {
|
export const ErrorCodeMap: Record<SaleorWebhookError, number> = {
|
||||||
OTHER: 500,
|
OTHER: 500,
|
||||||
MISSING_HOST_HEADER: 400,
|
MISSING_HOST_HEADER: 400,
|
||||||
|
@ -38,6 +48,7 @@ export const ErrorCodeMap: Record<SaleorWebhookError, number> = {
|
||||||
SIGNATURE_VERIFICATION_FAILED: 401,
|
SIGNATURE_VERIFICATION_FAILED: 401,
|
||||||
WRONG_METHOD: 405,
|
WRONG_METHOD: 405,
|
||||||
CANT_BE_PARSED: 400,
|
CANT_BE_PARSED: 400,
|
||||||
|
CONFIGURATION_ERROR: 500,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NextWebhookApiHandler<TPayload = unknown, TResp = unknown> = (
|
export type NextWebhookApiHandler<TPayload = unknown, TResp = unknown> = (
|
||||||
|
@ -61,18 +72,22 @@ export class SaleorAsyncWebhook<TPayload = unknown> {
|
||||||
|
|
||||||
apl: APL;
|
apl: APL;
|
||||||
|
|
||||||
constructor({
|
constructor(configuration: WebhookManifestConfiguration) {
|
||||||
name,
|
const { name, webhookPath, asyncEvent, apl, isActive = true } = configuration;
|
||||||
webhookPath,
|
|
||||||
subscriptionQueryAst,
|
|
||||||
query,
|
|
||||||
asyncEvent,
|
|
||||||
apl,
|
|
||||||
isActive = true,
|
|
||||||
}: WebhookManifestConfiguration) {
|
|
||||||
this.name = name || `${asyncEvent} webhook`;
|
this.name = name || `${asyncEvent} webhook`;
|
||||||
this.subscriptionQueryAst = subscriptionQueryAst;
|
if ("query" in configuration) {
|
||||||
this.query = query;
|
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.webhookPath = webhookPath;
|
||||||
this.asyncEvent = asyncEvent;
|
this.asyncEvent = asyncEvent;
|
||||||
this.isActive = isActive;
|
this.isActive = isActive;
|
||||||
|
|
Loading…
Reference in a new issue