mailboxconfig (IMP-07) bekommt eine quota_bytes-Spalte statt einer eigenen Tabelle — ein Postfach ist bereits eindeutig über (tenant_slug, name) identifiziert. SetQuotaBytes/LimitBytes, 0 = unbegrenzt (Standardwert, keine Migration bestehender Postfächer nötig). LimitBytes erfüllt strukturell quota.LimitProvider. storage.ArchiveMailboxPrefix (ARC-04-Ergänzung, Präfix ALLER Jahre eines Postfachs) und storage.UsageCounter: realer Speicherverbrauch durch echtes S3-Listing im physisch getrennten Mandanten-Bucket (ARC-06) — kein separat gepflegter Zählerstand. Neues Paket mail/internal/quota: Checker verbindet LimitProvider und UsageProvider. Kein konfiguriertes Limit = immer erlaubt (Core-LIC-05- Quota läuft unabhängig weiter — beide Ebenen bewusst unabhängig durchgesetzt, bekannter Fehler vermieden). smtp.QuotaChecker (schmale Schnittstelle, keine Paketkopplung an quota) wird in handleRcptTo geprüft, VOR der Datenübertragung: 552 (RFC 5321 "exceeded storage allocation") bei Überschreitung, Session bleibt nutzbar. nil-Checker erhält bisheriges Verhalten unverändert. Alle drei Pflichtprüfungen mit echten Nachweisen: Quota-Überschreitung liefert 552, Session bleibt funktionsfähig; ein anderes Postfach desselben Tenants läuft währenddessen vollständig normal durch; vollständiger Ende-zu-Ende-Integrationstest gegen reale Postgres- und MinIO-Instanzen — 5000 echte Bytes abgelegt, real gemessen, Limit knapp darunter/darüber gesetzt, SMTP reagiert jeweils korrekt auf den tatsächlichen gemessenen Wert. Dabei einen echten Cleanup-Fehler gefunden und behoben (defer schloss den Pool vor dem zugehörigen t.Cleanup, verwaiste Testdaten blieben zurück). go build/go vet/golangci-lint clean, gesamtes Mail-Modul regressionsfrei getestet.
57 lines
1.5 KiB
Go
57 lines
1.5 KiB
Go
package quota
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
)
|
|
|
|
type fakeLimits struct {
|
|
limitBytes int64
|
|
configured bool
|
|
}
|
|
|
|
func (f fakeLimits) LimitBytes(context.Context, string, string) (int64, bool, error) {
|
|
return f.limitBytes, f.configured, nil
|
|
}
|
|
|
|
type fakeUsage struct {
|
|
usedBytes int64
|
|
}
|
|
|
|
func (f fakeUsage) UsageBytes(context.Context, string, string) (int64, error) {
|
|
return f.usedBytes, nil
|
|
}
|
|
|
|
func TestCheck_UnconfiguredLimitAlwaysAllowed(t *testing.T) {
|
|
c := NewChecker(fakeLimits{configured: false}, fakeUsage{usedBytes: 1_000_000_000})
|
|
result, err := c.Check(context.Background(), "mandant-a", "postfach-x")
|
|
if err != nil {
|
|
t.Fatalf("check: %v", err)
|
|
}
|
|
if !result.Allowed {
|
|
t.Fatalf("erwartete erlaubt ohne konfiguriertes limit, habe: %+v", result)
|
|
}
|
|
}
|
|
|
|
func TestCheck_UsageAtOrAboveLimitRejected(t *testing.T) {
|
|
c := NewChecker(fakeLimits{limitBytes: 1000, configured: true}, fakeUsage{usedBytes: 1000})
|
|
result, err := c.Check(context.Background(), "mandant-a", "postfach-x")
|
|
if err != nil {
|
|
t.Fatalf("check: %v", err)
|
|
}
|
|
if result.Allowed {
|
|
t.Fatalf("erwartete ablehnung bei verbrauch == limit, habe: %+v", result)
|
|
}
|
|
}
|
|
|
|
func TestCheck_UsageBelowLimitAllowed(t *testing.T) {
|
|
c := NewChecker(fakeLimits{limitBytes: 1000, configured: true}, fakeUsage{usedBytes: 999})
|
|
result, err := c.Check(context.Background(), "mandant-a", "postfach-x")
|
|
if err != nil {
|
|
t.Fatalf("check: %v", err)
|
|
}
|
|
if !result.Allowed {
|
|
t.Fatalf("erwartete erlaubt bei verbrauch unter limit, habe: %+v", result)
|
|
}
|
|
}
|