@@ -23,14 +23,61 @@ import (
2323 "github.com/btcsuite/btcd/wire/v2"
2424 "github.com/btcsuite/btcwallet/wallet/internal/db"
2525 "github.com/btcsuite/btcwallet/wallet/internal/db/pg"
26+ pgschema "github.com/btcsuite/btcwallet/wallet/internal/sql/pg"
2627 "github.com/btcsuite/btcwallet/wallet/internal/sql/pg/sqlc"
2728 "github.com/docker/go-connections/nat"
29+ "github.com/jackc/pgx/v5"
30+ "github.com/jackc/pgx/v5/pgtype"
31+ "github.com/jackc/pgx/v5/stdlib"
2832 "github.com/stretchr/testify/require"
2933 "github.com/testcontainers/testcontainers-go"
3034 "github.com/testcontainers/testcontainers-go/modules/postgres"
3135 "github.com/testcontainers/testcontainers-go/wait"
3236)
3337
38+ // postgresTestStore owns integration-only adapters around a PostgreSQL Store.
39+ type postgresTestStore struct {
40+ * pg.Store
41+
42+ sqlDB * sql.DB
43+ }
44+
45+ var _ db.Store = (* postgresTestStore )(nil )
46+
47+ // DB returns the shared-pool database/sql adapter used by integration tests.
48+ func (s * postgresTestStore ) DB () * sql.DB {
49+ return s .sqlDB
50+ }
51+
52+ // RollbackAllMigrations rolls back all PostgreSQL migrations.
53+ func (s * postgresTestStore ) RollbackAllMigrations () error {
54+ return pgschema .RollbackMigrations (context .Background (), s .sqlDB )
55+ }
56+
57+ // ApplyAllMigrations reapplies all PostgreSQL migrations.
58+ func (s * postgresTestStore ) ApplyAllMigrations () error {
59+ return pgschema .ApplyMigrations (context .Background (), s .sqlDB )
60+ }
61+
62+ // Close closes the integration adapter and native PostgreSQL Store.
63+ func (s * postgresTestStore ) Close () error {
64+ err := s .sqlDB .Close ()
65+ storeErr := s .Store .Close ()
66+
67+ if err != nil {
68+ return fmt .Errorf ("close integration database: %w" , err )
69+ }
70+
71+ return storeErr
72+ }
73+
74+ // isPostgresTestStore reports whether store is the PostgreSQL test backend.
75+ func isPostgresTestStore (store any ) bool {
76+ _ , ok := store .(* postgresTestStore )
77+
78+ return ok
79+ }
80+
3481const (
3582 // pgMaxIdentifierLen is the PostgreSQL maximum identifier length
3683 // (NAMEDATALEN - 1).
@@ -249,7 +296,7 @@ func sanitizedPgDBName(t *testing.T) string {
249296// limit allows, exhausting the PostgreSQL connection pool. Avoid this by
250297// creating NewTestStore inside each parallel subtest so its lifecycle is tied
251298// to the subtest's parallel slot.
252- func NewTestStore (t * testing.T ) * pg. Store {
299+ func NewTestStore (t * testing.T ) * postgresTestStore {
253300 t .Helper ()
254301
255302 return NewTestStoreWithDerive (t , mockDeriveFunc ())
@@ -258,7 +305,16 @@ func NewTestStore(t *testing.T) *pg.Store {
258305// NewTestStoreWithDerive creates a new PostgreSQL database for testing with the
259306// provided address derivation function.
260307func NewTestStoreWithDerive (t * testing.T ,
261- deriveAddress db.AddressDerivationFunc ) * pg.Store {
308+ deriveAddress db.AddressDerivationFunc ) * postgresTestStore {
309+
310+ t .Helper ()
311+
312+ return newTestStore (t , deriveAddress , 0 )
313+ }
314+
315+ // newTestStore creates a PostgreSQL test store with an explicit pool limit.
316+ func newTestStore (t * testing.T , deriveAddress db.AddressDerivationFunc ,
317+ maxConnections int ) * postgresTestStore {
262318
263319 t .Helper ()
264320 ctx := t .Context ()
@@ -291,23 +347,52 @@ func NewTestStoreWithDerive(t *testing.T,
291347
292348 cfg := pg.Config {
293349 Dsn : testConnStr ,
294- MaxConnections : 0 ,
350+ MaxConnections : maxConnections ,
295351 DeriveAddress : deriveAddress ,
296352 }
297353
298354 store , err := pg .NewStore (t .Context (), cfg )
299355 require .NoError (t , err , "failed to create postgres store" )
300356
357+ testStore := & postgresTestStore {
358+ Store : store ,
359+ sqlDB : stdlib .OpenDBFromPool (store .Pool ()),
360+ }
361+
301362 t .Cleanup (func () {
302- _ = store .Close ()
363+ _ = testStore .Close ()
303364 })
304365
305- return store
366+ return testStore
367+ }
368+
369+ // TestPostgresTestStoreSharesPool verifies the integration adapter releases
370+ // its connection back to the native pool when only one pool slot is available.
371+ func TestPostgresTestStoreSharesPool (t * testing.T ) {
372+ t .Parallel ()
373+
374+ store := newTestStore (t , mockDeriveFunc (), 1 )
375+ sqlDB := store .DB ()
376+ require .Same (t , sqlDB , store .DB ())
377+
378+ ctx , cancel := context .WithTimeout (
379+ t .Context (), db .DefaultConnectionTimeout ,
380+ )
381+ defer cancel ()
382+
383+ require .NoError (t , sqlDB .PingContext (ctx ))
384+ require .Zero (t , store .Pool ().Stat ().AcquiredConns ())
385+
386+ wallets , err := store .Queries ().ListWallets (
387+ ctx , sqlc.ListWalletsParams {PageLimit : 1 },
388+ )
389+ require .NoError (t , err )
390+ require .Empty (t , wallets )
306391}
307392
308393// childSpendingTxIDs returns the direct child transaction IDs recorded for the
309394// provided parent transaction hash.
310- func childSpendingTxIDs (t * testing.T , store * pg. Store ,
395+ func childSpendingTxIDs (t * testing.T , store * postgresTestStore ,
311396 walletID uint32 ,
312397 txHash chainhash.Hash ) []int64 {
313398
@@ -340,7 +425,7 @@ func childSpendingTxIDs(t *testing.T, store *pg.Store,
340425
341426// txIDByHash returns the database row ID for the given wallet-scoped
342427// transaction hash and reports whether the row exists.
343- func txIDByHash (t * testing.T , store * pg. Store , walletID uint32 ,
428+ func txIDByHash (t * testing.T , store * postgresTestStore , walletID uint32 ,
344429 txHash chainhash.Hash ) (int64 , bool ) {
345430
346431 t .Helper ()
@@ -352,7 +437,7 @@ func txIDByHash(t *testing.T, store *pg.Store, walletID uint32,
352437 },
353438 )
354439 if err != nil {
355- if errors .Is (err , sql .ErrNoRows ) {
440+ if errors .Is (err , pgx .ErrNoRows ) {
356441 return 0 , false
357442 }
358443
@@ -364,7 +449,7 @@ func txIDByHash(t *testing.T, store *pg.Store, walletID uint32,
364449
365450// setTxStatus rewrites one wallet-scoped transaction row to the provided
366451// status using the internal status-update query.
367- func setTxStatus (t * testing.T , store * pg. Store , walletID uint32 ,
452+ func setTxStatus (t * testing.T , store * postgresTestStore , walletID uint32 ,
368453 txHash chainhash.Hash , status db.TxStatus ) {
369454
370455 t .Helper ()
@@ -385,7 +470,7 @@ func setTxStatus(t *testing.T, store *pg.Store, walletID uint32,
385470
386471// walletUtxoExists reports whether one wallet-scoped outpoint is currently
387472// present in the UTXO set.
388- func walletUtxoExists (t * testing.T , store * pg. Store ,
473+ func walletUtxoExists (t * testing.T , store * postgresTestStore ,
389474 walletID uint32 ,
390475 outPoint wire.OutPoint ) bool {
391476
@@ -399,7 +484,7 @@ func walletUtxoExists(t *testing.T, store *pg.Store,
399484 },
400485 )
401486 if err != nil {
402- if errors .Is (err , sql .ErrNoRows ) {
487+ if errors .Is (err , pgx .ErrNoRows ) {
403488 return false
404489 }
405490
@@ -411,7 +496,7 @@ func walletUtxoExists(t *testing.T, store *pg.Store,
411496
412497// walletUtxoSpent reports whether one wallet-scoped outpoint exists and is
413498// recorded as spent, i.e. its spend edge points at a spending transaction.
414- func walletUtxoSpent (t * testing.T , store * pg. Store ,
499+ func walletUtxoSpent (t * testing.T , store * postgresTestStore ,
415500 walletID uint32 ,
416501 outPoint wire.OutPoint ) bool {
417502
@@ -425,7 +510,7 @@ func walletUtxoSpent(t *testing.T, store *pg.Store,
425510 },
426511 )
427512 if err != nil {
428- if errors .Is (err , sql .ErrNoRows ) {
513+ if errors .Is (err , pgx .ErrNoRows ) {
429514 return false
430515 }
431516
@@ -436,7 +521,7 @@ func walletUtxoSpent(t *testing.T, store *pg.Store,
436521}
437522
438523// clearUtxosSpentByTxID clears all UTXO spend edges claimed by one transaction.
439- func clearUtxosSpentByTxID (t * testing.T , store * pg. Store ,
524+ func clearUtxosSpentByTxID (t * testing.T , store * postgresTestStore ,
440525 walletID uint32 , txHash chainhash.Hash ) {
441526
442527 t .Helper ()
@@ -447,7 +532,7 @@ func clearUtxosSpentByTxID(t *testing.T, store *pg.Store,
447532 rows , err := store .Queries ().ClearUtxosSpentByTxID (
448533 t .Context (), sqlc.ClearUtxosSpentByTxIDParams {
449534 WalletID : int64 (walletID ),
450- SpentByTxID : sql. NullInt64 {
535+ SpentByTxID : pgtype. Int8 {
451536 Int64 : txID ,
452537 Valid : true ,
453538 },
0 commit comments