diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-03-13 03:17:04 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-03-13 03:17:04 +0000 |
| commit | fe414fac958330c2302d9175d66e1b338e5b1864 (patch) | |
| tree | ef4941f5d01e84e7868e6b92bd0e6cecdcc2a64f /web/test | |
| parent | d5f83f8662c9f9c0fb52b206b06d4dd54a7788b4 (diff) | |
| parent | 55c20922cc7a671787fe94fdd53a7eb72ebd2596 (diff) | |
merge: resolve conflicts with local/master (stats tab + summary styles)
Keep file-based summary approach (CLAUDOMATOR_SUMMARY_FILE) from HEAD.
Combine Q&A History and Stats tab CSS from both branches.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'web/test')
| -rw-r--r-- | web/test/stats.test.mjs | 153 |
1 files changed, 153 insertions, 0 deletions
diff --git a/web/test/stats.test.mjs b/web/test/stats.test.mjs new file mode 100644 index 0000000..a7fe657 --- /dev/null +++ b/web/test/stats.test.mjs @@ -0,0 +1,153 @@ +// stats.test.mjs — Unit tests for stats tab computation functions. +// +// Run with: node --test web/test/stats.test.mjs + +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { computeTaskStats, computeExecutionStats } from '../app.js'; + +// ── Helpers ──────────────────────────────────────────────────────────────────── + +function makeTask(state) { + return { id: state, name: `task-${state}`, state }; +} + +function makeExec(state, costUSD = 0, durationMs = null) { + const started = new Date('2024-01-01T10:00:00Z'); + const finished = durationMs != null + ? new Date(started.getTime() + durationMs).toISOString() + : null; + return { + id: `exec-${Math.random()}`, + task_id: 'task-1', + task_name: 'Test Task', + state, + started_at: started.toISOString(), + finished_at: finished, + duration_ms: durationMs, + cost_usd: costUSD, + exit_code: state === 'completed' ? 0 : 1, + }; +} + +// ── computeTaskStats ─────────────────────────────────────────────────────────── + +describe('computeTaskStats', () => { + it('groups tasks by state', () => { + const tasks = [ + makeTask('RUNNING'), makeTask('RUNNING'), + makeTask('PENDING'), + makeTask('COMPLETED'), makeTask('COMPLETED'), makeTask('COMPLETED'), + ]; + const stats = computeTaskStats(tasks); + assert.equal(stats.byState.RUNNING, 2); + assert.equal(stats.byState.PENDING, 1); + assert.equal(stats.byState.COMPLETED, 3); + }); + + it('only includes states that have tasks', () => { + const tasks = [makeTask('RUNNING')]; + const stats = computeTaskStats(tasks); + assert.equal(stats.byState.RUNNING, 1); + assert.equal(stats.byState.PENDING, undefined); + assert.equal(stats.byState.COMPLETED, undefined); + }); + + it('returns empty byState for empty task list', () => { + const stats = computeTaskStats([]); + assert.deepEqual(stats.byState, {}); + }); + + it('counts all distinct states correctly', () => { + const states = ['PENDING', 'QUEUED', 'RUNNING', 'READY', 'COMPLETED', + 'FAILED', 'TIMED_OUT', 'CANCELLED', 'BUDGET_EXCEEDED', 'BLOCKED']; + const tasks = states.map(makeTask); + const stats = computeTaskStats(tasks); + for (const s of states) { + assert.equal(stats.byState[s], 1, `expected count 1 for state ${s}`); + } + }); +}); + +// ── computeExecutionStats ────────────────────────────────────────────────────── + +describe('computeExecutionStats', () => { + it('returns zeros for empty executions', () => { + const stats = computeExecutionStats([]); + assert.equal(stats.total, 0); + assert.equal(stats.successRate, 0); + assert.equal(stats.totalCostUSD, 0); + assert.equal(stats.avgDurationMs, null); + assert.deepEqual(stats.byOutcome, {}); + }); + + it('calculates total correctly', () => { + const execs = [makeExec('completed'), makeExec('failed'), makeExec('cancelled')]; + const stats = computeExecutionStats(execs); + assert.equal(stats.total, 3); + }); + + it('calculates success rate as fraction of completed out of total', () => { + const execs = [ + makeExec('completed'), makeExec('completed'), makeExec('completed'), + makeExec('failed'), + ]; + const stats = computeExecutionStats(execs); + assert.equal(stats.successRate, 0.75); + }); + + it('returns success rate 0 when all executions failed', () => { + const execs = [makeExec('failed'), makeExec('failed')]; + const stats = computeExecutionStats(execs); + assert.equal(stats.successRate, 0); + }); + + it('returns success rate 1 when all executions completed', () => { + const execs = [makeExec('completed'), makeExec('completed')]; + const stats = computeExecutionStats(execs); + assert.equal(stats.successRate, 1); + }); + + it('sums total cost correctly', () => { + const execs = [makeExec('completed', 0.5), makeExec('completed', 0.25), makeExec('failed', 0.1)]; + const stats = computeExecutionStats(execs); + assert.ok(Math.abs(stats.totalCostUSD - 0.85) < 0.0001, `expected 0.85, got ${stats.totalCostUSD}`); + }); + + it('calculates average duration from executions with duration_ms', () => { + const execs = [ + makeExec('completed', 0, 60000), // 1 min + makeExec('completed', 0, 120000), // 2 min + makeExec('failed', 0, 30000), // 30 sec + ]; + const stats = computeExecutionStats(execs); + assert.equal(stats.avgDurationMs, 70000); // (60000+120000+30000)/3 + }); + + it('ignores executions without duration_ms in avg calculation', () => { + const execs = [ + makeExec('completed', 0, 60000), + makeExec('running', 0, null), // still running, no duration + ]; + const stats = computeExecutionStats(execs); + assert.equal(stats.avgDurationMs, 60000); + }); + + it('returns null avgDurationMs when no executions have duration_ms', () => { + const execs = [makeExec('running', 0, null)]; + const stats = computeExecutionStats(execs); + assert.equal(stats.avgDurationMs, null); + }); + + it('groups executions by outcome in byOutcome', () => { + const execs = [ + makeExec('completed'), makeExec('completed'), + makeExec('failed'), + makeExec('cancelled'), + ]; + const stats = computeExecutionStats(execs); + assert.equal(stats.byOutcome.completed, 2); + assert.equal(stats.byOutcome.failed, 1); + assert.equal(stats.byOutcome.cancelled, 1); + }); +}); |
