This repository has no description
0

Configure Feed

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

vit / test / ship-skill.test.js
5.1 kB 106 lines
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, rmSync } from 'node:fs'; 7import { tmpdir } from 'node:os'; 8import { join } from 'node:path'; 9 10const agentEnv = { CLAUDECODE: '1' }; 11 12describe('vit ship --skill', () => { 13 test('rejects when run outside a coding agent', () => { 14 const tmp = join(tmpdir(), '.test-ship-skill-gate-' + Math.random().toString(36).slice(2)); 15 mkdirSync(tmp, { recursive: true }); 16 writeFileSync(join(tmp, 'SKILL.md'), '---\nname: test\ndescription: test skill\n---\n# Test'); 17 const r = run(`ship --skill ${tmp}`, '/tmp', { CLAUDECODE: '', GEMINI_CLI: '', CODEX_CI: '' }); 18 expect(r.exitCode).not.toBe(0); 19 expect(r.stderr).toContain('should be run by a coding agent'); 20 rmSync(tmp, { recursive: true, force: true }); 21 }); 22 23 test('errors when SKILL.md is missing', () => { 24 const tmp = join(tmpdir(), '.test-ship-skill-no-md-' + Math.random().toString(36).slice(2)); 25 mkdirSync(tmp, { recursive: true }); 26 const r = run(`ship --skill ${tmp}`, '/tmp', agentEnv); 27 expect(r.exitCode).not.toBe(0); 28 expect(r.stderr).toContain('no SKILL.md found'); 29 rmSync(tmp, { recursive: true, force: true }); 30 }); 31 32 test('errors when SKILL.md has no name', () => { 33 const tmp = join(tmpdir(), '.test-ship-skill-no-name-' + Math.random().toString(36).slice(2)); 34 mkdirSync(tmp, { recursive: true }); 35 writeFileSync(join(tmp, 'SKILL.md'), '---\ndescription: test\n---\n# Test'); 36 const r = run(`ship --skill ${tmp} --did did:plc:abc`, '/tmp', agentEnv); 37 expect(r.exitCode).not.toBe(0); 38 expect(r.stderr).toContain('name'); 39 rmSync(tmp, { recursive: true, force: true }); 40 }); 41 42 test('errors when SKILL.md has no description', () => { 43 const tmp = join(tmpdir(), '.test-ship-skill-no-desc-' + Math.random().toString(36).slice(2)); 44 mkdirSync(tmp, { recursive: true }); 45 writeFileSync(join(tmp, 'SKILL.md'), '---\nname: test\n---\n# Test'); 46 const r = run(`ship --skill ${tmp} --did did:plc:abc`, '/tmp', agentEnv); 47 expect(r.exitCode).not.toBe(0); 48 expect(r.stderr).toContain('description'); 49 rmSync(tmp, { recursive: true, force: true }); 50 }); 51 52 test('rejects invalid skill name (uppercase)', () => { 53 const tmp = join(tmpdir(), '.test-ship-skill-bad-name-' + Math.random().toString(36).slice(2)); 54 mkdirSync(tmp, { recursive: true }); 55 writeFileSync(join(tmp, 'SKILL.md'), '---\nname: BadName\ndescription: test\n---\n# Test'); 56 const r = run(`ship --skill ${tmp} --did did:plc:abc`, '/tmp', agentEnv); 57 expect(r.exitCode).not.toBe(0); 58 expect(r.stderr).toContain('lowercase'); 59 rmSync(tmp, { recursive: true, force: true }); 60 }); 61 62 test('rejects skill name starting with hyphen', () => { 63 const tmp = join(tmpdir(), '.test-ship-skill-hyphen-' + Math.random().toString(36).slice(2)); 64 mkdirSync(tmp, { recursive: true }); 65 writeFileSync(join(tmp, 'SKILL.md'), '---\nname: -bad\ndescription: test\n---\n# Test'); 66 const r = run(`ship --skill ${tmp} --did did:plc:abc`, '/tmp', agentEnv); 67 expect(r.exitCode).not.toBe(0); 68 expect(r.stderr).toContain('lowercase'); 69 rmSync(tmp, { recursive: true, force: true }); 70 }); 71 72 test('passes validation with valid skill (fails at auth, not validation)', () => { 73 const tmp = join(tmpdir(), '.test-ship-skill-valid-' + Math.random().toString(36).slice(2)); 74 mkdirSync(tmp, { recursive: true }); 75 writeFileSync(join(tmp, 'SKILL.md'), '---\nname: test-skill\ndescription: a test skill\n---\n# Test'); 76 const r = run(`ship --skill ${tmp} --did did:plc:abc`, '/tmp', agentEnv); 77 expect(r.exitCode).not.toBe(0); 78 // Should fail at auth, not at validation 79 expect(r.stderr).not.toContain('no SKILL.md'); 80 expect(r.stderr).not.toContain('name'); 81 expect(r.stderr).not.toContain('description'); 82 rmSync(tmp, { recursive: true, force: true }); 83 }); 84 85 test('can parse the existing vit skill SKILL.md', () => { 86 // Use the real skill directory in the vit repo 87 const skillDir = join(import.meta.dir, '..', 'skills', 'vit'); 88 const r = run(`ship --skill ${skillDir} --did did:plc:abc`, '/tmp', agentEnv); 89 expect(r.exitCode).not.toBe(0); 90 // Should fail at auth, not at SKILL.md parsing 91 expect(r.stderr).not.toContain('no SKILL.md'); 92 expect(r.stderr).not.toContain('frontmatter must include'); 93 expect(r.stderr).not.toContain('skill name must be'); 94 }); 95 96 test('does not require beacon for skill shipping', () => { 97 const tmp = join(tmpdir(), '.test-ship-skill-no-beacon-' + Math.random().toString(36).slice(2)); 98 mkdirSync(tmp, { recursive: true }); 99 writeFileSync(join(tmp, 'SKILL.md'), '---\nname: test-skill\ndescription: a test skill\n---\n# Test'); 100 const r = run(`ship --skill ${tmp} --did did:plc:abc`, tmp, agentEnv); 101 expect(r.exitCode).not.toBe(0); 102 // Should NOT fail due to beacon 103 expect(r.stderr).not.toContain('no beacon set'); 104 rmSync(tmp, { recursive: true, force: true }); 105 }); 106});