doctor.test.ts68 lines · main
| 1 | /** |
| 2 | * Doctor unit tests — focused on the pure logic that doesn't need the |
| 3 | * network. The interactive `runDoctor` orchestration (which fans out |
| 4 | * fetches and renders cli output) is exercised by the post-deploy |
| 5 | * integration suite that lives in infra/. |
| 6 | */ |
| 7 | |
| 8 | import assert from 'node:assert/strict'; |
| 9 | import { describe, test } from 'node:test'; |
| 10 | |
| 11 | describe('realtime origin derivation', () => { |
| 12 | // Mirrors the regex in doctor.ts. Refactor target: extract the helper |
| 13 | // once we add a second consumer; for now the single test pins the |
| 14 | // behaviour we depend on so any change to the api-host pattern shows |
| 15 | // up here. |
| 16 | function deriveRealtime(origin: string): string { |
| 17 | return origin.replace(/:\/\/api\./, '://realtime.'); |
| 18 | } |
| 19 | |
| 20 | test('rewrites api.<domain> → realtime.<domain>', () => { |
| 21 | assert.equal(deriveRealtime('https://api.briven.tech'), 'https://realtime.briven.tech'); |
| 22 | assert.equal( |
| 23 | deriveRealtime('http://api.briven.local:3001'), |
| 24 | 'http://realtime.briven.local:3001', |
| 25 | ); |
| 26 | }); |
| 27 | |
| 28 | test('leaves origins without an api. prefix unchanged', () => { |
| 29 | assert.equal(deriveRealtime('http://localhost:3001'), 'http://localhost:3001'); |
| 30 | assert.equal(deriveRealtime('https://briven.tech'), 'https://briven.tech'); |
| 31 | assert.equal(deriveRealtime('https://my-api.example.com'), 'https://my-api.example.com'); |
| 32 | }); |
| 33 | }); |
| 34 | |
| 35 | describe('boot-time formatting', () => { |
| 36 | function formatBoot(iso: string, nowMs: number): string { |
| 37 | const then = new Date(iso).getTime(); |
| 38 | if (!Number.isFinite(then)) return iso; |
| 39 | const sec = Math.max(0, Math.round((nowMs - then) / 1000)); |
| 40 | if (sec < 60) return `${sec}s ago`; |
| 41 | if (sec < 3600) return `${Math.round(sec / 60)}m ago`; |
| 42 | return `${Math.round(sec / 3600)}h ago`; |
| 43 | } |
| 44 | |
| 45 | const NOW = new Date('2026-05-10T20:00:00Z').getTime(); |
| 46 | |
| 47 | test('seconds-ago window', () => { |
| 48 | assert.equal(formatBoot('2026-05-10T19:59:30Z', NOW), '30s ago'); |
| 49 | assert.equal(formatBoot('2026-05-10T19:59:01Z', NOW), '59s ago'); |
| 50 | }); |
| 51 | |
| 52 | test('minutes-ago window', () => { |
| 53 | assert.equal(formatBoot('2026-05-10T19:55:00Z', NOW), '5m ago'); |
| 54 | assert.equal(formatBoot('2026-05-10T19:01:00Z', NOW), '59m ago'); |
| 55 | }); |
| 56 | |
| 57 | test('hours-ago window', () => { |
| 58 | assert.equal(formatBoot('2026-05-10T15:00:00Z', NOW), '5h ago'); |
| 59 | }); |
| 60 | |
| 61 | test('clamps negative deltas to zero (clock-skew safety)', () => { |
| 62 | assert.equal(formatBoot('2026-05-10T20:01:00Z', NOW), '0s ago'); |
| 63 | }); |
| 64 | |
| 65 | test('returns the raw string for unparseable input (best-effort)', () => { |
| 66 | assert.equal(formatBoot('not-a-date', NOW), 'not-a-date'); |
| 67 | }); |
| 68 | }); |