package inventory import ( "context" "errors" "os" "strings" "testing" "time" "github.com/novox/mesh-host/internal/profile" ) func TestAnUnreadableFactIsRecordedNotFatal(t *testing.T) { // An inventory missing one field is useful; an inventory that refused to be taken is not. // And an absent fact and a fact that failed to read are different things — collapsing them // loses the one worth acting on. failing := func(string) ([]byte, error) { return nil, errors.New("permission denied") } inv := Collect(context.Background(), failing, nil, time.Second) if inv.Machine == "" && len(inv.Unreadable) == 0 { t.Fatal("nothing was read and nothing was reported unreadable") } if len(inv.Unreadable) == 0 { t.Fatal("every file failed and nothing was recorded as unreadable") } for _, u := range inv.Unreadable { if !strings.Contains(u, ":") { t.Errorf("unreadable entry does not say which fact failed: %q", u) } } if inv.Architecture == "" || inv.CPUs == 0 { t.Error("facts that need no file were lost along with the ones that did") } } func TestDistributionIsReadFromTheMachineNotAssumed(t *testing.T) { cases := map[string]string{ "PRETTY_NAME=\"Arch Linux\"\nID=arch\n": "Arch Linux", "ID=arch\n": "arch", "# a comment\n\nID=debian\nPRETTY_NAME='Debian 13'\n": "Debian 13", "NAME=Weird\nPRETTY_NAME=\"Has=Equals\"\n": "Has=Equals", "": "", } for input, want := range cases { if got := distributionFrom(input); got != want { t.Errorf("distributionFrom(%q) = %q, want %q", input, got, want) } } } func TestMemoryIsReadOrReportedMissing(t *testing.T) { if kb, ok := memoryFrom("MemTotal: 32762868 kB\nMemFree: 1 kB\n"); !ok || kb != 32762868 { t.Errorf("memoryFrom returned %d, %v", kb, ok) } // A meminfo without MemTotal must not read as zero memory — a machine reporting no memory // would be excluded from placement for a parsing failure. if _, ok := memoryFrom("MemFree: 1 kB\n"); ok { t.Error("a meminfo with no MemTotal was read as a successful measurement") } if _, ok := memoryFrom("MemTotal: not-a-number kB\n"); ok { t.Error("an unparseable MemTotal was read as a successful measurement") } } func TestTheInventorySaysWhenItWasTaken(t *testing.T) { // A node unreachable for a week is an ordinary situation (novox/hq ADR 0004), so an // inventory that cannot be told from a stale one is missing the fact that matters. before := time.Now().UTC() inv := Collect(context.Background(), nil, nil, time.Second) if inv.ObservedAt.IsZero() { t.Fatal("the inventory does not say when it was observed") } if inv.ObservedAt.Before(before.Add(-time.Second)) || inv.ObservedAt.After(time.Now().UTC().Add(time.Second)) { t.Errorf("ObservedAt is not the moment of observation: %s", inv.ObservedAt) } } func TestTheHostDoesNotNameTheNode(t *testing.T) { // A node's name is assigned by the mesh. A host that named itself would be deciding // something, which is precisely what novox/hq ADR 0005 forbids it to do. inv := Collect(context.Background(), nil, nil, time.Second) hostname, _ := os.Hostname() if inv.Machine != hostname { t.Errorf("Machine is %q, not the machine's own hostname %q", inv.Machine, hostname) } } // --- against this machine --------------------------------------------------------------- func TestAgainstThisMachine_inventoryIsTrue(t *testing.T) { // novox/hq ADR 0017: behaviour against a real system is tested alongside, not mocked. inv := Collect(context.Background(), nil, profile.Default(nil), 10*time.Second) if inv.Machine == "" { t.Error("this machine did not report a name") } if inv.CPUs < 1 { t.Errorf("this machine reported %d cpus", inv.CPUs) } if inv.OS == "linux" { if inv.Kernel == "" { t.Error("a linux machine reported no kernel version") } if inv.MemoryKB <= 0 { t.Error("a linux machine reported no memory") } } if len(inv.Profile.Capabilities) == 0 { t.Error("the inventory carries no profile") } t.Logf("machine=%s dist=%q kernel=%s arch=%s cpus=%d mem=%dMB unreadable=%v", inv.Machine, inv.Distribution, inv.Kernel, inv.Architecture, inv.CPUs, inv.MemoryKB/1024, inv.Unreadable) }