2ec9e903ce
* feat: implement webhook tasker with async/sync fallback This PR implements a webhook tasker with async/sync fallback architecture to fix failing E2E tests. The solution follows the existing proration tasker pattern and uses Dependency Injection with @evyweb/ioctopus. Key changes: - Create IWebhookTasker interface for webhook delivery - Implement WebhookSyncTasker for immediate execution (E2E tests) - Implement WebhookAsyncTasker for queued execution (production) - Create main WebhookTasker class extending Tasker<IWebhookTasker> - Add DI modules and tokens for all tasker components - Update WebhookTaskerProducerService to use new WebhookTasker - Add unit tests for sync and async taskers The ENABLE_ASYNC_TASKER flag automatically selects the appropriate mode: - Production: Uses WebhookAsyncTasker to queue tasks - E2E Tests: Uses WebhookSyncTasker for immediate execution This ensures webhooks are delivered immediately in E2E tests without requiring the cron job that processes queued tasks. Co-Authored-By: ali@cal.com <alishahbaz7@gmail.com> * fix: update WebhookTaskerProducerService tests for new interface Update tests to use the new deps-based constructor and mockWebhookTasker.deliverWebhook instead of mockTasker.create Co-Authored-By: ali@cal.com <alishahbaz7@gmail.com> * fix: use moduleLoader pattern for WebhookProducerService in container Co-Authored-By: ali@cal.com <alishahbaz7@gmail.com> * refactor: replace InternalTasker with Trigger.dev for webhook delivery This commit refactors the WebhookTasker to use Trigger.dev instead of InternalTasker, following the pattern established in BookingEmailAndSmsTasker and PlatformOrganizationBillingTasker (PR #26803). Changes: - Replace WebhookAsyncTasker with WebhookTriggerTasker that uses trigger.dev - Create trigger.dev task files (deliver-webhook.ts, config.ts, schema.ts) - Update DI modules to use WebhookTriggerTasker - Remove old InternalTasker-based implementation - Update unit tests for new implementation The WebhookSyncTasker continues to execute webhooks immediately for E2E tests where ENABLE_ASYNC_TASKER is automatically false. Co-Authored-By: ali@cal.com <alishahbaz7@gmail.com> * fix: update comments to reflect Trigger.dev usage instead of InternalTasker Co-Authored-By: unknown <> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
100 lines
3.1 KiB
TypeScript
100 lines
3.1 KiB
TypeScript
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
|
|
import type { WebhookTaskConsumer } from "../service/WebhookTaskConsumer";
|
|
import type { WebhookTaskPayload } from "../types/webhookTask";
|
|
import { WebhookSyncTasker } from "./WebhookSyncTasker";
|
|
import { WebhookTriggerTasker } from "./WebhookTriggerTasker";
|
|
|
|
vi.mock("nanoid", () => ({
|
|
nanoid: vi.fn().mockReturnValue("test123456"),
|
|
}));
|
|
|
|
vi.mock("./trigger/deliver-webhook", () => ({
|
|
deliverWebhook: {
|
|
trigger: vi.fn().mockResolvedValue({ id: "trigger-task-id-123" }),
|
|
},
|
|
}));
|
|
|
|
const createMockWebhookTaskPayload = (): WebhookTaskPayload => ({
|
|
operationId: "test-operation-id",
|
|
triggerEvent: "BOOKING_CREATED",
|
|
bookingUid: "test-booking-uid",
|
|
eventTypeId: 1,
|
|
teamId: null,
|
|
userId: 1,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
|
|
describe("WebhookSyncTasker", () => {
|
|
let mockWebhookTaskConsumer: WebhookTaskConsumer;
|
|
let syncTasker: WebhookSyncTasker;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockWebhookTaskConsumer = {
|
|
processWebhookTask: vi.fn().mockResolvedValue(undefined),
|
|
} as unknown as WebhookTaskConsumer;
|
|
|
|
syncTasker = new WebhookSyncTasker({
|
|
webhookTaskConsumer: mockWebhookTaskConsumer,
|
|
});
|
|
});
|
|
|
|
it("should execute webhook delivery immediately via consumer", async () => {
|
|
const payload = createMockWebhookTaskPayload();
|
|
|
|
const result = await syncTasker.deliverWebhook(payload);
|
|
|
|
expect(mockWebhookTaskConsumer.processWebhookTask).toHaveBeenCalledTimes(1);
|
|
expect(mockWebhookTaskConsumer.processWebhookTask).toHaveBeenCalledWith(
|
|
payload,
|
|
expect.stringMatching(/^sync_/)
|
|
);
|
|
expect(result.taskId).toMatch(/^sync_/);
|
|
});
|
|
|
|
it("should generate unique task IDs for each delivery", async () => {
|
|
const payload = createMockWebhookTaskPayload();
|
|
|
|
const result1 = await syncTasker.deliverWebhook(payload);
|
|
const result2 = await syncTasker.deliverWebhook(payload);
|
|
|
|
expect(result1.taskId).toBe("sync_test123456");
|
|
expect(result2.taskId).toBe("sync_test123456");
|
|
expect(mockWebhookTaskConsumer.processWebhookTask).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("should propagate errors from consumer", async () => {
|
|
const payload = createMockWebhookTaskPayload();
|
|
const error = new Error("Consumer processing failed");
|
|
vi.mocked(mockWebhookTaskConsumer.processWebhookTask).mockRejectedValueOnce(error);
|
|
|
|
await expect(syncTasker.deliverWebhook(payload)).rejects.toThrow("Consumer processing failed");
|
|
});
|
|
});
|
|
|
|
describe("WebhookTriggerTasker", () => {
|
|
let triggerTasker: WebhookTriggerTasker;
|
|
let mockLogger: { info: ReturnType<typeof vi.fn>; error: ReturnType<typeof vi.fn> };
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockLogger = {
|
|
info: vi.fn(),
|
|
error: vi.fn(),
|
|
};
|
|
|
|
triggerTasker = new WebhookTriggerTasker({
|
|
logger: mockLogger as never,
|
|
});
|
|
});
|
|
|
|
it("should trigger webhook delivery via trigger.dev", async () => {
|
|
const payload = createMockWebhookTaskPayload();
|
|
|
|
const result = await triggerTasker.deliverWebhook(payload);
|
|
|
|
expect(result.taskId).toBe("trigger-task-id-123");
|
|
});
|
|
});
|