@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.
- package/package.json +1 -1
- package/src/__snapshots__/cli.test.ts.snap +19 -1
- package/src/cli.ts +2 -0
- package/src/commands/cliproxy/setup/validation.test.ts +168 -0
- package/src/commands/cliproxy/setup/validation.ts +36 -9
- package/src/commands/cliproxy/status.test.ts +368 -38
- package/src/commands/cliproxy/status.ts +168 -41
- package/src/commands/mcp.test.ts +5 -7
- package/src/commands/mcp.ts +3 -0
- package/src/commands/status.test.ts +36 -0
- package/src/commands/status.ts +10 -3
- package/src/commands/umami/deploy.test.ts +202 -0
- package/src/commands/umami/deploy.ts +132 -0
- package/src/commands/umami/host.test.ts +62 -0
- package/src/commands/umami/host.ts +31 -0
- package/src/commands/umami/index.ts +13 -0
- package/src/commands/umami/logs.test.ts +154 -0
- package/src/commands/umami/logs.ts +161 -0
- package/src/commands/umami/status.test.ts +387 -0
- package/src/commands/umami/status.ts +267 -0
|
@@ -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
|
|
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).
|
|
143
|
+
expect(result.summary).toMatch(/idle|recent: 0/)
|
|
147
144
|
})
|
|
148
145
|
|
|
149
|
-
it('returns ok
|
|
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).
|
|
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
|
|
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
|
-
|
|
169
|
-
|
|
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
|
|
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: '
|
|
270
|
+
summary: 'recent: 10',
|
|
266
271
|
})
|
|
267
272
|
|
|
268
|
-
expect(result).toBe('
|
|
273
|
+
expect(result).toBe('Recent requests: 10')
|
|
269
274
|
})
|
|
270
275
|
|
|
271
|
-
it('formats
|
|
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: '
|
|
280
|
+
summary: 'recent: 10, errors: 3',
|
|
276
281
|
})
|
|
277
282
|
|
|
278
|
-
expect(result).toBe('
|
|
283
|
+
expect(result).toBe('Recent requests: 10, 3 errors')
|
|
279
284
|
})
|
|
280
285
|
|
|
281
|
-
it('
|
|
286
|
+
it('formats an idle recent summary', () => {
|
|
282
287
|
const result = formatUsageSummaryLine({
|
|
283
288
|
title: 'Usage stats',
|
|
284
|
-
level: '
|
|
285
|
-
summary: '
|
|
289
|
+
level: 'ok',
|
|
290
|
+
summary: 'recent: 0 (idle)',
|
|
286
291
|
})
|
|
287
292
|
|
|
288
|
-
expect(result).
|
|
293
|
+
expect(result).toBe('Recent requests: 0')
|
|
289
294
|
})
|
|
290
295
|
|
|
291
|
-
it('returns null
|
|
296
|
+
it('returns null when summary has no recent-activity field', () => {
|
|
292
297
|
const result = formatUsageSummaryLine({
|
|
293
298
|
title: 'Usage stats',
|
|
294
|
-
level: '
|
|
295
|
-
summary: '
|
|
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('
|
|
306
|
+
it('returns null for error summaries without a recent field', () => {
|
|
302
307
|
const result = formatUsageSummaryLine({
|
|
303
308
|
title: 'Usage stats',
|
|
304
|
-
level: '
|
|
305
|
-
summary: '
|
|
309
|
+
level: 'error',
|
|
310
|
+
summary: 'Unable to read usage stats: socket hang up',
|
|
306
311
|
})
|
|
307
312
|
|
|
308
|
-
expect(result).
|
|
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
|
+
})
|