mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-08-24 10:14:19 -05:00
* Fix flaky smoke build checks Replace feature-flag build preprocessing with a Bun onLoad transform so smoke/build no longer rewrites tracked src files while tests may be reading them. Keep telemetry stubs ahead of the feature transform in both CLI and SDK builds, preserve non-empty text token counts in hybrid context splitting, and make the corrupted Orama stress test assert against the actual project directory used by the test. Verified with bun run smoke, bun test src/utils/hybridContextStrategy.test.ts, bun test src/utils/knowledgeGraph.stress.test.ts --rerun-each 3, and bun test --max-concurrency=1. * Harden KnowledgeGraph smoke stress isolation Give each KnowledgeGraph stress test its own temporary config directory and remove it during teardown so Orama, SQLite, and corrupted-file state cannot bleed between stress cases or later PR test runs. Reviewed at least 20 open PRs and found the recurring smoke-and-tests failure cluster is the full unit suite, especially KnowledgeGraph corrupted Orama recovery. Verified with bun test src/utils/knowledgeGraph.stress.test.ts --rerun-each 5, bun test --max-concurrency=1, and bun run smoke. * Harden smoke test isolation Audit and harden broad smoke-adjacent test suites for process-global leaks, including env/config restoration, shared registry/module mock cleanup, fetch/axios/mock restoration, and global MACRO/platform/sandbox mutations. Replace fragile render sleeps in interactive tests with output-driven waits, and isolate provider/model/profile tests behind the shared mutation lock so unrelated PRs do not inherit stale process state. Make SQLite knowledge graph cleanup clear closed on-disk databases before best-effort file cleanup, with coverage for the stale database reset path. Verified: bun run smoke; bun test --max-concurrency=1; python -m pytest -q python/tests; bun run security:pr-scan -- --base origin/main; bun run test:provider; npm run test:provider-recommendation. * Harden test isolation across smoke suite Guard process-global test mutations with the shared mutation lock across env, module mock, config cache, and storage tests.\n\nDeep-copy global config snapshots, restore transient globals precisely, and make plugin/LSP mocks expose compatible export surfaces so concurrent test loading does not poison unrelated suites.\n\nReplace fixed SDK cleanup sleeps with call polling to remove timing sensitivity.\n\nVerification:\n- bun test --max-concurrency=1\n- bun run smoke\n- python -m pytest -q python/tests\n- bun run test:provider\n- npm run test:provider-recommendation\n- bun run security:pr-scan -- --base origin/main\n- git diff --check * Close remaining test global-state leaks Guard remaining cache, plugin, console, and VS Code module-mock tests with the shared mutation lock.\n\nThis follow-up audit covers non-env process-global state that can leak across test files: tool schema cache, cache stats tracker state, plugin loader caches, console.error replacement, and VS Code mock.module usage.\n\nVerification:\n- leak-surface scans for env/global/mock.module/cache outliers\n- duplicate top-level mock collision cluster\n- affected tests cluster\n- bun test --max-concurrency=1\n- bun run smoke\n- git diff --check * Guard remaining mock restore cleanup Lock tests that call bun:test mock.restore without installing module mocks themselves.\n\nmock.restore is process-global, so these cleanup hooks can still tear down another test file's active module mocks when files run concurrently.\n\nVerification:\n- expanded leak scans for env, globals, module mocks, mock.restore, timers, argv, and caches\n- bun test src/components/useCodexOAuthFlow.test.tsx src/services/github/deviceFlow.test.ts\n- bun test --max-concurrency=1\n- bun run smoke\n- git diff --check * Harden test isolation for smoke stability Serialize tests that mutate process-global state behind the shared mutation lock, including process.env, transient globals, global config/cache state, storage mocks, and Bun module mocks. Add isolated env mutex instances for SDK mutex tests so timeout coverage no longer manipulates the live process-global mutex. Move top-level mock.module setup behind lock acquisition and restore mocks before releasing locks to prevent cross-file leakage under parallel smoke runs. Verified with: bun test --max-concurrency=1; bun test; bun run smoke.
168 lines
5.3 KiB
TypeScript
168 lines
5.3 KiB
TypeScript
import { describe, test, expect } from 'bun:test'
|
|
import {
|
|
assertValidSessionId,
|
|
mapMessageToSDK,
|
|
createEnvMutexForTesting,
|
|
} from '../../src/entrypoints/sdk/shared.js'
|
|
|
|
describe('assertValidSessionId', () => {
|
|
test('accepts valid UUID v4', () => {
|
|
expect(() => assertValidSessionId('00000000-0000-0000-0000-000000000000')).not.toThrow()
|
|
expect(() => assertValidSessionId('550e8400-e29b-41d4-a716-446655440000')).not.toThrow()
|
|
})
|
|
|
|
test('rejects non-UUID string', () => {
|
|
expect(() => assertValidSessionId('not-a-uuid')).toThrow('Invalid session ID')
|
|
})
|
|
|
|
test('rejects empty string', () => {
|
|
expect(() => assertValidSessionId('')).toThrow('Invalid session ID')
|
|
})
|
|
|
|
test('rejects UUID with wrong format', () => {
|
|
expect(() => assertValidSessionId('00000000-0000-0000-0000')).toThrow('Invalid session ID')
|
|
})
|
|
|
|
test('rejects path traversal attempts', () => {
|
|
expect(() => assertValidSessionId('../../etc/passwd')).toThrow('Invalid session ID')
|
|
})
|
|
})
|
|
|
|
describe('mapMessageToSDK', () => {
|
|
test('preserves type field from message', () => {
|
|
const result = mapMessageToSDK({ type: 'assistant', content: 'hello' })
|
|
expect(result.type).toBe('assistant')
|
|
})
|
|
|
|
test('defaults to unknown when type is missing', () => {
|
|
const result = mapMessageToSDK({ content: 'hello' })
|
|
expect(result.type).toBe('unknown')
|
|
})
|
|
|
|
test('spreads all fields through', () => {
|
|
const msg = {
|
|
type: 'result',
|
|
session_id: 'test-123',
|
|
subtype: 'success',
|
|
cost_usd: 0.01,
|
|
}
|
|
const result = mapMessageToSDK(msg)
|
|
expect((result as any).session_id).toBe('test-123')
|
|
expect((result as any).subtype).toBe('success')
|
|
expect((result as any).cost_usd).toBe(0.01)
|
|
})
|
|
|
|
test('preserves nested objects', () => {
|
|
const msg = {
|
|
type: 'assistant',
|
|
message: {
|
|
role: 'assistant',
|
|
content: [{ type: 'text', text: 'Hello world' }],
|
|
},
|
|
}
|
|
const result = mapMessageToSDK(msg)
|
|
expect((result as any).message.content[0].text).toBe('Hello world')
|
|
})
|
|
|
|
test('throws TypeError for null input', () => {
|
|
expect(() => mapMessageToSDK(null as any)).toThrow(TypeError)
|
|
expect(() => mapMessageToSDK(null as any)).toThrow('expected non-null object')
|
|
})
|
|
|
|
test('throws TypeError for non-object input', () => {
|
|
expect(() => mapMessageToSDK('string' as any)).toThrow(TypeError)
|
|
expect(() => mapMessageToSDK(42 as any)).toThrow(TypeError)
|
|
})
|
|
|
|
test('throws TypeError for invalid type field', () => {
|
|
expect(() => mapMessageToSDK({ type: 123 })).toThrow(TypeError)
|
|
expect(() => mapMessageToSDK({ type: 123 })).toThrow("'type' field must be string")
|
|
})
|
|
})
|
|
|
|
describe.serial('env mutex timeout', () => {
|
|
test('acquireEnvMutex returns timeout result when mutex is locked', async () => {
|
|
const { acquireEnvMutex, releaseEnvMutex } = createEnvMutexForTesting()
|
|
// First acquire locks the mutex
|
|
const firstResult = await acquireEnvMutex({ timeoutMs: 5_000 })
|
|
expect(firstResult.acquired).toBe(true)
|
|
|
|
// Second acquire with timeout should return timeout result
|
|
try {
|
|
const secondResult = await acquireEnvMutex({ timeoutMs: 100 })
|
|
expect(secondResult.acquired).toBe(false)
|
|
expect(secondResult.reason).toBe('timeout')
|
|
} finally {
|
|
releaseEnvMutex()
|
|
}
|
|
})
|
|
|
|
test('acquireEnvMutex succeeds before timeout', async () => {
|
|
const { acquireEnvMutex, releaseEnvMutex } = createEnvMutexForTesting()
|
|
const firstResult = await acquireEnvMutex({ timeoutMs: 5_000 })
|
|
expect(firstResult.acquired).toBe(true)
|
|
|
|
// Release after 50ms
|
|
setTimeout(releaseEnvMutex, 50)
|
|
|
|
// Second acquire with 200ms timeout should succeed
|
|
let acquiredSecond = false
|
|
try {
|
|
const result = await acquireEnvMutex({ timeoutMs: 200 })
|
|
acquiredSecond = result.acquired
|
|
expect(result.acquired).toBe(true)
|
|
} finally {
|
|
if (acquiredSecond) {
|
|
releaseEnvMutex()
|
|
}
|
|
}
|
|
})
|
|
|
|
test('acquireEnvMutex without timeout waits indefinitely (default behavior)', async () => {
|
|
const { acquireEnvMutex, releaseEnvMutex } = createEnvMutexForTesting()
|
|
const firstResult = await acquireEnvMutex({ timeoutMs: 5_000 })
|
|
expect(firstResult.acquired).toBe(true)
|
|
|
|
// Release after short delay
|
|
setTimeout(releaseEnvMutex, 50)
|
|
|
|
// No timeout option - should wait and succeed
|
|
let acquiredSecond = false
|
|
try {
|
|
const result = await acquireEnvMutex()
|
|
acquiredSecond = result.acquired
|
|
expect(result.acquired).toBe(true)
|
|
} finally {
|
|
if (acquiredSecond) {
|
|
releaseEnvMutex()
|
|
}
|
|
}
|
|
})
|
|
|
|
test('mutex remains functional after timeout', async () => {
|
|
const { acquireEnvMutex, releaseEnvMutex } = createEnvMutexForTesting()
|
|
// First acquire locks it
|
|
const firstResult = await acquireEnvMutex({ timeoutMs: 5_000 })
|
|
expect(firstResult.acquired).toBe(true)
|
|
|
|
try {
|
|
// Second acquire with timeout fails
|
|
const result2 = await acquireEnvMutex({ timeoutMs: 50 })
|
|
expect(result2.acquired).toBe(false)
|
|
} finally {
|
|
// Release the first
|
|
releaseEnvMutex()
|
|
}
|
|
|
|
// Third acquire should succeed (mutex not permanently locked)
|
|
const result3 = await acquireEnvMutex({ timeoutMs: 100 })
|
|
try {
|
|
expect(result3.acquired).toBe(true)
|
|
} finally {
|
|
if (result3.acquired) {
|
|
releaseEnvMutex()
|
|
}
|
|
}
|
|
})
|
|
})
|