Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ✨ webhook request validation logic #24

Merged
merged 2 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/enums/WebhookEvent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export const WebhookEvent = {
order_status_update: "order_status_update",
template_preview_rendered: "template_preview_rendered",
batch_status_update: "batch_status_update",
} as const;

export type WebhookEvent = (typeof WebhookEvent)[keyof typeof WebhookEvent];
8 changes: 8 additions & 0 deletions src/models/Batch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ export class Batch {
return this._data.estimatedPrice;
}

public get estimatedTax(): number {
return this._data.estimatedTax;
}

public get sender(): Address {
return this._data.sender;
}

public get sendDate(): Date | undefined {
return this._data.sendDate ? new Date(this._data.sendDate) : undefined;
}
Expand Down
18 changes: 16 additions & 2 deletions src/models/WebhookRequest.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import {
IBatchStatusUpdateWebhookRequest,
IOrderStatusUpdateWebhookRequest,
ITemplatePreviewRenderedWebhookRequest,
IWebhookRequest,
} from "~/models/_interfaces/IWebhookRequest";
import { Protected } from "~/PrintOne";
import { Batch } from "~/models/Batch";
import { Order } from "~/models/Order";
import { Protected } from "~/PrintOne";
import { PreviewDetails } from "~/models/PreviewDetails";

abstract class AbstractWebhookRequest<T, E extends IWebhookRequest> {
Expand All @@ -26,7 +28,8 @@ abstract class AbstractWebhookRequest<T, E extends IWebhookRequest> {

export type WebhookRequest =
| OrderStatusUpdateWebhookRequest
| TemplatePreviewRenderedWebhookRequest;
| TemplatePreviewRenderedWebhookRequest
| BatchStatusUpdateWebhookRequest;

export function webhookRequestFactory(
_protected: Protected,
Expand All @@ -39,6 +42,8 @@ export function webhookRequestFactory(
return new OrderStatusUpdateWebhookRequest(_protected, data);
case "template_preview_rendered":
return new TemplatePreviewRenderedWebhookRequest(_protected, data);
case "batch_status_update":
return new BatchStatusUpdateWebhookRequest(_protected, data);
default:
throw new Error(`Unknown webhook event: ${event}`);
}
Expand All @@ -61,3 +66,12 @@ export class TemplatePreviewRenderedWebhookRequest extends AbstractWebhookReques
return new PreviewDetails(this._protected, this._data.data);
}
}

export class BatchStatusUpdateWebhookRequest extends AbstractWebhookRequest<
Batch,
IBatchStatusUpdateWebhookRequest
> {
get data(): Batch {
return new Batch(this._protected, this._data.data);
}
}
4 changes: 4 additions & 0 deletions src/models/_interfaces/IBatch.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Address } from "~/models/Address";

export type IBatch = {
id: string;
companyId: string;
Expand All @@ -8,6 +10,8 @@ export type IBatch = {
isBillable: boolean;
templateId: string;
estimatedPrice: number;
estimatedTax: number;
sender: Address;
sendDate: string | null;
status: string;
orders: {
Expand Down
10 changes: 9 additions & 1 deletion src/models/_interfaces/IWebhookRequest.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { IBatch } from "~/models/_interfaces/IBatch";
import { IOrder } from "~/models/_interfaces/IOrder";
import { IPreviewDetails } from "~/models/_interfaces/IPreviewDetails";

export type IWebhookRequest =
| IOrderStatusUpdateWebhookRequest
| ITemplatePreviewRenderedWebhookRequest;
| ITemplatePreviewRenderedWebhookRequest
| IBatchStatusUpdateWebhookRequest;

export type IOrderStatusUpdateWebhookRequest = {
data: IOrder;
Expand All @@ -16,3 +18,9 @@ export type ITemplatePreviewRenderedWebhookRequest = {
event: "template_preview_rendered";
createdAt: string;
};

export type IBatchStatusUpdateWebhookRequest = {
data: IBatch;
event: "batch_status_update";
createdAt: string;
};
44 changes: 44 additions & 0 deletions test/PrintOne.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { BatchStatus } from "../src/enums/BatchStatus";
import { Webhook } from "~/models/Webhook";
import { WebhookEvent } from "~/enums/WebhookEvent";
import {
BatchStatusUpdateWebhookRequest,
OrderStatusUpdateWebhookRequest,
TemplatePreviewRenderedWebhookRequest,
} from "~/models/WebhookRequest";
Expand Down Expand Up @@ -1552,6 +1553,8 @@ describe("getBatch", function () {
expect(batch.templateId).toEqual(expect.any(String));
expect(batch.isBillable).toEqual(expect.any(Boolean));
expect(batch.estimatedPrice).toEqual(expect.any(Number));
expect(batch.estimatedTax).toEqual(expect.any(Number));
expect(batch.sender).toEqual(expect.any(Object));
expect(batch.sendDate).toEqual(
expect.toBeOneOf([undefined, expect.any(Date)]),
);
Expand Down Expand Up @@ -2015,6 +2018,47 @@ describe("validateWebhook", function () {
expect(webhook.data).toEqual(expect.any(Order));
});

it('should return BatchStatusUpdateWebhookRequest if event is "batch_status_update"', async function () {
// arrange
const body = JSON.stringify({
data: {
id: "batch_1",
companyId: "2bd4c679-3d59-4a6f-a815-a60424746f8d",
name: "Test batch",
finish: "GLOSSY",
templateId: "tmpl_AyDg3PxvP5ydyGq3kSFfj",
status: "batch_created",
createdAt: "2024-06-03T13:14:46.501Z",
updatedAt: "2024-06-03T13:14:46.501Z",
sendDate: null,
isBillable: true,
estimatedPrice: 0,
orders: {
processing: 0,
success: 0,
failed: 0,
cancelled: 0,
},
},
event: "batch_status_update",
created_at: "2024-06-03T13:14:46.501Z",
});

const headers = {
"x-printone-hmac-sha256": "blmkCA9eG2fajvgpHx/RBirRO8rA4wRGf6gr1/v+V0g=",
};

// act
const webhook = await client.validateWebhook(body, headers, "secret");

// assert
expect(webhook).toBeDefined();
expect(webhook).toEqual(expect.any(BatchStatusUpdateWebhookRequest));
expect(webhook.event).toEqual(WebhookEvent.batch_status_update);
expect(webhook.createdAt).toEqual(expect.any(Date));
expect(webhook.data).toEqual(expect.any(Batch));
});

it("should return TemplatePreviewRenderedWebhookRequest if event is template_preview_rendered", async function () {
// arrange
const body = JSON.stringify({
Expand Down
Loading