Files
br c8df9d7651 erste module drin.
zwischenstand: Architektur aktualisiert, TOFU implementiert,
2026-10-01 22:22:13 +02:00

144 lines
3.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package store
import (
"context"
"errors"
"path/filepath"
"testing"
"time"
)
var ctx = context.Background()
// fakeClock ist eine steuerbare Uhr für deterministische Zeitstempel.
type fakeClock struct{ t time.Time }
func (c *fakeClock) now() time.Time { return c.t }
func (c *fakeClock) advance(d time.Duration) { c.t = c.t.Add(d) }
// openTest öffnet einen frischen Store in einem temporären Verzeichnis mit
// steuerbarer Uhr.
func openTest(t *testing.T) (*Store, *fakeClock) {
t.Helper()
s, err := Open(ctx, filepath.Join(t.TempDir(), "sultan.db"))
if err != nil {
t.Fatalf("Open: %v", err)
}
t.Cleanup(func() { s.Close() })
clock := &fakeClock{t: time.Date(2026, 10, 1, 12, 0, 0, 123456789, time.UTC)}
s.now = clock.now
return s, clock
}
func TestOpen_MigratesSchema(t *testing.T) {
s, _ := openTest(t)
v, err := s.SchemaVersion(ctx)
if err != nil {
t.Fatal(err)
}
if v != len(migrations) || v < 1 {
t.Errorf("Schema-Version = %d, erwartet %d", v, len(migrations))
}
}
func TestOpen_ReopenKeepsData(t *testing.T) {
path := filepath.Join(t.TempDir(), "sultan.db")
s, err := Open(ctx, path)
if err != nil {
t.Fatal(err)
}
if _, err := s.CreateTenant(ctx, sampleTenant("t1")); err != nil {
t.Fatal(err)
}
if err := s.Close(); err != nil {
t.Fatal(err)
}
s, err = Open(ctx, path)
if err != nil {
t.Fatalf("erneutes Open: %v", err)
}
defer s.Close()
if _, err := s.GetTenant(ctx, "t1"); err != nil {
t.Errorf("Daten nach erneutem Öffnen weg: %v", err)
}
if v, _ := s.SchemaVersion(ctx); v != len(migrations) {
t.Errorf("Schema-Version nach erneutem Öffnen = %d", v)
}
}
func TestOpen_InvalidPath(t *testing.T) {
_, err := Open(ctx, filepath.Join(t.TempDir(), "gibt", "es", "nicht", "sultan.db"))
if err == nil {
t.Error("erwartet Fehler für nicht existierendes Verzeichnis")
}
}
func TestOpen_RejectsNewerSchema(t *testing.T) {
path := filepath.Join(t.TempDir(), "sultan.db")
s, err := Open(ctx, path)
if err != nil {
t.Fatal(err)
}
if _, err := s.db.ExecContext(ctx, "PRAGMA user_version = 999"); err != nil {
t.Fatal(err)
}
s.Close()
if _, err := Open(ctx, path); !errors.Is(err, ErrSchemaTooNew) {
t.Errorf("erwartet ErrSchemaTooNew, bekommen %v", err)
}
}
func TestOpen_ForeignKeysEnabled(t *testing.T) {
s, _ := openTest(t)
var on int
if err := s.db.QueryRowContext(ctx, "PRAGMA foreign_keys").Scan(&on); err != nil {
t.Fatal(err)
}
if on != 1 {
t.Error("foreign_keys ist nicht aktiv – ON DELETE CASCADE würde nicht greifen")
}
}
func TestDBTime_Scan(t *testing.T) {
want := time.Date(2026, 12, 15, 10, 30, 0, 987654321, time.UTC)
cet := want.In(time.FixedZone("CET", 3600))
for name, src := range map[string]any{
"Text": "2026-12-15T10:30:00.987654321Z",
"Bytes": []byte("2026-12-15T10:30:00.987654321Z"),
"time.Time (CET)": cet,
} {
t.Run(name, func(t *testing.T) {
var got time.Time
if err := (dbTime{&got}).Scan(src); err != nil {
t.Fatal(err)
}
if !got.Equal(want) || got.Location() != time.UTC {
t.Errorf("got %v, erwartet %v in UTC", got, want)
}
})
}
var got time.Time
if err := (dbTime{&got}).Scan("15.12.2026"); err == nil {
t.Error("ungültiger Text: erwartet Fehler")
}
if err := (dbTime{&got}).Scan(int64(42)); err == nil {
t.Error("unerwarteter Typ: erwartet Fehler")
}
}
// Zeitstempel müssen als Text fester Breite gespeichert werden, sonst
// funktionieren Vergleiche in SQL (z. B. PurgeEnrollmentTokens) nicht.
func TestTimesStoredAsSortableText(t *testing.T) {
s, _ := openTest(t)
mustTenant(t, s, "t1")
var typ, raw string
if err := s.db.QueryRowContext(ctx, "SELECT typeof(created_at), CAST(created_at AS TEXT) FROM tenants").Scan(&typ, &raw); err != nil {
t.Fatal(err)
}
if typ != "text" || raw != "2026-10-01T12:00:00.123456789Z" {
t.Errorf("gespeichert als %s %q", typ, raw)
}
}