Skip to content

Clean up globals patching, supports vitest#222

Merged
ianmacartney merged 9 commits intomainfrom
ian/vitest-compat
Mar 30, 2026
Merged

Clean up globals patching, supports vitest#222
ianmacartney merged 9 commits intomainfrom
ian/vitest-compat

Conversation

@ianmacartney
Copy link
Copy Markdown
Member

@ianmacartney ianmacartney commented Mar 21, 2026

Environment cleanup and test improvements

  • Restore patched globals after workflow execution to prevent test interference
  • Register workpool component with correct namespaced path
  • Add comprehensive test coverage for workflow error handling, nested workflows, signals, user confirmation, and cancellation scenarios
  • Remove setTimeout call from inline test action to avoid non-deterministic behavior
  • Update convex-test dependency to version 0.0.43
  • Enable previously skipped inline test suite

Requires fixes currently in get-convex/convex-test#76

Summary by CodeRabbit

  • Tests

    • Expanded workflow scenario test coverage with retry, nesting, and signal-based execution paths.
    • Added end-to-end testing framework for workflow validation.
    • Enabled inline query and mutation tests.
  • Chores

    • Enhanced error handling and reporting in workflow mutations.
    • Improved environment cleanup and restoration.
    • Updated test execution scripts.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 21, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The changes introduce a comprehensive end-to-end testing infrastructure for Convex workflows. A new e2e testing module exposes endpoints to start multiple workflows and poll their status, paired with an executable Node.js script that orchestrates workflow execution, approval handling, and terminal state validation. Supporting changes update environment cleanup patterns, enable previously skipped tests, and modify test workflows to remove timing delays.

Changes

Cohort / File(s) Summary
E2E Testing Infrastructure
example/convex/e2e.ts, example/e2e.mjs
New endpoints (startAll, statusAll, approveConfirmation) to initiate and monitor workflows; new orchestration script that starts all workflows, subscribes to status updates, approves confirmations on schedule, and validates terminal state completion within a 60-second timeout.
Test Suite Enhancements
example/convex/example.test.ts, example/convex/inlineTest.test.ts, example/convex/inlineTest.ts
Expanded example.test.ts with multiple workflow scenario tests covering retry, nesting, signals, and cancellation; enabled previously skipped inline tests in inlineTest.test.ts; renamed and removed delay from slowAction in inlineTest.ts.
Environment & Cleanup
src/client/environment.ts, src/client/workflowMutation.ts
setupEnvironment now returns a cleanup function to restore captured global state; workflowMutation invokes cleanup in a finally block to guarantee restoration; restructured error handling to normalize handler failures.
Configuration & Registry
package.json, src/test.ts, eslint.config.js
Added test:e2e script invoking the new e2e runner; bumped convex-test dependency; updated workpool.register call to include namespace path; excluded e2e script from linting.

Sequence Diagram

sequenceDiagram
    participant Client as E2E Client
    participant API as Convex API
    participant WF as Workflows
    participant State as Workflow State

    Client->>API: startAll()
    activate API
    API->>WF: start exampleWorkflow, catchErrorWorkflow, parentWorkflow, signalBasedWorkflow, confirmationWorkflow
    API-->>Client: WorkflowIds
    deactivate API

    loop Status Polling
        Client->>API: statusAll(workflowIds)
        API->>State: query workflow status
        State-->>API: WorkflowStatus[]
        API-->>Client: Status updates
    end

    Client->>API: approveConfirmation(workflowId)
    activate API
    API->>WF: execute chooseProposal mutation
    WF->>State: update confirmation workflow
    State-->>WF: completed
    deactivate API

    par Parallel Monitoring
        WF-->>State: transitions (executing → completed/failed/canceled)
    and
        Client->>Client: track terminal states
    end

    Client->>Client: validate all workflows reached terminal state
    Client->>Client: report results and exit
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Add user stack track to error reporting #218: Modifies RunResult propagation and error reporting in workflow handling, aligning with the error handling restructuring in workflowMutation.ts and the new error validation in test scenarios.
  • allow inline execution #219: Updates the same inlineTest.ts and inlineTest.test.ts files; this PR builds upon that foundation by renaming the slow action and enabling the full test suite.

