Files
agent-lib/internal/e2e/vendor_exclude_test.go
T
m3ta-chiron 0c779bc970 feat(vendor): --exclude plus content-type selection (--types/--exclude-types)
vendor add gains --exclude a,b,c (all mode minus those ids) as the mirror
of --include, for taking the majority of a source while dropping a few
items. Mutual exclusion with --include is a hard error, unknown ids fail
naming the available ones (generalized checkIDEntries).

vendor add and vendor select gain --types (allow-list) and --exclude-types
(deny-list) over the four content types. The filter is stored as the
optional, v2-compatible Selection.exclude_types field and survives vendor
update: new upstream items of an excluded type never enter the work
repository (unlike id expansion, which would silently re-admit them).
--types/--exclude-types replace the filter on select, absent flags keep
it, and --mode all clears it along with include/exclude.

Also fixes materializeTree to create the staging directory for an empty
selection — previously select-to-zero failed at the staging rename.
2026-10-02 12:35:39 +02:00

76 lines
2.3 KiB
Go

package e2e
import (
"os"
"path/filepath"
"testing"
)
// TestVendorAddExcludeMode pins --exclude: all mode minus the listed ids,
// materialized and recorded in the lockfile as mode "all" with an exclude
// list.
func TestVendorAddExcludeMode(t *testing.T) {
h := newHarness(t)
standardFixture(h, t)
h.mustRun(t, "vendor", "add", "curated", h.upstreamDir, "--exclude", "broken-skill,scout")
for _, p := range []string{
"external/curated/skills/good-skill/SKILL.md",
"external/curated/commands/review.md",
"external/curated/mcp/search.yaml",
} {
if _, err := os.Stat(filepath.Join(h.workDir, p)); err != nil {
t.Errorf("non-excluded item must be materialized: %s", p)
}
}
for _, p := range []string{
"external/curated/skills/broken-skill/SKILL.md",
"external/curated/agents/scout.md",
} {
if _, err := os.Stat(filepath.Join(h.workDir, p)); err == nil {
t.Errorf("excluded item must NOT be materialized: %s", p)
}
}
lock := readFile(t, filepath.Join(h.workDir, "agent-lib.lock.json"))
if !contains(lock, `"mode": "all"`) {
t.Errorf("exclude keeps all mode:\n%s", lock)
}
if !contains(lock, `"exclude": [`) {
t.Errorf("exclude list must be recorded:\n%s", lock)
}
}
// TestVendorAddExcludeTypoFailsClean pins the typo protection for --exclude.
func TestVendorAddExcludeTypoFailsClean(t *testing.T) {
h := newHarness(t)
standardFixture(h, t)
out, err := h.run(t, "vendor", "add", "curated", h.upstreamDir, "--exclude", "good-skill,nope")
if err == nil {
t.Fatalf("typo in exclude list must fail:\n%s", out)
}
if !contains(out, "exclude entry") && !contains(out, "not found upstream") {
t.Errorf("error must name the unknown exclude entry:\n%s", out)
}
if _, statErr := os.Stat(filepath.Join(h.workDir, "external/curated")); statErr == nil {
t.Error("failed add must not leave an external area behind")
}
}
// TestVendorAddIncludeAndExcludeConflict pins the mutual exclusion.
func TestVendorAddIncludeAndExcludeConflict(t *testing.T) {
h := newHarness(t)
standardFixture(h, t)
out, err := h.run(t, "vendor", "add", "curated", h.upstreamDir,
"--include", "good-skill", "--exclude", "scout")
if err == nil {
t.Fatalf("--include and --exclude together must fail:\n%s", out)
}
if !contains(out, "--include and --exclude cannot be combined") {
t.Errorf("error must explain the conflict:\n%s", out)
}
}