all repos

onasty @ 9de58ade40472a1ac41c3ef90d83a3df291b02f9

a one-time notes service

onasty/e2e/e2e_test.go (view raw)

Smirnov Oleksandr Smirnov Oleksandr
ss2316544@gmail.com
refactor: logger set default interface (#131)..., 12 months ago
1
package e2e_test
2
3
import (
4
	"context"
5
	"fmt"
6
	"net/http"
7
	"testing"
8
	"time"
9
10
	"github.com/gin-gonic/gin"
11
	"github.com/golang-migrate/migrate/v4"
12
	"github.com/golang-migrate/migrate/v4/database/pgx"
13
	"github.com/jackc/pgx/v5/stdlib"
14
	"github.com/olexsmir/onasty/internal/config"
15
	"github.com/olexsmir/onasty/internal/hasher"
16
	"github.com/olexsmir/onasty/internal/jwtutil"
17
	"github.com/olexsmir/onasty/internal/logger"
18
	"github.com/olexsmir/onasty/internal/service/notesrv"
19
	"github.com/olexsmir/onasty/internal/service/usersrv"
20
	"github.com/olexsmir/onasty/internal/store/psql/noterepo"
21
	"github.com/olexsmir/onasty/internal/store/psql/passwordtokrepo"
22
	"github.com/olexsmir/onasty/internal/store/psql/sessionrepo"
23
	"github.com/olexsmir/onasty/internal/store/psql/userepo"
24
	"github.com/olexsmir/onasty/internal/store/psql/vertokrepo"
25
	"github.com/olexsmir/onasty/internal/store/psqlutil"
26
	"github.com/olexsmir/onasty/internal/store/rdb"
27
	"github.com/olexsmir/onasty/internal/store/rdb/notecache"
28
	"github.com/olexsmir/onasty/internal/store/rdb/usercache"
29
	httptransport "github.com/olexsmir/onasty/internal/transport/http"
30
	"github.com/olexsmir/onasty/internal/transport/http/ratelimit"
31
	"github.com/redis/go-redis/v9"
32
	"github.com/stretchr/testify/require"
33
	"github.com/stretchr/testify/suite"
34
	"github.com/testcontainers/testcontainers-go"
35
	tcpostgres "github.com/testcontainers/testcontainers-go/modules/postgres"
36
	tcredis "github.com/testcontainers/testcontainers-go/modules/redis"
37
	"github.com/testcontainers/testcontainers-go/wait"
38
39
	_ "github.com/golang-migrate/migrate/v4/source/file"
40
)
41
42
type (
43
	stopFunc     func()
44
	AppTestSuite struct {
45
		suite.Suite
46
47
		ctx     context.Context
48
		require *require.Assertions
49
50
		postgresDB   *psqlutil.DB
51
		stopPostgres stopFunc
52
53
		redisDB   *rdb.DB
54
		stopRedis stopFunc
55
56
		router       http.Handler
57
		hasher       hasher.Hasher
58
		jwtTokenizer jwtutil.JWTTokenizer
59
	}
60
	errorResponse struct {
61
		Message string `json:"message"`
62
	}
63
)
64
65
func TestAppSuite(t *testing.T) {
66
	if testing.Short() {
67
		t.Skip()
68
	}
69
70
	// gin output is too verbose(and annoying) in tests
71
	gin.SetMode(gin.TestMode)
72
73
	suite.Run(t, new(AppTestSuite))
74
}
75
76
func (e *AppTestSuite) SetupSuite() {
77
	e.ctx = context.Background()
78
	e.require = e.Require()
79
80
	e.postgresDB, e.stopPostgres = e.prepPostgres()
81
	e.redisDB, e.stopRedis = e.prepRedis()
82
83
	e.initDeps()
84
}
85
86
func (e *AppTestSuite) TearDownSuite() {
87
	e.stopPostgres()
88
	e.stopRedis()
89
}
90
91
// initDeps initializes the dependencies for the app
92
// and sets up the router for tests
93
func (e *AppTestSuite) initDeps() {
94
	cfg := e.getConfig()
95
96
	err := logger.SetDefault(cfg.LogLevel, cfg.LogFormat, cfg.LogShowLine)
97
	e.require.NoError(err)
98
99
	e.hasher = hasher.NewSHA256Hasher(cfg.PasswordSalt)
100
	e.jwtTokenizer = jwtutil.NewJWTUtil(cfg.JwtSigningKey, time.Hour)
101
102
	sessionrepo := sessionrepo.New(e.postgresDB)
103
	vertokrepo := vertokrepo.New(e.postgresDB)
104
	pwdtokrepo := passwordtokrepo.NewPasswordResetTokenRepo(e.postgresDB)
105
106
	stubOAuthProvider := newOauthProviderMock()
107
108
	userepo := userepo.New(e.postgresDB)
109
	usercache := usercache.New(e.redisDB, cfg.CacheUsersTTL)
110
	usersrv := usersrv.New(
111
		userepo,
112
		sessionrepo,
113
		vertokrepo,
114
		pwdtokrepo,
115
		e.hasher,
116
		e.jwtTokenizer,
117
		newMailerMockService(),
118
		usercache,
119
		stubOAuthProvider,
120
		stubOAuthProvider,
121
		cfg.JwtRefreshTokenTTL,
122
		cfg.VerificationTokenTTL,
123
		cfg.ResetPasswordTokenTTL,
124
	)
125
126
	notecache := notecache.New(e.redisDB, cfg.CacheUsersTTL)
127
	noterepo := noterepo.New(e.postgresDB)
128
	notesrv := notesrv.New(noterepo, e.hasher, notecache)
129
130
	// for testing purposes, it's ok to have high values ig
131
	ratelimitCfg := ratelimit.Config{
132
		RPS:   1000,
133
		TTL:   time.Millisecond,
134
		Burst: 1000,
135
	}
136
137
	handler := httptransport.NewTransport(
138
		usersrv,
139
		notesrv,
140
		cfg.AppEnv,
141
		cfg.AppURL,
142
		cfg.CORSAllowedOrigins,
143
		cfg.CORSMaxAge,
144
		ratelimitCfg,
145
	)
146
	e.router = handler.Handler()
147
}
148
149
func (e *AppTestSuite) prepPostgres() (*psqlutil.DB, stopFunc) {
150
	dbCredential := "testing"
151
	postgresContainer, err := tcpostgres.Run(e.ctx,
152
		"postgres:16-alpine",
153
		tcpostgres.WithUsername(dbCredential),
154
		tcpostgres.WithPassword(dbCredential),
155
		tcpostgres.WithDatabase(dbCredential),
156
		testcontainers.WithWaitStrategy(wait.ForListeningPort("5432/tcp")))
157
	e.require.NoError(err)
158
159
	stop := func() { e.require.NoError(postgresContainer.Terminate(e.ctx)) }
160
161
	// connect to the db
162
	host, err := postgresContainer.Host(e.ctx)
163
	e.require.NoError(err)
164
165
	port, err := postgresContainer.MappedPort(e.ctx, "5432/tcp")
166
	e.require.NoError(err)
167
168
	db, err := psqlutil.Connect(e.ctx, fmt.Sprintf( //nolint:nosprintfhostport
169
		"postgres://%s:%s@%s:%s/%s",
170
		dbCredential,
171
		dbCredential,
172
		host,
173
		port.Port(),
174
		dbCredential,
175
	))
176
	e.require.NoError(err)
177
178
	// run migrations
179
	sdb := stdlib.OpenDBFromPool(db.Pool)
180
	driver, err := pgx.WithInstance(sdb, &pgx.Config{}) //nolint:exhaustruct
181
	e.require.NoError(err)
182
183
	m, err := migrate.NewWithDatabaseInstance(
184
		"file://../migrations/",
185
		"pgxv5", driver,
186
	)
187
	e.require.NoError(err)
188
189
	e.require.NoError(m.Up())
190
	e.require.NoError(driver.Close())
191
192
	return db, stop
193
}
194
195
func (e *AppTestSuite) prepRedis() (*rdb.DB, stopFunc) {
196
	redisContainer, err := tcredis.Run(e.ctx, "redis:7.4-alpine")
197
	e.require.NoError(err)
198
199
	stop := func() { e.require.NoError(redisContainer.Terminate(e.ctx)) }
200
201
	uri, err := redisContainer.ConnectionString(e.ctx)
202
	e.require.NoError(err)
203
204
	connOpts, err := redis.ParseURL(uri)
205
	e.require.NoError(err)
206
207
	redis, err := rdb.Connect(e.ctx, connOpts.Addr, connOpts.Password, connOpts.DB)
208
	e.require.NoError(err)
209
210
	return redis, stop
211
}
212
213
func (e *AppTestSuite) getConfig() *config.Config {
214
	e.T().Setenv("APP_ENV", "test")
215
	e.T().Setenv("APP_URL", "localhost")
216
	e.T().Setenv("PASSWORD_SALT", "salty-password")
217
	e.T().Setenv("NOTE_PASSWORD_SALT", "salty-noted-password")
218
	e.T().Setenv("JWT_SIGNING_KEY", "jwt-key")
219
	e.T().Setenv("LOG_SHOW_LINE", "true")
220
	e.T().Setenv("LOG_FORMAT", "text")
221
	e.T().Setenv("LOG_LEVEL", "debug")
222
223
	return config.NewConfig()
224
}