* feat(api): add webhook CRUD API router and tests Add tRPC router for managing workspace webhooks: - list, create, update, delete endpoints (admin role required) - test endpoint to send a synthetic payload to a webhook URL - URL validation, event subscription filtering - Unit tests for all router procedures - Integration tests with PGlite test database - Add vitest config and test infrastructure for API package Depends on #391 (DB schema & repository). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(api): use assertPermission instead of assertUserInWorkspace Replace assertUserInWorkspace with assertPermission("workspace:manage") per project conventions. The permissions system is the preferred authorization approach for new code. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(api): use @kan/db alias instead of relative imports in tests Replace relative path imports (../../db/src/...) with the @kan/db alias configured in vitest.config.ts for consistency and robustness. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(api): use webhookUrlSchema in router input validation Cherry-pick router-related changes from b2cc9ac: - Use extracted webhookUrlSchema zod validator in create/update input schemas for consistent SSRF checks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(api): replace dynamic import with static import for webhook utility Add packages/api/src/utils/webhook.ts with sendWebhookToUrl, createCardWebhookPayload, and webhookUrlSchema. Replace the dynamic import() in the test endpoint with a static import at the top of the file for better tree-shaking, type-checking, and readability. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(api): align sendWebhooksForWorkspace tests with merged PR #392 The merged delivery utility uses client-side event filtering (getActiveByWorkspaceId takes 2 args, not 3). Update test assertions to match the actual implementation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Henry <30578846+hjball@users.noreply.github.com>
76 lines
2.1 KiB
TypeScript
76 lines
2.1 KiB
TypeScript
import { PGlite } from "@electric-sql/pglite";
|
|
import { uuid_ossp } from "@electric-sql/pglite/contrib/uuid_ossp";
|
|
import { pg_trgm } from "@electric-sql/pglite/contrib/pg_trgm";
|
|
import { drizzle } from "drizzle-orm/pglite";
|
|
import { migrate } from "drizzle-orm/pglite/migrator";
|
|
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
import type { Pool } from "pg";
|
|
|
|
import * as schema from "@kan/db/schema";
|
|
|
|
export type TestDbClient = NodePgDatabase<typeof schema> & {
|
|
$client: Pool;
|
|
};
|
|
|
|
/**
|
|
* Creates a fresh in-memory PGlite database for testing.
|
|
* Each call returns an isolated database instance with migrations applied.
|
|
*/
|
|
export async function createTestDb(): Promise<TestDbClient> {
|
|
const client = new PGlite({
|
|
extensions: { uuid_ossp, pg_trgm },
|
|
});
|
|
|
|
const db = drizzle(client, { schema });
|
|
|
|
// Run migrations
|
|
await migrate(db, { migrationsFolder: "../../packages/db/migrations" });
|
|
|
|
return db as unknown as TestDbClient;
|
|
}
|
|
|
|
/**
|
|
* Seeds a test database with a workspace and user for testing.
|
|
* Returns the created entities for use in tests.
|
|
*/
|
|
export async function seedTestData(db: TestDbClient) {
|
|
// Create a test user
|
|
const [user] = await db
|
|
.insert(schema.users)
|
|
.values({
|
|
id: crypto.randomUUID(),
|
|
name: "Test User",
|
|
email: "test@example.com",
|
|
emailVerified: true,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
})
|
|
.returning();
|
|
|
|
// Create a test workspace (publicId must be exactly 12 chars)
|
|
const [workspace] = await db
|
|
.insert(schema.workspaces)
|
|
.values({
|
|
publicId: "wstest123456",
|
|
name: "Test Workspace",
|
|
slug: "test-workspace",
|
|
ownerId: user!.id,
|
|
createdAt: new Date(),
|
|
})
|
|
.returning();
|
|
|
|
// Add user as admin member of workspace
|
|
await db.insert(schema.workspaceMembers).values({
|
|
publicId: "wm1234567890",
|
|
email: user!.email,
|
|
workspaceId: workspace!.id,
|
|
userId: user!.id,
|
|
createdBy: user!.id,
|
|
role: "admin",
|
|
status: "active",
|
|
createdAt: new Date(),
|
|
});
|
|
|
|
return { user: user!, workspace: workspace! };
|
|
}
|