CI / build (push) Successful in 30s
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>
78 lines
2.5 KiB
Go
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)
|
|
}
|
|
}
|