mirror of
https://github.com/codeaashu/claude-code.git
synced 2026-04-08 22:28:48 +03:00
234 lines
8.1 KiB
TypeScript
234 lines
8.1 KiB
TypeScript
// scripts/test-services.ts
|
|
// Test that all services initialize without crashing
|
|
// Usage: bun scripts/test-services.ts
|
|
|
|
import '../src/shims/preload.js'
|
|
|
|
// Ensure we don't accidentally talk to real servers
|
|
process.env.NODE_ENV = process.env.NODE_ENV || 'test'
|
|
|
|
type TestResult = { name: string; status: 'pass' | 'fail' | 'skip'; detail?: string }
|
|
const results: TestResult[] = []
|
|
|
|
function pass(name: string, detail?: string) {
|
|
results.push({ name, status: 'pass', detail })
|
|
console.log(` ✅ ${name}${detail ? ` — ${detail}` : ''}`)
|
|
}
|
|
|
|
function fail(name: string, detail: string) {
|
|
results.push({ name, status: 'fail', detail })
|
|
console.log(` ❌ ${name} — ${detail}`)
|
|
}
|
|
|
|
function skip(name: string, detail: string) {
|
|
results.push({ name, status: 'skip', detail })
|
|
console.log(` ⏭️ ${name} — ${detail}`)
|
|
}
|
|
|
|
async function testGrowthBook() {
|
|
console.log('\n--- GrowthBook (Feature Flags) ---')
|
|
try {
|
|
const gb = await import('../src/services/analytics/growthbook.js')
|
|
|
|
// Test cached feature value returns default when GrowthBook is unavailable
|
|
const boolResult = gb.getFeatureValue_CACHED_MAY_BE_STALE('nonexistent_feature', false)
|
|
if (boolResult === false) {
|
|
pass('getFeatureValue_CACHED_MAY_BE_STALE (bool)', 'returns default false')
|
|
} else {
|
|
fail('getFeatureValue_CACHED_MAY_BE_STALE (bool)', `expected false, got ${boolResult}`)
|
|
}
|
|
|
|
const strResult = gb.getFeatureValue_CACHED_MAY_BE_STALE('nonexistent_str', 'default_val')
|
|
if (strResult === 'default_val') {
|
|
pass('getFeatureValue_CACHED_MAY_BE_STALE (str)', 'returns default string')
|
|
} else {
|
|
fail('getFeatureValue_CACHED_MAY_BE_STALE (str)', `expected "default_val", got "${strResult}"`)
|
|
}
|
|
|
|
// Test Statsig gate check returns false
|
|
const gateResult = gb.checkStatsigFeatureGate_CACHED_MAY_BE_STALE('nonexistent_gate')
|
|
if (gateResult === false) {
|
|
pass('checkStatsigFeatureGate_CACHED_MAY_BE_STALE', 'returns false for unknown gate')
|
|
} else {
|
|
fail('checkStatsigFeatureGate_CACHED_MAY_BE_STALE', `expected false, got ${gateResult}`)
|
|
}
|
|
} catch (err: any) {
|
|
fail('GrowthBook import', err.message)
|
|
}
|
|
}
|
|
|
|
async function testAnalyticsSink() {
|
|
console.log('\n--- Analytics Sink ---')
|
|
try {
|
|
const analytics = await import('../src/services/analytics/index.js')
|
|
|
|
// logEvent should queue without crashing when no sink is attached
|
|
analytics.logEvent('test_event', { test_key: 1 })
|
|
pass('logEvent (no sink)', 'queues without crash')
|
|
|
|
await analytics.logEventAsync('test_async_event', { test_key: 2 })
|
|
pass('logEventAsync (no sink)', 'queues without crash')
|
|
} catch (err: any) {
|
|
fail('Analytics sink', err.message)
|
|
}
|
|
}
|
|
|
|
async function testPolicyLimits() {
|
|
console.log('\n--- Policy Limits ---')
|
|
try {
|
|
const pl = await import('../src/services/policyLimits/index.js')
|
|
|
|
// isPolicyAllowed should return true (fail open) when no restrictions loaded
|
|
const result = pl.isPolicyAllowed('allow_remote_sessions')
|
|
if (result === true) {
|
|
pass('isPolicyAllowed (no cache)', 'fails open — returns true')
|
|
} else {
|
|
fail('isPolicyAllowed (no cache)', `expected true (fail open), got ${result}`)
|
|
}
|
|
|
|
// isPolicyLimitsEligible should return false without valid auth
|
|
const eligible = pl.isPolicyLimitsEligible()
|
|
pass('isPolicyLimitsEligible', `returns ${eligible} (expected false in test env)`)
|
|
} catch (err: any) {
|
|
fail('Policy limits', err.message)
|
|
}
|
|
}
|
|
|
|
async function testRemoteManagedSettings() {
|
|
console.log('\n--- Remote Managed Settings ---')
|
|
try {
|
|
const rms = await import('../src/services/remoteManagedSettings/index.js')
|
|
|
|
// isEligibleForRemoteManagedSettings should return false without auth
|
|
const eligible = rms.isEligibleForRemoteManagedSettings()
|
|
pass('isEligibleForRemoteManagedSettings', `returns ${eligible} (expected false in test env)`)
|
|
|
|
// waitForRemoteManagedSettingsToLoad should resolve immediately if not eligible
|
|
await rms.waitForRemoteManagedSettingsToLoad()
|
|
pass('waitForRemoteManagedSettingsToLoad', 'resolves immediately when not eligible')
|
|
} catch (err: any) {
|
|
fail('Remote managed settings', err.message)
|
|
}
|
|
}
|
|
|
|
async function testBootstrapData() {
|
|
console.log('\n--- Bootstrap Data ---')
|
|
try {
|
|
const bootstrap = await import('../src/services/api/bootstrap.js')
|
|
|
|
// fetchBootstrapData should not crash — just skip when no auth
|
|
await bootstrap.fetchBootstrapData()
|
|
pass('fetchBootstrapData', 'completes without crash (skips when no auth)')
|
|
} catch (err: any) {
|
|
// fetchBootstrapData catches its own errors, so this means an import-level issue
|
|
fail('Bootstrap data', err.message)
|
|
}
|
|
}
|
|
|
|
async function testSessionMemoryUtils() {
|
|
console.log('\n--- Session Memory ---')
|
|
try {
|
|
const smUtils = await import('../src/services/SessionMemory/sessionMemoryUtils.js')
|
|
|
|
// Default config should be sensible
|
|
const config = smUtils.DEFAULT_SESSION_MEMORY_CONFIG
|
|
if (config.minimumMessageTokensToInit > 0 && config.minimumTokensBetweenUpdate > 0) {
|
|
pass('DEFAULT_SESSION_MEMORY_CONFIG', `init=${config.minimumMessageTokensToInit} tokens, update=${config.minimumTokensBetweenUpdate} tokens`)
|
|
} else {
|
|
fail('DEFAULT_SESSION_MEMORY_CONFIG', 'unexpected config values')
|
|
}
|
|
|
|
// getLastSummarizedMessageId should return undefined initially
|
|
const lastId = smUtils.getLastSummarizedMessageId()
|
|
if (lastId === undefined) {
|
|
pass('getLastSummarizedMessageId', 'returns undefined initially')
|
|
} else {
|
|
fail('getLastSummarizedMessageId', `expected undefined, got ${lastId}`)
|
|
}
|
|
} catch (err: any) {
|
|
fail('Session memory utils', err.message)
|
|
}
|
|
}
|
|
|
|
async function testCostTracker() {
|
|
console.log('\n--- Cost Tracking ---')
|
|
try {
|
|
const ct = await import('../src/cost-tracker.js')
|
|
|
|
// Total cost should start at 0
|
|
const cost = ct.getTotalCost()
|
|
if (cost === 0) {
|
|
pass('getTotalCost', 'starts at $0.00')
|
|
} else {
|
|
pass('getTotalCost', `current: $${cost.toFixed(4)} (non-zero means restored session)`)
|
|
}
|
|
|
|
// Duration should be available
|
|
const duration = ct.getTotalDuration()
|
|
pass('getTotalDuration', `${duration}ms`)
|
|
|
|
// Token counters should be available
|
|
const inputTokens = ct.getTotalInputTokens()
|
|
const outputTokens = ct.getTotalOutputTokens()
|
|
pass('Token counters', `input=${inputTokens}, output=${outputTokens}`)
|
|
|
|
// Lines changed
|
|
const added = ct.getTotalLinesAdded()
|
|
const removed = ct.getTotalLinesRemoved()
|
|
pass('Lines changed', `+${added} -${removed}`)
|
|
} catch (err: any) {
|
|
fail('Cost tracker', err.message)
|
|
}
|
|
}
|
|
|
|
async function testInit() {
|
|
console.log('\n--- Init (entrypoint) ---')
|
|
try {
|
|
const { init } = await import('../src/entrypoints/init.js')
|
|
await init()
|
|
pass('init()', 'completed successfully')
|
|
} catch (err: any) {
|
|
fail('init()', err.message)
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
console.log('=== Services Layer Smoke Test ===')
|
|
console.log(`Environment: NODE_ENV=${process.env.NODE_ENV}`)
|
|
console.log(`Auth: ANTHROPIC_API_KEY=${process.env.ANTHROPIC_API_KEY ? '(set)' : '(not set)'}`)
|
|
|
|
// Test individual services first (order: least-dependent → most-dependent)
|
|
await testAnalyticsSink()
|
|
await testGrowthBook()
|
|
await testPolicyLimits()
|
|
await testRemoteManagedSettings()
|
|
await testBootstrapData()
|
|
await testSessionMemoryUtils()
|
|
await testCostTracker()
|
|
|
|
// Then test the full init sequence
|
|
await testInit()
|
|
|
|
// Summary
|
|
console.log('\n=== Summary ===')
|
|
const passed = results.filter(r => r.status === 'pass').length
|
|
const failed = results.filter(r => r.status === 'fail').length
|
|
const skipped = results.filter(r => r.status === 'skip').length
|
|
console.log(` ${passed} passed, ${failed} failed, ${skipped} skipped`)
|
|
|
|
if (failed > 0) {
|
|
console.log('\nFailed tests:')
|
|
for (const r of results.filter(r => r.status === 'fail')) {
|
|
console.log(` ❌ ${r.name}: ${r.detail}`)
|
|
}
|
|
process.exit(1)
|
|
}
|
|
|
|
console.log('\n✅ All services handle graceful degradation correctly')
|
|
}
|
|
|
|
main().catch(err => {
|
|
console.error('Fatal error in smoke test:', err)
|
|
process.exit(1)
|
|
})
|