Suggested reviewers

  • reeceyang
  • sethconvex

Poem

🐰 Workflows now run end-to-end with care,
Cleanup restored in every affair,
From test to e2e we orchestrate the flow,
With timers and signals, watch statuses grow,
A robust test suite—hooray! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: fixing global patching cleanup and vitest compatibility, which are reflected across environment setup, test configuration, and dependency updates.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ian/vitest-compat

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ianmacartney ianmacartney changed the title do globals cleanup Add comprehensive workflow tests and fix environment cleanup Mar 21, 2026
Copy link
Copy Markdown
Member Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@ianmacartney ianmacartney changed the title Add comprehensive workflow tests and fix environment cleanup Sup Mar 21, 2026
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Mar 21, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@convex-dev/workflow@222

commit: aefba81

@ianmacartney ianmacartney changed the title Sup Clean up globals patching, supports vitest Mar 21, 2026
@ianmacartney ianmacartney marked this pull request as ready for review March 21, 2026 05:56
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
example/convex/inlineTest.ts (1)

63-64: Consider removing the “re-add setTimeout” TODO.

Keeping this note may encourage reintroducing nondeterministic timing in tests; prefer deleting it or moving rationale to a tracked issue.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@example/convex/inlineTest.ts` around lines 63 - 64, Remove the in-code TODO
and commented setTimeout lines in example/convex/inlineTest.ts (the "// TODO:
use setTimeout..." and the commented await new Promise(...) line) to avoid
encouraging nondeterministic timing in tests; if you need to preserve the
rationale, create a tracked issue in the repo and replace the TODO with a
one-line comment linking to that issue number or add the issue ID to the PR
description instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@example/convex/inlineTest.ts`:
- Around line 63-64: Remove the in-code TODO and commented setTimeout lines in
example/convex/inlineTest.ts (the "// TODO: use setTimeout..." and the commented
await new Promise(...) line) to avoid encouraging nondeterministic timing in
tests; if you need to preserve the rationale, create a tracked issue in the repo
and replace the TODO with a one-line comment linking to that issue number or add
the issue ID to the PR description instead.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d7135d92-ab5c-4596-893f-5e63d664eee8

📥 Commits

Reviewing files that changed from the base of the PR and between 6abc0b0 and bbee0d8.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • example/convex/example.test.ts
  • example/convex/inlineTest.test.ts
  • example/convex/inlineTest.ts
  • package.json
  • src/client/environment.ts
  • src/client/workflowMutation.ts
  • src/test.ts
✅ Files skipped from review due to trivial changes (1)
  • package.json
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/client/workflowMutation.ts
  • src/test.ts
  • src/client/environment.ts
  • example/convex/example.test.ts
  • example/convex/inlineTest.test.ts

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
example/convex/setup.test.ts (1)

11-12: Use a typed cast instead of as any to document the type bridge requirement.

Line 12 bypasses type-safety due to an alpha version incompatibility between convex-test and @convex-dev/workflow/test. Replace the untyped cast with an explicit type parameter and update the TODO to reference the upstream dependency constraint.

