// SPDX-License-Identifier: GPL-2.0-or-later // Copyright (C) 2026 Bryan Joshua Pedini package main import ( "context" "database/sql" "errors" "fmt" "io" "log/slog" "regexp" "strings" "testing" "time" ) var tokenAlphabet = regexp.MustCompile(`^[A-Za-z0-9_-]{43}$`) var t0 = time.Date(2026, 10, 5, 12, 0, 0, 123456789, time.UTC) func TestGenerateToken(t *testing.T) { t.Parallel() seen := map[string]bool{} for i := 0; i < 1000; i++ { tok, err := GenerateToken() if err != nil { t.Fatal(err) } if tok == "" { t.Fatal("empty token") } if len(tok) != tokenLength { t.Fatalf("length %d, want %d", len(tok), tokenLength) } if !tokenAlphabet.MatchString(tok) { t.Fatalf("token %q outside the URL-safe alphabet", tok) } if seen[tok] { t.Fatalf("duplicate token after %d generations", i) } seen[tok] = true } } func TestGenerateTokenFromDeterministicReader(t *testing.T) { t.Parallel() tok, err := generateTokenFrom(strings.NewReader(strings.Repeat("\xff", tokenBytes))) if err != nil { t.Fatal(err) } if tok != strings.Repeat("_", 42)+"8" { t.Fatalf("unexpected encoding %q", tok) } } func TestGenerateTokenReaderError(t *testing.T) { t.Parallel() if _, err := generateTokenFrom(strings.NewReader("short")); !errors.Is(err, io.ErrUnexpectedEOF) { t.Fatalf("expected short-read error, got %v", err) } } // newTestStore returns a store with a deterministic clock (1ms steps) and generator func newTestStore(t *testing.T, gen func() (string, error)) (*TokenStore, *sql.DB) { t.Helper() db := openTestDB(t) return NewTokenStore(db, traceDiscard(), newFakeClock(t0, time.Millisecond).Now, gen), db } func TestCreateTokenDescriptions(t *testing.T) { t.Parallel() descriptions := []string{ "", "ci deploy token", " leading and trailing ", "quotes ' \" ` and $(rm -rf /); DROP TABLE tokens; --", "line1\nline2\r\n\ttab", "unicode 日本語 🚀 full", "ctl \x01\x1b[31m\x7f", string([]byte{0xff, 0xfe, 'x'}), "nul\x00byte", strings.Repeat("long", 25000), } store, db := newTestStore(t, GenerateToken) for _, desc := range descriptions { tok, err := store.CreateToken(context.Background(), desc) if err != nil { t.Fatalf("CreateToken(%q): %v", desc, err) } if !tok.Active || tok.DeletedAt != nil || tok.Description != desc || !tokenAlphabet.MatchString(tok.Token) { t.Fatalf("unexpected token %+v", tok) } var stored string if err := db.QueryRow(`SELECT description FROM tokens WHERE token = ?`, tok.Token).Scan(&stored); err != nil { t.Fatal(err) } if stored != desc { t.Fatalf("description not stored verbatim: %q != %q", stored, desc) } got, err := store.GetToken(context.Background(), tok.Token) if err != nil || got.Description != desc { t.Fatalf("GetToken description = %q, %v", got.Description, err) } } if n := countRows(t, db, "tokens"); n != len(descriptions) { t.Fatalf("rows = %d", n) } // The SQL-injection payload did nothing if n := countRows(t, db, "operations"); n != 0 { t.Fatalf("operations = %d", n) } } func TestCreateTokenTimestamps(t *testing.T) { t.Parallel() local := time.FixedZone("UTC+5", 5*3600) db := openTestDB(t) store := NewTokenStore(db, traceDiscard(), func() time.Time { return time.Date(2026, 3, 4, 10, 0, 0, 500, local) }, seqGenerator("a")) tok, err := store.CreateToken(context.Background(), "") if err != nil { t.Fatal(err) } var created string if err := db.QueryRow(`SELECT created_at FROM tokens WHERE token = 'a'`).Scan(&created); err != nil { t.Fatal(err) } if created != "2026-03-04T05:00:00.0000005Z" { t.Fatalf("created_at = %q", created) } if tok.CreatedAt.Location() != time.UTC || !tok.CreatedAt.Equal(time.Date(2026, 3, 4, 5, 0, 0, 500, time.UTC)) { t.Fatalf("CreatedAt = %v", tok.CreatedAt) } if _, err := time.Parse(time.RFC3339Nano, created); err != nil { t.Fatalf("not RFC3339Nano: %v", err) } } func TestCreateTokenCollisionRetry(t *testing.T) { t.Parallel() log, buf := newTestLogger(LevelTrace) db := openTestDB(t) store := NewTokenStore(db, log, newFakeClock(t0, time.Millisecond).Now, seqGenerator("same", "same", "same", "other")) if _, err := store.CreateToken(context.Background(), "first"); err != nil { t.Fatal(err) } tok, err := store.CreateToken(context.Background(), "second") if err != nil { t.Fatalf("expected retry to succeed: %v", err) } if tok.Token != "other" { t.Fatalf("token = %q", tok.Token) } if c := strings.Count(buf.String(), "generated token collided"); c != 2 { t.Fatalf("collision logs = %d", c) } // The colliding row was not modified first, err := store.GetToken(context.Background(), "same") if err != nil || first.Description != "first" { t.Fatalf("existing row changed: %+v, %v", first, err) } } func TestCreateTokenCollisionExhausted(t *testing.T) { t.Parallel() gen := func() (string, error) { return "fixed", nil } store, db := newTestStore(t, gen) if _, err := store.CreateToken(context.Background(), ""); err != nil { t.Fatal(err) } if _, err := store.CreateToken(context.Background(), ""); !errors.Is(err, ErrTokenCollision) { t.Fatalf("expected ErrTokenCollision, got %v", err) } if n := countRows(t, db, "tokens"); n != 1 { t.Fatalf("rows = %d", n) } } func TestCreateTokenGeneratorError(t *testing.T) { t.Parallel() store, _ := newTestStore(t, func() (string, error) { return "", errors.New("no entropy") }) if _, err := store.CreateToken(context.Background(), ""); err == nil || !strings.Contains(err.Error(), "no entropy") { t.Fatalf("got %v", err) } } func TestCreateTokenInsertError(t *testing.T) { t.Parallel() store, db := newTestStore(t, GenerateToken) db.Close() if _, err := store.CreateToken(context.Background(), ""); err == nil || !strings.Contains(err.Error(), "insert token") { t.Fatalf("got %v", err) } } func TestListTokensEmpty(t *testing.T) { t.Parallel() store, _ := newTestStore(t, GenerateToken) for _, activeOnly := range []bool{false, true} { tokens, err := store.ListTokens(context.Background(), activeOnly) if err != nil { t.Fatal(err) } if tokens == nil || len(tokens) != 0 { t.Fatalf("activeOnly=%v: expected empty non-nil slice, got %#v", activeOnly, tokens) } } } func TestListTokensOne(t *testing.T) { t.Parallel() store, _ := newTestStore(t, seqGenerator("only")) created, err := store.CreateToken(context.Background(), "d") if err != nil { t.Fatal(err) } tokens, err := store.ListTokens(context.Background(), false) if err != nil { t.Fatal(err) } if len(tokens) != 1 || tokens[0].Token != "only" || tokens[0].Description != "d" || !tokens[0].Active || !tokens[0].CreatedAt.Equal(created.CreatedAt) || tokens[0].DeletedAt != nil { t.Fatalf("got %+v", tokens) } } func TestListTokensManyOrdering(t *testing.T) { t.Parallel() db := openTestDB(t) store := NewTokenStore(db, traceDiscard(), time.Now, GenerateToken) // Rows inserted out of order, with fraction lengths that break lexical ordering // "y", "a" and "w" are inactive rows := []struct { token, created string active int }{ {"z", "2026-01-01T00:00:00.1Z", 1}, {"y", "2026-01-01T00:00:00Z", 0}, {"x", "2026-01-01T00:00:00.12Z", 1}, {"b", "2026-01-01T00:00:00.5Z", 1}, {"a", "2026-01-01T00:00:00.5Z", 0}, {"w", "2025-12-31T23:59:59.999999999Z", 0}, {"c", "2026-01-01T00:00:00.5Z", 1}, } for _, r := range rows { deleted := any(nil) if r.active == 0 { deleted = "2026-02-01T00:00:00Z" } mustExec(t, db, `INSERT INTO tokens VALUES (?, '', ?, ?, ?)`, r.token, r.active, r.created, deleted) } for activeOnly, want := range map[bool]string{false: "w,y,z,x,a,b,c", true: "z,x,b,c"} { tokens, err := store.ListTokens(context.Background(), activeOnly) if err != nil { t.Fatal(err) } var got []string for _, tok := range tokens { got = append(got, tok.Token) } if strings.Join(got, ",") != want { t.Fatalf("activeOnly=%v: order = %v, want %s", activeOnly, got, want) } } } func TestListTokensMany(t *testing.T) { t.Parallel() store, _ := newTestStore(t, GenerateToken) var want []string for i := 0; i < 200; i++ { tok, err := store.CreateToken(context.Background(), fmt.Sprint(i)) if err != nil { t.Fatal(err) } want = append(want, tok.Token) } for i := 0; i < 200; i += 3 { if err := store.DeactivateToken(context.Background(), want[i]); err != nil { t.Fatal(err) } } tokens, err := store.ListTokens(context.Background(), false) if err != nil { t.Fatal(err) } if len(tokens) != 200 { t.Fatalf("len = %d", len(tokens)) } for i, tok := range tokens { inactive := i%3 == 0 if tok.Token != want[i] || tok.Description != fmt.Sprint(i) || tok.Active == inactive || (tok.DeletedAt != nil) != inactive { t.Fatalf("row %d = %+v", i, tok) } } // Only the active ones, same relative order active, err := store.ListTokens(context.Background(), true) if err != nil { t.Fatal(err) } var wantActive []Token for i, tok := range tokens { if i%3 != 0 { wantActive = append(wantActive, tok) } } if len(active) != len(wantActive) { t.Fatalf("active len = %d, want %d", len(active), len(wantActive)) } for i, tok := range active { if tok.Token != wantActive[i].Token || !tok.Active || tok.DeletedAt != nil { t.Fatalf("active row %d = %+v", i, tok) } } } func TestListTokensErrors(t *testing.T) { t.Parallel() // Query error: closed database store, db := newTestStore(t, GenerateToken) db.Close() for _, activeOnly := range []bool{false, true} { if _, err := store.ListTokens(context.Background(), activeOnly); err == nil || !strings.Contains(err.Error(), "query tokens") { t.Fatalf("activeOnly=%v: got %v", activeOnly, err) } } // Malformed rows written by something else for name, row := range map[string][]any{ "bad created_at": {"t", "", 1, "yesterday", nil}, "bad deleted_at": {"t", "", 0, "2026-01-01T00:00:00Z", "tomorrow"}, } { store, db := newTestStore(t, GenerateToken) mustExec(t, db, `INSERT INTO tokens VALUES (?, ?, ?, ?, ?)`, row...) if _, err := store.ListTokens(context.Background(), false); err == nil || !strings.Contains(err.Error(), "parse stored timestamp") { t.Errorf("%s: got %v", name, err) } } // The inactive malformed row is filtered out in SQL, before it is parsed store, db = newTestStore(t, GenerateToken) mustExec(t, db, `INSERT INTO tokens VALUES ('t', '', 0, '2026-01-01T00:00:00Z', 'tomorrow')`) if tokens, err := store.ListTokens(context.Background(), true); err != nil || len(tokens) != 0 { t.Fatalf("active only: got %v, %v", tokens, err) } } // cancelOnRecord is a slog handler that cancels a context when it sees a record with the given message type cancelOnRecord struct { slog.Handler msg string cancel context.CancelFunc } func (h cancelOnRecord) Enabled(context.Context, slog.Level) bool { return true } func (h cancelOnRecord) Handle(ctx context.Context, r slog.Record) error { if r.Message == h.msg { h.cancel() // Give database/sql and the driver time to observe the cancellation time.Sleep(50 * time.Millisecond) } return nil } func TestListTokensIterationError(t *testing.T) { t.Parallel() db := openTestDB(t) for i := 0; i < 500; i++ { mustExec(t, db, `INSERT INTO tokens VALUES (?, '', 1, '2026-01-01T00:00:00Z', NULL)`, fmt.Sprint(i)) } ctx, cancel := context.WithCancel(context.Background()) defer cancel() log := slog.New(cancelOnRecord{msg: "row read", cancel: cancel}) store := NewTokenStore(db, log, time.Now, GenerateToken) if _, err := store.ListTokens(ctx, false); err == nil || !strings.Contains(err.Error(), "iterate token rows") { t.Fatalf("got %v", err) } } // newLegacySchemaStore creates a database whose tables were pre-created without constraints, // so rows the real schema would reject can be used to exercise defensive error paths func newLegacySchemaStore(t *testing.T, ddl string) (*TokenStore, *sql.DB) { t.Helper() path := testDBPath(t) raw, err := sql.Open("sqlite", "file:"+path) if err != nil { t.Fatal(err) } mustExec(t, raw, ddl) raw.Close() db, err := OpenDatabase(context.Background(), path, traceDiscard()) if err != nil { t.Fatal(err) } t.Cleanup(func() { db.Close() }) return NewTokenStore(db, traceDiscard(), newFakeClock(t0, time.Millisecond).Now, GenerateToken), db } func TestTokenScanErrors(t *testing.T) { t.Parallel() store, db := newLegacySchemaStore(t, `CREATE TABLE tokens (token TEXT, description TEXT, active, created_at TEXT, deleted_at TEXT)`) mustExec(t, db, `INSERT INTO tokens VALUES ('t', NULL, 'not-a-number', '2026-01-01T00:00:00Z', NULL)`) if _, err := store.ListTokens(context.Background(), false); err == nil || !strings.Contains(err.Error(), "read token row") { t.Fatalf("ListTokens: got %v", err) } if _, err := store.GetToken(context.Background(), "t"); err == nil || !strings.Contains(err.Error(), "query token") { t.Fatalf("GetToken: got %v", err) } if err := store.DeactivateToken(context.Background(), "t"); err == nil || !strings.Contains(err.Error(), "query token") { t.Fatalf("DeactivateToken: got %v", err) } } func TestGetToken(t *testing.T) { t.Parallel() store, _ := newTestStore(t, seqGenerator("tok-a", "tok-b")) a, _ := store.CreateToken(context.Background(), "A") if _, err := store.CreateToken(context.Background(), "B"); err != nil { t.Fatal(err) } // Existing got, err := store.GetToken(context.Background(), "tok-a") if err != nil || got.Token != "tok-a" || got.Description != "A" || !got.Active || !got.CreatedAt.Equal(a.CreatedAt) || got.DeletedAt != nil { t.Fatalf("got %+v, %v", got, err) } // Unknown, near-match, case change, whitespace for _, v := range []string{"", "tok-c", "TOK-A", "tok-a ", " tok-a", "tok-", "tok-a'--", "%"} { if _, err := store.GetToken(context.Background(), v); !errors.Is(err, ErrTokenNotFound) { t.Errorf("GetToken(%q) = %v", v, err) } } // Inactive if err := store.DeactivateToken(context.Background(), "tok-b"); err != nil { t.Fatal(err) } got, err = store.GetToken(context.Background(), "tok-b") if err != nil || got.Active || got.DeletedAt == nil { t.Fatalf("inactive: %+v, %v", got, err) } // Query error store.db.Close() if _, err := store.GetToken(context.Background(), "tok-a"); err == nil || errors.Is(err, ErrTokenNotFound) { t.Fatalf("closed db: %v", err) } } func TestDeactivateToken(t *testing.T) { t.Parallel() store, db := newTestStore(t, seqGenerator("tok-a", "tok-b")) if _, err := store.CreateToken(context.Background(), ""); err != nil { // created at t0 t.Fatal(err) } if _, err := store.CreateToken(context.Background(), ""); err != nil { // created at t0+1ms t.Fatal(err) } if err := store.DeactivateToken(context.Background(), "tok-a"); err != nil { // deleted at t0+2ms t.Fatal(err) } var active int var deleted sql.NullString if err := db.QueryRow(`SELECT active, deleted_at FROM tokens WHERE token = 'tok-a'`).Scan(&active, &deleted); err != nil { t.Fatal(err) } if active != 0 || deleted.String != "2026-10-05T12:00:00.125456789Z" { t.Fatalf("active=%d deleted_at=%+v", active, deleted) } // The other token is untouched if err := db.QueryRow(`SELECT active, deleted_at FROM tokens WHERE token = 'tok-b'`).Scan(&active, &deleted); err != nil || active != 1 || deleted.Valid { t.Fatalf("tok-b changed: %d %+v %v", active, deleted, err) } // Already inactive: error, and deleted_at keeps its original value if err := store.DeactivateToken(context.Background(), "tok-a"); !errors.Is(err, ErrTokenInactive) { t.Fatalf("second delete: %v", err) } if err := db.QueryRow(`SELECT deleted_at FROM tokens WHERE token = 'tok-a'`).Scan(&deleted); err != nil || deleted.String != "2026-10-05T12:00:00.125456789Z" { t.Fatalf("deleted_at changed: %+v", deleted) } // Unknown if err := store.DeactivateToken(context.Background(), "nope"); !errors.Is(err, ErrTokenNotFound) { t.Fatalf("unknown: %v", err) } if n := countRows(t, db, "tokens"); n != 2 { t.Fatalf("rows = %d", n) } } func TestDeactivateTokenErrors(t *testing.T) { t.Parallel() // Begin error store, db := newTestStore(t, GenerateToken) db.Close() if err := store.DeactivateToken(context.Background(), "x"); err == nil || !strings.Contains(err.Error(), "begin transaction") { t.Fatalf("begin: %v", err) } // Update error: a trigger rejects the update store, db = newTestStore(t, seqGenerator("t")) if _, err := store.CreateToken(context.Background(), ""); err != nil { t.Fatal(err) } mustExec(t, db, `CREATE TRIGGER no_update BEFORE UPDATE ON tokens BEGIN SELECT RAISE(ABORT, 'refused'); END`) if err := store.DeactivateToken(context.Background(), "t"); err == nil || !strings.Contains(err.Error(), "update token") { t.Fatalf("update: %v", err) } var active int if err := db.QueryRow(`SELECT active FROM tokens WHERE token = 't'`).Scan(&active); err != nil || active != 1 { t.Fatalf("token changed after failed update: %d %v", active, err) } // Commit error: a deferred foreign key on deleted_at is violated only at COMMIT store, db = newLegacySchemaStore(t, ` CREATE TABLE parents (id TEXT PRIMARY KEY); CREATE TABLE tokens ( token TEXT PRIMARY KEY NOT NULL, description TEXT NOT NULL DEFAULT '', active INTEGER NOT NULL, created_at TEXT NOT NULL, deleted_at TEXT NULL REFERENCES parents(id) DEFERRABLE INITIALLY DEFERRED)`) mustExec(t, db, `INSERT INTO tokens VALUES ('t', '', 1, '2026-01-01T00:00:00Z', NULL)`) if err := store.DeactivateToken(context.Background(), "t"); err == nil || !strings.Contains(err.Error(), "commit transaction") { t.Fatalf("commit: %v", err) } if err := db.QueryRow(`SELECT active FROM tokens WHERE token = 't'`).Scan(&active); err != nil || active != 1 { t.Fatalf("token changed after failed commit: %d %v", active, err) } } func TestTokenStoreTraceRedaction(t *testing.T) { t.Parallel() log, buf := newTestLogger(LevelTrace) db := openTestDB(t) const secret = "SECRET_TOKEN_VALUE_0123456789abcdefghijklmno" store := NewTokenStore(db, log, newFakeClock(t0, time.Millisecond).Now, seqGenerator(secret)) if _, err := store.CreateToken(context.Background(), "my description"); err != nil { t.Fatal(err) } if _, err := store.GetToken(context.Background(), secret); err != nil { t.Fatal(err) } if _, err := store.ListTokens(context.Background(), false); err != nil { t.Fatal(err) } if err := store.DeactivateToken(context.Background(), secret); err != nil { t.Fatal(err) } _ = store.DeactivateToken(context.Background(), secret) _, _ = store.GetToken(context.Background(), secret+"x") _ = store.DeactivateToken(context.Background(), secret+"x") out := buf.String() if strings.Contains(out, secret) || strings.Contains(out, secret[:12]) { t.Fatalf("token secret leaked into trace output:\n%s", out) } id := tokenID(secret) for _, want := range []string{ `msg="function entry" func=TokenStore.CreateToken`, `msg="function exit" func=TokenStore.CreateToken`, `msg="row inserted" table=tokens token_id=` + id + ` description="my description" active=true created_at=2026-10-05T12:00:00.123456789Z deleted_at=`, `msg="row read" table=tokens token_id=` + id, `msg="row updated" table=tokens token_id=` + id + ` active_from=true active_to=false deleted_at=`, `msg="row already inactive" table=tokens token_id=` + id, `msg="row not found" table=tokens token_id=` + tokenID(secret+"x"), `func=TokenStore.ListTokens`, `func=TokenStore.GetToken`, `func=TokenStore.DeactivateToken`, } { if !strings.Contains(out, want) { t.Errorf("missing trace record %q", want) } } }