From 7e528f30b375e65c2ec162d73e7376ce9490c7c5 Mon Sep 17 00:00:00 2001 From: Thales Maciel Date: Thu, 30 Apr 2026 10:49:22 -0300 Subject: [PATCH] test: add installmeta tests --- internal/installmeta/installmeta_test.go | 155 +++++++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/internal/installmeta/installmeta_test.go b/internal/installmeta/installmeta_test.go index 3901d88..1b9044c 100644 --- a/internal/installmeta/installmeta_test.go +++ b/internal/installmeta/installmeta_test.go @@ -1,7 +1,11 @@ package installmeta import ( + "errors" + "os" + "os/user" "path/filepath" + "strconv" "testing" "time" ) @@ -31,6 +35,157 @@ func TestSaveLoadRoundTrip(t *testing.T) { } } +func TestSaveCreatesParentDir(t *testing.T) { + path := filepath.Join(t.TempDir(), "nested", "dir", "install.toml") + meta := Metadata{OwnerUser: "dev", OwnerUID: 1, OwnerGID: 1, OwnerHome: "/home/dev"} + if err := Save(path, meta); err != nil { + t.Fatalf("Save: %v", err) + } + if _, err := os.Stat(path); err != nil { + t.Fatalf("file not written: %v", err) + } +} + +func TestSaveRejectsInvalidMetadata(t *testing.T) { + path := filepath.Join(t.TempDir(), "install.toml") + if err := Save(path, Metadata{OwnerUID: 1, OwnerGID: 1, OwnerHome: "/home/dev"}); err == nil { + t.Fatal("Save() = nil, want validation error") + } + if _, err := os.Stat(path); !errors.Is(err, os.ErrNotExist) { + t.Fatalf("Save wrote a file despite validation error: stat err = %v", err) + } +} + +func TestLoadMissingFile(t *testing.T) { + _, err := Load(filepath.Join(t.TempDir(), "missing.toml")) + if !errors.Is(err, os.ErrNotExist) { + t.Fatalf("Load() = %v, want os.ErrNotExist", err) + } +} + +func TestLoadInvalidTOML(t *testing.T) { + path := filepath.Join(t.TempDir(), "install.toml") + if err := os.WriteFile(path, []byte("not = valid = toml\n"), 0o644); err != nil { + t.Fatal(err) + } + if _, err := Load(path); err == nil { + t.Fatal("Load() = nil, want TOML parse error") + } +} + +func TestLoadRejectsInvalidPersistedMetadata(t *testing.T) { + // File parses but fails Validate (no owner_user) — Load must surface + // the validation error rather than returning a zero-value Metadata. + path := filepath.Join(t.TempDir(), "install.toml") + if err := os.WriteFile(path, []byte("owner_uid = 1\nowner_gid = 1\nowner_home = \"/home/dev\"\n"), 0o644); err != nil { + t.Fatal(err) + } + if _, err := Load(path); err == nil { + t.Fatal("Load() = nil, want validation error") + } +} + +func TestValidate(t *testing.T) { + tests := []struct { + name string + m Metadata + ok bool + }{ + {"valid", Metadata{OwnerUser: "dev", OwnerUID: 1, OwnerGID: 1, OwnerHome: "/home/dev"}, true}, + {"missing owner_user", Metadata{OwnerUID: 1, OwnerGID: 1, OwnerHome: "/home/dev"}, false}, + {"whitespace owner_user", Metadata{OwnerUser: " ", OwnerUID: 1, OwnerGID: 1, OwnerHome: "/home/dev"}, false}, + {"negative uid", Metadata{OwnerUser: "dev", OwnerUID: -1, OwnerGID: 1, OwnerHome: "/home/dev"}, false}, + {"negative gid", Metadata{OwnerUser: "dev", OwnerUID: 1, OwnerGID: -1, OwnerHome: "/home/dev"}, false}, + {"empty home", Metadata{OwnerUser: "dev", OwnerUID: 1, OwnerGID: 1, OwnerHome: ""}, false}, + {"relative home", Metadata{OwnerUser: "dev", OwnerUID: 1, OwnerGID: 1, OwnerHome: "home/dev"}, false}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.m.Validate() + if tc.ok && err != nil { + t.Fatalf("Validate() = %v, want nil", err) + } + if !tc.ok && err == nil { + t.Fatal("Validate() = nil, want error") + } + }) + } +} + +func TestLookupOwnerEmpty(t *testing.T) { + if _, err := LookupOwner(""); err == nil { + t.Fatal("LookupOwner(\"\") = nil, want error") + } + if _, err := LookupOwner(" "); err == nil { + t.Fatal("LookupOwner(\" \") = nil, want error") + } +} + +func TestLookupOwnerMissing(t *testing.T) { + if _, err := LookupOwner("definitely-no-such-user-banger-test"); err == nil { + t.Fatal("LookupOwner(missing) = nil, want error") + } +} + +func TestLookupOwnerCurrentUser(t *testing.T) { + cur, err := user.Current() + if err != nil { + t.Skipf("user.Current: %v", err) + } + got, err := LookupOwner(cur.Username) + if err != nil { + t.Fatalf("LookupOwner(%q): %v", cur.Username, err) + } + wantUID, _ := strconv.Atoi(cur.Uid) + wantGID, _ := strconv.Atoi(cur.Gid) + if got.OwnerUser != cur.Username || got.OwnerUID != wantUID || got.OwnerGID != wantGID || got.OwnerHome != cur.HomeDir { + t.Fatalf("LookupOwner = %+v, want user=%s uid=%d gid=%d home=%s", + got, cur.Username, wantUID, wantGID, cur.HomeDir) + } +} + +func TestUpdateBuildInfo(t *testing.T) { + path := filepath.Join(t.TempDir(), "install.toml") + original := Metadata{ + OwnerUser: "dev", + OwnerUID: 1000, + OwnerGID: 1000, + OwnerHome: "/home/dev", + InstalledAt: time.Unix(1710000000, 0).UTC(), + Version: "v0.1.0", + Commit: "old", + BuiltAt: "2026-01-01T00:00:00Z", + } + if err := Save(path, original); err != nil { + t.Fatalf("Save: %v", err) + } + + if err := UpdateBuildInfo(path, " v0.2.0 ", " new ", " 2026-04-30T00:00:00Z "); err != nil { + t.Fatalf("UpdateBuildInfo: %v", err) + } + + got, err := Load(path) + if err != nil { + t.Fatalf("Load: %v", err) + } + if got.Version != "v0.2.0" || got.Commit != "new" || got.BuiltAt != "2026-04-30T00:00:00Z" { + t.Fatalf("build fields = %q/%q/%q, want trimmed values", got.Version, got.Commit, got.BuiltAt) + } + // Identity must be preserved. + if got.OwnerUser != original.OwnerUser || got.OwnerUID != original.OwnerUID || + got.OwnerGID != original.OwnerGID || got.OwnerHome != original.OwnerHome || + !got.InstalledAt.Equal(original.InstalledAt) { + t.Fatalf("identity changed: got %+v, want %+v", got, original) + } +} + +func TestUpdateBuildInfoMissingFile(t *testing.T) { + err := UpdateBuildInfo(filepath.Join(t.TempDir(), "missing.toml"), "v1", "c", "t") + if !errors.Is(err, os.ErrNotExist) { + t.Fatalf("UpdateBuildInfo() = %v, want os.ErrNotExist", err) + } +} + func TestValidateRejectsMissingOwner(t *testing.T) { err := Metadata{OwnerUID: 1000, OwnerGID: 1000, OwnerHome: "/home/dev"}.Validate() if err == nil {