package main import ( "testing" "github.com/glebarez/sqlite" "gorm.io/gorm" ) func newCreditTestApp(t *testing.T) *App { t.Helper() db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{}) if err != nil { t.Fatal(err) } if err := db.AutoMigrate(&Task{}, &MediaAsset{}, &User{}, &CreditAccount{}, &CreditTransaction{}, &CreditReservation{}); err != nil { t.Fatal(err) } return &App{db: db} } func TestCreditReservationRetryAfterRefund(t *testing.T) { a := newCreditTestApp(t) u := User{Username: "u", Role: RoleUser, Status: UserActive} if err := a.db.Create(&u).Error; err != nil { t.Fatal(err) } if err := a.db.Create(&CreditAccount{UserID: u.ID, Balance: 1}).Error; err != nil { t.Fatal(err) } task := Task{UserID: u.ID, Source: "kw", RID: "1", Quality: "standard", Status: "error"} if err := a.db.Create(&task).Error; err != nil { t.Fatal(err) } r1, err := a.reserveCredit(u.ID, task.ID, 1) if err != nil { t.Fatal(err) } task.CreditReservationID = r1.ID if err := a.refundCredit(&task); err != nil { t.Fatal(err) } r2, err := a.reserveCredit(u.ID, task.ID, 1) if err != nil { t.Fatal(err) } if r2.ID == r1.ID { t.Fatal("retry reused refunded reservation") } var account CreditAccount if err := a.db.Where("user_id = ?", u.ID).First(&account).Error; err != nil { t.Fatal(err) } if account.Balance != 1 || account.Reserved != 1 { t.Fatalf("unexpected account: %+v", account) } } func TestSharedAssetIsReusedAcrossUsers(t *testing.T) { a := newCreditTestApp(t) u1 := User{Username: "a1", Role: RoleUser, Status: UserActive} u2 := User{Username: "a2", Role: RoleUser, Status: UserActive} if err := a.db.Create(&u1).Error; err != nil { t.Fatal(err) } if err := a.db.Create(&u2).Error; err != nil { t.Fatal(err) } t1 := Task{UserID: u1.ID, Source: "kw", RID: "same", Quality: "standard", Title: "Song", Artist: "Artist"} t2 := Task{UserID: u2.ID, Source: "kw", RID: "same", Quality: "standard", Title: "Song", Artist: "Artist"} if err := a.db.Create(&t1).Error; err != nil { t.Fatal(err) } if err := a.db.Create(&t2).Error; err != nil { t.Fatal(err) } asset, reused, err := a.acquireAsset(&t1) if err != nil || reused { t.Fatalf("first acquire: asset=%+v reused=%v err=%v", asset, reused, err) } if asset.ID == 0 { t.Fatal("missing asset id") } if err := a.db.Model(&MediaAsset{}).Where("id = ?", asset.ID).Updates(map[string]any{"status": "done", "owner_task_id": 0, "local_path": "/etc/hosts", "format": "mp3", "size": 123}).Error; err != nil { t.Fatal(err) } t2.AssetID = asset.ID asset2, reused, err := a.acquireAsset(&t2) if err != nil || !reused || asset2.ID != asset.ID { t.Fatalf("second acquire: asset=%+v reused=%v err=%v", asset2, reused, err) } } func TestCommittedCreditIsNotRefunded(t *testing.T) { a := newCreditTestApp(t) u := User{Username: "u2", Role: RoleUser, Status: UserActive} if err := a.db.Create(&u).Error; err != nil { t.Fatal(err) } if err := a.db.Create(&CreditAccount{UserID: u.ID, Balance: 1}).Error; err != nil { t.Fatal(err) } task := Task{UserID: u.ID, Source: "kw", RID: "2", Quality: "standard", Status: "pending"} if err := a.db.Create(&task).Error; err != nil { t.Fatal(err) } r, err := a.reserveCredit(u.ID, task.ID, 1) if err != nil { t.Fatal(err) } task.CreditReservationID = r.ID if err := a.commitCredit(&task); err != nil { t.Fatal(err) } if err := a.refundCredit(&task); err != nil { t.Fatal(err) } var account CreditAccount if err := a.db.Where("user_id = ?", u.ID).First(&account).Error; err != nil { t.Fatal(err) } if account.Balance != 0 || account.Reserved != 0 { t.Fatalf("unexpected account after committed refund: %+v", account) } }