|
3 | 3 | package main |
4 | 4 |
|
5 | 5 | import ( |
| 6 | + "io" |
6 | 7 | "os" |
7 | 8 | "path/filepath" |
8 | 9 | "strings" |
9 | 10 | "testing" |
10 | 11 | ) |
11 | 12 |
|
12 | 13 | // runInit is a test helper that sets initTools to the given list, invokes the |
13 | | -// cobra Run closure against dir, then restores the original value. The |
14 | | -// function panics if initListTools or initAllTools are left non-default from a |
15 | | -// previous test. |
| 14 | +// cobra Run closure against dir, then restores the original values. |
| 15 | +// vsixData is set to nil during the call to prevent installVSCodeExtension from |
| 16 | +// invoking the external 'code' CLI or writing .vsix files on CI / dev machines. |
16 | 17 | func runInit(t *testing.T, tools []string, dir string) { |
17 | 18 | t.Helper() |
18 | | - prev := initTools |
19 | | - t.Cleanup(func() { initTools = prev }) |
| 19 | + prevTools := initTools |
| 20 | + prevVsix := vsixData |
| 21 | + t.Cleanup(func() { |
| 22 | + initTools = prevTools |
| 23 | + vsixData = prevVsix |
| 24 | + }) |
20 | 25 | initTools = tools |
| 26 | + vsixData = nil // disable VS Code extension installation during tests |
21 | 27 | initCmd.Run(initCmd, []string{dir}) |
22 | 28 | } |
23 | 29 |
|
@@ -72,11 +78,11 @@ func TestWrapSkillContent_FrontmatterPresent(t *testing.T) { |
72 | 78 | if !strings.HasPrefix(text, "---\n") { |
73 | 79 | t.Errorf("wrapped content should start with '---\\n', got: %q", text[:min(40, len(text))]) |
74 | 80 | } |
75 | | - if !strings.Contains(text, "name: my-skill\n") { |
76 | | - t.Error("frontmatter should contain 'name: my-skill'") |
| 81 | + if !strings.Contains(text, "name: 'my-skill'\n") { |
| 82 | + t.Error("frontmatter should contain 'name: 'my-skill''") |
77 | 83 | } |
78 | | - if !strings.Contains(text, "description: My Skill\n") { |
79 | | - t.Error("frontmatter should contain 'description: My Skill'") |
| 84 | + if !strings.Contains(text, "description: 'My Skill'\n") { |
| 85 | + t.Error("frontmatter should contain 'description: 'My Skill''") |
80 | 86 | } |
81 | 87 | if !strings.Contains(text, "compatibility: opencode\n") { |
82 | 88 | t.Error("frontmatter should contain 'compatibility: opencode'") |
@@ -217,8 +223,8 @@ func TestInitOpenCode_EachSkillHasValidFrontmatter(t *testing.T) { |
217 | 223 | if !strings.HasPrefix(text, "---\n") { |
218 | 224 | t.Errorf("skill %q: SKILL.md should start with YAML frontmatter '---'", e.Name()) |
219 | 225 | } |
220 | | - if !strings.Contains(text, "name: "+e.Name()) { |
221 | | - t.Errorf("skill %q: SKILL.md frontmatter should contain 'name: %s'", e.Name(), e.Name()) |
| 226 | + if !strings.Contains(text, "name: '"+e.Name()+"'") { |
| 227 | + t.Errorf("skill %q: SKILL.md frontmatter should contain 'name: '%s''", e.Name(), e.Name()) |
222 | 228 | } |
223 | 229 | if !strings.Contains(text, "compatibility: opencode") { |
224 | 230 | t.Errorf("skill %q: SKILL.md should contain 'compatibility: opencode'", e.Name()) |
@@ -334,3 +340,63 @@ func TestInitBothTools_CreatesAllFiles(t *testing.T) { |
334 | 340 | t.Error(".claude/lint-rules/ should contain lint rule files") |
335 | 341 | } |
336 | 342 | } |
| 343 | + |
| 344 | +// runInitAllTools is like runInit but exercises the --all-tools path. |
| 345 | +func runInitAllTools(t *testing.T, dir string) { |
| 346 | + t.Helper() |
| 347 | + prevTools := initTools |
| 348 | + prevAll := initAllTools |
| 349 | + prevVsix := vsixData |
| 350 | + t.Cleanup(func() { |
| 351 | + initTools = prevTools |
| 352 | + initAllTools = prevAll |
| 353 | + vsixData = prevVsix |
| 354 | + }) |
| 355 | + initTools = []string{} |
| 356 | + initAllTools = true |
| 357 | + vsixData = nil |
| 358 | + initCmd.Run(initCmd, []string{dir}) |
| 359 | +} |
| 360 | + |
| 361 | +func TestInitAllTools_CreatesAllFilesWithoutDuplicateLintRules(t *testing.T) { |
| 362 | + dir := t.TempDir() |
| 363 | + // Capture stdout to count lint-rule log lines. |
| 364 | + origStdout := os.Stdout |
| 365 | + r, w, _ := os.Pipe() |
| 366 | + os.Stdout = w |
| 367 | + |
| 368 | + runInitAllTools(t, dir) |
| 369 | + |
| 370 | + w.Close() |
| 371 | + os.Stdout = origStdout |
| 372 | + outBytes, _ := io.ReadAll(r) |
| 373 | + output := string(outBytes) |
| 374 | + |
| 375 | + // Claude artifacts |
| 376 | + if !fileExists(filepath.Join(dir, "CLAUDE.md")) { |
| 377 | + t.Error("CLAUDE.md should exist with --all-tools") |
| 378 | + } |
| 379 | + if !fileExists(filepath.Join(dir, ".claude", "settings.json")) { |
| 380 | + t.Error(".claude/settings.json should exist with --all-tools") |
| 381 | + } |
| 382 | + if n := countFilesInDir(filepath.Join(dir, ".claude", "commands")); n == 0 { |
| 383 | + t.Error(".claude/commands/ should contain command files with --all-tools") |
| 384 | + } |
| 385 | + |
| 386 | + // OpenCode artifacts |
| 387 | + if !fileExists(filepath.Join(dir, "opencode.json")) { |
| 388 | + t.Error("opencode.json should exist with --all-tools") |
| 389 | + } |
| 390 | + if n := countSubDirs(filepath.Join(dir, ".opencode", "skills")); n == 0 { |
| 391 | + t.Error(".opencode/skills/ should contain skill dirs with --all-tools") |
| 392 | + } |
| 393 | + |
| 394 | + // Lint rules must exist and the creation message must appear exactly once. |
| 395 | + if n := countFilesInDir(filepath.Join(dir, ".claude", "lint-rules")); n == 0 { |
| 396 | + t.Error(".claude/lint-rules/ should contain lint rule files with --all-tools") |
| 397 | + } |
| 398 | + lintMsgCount := strings.Count(output, "lint rule files in .claude/lint-rules/") |
| 399 | + if lintMsgCount != 1 { |
| 400 | + t.Errorf("lint rule creation message should appear exactly once, got %d:\n%s", lintMsgCount, output) |
| 401 | + } |
| 402 | +} |
0 commit comments