From c7c4592579038ea6f9a916ff4adfccecbe3f877b Mon Sep 17 00:00:00 2001 From: Tony Xiao Date: Fri, 3 Apr 2026 09:10:07 -0700 Subject: [PATCH 1/2] test(service): share one Temporal dev server across test files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TestWorkflowEnvironment.createLocal() spawns a real Temporal dev server process. With Vitest's default file parallelism, workflow.test.ts and app.test.ts both called createLocal() concurrently, causing occasional "connection reset by peer" startup races on CI. Fix: start one shared dev server in vitest.global-setup.ts (Vitest globalSetup hook) and provide its address via Vitest's provide/inject API. Each test file connects with createFromExistingServer(), which only opens client connections — teardown() on these envs closes connections but does not kill the server. Co-Authored-By: Claude Sonnet 4.6 Committed-By-Agent: claude --- apps/service/src/__tests__/workflow.test.ts | 10 +++++++--- apps/service/src/api/app.test.ts | 8 ++++++-- apps/service/vitest.config.ts | 1 + apps/service/vitest.d.ts | 7 +++++++ apps/service/vitest.global-setup.ts | 20 ++++++++++++++++++++ 5 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 apps/service/vitest.d.ts create mode 100644 apps/service/vitest.global-setup.ts diff --git a/apps/service/src/__tests__/workflow.test.ts b/apps/service/src/__tests__/workflow.test.ts index eaf2db62f..b8c1343b9 100644 --- a/apps/service/src/__tests__/workflow.test.ts +++ b/apps/service/src/__tests__/workflow.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, beforeAll, afterAll } from 'vitest' +import { describe, it, expect, beforeAll, afterAll, inject } from 'vitest' import { TestWorkflowEnvironment } from '@temporalio/testing' import { Worker } from '@temporalio/worker' import path from 'node:path' @@ -31,10 +31,14 @@ function stubActivities(overrides: Partial = {}): SyncActivities let testEnv: TestWorkflowEnvironment beforeAll(async () => { - testEnv = await TestWorkflowEnvironment.createLocal() -}, 120_000) + // Connect to the shared dev server started in vitest.global-setup.ts instead of + // spawning a new one per file — avoids concurrent startup races under file parallelism. + const address = inject('temporalTestServerAddress') + testEnv = await TestWorkflowEnvironment.createFromExistingServer({ address }) +}, 30_000) afterAll(async () => { + // teardown() on an existing-server env only closes connections, not the server itself. await testEnv?.teardown() }) diff --git a/apps/service/src/api/app.test.ts b/apps/service/src/api/app.test.ts index 8a0f9c87a..eccc4629e 100644 --- a/apps/service/src/api/app.test.ts +++ b/apps/service/src/api/app.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, beforeAll, afterAll } from 'vitest' +import { describe, expect, it, beforeAll, afterAll, inject } from 'vitest' import type { WorkflowClient } from '@temporalio/client' import { TestWorkflowEnvironment } from '@temporalio/testing' import { Worker } from '@temporalio/worker' @@ -90,7 +90,10 @@ let worker: Worker let workerRunning: Promise beforeAll(async () => { - testEnv = await TestWorkflowEnvironment.createLocal() + // Connect to the shared dev server started in vitest.global-setup.ts instead of + // spawning a new one per file — avoids concurrent startup races under file parallelism. + const address = inject('temporalTestServerAddress') + testEnv = await TestWorkflowEnvironment.createFromExistingServer({ address }) worker = await Worker.create({ connection: testEnv.nativeConnection, taskQueue: 'test-api', @@ -103,6 +106,7 @@ beforeAll(async () => { afterAll(async () => { worker?.shutdown() await workerRunning + // teardown() on an existing-server env only closes connections, not the server itself. await testEnv?.teardown() }) diff --git a/apps/service/vitest.config.ts b/apps/service/vitest.config.ts index 592756e35..eab0960f4 100644 --- a/apps/service/vitest.config.ts +++ b/apps/service/vitest.config.ts @@ -2,6 +2,7 @@ import { defineConfig } from 'vitest/config' export default defineConfig({ test: { + globalSetup: ['./vitest.global-setup.ts'], exclude: ['**/node_modules/**', '**/dist/**', '**/*.integration.test.ts'], testTimeout: 60_000, hookTimeout: 60_000, diff --git a/apps/service/vitest.d.ts b/apps/service/vitest.d.ts new file mode 100644 index 000000000..e23ea7281 --- /dev/null +++ b/apps/service/vitest.d.ts @@ -0,0 +1,7 @@ +import 'vitest' + +declare module 'vitest' { + export interface ProvidedContext { + temporalTestServerAddress: string + } +} diff --git a/apps/service/vitest.global-setup.ts b/apps/service/vitest.global-setup.ts new file mode 100644 index 000000000..2e51d81c9 --- /dev/null +++ b/apps/service/vitest.global-setup.ts @@ -0,0 +1,20 @@ +/** + * Vitest global setup: starts ONE Temporal dev server for the entire test run. + * + * Each test file connects to this shared server via TestWorkflowEnvironment.createFromExistingServer() + * rather than calling createLocal() per-file, which avoids concurrent startup races under + * Vitest's default file parallelism. + */ +import { TestWorkflowEnvironment } from '@temporalio/testing' +import type { GlobalSetupContext } from 'vitest/node' + +let env: TestWorkflowEnvironment | undefined + +export async function setup({ provide }: GlobalSetupContext) { + env = await TestWorkflowEnvironment.createLocal() + provide('temporalTestServerAddress', env.address) +} + +export async function teardown() { + await env?.teardown() +} From 0797defcfadce783d533a672b87114ea4bbf2e78 Mon Sep 17 00:00:00 2001 From: Tony Xiao Date: Fri, 3 Apr 2026 09:11:11 -0700 Subject: [PATCH 2/2] chore: format Committed-By-Agent: claude --- e2e/docs.test.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/e2e/docs.test.ts b/e2e/docs.test.ts index f63b293d9..7a73ec57d 100644 --- a/e2e/docs.test.ts +++ b/e2e/docs.test.ts @@ -7,11 +7,7 @@ const ROOT = join(import.meta.dirname, '..') // All plan and design files must start with YYYY-MM-DD- const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}-[\w-]+\.md$/ -const CHECKED_DIRS = [ - 'docs/plans/active', - 'docs/plans/completed', - 'docs/design', -] +const CHECKED_DIRS = ['docs/plans/active', 'docs/plans/completed', 'docs/design'] for (const dir of CHECKED_DIRS) { describe(`${dir} naming convention`, () => {