all repos

onasty @ 33a27b8

a one-time notes service

onasty/e2e/e2e_test.go (view raw)

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