test(cli): deflake daemon command integration assertions

What changed:
- Updated packages/cli/tests/03-daemon.test.ts to match current daemon command semantics where  reports local state and exits successfully when stopped.
- Removed host-based failure assumptions from status/status --json checks and asserted deterministic  output for an isolated PASEO_HOME.
- Hardened restart coverage to use an isolated random port and explicit cleanup via .
- Added best-effort forced cleanup in  to avoid leaking a daemon process if assertions fail mid-test.

Reasoning:
- Recent CLI refactors switched  from connection-failure behavior to local PID/config introspection, so the old test expectation () became incorrect and flaky.
- The restart path can occasionally exceed graceful stop timeout immediately after spawn; forced cleanup makes the test reliable without changing product behavior.
- This is a behavior-preserving test hardening refactor aligned with the refactor skill contract.

Verification:
- npx tsx packages/cli/tests/03-daemon.test.ts
- npm run typecheck

Accomplishments for next agent:
-  now validates current status semantics and no longer depends on transient host connectivity assumptions.
- Restart coverage now cleans up deterministically, reducing local test pollution and follow-on failures.

Challenges / notes for next agent:
- Restart cleanup still depends on process management timing; this test now uses  cleanup intentionally to keep CI/dev runs stable.
- If you later tighten restart lifecycle behavior, consider adding a dedicated unit-level test around stop timeout + escalation in local daemon utilities.
This commit is contained in:
Mohamed Boudra
2026-02-14 08:25:50 +07:00
parent 1b84613be1
commit 86b291814b
+39 -66
View File
@@ -3,15 +3,15 @@
/** /**
* Phase 2: Daemon Command Tests * Phase 2: Daemon Command Tests
* *
* Tests daemon commands - currently focused on error cases and help * Tests daemon commands with an isolated PASEO_HOME.
* since daemon start may not be fully working yet.
* *
* Tests: * Tests:
* - daemon --help shows subcommands * - daemon --help shows subcommands
* - daemon pair prints a local pairing link without requiring a running daemon * - daemon pair prints a local pairing link without requiring a running daemon
* - daemon status fails gracefully when daemon not running * - daemon status reports stopped when daemon not running
* - daemon status --json outputs valid JSON (even for errors) * - daemon status --json outputs valid JSON
* - daemon stop handles daemon not running gracefully * - daemon stop handles daemon not running gracefully
* - daemon restart starts the daemon and can be cleaned up
*/ */
import assert from 'node:assert' import assert from 'node:assert'
@@ -24,7 +24,7 @@ $.verbose = false
console.log('=== Daemon Commands ===\n') console.log('=== Daemon Commands ===\n')
// Get random port that's definitely not in use (never 6767) // Keep restart off default 6767 to avoid collisions with any existing daemon.
const port = 10000 + Math.floor(Math.random() * 50000) const port = 10000 + Math.floor(Math.random() * 50000)
const paseoHome = await mkdtemp(join(tmpdir(), 'paseo-test-home-')) const paseoHome = await mkdtemp(join(tmpdir(), 'paseo-test-home-'))
@@ -53,87 +53,60 @@ try {
console.log('✓ daemon pair prints local pairing URL\n') console.log('✓ daemon pair prints local pairing URL\n')
} }
// Test 3: daemon status fails gracefully when daemon not running // Test 3: daemon status reports stopped when daemon not running
{ {
console.log('Test 3: daemon status fails gracefully when not running') console.log('Test 3: daemon status reports stopped when not running')
const result = const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo daemon status`.nothrow() await $`PASEO_HOME=${paseoHome} npx paseo daemon status`.nothrow()
assert.notStrictEqual(result.exitCode, 0, 'should fail when daemon not running') assert.strictEqual(result.exitCode, 0, 'status should succeed when daemon is stopped')
// The error should mention something about daemon not running or connection const output = result.stdout.toLowerCase()
const output = result.stdout + result.stderr assert(output.includes('status'), 'status table should include Status row')
const hasDaemonError = assert(output.includes('stopped'), 'status should report stopped')
output.toLowerCase().includes('daemon') || console.log('✓ daemon status reports stopped when not running\n')
output.toLowerCase().includes('connect') ||
output.toLowerCase().includes('running')
assert(hasDaemonError, 'error message should mention daemon/connect/running')
console.log('✓ daemon status fails gracefully when not running\n')
} }
// Test 4: daemon status --json outputs valid JSON (even for errors) // Test 4: daemon status --json outputs valid JSON
{ {
console.log('Test 4: daemon status --json outputs JSON') console.log('Test 4: daemon status --json outputs JSON')
const result = const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo daemon status --json`.nothrow() await $`PASEO_HOME=${paseoHome} npx paseo daemon status --json`.nothrow()
// Should still fail (daemon not running) assert.strictEqual(result.exitCode, 0, '--json status should succeed')
assert.notStrictEqual(result.exitCode, 0, 'should fail when daemon not running') const status = JSON.parse(result.stdout)
// But output should be valid JSON assert.strictEqual(status.status, 'stopped', 'json status should report stopped')
const output = result.stdout.trim() assert.strictEqual(status.home, paseoHome, 'json status should reflect the isolated home')
if (output.length > 0) { console.log('✓ daemon status --json outputs valid JSON\n')
try {
JSON.parse(output)
console.log('✓ daemon status --json outputs valid JSON\n')
} catch {
// If stdout is empty, check if stderr has the error (acceptable for now)
console.log('✓ daemon status --json handled error (output may be in stderr)\n')
}
} else {
// Empty stdout is acceptable if error is in stderr
console.log('✓ daemon status --json handled error gracefully\n')
}
} }
// Test 5: daemon stop handles daemon not running gracefully // Test 5: daemon stop handles daemon not running gracefully
{ {
console.log('Test 5: daemon stop handles daemon not running') console.log('Test 5: daemon stop handles daemon not running')
const result = const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo daemon stop`.nothrow() await $`PASEO_HOME=${paseoHome} npx paseo daemon stop`.nothrow()
// Stop should succeed even if daemon not running (idempotent) // Stop should succeed even if daemon is not running (idempotent).
// OR it should fail gracefully with a clear message assert.strictEqual(result.exitCode, 0, 'stop should succeed when daemon not running')
const output = result.stdout + result.stderr const output = result.stdout + result.stderr
if (result.exitCode === 0) { const mentionsNotRunning =
// If it succeeds, it should mention the daemon wasn't running output.toLowerCase().includes('not running') ||
const mentionsNotRunning = output.toLowerCase().includes('was not running')
output.toLowerCase().includes('not running') || assert(mentionsNotRunning, 'output should mention daemon was not running')
output.toLowerCase().includes('was not running') console.log('✓ daemon stop succeeds gracefully when daemon not running\n')
assert(mentionsNotRunning, 'success output should mention daemon was not running')
console.log('✓ daemon stop succeeds gracefully when daemon not running\n')
} else {
// If it fails, error should be clear
const hasError =
output.toLowerCase().includes('daemon') ||
output.toLowerCase().includes('connect') ||
output.toLowerCase().includes('not running')
assert(hasError, 'error message should be clear about daemon state')
console.log('✓ daemon stop fails gracefully when daemon not running\n')
}
} }
// Test 6: daemon restart fails when daemon not running // Test 6: daemon restart starts daemon and can be stopped
{ {
console.log('Test 6: daemon restart fails when daemon not running') console.log('Test 6: daemon restart starts daemon and can be stopped')
const result = const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo daemon restart`.nothrow() await $`PASEO_HOME=${paseoHome} npx paseo daemon restart --port ${String(port)}`.nothrow()
// Restart should fail when daemon not running (can't restart something that's not running) assert.strictEqual(result.exitCode, 0, 'restart should succeed even when previously stopped')
assert.notStrictEqual(result.exitCode, 0, 'should fail when daemon not running') assert(result.stdout.toLowerCase().includes('restarted'), 'output should report restart')
const output = result.stdout + result.stderr
const hasError = const cleanup = await $`PASEO_HOME=${paseoHome} npx paseo daemon stop --force`.nothrow()
output.toLowerCase().includes('daemon') || assert.strictEqual(cleanup.exitCode, 0, 'cleanup stop should succeed after restart')
output.toLowerCase().includes('not running') || console.log('✓ daemon restart starts and stop cleanup succeeds\n')
output.toLowerCase().includes('connect')
assert(hasError, 'error message should mention daemon state')
console.log('✓ daemon restart fails appropriately when daemon not running\n')
} }
} finally { } finally {
// Best-effort daemon cleanup in case assertions fail before explicit stop.
await $`PASEO_HOME=${paseoHome} npx paseo daemon stop --force`.nothrow()
// Clean up temp directory // Clean up temp directory
await rm(paseoHome, { recursive: true, force: true }) await rm(paseoHome, { recursive: true, force: true })
} }