open source is social v-it.org
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 sol pbc
3
4import { describe, test, expect } from 'bun:test';
5import { run } from './helpers.js';
6
7describe('vit explore', () => {
8 test('shows help', () => {
9 const result = run('explore --help', '/tmp');
10 expect(result.exitCode).toBe(0);
11 expect(result.stdout).toContain('explore');
12 });
13
14 test('stats returns JSON', () => {
15 const result = run('explore stats --json', '/tmp');
16 expect(result.exitCode).toBe(0);
17 const data = JSON.parse(result.stdout);
18 expect(data.ok).toBe(true);
19 expect(typeof data.total_caps).toBe('number');
20 });
21
22 test('caps returns JSON', () => {
23 const result = run('explore caps --json --limit 2', '/tmp');
24 expect(result.exitCode).toBe(0);
25 const data = JSON.parse(result.stdout);
26 expect(data.ok).toBe(true);
27 expect(Array.isArray(data.caps)).toBe(true);
28 });
29
30 test('skills returns JSON', () => {
31 const result = run('explore skills --json --limit 2', '/tmp');
32 expect(result.exitCode).toBe(0);
33 const data = JSON.parse(result.stdout);
34 expect(data.ok).toBe(true);
35 expect(Array.isArray(data.skills)).toBe(true);
36 });
37
38 test('beacons returns JSON', () => {
39 const result = run('explore beacons --json', '/tmp');
40 expect(result.exitCode).toBe(0);
41 const data = JSON.parse(result.stdout);
42 expect(data.ok).toBe(true);
43 expect(Array.isArray(data.beacons)).toBe(true);
44 });
45
46 test('graceful error on unreachable URL', () => {
47 const result = run('explore stats --explore-url http://localhost:1 --json', '/tmp');
48 expect(result.exitCode).not.toBe(0);
49 const data = JSON.parse(result.stdout);
50 expect(data.ok).toBe(false);
51 expect(data.error).toContain('unavailable');
52 });
53
54 test('graceful error on invalid URL', () => {
55 const result = run('explore stats --explore-url not-a-url --json', '/tmp');
56 expect(result.exitCode).not.toBe(0);
57 const data = JSON.parse(result.stdout);
58 expect(data.ok).toBe(false);
59 expect(data.error).toContain('unavailable');
60 });
61
62 test('vouches requires --cap or --ref', () => {
63 const result = run('explore vouches --json', '/tmp');
64 expect(result.exitCode).not.toBe(0);
65 const data = JSON.parse(result.stdout);
66 expect(data.ok).toBe(false);
67 });
68
69 test('env var override works', () => {
70 const result = run('explore stats --json', '/tmp', { VIT_EXPLORE_URL: 'http://localhost:1' });
71 expect(result.exitCode).not.toBe(0);
72 const data = JSON.parse(result.stdout);
73 expect(data.ok).toBe(false);
74 expect(data.error).toContain('unavailable');
75 });
76
77 test('flag overrides env var', () => {
78 const result = run(
79 'explore stats --json --explore-url https://explore.v-it.org',
80 '/tmp',
81 { VIT_EXPLORE_URL: 'http://localhost:1' },
82 );
83 expect(result.exitCode).toBe(0);
84 const data = JSON.parse(result.stdout);
85 expect(data.ok).toBe(true);
86 });
87
88 test('cap detail returns JSON', () => {
89 const result = run('explore cap network-content-seeding --json', '/tmp');
90 expect(result.exitCode).toBe(0);
91 const data = JSON.parse(result.stdout);
92 expect(data.ok).toBe(true);
93 expect(data.cap).toBeDefined();
94 expect(data.cap.ref).toBe('network-content-seeding');
95 expect(data.cap.title).toBeDefined();
96 });
97
98 test('cap detail with beacon', () => {
99 const result = run('explore cap network-content-seeding --beacon vit:github.com/solpbc/vit --json', '/tmp');
100 expect(result.exitCode).toBe(0);
101 const data = JSON.parse(result.stdout);
102 expect(data.ok).toBe(true);
103 expect(data.cap).toBeDefined();
104 expect(data.cap.ref).toBe('network-content-seeding');
105 });
106
107 test('cap not found', () => {
108 const result = run('explore cap nonexistent-ref-xyz --json', '/tmp');
109 expect(result.exitCode).not.toBe(0);
110 const data = JSON.parse(result.stdout);
111 expect(data.ok).toBe(false);
112 expect(data.error).toContain("no cap found with ref 'nonexistent-ref-xyz'");
113 });
114
115 test('skill detail returns JSON', () => {
116 const result = run('explore skill atproto-records --json', '/tmp');
117 expect(result.exitCode).toBe(0);
118 const data = JSON.parse(result.stdout);
119 expect(data.ok).toBe(true);
120 expect(data.skill).toBeDefined();
121 expect(data.skill.name).toBe('atproto-records');
122 expect(data.skill.version).toBeDefined();
123 });
124
125 test('skill not found', () => {
126 const result = run('explore skill nonexistent-skill-xyz --json', '/tmp');
127 expect(result.exitCode).not.toBe(0);
128 const data = JSON.parse(result.stdout);
129 expect(data.ok).toBe(false);
130 expect(data.error).toContain("no skill found with name 'nonexistent-skill-xyz'");
131 });
132
133 test('caps --kind filter passes kind to API', () => {
134 const result = run('explore caps --kind request --json --limit 2', '/tmp');
135 expect(result.exitCode).toBe(0);
136 const data = JSON.parse(result.stdout);
137 expect(data.ok).toBe(true);
138 expect(Array.isArray(data.caps)).toBe(true);
139 });
140
141 test('caps gracefully degrades on unreachable URL with --kind', () => {
142 const result = run('explore caps --kind request --explore-url http://localhost:1 --json', '/tmp');
143 expect(result.exitCode).not.toBe(0);
144 const data = JSON.parse(result.stdout);
145 expect(data.ok).toBe(false);
146 expect(data.error).toContain('unavailable');
147 });
148
149 test('bare explore returns stats JSON', () => {
150 const result = run('explore --json', '/tmp');
151 expect(result.exitCode).toBe(0);
152 const data = JSON.parse(result.stdout);
153 expect(data.ok).toBe(true);
154 expect(typeof data.total_caps).toBe('number');
155 });
156});