all repos

onasty @ 73fe76ee8df1ac746de478c5bf3943b232ead3c9

a one-time notes service

onasty/e2e/e2e_test.go (view raw)

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