Add workflow queue test

This commit is contained in:
Konstantin Wohlwend 2025-09-03 14:07:27 -07:00
parent 7387f029c0
commit 41b7d7095b
2 changed files with 67 additions and 8 deletions

View File

@ -83,7 +83,7 @@ export async function compileWorkflowSource(source: string): Promise<Result<stri
if (!callbackFunc) {
throw new Error(\`Callback \${callbackId} not found. Was it maybe deleted from the workflow?\`);
}
return callbackFunc(JSON.parse(data.dataJson));
return callbackFunc(data);
});
let scheduledCallback = undefined;
globalThis.scheduleCallback = ({ callbackId, data, scheduleAt }) => {

View File

@ -27,23 +27,25 @@ async function configureEmailAndWorkflow(workflowId: string, tsSource: string, e
});
}
const waitRetries = 15;
async function waitForMailboxSubject(mailbox: Mailbox, subject: string) {
for (let i = 0; i < 15; i++) {
for (let i = 0; i < waitRetries; i++) {
const messages = await mailbox.fetchMessages();
const message = messages.find((m) => m.subject === subject);
if (message) return;
await wait(1_000);
}
throw new Error(`Message with subject ${subject} not found after 15 tries`);
throw new Error(`Message with subject ${subject} not found after ${waitRetries} tries`);
}
async function waitForServerMetadataNotNull(userId: string, key: string) {
for (let i = 0; i < 15; i++) {
for (let i = 0; i < waitRetries; i++) {
const user = await niceBackendFetch(`/api/v1/users/${userId}`, { accessType: "server" });
if (user.body.server_metadata?.[key]) return;
await wait(1_000);
}
throw new Error(`Server metadata for user ${userId} with key ${key} not found after 15 tries`);
throw new Error(`Server metadata for user ${userId} with key ${key} not found after ${waitRetries} tries`);
}
test("onSignUp workflow sends email for client sign-up", async ({ expect }) => {
@ -54,8 +56,10 @@ test("onSignUp workflow sends email for client sign-up", async ({ expect }) => {
await configureEmailAndWorkflow("wf-email", `
onSignUp(async (user) => {
await stackApp.sendEmail({ userIds: [user.id], subject: ${JSON.stringify(subject)}, html: "<p>hi</p>" });
// schedule a callback as an example (we don't actually test whether it executed successfully)
return scheduleCallback({
scheduleAt: new Date(Date.now() + 120_000),
scheduleAt: new Date(Date.now() + 7_000),
data: { "example": "data" },
callbackId: "my-callback",
});
@ -100,6 +104,61 @@ test("onSignUp workflow sends email for client sign-up", async ({ expect }) => {
timeout: 40_000,
});
test("onSignUp workflow can schedule callbacks", async ({ expect }) => {
await Project.createAndSwitch();
const mailbox = await bumpEmailAddress({ unindexed: true });
const subject = `WF client signup ${crypto.randomUUID()}`;
await configureEmailAndWorkflow("wf-email", `
onSignUp(async (user) => {
return scheduleCallback({
scheduleAt: new Date(Date.now() + 7_000),
data: { "userId": user.id },
callbackId: "my-callback",
});
});
registerCallback("my-callback", async (data) => {
await stackApp.sendEmail({ userIds: [data.userId], subject: ${JSON.stringify(subject)}, html: "<p>hi</p>" });
});
`);
await Auth.Password.signUpWithEmail({ password: "password" });
// since we wait for the callback, add some extra time
await wait(10_000);
await waitForMailboxSubject(mailbox, subject);
expect(await mailbox.fetchMessages()).toMatchInlineSnapshot(`
[
MailboxMessage {
"attachments": [],
"body": {
"html": "http://localhost:12345/some-callback-url?code=%3Cstripped+query+param%3E",
"text": "http://localhost:12345/some-callback-url?code=%3Cstripped+query+param%3E",
},
"from": "Test Project <test@example.com>",
"subject": "Verify your email at New Project",
"to": ["<unindexed-mailbox--<stripped UUID>@stack-generated.example.com>"],
<some fields may have been hidden>,
},
MailboxMessage {
"attachments": [],
"body": {
"html": "<!DOCTYPE html PUBLIC \\"-//W3C//DTD XHTML 1.0 Transitional//EN\\" \\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\\"><html dir=\\"ltr\\" lang=\\"en\\"><head><meta content=\\"text/html; charset=UTF-8\\" http-equiv=\\"Content-Type\\"/><meta name=\\"x-apple-disable-message-reformatting\\"/></head><body style=\\"background-color:rgb(250,251,251);font-family:ui-sans-serif, system-ui, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;, &quot;Noto Color Emoji&quot;;font-size:1rem;line-height:1.5rem\\"><!--$--><table align=\\"center\\" width=\\"100%\\" border=\\"0\\" cellPadding=\\"0\\" cellSpacing=\\"0\\" role=\\"presentation\\" style=\\"background-color:rgb(255,255,255);padding:45px;border-radius:0.5rem;max-width:37.5em\\"><tbody><tr style=\\"width:100%\\"><td><div><p>hi</p></div></td></tr></tbody></table><!--7--><!--/$--></body></html>",
"text": "hi",
},
"from": "Test Project <test@example.com>",
"subject": "WF client signup <stripped UUID>",
"to": ["<unindexed-mailbox--<stripped UUID>@stack-generated.example.com>"],
<some fields may have been hidden>,
},
]
`);
}, {
timeout: 40_000,
});
test("onSignUp workflow sends email for server-created user", async ({ expect }) => {
await Project.createAndSwitch();
await InternalApiKey.createAndSetProjectKeys();
@ -156,7 +215,7 @@ test("disabled workflows do not trigger", async ({ expect }) => {
await Auth.Password.signUpWithEmail({ password: "password" });
await wait(18_000);
await wait(25_000);
expect(await mailbox.fetchMessages()).toMatchInlineSnapshot(`
[
@ -244,7 +303,7 @@ test("anonymous sign-up does not trigger; upgrade triggers workflow", async ({ e
const { userId: anonUserId } = await Auth.Anonymous.signUp();
// ensure marker not present yet
await wait(18_000);
await wait(25_000);
const me1 = await niceBackendFetch("/api/v1/users/me", { accessType: "client" });
expect(me1.body.server_metadata?.[markerKey]).toBeUndefined();