@@ -3,7 +3,10 @@ package dbtestutil
3
3
import (
4
4
"context"
5
5
"database/sql"
6
+ "fmt"
7
+ "net/url"
6
8
"os"
9
+ "strings"
7
10
"testing"
8
11
9
12
"github.com/stretchr/testify/require"
@@ -19,9 +22,29 @@ func WillUsePostgres() bool {
19
22
return os .Getenv ("DB" ) != ""
20
23
}
21
24
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 ) {
23
41
t .Helper ()
24
42
43
+ var o options
44
+ for _ , opt := range opts {
45
+ opt (& o )
46
+ }
47
+
25
48
db := dbfake .New ()
26
49
ps := pubsub .NewInMemory ()
27
50
if WillUsePostgres () {
@@ -35,6 +58,13 @@ func NewDB(t testing.TB) (database.Store, pubsub.Pubsub) {
35
58
require .NoError (t , err )
36
59
t .Cleanup (closePg )
37
60
}
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
+
38
68
sqlDB , err := sql .Open ("postgres" , connectionURL )
39
69
require .NoError (t , err )
40
70
t .Cleanup (func () {
@@ -51,3 +81,51 @@ func NewDB(t testing.TB) (database.Store, pubsub.Pubsub) {
51
81
52
82
return db , ps
53
83
}
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