Files
nexarch/internal/usage/storage_test.go
sysopsandClaude Sonnet 5 b6184b67aa LIC-05: speicherverbrauch-metrik-je-tenant
internal/usage/storage.go: ReportStorageWrite/ReportStorageDelete sind
duenne Spezialisierungen von LIC-03s bereits atomarem Store.Increment auf
die feste Metrik "storage_bytes" — Loeschung nutzt einfach ein negatives
Delta desselben UPSERT-Mechanismus, kein zweiter Zaehl-Codepfad. Damit
uebernehmen Akzeptanzkriterium 2 (atomar, race-frei) und die zugehoerigen
LIC-03-Garantien direkt, ohne Duplikat.

CurrentStorageUsage ist ein einfaches Store.Get auf dieselbe Metrik —
LIC-03 kann denselben Wert ueber Store.Get(tenant, StorageBytesMetric)
abfragen (Akzeptanzkriterium 3, per Test TestCurrentStorageUsage_MatchesGenericStoreGet
belegt: kein zweiter, abweichender Zaehlmechanismus).

Die Objekt-Storage-Treiber der Module (DMS FDN-03, Mail ARC-01), die diese
Funktionen bei jedem Schreib-/Loeschvorgang aufrufen wuerden, existieren als
Code noch nicht (nur geplant in dms-kanban/mail-kanban) — diese Kachel
implementiert nur die Core-seitige Zaehl-Schnittstelle, analog zum
AUD-05/RetentionRegistrar-Muster.

Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. Paralleler Schreib-Test (viele gleichzeitige Uploads) ergibt korrekten
   Endstand ohne verlorene Updates —
   TestReportStorageWrite_ConcurrentUploadsSumCorrectly: 10 parallele
   Schreibvorgaenge unterschiedlicher Groesse, Endstand exakt gleich der in
   Go unabhaengig berechneten Summe. PASS.
2. Loeschvorgang dekrementiert korrekt — TestReportStorageDelete_Decrements. PASS.
3. Abfrage liefert konsistenten Wert mit unabhaengiger Kontrollzaehlung —
   TestCurrentStorageUsage_MatchesIndependentTally (gemischte Schreib-/
   Loeschfolge, in Go parallel mitgezaehlt) und
   TestCurrentStorageUsage_MatchesGenericStoreGet. PASS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 21:13:26 +02:00

131 lines
3.4 KiB
Go

package usage
import (
"context"
"sync"
"testing"
)
// Akzeptanzkriterium 1 + 2 + Pruefung 1: paralleler Schreib-Test (viele
// gleichzeitige Uploads) ergibt korrekten Endstand ohne verlorene Updates.
func TestReportStorageWrite_ConcurrentUploadsSumCorrectly(t *testing.T) {
store, cleanup := setupTest(t)
defer cleanup()
ctx := context.Background()
tenant := newTenantID()
sizes := []int64{1024, 2048, 4096, 8192, 512, 256, 1000, 999, 1, 7000}
var wg sync.WaitGroup
for _, size := range sizes {
wg.Add(1)
go func(sz int64) {
defer wg.Done()
if err := store.ReportStorageWrite(ctx, tenant, sz); err != nil {
t.Errorf("report write: %v", err)
}
}(size)
}
wg.Wait()
var expected int64
for _, s := range sizes {
expected += s
}
got, err := store.CurrentStorageUsage(ctx, tenant)
if err != nil {
t.Fatalf("current usage: %v", err)
}
if got != expected {
t.Fatalf("erwartet %d bytes (unabhaengige kontrollsumme), habe %d — hinweis auf verlorene updates", expected, got)
}
}
// Akzeptanzkriterium 1 + Pruefung 2: Loeschvorgang dekrementiert korrekt.
func TestReportStorageDelete_Decrements(t *testing.T) {
store, cleanup := setupTest(t)
defer cleanup()
ctx := context.Background()
tenant := newTenantID()
if err := store.ReportStorageWrite(ctx, tenant, 10_000); err != nil {
t.Fatalf("write: %v", err)
}
if err := store.ReportStorageDelete(ctx, tenant, 3_000); err != nil {
t.Fatalf("delete: %v", err)
}
got, err := store.CurrentStorageUsage(ctx, tenant)
if err != nil {
t.Fatalf("current usage: %v", err)
}
if got != 7_000 {
t.Fatalf("erwartet 7000 nach schreiben(10000)+loeschen(3000), habe %d", got)
}
}
// Akzeptanzkriterium 3 + Pruefung 3: Abfrage liefert konsistenten Wert mit
// einer unabhaengigen Kontrollzaehlung ueber gemischte Schreib-/Loeschvorgaenge.
func TestCurrentStorageUsage_MatchesIndependentTally(t *testing.T) {
store, cleanup := setupTest(t)
defer cleanup()
ctx := context.Background()
tenant := newTenantID()
type op struct {
write bool
size int64
}
ops := []op{
{true, 5000}, {true, 3000}, {false, 1000}, {true, 2000}, {false, 4000}, {true, 500},
}
var tally int64
for _, o := range ops {
if o.write {
if err := store.ReportStorageWrite(ctx, tenant, o.size); err != nil {
t.Fatalf("write: %v", err)
}
tally += o.size
} else {
if err := store.ReportStorageDelete(ctx, tenant, o.size); err != nil {
t.Fatalf("delete: %v", err)
}
tally -= o.size
}
}
got, err := store.CurrentStorageUsage(ctx, tenant)
if err != nil {
t.Fatalf("current usage: %v", err)
}
if got != tally {
t.Fatalf("erwartet %d (unabhaengige kontrollzaehlung), habe %d", tally, got)
}
}
// Akzeptanzkriterium 3: LIC-03s generischer Store.Get liefert denselben Wert
// wie CurrentStorageUsage — kein zweiter, abweichender Zaehlmechanismus.
func TestCurrentStorageUsage_MatchesGenericStoreGet(t *testing.T) {
store, cleanup := setupTest(t)
defer cleanup()
ctx := context.Background()
tenant := newTenantID()
if err := store.ReportStorageWrite(ctx, tenant, 42); err != nil {
t.Fatalf("write: %v", err)
}
viaStorage, err := store.CurrentStorageUsage(ctx, tenant)
if err != nil {
t.Fatalf("current usage: %v", err)
}
viaGeneric, err := store.Get(ctx, tenant, StorageBytesMetric)
if err != nil {
t.Fatalf("generic get: %v", err)
}
if viaStorage != viaGeneric || viaStorage != 42 {
t.Fatalf("erwartet beide wege liefern 42, habe storage=%d generic=%d", viaStorage, viaGeneric)
}
}