-
Notifications
You must be signed in to change notification settings - Fork 0
/
test_helper.go
180 lines (160 loc) · 5.66 KB
/
test_helper.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
package dbtest
import (
"context"
"database/sql"
"net/http"
"strings"
"github.com/DIMO-Network/valuations-api/internal/controllers/helpers"
"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt/v5"
_ "embed" //nolint
"fmt"
"os"
"testing"
"time"
"github.com/DIMO-Network/shared/db"
"github.com/DIMO-Network/valuations-api/internal/config"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
"github.com/pressly/goose/v3"
"github.com/rs/zerolog"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)
// StartContainerDatabase starts postgres container with default test settings, and migrates the db. Caller must terminate container.
func StartContainerDatabase(ctx context.Context, dbName string, t *testing.T, migrationsDirRelPath string) (db.Store, testcontainers.Container) {
logger := zerolog.New(os.Stdout).With().Timestamp().Logger()
settings := getTestDbSettings(dbName)
pgPort := "5432/tcp"
dbURL := func(_ string, port nat.Port) string {
return fmt.Sprintf("postgres://%s:%s@localhost:%s/%s?sslmode=disable", settings.DB.User, settings.DB.Password, port.Port(), settings.DB.Name)
}
cr := testcontainers.ContainerRequest{
Image: "postgres:12.9-alpine",
Env: map[string]string{"POSTGRES_USER": settings.DB.User, "POSTGRES_PASSWORD": settings.DB.Password, "POSTGRES_DB": settings.DB.Name},
ExposedPorts: []string{pgPort},
Cmd: []string{"postgres", "-c", "fsync=off"},
WaitingFor: wait.ForSQL(nat.Port(pgPort), "postgres", dbURL),
}
pgContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: cr,
Started: true,
})
if err != nil {
return handleContainerStartErr(ctx, err, pgContainer, t)
}
mappedPort, err := pgContainer.MappedPort(ctx, nat.Port(pgPort))
if err != nil {
return handleContainerStartErr(ctx, errors.Wrap(err, "failed to get container external port"), pgContainer, t)
}
fmt.Printf("postgres container session %s ready and running at port: %s \n", pgContainer.SessionID(), mappedPort)
//defer pgContainer.Terminate(ctx) // this should be done by the caller
settings.DB.Port = mappedPort.Port()
pdb := db.NewDbConnectionForTest(ctx, &settings.DB, false)
pdb.WaitForDB(logger)
_, err = pdb.DBS().Writer.Exec(fmt.Sprintf(`
grant usage on schema public to public;
grant create on schema public to public;
CREATE SCHEMA IF NOT EXISTS %s;
ALTER USER postgres SET search_path = %s, public;
SET search_path = %s, public;
`, dbName, dbName, dbName))
if err != nil {
return handleContainerStartErr(ctx, errors.Wrapf(err, "failed to apply schema. session: %s, port: %s",
pgContainer.SessionID(), mappedPort.Port()), pgContainer, t)
}
logger.Info().Msgf("set default search_path for user postgres to %s", dbName)
// add truncate tables func
_, err = pdb.DBS().Writer.Exec(fmt.Sprintf(`
CREATE OR REPLACE FUNCTION truncate_tables() RETURNS void AS $$
DECLARE
statements CURSOR FOR
SELECT tablename FROM pg_tables
WHERE schemaname = '%s' and tablename != 'migrations';
BEGIN
FOR stmt IN statements LOOP
EXECUTE 'TRUNCATE TABLE ' || quote_ident(stmt.tablename) || ' CASCADE;';
END LOOP;
END;
$$ LANGUAGE plpgsql;
`, dbName))
if err != nil {
return handleContainerStartErr(ctx, errors.Wrap(err, "failed to create truncate func"), pgContainer, t)
}
goose.SetTableName(dbName + ".migrations")
if err := goose.RunContext(ctx, "up", pdb.DBS().Writer.DB, migrationsDirRelPath); err != nil {
return handleContainerStartErr(ctx, errors.Wrap(err, "failed to apply goose migrations for test"), pgContainer, t)
}
return pdb, pgContainer
}
// getTestDbSettings builds test db config.Settings object
func getTestDbSettings(dbName string) config.Settings {
settings := config.Settings{
LogLevel: "info",
DB: db.Settings{
Name: dbName,
Host: "localhost",
Port: "6669",
User: "postgres",
Password: "postgres",
MaxOpenConnections: 2,
MaxIdleConnections: 2,
},
ServiceName: "valuations-api",
}
return settings
}
func handleContainerStartErr(ctx context.Context, err error, container testcontainers.Container, t *testing.T) (db.Store, testcontainers.Container) {
if err != nil {
fmt.Println("start container error: " + err.Error())
if container != nil {
container.Terminate(ctx) //nolint
}
t.Fatal(err)
}
return db.Store{}, container
}
// TruncateTables truncates tables for the test db, useful to run as teardown at end of each DB dependent test.
func TruncateTables(db *sql.DB, t *testing.T) {
_, err := db.Exec(`SELECT truncate_tables();`)
if err != nil {
fmt.Println("truncating tables failed.")
t.Fatal(err)
}
}
func Logger() *zerolog.Logger {
l := zerolog.New(os.Stdout).With().
Timestamp().
Str("app", "valuations-api").
Logger()
return &l
}
// SetupAppFiber sets up app fiber with defaults for testing, like our production error handler.
func SetupAppFiber(logger zerolog.Logger) *fiber.App {
app := fiber.New(fiber.Config{
ErrorHandler: func(c *fiber.Ctx, err error) error {
return helpers.ErrorHandler(c, err, &logger, false)
},
})
return app
}
// AuthInjectorTestHandler injects fake jwt with sub
func AuthInjectorTestHandler(userID string) fiber.Handler {
return func(c *fiber.Ctx) error {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sub": userID,
"nbf": time.Now().Unix(),
})
c.Locals("user", token)
return c.Next()
}
}
func BuildRequest(method, url, body string) *http.Request {
req, _ := http.NewRequest(
method,
url,
strings.NewReader(body),
)
req.Header.Set("Content-Type", "application/json")
return req
}