[core-be-agent] bundle: add bundle_helpers_test.go — 17 cases for pure helpers
Some checks failed
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 10s
CI / Detect changes (pull_request) Successful in 30s
E2E API Smoke Test / detect-changes (pull_request) Successful in 41s
Harness Replays / detect-changes (pull_request) Successful in 17s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 51s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 14s
qa-review / approved (pull_request) Failing after 13s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 44s
gate-check-v3 / gate-check (pull_request) Successful in 23s
security-review / approved (pull_request) Failing after 14s
sop-checklist / all-items-acked (pull_request) acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4 — body-unfilled: 7
sop-checklist-gate / gate (pull_request) Successful in 13s
sop-tier-check / tier-check (pull_request) Successful in 14s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 45s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m16s
CI / Canvas (Next.js) (pull_request) Successful in 14s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 12s
CI / Python Lint & Test (pull_request) Successful in 10s
Harness Replays / Harness Replays (pull_request) Successful in 6s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 11s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 9s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 1m29s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Failing after 2m35s
CI / Platform (Go) (pull_request) Failing after 2m56s
CI / all-required (pull_request) Successful in 1s
Some checks failed
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 10s
CI / Detect changes (pull_request) Successful in 30s
E2E API Smoke Test / detect-changes (pull_request) Successful in 41s
Harness Replays / detect-changes (pull_request) Successful in 17s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 51s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 14s
qa-review / approved (pull_request) Failing after 13s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 44s
gate-check-v3 / gate-check (pull_request) Successful in 23s
security-review / approved (pull_request) Failing after 14s
sop-checklist / all-items-acked (pull_request) acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4 — body-unfilled: 7
sop-checklist-gate / gate (pull_request) Successful in 13s
sop-tier-check / tier-check (pull_request) Successful in 14s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 45s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m16s
CI / Canvas (Next.js) (pull_request) Successful in 14s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 12s
CI / Python Lint & Test (pull_request) Successful in 10s
Harness Replays / Harness Replays (pull_request) Successful in 6s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 11s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 9s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 1m29s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Failing after 2m35s
CI / Platform (Go) (pull_request) Failing after 2m56s
CI / all-required (pull_request) Successful in 1s
Tests: - splitLines: basic, trailing newline, empty, single char - extractDescription: plain text, after frontmatter, skips comments, only comments, empty, frontmatter-only - nilIfEmpty: empty→nil, non-empty→same - buildBundleConfigFiles: system prompt, config.yaml prompts, skill files, combined, empty bundle - findConfigDir: exact name match, fallback to first, no dirs→"", unreadable dir→"" No go binary in container — validated by CI.
This commit is contained in:
parent
591f643e89
commit
dcef8d3a79
243
workspace-server/internal/bundle/bundle_helpers_test.go
Normal file
243
workspace-server/internal/bundle/bundle_helpers_test.go
Normal file
@ -0,0 +1,243 @@
|
||||
package bundle
|
||||
|
||||
// bundle_helpers_test.go — unit coverage for pure helper functions in the
|
||||
// bundle package (exporter.go, importer.go).
|
||||
//
|
||||
// Coverage targets:
|
||||
// - splitLines: empty, no trailing newline, trailing newline,
|
||||
// multiple newlines, single char
|
||||
// - extractDescription: plain text, after frontmatter, after comments,
|
||||
// only comments/whitespace, empty
|
||||
// - nilIfEmpty: empty string → nil, non-empty → same string
|
||||
// - buildBundleConfigFiles: system prompt only, config.yaml prompt,
|
||||
// skill files, combined, empty bundle
|
||||
// - findConfigDir: exact name match, fallback to first dir,
|
||||
// no match returns fallback, unreadable dir returns ""
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// ---------- splitLines ----------
|
||||
|
||||
func TestSplitLines_Basic(t *testing.T) {
|
||||
got := splitLines("a\nb\nc")
|
||||
want := []string{"a", "b", "c"}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("len=%d; want %d", len(got), len(want))
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("got[%d]=%q; want %q", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitLines_TrailingNewline(t *testing.T) {
|
||||
got := splitLines("a\nb\n")
|
||||
want := []string{"a", "b"}
|
||||
if len(got) != len(want) {
|
||||
t.Errorf("trailing newline should not produce extra empty string; got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitLines_Empty(t *testing.T) {
|
||||
got := splitLines("")
|
||||
want := []string{""}
|
||||
if len(got) != len(want) {
|
||||
t.Errorf("empty string should produce one empty-string element; got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitLines_SingleCharNoNewline(t *testing.T) {
|
||||
got := splitLines("x")
|
||||
want := []string{"x"}
|
||||
if len(got) != 1 || got[0] != "x" {
|
||||
t.Errorf("single char; got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- extractDescription ----------
|
||||
|
||||
func TestExtractDescription_PlainText(t *testing.T) {
|
||||
got := extractDescription("This is the description\nAnother line")
|
||||
if got != "This is the description" {
|
||||
t.Errorf("got %q; want %q", got, "This is the description")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractDescription_AfterFrontmatter(t *testing.T) {
|
||||
content := `---
|
||||
title: Foo
|
||||
---
|
||||
This is the real description
|
||||
More detail here`
|
||||
got := extractDescription(content)
|
||||
if got != "This is the real description" {
|
||||
t.Errorf("got %q; want %q", got, "This is the real description")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractDescription_SkipsComments(t *testing.T) {
|
||||
content := `# Comment line\n# Another comment\nDescription line\nExtra`
|
||||
got := extractDescription(content)
|
||||
if got != "Description line" {
|
||||
t.Errorf("got %q; want %q", got, "Description line")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractDescription_OnlyComments(t *testing.T) {
|
||||
got := extractDescription("# Comment\n# Another")
|
||||
if got != "" {
|
||||
t.Errorf("only comments → want empty; got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractDescription_Empty(t *testing.T) {
|
||||
got := extractDescription("")
|
||||
if got != "" {
|
||||
t.Errorf("empty → want empty; got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractDescription_FrontmatterOnly(t *testing.T) {
|
||||
content := "---\nkey: value\n---"
|
||||
got := extractDescription(content)
|
||||
if got != "" {
|
||||
t.Errorf("frontmatter only → want empty; got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- nilIfEmpty ----------
|
||||
|
||||
func TestNilIfEmpty_Empty(t *testing.T) {
|
||||
got := nilIfEmpty("")
|
||||
if got != nil {
|
||||
t.Errorf("nilIfEmpty(\"\") = %v; want nil", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNilIfEmpty_NonEmpty(t *testing.T) {
|
||||
got := nilIfEmpty("hello")
|
||||
if got != "hello" {
|
||||
t.Errorf("nilIfEmpty(\"hello\") = %v; want \"hello\"", got)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- buildBundleConfigFiles ----------
|
||||
|
||||
func TestBuildBundleConfigFiles_SystemPrompt(t *testing.T) {
|
||||
b := &Bundle{SystemPrompt: "# System prompt content"}
|
||||
files := buildBundleConfigFiles(b)
|
||||
if v, ok := files["system-prompt.md"]; !ok {
|
||||
t.Error("system-prompt.md missing")
|
||||
} else if string(v) != "# System prompt content" {
|
||||
t.Errorf("system-prompt.md = %q; want %q", v, "# System prompt content")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildBundleConfigFiles_ConfigYaml(t *testing.T) {
|
||||
b := &Bundle{Prompts: map[string]string{"config.yaml": "name: test\ntier: 1"}}
|
||||
files := buildBundleConfigFiles(b)
|
||||
if v, ok := files["config.yaml"]; !ok {
|
||||
t.Error("config.yaml missing from prompts")
|
||||
} else if string(v) != "name: test\ntier: 1" {
|
||||
t.Errorf("config.yaml = %q; want %q", v, "name: test\ntier: 1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildBundleConfigFiles_SkillFiles(t *testing.T) {
|
||||
b := &Bundle{
|
||||
Skills: []BundleSkill{
|
||||
{ID: "my-skill", Files: map[string]string{
|
||||
"SKILL.md": "# My Skill",
|
||||
"prompt.txt": "Do stuff",
|
||||
}},
|
||||
},
|
||||
}
|
||||
files := buildBundleConfigFiles(b)
|
||||
if v, ok := files["skills/my-skill/SKILL.md"]; !ok {
|
||||
t.Error("skills/my-skill/SKILL.md missing")
|
||||
} else if string(v) != "# My Skill" {
|
||||
t.Errorf("skills/my-skill/SKILL.md = %q; want %q", v, "# My Skill")
|
||||
}
|
||||
if v, ok := files["skills/my-skill/prompt.txt"]; !ok {
|
||||
t.Error("skills/my-skill/prompt.txt missing")
|
||||
} else if string(v) != "Do stuff" {
|
||||
t.Errorf("skills/my-skill/prompt.txt = %q; want %q", v, "Do stuff")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildBundleConfigFiles_Combined(t *testing.T) {
|
||||
b := &Bundle{
|
||||
SystemPrompt: "System",
|
||||
Prompts: map[string]string{"config.yaml": "cfg"},
|
||||
Skills: []BundleSkill{
|
||||
{ID: "s1", Files: map[string]string{"a.md": "A"}},
|
||||
},
|
||||
}
|
||||
files := buildBundleConfigFiles(b)
|
||||
if len(files) != 3 {
|
||||
t.Errorf("got %d files; want 3", len(files))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildBundleConfigFiles_Empty(t *testing.T) {
|
||||
b := &Bundle{}
|
||||
files := buildBundleConfigFiles(b)
|
||||
if len(files) != 0 {
|
||||
t.Errorf("empty bundle should produce no files; got %d", len(files))
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- findConfigDir ----------
|
||||
|
||||
func TestFindConfigDir_ExactMatch(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
sub := filepath.Join(dir, "ws-abc")
|
||||
if err := os.MkdirAll(sub, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(sub, "config.yaml"), []byte("name: my-workspace\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got := findConfigDir(dir, "my-workspace")
|
||||
if got != sub {
|
||||
t.Errorf("got %q; want %q", got, sub)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindConfigDir_FallbackToFirst(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
sub1 := filepath.Join(dir, "ws-1")
|
||||
sub2 := filepath.Join(dir, "ws-2")
|
||||
os.MkdirAll(sub1, 0o755)
|
||||
os.MkdirAll(sub2, 0o755)
|
||||
os.WriteFile(filepath.Join(sub1, "config.yaml"), []byte("name: other\n"), 0o644)
|
||||
os.WriteFile(filepath.Join(sub2, "config.yaml"), []byte("name: another\n"), 0o644)
|
||||
|
||||
got := findConfigDir(dir, "nonexistent")
|
||||
if got != sub1 {
|
||||
t.Errorf("no match → fallback to first; got %q; want %q", got, sub1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindConfigDir_NoMatchNoFallback(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
// No subdirectories
|
||||
got := findConfigDir(dir, "anything")
|
||||
if got != "" {
|
||||
t.Errorf("no dirs → want empty; got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindConfigDir_UnreadableDir(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
got := findConfigDir(dir, "anything")
|
||||
if got != "" {
|
||||
t.Errorf("unreadable top-level → want empty; got %q", got)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user