package ledger import ( "errors" "testing" "sultan/pkg/store" ) func cents(v int64) *int64 { return &v } // Klasse und Preis gelten ab einem Monat; Kundenpreise haben Vorrang. func TestProducts_Versions(t *testing.T) { l := open(t) if err := l.AddProduct(ctx, Product{ID: "pingvin", Name: "Pingvin Share", Class: ClassFree}); err != nil { t.Fatal(err) } if err := l.AddProduct(ctx, Product{ID: "olat", Name: "OLAT", Class: ClassBillable, Price: cents(4900)}); err != nil { t.Fatal(err) } if err := l.SetProductVersion(ctx, "pingvin", "2027-03", ClassBillable, cents(500)); err != nil { t.Fatal(err) } l.AddCustomer(ctx, Customer{ID: "k-1001", Name: "Kunde A"}) if err := l.SetCustomerPrice(ctx, "olat", "k-1001", "2026-11", 3900); err != nil { t.Fatal(err) } cat, err := l.Catalog(ctx) if err != nil { t.Fatal(err) } tests := []struct { product, customer, month string class string price *int64 }{ {"pingvin", "", "2027-02", ClassFree, nil}, {"pingvin", "", "2027-03", ClassBillable, cents(500)}, {"pingvin", "", "2028-01", ClassBillable, cents(500)}, {"olat", "", "2026-10", ClassBillable, cents(4900)}, {"olat", "k-1001", "2026-10", ClassBillable, cents(4900)}, // Kundenpreis erst ab November {"olat", "k-1001", "2026-11", ClassBillable, cents(3900)}, {"olat", "andere", "2026-11", ClassBillable, cents(4900)}, } for _, tt := range tests { v, ok := cat.At(tt.product, tt.customer, tt.month) if !ok || v.Class != tt.class || !samePrice(v.Price, tt.price) { t.Errorf("At(%s, %s, %s) = %+v %v, erwartet %s %v", tt.product, tt.customer, tt.month, v, ok, tt.class, deref(tt.price)) } } if _, ok := cat.At("gibts-nicht", "", "2026-10"); ok { t.Error("unbekanntes Produkt gefunden") } list, _ := l.ListProducts(ctx) if len(list) != 2 || list[0].ID != "olat" { t.Errorf("ListProducts: %+v", list) } for _, err := range []error{ l.AddProduct(ctx, Product{ID: "olat", Name: "x", Class: ClassFree}), } { if !errors.Is(err, store.ErrExists) { t.Errorf("doppelt: %v", err) } } for _, err := range []error{ l.AddProduct(ctx, Product{ID: "x1", Name: "x", Class: "teuer"}), l.AddProduct(ctx, Product{ID: "x2", Name: "", Class: ClassFree}), l.SetProductVersion(ctx, "olat", "2026-13", ClassFree, nil), l.SetCustomerPrice(ctx, "olat", "k-1001", "2026/11", 100), } { if !errors.Is(err, store.ErrInvalid) { t.Errorf("ungültig: %v", err) } } if err := l.SetProductVersion(ctx, "gibts-nicht", "2026-11", ClassFree, nil); !errors.Is(err, store.ErrNotFound) { t.Errorf("unbekanntes Produkt: %v", err) } } func samePrice(a, b *int64) bool { return (a == nil && b == nil) || (a != nil && b != nil && *a == *b) } func deref(p *int64) any { if p == nil { return nil } return *p } // Zuordnung je Posten (Mandant, Protokoll, Router). func TestAssignments(t *testing.T) { l := open(t) l.AddProduct(ctx, Product{ID: "olat", Name: "OLAT", Class: ClassBillable}) a := Assignment{Tenant: "kunde", Protocol: "http", Router: "olat", Product: "olat", Start: "2026-11"} if err := l.SetAssignment(ctx, a); err != nil { t.Fatal(err) } a2 := Assignment{Tenant: "kunde", Protocol: "http", Router: "share", Class: ClassInternal} l.SetAssignment(ctx, a2) got, err := l.ListAssignments(ctx) if err != nil || len(got) != 2 || got[0] != a || got[1] != a2 { t.Errorf("ListAssignments = %+v, %v", got, err) } a.Start = "" l.SetAssignment(ctx, a) // ersetzt if got, _ := l.ListAssignments(ctx); got[0].Start != "" { t.Errorf("nicht ersetzt: %+v", got[0]) } for _, bad := range []Assignment{ {Tenant: "kunde", Protocol: "http", Router: "x", Product: "gibts-nicht"}, {Tenant: "kunde", Protocol: "http", Router: "x", Class: "teuer"}, {Tenant: "kunde", Protocol: "http", Router: "x", Start: "November"}, {Tenant: "kunde", Protocol: "http", Router: "x"}, // nichts zugeordnet } { if err := l.SetAssignment(ctx, bad); err == nil { t.Errorf("%+v akzeptiert", bad) } } if err := l.DeleteAssignment(ctx, "kunde", "http", "share"); err != nil { t.Fatal(err) } if err := l.DeleteAssignment(ctx, "kunde", "http", "share"); !errors.Is(err, store.ErrNotFound) { t.Errorf("zweites Löschen: %v", err) } }