254 lines
7.7 KiB
Go
254 lines
7.7 KiB
Go
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
|
|
}
|