This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

vit / test / explore.test.js
9.7 kB 272 lines
1// SPDX-License-Identifier: MIT 2// Copyright (c) 2026 sol pbc 3 4import { describe, test, expect, beforeAll, afterAll } from 'bun:test'; 5import { spawn } from 'node:child_process'; 6import { run } from './helpers.js'; 7 8let server; 9let port; 10 11beforeAll(async () => { 12 const serverScript = ` 13 const server = Bun.serve({ 14 port: 0, 15 fetch(req) { 16 const url = new URL(req.url); 17 const path = url.pathname; 18 19 if (path === '/api/stats') { 20 return Response.json({ 21 total_caps: 42, total_skills: 10, total_vouches: 5, 22 total_beacons: 3, active_dids: 8, skill_publishers: 4, 23 }); 24 } 25 26 if (path === '/api/caps') { 27 return Response.json({ 28 caps: [{ title: 'Test Cap', ref: 'test-cap', handle: 'test.bsky.social', 29 beacon: 'vit:github.com/test/repo', description: 'A test cap' }], 30 }); 31 } 32 33 if (path === '/api/skills') { 34 return Response.json({ 35 skills: [{ name: 'test-skill', version: '1.0.0', description: 'A test skill', 36 handle: 'test.bsky.social' }], 37 }); 38 } 39 40 if (path === '/api/beacons') { 41 return Response.json({ 42 beacons: [{ beacon: 'vit:github.com/test/repo', handle: 'test.bsky.social' }], 43 }); 44 } 45 46 if (path === '/api/cap') { 47 const ref = url.searchParams.get('ref'); 48 if (ref === 'network-content-seeding') { 49 return Response.json({ 50 cap: { 51 ref: 'network-content-seeding', title: 'Network Content Seeding', 52 beacon: 'vit:github.com/solpbc/vit', handle: 'test.bsky.social', 53 description: 'Seed content across the network', 54 record_json: JSON.stringify({ kind: 'feat', text: 'test body' }), 55 created_at: '2026-01-01T00:00:00Z', vouch_count: 0, 56 }, 57 }); 58 } 59 return Response.json({}); 60 } 61 62 if (path === '/api/skill') { 63 const name = url.searchParams.get('name'); 64 if (name === 'atproto-records') { 65 return Response.json({ 66 skill: { 67 name: 'atproto-records', version: '1.0.0', 68 description: 'AT Protocol record helpers', 69 handle: 'test.bsky.social', tags: 'atproto', 70 record_json: JSON.stringify({ license: 'MIT' }), 71 vouch_count: 0, 72 }, 73 }); 74 } 75 return Response.json({}); 76 } 77 78 return new Response('Not Found', { status: 404 }); 79 }, 80 }); 81 82 console.log(server.port); 83 `; 84 85 server = spawn('bun', ['-e', serverScript], { stdio: ['ignore', 'pipe', 'inherit'] }); 86 port = await new Promise((resolve, reject) => { 87 let started = false; 88 const timer = setTimeout(() => { 89 server.kill(); 90 reject(new Error('mock explore server failed to start')); 91 }, 5000); 92 93 server.stdout.setEncoding('utf-8'); 94 server.stdout.on('data', (chunk) => { 95 if (started) return; 96 const value = Number(String(chunk).trim().split(/\r?\n/, 1)[0]); 97 if (!Number.isNaN(value) && value > 0) { 98 started = true; 99 clearTimeout(timer); 100 resolve(value); 101 } 102 }); 103 server.once('exit', (code) => { 104 if (!started) { 105 clearTimeout(timer); 106 reject(new Error(`mock explore server exited early: ${code}`)); 107 } 108 }); 109 }); 110}); 111 112afterAll(async () => { 113 if (!server || server.exitCode !== null) return; 114 await new Promise((resolve) => { 115 server.once('exit', () => resolve()); 116 server.kill(); 117 }); 118}); 119 120describe('vit explore', () => { 121 test('shows help', () => { 122 const result = run('explore --help', '/tmp'); 123 expect(result.exitCode).toBe(0); 124 expect(result.stdout).toContain('explore'); 125 }); 126 127 test('stats returns JSON', () => { 128 const result = run(`explore stats --json --explore-url http://localhost:${port}`, '/tmp'); 129 expect(result.exitCode).toBe(0); 130 const data = JSON.parse(result.stdout); 131 expect(data.ok).toBe(true); 132 expect(typeof data.total_caps).toBe('number'); 133 }); 134 135 test('caps returns JSON', () => { 136 const result = run(`explore caps --json --limit 2 --explore-url http://localhost:${port}`, '/tmp'); 137 expect(result.exitCode).toBe(0); 138 const data = JSON.parse(result.stdout); 139 expect(data.ok).toBe(true); 140 expect(Array.isArray(data.caps)).toBe(true); 141 }); 142 143 test('skills returns JSON', () => { 144 const result = run(`explore skills --json --limit 2 --explore-url http://localhost:${port}`, '/tmp'); 145 expect(result.exitCode).toBe(0); 146 const data = JSON.parse(result.stdout); 147 expect(data.ok).toBe(true); 148 expect(Array.isArray(data.skills)).toBe(true); 149 }); 150 151 test('beacons returns JSON', () => { 152 const result = run(`explore beacons --json --explore-url http://localhost:${port}`, '/tmp'); 153 expect(result.exitCode).toBe(0); 154 const data = JSON.parse(result.stdout); 155 expect(data.ok).toBe(true); 156 expect(Array.isArray(data.beacons)).toBe(true); 157 }); 158 159 test('graceful error on unreachable URL', () => { 160 const result = run('explore stats --explore-url http://localhost:1 --json', '/tmp'); 161 expect(result.exitCode).not.toBe(0); 162 const data = JSON.parse(result.stdout); 163 expect(data.ok).toBe(false); 164 expect(data.error).toContain('request to http://localhost:1/api/stats failed'); 165 }); 166 167 test('graceful error on invalid URL', () => { 168 const result = run('explore stats --explore-url not-a-url --json', '/tmp'); 169 expect(result.exitCode).not.toBe(0); 170 const data = JSON.parse(result.stdout); 171 expect(data.ok).toBe(false); 172 expect(data.error).toContain('not-a-url'); 173 }); 174 175 test('vouches requires --cap or --ref', () => { 176 const result = run('explore vouches --json', '/tmp'); 177 expect(result.exitCode).not.toBe(0); 178 const data = JSON.parse(result.stdout); 179 expect(data.ok).toBe(false); 180 }); 181 182 test('env var override works', () => { 183 const result = run('explore stats --json', '/tmp', { VIT_EXPLORE_URL: 'http://localhost:1' }); 184 expect(result.exitCode).not.toBe(0); 185 const data = JSON.parse(result.stdout); 186 expect(data.ok).toBe(false); 187 expect(data.error).toContain('request to http://localhost:1/api/stats failed'); 188 }); 189 190 test('flag overrides env var', () => { 191 const result = run( 192 `explore stats --json --explore-url http://localhost:${port}`, 193 '/tmp', 194 { VIT_EXPLORE_URL: 'http://localhost:1' }, 195 ); 196 expect(result.exitCode).toBe(0); 197 const data = JSON.parse(result.stdout); 198 expect(data.ok).toBe(true); 199 }); 200 201 test('cap detail returns JSON', () => { 202 const result = run(`explore cap network-content-seeding --json --explore-url http://localhost:${port}`, '/tmp'); 203 expect(result.exitCode).toBe(0); 204 const data = JSON.parse(result.stdout); 205 expect(data.ok).toBe(true); 206 expect(data.cap).toBeDefined(); 207 expect(data.cap.ref).toBe('network-content-seeding'); 208 expect(data.cap.title).toBeDefined(); 209 }); 210 211 test('cap detail with beacon', () => { 212 const result = run( 213 `explore cap network-content-seeding --beacon vit:github.com/solpbc/vit --json --explore-url http://localhost:${port}`, 214 '/tmp', 215 ); 216 expect(result.exitCode).toBe(0); 217 const data = JSON.parse(result.stdout); 218 expect(data.ok).toBe(true); 219 expect(data.cap).toBeDefined(); 220 expect(data.cap.ref).toBe('network-content-seeding'); 221 }); 222 223 test('cap not found', () => { 224 const result = run(`explore cap nonexistent-ref-xyz --json --explore-url http://localhost:${port}`, '/tmp'); 225 expect(result.exitCode).not.toBe(0); 226 const data = JSON.parse(result.stdout); 227 expect(data.ok).toBe(false); 228 expect(data.error).toContain("no cap found with ref 'nonexistent-ref-xyz'"); 229 }); 230 231 test('skill detail returns JSON', () => { 232 const result = run(`explore skill atproto-records --json --explore-url http://localhost:${port}`, '/tmp'); 233 expect(result.exitCode).toBe(0); 234 const data = JSON.parse(result.stdout); 235 expect(data.ok).toBe(true); 236 expect(data.skill).toBeDefined(); 237 expect(data.skill.name).toBe('atproto-records'); 238 expect(data.skill.version).toBeDefined(); 239 }); 240 241 test('skill not found', () => { 242 const result = run(`explore skill nonexistent-skill-xyz --json --explore-url http://localhost:${port}`, '/tmp'); 243 expect(result.exitCode).not.toBe(0); 244 const data = JSON.parse(result.stdout); 245 expect(data.ok).toBe(false); 246 expect(data.error).toContain("no skill found with name 'nonexistent-skill-xyz'"); 247 }); 248 249 test('caps --kind filter passes kind to API', () => { 250 const result = run(`explore caps --kind request --json --limit 2 --explore-url http://localhost:${port}`, '/tmp'); 251 expect(result.exitCode).toBe(0); 252 const data = JSON.parse(result.stdout); 253 expect(data.ok).toBe(true); 254 expect(Array.isArray(data.caps)).toBe(true); 255 }); 256 257 test('caps gracefully degrades on unreachable URL with --kind', () => { 258 const result = run('explore caps --kind request --explore-url http://localhost:1 --json', '/tmp'); 259 expect(result.exitCode).not.toBe(0); 260 const data = JSON.parse(result.stdout); 261 expect(data.ok).toBe(false); 262 expect(data.error).toContain('request to http://localhost:1/api/caps?kind=request failed'); 263 }); 264 265 test('bare explore returns stats JSON', () => { 266 const result = run(`explore --json --explore-url http://localhost:${port}`, '/tmp'); 267 expect(result.exitCode).toBe(0); 268 const data = JSON.parse(result.stdout); 269 expect(data.ok).toBe(true); 270 expect(typeof data.total_caps).toBe('number'); 271 }); 272});