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';
6import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
7import { tmpdir } from 'node:os';
8import { join } from 'node:path';
9
10const agentEnv = { CLAUDECODE: '1' };
11
12function makeVitDir(base, beacon) {
13 const vitDir = join(base, '.vit');
14 mkdirSync(vitDir, { recursive: true });
15 writeFileSync(join(vitDir, 'config.json'), JSON.stringify({ beacon }));
16 return base;
17}
18
19describe('vit ship --kind request', () => {
20 test('accepts request as a valid kind value (errors at auth, not validation)', () => {
21 const tmp = join(tmpdir(), '.test-ship-req-' + Math.random().toString(36).slice(2));
22 makeVitDir(tmp, 'vit:github.com/test/project');
23 const r = run(
24 'ship --kind request --title "Add async validation" --description "Need async validators" --did did:plc:abc',
25 tmp,
26 agentEnv,
27 '',
28 );
29 expect(r.stderr).not.toMatch(/--kind must be one of/i);
30 rmSync(tmp, { recursive: true, force: true });
31 });
32
33 test('rejects request without beacon when not in vit-initialized dir', () => {
34 const tmp = join(tmpdir(), '.test-ship-req-nobeacon-' + Math.random().toString(36).slice(2));
35 mkdirSync(tmp, { recursive: true });
36 const r = run(
37 'ship --kind request --title "Add async validation" --description "Need async validators" --did did:plc:abc',
38 tmp,
39 agentEnv,
40 '',
41 );
42 expect(r.exitCode).not.toBe(0);
43 expect(r.stderr).toContain('request caps must be addressed to a project');
44 rmSync(tmp, { recursive: true, force: true });
45 });
46
47 test('accepts --beacon flag for request caps outside vit dir', () => {
48 const tmp = join(tmpdir(), '.test-ship-req-beacon-' + Math.random().toString(36).slice(2));
49 mkdirSync(tmp, { recursive: true });
50 const r = run(
51 'ship --kind request --title "Add async validation" --description "Need async validators" --beacon github.com/pydantic/pydantic --did did:plc:abc',
52 tmp,
53 agentEnv,
54 '',
55 );
56 // Should not fail on beacon validation — will fail at auth
57 expect(r.stderr).not.toContain('request caps must be addressed to a project');
58 expect(r.stderr).not.toMatch(/--kind must be one of/i);
59 rmSync(tmp, { recursive: true, force: true });
60 });
61
62 test('normalizes github.com URL to vit: beacon', () => {
63 const tmp = join(tmpdir(), '.test-ship-req-norm-' + Math.random().toString(36).slice(2));
64 mkdirSync(tmp, { recursive: true });
65 const r = run(
66 'ship --kind request --title "Add async validation" --description "Need async validators" --beacon github.com/pydantic/pydantic --did did:plc:abc',
67 tmp,
68 agentEnv,
69 '',
70 );
71 // Beacon normalization succeeds; error is at auth
72 expect(r.stderr).not.toContain('invalid --beacon');
73 rmSync(tmp, { recursive: true, force: true });
74 });
75
76 test('auto-generates ref from title when --ref not provided', () => {
77 const tmp = join(tmpdir(), '.test-ship-req-autoref-' + Math.random().toString(36).slice(2));
78 makeVitDir(tmp, 'vit:github.com/test/project');
79 const r = run(
80 'ship --kind request --title "Add async validation support" --description "Need async validators" --did did:plc:abc',
81 tmp,
82 agentEnv,
83 '',
84 );
85 // Should not fail on missing --ref
86 expect(r.stderr).not.toMatch(/--ref.*not specified/i);
87 // Auto-generated ref is printed (fails at auth, not ref generation)
88 expect(r.stderr).not.toMatch(/three lowercase words/i);
89 rmSync(tmp, { recursive: true, force: true });
90 });
91
92 test('requires --title for ref auto-generation', () => {
93 const tmp = join(tmpdir(), '.test-ship-req-notitle-' + Math.random().toString(36).slice(2));
94 makeVitDir(tmp, 'vit:github.com/test/project');
95 const r = run(
96 'ship --kind request --description "Need async validators" --did did:plc:abc',
97 tmp,
98 agentEnv,
99 '',
100 );
101 // Missing --title is caught before ref generation
102 expect(r.exitCode).not.toBe(0);
103 expect(r.stderr).toMatch(/--title/i);
104 rmSync(tmp, { recursive: true, force: true });
105 });
106
107 test('text is optional for request caps (empty stdin does not error)', () => {
108 const tmp = join(tmpdir(), '.test-ship-req-notext-' + Math.random().toString(36).slice(2));
109 makeVitDir(tmp, 'vit:github.com/test/project');
110 const r = run(
111 'ship --kind request --title "Add async validation support" --description "Need async validators" --did did:plc:abc',
112 tmp,
113 agentEnv,
114 '',
115 );
116 // Should not error on empty stdin for request caps
117 expect(r.stderr).not.toMatch(/body is required/i);
118 rmSync(tmp, { recursive: true, force: true });
119 });
120
121 test('--help shows request in --kind options', () => {
122 const r = run('ship --help');
123 expect(r.exitCode).toBe(0);
124 expect(r.stdout).toContain('request');
125 });
126
127 test('--help shows --beacon option', () => {
128 const r = run('ship --help');
129 expect(r.exitCode).toBe(0);
130 expect(r.stdout).toContain('--beacon');
131 });
132});