health.test.ts101 lines · main
| 1 | /** |
| 2 | * /info + /ready contract tests for apps/api — pin the readiness gate |
| 3 | * logic so the matrix of (configured? × reachable?) stays testable |
| 4 | * without standing up postgres / redis. Build-identity resolution is |
| 5 | * exercised by packages/shared/src/build-identity.test.ts since the |
| 6 | * resolver lives there now. |
| 7 | */ |
| 8 | |
| 9 | import { describe, expect, test } from 'bun:test'; |
| 10 | |
| 11 | describe('/ready check states', () => { |
| 12 | // Mirrors the ready endpoint's check projection. Decoupling the |
| 13 | // pure logic here makes the matrix of (configured? × reachable?) |
| 14 | // testable without standing up postgres / redis. |
| 15 | function checkState(args: { |
| 16 | configured: boolean; |
| 17 | reachable: boolean; |
| 18 | }): 'ok' | 'unreachable' | 'not_configured' { |
| 19 | if (!args.configured) return 'not_configured'; |
| 20 | return args.reachable ? 'ok' : 'unreachable'; |
| 21 | } |
| 22 | |
| 23 | test('configured + reachable → ok', () => { |
| 24 | expect(checkState({ configured: true, reachable: true })).toBe('ok'); |
| 25 | }); |
| 26 | |
| 27 | test('configured + unreachable → unreachable', () => { |
| 28 | expect(checkState({ configured: true, reachable: false })).toBe('unreachable'); |
| 29 | }); |
| 30 | |
| 31 | test('not configured → not_configured regardless of reachable', () => { |
| 32 | expect(checkState({ configured: false, reachable: false })).toBe('not_configured'); |
| 33 | expect(checkState({ configured: false, reachable: true })).toBe('not_configured'); |
| 34 | }); |
| 35 | }); |
| 36 | |
| 37 | describe('redis-required readiness gate', () => { |
| 38 | // Redis is required when configured (logs streaming + rate-limit |
| 39 | // hard-depend on it) but optional in dev mode. The /ready endpoint |
| 40 | // 503s only when configured-AND-unreachable; an unconfigured deploy |
| 41 | // ack 200 to let dev workflows keep moving. |
| 42 | function isReady(args: { |
| 43 | controlOk: boolean; |
| 44 | dataOk: boolean; |
| 45 | runtimeOk: boolean; |
| 46 | redisConfigured: boolean; |
| 47 | redisOk: boolean; |
| 48 | }): boolean { |
| 49 | const required = |
| 50 | args.controlOk && args.dataOk && args.runtimeOk && (!args.redisConfigured || args.redisOk); |
| 51 | return required; |
| 52 | } |
| 53 | |
| 54 | test('all healthy + redis configured ok → ready', () => { |
| 55 | expect( |
| 56 | isReady({ |
| 57 | controlOk: true, |
| 58 | dataOk: true, |
| 59 | runtimeOk: true, |
| 60 | redisConfigured: true, |
| 61 | redisOk: true, |
| 62 | }), |
| 63 | ).toBe(true); |
| 64 | }); |
| 65 | |
| 66 | test('redis unconfigured + everything else ok → ready (dev mode)', () => { |
| 67 | expect( |
| 68 | isReady({ |
| 69 | controlOk: true, |
| 70 | dataOk: true, |
| 71 | runtimeOk: true, |
| 72 | redisConfigured: false, |
| 73 | redisOk: false, |
| 74 | }), |
| 75 | ).toBe(true); |
| 76 | }); |
| 77 | |
| 78 | test('redis configured + unreachable → NOT ready', () => { |
| 79 | expect( |
| 80 | isReady({ |
| 81 | controlOk: true, |
| 82 | dataOk: true, |
| 83 | runtimeOk: true, |
| 84 | redisConfigured: true, |
| 85 | redisOk: false, |
| 86 | }), |
| 87 | ).toBe(false); |
| 88 | }); |
| 89 | |
| 90 | test('control plane down → NOT ready regardless of redis', () => { |
| 91 | expect( |
| 92 | isReady({ |
| 93 | controlOk: false, |
| 94 | dataOk: true, |
| 95 | runtimeOk: true, |
| 96 | redisConfigured: false, |
| 97 | redisOk: false, |
| 98 | }), |
| 99 | ).toBe(false); |
| 100 | }); |
| 101 | }); |