2026-08-04 18:24:39 +08:00
package tasks
import (
"context"
"database/sql"
"errors"
"fmt"
"math"
"reflect"
"sort"
2026-08-05 01:25:23 +08:00
"strings"
2026-08-04 18:24:39 +08:00
"sync"
"testing"
"time"
"cmbuyer/admin/internal/migrations"
)
var fixedStartTime = time . Date ( 2026 , 8 , 4 , 9 , 2 , 3 , 456000000 , time . FixedZone ( "UTC+8" , 8 * 60 * 60 ))
func TestStartPurchasesPersistsCompleteSnapshotsForOneAndHundredTasks ( t * testing . T ) {
for _ , count := range [] int { 1 , 100 } {
t . Run ( fmt . Sprintf ( "%d tasks" , count ), func ( t * testing . T ) {
database := migratedDatabase ( t )
store := configuredStartStore ( t , database )
store . now = func () time . Time { return fixedStartTime }
items := make ([] StartItem , 0 , count )
wantDrafts := make ( map [ string ] Draft , count )
for index := 1 ; index <= count ; index ++ {
id := startTestUUID ( index )
draft := Draft {
ID : id ,
Title : fmt . Sprintf ( "task-%03d" , index ),
GoodsID : fmt . Sprintf ( "937122%06d" , index ),
SKUColor : fmt . Sprintf ( "color-%03d" , index ),
SKUSize : fmt . Sprintf ( "size-%03d" , index ),
Quantity : index % 10 + 1 ,
MaxTotalPrice : fmt . Sprintf ( "%d.%02d" , index + 10 , index % 100 ),
}
if _ , err := store . CreateDraft ( context . Background (), draft ); err != nil {
t . Fatalf ( "create draft %d: %v" , index , err )
}
items = append ( items , StartItem { TaskID : id , ExpectedTaskVersion : 1 })
wantDrafts [ id ] = draft
}
sort . Slice ( items , func ( i , j int ) bool { return items [ i ]. TaskID > items [ j ]. TaskID })
command := StartCommand { StartKey : startTestUUID ( 1001 + count ), Tasks : items }
result , err := store . StartPurchases ( context . Background (), command , "authenticated-admin" )
if err != nil {
t . Fatalf ( "StartPurchases: %v" , err )
}
if result . StartKey != command . StartKey || result . AuthorizedCount != count || result . PaymentAutomated || len ( result . Tasks ) != count {
t . Fatalf ( "result = %#v" , result )
}
wantCreated := fixedStartTime . UTC ()
wantExpires := wantCreated . Add ( 15 * time . Minute )
seenAuthorizationIDs := map [ string ] bool {}
for index , authorized := range result . Tasks {
if index > 0 && result . Tasks [ index - 1 ]. TaskID >= authorized . TaskID {
t . Fatalf ( "result is not in canonical task order: %#v" , result . Tasks )
}
if authorized . TaskVersion != 2 || ! authorized . ExpiresAt . Equal ( wantExpires ) || ! validUUID ( authorized . AuthorizationID ) || seenAuthorizationIDs [ authorized . AuthorizationID ] {
t . Fatalf ( "authorized task = %#v" , authorized )
}
seenAuthorizationIDs [ authorized . AuthorizationID ] = true
want := wantDrafts [ authorized . TaskID ]
var taskStatus , taskUpdated , authTaskID , authStartKey , goodsID , color , size , priceCap , authStatus , createdBy , createdAt , expiresAt string
var taskVersion , authTaskVersion , quantity int
err := database . QueryRow ( `
SELECT t.status,t.version,t.updated_at,
a.task_id,a.task_version,a.start_key,a.goods_id,a.sku_color,a.sku_size,a.quantity,a.total_price_cap,a.status,a.created_by,a.created_at,a.expires_at
FROM tasks t JOIN order_authorizations a ON a.task_id=t.id WHERE a.id=?` , authorized . AuthorizationID ).
Scan ( & taskStatus , & taskVersion , & taskUpdated , & authTaskID , & authTaskVersion , & authStartKey , & goodsID , & color , & size , & quantity , & priceCap , & authStatus , & createdBy , & createdAt , & expiresAt )
if err != nil {
t . Fatalf ( "read authorization snapshot: %v" , err )
}
if taskStatus != "PENDING" || taskVersion != 2 || taskUpdated != wantCreated . Format ( time . RFC3339Nano ) ||
authTaskID != want . ID || authTaskVersion != 2 || authStartKey != command . StartKey ||
goodsID != want . GoodsID || color != want . SKUColor || size != want . SKUSize || quantity != want . Quantity || priceCap != want . MaxTotalPrice ||
authStatus != "ACTIVE" || createdBy != "authenticated-admin" || createdAt != wantCreated . Format ( time . RFC3339Nano ) || expiresAt != wantExpires . Format ( time . RFC3339Nano ) {
t . Fatalf ( "stored task/authorization mismatch for %s" , want . ID )
}
}
var distinctCreated , distinctExpires int
if err := database . QueryRow ( `SELECT COUNT(DISTINCT created_at), COUNT(DISTINCT expires_at) FROM order_authorizations WHERE start_key=?` , command . StartKey ). Scan ( & distinctCreated , & distinctExpires ); err != nil {
t . Fatal ( err )
}
if distinctCreated != 1 || distinctExpires != 1 {
t . Fatalf ( "batch timestamps are not shared: created=%d expires=%d" , distinctCreated , distinctExpires )
}
})
}
}
func TestStartPurchasesRejectsInvalidCommandsAndPolicyWithoutWrites ( t * testing . T ) {
validItem := StartItem { TaskID : startTestUUID ( 1 ), ExpectedTaskVersion : 1 }
hundredOne := make ([] StartItem , 101 )
for index := range hundredOne {
hundredOne [ index ] = StartItem { TaskID : startTestUUID ( index + 1 ), ExpectedTaskVersion : 1 }
}
for name , command := range map [ string ] StartCommand {
"invalid start key" : { StartKey : "not-a-uuid" , Tasks : [] StartItem { validItem }},
"empty tasks" : { StartKey : startTestUUID ( 1001 )},
"over batch limit" : { StartKey : startTestUUID ( 1001 ), Tasks : hundredOne },
"invalid task id" : { StartKey : startTestUUID ( 1001 ), Tasks : [] StartItem {{ TaskID : "1" , ExpectedTaskVersion : 1 }}},
"duplicate task" : { StartKey : startTestUUID ( 1001 ), Tasks : [] StartItem { validItem , validItem }},
"zero version" : { StartKey : startTestUUID ( 1001 ), Tasks : [] StartItem {{ TaskID : validItem . TaskID }}},
"overflow version" : { StartKey : startTestUUID ( 1001 ), Tasks : [] StartItem {{ TaskID : validItem . TaskID , ExpectedTaskVersion : math . MaxInt }}},
} {
t . Run ( name , func ( t * testing . T ) {
database := migratedDatabase ( t )
store := configuredStartStore ( t , database )
_ , err := store . StartPurchases ( context . Background (), command , "admin" )
if ! errors . Is ( err , ErrInvalidStart ) {
t . Fatalf ( "error = %v, want ErrInvalidStart" , err )
}
assertAuthorizationCount ( t , database , 0 )
})
}
for name , mutate := range map [ string ] func ( * SQLiteStore ){
"zero ttl" : func ( store * SQLiteStore ) { store . policy . AuthorizationTTL = 0 },
"zero quantity" : func ( store * SQLiteStore ) { store . policy . MaxQuantity = 0 },
"bad max price" : func ( store * SQLiteStore ) { store . policy . MaxTotalPrice = "999" },
} {
t . Run ( name , func ( t * testing . T ) {
database := migratedDatabase ( t )
store := configuredStartStore ( t , database )
createStartDraft ( t , store , validItem . TaskID )
mutate ( store )
_ , err := store . StartPurchases ( context . Background (), StartCommand { StartKey : startTestUUID ( 1001 ), Tasks : [] StartItem { validItem }}, "admin" )
if ! errors . Is ( err , ErrInvalidStart ) {
t . Fatalf ( "error = %v, want ErrInvalidStart" , err )
}
assertDraftUnchanged ( t , database , validItem . TaskID )
assertAuthorizationCount ( t , database , 0 )
})
}
database := migratedDatabase ( t )
store := configuredStartStore ( t , database )
createStartDraft ( t , store , validItem . TaskID )
_ , err := store . StartPurchases ( context . Background (), StartCommand { StartKey : startTestUUID ( 1001 ), Tasks : [] StartItem { validItem }}, "" )
if ! errors . Is ( err , ErrInvalidStart ) {
t . Fatalf ( "empty created_by error = %v" , err )
}
assertDraftUnchanged ( t , database , validItem . TaskID )
}
func TestStartPurchasesRejectsEveryTaskConflictWithoutAuthorization ( t * testing . T ) {
for name , mutate := range map [ string ] func ( * testing . T , * SQLiteStore , string , * StartItem ){
"missing" : func ( _ * testing . T , _ * SQLiteStore , _ string , item * StartItem ) {
item . TaskID = startTestUUID ( 99 )
},
"not draft" : func ( t * testing . T , store * SQLiteStore , id string , _ * StartItem ) {
execTestSQL ( t , store . database , `UPDATE tasks SET status='PENDING' WHERE id=?` , id )
},
"version mismatch" : func ( _ * testing . T , _ * SQLiteStore , _ string , item * StartItem ) {
item . ExpectedTaskVersion = 2
},
"empty goods id" : func ( t * testing . T , store * SQLiteStore , id string , _ * StartItem ) {
execTestSQL ( t , store . database , `UPDATE tasks SET goods_id='' WHERE id=?` , id )
},
"nondigit goods id" : func ( t * testing . T , store * SQLiteStore , id string , _ * StartItem ) {
execTestSQL ( t , store . database , `UPDATE tasks SET goods_id='937x' WHERE id=?` , id )
},
2026-08-05 01:25:23 +08:00
"overlong goods id" : func ( t * testing . T , store * SQLiteStore , id string , _ * StartItem ) {
execTestSQL ( t , store . database , `UPDATE tasks SET goods_id=? WHERE id=?` , strings . Repeat ( "1" , MaxGoodsIDCharacters + 1 ), id )
},
"invalid utf8 title" : func ( t * testing . T , store * SQLiteStore , id string , _ * StartItem ) {
execTestSQL ( t , store . database , `UPDATE tasks SET title=? WHERE id=?` , string ([] byte { 0xff }), id )
},
"overlong title" : func ( t * testing . T , store * SQLiteStore , id string , _ * StartItem ) {
execTestSQL ( t , store . database , `UPDATE tasks SET title=? WHERE id=?` , strings . Repeat ( "😀" , MaxTitleCodePoints + 1 ), id )
},
2026-08-04 18:24:39 +08:00
"empty color" : func ( t * testing . T , store * SQLiteStore , id string , _ * StartItem ) {
execTestSQL ( t , store . database , `UPDATE tasks SET sku_color='' WHERE id=?` , id )
},
2026-08-05 01:25:23 +08:00
"overlong color" : func ( t * testing . T , store * SQLiteStore , id string , _ * StartItem ) {
execTestSQL ( t , store . database , `UPDATE tasks SET sku_color=? WHERE id=?` , strings . Repeat ( "色" , MaxSKUTextCodePoints + 1 ), id )
},
2026-08-04 18:24:39 +08:00
"empty size" : func ( t * testing . T , store * SQLiteStore , id string , _ * StartItem ) {
execTestSQL ( t , store . database , `UPDATE tasks SET sku_size='' WHERE id=?` , id )
},
2026-08-05 01:25:23 +08:00
"overlong size" : func ( t * testing . T , store * SQLiteStore , id string , _ * StartItem ) {
execTestSQL ( t , store . database , `UPDATE tasks SET sku_size=? WHERE id=?` , strings . Repeat ( "码" , MaxSKUTextCodePoints + 1 ), id )
},
2026-08-04 18:24:39 +08:00
"quantity over policy" : func ( _ * testing . T , store * SQLiteStore , _ string , _ * StartItem ) {
store . policy . MaxQuantity = 1
},
"noncanonical price one decimal" : func ( t * testing . T , store * SQLiteStore , id string , _ * StartItem ) {
execTestSQL ( t , store . database , `UPDATE tasks SET max_total_price='12.8' WHERE id=?` , id )
},
"noncanonical leading zero" : func ( t * testing . T , store * SQLiteStore , id string , _ * StartItem ) {
execTestSQL ( t , store . database , `UPDATE tasks SET max_total_price='012.80' WHERE id=?` , id )
},
2026-08-05 01:25:23 +08:00
"overlong canonical price" : func ( t * testing . T , store * SQLiteStore , id string , _ * StartItem ) {
execTestSQL ( t , store . database , `UPDATE tasks SET max_total_price=? WHERE id=?` , strings . Repeat ( "1" , MaxMoneyASCIICharacters - 2 ) + ".00" , id )
},
2026-08-04 18:24:39 +08:00
"price over policy" : func ( _ * testing . T , store * SQLiteStore , _ string , _ * StartItem ) {
store . policy . MaxTotalPrice = "12.79"
},
} {
t . Run ( name , func ( t * testing . T ) {
database := migratedDatabase ( t )
store := configuredStartStore ( t , database )
id := startTestUUID ( 1 )
createStartDraft ( t , store , id )
item := StartItem { TaskID : id , ExpectedTaskVersion : 1 }
mutate ( t , store , id , & item )
_ , err := store . StartPurchases ( context . Background (), StartCommand { StartKey : startTestUUID ( 1001 ), Tasks : [] StartItem { item }}, "admin" )
if ! errors . Is ( err , ErrStartConflict ) {
t . Fatalf ( "error = %v, want ErrStartConflict" , err )
}
assertAuthorizationCount ( t , database , 0 )
})
}
}
2026-08-05 01:25:23 +08:00
func TestStartPurchasesReplayRejectsMalformedAuthorizationOrTaskSnapshot ( t * testing . T ) {
mutations := map [ string ] func ( * testing . T , * sql . DB , string ){
"authorization goods id" : func ( t * testing . T , database * sql . DB , id string ) {
execTestSQL ( t , database , `UPDATE order_authorizations SET goods_id=? WHERE task_id=?` , strings . Repeat ( "1" , MaxGoodsIDCharacters + 1 ), id )
},
"authorization color" : func ( t * testing . T , database * sql . DB , id string ) {
execTestSQL ( t , database , `UPDATE order_authorizations SET sku_color=? WHERE task_id=?` , strings . Repeat ( "色" , MaxSKUTextCodePoints + 1 ), id )
},
"authorization size" : func ( t * testing . T , database * sql . DB , id string ) {
execTestSQL ( t , database , `UPDATE order_authorizations SET sku_size=? WHERE task_id=?` , strings . Repeat ( "码" , MaxSKUTextCodePoints + 1 ), id )
},
"authorization money" : func ( t * testing . T , database * sql . DB , id string ) {
execTestSQL ( t , database , `UPDATE order_authorizations SET total_price_cap=? WHERE task_id=?` , strings . Repeat ( "1" , MaxMoneyASCIICharacters - 2 ) + ".00" , id )
},
"task title" : func ( t * testing . T , database * sql . DB , id string ) {
execTestSQL ( t , database , `UPDATE tasks SET title=? WHERE id=?` , strings . Repeat ( "😀" , MaxTitleCodePoints + 1 ), id )
},
}
for name , mutate := range mutations {
t . Run ( name , func ( t * testing . T ) {
database := migratedDatabase ( t )
store := configuredStartStore ( t , database )
id := startTestUUID ( 1 )
createStartDraft ( t , store , id )
command := StartCommand { StartKey : startTestUUID ( 1001 ), Tasks : [] StartItem {{ TaskID : id , ExpectedTaskVersion : 1 }}}
if _ , err := store . StartPurchases ( context . Background (), command , "admin" ); err != nil {
t . Fatal ( err )
}
mutate ( t , database , id )
if _ , err := store . StartPurchases ( context . Background (), command , "admin" ); ! errors . Is ( err , ErrStartConflict ) {
t . Fatalf ( "replay error = %v, want ErrStartConflict" , err )
}
assertAuthorizationCount ( t , database , 1 )
})
}
}
2026-08-04 18:24:39 +08:00
func TestStartPurchasesRollsBackWholeBatchForLateConflictAndSQLFailure ( t * testing . T ) {
for _ , test := range [] struct {
name string
breakBatch func ( * testing . T , * SQLiteStore , string )
}{
{ name : "late validation conflict" , breakBatch : func ( t * testing . T , store * SQLiteStore , secondID string ) {
execTestSQL ( t , store . database , `UPDATE tasks SET sku_size='' WHERE id=?` , secondID )
}},
{ name : "late SQL failure" , breakBatch : func ( t * testing . T , store * SQLiteStore , secondID string ) {
statement := fmt . Sprintf ( `CREATE TRIGGER reject_second_authorization BEFORE INSERT ON order_authorizations WHEN NEW.task_id='%s' BEGIN SELECT RAISE(ABORT, 'test failure'); END` , secondID )
execTestSQL ( t , store . database , statement )
}},
} {
t . Run ( test . name , func ( t * testing . T ) {
database := migratedDatabase ( t )
store := configuredStartStore ( t , database )
firstID , secondID := startTestUUID ( 1 ), startTestUUID ( 2 )
createStartDraft ( t , store , firstID )
createStartDraft ( t , store , secondID )
test . breakBatch ( t , store , secondID )
_ , err := store . StartPurchases ( context . Background (), StartCommand { StartKey : startTestUUID ( 1001 ), Tasks : [] StartItem {{ TaskID : firstID , ExpectedTaskVersion : 1 }, { TaskID : secondID , ExpectedTaskVersion : 1 }}}, "admin" )
if err == nil {
t . Fatal ( "StartPurchases unexpectedly succeeded" )
}
assertDraftUnchanged ( t , database , firstID )
var secondStatus string
var secondVersion int
if err := database . QueryRow ( `SELECT status,version FROM tasks WHERE id=?` , secondID ). Scan ( & secondStatus , & secondVersion ); err != nil {
t . Fatal ( err )
}
if secondStatus != "DRAFT" || secondVersion != 1 {
t . Fatalf ( "second task = %s/v%d, want DRAFT/v1" , secondStatus , secondVersion )
}
assertAuthorizationCount ( t , database , 0 )
})
}
}
func TestStartPurchasesReplayIsStableAndRejectsDifferentOrIncompleteSets ( t * testing . T ) {
database := migratedDatabase ( t )
store := configuredStartStore ( t , database )
firstID , secondID , thirdID := startTestUUID ( 1 ), startTestUUID ( 2 ), startTestUUID ( 3 )
for _ , id := range [] string { firstID , secondID , thirdID } {
createStartDraft ( t , store , id )
}
command := StartCommand { StartKey : startTestUUID ( 1001 ), Tasks : [] StartItem {{ TaskID : secondID , ExpectedTaskVersion : 1 }, { TaskID : firstID , ExpectedTaskVersion : 1 }}}
first , err := store . StartPurchases ( context . Background (), command , "admin" )
if err != nil {
t . Fatal ( err )
}
command . Tasks [ 0 ], command . Tasks [ 1 ] = command . Tasks [ 1 ], command . Tasks [ 0 ]
replay , err := store . StartPurchases ( context . Background (), command , "admin" )
if err != nil || ! reflect . DeepEqual ( replay , first ) {
t . Fatalf ( "replay = (%#v, %v), want %#v" , replay , err , first )
}
assertAuthorizationCount ( t , database , 2 )
conflicting := [] StartCommand {
{ StartKey : command . StartKey , Tasks : command . Tasks [: 1 ]},
{ StartKey : command . StartKey , Tasks : [] StartItem {{ TaskID : firstID , ExpectedTaskVersion : 2 }, { TaskID : secondID , ExpectedTaskVersion : 1 }}},
{ StartKey : command . StartKey , Tasks : [] StartItem {{ TaskID : firstID , ExpectedTaskVersion : 1 }, { TaskID : secondID , ExpectedTaskVersion : 1 }, { TaskID : thirdID , ExpectedTaskVersion : 1 }}},
}
for _ , changed := range conflicting {
if _ , err := store . StartPurchases ( context . Background (), changed , "admin" ); ! errors . Is ( err , ErrStartConflict ) {
t . Fatalf ( "different payload error = %v" , err )
}
}
assertAuthorizationCount ( t , database , 2 )
assertDraftUnchanged ( t , database , thirdID )
execTestSQL ( t , database , `DELETE FROM order_authorizations WHERE task_id=?` , secondID )
if _ , err := store . StartPurchases ( context . Background (), command , "admin" ); ! errors . Is ( err , ErrStartConflict ) {
t . Fatalf ( "incomplete replay error = %v" , err )
}
assertAuthorizationCount ( t , database , 1 )
}
func TestStartPurchasesConcurrentReplayAndVersionRace ( t * testing . T ) {
t . Run ( "same key replays one stable result" , func ( t * testing . T ) {
database := migratedDatabase ( t )
store := configuredStartStore ( t , database )
id := startTestUUID ( 1 )
createStartDraft ( t , store , id )
command := StartCommand { StartKey : startTestUUID ( 1001 ), Tasks : [] StartItem {{ TaskID : id , ExpectedTaskVersion : 1 }}}
const callers = 16
start := make ( chan struct {})
results := make ( chan StartResult , callers )
errorsChannel := make ( chan error , callers )
var group sync . WaitGroup
for range callers {
group . Add ( 1 )
go func () {
defer group . Done ()
<- start
result , err := store . StartPurchases ( context . Background (), command , "admin" )
if err != nil {
errorsChannel <- err
return
}
results <- result
}()
}
close ( start )
group . Wait ()
close ( results )
close ( errorsChannel )
for err := range errorsChannel {
t . Fatalf ( "concurrent replay: %v" , err )
}
var want StartResult
for result := range results {
if want . StartKey == "" {
want = result
} else if ! reflect . DeepEqual ( result , want ) {
t . Fatalf ( "unstable replay: %#v != %#v" , result , want )
}
}
assertAuthorizationCount ( t , database , 1 )
var version int
if err := database . QueryRow ( `SELECT version FROM tasks WHERE id=?` , id ). Scan ( & version ); err != nil || version != 2 {
t . Fatalf ( "task version = %d, err=%v" , version , err )
}
})
t . Run ( "different keys race one expected version" , func ( t * testing . T ) {
database := migratedDatabase ( t )
store := configuredStartStore ( t , database )
id := startTestUUID ( 1 )
createStartDraft ( t , store , id )
start := make ( chan struct {})
errorsChannel := make ( chan error , 2 )
var group sync . WaitGroup
for _ , key := range [] string { startTestUUID ( 1001 ), startTestUUID ( 1002 )} {
group . Add ( 1 )
go func ( startKey string ) {
defer group . Done ()
<- start
_ , err := store . StartPurchases ( context . Background (), StartCommand { StartKey : startKey , Tasks : [] StartItem {{ TaskID : id , ExpectedTaskVersion : 1 }}}, "admin" )
errorsChannel <- err
}( key )
}
close ( start )
group . Wait ()
close ( errorsChannel )
successes , conflicts := 0 , 0
for err := range errorsChannel {
switch {
case err == nil :
successes ++
case errors . Is ( err , ErrStartConflict ):
conflicts ++
default :
t . Fatalf ( "unexpected race error: %v" , err )
}
}
if successes != 1 || conflicts != 1 {
t . Fatalf ( "success/conflict = %d/%d, want 1/1" , successes , conflicts )
}
assertAuthorizationCount ( t , database , 1 )
})
}
func TestSQLiteStoreRejectsV1SchemaAtStartup ( t * testing . T ) {
database := openDatabase ( t )
if err := migrations . Run ( context . Background (), database , migrationDirectory ( t ), "up-by-one" ); err != nil {
t . Fatalf ( "migrate to v1: %v" , err )
}
if _ , err := NewSQLiteStore ( database ); err == nil {
t . Fatal ( "NewSQLiteStore accepted the v1 two-pass schema" )
}
}
func configuredStartStore ( t * testing . T , database * sql . DB ) * SQLiteStore {
t . Helper ()
store , err := NewSQLiteStore ( database )
if err != nil {
t . Fatalf ( "NewSQLiteStore: %v" , err )
}
store . SetStartPolicy ( StartPolicy { AuthorizationTTL : 15 * time . Minute , MaxQuantity : 10 , MaxTotalPrice : "999.99" })
return store
}
func createStartDraft ( t * testing . T , store * SQLiteStore , id string ) {
t . Helper ()
draft := Draft { ID : id , Title : "test" , GoodsID : "937122477375" , SKUColor : "黑色" , SKUSize : "M" , Quantity : 2 , MaxTotalPrice : "12.80" }
if _ , err := store . CreateDraft ( context . Background (), draft ); err != nil {
t . Fatalf ( "CreateDraft: %v" , err )
}
}
func startTestUUID ( number int ) string {
return fmt . Sprintf ( "%08x-1234-4abc-a123-%012x" , number , number )
}
func assertAuthorizationCount ( t * testing . T , database * sql . DB , want int ) {
t . Helper ()
var got int
if err := database . QueryRow ( `SELECT COUNT(*) FROM order_authorizations` ). Scan ( & got ); err != nil {
t . Fatal ( err )
}
if got != want {
t . Fatalf ( "authorization count = %d, want %d" , got , want )
}
}
func assertDraftUnchanged ( t * testing . T , database * sql . DB , id string ) {
t . Helper ()
var status string
var version int
if err := database . QueryRow ( `SELECT status,version FROM tasks WHERE id=?` , id ). Scan ( & status , & version ); err != nil {
t . Fatal ( err )
}
if status != "DRAFT" || version != 1 {
t . Fatalf ( "task %s = %s/v%d, want DRAFT/v1" , id , status , version )
}
}
func execTestSQL ( t * testing . T , database * sql . DB , statement string , arguments ... any ) {
t . Helper ()
if _ , err := database . Exec ( statement , arguments ... ); err != nil {
t . Fatalf ( "execute test SQL: %v" , err )
}
}