perf(S01/T01): Add token-bucket rate limiter (1 req/sec), SQLite respon…

- backend/explore/ratelimiter.go
- backend/explore/cache.go
- backend/database/sql/schemas/explore_cache.sql
- backend/database/database.go
This commit is contained in:
2026-03-23 07:55:25 -04:00
parent 398fd5aaae
commit 8fc075c24a
9 changed files with 652 additions and 1 deletions
+191
View File
@@ -1041,3 +1041,194 @@ func TestMigration10PlayHistory(t *testing.T) {
t.Errorf("track_metadata play_count = %d, want 1", viewPlayCount)
}
}
// ---------------------------------------------------------------------------
// Migration 11 — explore_cache table
// ---------------------------------------------------------------------------
func TestMigration11ExploreCache(t *testing.T) {
t.Parallel()
db := NewTestDB(t)
// Verify user_version >= 11.
var version int
verRows, err := db.QueryContext("PRAGMA user_version")
if err != nil {
t.Fatalf("PRAGMA user_version: %v", err)
}
if !verRows.Next() {
_ = verRows.Close()
t.Fatal("PRAGMA user_version: no row returned")
}
if err := verRows.Scan(&version); err != nil {
_ = verRows.Close()
t.Fatalf("scan user_version: %v", err)
}
_ = verRows.Close()
if version < 11 {
t.Errorf("user_version = %d, want >= 11", version)
}
// Verify explore_cache table exists.
var tableCount int64
tblRows, err := db.QueryContext(
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='explore_cache'",
)
if err != nil {
t.Fatalf("query sqlite_master: %v", err)
}
if !tblRows.Next() {
_ = tblRows.Close()
t.Fatal("no row from sqlite_master query")
}
if err := tblRows.Scan(&tableCount); err != nil {
_ = tblRows.Close()
t.Fatalf("scan table count: %v", err)
}
_ = tblRows.Close()
if tableCount != 1 {
t.Errorf("explore_cache table count = %d, want 1", tableCount)
}
// Verify all expected columns exist.
expectedCols := map[string]bool{
"url_key": false,
"response": false,
"mbid": false,
"entity_type": false,
"expires_at": false,
"created_at": false,
}
colRows, err := db.QueryContext(
"PRAGMA table_info(explore_cache)",
)
if err != nil {
t.Fatalf("PRAGMA table_info(explore_cache): %v", err)
}
for colRows.Next() {
var (
cid int64
name string
colType string
notNull int64
dfltValue sql.NullString
pk int64
)
if err := colRows.Scan(
&cid, &name, &colType, &notNull, &dfltValue, &pk,
); err != nil {
_ = colRows.Close()
t.Fatalf("scan table_info row: %v", err)
}
if _, ok := expectedCols[name]; ok {
expectedCols[name] = true
}
}
_ = colRows.Close()
for col, found := range expectedCols {
if !found {
t.Errorf("explore_cache missing column: %s", col)
}
}
// Verify indexes exist.
idxRows, err := db.QueryContext(
"SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='explore_cache'",
)
if err != nil {
t.Fatalf("query indexes: %v", err)
}
indexes := map[string]bool{}
for idxRows.Next() {
var name string
if err := idxRows.Scan(&name); err != nil {
_ = idxRows.Close()
t.Fatalf("scan index name: %v", err)
}
indexes[name] = true
}
_ = idxRows.Close()
if !indexes["idx_explore_cache_expires"] {
t.Error("missing index: idx_explore_cache_expires")
}
if !indexes["idx_explore_cache_mbid"] {
t.Error("missing index: idx_explore_cache_mbid")
}
// Round-trip: insert and read back.
_, err = db.ExecContext(
`INSERT INTO explore_cache (url_key, response, mbid, entity_type, expires_at)
VALUES ('test-key', '{"data":"value"}', 'abc-123', 'artist', datetime('now', '+1 hour'))`,
)
if err != nil {
t.Fatalf("insert explore_cache: %v", err)
}
rows, err := db.QueryContext(
"SELECT url_key, response, mbid, entity_type FROM explore_cache WHERE url_key = 'test-key'",
)
if err != nil {
t.Fatalf("query explore_cache: %v", err)
}
if !rows.Next() {
_ = rows.Close()
t.Fatal("explore_cache row not found")
}
var (
urlKey string
response string
mbid sql.NullString
entityType sql.NullString
)
if err := rows.Scan(&urlKey, &response, &mbid, &entityType); err != nil {
_ = rows.Close()
t.Fatalf("scan explore_cache row: %v", err)
}
_ = rows.Close()
if urlKey != "test-key" {
t.Errorf("url_key = %q, want %q", urlKey, "test-key")
}
if response != `{"data":"value"}` {
t.Errorf("response = %q, want %q", response, `{"data":"value"}`)
}
if !mbid.Valid || mbid.String != "abc-123" {
t.Errorf("mbid = %v, want abc-123", mbid)
}
if !entityType.Valid || entityType.String != "artist" {
t.Errorf("entity_type = %v, want artist", entityType)
}
}