all repos

onasty @ 40bb8b9396a7ed0f5c599dcbe7ccec4febc9ed8f

a one-time notes service

onasty/e2e/apiv1_auth_test.go (view raw)

Smirnov Oleksandr Smirnov Oleksandr
ss2316544@gmail.com
feat: /me (#132)..., 12 months ago
1
package e2e_test
2
3
import (
4
	"net/http"
5
	"time"
6
7
	"github.com/gofrs/uuid/v5"
8
	"github.com/olexsmir/onasty/internal/hasher"
9
	"github.com/olexsmir/onasty/internal/models"
10
)
11
12
type apiv1AuthSignUpRequest struct {
13
	Email    string `json:"email"`
14
	Password string `json:"password"`
15
}
16
17
func (e *AppTestSuite) TestAuthV1_SignUP() {
18
	email := e.uuid() + "test@test.com"
19
	password := "password"
20
21
	httpResp := e.httpRequest(
22
		http.MethodPost,
23
		"/api/v1/auth/signup",
24
		e.jsonify(apiv1AuthSignUpRequest{
25
			Email:    email,
26
			Password: password,
27
		}),
28
	)
29
30
	dbUser := e.getUserByEmail(email)
31
	hashedPasswd, err := e.hasher.Hash(password)
32
	e.require.NoError(err)
33
34
	e.Equal(http.StatusCreated, httpResp.Code)
35
	e.Equal(dbUser.Email, email)
36
	e.Equal(dbUser.Password, hashedPasswd)
37
}
38
39
func (e *AppTestSuite) TestAuthV1_SignUP_badrequest() {
40
	tests := []struct {
41
		name     string
42
		email    string
43
		password string
44
	}{
45
		{name: "all fields empty", email: "", password: ""},
46
		{name: "non valid email", email: "email", password: "password"},
47
		{name: "non valid password", email: "test@test.com", password: "12345"},
48
	}
49
	for _, t := range tests {
50
		httpResp := e.httpRequest(
51
			http.MethodPost,
52
			"/api/v1/auth/signup",
53
			e.jsonify(apiv1AuthSignUpRequest{
54
				Email:    t.email,
55
				Password: t.password,
56
			}),
57
		)
58
59
		e.Equal(http.StatusBadRequest, httpResp.Code)
60
	}
61
}
62
63
type (
64
	apiv1AuthSignInRequest struct {
65
		Email    string `json:"email"`
66
		Password string `json:"password"`
67
	}
68
	apiv1AuthSignInResponse struct {
69
		AccessToken  string `json:"access_token"`
70
		RefreshToken string `json:"refresh_token"`
71
	}
72
)
73
74
func (e *AppTestSuite) TestAuthV1_VerifyEmail() {
75
	email := e.uuid() + "email@email.com"
76
	password := "qwerty"
77
78
	httpResp := e.httpRequest(
79
		http.MethodPost,
80
		"/api/v1/auth/signup",
81
		e.jsonify(apiv1AuthSignUpRequest{
82
			Email:    email,
83
			Password: password,
84
		}),
85
	)
86
87
	e.Equal(http.StatusCreated, httpResp.Code)
88
89
	user := e.getLastUserByEmail(email)
90
	token := e.getVerificationTokenByUserID(user.ID)
91
	e.Equal(token.Token, mockMailStore[email])
92
93
	httpResp = e.httpRequest(http.MethodGet, "/api/v1/auth/verify/"+token.Token, nil)
94
	e.Equal(http.StatusOK, httpResp.Code)
95
96
	user = e.getLastUserByEmail(email)
97
	e.Equal(user.Activated, true)
98
}
99
100
func (e *AppTestSuite) TestAuthV1_ResendVerificationEmail() {
101
	email, password := e.uuid()+"email@email.com", e.uuid()
102
103
	// create test user
104
	signUpHTTPResp := e.httpRequest(
105
		http.MethodPost,
106
		"/api/v1/auth/signup",
107
		e.jsonify(apiv1AuthSignUpRequest{
108
			Email:    email,
109
			Password: password,
110
		}),
111
	)
112
113
	e.Equal(http.StatusCreated, signUpHTTPResp.Code)
114
115
	// handle sending of the email
116
	httpResp := e.httpRequest(
117
		http.MethodPost,
118
		"/api/v1/auth/resend-verification-email",
119
		e.jsonify(apiv1AuthSignInRequest{
120
			Email:    email,
121
			Password: password,
122
		}),
123
	)
124
125
	e.Equal(http.StatusOK, httpResp.Code)
126
	e.NotEmpty(mockMailStore[email])
127
}
128
129
func (e *AppTestSuite) TestAuthV1_ResendVerificationEmail_wrong() {
130
	email, password := e.uuid()+"@"+e.uuid()+".com", "password"
131
	e.insertUser(email, password, true)
132
133
	tests := []struct {
134
		name         string
135
		email        string
136
		password     string
137
		expectedCode int
138
	}{
139
		{
140
			name:         "activated account",
141
			email:        email,
142
			password:     password,
143
			expectedCode: http.StatusBadRequest,
144
		},
145
		{
146
			name:         "wrong credentials",
147
			email:        email,
148
			password:     e.uuid(),
149
			expectedCode: http.StatusUnauthorized,
150
		},
151
	}
152
153
	for _, t := range tests {
154
		httpResp := e.httpRequest(
155
			http.MethodPost,
156
			"/api/v1/auth/resend-verification-email",
157
			e.jsonify(apiv1AuthSignInRequest{
158
				Email:    t.email,
159
				Password: t.password,
160
			}))
161
162
		e.Equal(httpResp.Code, t.expectedCode)
163
		e.Empty(mockMailStore[t.email])
164
	}
165
}
166
167
func (e *AppTestSuite) TestAuthV1_SignIn() {
168
	email := e.uuid() + "email@email.com"
169
	password := "qwerty"
170
	uid := e.insertUser(email, password, true)
171
172
	httpResp := e.httpRequest(
173
		http.MethodPost,
174
		"/api/v1/auth/signin",
175
		e.jsonify(apiv1AuthSignInRequest{
176
			Email:    email,
177
			Password: password,
178
		}),
179
	)
180
181
	var body apiv1AuthSignInResponse
182
	e.readBodyAndUnjsonify(httpResp.Body, &body)
183
184
	session := e.getLastSessionByUserID(uid)
185
	parsedToken := e.parseJwtToken(body.AccessToken)
186
187
	e.Equal(http.StatusOK, httpResp.Code)
188
	e.Equal(body.RefreshToken, session.RefreshToken)
189
	e.Equal(parsedToken.UserID, uid.String())
190
}
191
192
func (e *AppTestSuite) TestAuthV1_SignIn_wrong() {
193
	password := "password"
194
	email := e.uuid() + "@test.com"
195
	e.insertUser(email, "password", true)
196
197
	unactivatedEmail := e.uuid() + "@test.com"
198
	e.insertUser(unactivatedEmail, password, false)
199
200
	//exhaustruct:ignore
201
	tests := []struct {
202
		name         string
203
		email        string
204
		password     string
205
		expectedCode int
206
207
		expectMsg   bool
208
		expectedMsg string
209
	}{
210
		{
211
			name:         "inactivated user",
212
			email:        unactivatedEmail,
213
			password:     password,
214
			expectedCode: http.StatusBadRequest,
215
			expectMsg:    true,
216
			expectedMsg:  models.ErrUserIsNotActivated.Error(),
217
		},
218
		{
219
			name:         "wrong email",
220
			email:        "wrong@email.com",
221
			password:     password,
222
			expectedCode: http.StatusBadRequest,
223
		},
224
		{
225
			name:         "wrong password",
226
			email:        email,
227
			password:     "wrong-wrong",
228
			expectedCode: http.StatusUnauthorized,
229
		},
230
	}
231
232
	for _, t := range tests {
233
		httpResp := e.httpRequest(
234
			http.MethodPost,
235
			"/api/v1/auth/signin",
236
			e.jsonify(apiv1AuthSignInRequest{
237
				Email:    t.email,
238
				Password: t.password,
239
			}),
240
		)
241
242
		if t.expectMsg {
243
			var body errorResponse
244
			e.readBodyAndUnjsonify(httpResp.Body, &body)
245
246
			e.Equal(body.Message, t.expectedMsg)
247
		}
248
249
		e.Equal(t.expectedCode, httpResp.Code)
250
	}
251
}
252
253
type apiv1AuthRefreshTokensRequest struct {
254
	RefreshToken string `json:"refresh_token"`
255
}
256
257
func (e *AppTestSuite) TestAuthV1_RefreshTokens() {
258
	uid, toks := e.createAndSingIn(e.uuid()+"@test.com", "password")
259
	httpResp := e.httpRequest(
260
		http.MethodPost,
261
		"/api/v1/auth/refresh-tokens",
262
		e.jsonify(apiv1AuthRefreshTokensRequest{
263
			RefreshToken: toks.RefreshToken,
264
		}),
265
	)
266
267
	var body apiv1AuthSignInResponse
268
	e.readBodyAndUnjsonify(httpResp.Body, &body)
269
270
	sessionDB := e.getLastSessionByUserID(uid)
271
	e.Equal(e.parseJwtToken(body.AccessToken).UserID, uid.String())
272
273
	e.Equal(httpResp.Code, http.StatusOK)
274
	e.NotEqual(toks.RefreshToken, body.RefreshToken)
275
	e.Equal(body.RefreshToken, sessionDB.RefreshToken)
276
}
277
278
func (e *AppTestSuite) TestAuthV1_RefreshTokens_wrong() {
279
	httpResp := e.httpRequest(
280
		http.MethodPost,
281
		"/api/v1/auth/refresh-tokens",
282
		e.jsonify(apiv1AuthRefreshTokensRequest{
283
			RefreshToken: e.uuid(),
284
		}),
285
	)
286
287
	e.Equal(httpResp.Code, http.StatusBadRequest)
288
}
289
290
type apiV1AuthLogoutRequest struct {
291
	RefreshToken string `json:"refresh_token"`
292
}
293
294
func (e *AppTestSuite) TestAuthV1_Logout() {
295
	uid, toks := e.createAndSingIn(e.uuid()+"@test.com", "password")
296
297
	sessionDB := e.getLastSessionByUserID(uid)
298
	e.NotEmpty(sessionDB.RefreshToken)
299
300
	httpResp := e.httpRequest(
301
		http.MethodPost,
302
		"/api/v1/auth/logout",
303
		e.jsonify(apiV1AuthLogoutRequest{
304
			RefreshToken: toks.RefreshToken,
305
		}),
306
		toks.AccessToken,
307
	)
308
	e.Equal(httpResp.Code, http.StatusNoContent)
309
310
	sessionDB = e.getLastSessionByUserID(uid)
311
	e.Empty(sessionDB.RefreshToken)
312
}
313
314
func (e *AppTestSuite) TestAuthV1_LogoutAll() {
315
	uid, toks := e.createAndSingIn(e.uuid()+"@test.com", "password")
316
317
	var res int
318
	query := "select count(*) from sessions where user_id = $1"
319
320
	err := e.postgresDB.QueryRow(e.ctx, query, uid).Scan(&res)
321
	e.require.NoError(err)
322
	e.NotZero(res)
323
324
	httpResp := e.httpRequest(http.MethodPost, "/api/v1/auth/logout/all", nil, toks.AccessToken)
325
	e.Equal(httpResp.Code, http.StatusNoContent)
326
327
	err = e.postgresDB.QueryRow(e.ctx, query, uid).Scan(&res)
328
	e.require.NoError(err)
329
	e.Zero(res)
330
}
331
332
type apiv1AuthChangePasswordRequest struct {
333
	CurrentPassword string `json:"current_password"`
334
	NewPassword     string `json:"new_password"`
335
}
336
337
func (e *AppTestSuite) TestAuthV1_ChangePassword() {
338
	password := e.uuid()
339
	newPassword := e.uuid()
340
	email := e.uuid() + "@test.com"
341
	_, toks := e.createAndSingIn(email, password)
342
343
	httpResp := e.httpRequest(
344
		http.MethodPost,
345
		"/api/v1/auth/change-password",
346
		e.jsonify(apiv1AuthChangePasswordRequest{
347
			CurrentPassword: password,
348
			NewPassword:     newPassword,
349
		}),
350
		toks.AccessToken,
351
	)
352
353
	e.Equal(httpResp.Code, http.StatusOK)
354
355
	userDB := e.getUserByEmail(email)
356
	e.NoError(e.hasher.Compare(userDB.Password, newPassword))
357
}
358
359
func (e *AppTestSuite) TestAuthV1_ChangePassword_wrongPassword() {
360
	password := e.uuid()
361
	newPassword := e.uuid()
362
	email := e.uuid() + "@test.com"
363
	_, toks := e.createAndSingIn(email, password)
364
365
	httpResp := e.httpRequest(
366
		http.MethodPost,
367
		"/api/v1/auth/change-password",
368
		e.jsonify(apiv1AuthChangePasswordRequest{
369
			CurrentPassword: e.uuid(),
370
			NewPassword:     newPassword,
371
		}),
372
		toks.AccessToken,
373
	)
374
375
	e.Equal(http.StatusBadRequest, httpResp.Code)
376
377
	userDB := e.getUserByEmail(email)
378
379
	err := e.hasher.Compare(userDB.Password, newPassword)
380
	e.ErrorIs(err, hasher.ErrMismatchedHashes)
381
}
382
383
type (
384
	apiV1AuthResetPasswordRequest struct {
385
		Email string `json:"email"`
386
	}
387
	apiV1AuthSetPasswordRequest struct {
388
		Password string `json:"password"`
389
	}
390
)
391
392
func (e *AppTestSuite) TestAuthV1_ResetPassword() {
393
	email := e.uuid() + "@test.com"
394
	uid, _ := e.createAndSingIn(email, "password")
395
396
	httpResp := e.httpRequest(
397
		http.MethodPost,
398
		"/api/v1/auth/reset-password",
399
		e.jsonify(apiV1AuthResetPasswordRequest{
400
			Email: email,
401
		}),
402
	)
403
404
	e.Equal(httpResp.Code, http.StatusOK)
405
406
	token := e.getResetPasswordTokenByUserID(uid)
407
	e.Empty(token.UsedAt)
408
	e.Equal(mockMailStore[email], token.Token)
409
410
	// set new password
411
	password := e.uuid()
412
	httpResp = e.httpRequest(
413
		http.MethodPost,
414
		"/api/v1/auth/reset-password/"+token.Token,
415
		e.jsonify(apiV1AuthSetPasswordRequest{
416
			Password: password,
417
		}),
418
	)
419
420
	dbUser := e.getUserByEmail(email)
421
	e.Equal(httpResp.Code, http.StatusOK)
422
	e.NoError(e.hasher.Compare(dbUser.Password, password))
423
424
	token = e.getResetPasswordTokenByUserID(uid)
425
	e.NotEmpty(token.UsedAt)
426
}
427
428
func (e *AppTestSuite) TestAuthV1_ResetPassword_nonExistentUser() {
429
	_, _ = e.createAndSingIn(e.uuid()+"@test.com", "password")
430
	httpResp := e.httpRequest(
431
		http.MethodPost,
432
		"/api/v1/auth/reset-password",
433
		e.jsonify(apiV1AuthResetPasswordRequest{
434
			Email: e.uuid() + "@testing.com",
435
		}),
436
	)
437
438
	e.Equal(httpResp.Code, http.StatusBadRequest)
439
}
440
441
type getMeResponse struct {
442
	Email     string    `json:"email"`
443
	CreatedAt time.Time `json:"created_at"`
444
}
445
446
func (e *AppTestSuite) TestApiV1_getMe() {
447
	email := e.uuid() + "@test.com"
448
	_, toks := e.createAndSingIn(email, "password")
449
450
	httpResp := e.httpRequest(http.MethodGet, "/api/v1/me", nil, toks.AccessToken)
451
452
	e.Equal(httpResp.Code, http.StatusOK)
453
454
	var body getMeResponse
455
	e.readBodyAndUnjsonify(httpResp.Body, &body)
456
457
	e.Equal(email, body.Email)
458
	e.NotZero(body.CreatedAt)
459
}
460
461
// createAndSingIn creates an activated user, logs them in,
462
// and returns their userID along with access and refresh tokens.
463
func (e *AppTestSuite) createAndSingIn(
464
	email, password string,
465
) (uuid.UUID, apiv1AuthSignInResponse) {
466
	uid := e.insertUser(email, password, true)
467
	httpResp := e.httpRequest(
468
		http.MethodPost,
469
		"/api/v1/auth/signin",
470
		e.jsonify(apiv1AuthSignInRequest{
471
			Email:    email,
472
			Password: password,
473
		}),
474
	)
475
476
	e.Equal(httpResp.Code, http.StatusOK)
477
478
	var body apiv1AuthSignInResponse
479
	e.readBodyAndUnjsonify(httpResp.Body, &body)
480
481
	return uid, body
482
}