This repository has no description
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, writeFileSync, readFileSync, existsSync, rmSync } from 'node:fs';
7import { tmpdir } from 'node:os';
8import { join } from 'node:path';
9
10const noAgentEnv = { CLAUDECODE: '', GEMINI_CLI: '', CODEX_CI: '' };
11const agentEnv = { CLAUDECODE: '1' };
12
13describe('vit vet', () => {
14 test('shows help with [ref] argument', () => {
15 const result = run('vet --help');
16 expect(result.stdout).toContain('[ref]');
17 });
18
19 test('rejects invalid ref format (human)', () => {
20 const result = run('vet not-valid', undefined, noAgentEnv);
21 expect(result.exitCode).not.toBe(0);
22 expect(result.stderr).toContain('invalid ref');
23 });
24
25 test('errors when no ref and no --dangerous-accept', () => {
26 const result = run('vet', undefined, noAgentEnv);
27 expect(result.exitCode).not.toBe(0);
28 expect(result.stderr).toContain('ref argument is required');
29 });
30
31 // --- dangerous-accept tests ---
32
33 describe('--dangerous-accept', () => {
34 test('without --confirm: prints warning, does NOT write flag', () => {
35 const tmp = join(tmpdir(), '.test-vet-da-' + Math.random().toString(36).slice(2));
36 mkdirSync(join(tmp, '.vit'), { recursive: true });
37 const result = run('vet --dangerous-accept', tmp, noAgentEnv);
38 expect(result.exitCode).toBe(0);
39 expect(result.stdout).toContain('WARNING');
40 expect(result.stdout).toContain('--dangerous-accept --confirm');
41 expect(existsSync(join(tmp, '.vit', 'dangerous-accept'))).toBe(false);
42 rmSync(tmp, { recursive: true, force: true });
43 });
44
45 test('with --confirm: writes flag file and .gitignore', () => {
46 const tmp = join(tmpdir(), '.test-vet-da-confirm-' + Math.random().toString(36).slice(2));
47 mkdirSync(join(tmp, '.vit'), { recursive: true });
48 const result = run('vet --dangerous-accept --confirm', tmp, noAgentEnv);
49 expect(result.exitCode).toBe(0);
50 expect(result.stdout).toContain('dangerous-accept enabled');
51 expect(existsSync(join(tmp, '.vit', 'dangerous-accept'))).toBe(true);
52 const flagContent = JSON.parse(readFileSync(join(tmp, '.vit', 'dangerous-accept'), 'utf-8').trim());
53 expect(flagContent.acceptedAt).toBeTruthy();
54 const gitignore = readFileSync(join(tmp, '.vit', '.gitignore'), 'utf-8');
55 expect(gitignore).toContain('dangerous-accept');
56 rmSync(tmp, { recursive: true, force: true });
57 });
58
59 test('blocked when agent detected', () => {
60 const tmp = join(tmpdir(), '.test-vet-da-agent-' + Math.random().toString(36).slice(2));
61 mkdirSync(join(tmp, '.vit'), { recursive: true });
62 const result = run('vet --dangerous-accept', tmp, agentEnv);
63 expect(result.exitCode).toBe(1);
64 expect(result.stderr).toContain('human-only');
65 expect(existsSync(join(tmp, '.vit', 'dangerous-accept'))).toBe(false);
66 rmSync(tmp, { recursive: true, force: true });
67 });
68
69 test('blocked with --confirm when agent detected', () => {
70 const tmp = join(tmpdir(), '.test-vet-da-agent-c-' + Math.random().toString(36).slice(2));
71 mkdirSync(join(tmp, '.vit'), { recursive: true });
72 const result = run('vet --dangerous-accept --confirm', tmp, agentEnv);
73 expect(result.exitCode).toBe(1);
74 expect(result.stderr).toContain('human-only');
75 expect(existsSync(join(tmp, '.vit', 'dangerous-accept'))).toBe(false);
76 rmSync(tmp, { recursive: true, force: true });
77 });
78
79 test('running twice does not duplicate .gitignore entry', () => {
80 const tmp = join(tmpdir(), '.test-vet-da-twice-' + Math.random().toString(36).slice(2));
81 mkdirSync(join(tmp, '.vit'), { recursive: true });
82 run('vet --dangerous-accept --confirm', tmp, noAgentEnv);
83 run('vet --dangerous-accept --confirm', tmp, noAgentEnv);
84 const gitignore = readFileSync(join(tmp, '.vit', '.gitignore'), 'utf-8');
85 const matches = gitignore.match(/dangerous-accept/g);
86 expect(matches.length).toBe(1);
87 rmSync(tmp, { recursive: true, force: true });
88 });
89 });
90
91 // --- agent gate tests ---
92
93 describe('agent gate', () => {
94 test('agent without flags: error with sandboxed sub-agent hint', () => {
95 const result = run('vet fast-cache-invalidation', undefined, agentEnv);
96 expect(result.exitCode).toBe(1);
97 expect(result.stderr).toContain('vit vet is for operator review');
98 expect(result.stderr).toContain('--trust --confirm');
99 expect(result.stderr).toContain('sandboxed sub-agent');
100 });
101
102 test('agent with --trust but no --confirm: error', () => {
103 const result = run('vet fast-cache-invalidation --trust', undefined, agentEnv);
104 expect(result.exitCode).toBe(1);
105 expect(result.stderr).toContain('vit vet is for operator review');
106 expect(result.stderr).toContain('--trust --confirm');
107 });
108
109 test('agent with --confirm but no --trust: error', () => {
110 const result = run('vet fast-cache-invalidation --confirm', undefined, agentEnv);
111 expect(result.exitCode).toBe(1);
112 expect(result.stderr).toContain('vit vet is for operator review');
113 });
114
115 test('agent with --trust --confirm: passes agent gate (fails at DID)', () => {
116 // Should pass agent gate but fail later at DID check
117 const configHome = join(tmpdir(), '.test-vet-tc-' + Math.random().toString(36).slice(2));
118 mkdirSync(configHome, { recursive: true });
119 const result = run('vet fast-cache-invalidation --trust --confirm', undefined, { ...agentEnv, XDG_CONFIG_HOME: configHome });
120 // Should NOT contain the agent gate error
121 expect(result.stderr).not.toContain('vit vet is for operator review');
122 rmSync(configHome, { recursive: true, force: true });
123 });
124
125 test('human with --trust --confirm: passes (--confirm harmless for humans)', () => {
126 const configHome = join(tmpdir(), '.test-vet-tc-human-' + Math.random().toString(36).slice(2));
127 mkdirSync(configHome, { recursive: true });
128 const result = run('vet fast-cache-invalidation --trust --confirm', undefined, { ...noAgentEnv, XDG_CONFIG_HOME: configHome });
129 // Should NOT contain the agent gate error
130 expect(result.stderr).not.toContain('vit vet is for operator review');
131 rmSync(configHome, { recursive: true, force: true });
132 });
133 });
134
135 describe('--sandbox', () => {
136 test('--sandbox without agent outside agent env: error with hint', () => {
137 const result = run('vet fast-cache-invalidation --sandbox', undefined, noAgentEnv);
138 expect(result.exitCode).not.toBe(0);
139 expect(result.stderr).toContain('could not detect agent');
140 expect(result.stderr).toContain('--sandbox claude');
141 });
142
143 test('--sandbox bogus: error about unknown agent', () => {
144 const result = run('vet fast-cache-invalidation --sandbox bogus', undefined, noAgentEnv);
145 expect(result.exitCode).not.toBe(0);
146 expect(result.stderr).toContain('unknown sandbox agent');
147 });
148
149 test('--sandbox claude accepted (passes validation, fails at DID)', () => {
150 const configHome = join(tmpdir(), '.test-vet-sb-' + Math.random().toString(36).slice(2));
151 mkdirSync(configHome, { recursive: true });
152 const result = run('vet fast-cache-invalidation --sandbox claude', undefined, { ...noAgentEnv, XDG_CONFIG_HOME: configHome });
153 // Should NOT contain validation errors for sandbox agent
154 expect(result.stderr).not.toContain('unknown sandbox agent');
155 rmSync(configHome, { recursive: true, force: true });
156 });
157
158 test('--sandbox codex accepted', () => {
159 const configHome = join(tmpdir(), '.test-vet-sb-codex-' + Math.random().toString(36).slice(2));
160 mkdirSync(configHome, { recursive: true });
161 const result = run('vet fast-cache-invalidation --sandbox codex', undefined, { ...noAgentEnv, XDG_CONFIG_HOME: configHome });
162 expect(result.stderr).not.toContain('unknown sandbox agent');
163 rmSync(configHome, { recursive: true, force: true });
164 });
165
166 test('--sandbox gemini accepted', () => {
167 const configHome = join(tmpdir(), '.test-vet-sb-gemini-' + Math.random().toString(36).slice(2));
168 mkdirSync(configHome, { recursive: true });
169 const result = run('vet fast-cache-invalidation --sandbox gemini', undefined, { ...noAgentEnv, XDG_CONFIG_HOME: configHome });
170 expect(result.stderr).not.toContain('unknown sandbox agent');
171 rmSync(configHome, { recursive: true, force: true });
172 });
173
174 test('--sandbox without value in agent env: auto-detects agent', () => {
175 const configHome = join(tmpdir(), '.test-vet-sb-auto-' + Math.random().toString(36).slice(2));
176 mkdirSync(configHome, { recursive: true });
177 const result = run('vet fast-cache-invalidation --sandbox', undefined, { ...agentEnv, XDG_CONFIG_HOME: configHome });
178 // Should pass sandbox agent resolution (claude code → claude), fail later at DID
179 expect(result.stderr).not.toContain('could not detect agent');
180 expect(result.stderr).not.toContain('unknown sandbox agent');
181 rmSync(configHome, { recursive: true, force: true });
182 });
183
184 test('--sandbox passes agent gate (agent env + --sandbox flag)', () => {
185 const configHome = join(tmpdir(), '.test-vet-sb-gate-' + Math.random().toString(36).slice(2));
186 mkdirSync(configHome, { recursive: true });
187 const result = run('vet fast-cache-invalidation --sandbox claude', undefined, { ...agentEnv, XDG_CONFIG_HOME: configHome });
188 // Should NOT contain the agent gate error
189 expect(result.stderr).not.toContain('vit vet is for operator review');
190 rmSync(configHome, { recursive: true, force: true });
191 });
192
193 test('--sandbox shows in help output', () => {
194 const result = run('vet --help');
195 expect(result.stdout).toContain('--sandbox');
196 });
197
198 test('--sandbox --json without agent outside agent env: JSON error', () => {
199 const result = run('vet fast-cache-invalidation --sandbox --json', undefined, noAgentEnv);
200 const parsed = JSON.parse(result.stdout);
201 expect(parsed.ok).toBe(false);
202 expect(parsed.error).toContain('could not detect agent');
203 });
204
205 test('--sandbox --json bogus: JSON error', () => {
206 const result = run('vet fast-cache-invalidation --sandbox bogus --json', undefined, noAgentEnv);
207 const parsed = JSON.parse(result.stdout);
208 expect(parsed.ok).toBe(false);
209 expect(parsed.error).toContain('unknown sandbox agent');
210 });
211 });
212});