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 } from 'node:fs';
7import { tmpdir } from 'node:os';
8import { join } from 'node:path';
9
10const agentEnv = { CLAUDECODE: '1' };
11
12describe('vit ship', () => {
13 test('rejects when run outside a coding agent', () => {
14 const r = run('ship --title "Hi" --description "desc" --ref "one-two-three"', '/tmp', { CLAUDECODE: '', GEMINI_CLI: '', CODEX_CI: '' }, 'body text');
15 expect(r.exitCode).not.toBe(0);
16 expect(r.stderr).toContain('should be run by a coding agent');
17 });
18
19 test('fails when --title is missing', () => {
20 const r = run('ship --description "desc" --ref "one-two-three"');
21 expect(r.exitCode).not.toBe(0);
22 expect(r.stderr).toMatch(/--title/i);
23 });
24
25 test('fails when --description is missing', () => {
26 const r = run('ship --title "Hi" --ref "one-two-three"');
27 expect(r.exitCode).not.toBe(0);
28 expect(r.stderr).toMatch(/--description/i);
29 });
30
31 test('fails when --ref is missing', () => {
32 const r = run('ship --title "Hi" --description "desc"');
33 expect(r.exitCode).not.toBe(0);
34 expect(r.stderr).toMatch(/--ref/i);
35 });
36
37 test('fails when stdin body is empty', () => {
38 const r = run('ship --title "Hi" --description "desc" --ref "one-two-three" --did "did:plc:abc"', undefined, agentEnv, '');
39 expect(r.exitCode).not.toBe(0);
40 expect(r.stderr).toMatch(/body is required/i);
41 });
42
43 test('rejects ref with uppercase letters', () => {
44 const r = run('ship --title "Hi" --description "desc" --ref "Bad-Ref-Here" --did "did:plc:abc"', undefined, agentEnv, 'body text');
45 expect(r.exitCode).not.toBe(0);
46 expect(r.stderr).toMatch(/three lowercase words/i);
47 });
48
49 test('rejects ref with wrong segment count', () => {
50 const r = run('ship --title "Hi" --description "desc" --ref "only-two" --did "did:plc:abc"', undefined, agentEnv, 'body text');
51 expect(r.exitCode).not.toBe(0);
52 expect(r.stderr).toMatch(/three lowercase words/i);
53 });
54
55 test('rejects ref with digits', () => {
56 const r = run('ship --title "Hi" --description "desc" --ref "has-num-3bers" --did "did:plc:abc"', undefined, agentEnv, 'body text');
57 expect(r.exitCode).not.toBe(0);
58 expect(r.stderr).toMatch(/three lowercase words/i);
59 });
60
61 test('rejects --recap with invalid ref format', () => {
62 const r = run('ship --title "Hi" --description "desc" --ref "one-two-three" --recap "BAD" --did "did:plc:abc"', undefined, agentEnv, 'body text');
63 expect(r.exitCode).not.toBe(0);
64 expect(r.stderr).toMatch(/--recap must be exactly three lowercase words/i);
65 });
66
67 test('rejects --kind with invalid value', () => {
68 const r = run('ship --title "Hi" --description "desc" --ref "one-two-three" --kind "invalid" --did "did:plc:abc"', undefined, agentEnv, 'body text');
69 expect(r.exitCode).not.toBe(0);
70 expect(r.stderr).toMatch(/--kind must be one of/i);
71 });
72
73 test('--help shows --kind and --recap', () => {
74 const r = run('ship --help');
75 expect(r.exitCode).toBe(0);
76 expect(r.stdout).toContain('--kind <kind>');
77 expect(r.stdout).toContain('--recap <ref>');
78 });
79
80 test('accepts valid ref format (fails at auth, not validation)', () => {
81 const r = run('ship --title "Hi" --description "desc" --ref "one-two-three" --did "did:plc:abc"', undefined, agentEnv, 'body text');
82 expect(r.exitCode).not.toBe(0);
83 expect(r.stderr).not.toMatch(/three lowercase words/i);
84 expect(r.stderr).not.toMatch(/body is required/i);
85 });
86
87 test('errors when no beacon set', () => {
88 const tmp = join(tmpdir(), '.test-ship-beacon-' + Math.random().toString(36).slice(2));
89 mkdirSync(tmp, { recursive: true });
90 const r = run('ship --title "Hi" --description "desc" --ref "one-two-three" --did "did:plc:abc"', tmp, agentEnv, 'body text');
91 expect(r.exitCode).not.toBe(0);
92 expect(r.stderr).toContain('no beacon set');
93 rmSync(tmp, { recursive: true, force: true });
94 });
95});