Files
cad-atlas/internal/atlas/cluster_test.go
T
mathiasandClaude Opus 4.8 3d1f76997b
CD / Lint / Test / Vet (push) Successful in 4s
CD / Build & Import (push) Successful in 13s
CD / Detect unsubstituted template (push) Successful in 0s
CD / Deploy via GitOps (push) Has been skipped
feat(atlas): Phase C — live CD/deploy state on stage 07
Stage 07 now shows the pod's own live deploy state — deployed image tag +
N/M replicas ready, green only when the rollout is fully ready — read from the
k8s Deployment in-cluster (new namespaced deployments-read Role). DeployState/
DeployNode test-first; prepended to the authored CD narrative, fallback-safe.

Verified: build/vet/lint(0)/test green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 07:39:01 +02:00

138 lines
4.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 TestDeployState_ParsesImageAndReplicas(t *testing.T) {
dep := []byte(`{"spec":{"replicas":2,"template":{"spec":{"containers":[
{"name":"cad-atlas","image":"localhost:5000/cad-atlas:3ff922a"}]}}},
"status":{"readyReplicas":1,"replicas":2}}`)
d, err := atlas.DeployState(dep)
if err != nil {
t.Fatalf("DeployState: %v", err)
}
if d.Image != "localhost:5000/cad-atlas:3ff922a" || d.Ready != 1 || d.Desired != 2 {
t.Fatalf("deploy = %+v", d)
}
if d.Tag() != "3ff922a" {
t.Fatalf("tag = %q, want 3ff922a", d.Tag())
}
}
func TestDeployNode_GreenOnlyWhenFullyReady(t *testing.T) {
ready := atlas.DeployNode(atlas.Deploy{Image: "localhost:5000/cad-atlas:abc", Ready: 1, Desired: 1})
if ready.Pill != "var(--green)" {
t.Fatalf("ready pill = %q, want green", ready.Pill)
}
if ready.Title != "◆ deployed · cad-atlas:abc · 1/1 ready" {
t.Fatalf("title = %q", ready.Title)
}
rolling := atlas.DeployNode(atlas.Deploy{Image: "x/cad-atlas:def", Ready: 0, Desired: 1})
if rolling.Pill != "var(--amber)" {
t.Fatalf("rolling pill = %q, want amber", rolling.Pill)
}
}
func TestHostsFromNodes_ErrorsOnBadJSON(t *testing.T) {
if _, err := atlas.HostsFromNodes([]byte("{not json")); err == nil {
t.Fatal("expected error on bad JSON, got nil")
}
}