Skip to content

Webhook Fixture

Webhook Fixture is a fixture for testing Flowcore Webhooks in a Flowcore Unified Api Service.

Install the Webhook Fixture

Terminal window
flowcore component add fixture/webhook-fixture
Or install the Webhook Fixture manually

Create a /fixtures/webhook.ts file in your project’s test folder, and add the following code:

import type { FlowcoreEventSchema } from "@flowcore/sdk-transformer-core"
import type { Static } from "@sinclair/typebox"
import Elysia from "elysia"
import { v4 as uuid } from "uuid"
export type WebhookTestFixtureServer = Elysia<"">
export type WebhookTestFixtureOptions = {
tenant: string
dataCore: string
port: number
secret: string
transformerPort: number
secured?: boolean
}
export type WebhookTestFixtureEndpoint = {
flowType: string
eventType: string
redirectTo: string
}
export class WebhookTestFixture {
private readonly app = new Elysia()
private readonly redirects: WebhookTestFixtureEndpoint[] = []
constructor(private readonly options: WebhookTestFixtureOptions) {
this.options = { ...{ secured: false }, ...options }
}
public addEndpoint(flowType: string, eventType: string, redirectTo?: string): this {
this.redirects.push({
flowType,
eventType,
redirectTo: redirectTo ?? `http://localhost:${this.options.transformerPort}/transformers/${flowType}`,
})
return this
}
public async stop() {
await this.app.stop()
}
public async start() {
this.app.get("/health", () => "ok")
for (const redirect of this.redirects) {
this.app.post(
`/event/${this.options.tenant}/${this.options.dataCore}/${redirect.flowType}/${redirect.eventType}`,
async (req) => {
let metadata: Record<string, string> | undefined
if (req.headers["x-flowcore-metadata-json"]) {
metadata = JSON.parse(Buffer.from(req.headers["x-flowcore-metadata-json"], "base64").toString("utf-8"))
}
const event: Static<typeof FlowcoreEventSchema> = {
eventId: uuid(),
aggregator: redirect.flowType,
eventType: redirect.eventType,
validTime: new Date().toISOString(),
payload: req.body,
metadata: metadata ?? {},
}
if (this.options.secured) {
if (!metadata?.["audit/user-id"]) {
console.error(
`Missing audit/user-id in metadata when secured webhook is expected when sending to: /event/${this.options.tenant}/${this.options.dataCore}/${redirect.flowType}/${redirect.eventType}`,
)
throw new Error("Missing audit/user-id in metadata when secured webhook is expected")
}
if (metadata["audit/user-id"] && !metadata?.["audit/on-behalf-of"]) {
console.error(
`Missing audit/on-behalf-of in metadata when secured webhook is expected when sending, as system, to: /event/${this.options.tenant}/${this.options.dataCore}/${redirect.flowType}/${redirect.eventType}`,
)
throw new Error("Missing audit/on-behalf-of in metadata when secured webhook is expected")
}
}
try {
const response = await fetch(`${redirect.redirectTo}`, {
method: "POST",
body: JSON.stringify(event),
headers: {
"Content-Type": "application/json",
"x-secret": this.options.secret,
},
})
if (![200, 201].includes(response.status)) {
throw new Error(
`Received non-success status code: ${response.status} (${response.statusText}) with body ${await response.text()}`,
)
}
} catch (error) {
console.log(`Error ${redirect.flowType}/${redirect.eventType}: ${error}`)
}
return {
eventId: event.eventId,
}
},
)
}
return this.app.listen(this.options.port)
}
}

Usage

Run the follwing commands in the setup phase of your test suite:

import { WebhookTestFixture } from "@fixtures/fixture-webhook"
export const webhookFixture = new WebhookTestFixture({
tenant: env.FLOWCORE_TENANT,
dataCore: env.FLOWCORE_DATA_CORE,
port: WEBHOOK_PORT,
secret: env.TRANSFORMER_SECRET,
transformerPort: env.SERVICE_PORT,
secured: true,
})
.addEndpoint("my-flow", "my-event")

then in your test suite, you should start and stop the webhook fixture in the before and after hooks:

beforeEach(async () => {
await webhookFixture.start()
})
afterEach(async () => {
await webhookFixture.stop()
})