diff --git a/server/app/goauto/shopeeproduct/auto_match_scan_test.go b/server/app/goauto/shopeeproduct/auto_match_scan_test.go index 19a2b86..e8451ee 100644 --- a/server/app/goauto/shopeeproduct/auto_match_scan_test.go +++ b/server/app/goauto/shopeeproduct/auto_match_scan_test.go @@ -18,6 +18,23 @@ import ( const sizeScanSpecs = `[{"name":"尺码","role":"size","values":[{"name":"XL","source":"import"}]}]` const otherScanSpecs = `[{"name":"材质","role":"other","values":[{"name":"棉","source":"import"}]}]` +func openScanTestDB(t *testing.T) *gorm.DB { + t.Helper() + db := openTestDB(t) + sqlDB, err := db.DB() + if err != nil { + t.Fatal(err) + } + // Closing the final connection releases the named in-memory database, + // including when go test repeats the same t.Name via -count. + t.Cleanup(func() { + if err := sqlDB.Close(); err != nil { + t.Errorf("close scan test database: %v", err) + } + }) + return db +} + func seedScanProducts(t *testing.T, db *gorm.DB, pddID uint64, count int, specs string) []models.ShopeeProduct { t.Helper() products := make([]models.ShopeeProduct, count) @@ -62,7 +79,7 @@ func scanCheckpoint(t *testing.T, run *AutoMatchRunView, cursor uint64, reason s } func TestAutoMatchScanPassesLongEmptyPrefix(t *testing.T) { - db := openTestDB(t) + db := openScanTestDB(t) pdd := seedPDDProduct(t, db, "active") seedScanProducts(t, db, pdd.ID, 1812, `[]`) seedScanProducts(t, db, pdd.ID, 1, sizeScanSpecs) @@ -74,7 +91,7 @@ func TestAutoMatchScanPassesLongEmptyPrefix(t *testing.T) { } func TestAutoMatchScanRotatesAcrossServiceRestart(t *testing.T) { - db := openTestDB(t) + db := openScanTestDB(t) pdd := seedPDDProduct(t, db, "active") prefix := seedScanProducts(t, db, pdd.ID, 2001, otherScanSpecs) seedScanProducts(t, db, pdd.ID, 1, sizeScanSpecs) @@ -98,7 +115,7 @@ func TestAutoMatchScanRotatesAcrossServiceRestart(t *testing.T) { } func TestAutoMatchScanBatchLimitKeepsLastExaminedOnShortPage(t *testing.T) { - db := openTestDB(t) + db := openScanTestDB(t) pdd := seedPDDProduct(t, db, "active") products := seedScanProducts(t, db, pdd.ID, 3, sizeScanSpecs) for i := 0; i < 3; i++ { @@ -115,7 +132,7 @@ func TestAutoMatchScanBatchLimitKeepsLastExaminedOnShortPage(t *testing.T) { } func TestAutoMatchScanExactBudgetDoesNotAssumeEnd(t *testing.T) { - db := openTestDB(t) + db := openScanTestDB(t) pdd := seedPDDProduct(t, db, "active") products := seedScanProducts(t, db, pdd.ID, 2000, otherScanSpecs) first := runScan(t, NewService(db), 20) @@ -130,7 +147,7 @@ func TestAutoMatchScanExactBudgetDoesNotAssumeEnd(t *testing.T) { func TestAutoMatchEligibilityRejectsUnusableSpecsBeforePDDRead(t *testing.T) { for i, specs := range []string{"", `[]`, `null`, `""`, otherScanSpecs, `[{"role":"size","values":[]}]`, `[{"role":"color","values":[{"name":" "}]}]`} { t.Run(fmt.Sprint(i), func(t *testing.T) { - db := openTestDB(t) + db := openScanTestDB(t) missing := uint64(999) _, eligible, err := NewService(db).autoMatchEligibility(context.Background(), models.ShopeeProduct{PDDProductID: &missing, SpecsJSON: specs}) if err != nil || eligible { @@ -141,7 +158,7 @@ func TestAutoMatchEligibilityRejectsUnusableSpecsBeforePDDRead(t *testing.T) { } func TestAutoMatchScanEmptyRepresentationsAndSingleDimension(t *testing.T) { - db := openTestDB(t) + db := openScanTestDB(t) pdd := seedPDDProduct(t, db, "active") for _, specs := range []string{`[]`, `null`, `""`} { seedScanProducts(t, db, pdd.ID, 1, specs) @@ -157,7 +174,7 @@ func TestAutoMatchScanEmptyRepresentationsAndSingleDimension(t *testing.T) { func TestAutoMatchScanLatestCommittedZeroWinsAndNullIsIgnored(t *testing.T) { for _, latest := range []uint64{0, 2} { t.Run(fmt.Sprint(latest), func(t *testing.T) { - db := openTestDB(t) + db := openScanTestDB(t) pdd := seedPDDProduct(t, db, "active") products := seedScanProducts(t, db, pdd.ID, 3, otherScanSpecs) old := uint64(1) @@ -179,7 +196,7 @@ func TestAutoMatchScanLatestCommittedZeroWinsAndNullIsIgnored(t *testing.T) { func TestAutoMatchRenewAndFinishRejectLostLease(t *testing.T) { for _, change := range []string{"owner", "expired", "slot", "status"} { t.Run(change, func(t *testing.T) { - db := openTestDB(t) + db := openScanTestDB(t) s := NewService(db) run, _, err := s.StartAutoMatchRun(context.Background(), "manual", uuid.NewString(), nil, 20) if err != nil { @@ -218,7 +235,7 @@ func TestAutoMatchRenewAndFinishRejectLostLease(t *testing.T) { } func TestAutoMatchScanDatabaseErrorDoesNotCommitCheckpoint(t *testing.T) { - db := openTestDB(t) + db := openScanTestDB(t) pdd := seedPDDProduct(t, db, "active") products := seedScanProducts(t, db, pdd.ID, 2, sizeScanSpecs) s := NewService(db) @@ -255,7 +272,7 @@ func TestAutoMatchScanDatabaseErrorDoesNotCommitCheckpoint(t *testing.T) { func TestAutoMatchClaimRejectsLostOrExpiredRun(t *testing.T) { for _, change := range []string{"owner", "expired", "slot", "status"} { t.Run(change, func(t *testing.T) { - db := openTestDB(t) + db := openScanTestDB(t) s := NewService(db) run, _, err := s.StartAutoMatchRun(context.Background(), "manual", uuid.NewString(), nil, 20) if err != nil { @@ -291,7 +308,7 @@ func TestAutoMatchClaimRejectsLostOrExpiredRun(t *testing.T) { } func TestAutoMatchScanOwnerLossDuringProviderStopsNextCallAndSave(t *testing.T) { - db := openTestDB(t) + db := openScanTestDB(t) s := NewService(db) pdd := seedPDDProduct(t, db, "active") products := seedScanProducts(t, db, pdd.ID, 2, `[{"name":"颜色","role":"color","values":[{"name":"深黑","source":"import"}]},{"name":"尺码","role":"size","values":[{"name":"大号","source":"import"}]}]`) @@ -342,7 +359,7 @@ func TestAutoMatchScanOwnerLossDuringProviderStopsNextCallAndSave(t *testing.T) } func TestAutoMatchScanDeadlineStopsProviderAndKeepsLastDecision(t *testing.T) { - db := openTestDB(t) + db := openScanTestDB(t) pdd := seedPDDProduct(t, db, "active") first := seedScanProducts(t, db, pdd.ID, 1, otherScanSpecs)[0] products := seedScanProducts(t, db, pdd.ID, 2, `[{"name":"颜色","role":"color","values":[{"name":"深黑","source":"import"}]}]`) @@ -394,7 +411,7 @@ func TestAutoMatchScanDeadlineStopsProviderAndKeepsLastDecision(t *testing.T) { } func TestAutoMatchScanDeadlineBoundsDatabaseAndNoClaimAfterBudget(t *testing.T) { - db := openTestDB(t) + db := openScanTestDB(t) pdd := seedPDDProduct(t, db, "active") seedScanProducts(t, db, pdd.ID, 2, sizeScanSpecs) s := NewService(db) @@ -439,7 +456,7 @@ func TestAutoMatchScanDeadlineBoundsDatabaseAndNoClaimAfterBudget(t *testing.T) } func TestAutoMatchScanRecoveryIgnoresExpiredCheckpoint(t *testing.T) { - db := openTestDB(t) + db := openScanTestDB(t) s := NewService(db) run, _, err := s.StartAutoMatchRun(context.Background(), "manual", uuid.NewString(), nil, 20) if err != nil { @@ -461,7 +478,7 @@ func TestAutoMatchScanRecoveryIgnoresExpiredCheckpoint(t *testing.T) { } func TestAutoMatchScanExactPageBoundaryAndCandidateFilters(t *testing.T) { - db := openTestDB(t) + db := openScanTestDB(t) pdd := seedPDDProduct(t, db, "active") seedScanProducts(t, db, pdd.ID, 200, otherScanSpecs) seedScanProducts(t, db, pdd.ID, 1, sizeScanSpecs) @@ -485,7 +502,7 @@ func TestAutoMatchScanExactPageBoundaryAndCandidateFilters(t *testing.T) { func TestAutoMatchWorkRetryAndCooldownPreserved(t *testing.T) { for _, status := range []string{"completed", "unmatched", "max_retry", "cooldown", "occupied", "retryable"} { t.Run(status, func(t *testing.T) { - db := openTestDB(t) + db := openScanTestDB(t) s := NewService(db) run, _, err := s.StartAutoMatchRun(context.Background(), "manual", uuid.NewString(), nil, 20) if err != nil { @@ -534,7 +551,7 @@ func TestAutoMatchWorkRetryAndCooldownPreserved(t *testing.T) { } func TestAutoMatchClaimDoesNotUseLeaseTimeBeforeLockWait(t *testing.T) { - db := openTestDB(t) + db := openScanTestDB(t) s := NewService(db) run, _, err := s.StartAutoMatchRun(context.Background(), "manual", uuid.NewString(), nil, 20) if err != nil { @@ -558,7 +575,7 @@ func TestAutoMatchClaimDoesNotUseLeaseTimeBeforeLockWait(t *testing.T) { } func TestAutoMatchProviderGuardDatabaseErrorMustNotRetryOrCheckpoint(t *testing.T) { - db := openTestDB(t) + db := openScanTestDB(t) pdd := seedPDDProduct(t, db, "active") seedScanProducts(t, db, pdd.ID, 1, `[{"name":"颜色","role":"color","values":[{"name":"深黑","source":"import"}]}]`) var calls atomic.Int32 @@ -603,7 +620,7 @@ func TestAutoMatchProviderGuardDatabaseErrorMustNotRetryOrCheckpoint(t *testing. } func TestAutoMatchProviderSettingsDatabaseErrorMustNotRetryOrCheckpoint(t *testing.T) { - db := openTestDB(t) + db := openScanTestDB(t) pdd := seedPDDProduct(t, db, "active") seedScanProducts(t, db, pdd.ID, 1, `[{"name":"颜色","role":"color","values":[{"name":"深黑","source":"import"}]}]`) var calls atomic.Int32 @@ -648,7 +665,7 @@ func TestAutoMatchProviderSettingsDatabaseErrorMustNotRetryOrCheckpoint(t *testi func TestAutoMatchRunDatabaseWriteFailuresAreReturned(t *testing.T) { for _, operation := range []string{"renew", "finish", "work"} { t.Run(operation, func(t *testing.T) { - db := openTestDB(t) + db := openScanTestDB(t) s := NewService(db) run, _, err := s.StartAutoMatchRun(context.Background(), "manual", uuid.NewString(), nil, 20) if err != nil { @@ -686,7 +703,7 @@ func TestAutoMatchRunDatabaseWriteFailuresAreReturned(t *testing.T) { } func TestAutoMatchDeadlineDuringCheckpointReadCannotCommitFalseHead(t *testing.T) { - db := openTestDB(t) + db := openScanTestDB(t) s := NewService(db) checkpoint := uint64(1700) old := models.ShopeeSpecAutoMatchRun{RequestID: uuid.NewString(), Trigger: "manual", Status: "completed", ResumeAfterID: &checkpoint}