194 lines
5.9 KiB
Go
194 lines
5.9 KiB
Go
package installmeta
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"os/user"
|
|
"path/filepath"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestSaveLoadRoundTrip(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "install.toml")
|
|
want := Metadata{
|
|
OwnerUser: "dev",
|
|
OwnerUID: 1000,
|
|
OwnerGID: 1000,
|
|
OwnerHome: "/home/dev",
|
|
InstalledAt: time.Unix(1710000000, 0).UTC(),
|
|
Version: "v1.2.3",
|
|
Commit: "abc123",
|
|
BuiltAt: "2026-04-23T00:00:00Z",
|
|
}
|
|
|
|
if err := Save(path, want); err != nil {
|
|
t.Fatalf("Save: %v", err)
|
|
}
|
|
got, err := Load(path)
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
if got != want {
|
|
t.Fatalf("Load() = %+v, want %+v", got, want)
|
|
}
|
|
}
|
|
|
|
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 {
|
|
t.Fatal("Validate() = nil, want missing owner_user error")
|
|
}
|
|
}
|