@marcusrbrown/infra 0.8.1 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,7 @@ import {
8
8
  cliproxyStatusAction,
9
9
  formatDurationMs,
10
10
  formatUsageSummaryLine,
11
+ getCliproxyStatusSummary,
11
12
  levelLabel,
12
13
  stripTrailingSlash,
13
14
  toNumber,
@@ -131,50 +132,54 @@ describe('cliproxy status helpers', () => {
131
132
  })
132
133
 
133
134
  describe('checkUsageStats', () => {
134
- it('returns ok when failures are zero (nested usage object)', async () => {
135
+ it('returns ok for empty array (idle)', async () => {
135
136
  globalThis.fetch = createFetchImplementation(
136
- async () =>
137
- new Response(
138
- JSON.stringify({failed_requests: 0, usage: {total_requests: 10, failure_count: 0, success_count: 10}}),
139
- {status: 200, headers: {'content-type': 'application/json'}},
140
- ),
137
+ async () => new Response('[]', {status: 200, headers: {'content-type': 'application/json'}}),
141
138
  )
142
139
 
143
140
  const result = await checkUsageStats('https://cliproxy.example.com', 'secret')
144
141
 
145
142
  expect(result.level).toBe('ok')
146
- expect(result.summary).toBe('total_requests=10, failure_count=0')
143
+ expect(result.summary).toMatch(/idle|recent: 0/)
147
144
  })
148
145
 
149
- it('returns ok with flat payload (backwards compat)', async () => {
146
+ it('returns ok for all-success queue array', async () => {
147
+ const queue = [{status: 200}, {status: 201}, {status: 200}]
150
148
  globalThis.fetch = createFetchImplementation(
151
- async () =>
152
- new Response(JSON.stringify({total_requests: 10, failure_count: 0}), {
153
- status: 200,
154
- headers: {'content-type': 'application/json'},
155
- }),
149
+ async () => new Response(JSON.stringify(queue), {status: 200, headers: {'content-type': 'application/json'}}),
156
150
  )
157
151
 
158
152
  const result = await checkUsageStats('https://cliproxy.example.com', 'secret')
159
153
 
160
154
  expect(result.level).toBe('ok')
161
- expect(result.summary).toBe('total_requests=10, failure_count=0')
155
+ expect(result.summary).toContain('recent: 3')
156
+ })
157
+
158
+ it('returns warning for queue with error-status records', async () => {
159
+ const queue = [{status: 200}, {status: 500}, {status: 200}]
160
+ globalThis.fetch = createFetchImplementation(
161
+ async () => new Response(JSON.stringify(queue), {status: 200, headers: {'content-type': 'application/json'}}),
162
+ )
163
+
164
+ const result = await checkUsageStats('https://cliproxy.example.com', 'secret')
165
+
166
+ expect(result.level).toBe('warning')
167
+ expect(result.summary).toContain('recent: 3')
168
+ expect(result.summary).toContain('errors: 1')
162
169
  })
163
170
 
164
- it('returns warning when token refresh is likely needed', async () => {
171
+ it('returns warning when usage-queue returns a non-array object', async () => {
165
172
  globalThis.fetch = createFetchImplementation(
166
173
  async () =>
167
- new Response(
168
- JSON.stringify({failed_requests: 3, usage: {total_requests: 10, failure_count: 3, success_count: 7}}),
169
- {status: 200, headers: {'content-type': 'application/json'}},
170
- ),
174
+ new Response(JSON.stringify({unexpected: 'object'}), {
175
+ status: 200,
176
+ headers: {'content-type': 'application/json'},
177
+ }),
171
178
  )
172
179
 
173
180
  const result = await checkUsageStats('https://cliproxy.example.com', 'secret')
174
181
 
175
182
  expect(result.level).toBe('warning')
176
- expect(result.summary).toContain('total_requests=10, failure_count=3')
177
- expect(result.summary).toContain('token refresh likely needed')
178
183
  })
179
184
 
180
185
  it('returns warning when rate limited', async () => {
@@ -258,54 +263,54 @@ describe('cliproxy status helpers', () => {
258
263
  })
259
264
 
260
265
  describe('formatUsageSummaryLine', () => {
261
- it('formats zero-failure summary', () => {
266
+ it('formats a recent-activity summary with no errors', () => {
262
267
  const result = formatUsageSummaryLine({
263
268
  title: 'Usage stats',
264
269
  level: 'ok',
265
- summary: 'total_requests=10, failure_count=0',
270
+ summary: 'recent: 10',
266
271
  })
267
272
 
268
- expect(result).toBe('Requests: 10 total, 0 failed (0.0% failure rate)')
273
+ expect(result).toBe('Recent requests: 10')
269
274
  })
270
275
 
271
- it('formats non-zero failure summary with correct rate', () => {
276
+ it('formats a recent-activity summary with errors appended', () => {
272
277
  const result = formatUsageSummaryLine({
273
278
  title: 'Usage stats',
274
279
  level: 'warning',
275
- summary: 'total_requests=10, failure_count=3 (token refresh likely needed)',
280
+ summary: 'recent: 10, errors: 3',
276
281
  })
277
282
 
278
- expect(result).toBe('Requests: 10 total, 3 failed (30.0% failure rate)')
283
+ expect(result).toBe('Recent requests: 10, 3 errors')
279
284
  })
280
285
 
281
- it('returns null when summary has no numeric fields', () => {
286
+ it('formats an idle recent summary', () => {
282
287
  const result = formatUsageSummaryLine({
283
288
  title: 'Usage stats',
284
- level: 'warning',
285
- summary: 'Rate limited by management API (HTTP 429). Retry in a few moments.',
289
+ level: 'ok',
290
+ summary: 'recent: 0 (idle)',
286
291
  })
287
292
 
288
- expect(result).toBeNull()
293
+ expect(result).toBe('Recent requests: 0')
289
294
  })
290
295
 
291
- it('returns null for error summaries without numeric fields', () => {
296
+ it('returns null when summary has no recent-activity field', () => {
292
297
  const result = formatUsageSummaryLine({
293
298
  title: 'Usage stats',
294
- level: 'error',
295
- summary: 'Unable to read usage stats: socket hang up',
299
+ level: 'warning',
300
+ summary: 'Rate limited by management API (HTTP 429). Retry in a few moments.',
296
301
  })
297
302
 
298
303
  expect(result).toBeNull()
299
304
  })
300
305
 
301
- it('handles zero total requests without division by zero', () => {
306
+ it('returns null for error summaries without a recent field', () => {
302
307
  const result = formatUsageSummaryLine({
303
308
  title: 'Usage stats',
304
- level: 'ok',
305
- summary: 'total_requests=0, failure_count=0',
309
+ level: 'error',
310
+ summary: 'Unable to read usage stats: socket hang up',
306
311
  })
307
312
 
308
- expect(result).toBe('Requests: 0 total, 0 failed (0.0% failure rate)')
313
+ expect(result).toBeNull()
309
314
  })
310
315
  })
311
316
  })
@@ -387,3 +392,328 @@ describe('cliproxyStatusAction (Tier-2 ctx capture)', () => {
387
392
  }
388
393
  })
389
394
  })
395
+
396
+ describe('usage-queue migration', () => {
397
+ afterEach(() => {
398
+ globalThis.fetch = originalFetch
399
+ })
400
+
401
+ it('populated usage-queue returns recent-activity summary with correct total and error count', async () => {
402
+ const queue = [
403
+ {status: 200, model: 'claude-3-5-sonnet'},
404
+ {status: 500, model: 'claude-3-5-sonnet'},
405
+ {status: 200, model: 'claude-3-5-sonnet'},
406
+ ]
407
+ globalThis.fetch = createFetchImplementation(async url => {
408
+ if (url.includes('/v0/management/usage-queue')) {
409
+ return new Response(JSON.stringify(queue), {status: 200, headers: {'content-type': 'application/json'}})
410
+ }
411
+
412
+ throw new Error(`Unexpected fetch: ${url}`)
413
+ })
414
+
415
+ const result = await checkUsageStats('https://cliproxy.example.com', 'secret')
416
+
417
+ expect(result.level).not.toBe('error')
418
+ expect(result.summary).toContain('recent: 3')
419
+ expect(result.summary).toContain('errors: 1')
420
+ })
421
+
422
+ it('empty usage-queue returns ok/idle result, not an error', async () => {
423
+ globalThis.fetch = createFetchImplementation(async url => {
424
+ if (url.includes('/v0/management/usage-queue')) {
425
+ return new Response('[]', {status: 200, headers: {'content-type': 'application/json'}})
426
+ }
427
+
428
+ throw new Error(`Unexpected fetch: ${url}`)
429
+ })
430
+
431
+ const result = await checkUsageStats('https://cliproxy.example.com', 'secret')
432
+
433
+ expect(result.level).toBe('ok')
434
+ expect(result.summary).toMatch(/idle|recent: 0/)
435
+ })
436
+
437
+ it('malformed usage-queue response returns warning, not error, and does not throw', async () => {
438
+ globalThis.fetch = createFetchImplementation(async url => {
439
+ if (url.includes('/v0/management/usage-queue')) {
440
+ return new Response('{"not":"an-array"}', {status: 200, headers: {'content-type': 'application/json'}})
441
+ }
442
+
443
+ throw new Error(`Unexpected fetch: ${url}`)
444
+ })
445
+
446
+ const result = await checkUsageStats('https://cliproxy.example.com', 'secret')
447
+
448
+ expect(result.level).toBe('warning')
449
+ })
450
+
451
+ it('formatUsageSummaryLine returns a human-friendly line for recent-window summary', () => {
452
+ const result = formatUsageSummaryLine({
453
+ title: 'Usage stats',
454
+ level: 'ok',
455
+ summary: 'recent: 5, errors: 1',
456
+ })
457
+
458
+ expect(result).not.toBeNull()
459
+ expect(result).toContain('5')
460
+ })
461
+ })
462
+
463
+ describe('management auth failure surfaces in unified summary (FIX 4)', () => {
464
+ afterEach(() => {
465
+ globalThis.fetch = originalFetch
466
+ })
467
+
468
+ it('getCliproxyStatusSummary with a bad key (401) shows auth failure in version and usageStats, not "— (no key)"', async () => {
469
+ globalThis.fetch = createFetchImplementation(async (url, init) => {
470
+ const hdrs = init?.headers
471
+ const hasKey =
472
+ hdrs instanceof Headers
473
+ ? hdrs.has('x-management-key')
474
+ : hdrs !== null && hdrs !== undefined && typeof hdrs === 'object' && 'x-management-key' in hdrs
475
+ if (url.includes('/v0/management/') && hasKey) {
476
+ return new Response('Unauthorized', {status: 401})
477
+ }
478
+
479
+ return new Response('ok', {status: 200})
480
+ })
481
+
482
+ const summary = await getCliproxyStatusSummary('https://cliproxy.example.com', 'bad-key', false)
483
+
484
+ // Must NOT show the no-key sentinel — a bad key is distinct from no key
485
+ expect(summary.version).not.toBe('— (no key)')
486
+ expect(summary.usageStats).not.toBe('— (no key)')
487
+ // Must contain some error/auth indicator
488
+ expect(summary.version.toLowerCase()).toMatch(/error|auth|401|management|unauthorized/i)
489
+ expect(summary.usageStats.toLowerCase()).toMatch(/error|auth|401|management|unauthorized/i)
490
+ })
491
+ })
492
+
493
+ describe('ban body word-boundary detection (FIX 5)', () => {
494
+ afterEach(() => {
495
+ globalThis.fetch = originalFetch
496
+ })
497
+
498
+ it('403 with body {detail:"bandwidth exceeded"} is NOT treated as a ban (generic 403)', async () => {
499
+ globalThis.fetch = createFetchImplementation(async url => {
500
+ if (url.includes('/v0/management/config')) {
501
+ return new Response(JSON.stringify({detail: 'bandwidth exceeded'}), {
502
+ status: 403,
503
+ headers: {'content-type': 'application/json'},
504
+ })
505
+ }
506
+
507
+ return new Response('ok', {status: 200})
508
+ })
509
+
510
+ const {ctx, captured} = createCapturedCtx()
511
+ try {
512
+ await cliproxyStatusAction({url: 'https://cliproxy.example.com', key: 'any-key'}, ctx)
513
+ } catch (error) {
514
+ if (!(error instanceof MockProcessExit)) throw error
515
+ }
516
+
517
+ const output = [...captured.stdout, ...captured.stderr].join('\n')
518
+ expect(output.toLowerCase()).not.toMatch(/ip.?ban/)
519
+ })
520
+
521
+ it('403 with body {error:"IP banned"} IS treated as a ban', async () => {
522
+ globalThis.fetch = createFetchImplementation(async url => {
523
+ if (url.includes('/v0/management/config')) {
524
+ return new Response(JSON.stringify({error: 'IP banned'}), {
525
+ status: 403,
526
+ headers: {'content-type': 'application/json'},
527
+ })
528
+ }
529
+
530
+ return new Response('ok', {status: 200})
531
+ })
532
+
533
+ const {ctx, captured} = createCapturedCtx()
534
+ try {
535
+ await cliproxyStatusAction({url: 'https://cliproxy.example.com', key: 'any-key'}, ctx)
536
+ } catch (error) {
537
+ if (!(error instanceof MockProcessExit)) throw error
538
+ }
539
+
540
+ const output = [...captured.stdout, ...captured.stderr].join('\n')
541
+ expect(output.toLowerCase()).toMatch(/ip.?ban/)
542
+ })
543
+ })
544
+
545
+ describe('reachability probe targets /healthz liveness endpoint', () => {
546
+ afterEach(() => {
547
+ globalThis.fetch = originalFetch
548
+ })
549
+
550
+ it('cliproxyStatusAction reports HTTP reachable when /healthz returns 200 and bare base returns 404', async () => {
551
+ const BASE = 'https://cliproxy.example.com'
552
+ globalThis.fetch = createFetchImplementation(async url => {
553
+ if (url === `${BASE}/healthz`) return new Response('{"status":"ok"}', {status: 200})
554
+ if (url === BASE) return new Response('Not Found', {status: 404})
555
+ return new Response('ok', {status: 200})
556
+ })
557
+
558
+ const {ctx, captured} = createCapturedCtx()
559
+ await cliproxyStatusAction({url: BASE}, ctx)
560
+
561
+ const output = [...captured.stdout, ...captured.stderr].join('\n')
562
+ expect(output).toContain('OK')
563
+ expect(output).not.toMatch(/ERROR.*HTTP reachability|HTTP reachability.*ERROR/)
564
+ })
565
+
566
+ it('getCliproxyStatusSummary reports http ok when /healthz returns 200 and bare base returns 404', async () => {
567
+ const BASE = 'https://cliproxy.example.com'
568
+ globalThis.fetch = createFetchImplementation(async url => {
569
+ if (url === `${BASE}/healthz`) return new Response('{"status":"ok"}', {status: 200})
570
+ if (url === BASE) return new Response('Not Found', {status: 404})
571
+ return new Response('ok', {status: 200})
572
+ })
573
+
574
+ const summary = await getCliproxyStatusSummary(BASE, '', false)
575
+
576
+ expect(summary.http).toMatch(/^OK/)
577
+ })
578
+ })
579
+
580
+ describe('management auth probe (ban-awareness)', () => {
581
+ afterEach(() => {
582
+ globalThis.fetch = originalFetch
583
+ })
584
+
585
+ it('bad management key causes at most one management fetch and skips version+usage calls', async () => {
586
+ const managementFetchUrls: string[] = []
587
+
588
+ globalThis.fetch = createFetchImplementation(async (url, init) => {
589
+ const hdrs = init?.headers
590
+ const hasManagementKey =
591
+ hdrs instanceof Headers
592
+ ? hdrs.has('x-management-key')
593
+ : hdrs !== null && hdrs !== undefined && typeof hdrs === 'object' && 'x-management-key' in hdrs
594
+ if (url.includes('/v0/management/') && hasManagementKey) {
595
+ managementFetchUrls.push(url)
596
+ return new Response('Unauthorized', {status: 401})
597
+ }
598
+
599
+ if (url.includes('/healthz') || !url.includes('/v0/management/')) {
600
+ return new Response('ok', {status: 200})
601
+ }
602
+
603
+ throw new Error(`Unexpected fetch: ${url}`)
604
+ })
605
+
606
+ const {ctx} = createCapturedCtx()
607
+ // Auth failure yields error-level result → exit(1) → MockProcessExit thrown
608
+ try {
609
+ await cliproxyStatusAction({url: 'https://cliproxy.example.com', key: 'bad-key'}, ctx)
610
+ } catch (error) {
611
+ if (!(error instanceof MockProcessExit)) throw error
612
+ }
613
+
614
+ expect(managementFetchUrls.length).toBe(1)
615
+ expect(managementFetchUrls[0]).toContain('/v0/management/config')
616
+ })
617
+
618
+ it('403 with ban body surfaces a distinct IP-banned message', async () => {
619
+ globalThis.fetch = createFetchImplementation(async url => {
620
+ if (url.includes('/v0/management/config')) {
621
+ return new Response(JSON.stringify({error: 'IP banned'}), {
622
+ status: 403,
623
+ headers: {'content-type': 'application/json'},
624
+ })
625
+ }
626
+
627
+ return new Response('ok', {status: 200})
628
+ })
629
+
630
+ const {ctx, captured} = createCapturedCtx()
631
+ // 403+ban → error level → exit(1)
632
+ try {
633
+ await cliproxyStatusAction({url: 'https://cliproxy.example.com', key: 'any-key'}, ctx)
634
+ } catch (error) {
635
+ if (!(error instanceof MockProcessExit)) throw error
636
+ }
637
+
638
+ const output = [...captured.stdout, ...captured.stderr].join('\n')
639
+ expect(output.toLowerCase()).toMatch(/ip.?ban/)
640
+ })
641
+
642
+ it('auth error message does not contain the management key value', async () => {
643
+ const secretKey = 'super-secret-mgmt-key-12345'
644
+
645
+ globalThis.fetch = createFetchImplementation(async url => {
646
+ if (url.includes('/v0/management/config')) {
647
+ return new Response('Unauthorized', {status: 401})
648
+ }
649
+
650
+ return new Response('ok', {status: 200})
651
+ })
652
+
653
+ const {ctx, captured} = createCapturedCtx()
654
+ // 401 → error level → exit(1)
655
+ try {
656
+ await cliproxyStatusAction({url: 'https://cliproxy.example.com', key: secretKey}, ctx)
657
+ } catch (error) {
658
+ if (!(error instanceof MockProcessExit)) throw error
659
+ }
660
+
661
+ const allOutput = [...captured.stdout, ...captured.stderr].join('\n')
662
+ expect(allOutput).not.toContain(secretKey)
663
+ })
664
+
665
+ it('successful probe allows version and usage checks to proceed in parallel', async () => {
666
+ const fetchedUrls: string[] = []
667
+
668
+ globalThis.fetch = createFetchImplementation(async url => {
669
+ fetchedUrls.push(url)
670
+ if (url.includes('/v0/management/config')) {
671
+ return new Response(JSON.stringify({config: 'ok'}), {
672
+ status: 200,
673
+ headers: {'content-type': 'application/json'},
674
+ })
675
+ }
676
+
677
+ if (url.includes('/v0/management/usage-queue')) {
678
+ return new Response('[]', {status: 200, headers: {'content-type': 'application/json'}})
679
+ }
680
+
681
+ if (url.includes('/v0/management/latest-version')) {
682
+ return new Response(JSON.stringify({'latest-version': 'v7.1.31'}), {
683
+ status: 200,
684
+ headers: {'content-type': 'application/json'},
685
+ })
686
+ }
687
+
688
+ return new Response('ok', {status: 200})
689
+ })
690
+
691
+ const {ctx} = createCapturedCtx()
692
+ await cliproxyStatusAction({url: 'https://cliproxy.example.com', key: 'valid-key'}, ctx)
693
+
694
+ expect(fetchedUrls.some(u => u.includes('/v0/management/latest-version'))).toBe(true)
695
+ expect(fetchedUrls.some(u => u.includes('/v0/management/usage-queue'))).toBe(true)
696
+ })
697
+
698
+ it('getCliproxyStatusSummary with bad key fires only one management fetch', async () => {
699
+ const managementFetchUrls: string[] = []
700
+
701
+ globalThis.fetch = createFetchImplementation(async (url, init) => {
702
+ const hdrs = init?.headers
703
+ const hasKey =
704
+ hdrs instanceof Headers
705
+ ? hdrs.has('x-management-key')
706
+ : hdrs !== null && hdrs !== undefined && typeof hdrs === 'object' && 'x-management-key' in hdrs
707
+ if (url.includes('/v0/management/') && hasKey) {
708
+ managementFetchUrls.push(url)
709
+ return new Response('Unauthorized', {status: 401})
710
+ }
711
+
712
+ return new Response('ok', {status: 200})
713
+ })
714
+
715
+ await getCliproxyStatusSummary('https://cliproxy.example.com', 'bad-key', false)
716
+
717
+ expect(managementFetchUrls.length).toBe(1)
718
+ })
719
+ })