diff --git a/examples/astro/package.json b/examples/astro/package.json index 30df0c5..204e4b9 100644 --- a/examples/astro/package.json +++ b/examples/astro/package.json @@ -12,7 +12,7 @@ "dependencies": { "@astrojs/check": "^0.9.4", "@astrojs/vercel": "^7.8.2", - "@upstash/workflow": "0.1.2-canary-astro", + "@upstash/workflow": "latest", "astro": "^4.16.7", "typescript": "^5.6.3" }, diff --git a/examples/cloudflare-workers-hono/ci.test.ts b/examples/cloudflare-workers-hono/ci.test.ts new file mode 100644 index 0000000..c89a336 --- /dev/null +++ b/examples/cloudflare-workers-hono/ci.test.ts @@ -0,0 +1,65 @@ + +import { Client } from "@upstash/qstash" +import { Redis } from "@upstash/redis" +import { serve } from "@upstash/workflow/nextjs" +import { describe, test, expect } from "bun:test" + +const qstashClient = new Client({ + baseUrl: "https://workflow-tests.requestcatcher.com/", + token: "mock" +}) + +// @ts-expect-error mocking publishJSON +qstashClient.publishJSON = async () => { + return { messageId: "msgId" } +} + +const { POST: serveHandler } = serve( + async (context) => { + await context.sleep("sleeping", 10) + }, { + qstashClient, + receiver: undefined + } +) + +describe("cloudflare workers tests", () => { + test("should send first invocation", async () => { + const request = new Request("https://workflow-tests.requestcatcher.com/") + const response = await serveHandler(request) + + // it should send a request, but get failed to parse error because + // request catcher returns string + expect(response.status).toBe(200) + const result = await response.json() as { workflowRunId: string } + expect(result.workflowRunId).toBeTruthy() + }) + + if (process.env.DEPLOYMENT_URL) { + test("should run workflow successfully", async () => { + const redis = Redis.fromEnv() + const client = new Client({ token: process.env.QSTASH_TOKEN! }) + + const secret = "secret" + Math.floor(Math.random() * 10000).toString() + await client.publishJSON({ + url: `${process.env.DEPLOYMENT_URL}/ci`, + body: { text: "hello world!" }, + method: "POST", + headers: { + "Content-type": "text/plain", + "secret-header": secret + } + }) + + await new Promise(r => setTimeout(r, 3000)); + + const result = await redis.get(`ci-cf-ran-${secret}`) + + if (result !== secret) { + throw new Error("Cloudflare workflow didn't run") + } + }) + } else { + console.log("skipping workflow run tests because DEPLOYMENT_URL is not set"); + } +}) diff --git a/examples/cloudflare-workers-hono/package.json b/examples/cloudflare-workers-hono/package.json index e0c24dd..f798ba7 100644 --- a/examples/cloudflare-workers-hono/package.json +++ b/examples/cloudflare-workers-hono/package.json @@ -3,6 +3,7 @@ "version": "0.0.0", "dependencies": { "@upstash/qstash": "latest", + "@upstash/redis": "^1.34.3", "@upstash/workflow": "latest", "hono": "^4.5.8" }, diff --git a/examples/cloudflare-workers-hono/src/app.ts b/examples/cloudflare-workers-hono/src/app.ts index 893672d..175f438 100644 --- a/examples/cloudflare-workers-hono/src/app.ts +++ b/examples/cloudflare-workers-hono/src/app.ts @@ -1,6 +1,7 @@ import { Hono } from "hono"; import { serve, WorkflowBindings } from "@upstash/workflow/hono"; import { landingPage } from "./page"; +import { Redis } from "@upstash/redis/cloudflare"; const app = new Hono<{ Bindings: WorkflowBindings }>(); @@ -34,4 +35,37 @@ app.post( ), ); +/** + * endpoint for the ci tests + */ +app.post( + "/ci", + serve<{ text: string }>( + async (context) => { + const input = context.requestPayload.text; + const result1 = await context.run("step1", async () => { + const output = someWork(input); + console.log("step 1 input", input, "output", output); + return output; + }); + + await context.sleep("sleep", 1); + + const secret = context.headers.get("secret-header") + if (!secret) { + console.error("secret not found"); + throw new Error("secret not found. can't end the CI workflow") + } else { + console.log("saving secret to redis"); + // @ts-expect-error env isn't typed + const redis = Redis.fromEnv(context.env) + await redis.set(`ci-cf-ran-${secret}`, secret) + } + }, + { + receiver: undefined, + }, + ), +); + export default app; diff --git a/examples/cloudflare-workers-hono/wrangler.toml b/examples/cloudflare-workers-hono/wrangler.toml index bb3d0b6..c2b4da7 100644 --- a/examples/cloudflare-workers-hono/wrangler.toml +++ b/examples/cloudflare-workers-hono/wrangler.toml @@ -2,4 +2,4 @@ name = "upstash-workflow-cf-hono" main = "src/index.ts" compatibility_date = "2024-08-15" -compatibility_flags = ["nodejs_compat"] \ No newline at end of file +compatibility_flags = ["nodejs_compat"] diff --git a/examples/cloudflare-workers/ci.test.ts b/examples/cloudflare-workers/ci.test.ts deleted file mode 100644 index dccf5c7..0000000 --- a/examples/cloudflare-workers/ci.test.ts +++ /dev/null @@ -1,34 +0,0 @@ - -import { Client } from "@upstash/qstash" -import { serve } from "@upstash/workflow/nextjs" -import { describe, test, expect } from "bun:test" - -const qstashClient = new Client({ - baseUrl: "https://workflow-tests.requestcatcher.com/", - token: "mock" -}) - -const { POST: serveHandler } = serve( - async (context) => { - await context.sleep("sleeping", 10) - }, { - // @ts-expect-error type mismatch - qstashClient, - receiver: undefined - } -) - -describe("nextjs tests", () => { - test("first invocation", async () => { - const request = new Request("https://workflow-tests.requestcatcher.com/") - const response = await serveHandler(request) - - // it should send a request, but get failed to parse error because - // request catcher returns string - expect(response.status).toBe(500) - expect(await response.json()).toEqual({ - error: "SyntaxError", - message: "Failed to parse JSON", - }) - }) -}) diff --git a/examples/cloudflare-workers/test/index.spec.ts b/examples/cloudflare-workers/test/index.spec.ts deleted file mode 100644 index 0ce3c77..0000000 --- a/examples/cloudflare-workers/test/index.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -// test/index.spec.ts -import { env, createExecutionContext, waitOnExecutionContext, SELF } from 'cloudflare:test'; -import { describe, it, expect } from 'vitest'; -import worker from '../src/index'; - -// For now, you'll need to do something like this to get a correctly-typed -// `Request` to pass to `worker.fetch()`. -const IncomingRequest = Request; - -describe('Hello World worker', () => { - it('responds with Hello World! (unit style)', async () => { - const request = new IncomingRequest('http://example.com'); - // Create an empty context to pass to `worker.fetch()`. - const ctx = createExecutionContext(); - const response = await worker.fetch(request, env, ctx); - // Wait for all `Promise`s passed to `ctx.waitUntil()` to settle before running test assertions - await waitOnExecutionContext(ctx); - expect(await response.text()).toMatchInlineSnapshot(`"Hello World!"`); - }); - - it('responds with Hello World! (integration style)', async () => { - const response = await SELF.fetch('https://example.com'); - expect(await response.text()).toMatchInlineSnapshot(`"Hello World!"`); - }); -}); diff --git a/examples/nextjs-12/ci.mjs b/examples/nextjs-12/ci.mjs index 00ca2f4..e760ec8 100644 --- a/examples/nextjs-12/ci.mjs +++ b/examples/nextjs-12/ci.mjs @@ -1,6 +1,7 @@ // this file is need for the Node 18 test import { Client } from "@upstash/qstash" +import { Redis } from "@upstash/redis" import { serve } from "@upstash/workflow/nextjs" const qstashClient = new Client({ @@ -12,6 +13,8 @@ qstashClient.publishJSON = async () => { return { messageId: "msgId" } } +console.log(">>> TESTING INITIAL INVOCATION") + const { POST: serveHandler } = serve( async (context) => { await context.sleep("sleeping", 10) @@ -33,4 +36,38 @@ if (status !== 200) { if (!body.workflowRunId) { throw new Error(`ci failed. body doesn't have workflowRunId field. status: ${status}, body: ${body}`) } -console.log(">>> CI SUCCESFUL") \ No newline at end of file + +console.log(">>> TESTED INITIAL INVOCATION SUCCESFULLY") + +const deploymentUrl = process.env.DEPLOYMENT_URL +if (deploymentUrl) { + console.log(">>> TESTING WORKFLOW RUN") + + const client = new Client({ + token: process.env.QSTASH_TOKEN, + }) + const redis = Redis.fromEnv() + + const secret = Math.floor(Math.random() * 10000).toString() + await client.publishJSON({ + url: `${deploymentUrl}/api/ci`, + method: "POST", + body: "hello world!", + headers: { + "Content-type": "text/plain", + "secret-header": secret + } + }) + + await new Promise(r => setTimeout(r, 3000)); + + const result = await redis.get(`ci-cf-ran-${secret}`) + + if (result.toString() !== secret) { + throw new Error("Cloudflare workflow didn't run") + } + + console.log(">>> TESTED WORKFLOW RUN SUCCESFULLY") +} else { + console.warn(">>> SKIPPING WORKFLOW RUN TEST. DEPLOYMENT_URL NOT SET") +} diff --git a/examples/nextjs-12/package.json b/examples/nextjs-12/package.json index 53349c3..e4c76c0 100644 --- a/examples/nextjs-12/package.json +++ b/examples/nextjs-12/package.json @@ -9,7 +9,9 @@ "lint": "next lint" }, "dependencies": { - "@upstash/workflow": "^0.1.2", + "@upstash/redis": "^1.34.3", + "@upstash/qstash": "latest", + "@upstash/workflow": "latest", "next": "12.3.4", "react": "18.3.1", "react-dom": "18.3.1" diff --git a/examples/nextjs-12/pages/api/ci.js b/examples/nextjs-12/pages/api/ci.js new file mode 100644 index 0000000..8a39cba --- /dev/null +++ b/examples/nextjs-12/pages/api/ci.js @@ -0,0 +1,49 @@ +/** + * this endpoint is for the CI of @upstash/workflow. + * + * refer to workflow.js for a simpler example. + */ +import { servePagesRouter } from "@upstash/workflow/nextjs"; +import { Redis } from "@upstash/redis" + +const someWork = (input) => { + return `processed '${JSON.stringify(input)}'` +} + +const baseUrl = process.env.VERCEL_URL + ? `https://${process.env.VERCEL_URL}` + : process.env.UPSTASH_WORKFLOW_URL + ? process.env.UPSTASH_WORKFLOW_URL + : "http://localhost:3001" + +const endpointUrl = `${baseUrl}/api/ci` + +const redis = Redis.fromEnv() + +const { handler } = servePagesRouter( + async (context) => { + const input = context.requestPayload + const result1 = await context.run("step1", async () => { + const output = someWork(input) + console.log("step 1 input", input, "output", output) + return output + }); + + await context.sleep("sleep", 1); + + const secret = context.headers.get("secret-header") + if (!secret) { + console.error("secret not found"); + throw new Error("secret not found. can't end the CI workflow") + } else { + console.log("saving secret to redis"); + await redis.set(`ci-cf-ran-${secret}`, secret) + } + }, + { + retries: 0, + url: endpointUrl + } +) + +export default handler diff --git a/examples/nextjs-12/pages/api/workflow.js b/examples/nextjs-12/pages/api/workflow.js index f48d34a..1bb2765 100644 --- a/examples/nextjs-12/pages/api/workflow.js +++ b/examples/nextjs-12/pages/api/workflow.js @@ -27,11 +27,8 @@ const { handler } = servePagesRouter( }); }, { - receiver: undefined, url: endpointUrl } ) -export default async (request, response) => { - return handler(request, response) -} +export default handler diff --git a/examples/nextjs/ci.test.ts b/examples/nextjs/ci.test.ts index 7f94f13..c6fa249 100644 --- a/examples/nextjs/ci.test.ts +++ b/examples/nextjs/ci.test.ts @@ -8,6 +8,11 @@ const qstashClient = new Client({ token: "mock" }) +// @ts-expect-error mocking publishJSON +qstashClient.publishJSON = async () => { + return { messageId: "msgId" } +} + const { POST: serveHandler } = serve( async (context) => { await context.sleep("sleeping", 10) @@ -18,16 +23,14 @@ const { POST: serveHandler } = serve( ) describe("nextjs tests", () => { - test("first invocation", async () => { + test("should send first invocation", async () => { const request = new Request("https://workflow-tests.requestcatcher.com/") const response = await serveHandler(request) // it should send a request, but get failed to parse error because // request catcher returns string - expect(response.status).toBe(500) - expect(await response.json()).toEqual({ - error: "SyntaxError", - message: "Failed to parse JSON", - }) + expect(response.status).toBe(200) + const result = await response.json() + expect(result.workflowRunId).toBeTruthy() }) })