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>
66 lines
2.2 KiB
Go
66 lines
2.2 KiB
Go
package config
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func env(vars map[string]string) func(string) string {
|
|
return func(key string) string { return vars[key] }
|
|
}
|
|
|
|
func TestDefaults(t *testing.T) {
|
|
c, err := parse(env(map[string]string{"NODE_NAME": "host-1"}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if c.Mode != ModePrimary || c.Port != "8080" || c.LabelPrefix != "sema.dashboard" || c.Title != "HODA" ||
|
|
c.LinkTarget != "_blank" || c.DataDir != "/data" || c.DockerHost != "unix:///var/run/docker.sock" {
|
|
t.Errorf("defaults = %+v", c)
|
|
}
|
|
if c.PeerInterval != 5*time.Second || c.ResyncInterval != time.Minute || c.IconRefresh != 24*time.Hour {
|
|
t.Errorf("default intervals = %s, %s, %s", c.PeerInterval, c.ResyncInterval, c.IconRefresh)
|
|
}
|
|
}
|
|
|
|
func TestPeers(t *testing.T) {
|
|
c, err := parse(env(map[string]string{
|
|
"PEERS": " http://10.0.0.12:8081/ , ,https://host-3.lan ",
|
|
"PEER_TOKEN": "secret",
|
|
"LABEL_PREFIX": "sema.dashboard.",
|
|
}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(c.Peers) != 2 || c.Peers[0] != "http://10.0.0.12:8081" || c.Peers[1] != "https://host-3.lan" {
|
|
t.Errorf("peers = %q", c.Peers)
|
|
}
|
|
if c.LabelPrefix != "sema.dashboard" {
|
|
t.Errorf("label prefix = %q, want the trailing dot dropped", c.LabelPrefix)
|
|
}
|
|
}
|
|
|
|
func TestInvalid(t *testing.T) {
|
|
tests := []struct {
|
|
vars map[string]string
|
|
want string
|
|
}{
|
|
{map[string]string{"MODE": "agent"}, "PEER_TOKEN is required"},
|
|
{map[string]string{"PEERS": "http://10.0.0.12:8081"}, "PEER_TOKEN is required"},
|
|
{map[string]string{"MODE": "agent", "PEER_TOKEN": "x", "PEERS": "http://a"}, "PEERS only applies"},
|
|
{map[string]string{"PEERS": "10.0.0.12:8081", "PEER_TOKEN": "x"}, "must start with http"},
|
|
{map[string]string{"MODE": "server"}, "must be primary or agent"},
|
|
{map[string]string{"PORT": "80a"}, "not a valid port"},
|
|
{map[string]string{"PEER_INTERVAL": "5"}, "not a positive duration"},
|
|
{map[string]string{"LINK_TARGET": "_top"}, "must be _blank or _self"},
|
|
{map[string]string{"LOG_LEVEL": "loud"}, "LOG_LEVEL"},
|
|
}
|
|
for _, tt := range tests {
|
|
_, err := parse(env(tt.vars))
|
|
if err == nil || !strings.Contains(err.Error(), tt.want) {
|
|
t.Errorf("parse(%v) error = %v, want one containing %q", tt.vars, err, tt.want)
|
|
}
|
|
}
|
|
}
|