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
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:
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user