fix quota reset reservation race
Desktop Client Build / Resolve Build Metadata (push) Successful in 28s
Frontend CI / Frontend (push) Successful in 3m57s
Backend CI / Backend (push) Successful in 17m3s
Desktop Client Build / Build Desktop Client (push) Successful in 26m8s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 51s

This commit is contained in:
2026-05-12 02:24:57 +08:00
parent 1aad002a3e
commit 01fa2b8309
13 changed files with 337 additions and 168 deletions
+2 -2
View File
@@ -602,7 +602,7 @@ WITH reset AS (
updated_at = NOW()
WHERE tenant_id = $1
AND quota_type = $2
AND status = 'confirmed'
AND status IN ('pending', 'confirmed')
AND created_at >= $3
AND created_at < $4
RETURNING reserved_amount
@@ -627,7 +627,7 @@ WITH reset AS (
updated_at = NOW()
WHERE tenant_id = $1
AND quota_type = 'ai_points'
AND status = 'confirmed'
AND status IN ('pending', 'confirmed')
AND created_at >= $2
AND created_at < $3
RETURNING reserved_amount
@@ -0,0 +1,77 @@
package repository
import (
"context"
"errors"
"strings"
"testing"
"time"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
)
var (
testStart = time.Unix(0, 0).UTC()
testEnd = time.Unix(3600, 0).UTC()
)
type captureResetSQLTx struct {
sql string
}
func (tx *captureResetSQLTx) QueryRow(_ context.Context, sql string, _ ...any) pgx.Row {
tx.sql = sql
return failingRow{}
}
func (tx *captureResetSQLTx) Begin(context.Context) (pgx.Tx, error) { return nil, errors.New("unused") }
func (tx *captureResetSQLTx) Commit(context.Context) error { return nil }
func (tx *captureResetSQLTx) Rollback(context.Context) error { return nil }
func (tx *captureResetSQLTx) CopyFrom(context.Context, pgx.Identifier, []string, pgx.CopyFromSource) (int64, error) {
return 0, errors.New("unused")
}
func (tx *captureResetSQLTx) SendBatch(context.Context, *pgx.Batch) pgx.BatchResults {
return nil
}
func (tx *captureResetSQLTx) LargeObjects() pgx.LargeObjects { return pgx.LargeObjects{} }
func (tx *captureResetSQLTx) Prepare(context.Context, string, string) (*pgconn.StatementDescription, error) {
return nil, errors.New("unused")
}
func (tx *captureResetSQLTx) Exec(context.Context, string, ...any) (pgconn.CommandTag, error) {
return pgconn.CommandTag{}, errors.New("unused")
}
func (tx *captureResetSQLTx) Query(context.Context, string, ...any) (pgx.Rows, error) {
return nil, errors.New("unused")
}
func (tx *captureResetSQLTx) Conn() *pgx.Conn { return nil }
type failingRow struct{}
func (failingRow) Scan(...any) error { return errors.New("stop after capturing sql") }
func TestResetQuotaReservationsSQLMatchesEffectiveUsage(t *testing.T) {
tx := &captureResetSQLTx{}
_, _, _ = resetQuotaReservations(context.Background(), tx, 1, "article_generation", testStart, testEnd)
if !strings.Contains(tx.sql, "status IN ('pending', 'confirmed')") {
t.Fatalf("reset must clear every status counted as used; query:\n%s", tx.sql)
}
if strings.Contains(tx.sql, "ai_point_usage_logs") {
t.Fatalf("reset must not mutate AI point usage logs; query:\n%s", tx.sql)
}
}
func TestResetAIPointReservationsSQLDoesNotTouchUsageLogs(t *testing.T) {
tx := &captureResetSQLTx{}
_, _, _ = resetAIPointReservations(context.Background(), tx, 1, testStart, testEnd)
if !strings.Contains(tx.sql, "status IN ('pending', 'confirmed')") {
t.Fatalf("reset must clear every status counted as used; query:\n%s", tx.sql)
}
if strings.Contains(tx.sql, "ai_point_usage_logs") {
t.Fatalf("reset must not mutate AI point usage logs; query:\n%s", tx.sql)
}
}