227 lines
6.4 KiB
Go
227 lines
6.4 KiB
Go
package store
|
|
|
|
import (
|
|
"errors"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func sampleNode(id, publicKey string) Node {
|
|
return Node{
|
|
ID: id,
|
|
Hostname: "host-" + id,
|
|
PublicKey: publicKey,
|
|
IPAddress: "100.64.0.42",
|
|
}
|
|
}
|
|
|
|
func mustNode(t *testing.T, s *Store, id, tenantID, publicKey string) Node {
|
|
t.Helper()
|
|
n := sampleNode(id, publicKey)
|
|
n.TenantID = tenantID
|
|
created, err := s.CreateNode(ctx, n)
|
|
if err != nil {
|
|
t.Fatalf("CreateNode(%s): %v", id, err)
|
|
}
|
|
return created
|
|
}
|
|
|
|
func TestCreateNode_RoundTrip(t *testing.T) {
|
|
s, clock := openTest(t)
|
|
mustTenant(t, s, "t1")
|
|
n := sampleNode("node-proxmox-vm-42", "MCowBQYDK2VwAyEAabc=")
|
|
n.TenantID = "t1"
|
|
|
|
created, err := s.CreateNode(ctx, n)
|
|
if err != nil {
|
|
t.Fatalf("CreateNode: %v", err)
|
|
}
|
|
if created.Status != NodeOnline {
|
|
t.Errorf("Default-Status = %q, erwartet online", created.Status)
|
|
}
|
|
if !created.LastSeen.Equal(clock.now()) {
|
|
t.Errorf("LastSeen = %v, erwartet jetzt", created.LastSeen)
|
|
}
|
|
|
|
byID, err := s.GetNode(ctx, "node-proxmox-vm-42")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
byKey, err := s.GetNodeByPublicKey(ctx, "MCowBQYDK2VwAyEAabc=")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(byID, created) || !reflect.DeepEqual(byKey, created) {
|
|
t.Errorf("gelesen weicht ab\n byID: %+v\nbyKey: %+v\n want: %+v", byID, byKey, created)
|
|
}
|
|
}
|
|
|
|
func TestCreateNode_KeepsExplicitValues(t *testing.T) {
|
|
s, _ := openTest(t)
|
|
mustTenant(t, s, "t1")
|
|
seen := time.Date(2026, 9, 1, 8, 0, 0, 0, time.UTC)
|
|
n := sampleNode("n1", "k1")
|
|
n.TenantID, n.Status, n.LastSeen = "t1", NodeDrained, seen
|
|
|
|
created, err := s.CreateNode(ctx, n)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if created.Status != NodeDrained || !created.LastSeen.Equal(seen) {
|
|
t.Errorf("explizite Werte überschrieben: %+v", created)
|
|
}
|
|
}
|
|
|
|
func TestCreateNode_Errors(t *testing.T) {
|
|
s, _ := openTest(t)
|
|
mustTenant(t, s, "t1")
|
|
mustNode(t, s, "n1", "t1", "key1")
|
|
|
|
unknownTenant := sampleNode("n2", "key2")
|
|
unknownTenant.TenantID = "fehlt"
|
|
if _, err := s.CreateNode(ctx, unknownTenant); !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("unbekannter Mandant: erwartet ErrNotFound, bekommen %v", err)
|
|
}
|
|
|
|
dupID := sampleNode("n1", "key-neu")
|
|
dupID.TenantID = "t1"
|
|
if _, err := s.CreateNode(ctx, dupID); !errors.Is(err, ErrExists) {
|
|
t.Errorf("doppelte ID: erwartet ErrExists, bekommen %v", err)
|
|
}
|
|
|
|
dupKey := sampleNode("n-neu", "key1")
|
|
dupKey.TenantID = "t1"
|
|
if _, err := s.CreateNode(ctx, dupKey); !errors.Is(err, ErrExists) {
|
|
t.Errorf("doppelter Public Key: erwartet ErrExists, bekommen %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCreateNode_Validation(t *testing.T) {
|
|
tests := map[string]func(*Node){
|
|
"ID leer": func(n *Node) { n.ID = "" },
|
|
"Mandant leer": func(n *Node) { n.TenantID = "" },
|
|
"Hostname leer": func(n *Node) { n.Hostname = "" },
|
|
"Public Key leer": func(n *Node) { n.PublicKey = " " },
|
|
"IP leer": func(n *Node) { n.IPAddress = "" },
|
|
"unbekannter Status": func(n *Node) { n.Status = "schläft" },
|
|
}
|
|
for name, mutate := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
s, _ := openTest(t)
|
|
mustTenant(t, s, "t1")
|
|
n := sampleNode("n1", "k1")
|
|
n.TenantID = "t1"
|
|
mutate(&n)
|
|
if _, err := s.CreateNode(ctx, n); !errors.Is(err, ErrInvalid) {
|
|
t.Errorf("erwartet ErrInvalid, bekommen %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetNode_NotFound(t *testing.T) {
|
|
s, _ := openTest(t)
|
|
if _, err := s.GetNode(ctx, "fehlt"); !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("GetNode: erwartet ErrNotFound, bekommen %v", err)
|
|
}
|
|
if _, err := s.GetNodeByPublicKey(ctx, "fehlt"); !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("GetNodeByPublicKey: erwartet ErrNotFound, bekommen %v", err)
|
|
}
|
|
}
|
|
|
|
func TestListNodes(t *testing.T) {
|
|
s, _ := openTest(t)
|
|
mustTenant(t, s, "t1")
|
|
mustTenant(t, s, "t2")
|
|
mustNode(t, s, "c", "t1", "k-c")
|
|
mustNode(t, s, "a", "t1", "k-a")
|
|
mustNode(t, s, "b", "t2", "k-b")
|
|
|
|
ids := func(tenantID string) []string {
|
|
t.Helper()
|
|
nodes, err := s.ListNodes(ctx, tenantID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out := []string{}
|
|
for _, n := range nodes {
|
|
out = append(out, n.ID)
|
|
}
|
|
return out
|
|
}
|
|
if got := ids("t1"); !reflect.DeepEqual(got, []string{"a", "c"}) {
|
|
t.Errorf("t1: %v", got)
|
|
}
|
|
if got := ids(""); !reflect.DeepEqual(got, []string{"a", "b", "c"}) {
|
|
t.Errorf("alle: %v", got)
|
|
}
|
|
if got := ids("leer"); len(got) != 0 {
|
|
t.Errorf("unbekannter Mandant: %v", got)
|
|
}
|
|
}
|
|
|
|
func TestTouchNode(t *testing.T) {
|
|
s, clock := openTest(t)
|
|
mustTenant(t, s, "t1")
|
|
mustNode(t, s, "n1", "t1", "k1")
|
|
if err := s.SetNodeStatus(ctx, "n1", NodeDrained); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
clock.advance(45 * time.Second)
|
|
|
|
if err := s.TouchNode(ctx, "n1", "192.168.1.5"); err != nil {
|
|
t.Fatalf("TouchNode: %v", err)
|
|
}
|
|
got, _ := s.GetNode(ctx, "n1")
|
|
if !got.LastSeen.Equal(clock.now()) || got.IPAddress != "192.168.1.5" {
|
|
t.Errorf("last_seen=%v ip=%q", got.LastSeen, got.IPAddress)
|
|
}
|
|
if got.Status != NodeDrained {
|
|
t.Errorf("TouchNode darf den Status nicht ändern, ist %q", got.Status)
|
|
}
|
|
if err := s.TouchNode(ctx, "fehlt", "1.2.3.4"); !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("unbekannt: erwartet ErrNotFound, bekommen %v", err)
|
|
}
|
|
if err := s.TouchNode(ctx, "n1", ""); !errors.Is(err, ErrInvalid) {
|
|
t.Errorf("leere IP: erwartet ErrInvalid, bekommen %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSetNodeStatus(t *testing.T) {
|
|
s, _ := openTest(t)
|
|
mustTenant(t, s, "t1")
|
|
mustNode(t, s, "n1", "t1", "k1")
|
|
for _, st := range []NodeStatus{NodeOffline, NodeDrained, NodeOnline} {
|
|
if err := s.SetNodeStatus(ctx, "n1", st); err != nil {
|
|
t.Fatalf("%s: %v", st, err)
|
|
}
|
|
if got, _ := s.GetNode(ctx, "n1"); got.Status != st {
|
|
t.Errorf("Status = %q, erwartet %q", got.Status, st)
|
|
}
|
|
}
|
|
if err := s.SetNodeStatus(ctx, "n1", "kaputt"); !errors.Is(err, ErrInvalid) {
|
|
t.Errorf("ungültig: erwartet ErrInvalid, bekommen %v", err)
|
|
}
|
|
if err := s.SetNodeStatus(ctx, "fehlt", NodeOnline); !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("unbekannt: erwartet ErrNotFound, bekommen %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDeleteNode(t *testing.T) {
|
|
s, _ := openTest(t)
|
|
mustTenant(t, s, "t1")
|
|
mustNode(t, s, "n1", "t1", "k1")
|
|
if err := s.DeleteNode(ctx, "n1"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.GetNode(ctx, "n1"); !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("Knoten noch da: %v", err)
|
|
}
|
|
if err := s.DeleteNode(ctx, "n1"); !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("zweites Löschen: erwartet ErrNotFound, bekommen %v", err)
|
|
}
|
|
// Public Key ist nach dem Löschen wieder frei (z. B. für re_enroll).
|
|
mustNode(t, s, "n1-neu", "t1", "k1")
|
|
}
|