Fix GitHub action webhook errors (#786)

<!--

Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md

-->

<!-- ELLIPSIS_HIDDEN -->


----

> [!IMPORTANT]
> Fix webhook handling in tests by updating URLs, adding retry logic,
and ensuring correct event handling.
> 
>   - **Behavior**:
> - Update webhook URL to `http://localhost:12345/webhook` in
`backend-helpers.ts` and `webhooks.test.ts`.
> - Add retry logic in `listWebhookAttempts()` in `backend-helpers.ts`
to handle slow responses.
> - Modify `findWebhookAttempt()` in `backend-helpers.ts` to use updated
`listWebhookAttempts()`.
>   - **Tests**:
>     - Update `webhooks.test.ts` to use local webhook URL for testing.
> - Add retry logic in `team-memberships.test.ts` to ensure webhook
events are captured.
> - Ensure `team_permission.created` events are correctly handled in
`team-memberships.test.ts`.
> 
> <sup>This description was created by </sup>[<img alt="Ellipsis"
src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=stack-auth%2Fstack-auth&utm_source=github&utm_medium=referral)<sup>
for cfbff54598. You can
[customize](https://app.ellipsis.dev/stack-auth/settings/summaries) this
summary. It will automatically update as commits are pushed.</sup>


<!-- ELLIPSIS_HIDDEN -->
This commit is contained in:
Zai Shi 2025-07-22 22:58:49 +02:00 committed by GitHub
parent d94e62b620
commit 5f7d23857c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 43 additions and 24 deletions

View File

@ -1305,7 +1305,7 @@ export namespace Webhook {
const createEndpointResponse = await niceFetch(STACK_SVIX_SERVER_URL + `/api/v1/app/${projectId}/endpoint`, {
method: "POST",
body: JSON.stringify({
url: "https://example.com"
url: "http://localhost:12345/webhook"
}),
headers: {
"Authorization": `Bearer ${svixToken}`,
@ -1323,7 +1323,7 @@ export namespace Webhook {
export async function findWebhookAttempt(projectId: string, endpointId: string, svixToken: string, fn: (msg: any) => boolean) {
// retry many times because Svix sucks and is slow
for (let i = 0; i < 20; i++) {
const attempts = await Webhook.listWebhookAttempts(projectId, endpointId, svixToken);
const attempts = await Webhook.listWebhookAttempts(projectId, endpointId, svixToken, 1);
const filtered = attempts.filter(fn);
if (filtered.length === 0) {
await wait(500);
@ -1337,28 +1337,37 @@ export namespace Webhook {
throw new Error(`Webhook attempt not found for project ${projectId}, endpoint ${endpointId}`);
}
export async function listWebhookAttempts(projectId: string, endpointId: string, svixToken: string) {
const response = await niceFetch(STACK_SVIX_SERVER_URL + `/api/v1/app/${projectId}/attempt/endpoint/${endpointId}`, {
method: "GET",
headers: {
"Authorization": `Bearer ${svixToken}`,
"Content-Type": "application/json",
},
});
const messages = await Promise.all(response.body.data.map(async (attempt: any) => {
const messageResponse = await niceFetch(STACK_SVIX_SERVER_URL + `/api/v1/app/${projectId}/msg/${attempt.msgId}?with_content=true`, {
export async function listWebhookAttempts(projectId: string, endpointId: string, svixToken: string, retryCount: number = 20) {
// retry many times because Svix sucks and is slow
for (let i = 0; i < retryCount; i++) {
const response = await niceFetch(STACK_SVIX_SERVER_URL + `/api/v1/app/${projectId}/attempt/endpoint/${endpointId}`, {
method: "GET",
headers: {
"Authorization": `Bearer ${svixToken}`,
"Content-Type": "application/json",
},
method: "GET",
});
return messageResponse.body;
}));
return messages.sort((a, b) => {
return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();
});
const messages = await Promise.all(response.body.data.map(async (attempt: any) => {
const messageResponse = await niceFetch(STACK_SVIX_SERVER_URL + `/api/v1/app/${projectId}/msg/${attempt.msgId}?with_content=true`, {
headers: {
"Authorization": `Bearer ${svixToken}`,
"Content-Type": "application/json",
},
method: "GET",
});
return messageResponse.body;
}));
if (messages.length === 0) {
await wait(500);
continue;
}
return messages.sort((a, b) => {
return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();
});
}
return [];
}
}

View File

@ -8,7 +8,7 @@ it("should be able to create a webhook", async ({ expect }) => {
const response = await niceBackendFetch("/api/v1/integrations/neon/webhooks", {
method: "POST",
body: {
url: "https://example.com/neon",
url: "http://localhost:12345/webhook",
description: "Test webhook",
},
headers: {

View File

@ -735,11 +735,21 @@ it("should trigger multiple permission webhooks when a custom permission is incl
// Wait for webhooks to be triggered
await wait(5000);
// Get webhook events
const attemptResponse = await Webhook.listWebhookAttempts(projectId, endpointId, svixToken);
let teamPermissionCreatedEvents: any[] = [];
// Check for team_permission.created events
const teamPermissionCreatedEvents = attemptResponse.filter(event => event.eventType === "team_permission.created");
for (let i = 0; i < 20; i++) {
// Get webhook events
const attemptResponse = await Webhook.listWebhookAttempts(projectId, endpointId, svixToken, 1);
// Check for team_permission.created events
teamPermissionCreatedEvents = attemptResponse.filter(event => event.eventType === "team_permission.created");
if (teamPermissionCreatedEvents.length === 2) {
break;
}
await wait(500);
}
// There should be two team permission created events (for both default permissions)
expect(teamPermissionCreatedEvents.length).toBe(2);