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,253 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func sampleTenant(id string) Tenant {
|
||||
return Tenant{
|
||||
ID: id,
|
||||
Name: "Kunde " + id,
|
||||
Email: id + "@example.com",
|
||||
Tier: TierPro,
|
||||
MaxRoutes: 10,
|
||||
AllowedDomains: []string{"*.firma.de", "shop.de"},
|
||||
ValidUntil: time.Date(2026, 12, 15, 0, 0, 0, 0, time.UTC),
|
||||
Status: TenantActive,
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTenant_RoundTrip(t *testing.T) {
|
||||
s, clock := openTest(t)
|
||||
in := sampleTenant("kunde_schmidt")
|
||||
in.ValidUntil = time.Date(2026, 12, 15, 10, 30, 0, 987654321, time.FixedZone("CET", 3600))
|
||||
|
||||
created, err := s.CreateTenant(ctx, in)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateTenant: %v", err)
|
||||
}
|
||||
if !created.CreatedAt.Equal(clock.now()) || !created.UpdatedAt.Equal(clock.now()) {
|
||||
t.Errorf("Zeitstempel: created=%v updated=%v, erwartet %v", created.CreatedAt, created.UpdatedAt, clock.now())
|
||||
}
|
||||
if !created.ValidUntil.Equal(in.ValidUntil) || created.ValidUntil.Location() != time.UTC {
|
||||
t.Errorf("ValidUntil = %v, erwartet %v in UTC", created.ValidUntil, in.ValidUntil)
|
||||
}
|
||||
|
||||
got, err := s.GetTenant(ctx, "kunde_schmidt")
|
||||
if err != nil {
|
||||
t.Fatalf("GetTenant: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, created) {
|
||||
t.Errorf("gelesen weicht ab\n got: %+v\nwant: %+v", got, created)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTenant_Defaults(t *testing.T) {
|
||||
s, _ := openTest(t)
|
||||
in := sampleTenant("t1")
|
||||
in.Tier, in.Status, in.AllowedDomains = "", "", nil
|
||||
|
||||
created, err := s.CreateTenant(ctx, in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if created.Tier != TierCommunity || created.Status != TenantActive {
|
||||
t.Errorf("Defaults: tier=%q status=%q", created.Tier, created.Status)
|
||||
}
|
||||
if created.AllowedDomains == nil || len(created.AllowedDomains) != 0 {
|
||||
t.Errorf("AllowedDomains = %#v, erwartet leere, nicht-nil Liste", created.AllowedDomains)
|
||||
}
|
||||
got, _ := s.GetTenant(ctx, "t1")
|
||||
if !reflect.DeepEqual(got, created) {
|
||||
t.Errorf("gelesen weicht ab\n got: %+v\nwant: %+v", got, created)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTenant_Duplicate(t *testing.T) {
|
||||
s, _ := openTest(t)
|
||||
if _, err := s.CreateTenant(ctx, sampleTenant("t1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := s.CreateTenant(ctx, sampleTenant("t1")); !errors.Is(err, ErrExists) {
|
||||
t.Errorf("erwartet ErrExists, bekommen %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTenant_Validation(t *testing.T) {
|
||||
tests := map[string]func(*Tenant){
|
||||
"ID leer": func(t *Tenant) { t.ID = "" },
|
||||
"Name leer": func(t *Tenant) { t.Name = " " },
|
||||
"E-Mail leer": func(t *Tenant) { t.Email = "" },
|
||||
"unbekannter Tier": func(t *Tenant) { t.Tier = "gold" },
|
||||
"unbekannter Status": func(t *Tenant) { t.Status = "pausiert" },
|
||||
"MaxRoutes < -1": func(t *Tenant) { t.MaxRoutes = -2 },
|
||||
"leere Domain": func(t *Tenant) { t.AllowedDomains = []string{"a.de", ""} },
|
||||
"ValidUntil fehlt": func(t *Tenant) { t.ValidUntil = time.Time{} },
|
||||
}
|
||||
for name, mutate := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
s, _ := openTest(t)
|
||||
in := sampleTenant("t1")
|
||||
mutate(&in)
|
||||
if _, err := s.CreateTenant(ctx, in); !errors.Is(err, ErrInvalid) {
|
||||
t.Errorf("erwartet ErrInvalid, bekommen %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTenant_AllStatusesAndTiers(t *testing.T) {
|
||||
s, _ := openTest(t)
|
||||
i := 0
|
||||
for _, tier := range []Tier{TierCommunity, TierPro, TierEnterprise} {
|
||||
for _, st := range []TenantStatus{TenantActive, TenantSuspendedPayment, TenantQuarantined, TenantExpired} {
|
||||
in := sampleTenant(string(rune('a' + i)))
|
||||
in.Tier, in.Status = tier, st
|
||||
if _, err := s.CreateTenant(ctx, in); err != nil {
|
||||
t.Errorf("tier=%s status=%s: %v", tier, st, err)
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTenant_NotFound(t *testing.T) {
|
||||
s, _ := openTest(t)
|
||||
if _, err := s.GetTenant(ctx, "fehlt"); !errors.Is(err, ErrNotFound) {
|
||||
t.Errorf("erwartet ErrNotFound, bekommen %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListTenants(t *testing.T) {
|
||||
s, _ := openTest(t)
|
||||
got, err := s.ListTenants(ctx)
|
||||
if err != nil || len(got) != 0 {
|
||||
t.Fatalf("leere DB: %v, %v", got, err)
|
||||
}
|
||||
for _, id := range []string{"c", "a", "b"} {
|
||||
if _, err := s.CreateTenant(ctx, sampleTenant(id)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
got, err = s.ListTenants(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var ids []string
|
||||
for _, tn := range got {
|
||||
ids = append(ids, tn.ID)
|
||||
}
|
||||
if !reflect.DeepEqual(ids, []string{"a", "b", "c"}) {
|
||||
t.Errorf("IDs = %v, erwartet sortiert", ids)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateTenant(t *testing.T) {
|
||||
s, clock := openTest(t)
|
||||
created, err := s.CreateTenant(ctx, sampleTenant("t1"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
clock.advance(time.Hour)
|
||||
|
||||
upd := created
|
||||
upd.Name = "Neuer Name"
|
||||
upd.Tier = TierEnterprise
|
||||
upd.MaxRoutes = -1
|
||||
upd.AllowedDomains = []string{"neu.de"}
|
||||
upd.ValidUntil = created.ValidUntil.AddDate(1, 0, 0)
|
||||
upd.CreatedAt = time.Time{} // darf nicht überschrieben werden
|
||||
|
||||
got, err := s.UpdateTenant(ctx, upd)
|
||||
if err != nil {
|
||||
t.Fatalf("UpdateTenant: %v", err)
|
||||
}
|
||||
if !got.CreatedAt.Equal(created.CreatedAt) {
|
||||
t.Errorf("CreatedAt verändert: %v", got.CreatedAt)
|
||||
}
|
||||
if !got.UpdatedAt.Equal(clock.now()) {
|
||||
t.Errorf("UpdatedAt = %v, erwartet %v", got.UpdatedAt, clock.now())
|
||||
}
|
||||
read, _ := s.GetTenant(ctx, "t1")
|
||||
if !reflect.DeepEqual(read, got) {
|
||||
t.Errorf("gelesen weicht ab\n got: %+v\nwant: %+v", read, got)
|
||||
}
|
||||
if read.Name != "Neuer Name" || read.MaxRoutes != -1 || read.Tier != TierEnterprise {
|
||||
t.Errorf("Änderungen nicht gespeichert: %+v", read)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateTenant_Errors(t *testing.T) {
|
||||
s, _ := openTest(t)
|
||||
if _, err := s.UpdateTenant(ctx, sampleTenant("fehlt")); !errors.Is(err, ErrNotFound) {
|
||||
t.Errorf("unbekannt: erwartet ErrNotFound, bekommen %v", err)
|
||||
}
|
||||
created, _ := s.CreateTenant(ctx, sampleTenant("t1"))
|
||||
created.Email = ""
|
||||
if _, err := s.UpdateTenant(ctx, created); !errors.Is(err, ErrInvalid) {
|
||||
t.Errorf("ungültig: erwartet ErrInvalid, bekommen %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetTenantStatus(t *testing.T) {
|
||||
s, clock := openTest(t)
|
||||
if _, err := s.CreateTenant(ctx, sampleTenant("t1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
clock.advance(time.Minute)
|
||||
if err := s.SetTenantStatus(ctx, "t1", TenantSuspendedPayment); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, _ := s.GetTenant(ctx, "t1")
|
||||
if got.Status != TenantSuspendedPayment || !got.UpdatedAt.Equal(clock.now()) {
|
||||
t.Errorf("status=%q updated=%v", got.Status, got.UpdatedAt)
|
||||
}
|
||||
if err := s.SetTenantStatus(ctx, "t1", "kaputt"); !errors.Is(err, ErrInvalid) {
|
||||
t.Errorf("ungültig: erwartet ErrInvalid, bekommen %v", err)
|
||||
}
|
||||
if err := s.SetTenantStatus(ctx, "fehlt", TenantActive); !errors.Is(err, ErrNotFound) {
|
||||
t.Errorf("unbekannt: erwartet ErrNotFound, bekommen %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteTenant_Cascades(t *testing.T) {
|
||||
s, clock := openTest(t)
|
||||
mustTenant(t, s, "t1")
|
||||
mustTenant(t, s, "t2")
|
||||
mustNode(t, s, "n1", "t1", "key1")
|
||||
mustNode(t, s, "n2", "t2", "key2")
|
||||
if err := s.AddEnrollmentToken(ctx, "tok1", "t1", clock.now().Add(time.Hour)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := s.DeleteTenant(ctx, "t1"); err != nil {
|
||||
t.Fatalf("DeleteTenant: %v", err)
|
||||
}
|
||||
if _, err := s.GetTenant(ctx, "t1"); !errors.Is(err, ErrNotFound) {
|
||||
t.Errorf("Mandant noch da: %v", err)
|
||||
}
|
||||
if _, err := s.GetNode(ctx, "n1"); !errors.Is(err, ErrNotFound) {
|
||||
t.Errorf("Knoten des Mandanten nicht mitgelöscht: %v", err)
|
||||
}
|
||||
if _, err := s.Enroll(ctx, "tok1", sampleNode("n3", "key3")); !errors.Is(err, ErrNotFound) {
|
||||
t.Errorf("Token des Mandanten nicht mitgelöscht: %v", err)
|
||||
}
|
||||
if _, err := s.GetNode(ctx, "n2"); err != nil {
|
||||
t.Errorf("fremder Knoten gelöscht: %v", err)
|
||||
}
|
||||
if err := s.DeleteTenant(ctx, "t1"); !errors.Is(err, ErrNotFound) {
|
||||
t.Errorf("zweites Löschen: erwartet ErrNotFound, bekommen %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func mustTenant(t *testing.T, s *Store, id string) Tenant {
|
||||
t.Helper()
|
||||
tn, err := s.CreateTenant(ctx, sampleTenant(id))
|
||||
if err != nil {
|
||||
t.Fatalf("CreateTenant(%s): %v", id, err)
|
||||
}
|
||||
return tn
|
||||
}
|
||||
Reference in new issue
Block a user