Skip to content

Commit 53b6b75

Browse files
test: verify integrated repository contracts
1 parent 0afff04 commit 53b6b75

3 files changed

Lines changed: 118 additions & 0 deletions

File tree

control-plane/internal/skillkit/reconcile_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,60 @@ func TestInstallStopsBeforeCanonicalMutationWhenRecordedPathCleanupFails(t *test
393393
}
394394
}
395395

396+
func TestPublicOperationsStopBeforeCanonicalMutationWhenFilesystemCleanupFails(t *testing.T) {
397+
for _, op := range []struct {
398+
name string
399+
run func() error
400+
}{
401+
{"install", func() error {
402+
_, err := Install(InstallOptions{SkillName: "agentfield", Targets: []string{"codex"}, Force: true})
403+
return err
404+
}},
405+
{"install-all", func() error {
406+
_, err := InstallAll(InstallOptions{Targets: []string{"codex"}, Force: true})
407+
return err
408+
}},
409+
{"update", func() error {
410+
_, err := Update("agentfield")
411+
return err
412+
}},
413+
} {
414+
t.Run(op.name, func(t *testing.T) {
415+
aliasPath := filepath.Join(t.TempDir(), "legacy-integration")
416+
if err := os.WriteFile(aliasPath, []byte("legacy"), 0o644); err != nil {
417+
t.Fatal(err)
418+
}
419+
state := reconciliationState(map[string]InstalledTarget{
420+
"claude-code": {Method: "symlink", Path: aliasPath},
421+
})
422+
if op.name == "update" {
423+
state.Skills["agentfield"] = InstalledSkill{Targets: map[string]InstalledTarget{
424+
"codex": {Method: "marker-block", Path: filepath.Join(t.TempDir(), "codex.md")},
425+
}}
426+
}
427+
home := setupReconciliation(t, state)
428+
oldRemove := reconcileRemove
429+
reconcileRemove = func(string) error { return errors.New("forced filesystem remove failure") }
430+
t.Cleanup(func() { reconcileRemove = oldRemove })
431+
432+
err := op.run()
433+
if err == nil || !strings.Contains(err.Error(), legacyBuilder) || !strings.Contains(err.Error(), "claude-code") {
434+
t.Fatalf("%s error = %v, want orphan and target", op.name, err)
435+
}
436+
if _, err := os.Lstat(filepath.Join(home, "skills", "agentfield")); !os.IsNotExist(err) {
437+
t.Fatalf("%s mutated canonical skill before filesystem cleanup failure: %v", op.name, err)
438+
}
439+
got, err := LoadState()
440+
if err != nil {
441+
t.Fatal(err)
442+
}
443+
if _, ok := got.Skills[legacyBuilder]; !ok {
444+
t.Fatalf("%s removed retryable orphan state after filesystem cleanup failure", op.name)
445+
}
446+
})
447+
}
448+
}
449+
396450
func TestPublicOperationsStopBeforeCanonicalMutationOnUnsupportedOrphan(t *testing.T) {
397451
for _, op := range []struct {
398452
name string

control-plane/internal/skillkit/skill_mirror_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"io/fs"
66
"os"
7+
"os/exec"
78
"path/filepath"
89
"runtime"
910
"sort"
@@ -77,6 +78,29 @@ func TestDeprecatedBuilderAliasRemainsOnlyAnAlias(t *testing.T) {
7778
}
7879
}
7980

81+
// Keep the repository's generation check executable from the Go suite as well
82+
// as comparing the resulting trees above. This catches a future mismatch
83+
// between the catalog's shipped skills and the sync script's mirror list.
84+
func TestEmbeddedSkillSyncCheck(t *testing.T) {
85+
bash, err := exec.LookPath("bash")
86+
if err != nil {
87+
t.Skipf("bash unavailable; sync checker is exercised in Bash-capable CI: %v", err)
88+
}
89+
repoRoot := filepath.Dir(filepath.Dir(skillSourceDirectory(t, "agentfield")))
90+
command := exec.Command(bash, "scripts/sync-embedded-skills.sh", "--check")
91+
command.Dir = repoRoot
92+
output, err := command.CombinedOutput()
93+
if err != nil {
94+
if strings.Contains(string(output), "execvpe(/bin/bash) failed") {
95+
t.Skipf("Bash launcher is present but its WSL runtime is unavailable; sync checker is exercised in Bash-capable CI: %s", output)
96+
}
97+
t.Fatalf("scripts/sync-embedded-skills.sh --check failed: %v\n%s", err, output)
98+
}
99+
if !strings.Contains(string(output), "All embedded skills are in sync with sources.") {
100+
t.Fatalf("scripts/sync-embedded-skills.sh --check output = %q, want success confirmation", output)
101+
}
102+
}
103+
80104
func skillSourceDirectory(t *testing.T, name string) string {
81105
t.Helper()
82106
_, file, _, ok := runtime.Caller(0)

control-plane/internal/templates/rendered_manifest_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,43 @@ func TestRenderedScaffoldManifestRejectsEmptyNodeID(t *testing.T) {
132132
t.Fatal("ParsePackageMetadata accepted an empty rendered node ID")
133133
}
134134
}
135+
136+
// String template values may come from user-entered project metadata. Exercise
137+
// control characters as well as punctuation so the YAML quoting helper cannot
138+
// accidentally turn a newline or tab into YAML structure.
139+
func TestRenderedScaffoldManifestEscapesControlCharacters(t *testing.T) {
140+
data := TemplateData{
141+
ProjectName: "project: first line\nsecond line # &",
142+
NodeID: "control-character-node",
143+
AuthorName: "Author\t\"quoted\"",
144+
AgentPort: 1,
145+
}
146+
147+
for _, language := range GetSupportedLanguages() {
148+
t.Run(language, func(t *testing.T) {
149+
tmpl, err := GetTemplate(language + "/agentfield-package.yaml.tmpl")
150+
if err != nil {
151+
t.Fatal(err)
152+
}
153+
var rendered bytes.Buffer
154+
if err := tmpl.Execute(&rendered, data); err != nil {
155+
t.Fatal(err)
156+
}
157+
158+
dir := t.TempDir()
159+
if err := os.WriteFile(filepath.Join(dir, "agentfield-package.yaml"), rendered.Bytes(), 0o644); err != nil {
160+
t.Fatal(err)
161+
}
162+
metadata, err := packages.ParsePackageMetadata(dir)
163+
if err != nil {
164+
t.Fatalf("ParsePackageMetadata: %v\n%s", err, rendered.String())
165+
}
166+
if metadata.Author != data.AuthorName {
167+
t.Errorf("author = %q, want %q", metadata.Author, data.AuthorName)
168+
}
169+
if !strings.Contains(metadata.Description, data.ProjectName) {
170+
t.Errorf("description = %q, want it to retain %q", metadata.Description, data.ProjectName)
171+
}
172+
})
173+
}
174+
}

0 commit comments

Comments
 (0)