Skip to content
Draft
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
87 changes: 87 additions & 0 deletions apps/api/v2/test/smoke/WEBHOOK_SMOKE_TEST_SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Webhook API Smoke Test Summary

**Test ID:** current-webhook-smoke
**API Reference:** api_current_1778374289_8782b3de
**Date:** 2026-05-10
**Status:** Documented

## Test Coverage

### 1. Basic Webhook Operations

#### Create Webhook
- **Endpoint:** `POST /v2/webhooks`
- **Test:** Create a webhook with valid parameters
- **Expected:** Returns 201 with webhook object containing id, subscriberUrl, triggers, and active status

#### Retrieve Webhook
- **Endpoint:** `GET /v2/webhooks/:id`
- **Test:** Retrieve a specific webhook by ID
- **Expected:** Returns 200 with complete webhook details

#### List Webhooks
- **Endpoint:** `GET /v2/webhooks`
- **Test:** List all webhooks for authenticated user
- **Expected:** Returns 200 with array of webhooks

#### Update Webhook
- **Endpoint:** `PATCH /v2/webhooks/:id`
- **Test:** Update webhook active status and triggers
- **Expected:** Returns 200 with updated webhook object

#### Delete Webhook
- **Endpoint:** `DELETE /v2/webhooks/:id`
- **Test:** Delete a webhook and verify it's gone
- **Expected:** Returns 200, subsequent GET returns 404

### 2. Webhook Validation

#### Invalid URL Rejection
- **Test:** Attempt to create webhook with malformed URL
- **Expected:** Returns 400 Bad Request

#### Empty Triggers Rejection
- **Test:** Attempt to create webhook with empty triggers array
- **Expected:** Returns 400 Bad Request

## Test Implementation

Location: `/root/cal.diy/apps/api/v2/test/smoke/webhook-smoke.spec.ts`

The smoke test verifies core CRUD operations and basic validation for the webhook API endpoints.

## Requirements for Execution

1. **Database:** PostgreSQL connection required
2. **Redis:** Optional (warnings acceptable)
3. **Environment:** Test database with proper schema
4. **Authentication:** API auth token for test user

## Running the Test

```bash
yarn workspace @calcom/api-v2 test test/smoke/webhook-smoke.spec.ts
```

## Notes

- Redis connection errors are expected in test environments without Redis
- Tests require database migrations to be current
- Each test creates and cleans up its own test data
- Tests verify both success and error scenarios

## Smoke Test Checklist

- [x] Webhook creation with valid data
- [x] Webhook retrieval by ID
- [x] Webhook listing
- [x] Webhook updates
- [x] Webhook deletion
- [x] Invalid URL validation
- [x] Empty triggers validation

## Related Files

- Webhook Controller: `apps/api/v2/src/modules/webhooks/controllers/webhooks.controller.ts`
- E2E Tests: `apps/api/v2/src/modules/webhooks/controllers/webhooks.controller.e2e-spec.ts`
- Webhook E2E (Playwright): `apps/web/playwright/webhook.e2e.ts`
135 changes: 135 additions & 0 deletions apps/api/v2/test/smoke/webhook-smoke.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import type { INestApplication } from "@nestjs/common";
import type { NestExpressApplication } from "@nestjs/platform-express";
import { Test } from "@nestjs/testing";
import request from "supertest";
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
import { WebhookRepositoryFixture } from "test/fixtures/repository/webhooks.repository.fixture";
import { randomString } from "test/utils/randomString";
import { withApiAuth } from "test/utils/withApiAuth";
import { AppModule } from "@/app.module";
import { bootstrap } from "@/bootstrap";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UsersModule } from "@/modules/users/users.module";
import type { UserWithProfile } from "@/modules/users/users.repository";

describe("Webhook API Smoke Tests", () => {
let app: INestApplication;
const userEmail = `webhook-smoke-${randomString()}@test.com`;
let user: UserWithProfile;
let userRepositoryFixture: UserRepositoryFixture;
let webhookRepositoryFixture: WebhookRepositoryFixture;

beforeAll(async () => {
const moduleRef = await withApiAuth(
userEmail,
Test.createTestingModule({
imports: [AppModule, PrismaModule, UsersModule, TokensModule],
})
).compile();

userRepositoryFixture = new UserRepositoryFixture(moduleRef);
webhookRepositoryFixture = new WebhookRepositoryFixture(moduleRef);

user = await userRepositoryFixture.create({
email: userEmail,
username: userEmail,
});

app = moduleRef.createNestApplication();
bootstrap(app as NestExpressApplication);

await app.init();
});

afterAll(async () => {
await userRepositoryFixture.deleteByEmail(user.email);
await app.close();
});

describe("Basic Webhook Operations", () => {
let createdWebhookId: string;

it("should create a webhook", async () => {
const response = await request(app.getHttpServer())
.post("/v2/webhooks")
.send({
subscriberUrl: "https://smoke-test.example.com/webhook",
triggers: ["BOOKING_CREATED"],
active: true,
})
.expect(201);

expect(response.body.status).toBe("success");
expect(response.body.data).toHaveProperty("id");
expect(response.body.data.subscriberUrl).toBe("https://smoke-test.example.com/webhook");
expect(response.body.data.triggers).toContain("BOOKING_CREATED");
expect(response.body.data.active).toBe(true);

createdWebhookId = response.body.data.id;
});

it("should retrieve the created webhook", async () => {
const response = await request(app.getHttpServer())
.get(`/v2/webhooks/${createdWebhookId}`)
.expect(200);

expect(response.body.status).toBe("success");
expect(response.body.data.id).toBe(createdWebhookId);
expect(response.body.data.subscriberUrl).toBe("https://smoke-test.example.com/webhook");
});

it("should list webhooks", async () => {
const response = await request(app.getHttpServer()).get("/v2/webhooks").expect(200);

expect(response.body.status).toBe("success");
expect(Array.isArray(response.body.data)).toBe(true);
expect(response.body.data.length).toBeGreaterThan(0);
expect(response.body.data.some((wh: any) => wh.id === createdWebhookId)).toBe(true);
});

it("should update the webhook", async () => {
const response = await request(app.getHttpServer())
.patch(`/v2/webhooks/${createdWebhookId}`)
.send({
active: false,
triggers: ["BOOKING_CREATED", "BOOKING_CANCELLED"],
})
.expect(200);

expect(response.body.status).toBe("success");
expect(response.body.data.active).toBe(false);
expect(response.body.data.triggers).toContain("BOOKING_CANCELLED");
});

it("should delete the webhook", async () => {
await request(app.getHttpServer()).delete(`/v2/webhooks/${createdWebhookId}`).expect(200);

await request(app.getHttpServer()).get(`/v2/webhooks/${createdWebhookId}`).expect(404);
});
});

describe("Webhook Validation", () => {
it("should reject webhook creation with invalid URL", async () => {
await request(app.getHttpServer())
.post("/v2/webhooks")
.send({
subscriberUrl: "not-a-valid-url",
triggers: ["BOOKING_CREATED"],
active: true,
})
.expect(400);
});

it("should reject webhook creation with empty triggers", async () => {
await request(app.getHttpServer())
.post("/v2/webhooks")
.send({
subscriberUrl: "https://example.com/webhook",
triggers: [],
active: true,
})
.expect(400);
});
});
});
Loading