package storage import ( "context" "errors" "os" "strings" "testing" "time" ) type fakeUsageReporter struct { reports []int64 } func (f *fakeUsageReporter) Report(_ context.Context, _, metric string, delta int64) error { if metric != UsageMetric { return errors.New("unerwartete metrik: " + metric) } f.reports = append(f.reports, delta) return nil } func newTestService(t *testing.T) (*Service, *fakeUsageReporter) { t.Helper() driver := NewLocalDriver(t.TempDir()) usage := &fakeUsageReporter{} return NewService(driver, usage, "acme"), usage } // TestPut_ReadBackIsByteIdentical ist die geforderte Pflichtprüfung 1: // ein geschriebenes Objekt liefert beim Lesen byteidentischen Inhalt. func TestPut_ReadBackIsByteIdentical(t *testing.T) { svc, _ := newTestService(t) ctx := context.Background() key := ObjectKey("msg-1", 0) content := "vollständig identischer Inhalt äöü" checksum, err := svc.Put(ctx, key, strings.NewReader(content), int64(len(content)), "text/plain") if err != nil { t.Fatalf("put: %v", err) } if checksum == "" { t.Fatal("erwartet nicht-leere prüfsumme") } got, err := svc.GetVerified(ctx, key) if err != nil { t.Fatalf("getverified: %v", err) } if string(got) != content { t.Fatalf("nicht byteidentisch: got %q, want %q", got, content) } } // TestGetVerified_DetectsTamperedObject ist die geforderte // Pflichtprüfung 2: ein absichtlich beschädigtes Objekt wird bei // Prüfsummenvergleich erkannt. func TestGetVerified_DetectsTamperedObject(t *testing.T) { dir := t.TempDir() driver := NewLocalDriver(dir) usage := &fakeUsageReporter{} svc := NewService(driver, usage, "acme") ctx := context.Background() key := ObjectKey("msg-tamper", 0) if _, err := svc.Put(ctx, key, strings.NewReader("originaler inhalt"), 17, "text/plain"); err != nil { t.Fatalf("put: %v", err) } // Objekt DIREKT am Dateisystem manipulieren — umgeht Service // vollständig, simuliert externe Beschädigung/Manipulation. full := driver.path(key) if err := os.WriteFile(full, []byte("MANIPULIERTER INHALT"), 0o644); err != nil { t.Fatalf("manipulation schreiben: %v", err) } _, err := svc.GetVerified(ctx, key) if !errors.Is(err, ErrChecksumMismatch) { t.Fatalf("erwartet ErrChecksumMismatch bei manipuliertem objekt, habe: %v", err) } } // TestPut_ManySmallObjectsAcceptableLatency ist die geforderte // Pflichtprüfung 3: Lasttest mit vielen kleinen Objekten bestätigt // akzeptable Latenz. func TestPut_ManySmallObjectsAcceptableLatency(t *testing.T) { svc, _ := newTestService(t) ctx := context.Background() const count = 500 start := time.Now() for i := 0; i < count; i++ { key := ObjectKey("msg-load", i) if _, err := svc.Put(ctx, key, strings.NewReader("kleiner anhang inhalt"), 21, "text/plain"); err != nil { t.Fatalf("put #%d: %v", i, err) } } elapsed := time.Since(start) avgPerObject := elapsed / count // Großzügige Grenze (10ms/Objekt inkl. Schreiben+Sidecar+Rücklese- // Verifikation) — Ziel ist der Nachweis, dass keine quadratische // oder anderweitig unverhältnismäßige Verschlechterung auftritt, // nicht ein knallhartes Performance-SLA. if avgPerObject > 10*time.Millisecond { t.Fatalf("erwartet akzeptable latenz (<10ms/objekt), habe %v/objekt (gesamt %v für %d objekte)", avgPerObject, elapsed, count) } t.Logf("Lasttest: %d Objekte in %v (%v/Objekt)", count, elapsed, avgPerObject) } // TestPut_ReportsUsageOnWriteAndDelete ist die geforderte // Pflichtprüfung 4: Melde-Aufruf an Core LIC-05 bei Schreib- und // Löschvorgang nachweislich ausgelöst, mit korrekter Größenangabe. func TestPut_ReportsUsageOnWriteAndDelete(t *testing.T) { svc, usage := newTestService(t) ctx := context.Background() key := ObjectKey("msg-usage", 0) content := "zwölf bytes!" if _, err := svc.Put(ctx, key, strings.NewReader(content), int64(len(content)), "text/plain"); err != nil { t.Fatalf("put: %v", err) } if len(usage.reports) != 1 || usage.reports[0] != int64(len(content)) { t.Fatalf("erwartet genau eine positive meldung mit größe %d, habe: %v", len(content), usage.reports) } if err := svc.Delete(ctx, key, int64(len(content))); err != nil { t.Fatalf("delete: %v", err) } if len(usage.reports) != 2 || usage.reports[1] != -int64(len(content)) { t.Fatalf("erwartet zusätzliche negative meldung mit -%d, habe: %v", len(content), usage.reports) } }