Proposed patch
 export function initConvexTest() {
   const t = convexTest(schema, modules);
-  // TODO: figure out why this isn't working
-  workflow.register(t as any);
+  // TODO: remove this cast once convex-test stable release resolves type compatibility
+  workflow.register(t as Parameters<typeof workflow.register>[0]);
   return t;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@example/convex/setup.test.ts` around lines 11 - 12, Replace the untyped cast
in the call to workflow.register(t as any) with an explicit type bridge by
supplying the correct generic/type parameter for workflow.register (e.g.,
workflow.register<YourTestType>(t)) so the cast documents the intended shape
instead of using any; also update the TODO comment to mention the upstream
dependency incompatibility between convex-test and `@convex-dev/workflow/test`
(alpha mismatch) so future readers know why the explicit typed bridge is
required.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@example/convex/setup.test.ts`:
- Around line 11-12: Replace the untyped cast in the call to workflow.register(t
as any) with an explicit type bridge by supplying the correct generic/type
parameter for workflow.register (e.g., workflow.register<YourTestType>(t)) so
the cast documents the intended shape instead of using any; also update the TODO
comment to mention the upstream dependency incompatibility between convex-test
and `@convex-dev/workflow/test` (alpha mismatch) so future readers know why the
explicit typed bridge is required.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ca0bd263-f87f-48b3-af71-656582c61ddb

📥 Commits

Reviewing files that changed from the base of the PR and between b317cb8 and cf8af43.

📒 Files selected for processing (1)
  • example/convex/setup.test.ts

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
example/convex/example.test.ts (1)

61-71: Assert an observable effect from parentWorkflow.

This only proves the workflow reached a terminal state. If the child invocation or follow-up mutation regresses but the parent still completes, this test still passes. Please assert the returned value or a persisted side effect that proves both steps ran.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@example/convex/example.test.ts` around lines 61 - 71, The test only asserts
workflow.status is "completed" — change it to assert an observable effect from
parentWorkflow by either retrieving the workflow result or verifying a persisted
side-effect: capture the return value from workflow.start (or call
workflow.result/getResult equivalent) for
internal.nestedWorkflow.parentWorkflow, or query the mutation/state change the
parent or its child should have produced after await
t.finishAllScheduledFunctions(vi.runAllTimers) using t.run to read the database
or read API; update the expect to assert that returned value or persisted record
demonstrates both the child invocation and follow-up mutation ran.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@example/convex/e2e.ts`:
- Around line 12-53: The public test helpers startAll (and similarly statusAll
and approveConfirmation) are currently exposed as top-level mutation/query
handlers; change their definitions to use internalMutation/internalQuery instead
(e.g., replace mutation({ ... }) with internalMutation({ ... }) and query({ ...
}) with internalQuery({ ... })), update any imports/exports that reference these
symbols, and ensure they are invoked only from a dedicated internal test
endpoint or directly from test scripts rather than being publicly reachable;
keep the handler signatures and returned payloads the same while limiting their
registration to the internal API surface.

In `@example/e2e.mjs`:
- Around line 57-77: The approval retry loop (tryApprove) currently only logs
after exhausting attempts and leaves the confirmation workflow stuck inProgress;
change it to fail the main run by throwing (or rejecting) when the final attempt
fails and ensure any approvalTimer is cleared so the outer async flow sees the
error; specifically modify tryApprove/approvalTimer handling so that on the last
failed attempt (attempt >= 5 and !approvalDone) you clearTimeout(approvalTimer)
and throw a descriptive Error (or reject the parent promise) instead of just
console.error, and consider replacing the fixed APPROVAL_DELAY_MS trigger with
polling the confirmation workflow state before attempting approval.
- Around line 23-29: The loop that loads .env.local assigns raw values
(process.env[match[1]] = match[2]) leaving trailing CR and surrounding quotes;
update the assignment in the for loop (the block that processes envLocal and
match[2]) to normalize the value by trimming whitespace (including \r) and
removing optional surrounding single or double quotes before setting
process.env[match[1]] so CONVEX_URL and other vars are usable by ConvexClient.

---

Nitpick comments:
In `@example/convex/example.test.ts`:
- Around line 61-71: The test only asserts workflow.status is "completed" —
change it to assert an observable effect from parentWorkflow by either
retrieving the workflow result or verifying a persisted side-effect: capture the
return value from workflow.start (or call workflow.result/getResult equivalent)
for internal.nestedWorkflow.parentWorkflow, or query the mutation/state change
the parent or its child should have produced after await
t.finishAllScheduledFunctions(vi.runAllTimers) using t.run to read the database
or read API; update the expect to assert that returned value or persisted record
demonstrates both the child invocation and follow-up mutation ran.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6813be1d-37e8-42d9-a598-ade17880cb9f

📥 Commits

Reviewing files that changed from the base of the PR and between cf8af43 and 2b79db6.

⛔ Files ignored due to path filters (2)
  • example/convex/_generated/api.d.ts is excluded by !**/_generated/**
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • eslint.config.js
  • example/convex/e2e.ts
  • example/convex/example.test.ts
  • example/convex/inlineTest.test.ts
  • example/convex/inlineTest.ts
  • example/e2e.mjs
  • package.json
  • src/client/environment.ts
  • src/client/workflowMutation.ts
  • src/test.ts
✅ Files skipped from review due to trivial changes (3)
  • eslint.config.js
  • example/convex/inlineTest.test.ts
  • src/client/environment.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/test.ts
  • package.json
  • example/convex/inlineTest.ts
  • src/client/workflowMutation.ts

Comment thread example/convex/e2e.ts
Comment thread example/e2e.mjs
Comment on lines +23 to +29
if (!process.env.CONVEX_URL) {
try {
const envLocal = readFileSync(".env.local", "utf-8");
for (const line of envLocal.split("\n")) {
const match = line.match(/^(\w+)=(.+?)(\s*#.*)?$/);
if (match) process.env[match[1]] = match[2];
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Normalize .env.local values before assigning them.

This parser preserves trailing \r on CRLF files and keeps surrounding quotes, so a valid CONVEX_URL can become unusable. Trim and unquote the captured value before passing it to ConvexClient.

Minimal fix
-      if (match) process.env[match[1]] = match[2];
+      if (match) {
+        const value = match[2].trim().replace(/^(['"])(.*)\1$/, "$2");
+        process.env[match[1]] = value;
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!process.env.CONVEX_URL) {
try {
const envLocal = readFileSync(".env.local", "utf-8");
for (const line of envLocal.split("\n")) {
const match = line.match(/^(\w+)=(.+?)(\s*#.*)?$/);
if (match) process.env[match[1]] = match[2];
}
if (!process.env.CONVEX_URL) {
try {
const envLocal = readFileSync(".env.local", "utf-8");
for (const line of envLocal.split("\n")) {
const match = line.match(/^(\w+)=(.+?)(\s*#.*)?$/);
if (match) {
const value = match[2].trim().replace(/^(['"])(.*)\1$/, "$2");
process.env[match[1]] = value;
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@example/e2e.mjs` around lines 23 - 29, The loop that loads .env.local assigns
raw values (process.env[match[1]] = match[2]) leaving trailing CR and
surrounding quotes; update the assignment in the for loop (the block that
processes envLocal and match[2]) to normalize the value by trimming whitespace
(including \r) and removing optional surrounding single or double quotes before
setting process.env[match[1]] so CONVEX_URL and other vars are usable by
ConvexClient.

Comment thread example/e2e.mjs
Comment on lines +57 to +77
const approvalTimer = setTimeout(async () => {
const tryApprove = async (attempt) => {
try {
console.log(`Approving confirmation workflow (attempt ${attempt})...`);
await client.mutation(api.e2e.approveConfirmation, {
workflowId: ids.confirmation,
});
approvalDone = true;
console.log("Confirmation approved.");
} catch (e) {
const msg = e.message;
if (attempt < 5 && !approvalDone) {
console.log(` Approval not ready: ${msg.slice(0, 80)}. Retrying...`);
await new Promise((r) => setTimeout(r, 2000));
return tryApprove(attempt + 1);
}
console.error(` Approval failed after ${attempt} attempts: ${msg}`);
}
};
await tryApprove(1);
}, APPROVAL_DELAY_MS);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fail fast when approval exhausts its retries.

After the last failed approveConfirmation attempt this only logs and returns, so the confirmation workflow stays inProgress and the script eventually fails with the unrelated global timeout. Reject the main run here, and ideally trigger approval from observed workflow state instead of a fixed 5s delay.

Also applies to: 80-83

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@example/e2e.mjs` around lines 57 - 77, The approval retry loop (tryApprove)
currently only logs after exhausting attempts and leaves the confirmation
workflow stuck inProgress; change it to fail the main run by throwing (or
rejecting) when the final attempt fails and ensure any approvalTimer is cleared
so the outer async flow sees the error; specifically modify
tryApprove/approvalTimer handling so that on the last failed attempt (attempt >=
5 and !approvalDone) you clearTimeout(approvalTimer) and throw a descriptive
Error (or reject the parent promise) instead of just console.error, and consider
replacing the fixed APPROVAL_DELAY_MS trigger with polling the confirmation
workflow state before attempting approval.

@ianmacartney ianmacartney requested a review from goffrie March 27, 2026 07:03
@ianmacartney ianmacartney merged commit beea892 into main Mar 30, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants