generated from mathias/template-go-web
NamespaceSummary (test-first) renders the substrate ns line from live cluster namespaces (system-filtered, sorted, capped). Handler now overlays both node specs and the ns line via a single cached liveOverlay(). Removed the inline SUBSTRATE/STAGES/NS data — /api/atlas.json is the single source; the page shows an error banner on fetch failure instead of stale data. GPU model naming intentionally NOT done: koala's node has no GPU product label (only nvidia.com/gpu count), so "1× GPU" is the API's truth — naming the RTX 5070 would need GPU-feature-discovery, out of scope. Verified: build/vet/lint(0)/test green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
106 lines
3.3 KiB
Go
106 lines
3.3 KiB
Go
package atlas_test
|
||
|
||
import (
|
||
"reflect"
|
||
"strings"
|
||
"testing"
|
||
|
||
"git.d-ma.be/mathias/cad-atlas/internal/atlas"
|
||
)
|
||
|
||
func TestHostsFromNodes_DerivesSpecFromNodeStatus(t *testing.T) {
|
||
nodes := []byte(`{"items":[
|
||
{"metadata":{"name":"koala"},
|
||
"status":{"capacity":{"cpu":"16","memory":"67108864Ki","nvidia.com/gpu":"1"},
|
||
"nodeInfo":{"architecture":"amd64","kubeletVersion":"v1.31.4+k3s1"}}},
|
||
{"metadata":{"name":"worker2"},
|
||
"status":{"capacity":{"cpu":"8","memory":"33554432Ki"},
|
||
"nodeInfo":{"architecture":"arm64","kubeletVersion":"v1.30.0+k3s1"}}}
|
||
]}`)
|
||
|
||
hosts, err := atlas.HostsFromNodes(nodes)
|
||
if err != nil {
|
||
t.Fatalf("HostsFromNodes: %v", err)
|
||
}
|
||
|
||
want := []atlas.Host{
|
||
{Name: "koala", Spec: "amd64 · 16 cpu · 64Gi · 1× GPU · k3s v1.31.4"},
|
||
{Name: "worker2", Spec: "arm64 · 8 cpu · 32Gi · k3s v1.30.0"},
|
||
}
|
||
if !reflect.DeepEqual(hosts, want) {
|
||
t.Fatalf("hosts = %+v\nwant %+v", hosts, want)
|
||
}
|
||
}
|
||
|
||
func TestMergeSubstrate_LiveOverridesAuthoredKeepsRestAppendsNew(t *testing.T) {
|
||
authored := []atlas.Host{
|
||
{Name: "koala", Spec: "RTX 5070 · authored"},
|
||
{Name: "iguana", Spec: "M2 Ultra · Ollama / mlx"},
|
||
{Name: "piguard", Spec: "NGINX reverse-proxy · ntfy"},
|
||
}
|
||
live := []atlas.Host{
|
||
{Name: "koala", Spec: "amd64 · 16 cpu · 60Gi · 1× GPU · k3s v1.34.5"},
|
||
{Name: "worker9", Spec: "arm64 · 4 cpu"},
|
||
}
|
||
|
||
got := atlas.MergeSubstrate(authored, live)
|
||
want := []atlas.Host{
|
||
{Name: "koala", Spec: "amd64 · 16 cpu · 60Gi · 1× GPU · k3s v1.34.5"}, // live wins
|
||
{Name: "iguana", Spec: "M2 Ultra · Ollama / mlx"}, // authored kept
|
||
{Name: "piguard", Spec: "NGINX reverse-proxy · ntfy"}, // authored kept
|
||
{Name: "worker9", Spec: "arm64 · 4 cpu"}, // live-only appended
|
||
}
|
||
if !reflect.DeepEqual(got, want) {
|
||
t.Fatalf("merge = %+v\nwant %+v", got, want)
|
||
}
|
||
}
|
||
|
||
func TestMergeSubstrate_NoLiveReturnsAuthored(t *testing.T) {
|
||
authored := []atlas.Host{{Name: "koala", Spec: "authored"}}
|
||
got := atlas.MergeSubstrate(authored, nil)
|
||
if !reflect.DeepEqual(got, authored) {
|
||
t.Fatalf("merge with no live = %+v, want %+v", got, authored)
|
||
}
|
||
}
|
||
|
||
func TestNamespaceSummary_FiltersSystemSortsAndJoins(t *testing.T) {
|
||
ns := []byte(`{"items":[
|
||
{"metadata":{"name":"gitea"}},
|
||
{"metadata":{"name":"kube-system"}},
|
||
{"metadata":{"name":"ai-stack"}},
|
||
{"metadata":{"name":"default"}},
|
||
{"metadata":{"name":"flux-system"}},
|
||
{"metadata":{"name":"brain"}}
|
||
]}`)
|
||
|
||
got, err := atlas.NamespaceSummary(ns)
|
||
if err != nil {
|
||
t.Fatalf("NamespaceSummary: %v", err)
|
||
}
|
||
if want := "ns: ai-stack · brain · gitea"; got != want {
|
||
t.Fatalf("summary = %q, want %q", got, want)
|
||
}
|
||
}
|
||
|
||
func TestNamespaceSummary_CapsWithMore(t *testing.T) {
|
||
var items []string
|
||
for i := 0; i < 15; i++ {
|
||
items = append(items, `{"metadata":{"name":"app`+string(rune('a'+i))+`"}}`)
|
||
}
|
||
ns := []byte(`{"items":[` + strings.Join(items, ",") + `]}`)
|
||
|
||
got, err := atlas.NamespaceSummary(ns)
|
||
if err != nil {
|
||
t.Fatalf("NamespaceSummary: %v", err)
|
||
}
|
||
if !strings.HasSuffix(got, "· +3 more") {
|
||
t.Fatalf("expected cap suffix, got %q", got)
|
||
}
|
||
}
|
||
|
||
func TestHostsFromNodes_ErrorsOnBadJSON(t *testing.T) {
|
||
if _, err := atlas.HostsFromNodes([]byte("{not json")); err == nil {
|
||
t.Fatal("expected error on bad JSON, got nil")
|
||
}
|
||
}
|