erste module drin.
zwischenstand: Architektur aktualisiert, TOFU implementiert,
This commit is contained in:
1 parent
9d5789c5d7
commit
c8df9d7651
41 files changed
+6455
-36
No files matched your search
@@ -0,0 +1,226 @@
|
||||
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")
|
||||
}
|
||||
Reference in new issue
Block a user