-
Notifications
You must be signed in to change notification settings - Fork 0
/
dbtest.go
206 lines (169 loc) · 5.23 KB
/
dbtest.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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// Package dbtest contains supporting code for running tests that hit the DB.
package dbtest
import (
"bufio"
"bytes"
"context"
"crypto/rand"
"crypto/rsa"
"fmt"
"testing"
"time"
dbUser "github.com/fadhilijuma/images/business/core/user/db"
"github.com/fadhilijuma/images/business/data/dbschema"
"github.com/fadhilijuma/images/business/sys/database"
"github.com/fadhilijuma/images/business/web/auth"
"github.com/fadhilijuma/images/foundation/docker"
"github.com/fadhilijuma/images/foundation/keystore"
"github.com/golang-jwt/jwt/v4"
"github.com/jmoiron/sqlx"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// Success and failure markers.
const (
Success = "\u2713"
Failed = "\u2717"
)
// StartDB starts a database instance.
func StartDB() (*docker.Container, error) {
image := "postgres:14-alpine"
port := "5432"
args := []string{"-e", "POSTGRES_PASSWORD=postgres"}
return docker.StartContainer(image, port, args...)
}
// StopDB stops a running database instance.
func StopDB(c *docker.Container) {
docker.StopContainer(c.ID)
}
// NewUnit creates a test database inside a Docker container. It creates the
// required table structure but the database is otherwise empty. It returns
// the database to use as well as a function to call at the end of the test.
func NewUnit(t *testing.T, c *docker.Container, dbName string) (*zap.SugaredLogger, *sqlx.DB, func()) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
dbM, err := database.Open(database.Config{
User: "postgres",
Password: "postgres",
Host: c.Host,
Name: "postgres",
DisableTLS: true,
})
if err != nil {
t.Fatalf("Opening database connection: %v", err)
}
t.Log("Waiting for database to be ready ...")
if err := database.StatusCheck(ctx, dbM); err != nil {
t.Fatalf("status check database: %v", err)
}
t.Log("Database ready")
if _, err := dbM.ExecContext(context.Background(), "CREATE DATABASE "+dbName); err != nil {
t.Fatalf("creating database %s: %v", dbName, err)
}
dbM.Close()
// =========================================================================
db, err := database.Open(database.Config{
User: "postgres",
Password: "postgres",
Host: c.Host,
Name: dbName,
DisableTLS: true,
})
if err != nil {
t.Fatalf("Opening database connection: %v", err)
}
t.Log("Migrate and seed database ...")
if err := dbschema.Migrate(ctx, db); err != nil {
docker.DumpContainerLogs(t, c.ID)
t.Fatalf("Migrating error: %s", err)
}
if err := dbschema.Seed(ctx, db); err != nil {
docker.DumpContainerLogs(t, c.ID)
t.Fatalf("Seeding error: %s", err)
}
t.Log("Ready for testing ...")
var buf bytes.Buffer
encoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
writer := bufio.NewWriter(&buf)
log := zap.New(
zapcore.NewCore(encoder, zapcore.AddSync(writer), zapcore.DebugLevel)).
Sugar()
// teardown is the function that should be invoked when the caller is done
// with the database.
teardown := func() {
t.Helper()
db.Close()
log.Sync()
writer.Flush()
fmt.Println("******************** LOGS ********************")
fmt.Print(buf.String())
fmt.Println("******************** LOGS ********************")
}
return log, db, teardown
}
// Test owns state for running and shutting down tests.
type Test struct {
DB *sqlx.DB
Log *zap.SugaredLogger
Auth *auth.Auth
Teardown func()
t *testing.T
}
// NewIntegration creates a database, seeds it, constructs an authenticator.
func NewIntegration(t *testing.T, c *docker.Container, dbName string) *Test {
log, db, teardown := NewUnit(t, c, dbName)
// Create RSA keys to enable authentication in our service.
keyID := "4754d86b-7a6d-4df5-9c65-224741361492"
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal(err)
}
// Build an authenticator using this private key and id for the key store.
auth, err := auth.New(keyID, keystore.NewMap(map[string]*rsa.PrivateKey{keyID: privateKey}))
if err != nil {
t.Fatal(err)
}
test := Test{
DB: db,
Log: log,
Auth: auth,
t: t,
Teardown: teardown,
}
return &test
}
// Token generates an authenticated token for a user.
func (test *Test) Token(email, pass string) string {
test.t.Log("Generating token for test ...")
store := dbUser.NewStore(test.Log, test.DB)
dbUsr, err := store.QueryByEmail(context.Background(), email)
if err != nil {
return ""
}
claims := auth.Claims{
RegisteredClaims: jwt.RegisteredClaims{
Subject: dbUsr.ID,
Issuer: "service project",
ExpiresAt: jwt.NewNumericDate(time.Now().UTC().Add(time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now().UTC()),
},
Roles: dbUsr.Roles,
}
token, err := test.Auth.GenerateToken(claims)
if err != nil {
test.t.Fatal(err)
}
return token
}
// StringPointer is a helper to get a *string from a string. It is in the tests
// package because we normally don't want to deal with pointers to basic types
// but it's useful in some tests.
func StringPointer(s string) *string {
return &s
}
// IntPointer is a helper to get a *int from a int. It is in the tests package
// because we normally don't want to deal with pointers to basic types but it's
// useful in some tests.
func IntPointer(i int) *int {
return &i
}