Skip to content

Commit d00bab3

Browse files
committed
Update fixtures example to match a simple workflow example
1 parent 85886f8 commit d00bab3

File tree

6 files changed

+204
-734
lines changed

6 files changed

+204
-734
lines changed
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# 🔁 workflows
22

3-
This Worker includes two Workflows: a basic workflow that runs a single step and completes `TestWorkflow`, and a long-running worflow that includes more steps `TestLongWorkflow`.
3+
This Worker includes a ModeratorWorkflow that serves as a template for an automated content moderation process.
44
The testing suite uses workflow mocking to validate the logic of each step.
55

6-
| Test | Overview |
7-
| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
8-
| [integration.test.ts](test/integration.test.ts) | Tests on the Worker's endpoints, ensuring that workflows are created correctly and statuses can be fetched. |
9-
| [unit.test.ts](test/unit.test.ts) | Tests on the internal logic of each workflow. It uses mocking to test steps in isolation. |
6+
| Test | Overview |
7+
| ----------------------------------------------- | ----------------------------------------------------------------------------------------- |
8+
| [integration.test.ts](test/integration.test.ts) | Tests on the Worker's endpoints, ensuring that workflows are created and run correctly. |
9+
| [unit.test.ts](test/unit.test.ts) | Tests on the internal logic of each workflow. It uses mocking to test steps in isolation. |
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
declare namespace Cloudflare {
22
interface Env {
3-
TEST_WORKFLOW: Workflow;
4-
TEST_LONG_WORKFLOW: Workflow;
3+
MODERATOR: Workflow;
54
}
65
}
76
interface Env extends Cloudflare.Env {}

fixtures/vitest-pool-workers-examples/workflows/src/index.ts

Lines changed: 46 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,51 +4,54 @@ import {
44
WorkflowStep,
55
} from "cloudflare:workers";
66

