package store import ( "errors" "reflect" "testing" "time" ) func TestLeases(t *testing.T) { s, clock := openTest(t) mustTenant(t, s, "t1") mustNode(t, s, "n1", "t1", "k1") if _, err := s.GetLease(ctx, "n1"); !errors.Is(err, ErrNotFound) { t.Errorf("ohne Lease: %v", err) } l := Lease{ NodeID: "n1", IssuedAt: clock.now(), RenewAfter: clock.now().Add(7 * 24 * time.Hour), ValidUntil: clock.now().Add(14 * 24 * time.Hour), Document: `{"node_id":"n1"}`, } if err := s.PutLease(ctx, l); err != nil { t.Fatalf("PutLease: %v", err) } got, err := s.GetLease(ctx, "n1") if err != nil || !reflect.DeepEqual(got, l) { t.Errorf("GetLease = %+v, %v", got, err) } l.ValidUntil = l.ValidUntil.Add(time.Hour) if err := s.PutLease(ctx, l); err != nil { t.Fatal(err) } if got, _ := s.GetLease(ctx, "n1"); !got.ValidUntil.Equal(l.ValidUntil) { t.Error("PutLease ersetzt nicht") } if err := s.PutLease(ctx, Lease{NodeID: "fehlt", Document: "{}"}); !errors.Is(err, ErrNotFound) { t.Errorf("unbekannter Knoten: %v", err) } s.DeleteNode(ctx, "n1") if _, err := s.GetLease(ctx, "n1"); !errors.Is(err, ErrNotFound) { t.Error("Lease nach Löschen des Knotens noch da") } } func TestTenantFeaturesAndPayment(t *testing.T) { s, clock := openTest(t) in := sampleTenant("t1") in.Features = []string{"custom_middleware", "websockets"} created, err := s.CreateTenant(ctx, in) if err != nil { t.Fatal(err) } got, _ := s.GetTenant(ctx, "t1") if !reflect.DeepEqual(got.Features, []string{"custom_middleware", "websockets"}) || !reflect.DeepEqual(got, created) { t.Errorf("Features: %+v", got.Features) } if !got.PaymentOverdueSince.IsZero() { t.Error("neuer Mandant mit offener Zahlung") } if def, _ := s.CreateTenant(ctx, sampleTenant("t2")); def.Features == nil || len(def.Features) != 0 { t.Errorf("Default-Features: %#v", def.Features) } due := clock.now().Add(-10 * 24 * time.Hour) if err := s.SetTenantPayment(ctx, "t1", due); err != nil { t.Fatal(err) } if got, _ := s.GetTenant(ctx, "t1"); !got.PaymentOverdueSince.Equal(due) { t.Errorf("offen seit: %v", got.PaymentOverdueSince) } if err := s.SetTenantPayment(ctx, "t1", time.Time{}); err != nil { t.Fatal(err) } if got, _ := s.GetTenant(ctx, "t1"); !got.PaymentOverdueSince.IsZero() { t.Error("bezahlt: Fälligkeit nicht gelöscht") } if err := s.SetTenantPayment(ctx, "fehlt", due); !errors.Is(err, ErrNotFound) { t.Errorf("unbekannter Mandant: %v", err) } bad := sampleTenant("t3") bad.Features = []string{"ok", " "} if _, err := s.CreateTenant(ctx, bad); !errors.Is(err, ErrInvalid) { t.Errorf("leeres Feature: %v", err) } } func TestEnroll_RecordsUsedAt(t *testing.T) { s, clock := openTest(t) mustTenant(t, s, "t1") s.AddEnrollmentToken(ctx, "tok", "t1", clock.now().Add(time.Hour)) clock.advance(time.Minute) if _, err := s.Enroll(ctx, "tok", sampleNode("n1", "k1")); err != nil { t.Fatal(err) } var usedAt time.Time if err := s.db.QueryRowContext(ctx, "SELECT used_at FROM enrollment_tokens").Scan(dbTime{&usedAt}); err != nil { t.Fatal(err) } if !usedAt.Equal(clock.now()) { t.Errorf("used_at = %v, erwartet %v", usedAt, clock.now()) } } func TestNodeCommands(t *testing.T) { s, clock := openTest(t) mustTenant(t, s, "t1") mustNode(t, s, "n1", "t1", "k1") if err := s.EnqueueNodeCommand(ctx, "n1", "resync", ""); err != nil { t.Fatal(err) } clock.advance(time.Second) s.EnqueueNodeCommand(ctx, "n1", "drain", "Umzug") cmds, err := s.PendingNodeCommands(ctx, "n1") if err != nil || len(cmds) != 2 || cmds[0].Command != "resync" || cmds[1].Command != "drain" || cmds[1].Reason != "Umzug" { t.Fatalf("ausstehend: %+v %v", cmds, err) } if err := s.MarkNodeCommandSent(ctx, cmds[0].ID); err != nil { t.Fatal(err) } if cmds, _ := s.PendingNodeCommands(ctx, "n1"); len(cmds) != 1 || cmds[0].Command != "drain" { t.Errorf("nach Versand: %+v", cmds) } if err := s.EnqueueNodeCommand(ctx, "fehlt", "resync", ""); !errors.Is(err, ErrNotFound) { t.Errorf("unbekannter Knoten: %v", err) } if err := s.EnqueueNodeCommand(ctx, "n1", "", ""); !errors.Is(err, ErrInvalid) { t.Errorf("leerer Befehl: %v", err) } } func TestUpdateNodePublicKey(t *testing.T) { s, _ := openTest(t) mustTenant(t, s, "t1") mustNode(t, s, "n1", "t1", "k1") mustNode(t, s, "n2", "t1", "k2") if err := s.UpdateNodePublicKey(ctx, "n1", "k1-neu"); err != nil { t.Fatal(err) } if n, _ := s.GetNodeByPublicKey(ctx, "k1-neu"); n.ID != "n1" { t.Errorf("neuer Schlüssel nicht gefunden: %+v", n) } if _, err := s.GetNodeByPublicKey(ctx, "k1"); !errors.Is(err, ErrNotFound) { t.Error("alter Schlüssel noch gültig") } if err := s.UpdateNodePublicKey(ctx, "n1", "k2"); !errors.Is(err, ErrExists) { t.Errorf("fremder Schlüssel: %v", err) } if err := s.UpdateNodePublicKey(ctx, "fehlt", "x"); !errors.Is(err, ErrNotFound) { t.Errorf("unbekannter Knoten: %v", err) } }