Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/__tests__/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,11 @@ describe('per-key rate quotas', () => {
describe('admin endpoints', () => {
const ORIGINAL = process.env.ADMIN_TOKEN
beforeEach(() => { process.env.ADMIN_TOKEN = 'admin-secret' })
afterEach(() => { process.env.ADMIN_TOKEN = ORIGINAL })
afterEach(() => {
// Node stringifies undefined → "undefined"; delete to truly clear.
if (ORIGINAL === undefined) delete process.env.ADMIN_TOKEN
else process.env.ADMIN_TOKEN = ORIGINAL
})

async function buildAdminApp() {
const app = Fastify()
Expand Down
12 changes: 7 additions & 5 deletions src/__tests__/pairs.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import Fastify from 'fastify'

// vi.mock factories are hoisted — declare all mock fns with vi.hoisted()
Expand Down Expand Up @@ -41,14 +41,12 @@ const ADMIN_KEY = 'test-admin-key-abc'
const VALID_ISSUER = 'GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5'

async function buildApp() {
const savedKey = process.env.ADMIN_API_KEY
// Keep ADMIN_API_KEY set through inject — the route reads it at request time.
// Restoring here (before inject) raced with parallel files mutating process.env.
process.env.ADMIN_API_KEY = ADMIN_KEY
const app = Fastify({ logger: false })
await registerPairsRoutes(app)
await app.ready()
// restore after app init
if (savedKey === undefined) delete process.env.ADMIN_API_KEY
else process.env.ADMIN_API_KEY = savedKey
return app
}

Expand All @@ -61,6 +59,10 @@ beforeEach(() => {
mockQuery.mockReset().mockResolvedValue({ rows: [] })
})

afterEach(() => {
delete process.env.ADMIN_API_KEY
})

describe('POST /pairs', () => {
it('adds a new pair and returns 201 with pairKey', async () => {
const app = await buildApp()
Expand Down
6 changes: 6 additions & 0 deletions src/__tests__/usage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ beforeEach(() => {
vi.clearAllMocks()
})

const ORIGINAL_ADMIN_TOKEN = process.env.ADMIN_TOKEN
afterEach(() => {
if (ORIGINAL_ADMIN_TOKEN === undefined) delete process.env.ADMIN_TOKEN
else process.env.ADMIN_TOKEN = ORIGINAL_ADMIN_TOKEN
})

function buildUsageApp() {
process.env.ADMIN_TOKEN = 'admin-secret'
const app = Fastify()
Expand Down
14 changes: 13 additions & 1 deletion src/__tests__/x402-quota.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'

const PAYMENT_ADDRESS = 'GPAYMENTADDRESS123456789012345678901234567890123456789012'

Expand Down Expand Up @@ -80,6 +80,18 @@ beforeEach(() => {
mockInitialize.mockReset().mockResolvedValue(undefined)
})

const ENV_KEYS = ['ORACLE_PAYMENT_ADDRESS', 'STELLAR_NETWORK', 'REQUIRE_API_KEY'] as const
const originalEnv: Partial<Record<(typeof ENV_KEYS)[number], string | undefined>> = {}
beforeEach(() => {
for (const key of ENV_KEYS) originalEnv[key] = process.env[key]
})
afterEach(() => {
for (const key of ENV_KEYS) {
if (originalEnv[key] === undefined) delete process.env[key]
else process.env[key] = originalEnv[key]
}
})

async function buildAppWithAuth() {
process.env.ORACLE_PAYMENT_ADDRESS = PAYMENT_ADDRESS
process.env.STELLAR_NETWORK = 'testnet'
Expand Down
6 changes: 4 additions & 2 deletions tests/aggregator.property.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ describe('Price aggregator property tests', () => {
// this test failed before running a single iteration — the mocked
// @stellar/stellar-sdk had no Networks export, which getNetworkConfig()
// needs); that volume of real work needs more than the 5s default.
it('produces valid route results for random venue prices', { timeout: 30000 }, async () => {
// 30s keeps real headroom for 10k async iterations under fork load while
// staying tight enough that a genuine hang still fails the run.
it('produces valid route results for random venue prices', { timeout: 30_000 }, async () => {
await fc.assert(
fc.asyncProperty(
fc.float({ min: 0, max: 2000, noNaN: true, noDefaultInfinity: true, noNegativeZero: true }),
Expand Down Expand Up @@ -119,5 +121,5 @@ describe('Price aggregator property tests', () => {
),
{ numRuns: 10000 }
)
}, 15000)
})
})
11 changes: 10 additions & 1 deletion vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,14 @@ export default defineConfig({
globals: true,
include: ['src/**/*.test.ts', 'tests/**/*.test.ts'],
exclude: ['dist/**', 'node_modules/**'],
// forks: each concurrent file gets its own process. This pins Vitest's
// existing default rather than changing it - the flake was env leakage
// within a reused fork, which setupFiles settles.
pool: 'forks',
setupFiles: ['./vitest.setup.ts'],
// resetModules()+@stellar/stellar-sdk reimport (networkVenueConfig) needs
// a little headroom over the 5s default. Kept deliberately tight: a loose
// ceiling hides a genuine hang as a slow pass.
testTimeout: 20_000,
},
})
})
34 changes: 34 additions & 0 deletions vitest.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Enforce process.env isolation across the suite.
*
* Root cause of intermittent auth.test.ts / pairs.test.ts (and rotating
* victims like networkVenueConfig.test.ts) failures: Vitest 4's default pool
* is already `forks`, so files do not run concurrently in one process. What
* leaks instead is `process.env` *within* a fork - a reused child process runs
* several files sequentially, so a key one file sets is still set when the
* next file starts, and any suite that assumes a clean environment fails
* depending on the order the files happen to be scheduled in.
*
* This setup file snapshots `process.env` before every test and restores it
* afterwards so mutations cannot leak to the next test in the same worker.
* Combined with `pool: 'forks'` in vitest.config.ts (separate process per
* concurrent file), cross-file leakage is eliminated without giving up
* file parallelism.
*/
import { beforeEach, afterEach } from 'vitest'

let envSnapshot: Record<string, string | undefined>

beforeEach(() => {
envSnapshot = { ...process.env }
})

afterEach(() => {
for (const key of Object.keys(process.env)) {
if (!(key in envSnapshot)) delete process.env[key]
}
for (const [key, value] of Object.entries(envSnapshot)) {
if (value === undefined) delete process.env[key]
else process.env[key] = value
}
})