7-
export class TestWorkflow extends WorkflowEntrypoint<Env> {
7+
export class ModeratorWorkflow extends WorkflowEntrypoint<Env> {
88
async run(_event: Readonly<WorkflowEvent<unknown>>, step: WorkflowStep) {
9-
console.log("Starting running...");
10-
11-
await step.do("step one", async () => {
12-
// some logic
13-
return "result of step one";
14-
});
15-
16-
return "test-workflow";
17-
}
18-
}
19-
20-
export class TestLongWorkflow extends WorkflowEntrypoint<Env, Params> {
21-
async run(event: Readonly<WorkflowEvent<unknown>>, step: WorkflowStep) {
229
await step.sleep("sleep for a while", "10 seconds");
2310

24-
await step.do(
25-
"my step",
26-
{
27-
retries: {
28-
limit: 5,
29-
delay: 50,
30-
backoff: "constant",
31-
},
32-
timeout: "1 second",
33-
},
34-
async () => {
35-
// some logic
36-
return "result of my step";
37-
}
38-
);
11+
// Get an initial analysis from an AI model
12+
const aiResult = await step.do("AI content scan", async () => {
13+
// Call to an workers-ai to scan the text content and return a violation score
3914

40-
await step.sleep("sleep for a day", "8 hours");
15+
// Simulated score:
16+
const violationScore = Math.floor(Math.random() * 100);
17+
18+
return { violationScore: violationScore };
19+
});
4120

42-
if (event.payload === "run event") {
43-
await step.waitForEvent("my event", {
44-
type: "event",
45-
timeout: "10 seconds",
21+
// Triage based on the AI score
22+
if (aiResult.violationScore < 10) {
23+
await step.do("auto approve content", async () => {
24+
// API call to set app content status to "approved"
25+
return { status: "auto_approved" };
4626
});
27+
return { status: "auto_approved" };
28+
}
29+
if (aiResult.violationScore > 90) {
30+
await step.do("auto reject content", async () => {
31+
// API call to set app content status to "rejected"
32+
return { status: "auto_rejected" };
33+
});
34+
return { status: "auto_rejected" };
4735
}
4836

49-
await step.sleep("sleep for a while", "5 seconds");
37+
// If the score is ambiguous, require human review
38+
type EventPayload = {
39+
moderatorAction: string;
40+
};
41+
const eventPayload = await step.waitForEvent<EventPayload>("human review", {
42+
type: "moderation-decision",
43+
timeout: "1 day",
44+
});
5045

51-
return "test-workflow";
46+
if (eventPayload) {
47+
// The moderator responded in time.
48+
const decision = eventPayload.payload.moderatorAction; // e.g., "approve" or "reject"
49+
await step.do("apply moderator decision", async () => {
50+
// API call to update content status based on the decision
51+
return { status: "moderated", decision: decision };
52+
});
53+
return { status: "moderated", decision: decision };
54+
}
5255
}
5356
}
5457

@@ -57,31 +60,30 @@ export default {
5760
const url = new URL(request.url);
5861
const maybeId = url.searchParams.get("id");
5962
if (maybeId !== null) {
60-
const instance = await env.TEST_WORKFLOW.get(maybeId);
63+
const instance = await env.MODERATOR.get(maybeId);
6164

6265
return Response.json(await instance.status());
6366
}
6467

65-
if (url.pathname === "/long-workflow") {
66-
const workflow = await env.TEST_LONG_WORKFLOW.create();
68+
if (url.pathname === "/moderate") {
69+
const workflow = await env.MODERATOR.create();
6770
return Response.json({
6871
id: workflow.id,
6972
details: await workflow.status(),
7073
});
7174
}
7275

73-
if (url.pathname === "/long-workflow-batch") {
74-
const workflows = await env.TEST_LONG_WORKFLOW.createBatch([
76+
if (url.pathname === "/moderate-batch") {
77+
const workflows = await env.MODERATOR.createBatch([
7578
{},
7679
{ id: "321" },
7780
{},
7881
]);
82+
7983
const ids = workflows.map((workflow) => workflow.id);
8084
return Response.json({ ids: ids });
8185
}
8286

83-
const workflow = await env.TEST_WORKFLOW.create();
84-
85-
return Response.json({ id: workflow.id });
87+
return new Response("Not found", { status: 404 });
8688
},
8789
};
Lines changed: 38 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,60 @@
1-
import { beforeEach } from "node:test";
21
import { env, introspectWorkflow, SELF } from "cloudflare:test";
3-
import { describe, expect, it, vi } from "vitest";
2+
import { expect, it } from "vitest";
43

54
const STATUS_COMPLETE = "complete";
5+
const STEP_NAME = "AI content scan";
6+
const mockResult = { violationScore: 0 };
67

7-
describe("Test Workflow", () => {
8-
it("should be able to trigger a workflow", async () => {
9-
// With `using` to ensure Workflow instances cleanup:
10-
await using introspector = await introspectWorkflow(env.TEST_WORKFLOW);
11-
const res = await SELF.fetch("https://mock-worker.local");
8+
it("workflow should be able to reach the end and be successful", async () => {
9+
// This example shows how to implicitly cleanUp Workflow instances
1210

13-
expect(res.status).toBe(200);
11+
// CONFIG with `using` to ensure Workflow instances cleanup:
12+
await using introspector = await introspectWorkflow(env.MODERATOR);
13+
introspector.modifyAll(async (m) => {
14+
await m.disableSleeps();
15+
await m.mockStepResult({ name: STEP_NAME }, mockResult);
1416
});
1517

16-
it("workflow should reach the end and be successful", async () => {
17-
// With `using` to ensure Workflow instances cleanup:
18-
await using introspector = await introspectWorkflow(env.TEST_WORKFLOW);
19-
const res = await SELF.fetch("https://mock-worker.local");
18+
await SELF.fetch(`https://mock-worker.local/moderate`);
2019

21-
const json = await res.json<{ id: string }>();
20+
const instances = introspector.get();
21+
expect(instances.length).toBe(1);
2222

23-
await vi.waitUntil(async () => {
24-
const res = await SELF.fetch(`https://mock-worker.local?id=${json.id}`);
23+
// ASSERTIONS:
24+
const instance = instances[0];
25+
expect(await instance.waitForStepResult({ name: STEP_NAME })).toEqual(
26+
mockResult
27+
);
28+
await instance.waitForStatus(STATUS_COMPLETE);
2529

26-
const statusJson = await res.json<{ status: string }>();
27-
expect(statusJson.status).toBe(STATUS_COMPLETE);
28-
return true;
29-
}, 1000);
30-
});
31-
32-
it("workflow should reach the end and be successful with introspector", async () => {
33-
// CONFIG with `using` to ensure Workflow instances cleanup:
34-
await using introspector = await introspectWorkflow(env.TEST_WORKFLOW);
35-
36-
await SELF.fetch("https://mock-worker.local");
37-
38-
const instances = introspector.get();
39-
expect(instances.length).toBe(1);
40-
41-
// ASSERTIONS:
42-
const instance = instances[0];
43-
await instance.waitForStatus(STATUS_COMPLETE);
44-
45-
// CLEANUP: ensured by `using`
46-
});
30+
// CLEANUP: ensured by `using`
4731
});
4832

49-
describe("Test long Workflow", () => {
50-
const STEP_NAME = "my step";
51-
const mockResult = "mocked result";
33+
it("workflow batch should be able to reach the end and be successful", async () => {
34+
// This example shows how to explicitly cleanUp Workflow instances
5235

53-
it("workflow should be able to introspect and reach the end and be successful", async () => {
54-
// CONFIG with `using` to ensure Workflow instances cleanup:
55-
await using introspector = await introspectWorkflow(env.TEST_LONG_WORKFLOW);
56-
introspector.modifyAll(async (m) => {
57-
await m.disableSleeps();
58-
await m.mockStepResult({ name: STEP_NAME }, mockResult);
59-
});
36+
// CONFIG:
37+
let introspector = await introspectWorkflow(env.MODERATOR);
38+
introspector.modifyAll(async (m) => {
39+
await m.disableSleeps();
40+
await m.mockStepResult({ name: STEP_NAME }, mockResult);
41+
});
6042

61-
await SELF.fetch("https://mock-worker.local/long-workflow");
43+
await SELF.fetch(`https://mock-worker.local/moderate-batch`);
6244

63-
const instances = introspector.get();
64-
expect(instances.length).toBe(1);
45+
const instances = introspector.get();
46+
expect(instances.length).toBe(3);
6547

66-
// ASSERTIONS:
67-
const instance = instances[0];
48+
// ASSERTIONS:
49+
for (const instance of instances) {
6850
expect(await instance.waitForStepResult({ name: STEP_NAME })).toEqual(
6951
mockResult
7052
);
7153
await instance.waitForStatus(STATUS_COMPLETE);
54+
}
7255

73-
// CLEANUP: ensured by `using`
74-
});
75-
76-
it("workflow batch should be able to introspect and reach the end and be successful (explicit cleanup)", async () => {
77-
// CONFIG:
78-
let introspector = await introspectWorkflow(env.TEST_LONG_WORKFLOW);
79-
introspector.modifyAll(async (m) => {
80-
await m.disableSleeps();
81-
await m.mockStepResult({ name: STEP_NAME }, mockResult);
82-
});
83-
84-
await SELF.fetch("https://mock-worker.local/long-workflow-batch");
85-
86-
const instances = introspector.get();
87-
expect(instances.length).toBe(3);
88-
89-
// ASSERTIONS:
90-
for (const instance of instances) {
91-
expect(await instance.waitForStepResult({ name: STEP_NAME })).toEqual(
92-
mockResult
93-
);
94-
await instance.waitForStatus(STATUS_COMPLETE);
95-
}
96-
97-
// CLEANUP:
98-
// Workflow introspector should be cleaned at the end of/after each test, if no `using` keyword is used for the introspector
99-
// Cleans up all intercepted instances
100-
await introspector.cleanUp();
101-
});
56+
// CLEANUP:
57+
// Workflow introspector should be cleaned at the end of/after each test, if no `using` keyword is used for the introspector
58+
// Cleans up all intercepted instances
59+
await introspector.cleanUp();
10260
});

0 commit comments

Comments
 (0)