Files
HODA/internal/apps/store_test.go
T
TMCandClaude Opus 5.5 9d6a921c34
CI / build (push) Successful in 30s
Start HODA: a homelab dashboard built from Docker labels
Apps opt in with sema.dashboard.* labels in their own compose file. The
primary reads its host's containers through a read-only socket proxy,
follows Docker events, and merges in what the agent on the second host
reports. Icons come from the icon label, the dashboard-icons set or the
app's favicon, with a letter as the fallback.

Go with only the standard library, a distroless image, and a Gitea
Actions workflow copied from FiMa's: checks in the Dockerfile's check
stage, a Trivy scan, and a push to git.mariosem.es/msemes/hoda.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 21:18:20 +01:00

78 lines
2.5 KiB
Go

package apps
import (
"testing"
"time"
)
func TestSnapshotOrder(t *testing.T) {
s := NewStore()
s.SetApps(LocalSource, "host-1", []App{
{ID: "000000000001", Name: "Vaultwarden", Group: "Dev"},
{ID: "000000000002", Name: "gitea", Group: "Dev"},
{ID: "000000000003", Name: "Jellyfin", Group: "Media"},
{ID: "000000000004", Name: "Zebra", Group: "dev", Order: -1},
{ID: "000000000006", Name: "Adminer", Group: DefaultGroup},
})
s.SetReported("http://host-2:8081", HostStatus{Name: "host-2", OK: true}, []App{
{ID: "000000000005", Name: "Immich", Group: "Media", Host: "host-2"},
})
hosts, list := s.Snapshot(nil)
var names []string
for _, a := range list {
names = append(names, a.Name)
}
want := []string{"Zebra", "gitea", "Vaultwarden", "Immich", "Jellyfin", "Adminer"}
if len(names) != len(want) {
t.Fatalf("apps = %v, want %v", names, want)
}
for i := range want {
if names[i] != want[i] {
t.Fatalf("apps = %v, want %v", names, want)
}
}
if len(hosts) != 2 || hosts[0].Name != "host-1" || hosts[1].Name != "host-2" {
t.Errorf("hosts = %+v, want host-1 then host-2", hosts)
}
for _, h := range hosts {
if !h.OK || h.LastOK != nil {
t.Errorf("host %+v: a reachable host is sent without lastOk", h)
}
}
}
func TestSetErrorKeepsApps(t *testing.T) {
s := NewStore()
at := time.Date(2026, 11, 2, 14, 2, 0, 0, time.UTC)
s.now = func() time.Time { return at }
s.SetApps(LocalSource, "host-1", []App{{ID: "000000000001", Name: "Gitea"}})
s.SetError(LocalSource, "host-1")
hosts, list := s.Snapshot(nil)
if len(list) != 1 {
t.Fatalf("apps = %d, want the last known app kept", len(list))
}
if hosts[0].OK || hosts[0].LastOK == nil || !hosts[0].LastOK.Equal(at) {
t.Errorf("host = %+v, want unreachable, last OK at %s", hosts[0], at)
}
}
func TestSnapshotLocalIcons(t *testing.T) {
s := NewStore()
s.SetApps(LocalSource, "host-1", []App{{ID: "000000000001", Name: "Gitea"}})
s.SetReported("http://host-2:8081", HostStatus{Name: "host-2", OK: true}, []App{
{ID: "000000000002", Name: "Immich", Icon: "/icons/000000000002?v=abc"},
})
_, list := s.Snapshot(func(id string) string { return "/icons/" + id + "?v=local" })
for _, a := range list {
want := map[string]string{"Gitea": "/icons/000000000001?v=local", "Immich": "/icons/000000000002?v=abc"}[a.Name]
if a.Icon != want {
t.Errorf("%s icon = %q, want %q", a.Name, a.Icon, want)
}
}
if key, _, ok := s.Lookup("000000000002"); !ok || key != "http://host-2:8081" {
t.Errorf("Lookup = %q, %v; want the agent's key", key, ok)
}
}