all repos

onasty @ 7cca7d4

a one-time notes service

onasty/e2e/e2e_test.go (view raw)

Smirnov Oleksandr Smirnov Oleksandr
ss2316544@gmail.com
feat: add cors (#115)..., 1 year ago
1
package e2e_test
2
3
import (
4
	"context"
5
	"fmt"
6
	"log/slog"
7
	"net/http"
8
	"testing"
9
	"time"
10
11
	"github.com/gin-gonic/gin"
12
	"github.com/golang-migrate/migrate/v4"
13
	"github.com/golang-migrate/migrate/v4/database/pgx"
14
	"github.com/jackc/pgx/v5/stdlib"
15
	"github.com/olexsmir/onasty/internal/config"
16
	"github.com/olexsmir/onasty/internal/hasher"
17
	"github.com/olexsmir/onasty/internal/jwtutil"
18
	"github.com/olexsmir/onasty/internal/logger"
19
	"github.com/olexsmir/onasty/internal/service/notesrv"
20
	"github.com/olexsmir/onasty/internal/service/usersrv"
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
	logger, err := logger.NewCustomLogger(cfg.LogLevel, cfg.LogFormat, cfg.LogShowLine)
98
	e.require.NoError(err)
99
100
	slog.SetDefault(logger)
101
102
	e.hasher = hasher.NewSHA256Hasher(cfg.PasswordSalt)
103
	e.jwtTokenizer = jwtutil.NewJWTUtil(cfg.JwtSigningKey, time.Hour)
104
105
	sessionrepo := sessionrepo.New(e.postgresDB)
106
	vertokrepo := vertokrepo.New(e.postgresDB)
107
	pwdtokrepo := passwordtokrepo.NewPasswordResetTokenRepo(e.postgresDB)
108
109
	oauthProvider := newOauthProviderMock()
110
111
	userepo := userepo.New(e.postgresDB)
112
	usercache := usercache.New(e.redisDB, cfg.CacheUsersTTL)
113
	usersrv := usersrv.New(
114
		userepo,
115
		sessionrepo,
116
		vertokrepo,
117
		pwdtokrepo,
118
		e.hasher,
119
		e.jwtTokenizer,
120
		newMailerMockService(),
121
		usercache,
122
		oauthProvider,
123
		oauthProvider,
124
		cfg.JwtRefreshTokenTTL,
125
		cfg.VerificationTokenTTL,
126
		cfg.ResetPasswordTokenTTL,
127
	)
128
129
	notecache := notecache.New(e.redisDB, cfg.CacheUsersTTL)
130
	noterepo := noterepo.New(e.postgresDB)
131
	notesrv := notesrv.New(noterepo, e.hasher, notecache)
132
133
	// for testing purposes, it's ok to have high values ig
134
	ratelimitCfg := ratelimit.Config{
135
		RPS:   1000,
136
		TTL:   time.Millisecond,
137
		Burst: 1000,
138
	}
139
140
	handler := httptransport.NewTransport(
141
		usersrv,
142
		notesrv,
143
		cfg.CORSAllowedOrigins,
144
		cfg.CORSMaxAge,
145
		ratelimitCfg,
146
	)
147
	e.router = handler.Handler()
148
}
149
150
func (e *AppTestSuite) prepPostgres() (*psqlutil.DB, stopFunc) {
151
	dbCredential := "testing"
152
	postgresContainer, err := tcpostgres.Run(e.ctx,
153
		"postgres:16-alpine",
154
		tcpostgres.WithUsername(dbCredential),
155
		tcpostgres.WithPassword(dbCredential),
156
		tcpostgres.WithDatabase(dbCredential),
157
		testcontainers.WithWaitStrategy(wait.ForListeningPort("5432/tcp")))
158
	e.require.NoError(err)
159
160
	stop := func() { e.require.NoError(postgresContainer.Terminate(e.ctx)) }
161
162
	// connect to the db
163
	host, err := postgresContainer.Host(e.ctx)
164
	e.require.NoError(err)
165
166
	port, err := postgresContainer.MappedPort(e.ctx, "5432/tcp")
167
	e.require.NoError(err)
168
169
	db, err := psqlutil.Connect(e.ctx, fmt.Sprintf( //nolint:nosprintfhostport
170
		"postgres://%s:%s@%s:%s/%s",
171
		dbCredential,
172
		dbCredential,
173
		host,
174
		port.Port(),
175
		dbCredential,
176
	))
177
	e.require.NoError(err)
178
179
	// run migrations
180
	sdb := stdlib.OpenDBFromPool(db.Pool)
181
	driver, err := pgx.WithInstance(sdb, &pgx.Config{}) //nolint:exhaustruct
182
	e.require.NoError(err)
183
184
	m, err := migrate.NewWithDatabaseInstance(
185
		"file://../migrations/",
186
		"pgxv5", driver,
187
	)
188
	e.require.NoError(err)
189
190
	e.require.NoError(m.Up())
191
	e.require.NoError(driver.Close())
192
193
	return db, stop
194
}
195
196
func (e *AppTestSuite) prepRedis() (*rdb.DB, stopFunc) {
197
	redisContainer, err := tcredis.Run(e.ctx, "redis:7.4-alpine")
198
	e.require.NoError(err)
199
200
	stop := func() { e.require.NoError(redisContainer.Terminate(e.ctx)) }
201
202
	uri, err := redisContainer.ConnectionString(e.ctx)
203
	e.require.NoError(err)
204
205
	connOpts, err := redis.ParseURL(uri)
206
	e.require.NoError(err)
207
208
	redis, err := rdb.Connect(e.ctx, connOpts.Addr, connOpts.Password, connOpts.DB)
209
	e.require.NoError(err)
210
211
	return redis, stop
212
}
213
214
func (e *AppTestSuite) getConfig() *config.Config {
215
	e.T().Setenv("APP_ENV", "test")
216
	e.T().Setenv("APP_URL", "localhost")
217
	e.T().Setenv("PASSWORD_SALT", "salty-password")
218
	e.T().Setenv("NOTE_PASSWORD_SALT", "salty-noted-password")
219
	e.T().Setenv("JWT_SIGNING_KEY", "jwt-key")
220
	e.T().Setenv("LOG_SHOW_LINE", "true")
221
	e.T().Setenv("LOG_FORMAT", "text")
222
	e.T().Setenv("LOG_LEVEL", "debug")
223
224
	return config.NewConfig()
225
}