open source is social v-it.org
at main 225 lines 10 kB view raw
1// SPDX-License-Identifier: MIT 2// Copyright (c) 2026 sol pbc 3 4import { describe, test, expect, beforeEach, afterEach } from 'bun:test'; 5import { run } from './helpers.js'; 6import { mkdirSync, rmSync, readFileSync, existsSync } from 'node:fs'; 7import { tmpdir } from 'node:os'; 8import { join } from 'node:path'; 9import { execSync } from 'node:child_process'; 10 11describe('vit init', () => { 12 let tmpDir; 13 14 beforeEach(() => { 15 tmpDir = join(tmpdir(), '.test-tmp-' + Math.random().toString(36).slice(2)); 16 mkdirSync(tmpDir, { recursive: true }); 17 }); 18 19 afterEach(() => { 20 rmSync(tmpDir, { recursive: true, force: true }); 21 }); 22 23 test('writes beacon from HTTPS URL', () => { 24 const result = run('init --beacon https://github.com/solpbc/vit.git', tmpDir, { CLAUDECODE: '1' }); 25 expect(result.exitCode).toBe(0); 26 expect(result.stdout).toContain('beacon: vit:github.com/solpbc/vit'); 27 28 const content = readFileSync(join(tmpDir, '.vit', 'config.json'), 'utf-8'); 29 expect(JSON.parse(content).beacon).toBe('vit:github.com/solpbc/vit'); 30 }); 31 32 test('writes beacon from SSH URL', () => { 33 const result = run('init --beacon git@github.com:solpbc/vit.git', tmpDir, { CLAUDECODE: '1' }); 34 expect(result.exitCode).toBe(0); 35 expect(result.stdout).toContain('beacon: vit:github.com/solpbc/vit'); 36 37 const content = readFileSync(join(tmpDir, '.vit', 'config.json'), 'utf-8'); 38 expect(JSON.parse(content).beacon).toBe('vit:github.com/solpbc/vit'); 39 }); 40 41 test('creates .vit directory if missing', () => { 42 expect(existsSync(join(tmpDir, '.vit'))).toBe(false); 43 run('init --beacon https://github.com/solpbc/vit.git', tmpDir, { CLAUDECODE: '1' }); 44 expect(existsSync(join(tmpDir, '.vit'))).toBe(true); 45 }); 46 47 test('generates .vit/README.md on init', () => { 48 run('init --beacon https://github.com/solpbc/vit.git', tmpDir, { CLAUDECODE: '1' }); 49 const readme = readFileSync(join(tmpDir, '.vit', 'README.md'), 'utf-8'); 50 expect(readme).toContain('social open source network'); 51 expect(readme).toContain('v-it.org'); 52 }); 53 54 test('overwrites existing beacon silently', () => { 55 run('init --beacon https://github.com/old/repo.git', tmpDir, { CLAUDECODE: '1' }); 56 run('init --beacon https://github.com/solpbc/vit.git', tmpDir, { CLAUDECODE: '1' }); 57 58 const content = readFileSync(join(tmpDir, '.vit', 'config.json'), 'utf-8'); 59 expect(JSON.parse(content).beacon).toBe('vit:github.com/solpbc/vit'); 60 }); 61 62 test('reads beacon from git remote with --beacon .', () => { 63 // Set up a git repo with a remote origin 64 execSync('git init', { cwd: tmpDir, stdio: 'pipe' }); 65 execSync('git remote add origin https://github.com/solpbc/vit.git', { cwd: tmpDir, stdio: 'pipe' }); 66 67 const result = run('init --beacon .', tmpDir, { CLAUDECODE: '1' }); 68 expect(result.exitCode).toBe(0); 69 expect(result.stdout).toContain('beacon: vit:github.com/solpbc/vit'); 70 }); 71 72 test('errors when --beacon . has no git remote', () => { 73 // tmpDir is not a git repo 74 const result = run('init --beacon .', tmpDir, { CLAUDECODE: '1' }); 75 expect(result.exitCode).not.toBe(0); 76 }); 77 78 test('reports beacon when no flag and beacon exists', () => { 79 run('init --beacon https://github.com/solpbc/vit.git', tmpDir, { CLAUDECODE: '1' }); 80 const result = run('init', tmpDir, { CLAUDECODE: '1' }); 81 expect(result.exitCode).toBe(0); 82 expect(result.stdout).toContain('beacon: vit:github.com/solpbc/vit'); 83 expect(result.stdout).toContain('hint: to change the beacon, run: vit init --beacon <git-url>'); 84 }); 85 86 test('--secondary stores secondaryBeacon with existing primary', () => { 87 run('init --beacon https://github.com/org/repo.git', tmpDir, { CLAUDECODE: '1' }); 88 const result = run('init --secondary https://github.com/upstream/repo.git', tmpDir, { CLAUDECODE: '1' }); 89 expect(result.exitCode).toBe(0); 90 91 const content = readFileSync(join(tmpDir, '.vit', 'config.json'), 'utf-8'); 92 const config = JSON.parse(content); 93 expect(config.beacon).toBe('vit:github.com/org/repo'); 94 expect(config.secondaryBeacon).toBe('vit:github.com/upstream/repo'); 95 }); 96 97 test('--secondary errors without existing primary beacon', () => { 98 const result = run('init --secondary https://github.com/upstream/repo.git', tmpDir, { CLAUDECODE: '1' }); 99 expect(result.exitCode).not.toBe(0); 100 }); 101 102 test('--beacon preserves existing secondaryBeacon', () => { 103 run('init --beacon https://github.com/org/repo.git', tmpDir, { CLAUDECODE: '1' }); 104 run('init --secondary https://github.com/upstream/repo.git', tmpDir, { CLAUDECODE: '1' }); 105 run('init --beacon https://github.com/org/newrepo.git', tmpDir, { CLAUDECODE: '1' }); 106 107 const content = readFileSync(join(tmpDir, '.vit', 'config.json'), 'utf-8'); 108 const config = JSON.parse(content); 109 expect(config.beacon).toBe('vit:github.com/org/newrepo'); 110 expect(config.secondaryBeacon).toBe('vit:github.com/upstream/repo'); 111 }); 112 113 test('displays secondary beacon when set', () => { 114 run('init --beacon https://github.com/org/repo.git --secondary https://github.com/upstream/repo.git', tmpDir, { CLAUDECODE: '1' }); 115 const result = run('init', tmpDir, { CLAUDECODE: '1' }); 116 expect(result.exitCode).toBe(0); 117 expect(result.stdout).toContain('beacon: vit:github.com/org/repo'); 118 expect(result.stdout).toContain('secondary beacon: vit:github.com/upstream/repo'); 119 }); 120 121 test('--json includes secondaryBeacon when set', () => { 122 run('init --beacon https://github.com/org/repo.git --secondary https://github.com/upstream/repo.git', tmpDir, { CLAUDECODE: '1' }); 123 const result = run('init --json', tmpDir, { CLAUDECODE: '1' }); 124 expect(result.exitCode).toBe(0); 125 const output = JSON.parse(result.stdout); 126 expect(output.beacon).toBe('vit:github.com/org/repo'); 127 expect(output.secondaryBeacon).toBe('vit:github.com/upstream/repo'); 128 }); 129 130 test('reports no beacon when .vit exists but directory is not a git repo', () => { 131 mkdirSync(join(tmpDir, '.vit'), { recursive: true }); 132 const result = run('init', tmpDir, { CLAUDECODE: '1' }); 133 expect(result.exitCode).toBe(0); 134 expect(result.stdout).toContain('status: no beacon'); 135 expect(result.stdout).toContain('git: false'); 136 expect(result.stdout).toContain('hint: run: vit init --beacon <canonical-git-url>'); 137 }); 138 139 test('reports .vit not found when no flag and no .vit dir', () => { 140 const result = run('init', tmpDir, { CLAUDECODE: '1' }); 141 expect(result.exitCode).toBe(0); 142 expect(result.stdout).toContain('status: not initialized'); 143 expect(result.stdout).toContain('git: false'); 144 expect(result.stdout).toContain('hint: run vit init from inside a git repository'); 145 }); 146 147 test('guides agent in fork repo with upstream and origin remotes', () => { 148 execSync('git init', { cwd: tmpDir, stdio: 'pipe' }); 149 execSync('git remote add origin https://github.com/agent/vit.git', { cwd: tmpDir, stdio: 'pipe' }); 150 execSync('git remote add upstream https://github.com/solpbc/vit.git', { cwd: tmpDir, stdio: 'pipe' }); 151 152 const result = run('init', tmpDir, { CLAUDECODE: '1' }); 153 expect(result.exitCode).toBe(0); 154 expect(result.stdout).toContain('status: not initialized'); 155 expect(result.stdout).toContain('git: true'); 156 expect(result.stdout).toContain('origin='); 157 expect(result.stdout).toContain('upstream='); 158 expect(result.stdout).toContain('hint: detected upstream remote'); 159 expect(result.stdout).toContain('vit init --beacon https://github.com/solpbc/vit.git'); 160 }); 161 162 test('guides agent in repo with only origin remote', () => { 163 execSync('git init', { cwd: tmpDir, stdio: 'pipe' }); 164 execSync('git remote add origin https://github.com/solpbc/vit.git', { cwd: tmpDir, stdio: 'pipe' }); 165 166 const result = run('init', tmpDir, { CLAUDECODE: '1' }); 167 expect(result.exitCode).toBe(0); 168 expect(result.stdout).toContain('status: not initialized'); 169 expect(result.stdout).toContain('git: true'); 170 expect(result.stdout).toContain('origin='); 171 expect(result.stdout).toContain('vit init --beacon https://github.com/solpbc/vit.git'); 172 }); 173 174 test('guides agent in git repo with no remotes', () => { 175 execSync('git init', { cwd: tmpDir, stdio: 'pipe' }); 176 177 const result = run('init', tmpDir, { CLAUDECODE: '1' }); 178 expect(result.exitCode).toBe(0); 179 expect(result.stdout).toContain('status: not initialized'); 180 expect(result.stdout).toContain('git: true'); 181 expect(result.stdout).toContain('remotes: none'); 182 expect(result.stdout).toContain('hint: no git remotes found'); 183 }); 184 185 test('guides agent in git repo with .vit but no beacon', () => { 186 execSync('git init', { cwd: tmpDir, stdio: 'pipe' }); 187 mkdirSync(join(tmpDir, '.vit'), { recursive: true }); 188 189 const result = run('init', tmpDir, { CLAUDECODE: '1' }); 190 expect(result.exitCode).toBe(0); 191 expect(result.stdout).toContain('status: no beacon'); 192 expect(result.stdout).toContain('git: true'); 193 expect(result.stdout).toContain('remotes: none'); 194 expect(result.stdout).toContain('hint: no git remotes found'); 195 }); 196 197 test('--beacon . prefers upstream over origin in fork', () => { 198 execSync('git init', { cwd: tmpDir, stdio: 'pipe' }); 199 execSync('git remote add origin https://github.com/agent/fork.git', { cwd: tmpDir, stdio: 'pipe' }); 200 execSync('git remote add upstream https://github.com/solpbc/vit.git', { cwd: tmpDir, stdio: 'pipe' }); 201 202 const result = run('init --beacon .', tmpDir, { CLAUDECODE: '1' }); 203 expect(result.exitCode).toBe(0); 204 expect(result.stdout).toContain('beacon: vit:github.com/solpbc/vit'); 205 }); 206 207 test('shows guidance for already initialized repo', () => { 208 run('init --beacon https://github.com/solpbc/vit.git', tmpDir, { CLAUDECODE: '1' }); 209 const result = run('init', tmpDir, { CLAUDECODE: '1' }); 210 expect(result.exitCode).toBe(0); 211 expect(result.stdout).toContain('beacon: vit:github.com/solpbc/vit'); 212 expect(result.stdout).toContain('hint: to change the beacon'); 213 }); 214 215 test('errors on invalid git URL', () => { 216 const result = run('init --beacon notaurl', tmpDir, { CLAUDECODE: '1' }); 217 expect(result.exitCode).not.toBe(0); 218 }); 219 220 test('rejects when run outside a coding agent', () => { 221 const result = run('init --beacon https://github.com/solpbc/vit.git', tmpDir, { CLAUDECODE: '', GEMINI_CLI: '', CODEX_CI: '' }); 222 expect(result.exitCode).toBe(1); 223 expect(result.stderr).toContain('should be run by a coding agent'); 224 }); 225});