badgr-cli 1.0.37 → 1.0.39

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.
@@ -498,3 +498,168 @@ describe('input validation for serve', () => {
498
498
  expect(process.exitCode).toBe(1);
499
499
  });
500
500
  });
501
+
502
+ // ─────────────────────────────────────────────────────────────────────────────
503
+ // 11. llama.cpp runtime (--runtime llama.cpp --gguf)
504
+ // ─────────────────────────────────────────────────────────────────────────────
505
+
506
+ describe('--runtime llama.cpp', () => {
507
+ const HF_REPO = 'HauhauCS/Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive';
508
+ const HF_FILE = 'Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-Q4_K_M.gguf';
509
+
510
+ it('errors when --hf-repo or --hf-file are missing', async () => {
511
+ await serveCommand(config, ['--runtime', 'llama.cpp', '--max-cost', '10'], chalk);
512
+ expect(process.exitCode).toBe(1);
513
+ expect(api.callApi).not.toHaveBeenCalled();
514
+ });
515
+
516
+ it('errors when only --hf-repo is given (missing --hf-file)', async () => {
517
+ await serveCommand(config, ['--runtime', 'llama.cpp', '--hf-repo', HF_REPO, '--max-cost', '10'], chalk);
518
+ expect(process.exitCode).toBe(1);
519
+ expect(api.callApi).not.toHaveBeenCalled();
520
+ });
521
+
522
+ it('uses llama.cpp image with LLAMA_ARG_HF_REPO and LLAMA_ARG_HF_FILE env vars', async () => {
523
+ // --no-wait: only POST /serve called (1 mock needed)
524
+ api.callApi.mockResolvedValueOnce(makeServeDep());
525
+ global.fetch = vi.fn().mockResolvedValue({ ok: true, status: 200 });
526
+
527
+ const p = serveCommand(
528
+ config,
529
+ ['--runtime', 'llama.cpp', '--hf-repo', HF_REPO, '--hf-file', HF_FILE, '--max-cost', '10', '--no-wait'],
530
+ chalk,
531
+ );
532
+ await vi.advanceTimersByTimeAsync(100);
533
+ await p;
534
+
535
+ const body = api.callApi.mock.calls[0][1].body;
536
+ expect(body.image).toBe('michaelmanleyx/llama-cpp:server-cuda');
537
+ expect(body.env.LLAMA_ARG_HF_REPO).toBe(HF_REPO);
538
+ expect(body.env.LLAMA_ARG_HF_FILE).toBe(HF_FILE);
539
+ expect(body.model).toBeUndefined();
540
+ expect(process.exitCode).toBeFalsy();
541
+ });
542
+
543
+ it('health-checks /health (not /models)', async () => {
544
+ api.callApi
545
+ .mockResolvedValueOnce(makeServeDep())
546
+ .mockResolvedValueOnce({ status: 'running' });
547
+
548
+ const fetchedUrls = [];
549
+ global.fetch = vi.fn().mockImplementation((url) => {
550
+ fetchedUrls.push(url);
551
+ return Promise.resolve({ ok: true, status: 200 });
552
+ });
553
+
554
+ const p = serveCommand(
555
+ config,
556
+ ['--runtime', 'llama.cpp', '--hf-repo', HF_REPO, '--hf-file', HF_FILE, '--max-cost', '10'],
557
+ chalk,
558
+ );
559
+ await vi.advanceTimersByTimeAsync(5000);
560
+ await p;
561
+
562
+ expect(fetchedUrls.some(u => u.includes('/health'))).toBe(true);
563
+ expect(fetchedUrls.some(u => u.endsWith('/models'))).toBe(false);
564
+ expect(process.exitCode).toBeFalsy();
565
+ });
566
+
567
+ it('records deployment and receipt as ready on success', async () => {
568
+ api.callApi
569
+ .mockResolvedValueOnce(makeServeDep())
570
+ .mockResolvedValueOnce({ status: 'running' });
571
+
572
+ global.fetch = vi.fn().mockResolvedValue({ ok: true, status: 200 });
573
+
574
+ const p = serveCommand(
575
+ config,
576
+ ['--runtime', 'llama.cpp', '--hf-repo', HF_REPO, '--hf-file', HF_FILE, '--max-cost', '10'],
577
+ chalk,
578
+ );
579
+ await vi.advanceTimersByTimeAsync(5000);
580
+ await p;
581
+
582
+ expect(store.addDeployment).toHaveBeenCalledWith(expect.objectContaining({
583
+ id: 'dep-serve-001',
584
+ }));
585
+ expect(store.updateReceipt).toHaveBeenCalledWith('rcpt-serve-001', expect.objectContaining({
586
+ status: 'ready',
587
+ }));
588
+ expect(process.exitCode).toBeFalsy();
589
+ });
590
+
591
+ it('merges user --env with HF env vars', async () => {
592
+ // --no-wait: only POST /serve called (1 mock needed)
593
+ api.callApi.mockResolvedValueOnce(makeServeDep());
594
+ global.fetch = vi.fn().mockResolvedValue({ ok: true, status: 200 });
595
+
596
+ const p = serveCommand(
597
+ config,
598
+ ['--runtime', 'llama.cpp', '--hf-repo', HF_REPO, '--hf-file', HF_FILE,
599
+ '--env', 'CTX_SIZE=8192', '--max-cost', '10', '--no-wait'],
600
+ chalk,
601
+ );
602
+ await vi.advanceTimersByTimeAsync(100);
603
+ await p;
604
+
605
+ const body = api.callApi.mock.calls[0][1].body;
606
+ expect(body.env.LLAMA_ARG_HF_REPO).toBe(HF_REPO);
607
+ expect(body.env.LLAMA_ARG_HF_FILE).toBe(HF_FILE);
608
+ expect(body.env.CTX_SIZE).toBe('8192');
609
+ });
610
+ });
611
+
612
+ // ─────────────────────────────────────────────────────────────────────────────
613
+ // 12. E2E smoke test — tiny HF GGUF (ggml-org/tiny-llamas / stories260K.gguf)
614
+ //
615
+ // Uses a real, publicly-available tiny GGUF (~1 MB) to verify the full
616
+ // request lifecycle: arg parsing → correct serve body → /health polling →
617
+ // receipt recorded as ready → OpenAI-compatible base URL returned.
618
+ // ─────────────────────────────────────────────────────────────────────────────
619
+
620
+ describe('llama.cpp E2E smoke — tiny GGUF (ggml-org/tiny-llamas)', () => {
621
+ const TINY_REPO = 'ggml-org/tiny-llamas';
622
+ const TINY_FILE = 'stories260K.gguf';
623
+
624
+ it('full lifecycle: arg parse → serve body → /health → ready receipt', async () => {
625
+ api.callApi
626
+ .mockResolvedValueOnce(makeServeDep({ model: undefined })) // POST /serve
627
+ .mockResolvedValueOnce({ status: 'running' }); // pre-health dep check
628
+
629
+ const fetchedUrls = [];
630
+ global.fetch = vi.fn().mockImplementation((url) => {
631
+ fetchedUrls.push(url);
632
+ return Promise.resolve({ ok: true, status: 200 });
633
+ });
634
+
635
+ const p = serveCommand(
636
+ config,
637
+ ['--runtime', 'llama.cpp',
638
+ '--hf-repo', TINY_REPO,
639
+ '--hf-file', TINY_FILE,
640
+ '--max-cost', '1'],
641
+ chalk,
642
+ );
643
+ await vi.advanceTimersByTimeAsync(5000);
644
+ await p;
645
+
646
+ // Correct image and env vars in the serve request
647
+ const body = api.callApi.mock.calls[0][1].body;
648
+ expect(body.image).toBe('michaelmanleyx/llama-cpp:server-cuda');
649
+ expect(body.env.LLAMA_ARG_HF_REPO).toBe(TINY_REPO);
650
+ expect(body.env.LLAMA_ARG_HF_FILE).toBe(TINY_FILE);
651
+ expect(body.model).toBeUndefined();
652
+
653
+ // Readiness check hits /health, not /models
654
+ expect(fetchedUrls.some(u => u.includes('/health'))).toBe(true);
655
+ expect(fetchedUrls.some(u => u.endsWith('/models'))).toBe(false);
656
+
657
+ // Receipt finalised as ready with endpoint URL
658
+ expect(store.updateReceipt).toHaveBeenCalledWith('rcpt-serve-001', expect.objectContaining({
659
+ status: 'ready',
660
+ endpointUrl: ENDPOINT_URL,
661
+ }));
662
+
663
+ expect(process.exitCode).toBeFalsy();
664
+ });
665
+ });