mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
feat(coderd/database/dbtestutil): set default database timezone to non-UTC in unit tests (#9672)
- Adds dbtestutil.WithTimezone(tz) to allow setting the timezone for a test database. - Modifies our test database setup code to pick a consistently weird timezone for the database. - Adds the facility randtz.Name() to pick a random timezone which is consistent across subtests (via sync.Once). - Adds a linter rule to warn against setting the test database timezone to UTC.
This commit is contained in:
@ -3,7 +3,10 @@ package dbtestutil
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -19,9 +22,27 @@ func WillUsePostgres() bool {
|
||||
return os.Getenv("DB") != ""
|
||||
}
|
||||
|
||||
func NewDB(t testing.TB) (database.Store, pubsub.Pubsub) {
|
||||
type options struct {
|
||||
fixedTimezone string
|
||||
}
|
||||
|
||||
type Option func(*options)
|
||||
|
||||
// WithTimezone sets the database to the defined timezone.
|
||||
func WithTimezone(tz string) Option {
|
||||
return func(o *options) {
|
||||
o.fixedTimezone = tz
|
||||
}
|
||||
}
|
||||
|
||||
func NewDB(t testing.TB, opts ...Option) (database.Store, pubsub.Pubsub) {
|
||||
t.Helper()
|
||||
|
||||
var o options
|
||||
for _, opt := range opts {
|
||||
opt(&o)
|
||||
}
|
||||
|
||||
db := dbfake.New()
|
||||
ps := pubsub.NewInMemory()
|
||||
if WillUsePostgres() {
|
||||
@ -35,6 +56,19 @@ func NewDB(t testing.TB) (database.Store, pubsub.Pubsub) {
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(closePg)
|
||||
}
|
||||
|
||||
if o.fixedTimezone == "" {
|
||||
// To make sure we find timezone-related issues, we set the timezone
|
||||
// of the database to a non-UTC one.
|
||||
// The below was picked due to the following properties:
|
||||
// - It has a non-UTC offset
|
||||
// - It has a fractional hour UTC offset
|
||||
// - It includes a daylight savings time component
|
||||
o.fixedTimezone = "Canada/Newfoundland"
|
||||
}
|
||||
dbName := dbNameFromConnectionURL(t, connectionURL)
|
||||
setDBTimezone(t, connectionURL, dbName, o.fixedTimezone)
|
||||
|
||||
sqlDB, err := sql.Open("postgres", connectionURL)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
@ -51,3 +85,28 @@ func NewDB(t testing.TB) (database.Store, pubsub.Pubsub) {
|
||||
|
||||
return db, ps
|
||||
}
|
||||
|
||||
// setRandDBTimezone sets the timezone of the database to the given timezone.
|
||||
// Note that the updated timezone only comes into effect on reconnect, so we
|
||||
// create our own connection for this and close the DB after we're done.
|
||||
func setDBTimezone(t testing.TB, dbURL, dbname, tz string) {
|
||||
t.Helper()
|
||||
|
||||
sqlDB, err := sql.Open("postgres", dbURL)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = sqlDB.Close()
|
||||
}()
|
||||
|
||||
// nolint: gosec // This unfortunately does not work with placeholders.
|
||||
_, err = sqlDB.Exec(fmt.Sprintf("ALTER DATABASE %s SET TIMEZONE TO %q", dbname, tz))
|
||||
require.NoError(t, err, "failed to set timezone for database")
|
||||
}
|
||||
|
||||
// dbNameFromConnectionURL returns the database name from the given connection URL,
|
||||
// where connectionURL is of the form postgres://user:pass@host:port/dbname
|
||||
func dbNameFromConnectionURL(t testing.TB, connectionURL string) string {
|
||||
u, err := url.Parse(connectionURL)
|
||||
require.NoError(t, err)
|
||||
return strings.TrimPrefix(u.Path, "/")
|
||||
}
|
||||
|
1034
coderd/database/dbtestutil/randtz/randtz.go
Normal file
1034
coderd/database/dbtestutil/randtz/randtz.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -19,10 +19,10 @@ const activityBumpWorkspace = `-- name: ActivityBumpWorkspace :exec
|
||||
WITH latest AS (
|
||||
SELECT
|
||||
workspace_builds.id::uuid AS build_id,
|
||||
workspace_builds.deadline::timestamp AS build_deadline,
|
||||
workspace_builds.max_deadline::timestamp AS build_max_deadline,
|
||||
workspace_builds.deadline::timestamp with time zone AS build_deadline,
|
||||
workspace_builds.max_deadline::timestamp with time zone AS build_max_deadline,
|
||||
workspace_builds.transition AS build_transition,
|
||||
provisioner_jobs.completed_at::timestamp AS job_completed_at,
|
||||
provisioner_jobs.completed_at::timestamp with time zone AS job_completed_at,
|
||||
(workspaces.ttl / 1000 / 1000 / 1000 || ' seconds')::interval AS ttl_interval
|
||||
FROM workspace_builds
|
||||
JOIN provisioner_jobs
|
||||
|
@ -6,10 +6,10 @@
|
||||
WITH latest AS (
|
||||
SELECT
|
||||
workspace_builds.id::uuid AS build_id,
|
||||
workspace_builds.deadline::timestamp AS build_deadline,
|
||||
workspace_builds.max_deadline::timestamp AS build_max_deadline,
|
||||
workspace_builds.deadline::timestamp with time zone AS build_deadline,
|
||||
workspace_builds.max_deadline::timestamp with time zone AS build_max_deadline,
|
||||
workspace_builds.transition AS build_transition,
|
||||
provisioner_jobs.completed_at::timestamp AS job_completed_at,
|
||||
provisioner_jobs.completed_at::timestamp with time zone AS job_completed_at,
|
||||
(workspaces.ttl / 1000 / 1000 / 1000 || ' seconds')::interval AS ttl_interval
|
||||
FROM workspace_builds
|
||||
JOIN provisioner_jobs
|
||||
|
Reference in New Issue
Block a user