@@ -3,7 +3,10 @@ package dbtestutil
33import (
44 "context"
55 "database/sql"
6+ "fmt"
7+ "net/url"
68 "os"
9+ "strings"
710 "testing"
811
912 "github.com/stretchr/testify/require"
@@ -19,9 +22,29 @@ func WillUsePostgres() bool {
1922 return os .Getenv ("DB" ) != ""
2023}
2124
22- func NewDB (t testing.TB ) (database.Store , pubsub.Pubsub ) {
25+ type options struct {
26+ fixedTimezone bool
27+ }
28+
29+ type Option func (* options )
30+
31+ // WithFixedTimezone disables the random timezone setting for the database.
32+ //
33+ // DEPRECATED: If you need to use this, you may have a timezone-related bug.
34+ func WithFixedTimezone () Option {
35+ return func (o * options ) {
36+ o .fixedTimezone = true
37+ }
38+ }
39+
40+ func NewDB (t testing.TB , opts ... Option ) (database.Store , pubsub.Pubsub ) {
2341 t .Helper ()
2442
43+ var o options
44+ for _ , opt := range opts {
45+ opt (& o )
46+ }
47+
2548 db := dbfake .New ()
2649 ps := pubsub .NewInMemory ()
2750 if WillUsePostgres () {
@@ -35,6 +58,13 @@ func NewDB(t testing.TB) (database.Store, pubsub.Pubsub) {
3558 require .NoError (t , err )
3659 t .Cleanup (closePg )
3760 }
61+
62+ if ! o .fixedTimezone {
63+ // To make sure we find timezone-related issues, we set the timezone of the database to a random one.
64+ dbName := dbNameFromConnectionURL (t , connectionURL )
65+ setRandDBTimezone (t , connectionURL , dbName )
66+ }
67+
3868 sqlDB , err := sql .Open ("postgres" , connectionURL )
3969 require .NoError (t , err )
4070 t .Cleanup (func () {
@@ -51,3 +81,51 @@ func NewDB(t testing.TB) (database.Store, pubsub.Pubsub) {
5181
5282 return db , ps
5383}
84+
85+ // setRandDBTimezone sets the timezone of the database to the given timezone.
86+ // The timezone change does not take effect until the next session, so
87+ // we do this in our own connection
88+ func setRandDBTimezone (t testing.TB , dbURL , dbname string ) {
89+ t .Helper ()
90+
91+ sqlDB , err := sql .Open ("postgres" , dbURL )
92+ require .NoError (t , err )
93+ defer func () {
94+ _ = sqlDB .Close ()
95+ }()
96+
97+ // Pick a random timezone. We can simply pick from pg_timezone_names.
98+ var tz string
99+ err = sqlDB .QueryRow ("SELECT name FROM pg_timezone_names ORDER BY RANDOM() LIMIT 1" ).Scan (& tz )
100+ require .NoError (t , err )
101+
102+ // Set the timezone for the database.
103+ t .Logf ("setting timezone of database %q to %q" , dbname , tz )
104+ // We apparently can't use placeholders here, sadly.
105+ // nolint: gosec // This is not user input and this is only executed in tests
106+ _ , err = sqlDB .Exec (fmt .Sprintf ("ALTER DATABASE %s SET TIMEZONE TO %q" , dbname , tz ))
107+ require .NoError (t , err , "failed to set timezone for database" )
108+
109+ // Ensure the timezone was set.
110+ // We need to reconnect for this.
111+ _ = sqlDB .Close ()
112+
113+ sqlDB , err = sql .Open ("postgres" , dbURL )
114+ defer func () {
115+ _ = sqlDB .Close ()
116+ }()
117+ require .NoError (t , err , "failed to reconnect to database" )
118+ var dbTz string
119+ err = sqlDB .QueryRow ("SHOW TIMEZONE" ).Scan (& dbTz )
120+ require .NoError (t , err , "failed to get timezone from database" )
121+ require .Equal (t , tz , dbTz , "database timezone was not set correctly" )
122+ }
123+
124+ // dbNameFromConnectionURL returns the database name from the given connection URL.
125+ func dbNameFromConnectionURL (t testing.TB , connectionURL string ) string {
126+ // connectionURL is of the form postgres://user:pass@host:port/dbname
127+ // We want to extract the dbname part.
128+ u , err := url .Parse (connectionURL )
129+ require .NoError (t , err )
130+ return strings .TrimPrefix (u .Path , "/" )
131+ }
0 commit comments