Files
agent-lib/internal/lockfile/lockfile_test.go
T
m3ta-chiron 2c7200380b feat: vendor add end-to-end with deterministic lockfile v2
- go-git in-process clone, default-branch/tag resolution, optional token auth
- web-tree URL normalization (github/gitlab) with implied discovery root
- discovery of skills/commands/agents/mcp, lenient frontmatter with warnings
- selection: all by default, --include with missing-entry hard errors
- external/<source>/<type>/ materialization preserving exec bits
- lockfile v2: deterministic JSON, pinned url/ref/rev, inventory, warnings
- offline black-box e2e suite against local fixture git repositories
2026-08-22 21:52:30 +02:00

130 lines
3.1 KiB
Go

package lockfile
import (
"os"
"path/filepath"
"testing"
)
func readFile(t *testing.T, path string) string {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
return string(data)
}
func TestMarshalDeterministic(t *testing.T) {
l1 := New()
l1.Sources["beta"] = &Source{
URL: "https://example.com/beta.git", Ref: "main", Rev: "cafe123",
Selection: Selection{Mode: ModeAll},
Renames: map[string]string{"zeta": "beta-zeta"},
Inventory: map[string][]string{TypeSkill: {"a", "zeta"}},
Warnings: []string{"a-warning", "b-warning"},
}
l1.Sources["alpha"] = &Source{
URL: "https://example.com/alpha.git", Ref: "v1.0", Rev: "abcd00",
Selection: Selection{Mode: ModeInclude, Include: []string{"a", "x"}},
}
l2 := New()
l2.Sources["alpha"] = l1.Sources["alpha"]
l2.Sources["beta"] = l1.Sources["beta"]
b1, err := Marshal(l1)
if err != nil {
t.Fatal(err)
}
b2, err := Marshal(l2)
if err != nil {
t.Fatal(err)
}
if string(b1) != string(b2) {
t.Fatalf("marshal not deterministic:\n%s\n--- vs ---\n%s", b1, b2)
}
if b1[len(b1)-1] != '\n' {
t.Error("missing trailing newline")
}
l1.Save(filepath.Join(t.TempDir(), FileName))
b3, err := Marshal(l1)
if err != nil {
t.Fatal(err)
}
if string(b1) != string(b3) {
t.Fatalf("save mutated already-canonical bytes:\n%s\n--- vs ---\n%s", b1, b3)
}
}
func TestSaveCanonicalizesUnsortedInput(t *testing.T) {
l := New()
l.Sources["s"] = &Source{
URL: "u", Ref: "r", Rev: "rev",
Selection: Selection{Mode: ModeInclude, Include: []string{"z", "a"}},
Warnings: []string{"w2", "w1"},
Inventory: map[string][]string{TypeSkill: {"z", "a"}},
}
path := filepath.Join(t.TempDir(), FileName)
if err := l.Save(path); err != nil {
t.Fatal(err)
}
got, err := Load(path)
if err != nil {
t.Fatal(err)
}
src := got.Sources["s"]
wantInclude := []string{"a", "z"}
if !slicesEqual(src.Selection.Include, wantInclude) {
t.Errorf("include = %v, want %v", src.Selection.Include, wantInclude)
}
wantWarnings := []string{"w1", "w2"}
if !slicesEqual(src.Warnings, wantWarnings) {
t.Errorf("warnings = %v, want %v", src.Warnings, wantWarnings)
}
wantInv := []string{"a", "z"}
if !slicesEqual(src.Inventory[TypeSkill], wantInv) {
t.Errorf("inventory = %v, want %v", src.Inventory[TypeSkill], wantInv)
}
}
func slicesEqual(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func TestLoadRoundTrip(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, FileName)
l := New()
l.Sources["s"] = &Source{URL: "u", Ref: "r", Rev: "rev1", Selection: Selection{Mode: ModeAll}}
if err := l.Save(path); err != nil {
t.Fatal(err)
}
got, err := Load(path)
if err != nil {
t.Fatal(err)
}
if got.Version != Version || got.Sources["s"].Rev != "rev1" {
t.Fatalf("round trip lost data: %+v", got)
}
if _, err := Load(filepath.Join(dir, "missing.json")); err != ErrNotExist {
t.Fatalf("want ErrNotExist, got %v", err)
}
os.WriteFile(path, []byte(`{"version": 1, "sources": {}}`), 0o644)
if _, err := Load(path); err == nil {
t.Fatal("version 1 lockfile must be rejected")
}
}