package store_test import ( "context" "errors" "path/filepath" "testing" "time" "vetting/internal/db" "vetting/internal/model" "vetting/internal/store" ) func newHosts(t *testing.T) *store.Hosts { t.Helper() conn, err := db.Open(filepath.Join(t.TempDir(), "vetting.db")) if err != nil { t.Fatalf("open db: %v", err) } t.Cleanup(func() { _ = conn.Close() }) return &store.Hosts{DB: conn} } func TestHostsGetByMAC(t *testing.T) { hosts := newHosts(t) ctx := context.Background() id, err := hosts.Create(ctx, model.Host{ Name: "mac-host", MAC: "AA:BB:CC:DD:EE:01", WoLBroadcastIP: "10.0.0.255", WoLPort: 9, ExpectedSpecYAML: "memory:\n total_gib: 16\n", }) if err != nil { t.Fatalf("create: %v", err) } // Lookup normalizes case — upper-case MAC resolves same row. got, err := hosts.GetByMAC(ctx, "Aa:Bb:Cc:Dd:Ee:01") if err != nil { t.Fatalf("GetByMAC: %v", err) } if got.ID != id || got.Name != "mac-host" { t.Fatalf("wrong row: %+v", got) } if got.LastSeenAt != nil { t.Fatalf("LastSeenAt = %v, want nil on fresh host", got.LastSeenAt) } if _, err := hosts.GetByMAC(ctx, "aa:bb:cc:dd:ee:99"); !errors.Is(err, store.ErrNotFound) { t.Fatalf("GetByMAC unknown = %v, want ErrNotFound", err) } } func TestHostsUpdateLastSeen(t *testing.T) { hosts := newHosts(t) ctx := context.Background() id, err := hosts.Create(ctx, model.Host{ Name: "ls-host", MAC: "aa:bb:cc:dd:ee:02", WoLBroadcastIP: "10.0.0.255", WoLPort: 9, ExpectedSpecYAML: "memory:\n total_gib: 8\n", Notes: "keep me", }) if err != nil { t.Fatalf("create: %v", err) } stamp := time.Date(2026, 4, 17, 12, 0, 0, 0, time.UTC) if err := hosts.UpdateLastSeen(ctx, "AA:BB:CC:DD:EE:02", stamp); err != nil { t.Fatalf("UpdateLastSeen: %v", err) } got, err := hosts.Get(ctx, id) if err != nil { t.Fatalf("Get: %v", err) } if got.LastSeenAt == nil || !got.LastSeenAt.Equal(stamp) { t.Fatalf("LastSeenAt = %v, want %v", got.LastSeenAt, stamp) } // Other fields untouched — targeted UPDATE must not stomp anything. if got.Name != "ls-host" || got.Notes != "keep me" || got.WoLPort != 9 { t.Fatalf("row damaged: %+v", got) } // A second update advances the timestamp. later := stamp.Add(45 * time.Second) if err := hosts.UpdateLastSeen(ctx, got.MAC, later); err != nil { t.Fatalf("second UpdateLastSeen: %v", err) } got, _ = hosts.Get(ctx, id) if !got.LastSeenAt.Equal(later) { t.Fatalf("LastSeenAt not advanced: %v", got.LastSeenAt) } // Unknown MAC is an error, not a silent no-op — a stale agent on a // re-registered box should complain loudly. if err := hosts.UpdateLastSeen(ctx, "aa:bb:cc:dd:ee:ff", later); !errors.Is(err, store.ErrNotFound) { t.Fatalf("UpdateLastSeen unknown = %v, want ErrNotFound", err) } }