Skip to content

Commit

Permalink
Merge pull request #150 from COS301-SE-2024/test/backend/integration-…
Browse files Browse the repository at this point in the history
…test-auth-handlers

chore: Refactor email sending to skip sending emails in test mode
  • Loading branch information
waveyboym authored Jul 6, 2024
2 parents 896d3f0 + 0fc1302 commit 6e48460
Show file tree
Hide file tree
Showing 6 changed files with 490 additions and 368 deletions.
4 changes: 2 additions & 2 deletions occupi-backend/pkg/handlers/auth_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func Login(ctx *gin.Context, appsession *models.AppSession, role string) {
}

if due {
reverifyUsersEmail(ctx, appsession, requestUser.Email)
ReverifyUsersEmail(ctx, appsession, requestUser.Email)
return
}

Expand Down Expand Up @@ -389,7 +389,7 @@ func VerifyOTP(ctx *gin.Context, appsession *models.AppSession) {
}

// handler for reverifying a users email address
func reverifyUsersEmail(ctx *gin.Context, appsession *models.AppSession, email string) {
func ReverifyUsersEmail(ctx *gin.Context, appsession *models.AppSession, email string) {
// generate a random otp for the user and send email
otp, err := utils.GenerateOTP()
if err != nil {
Expand Down
8 changes: 8 additions & 0 deletions occupi-backend/pkg/mail/mail.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import (

// SendMail sends an email using gomail
func SendMail(to string, subject string, body string) error {
if configs.GetGinRunMode() == "test" {
return nil // Do not send emails in test mode
}

from := configs.GetSystemEmail()
password := configs.GetSMTPPassword()
smtpHost := configs.GetSMTPHost()
Expand All @@ -32,6 +36,10 @@ func SendMail(to string, subject string, body string) error {
}

func SendMultipleEmailsConcurrently(emails []string, subject, body string, creator string) []string {
if configs.GetGinRunMode() == "test" {
return []string{} // Do not send emails in test mode
}

// Use a WaitGroup to wait for all goroutines to complete
var wg sync.WaitGroup
var emailErrors []string
Expand Down
286 changes: 286 additions & 0 deletions occupi-backend/tests/auth_handlers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
package tests

import (
//"errors"
"net/http"
"net/http/httptest"
"sync"
"testing"

"github.com/gin-gonic/gin"

"github.com/COS301-SE-2024/occupi/occupi-backend/configs"
"github.com/COS301-SE-2024/occupi/occupi-backend/pkg/authenticator"
"github.com/COS301-SE-2024/occupi/occupi-backend/pkg/constants"
"github.com/COS301-SE-2024/occupi/occupi-backend/pkg/router"
)

func TestInvalidLogoutHandler(t *testing.T) {
// connect to the database
db := configs.ConnectToDatabase(constants.AdminDBAccessOption)

// set gin run mode
gin.SetMode(configs.GetGinRunMode())

// Create a Gin router
ginRouter := gin.Default()

// Register routes
router.OccupiRouter(ginRouter, db)

// Create a request to pass to the handler
req, err := http.NewRequest("POST", "/auth/logout", nil)
if err != nil {
t.Fatal("Error creating request: ", err)
}

// Record the HTTP response
rr := httptest.NewRecorder()

// Serve the request
ginRouter.ServeHTTP(rr, req)

// Check the status code is what we expect
if status := rr.Code; status != http.StatusUnauthorized {
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusUnauthorized)
}
}

func TestValidLogoutHandler(t *testing.T) {
// connect to the database
db := configs.ConnectToDatabase(constants.AdminDBAccessOption)

// set gin run mode
gin.SetMode(configs.GetGinRunMode())

// Create a Gin router
ginRouter := gin.Default()

// Register routes
router.OccupiRouter(ginRouter, db)

// Create a request to pass to the handler
req, err := http.NewRequest("POST", "/auth/logout", nil)
if err != nil {
t.Fatal("Error creating request: ", err)
}

// Set up cookies for the request, "token" and "occupi-sessions-store"
token, _, err := authenticator.GenerateToken("[email protected]", constants.Basic)
if err != nil {
t.Fatal("Error generating token: ", err)
}
cookie1 := http.Cookie{
Name: "token",
Value: token,
}
req.AddCookie(&cookie1)

// Record the HTTP response
rr := httptest.NewRecorder()

// Serve the request
ginRouter.ServeHTTP(rr, req)

// Check the status code is what we expect
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
}

// ensure that protected route cannot be accessed like ping-auth
req, err = http.NewRequest("GET", "/ping-auth", nil)

if err != nil {
t.Fatal("Error creating request: ", err)
}

// record the HTTP response
rr = httptest.NewRecorder()

// serve the request
ginRouter.ServeHTTP(rr, req)

// check the status code is what we expect
if status := rr.Code; status != http.StatusUnauthorized {
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusUnauthorized)
}
}

func TestValidLogoutHandlerFromDomains(t *testing.T) {
// connect to the database
db := configs.ConnectToDatabase(constants.AdminDBAccessOption)

// set gin run mode
gin.SetMode(configs.GetGinRunMode())

// Create a Gin router
ginRouter := gin.Default()

// Register routes
router.OccupiRouter(ginRouter, db)

// read domains
domains := configs.GetOccupiDomains()

// use a wait group to handle concurrency
var wg sync.WaitGroup

for _, domain := range domains {
wg.Add(1)

go func(domain string) {
defer wg.Done()

// Create a request to pass to the handler
req, err := http.NewRequest("POST", "/auth/logout", nil)
if err != nil {
t.Errorf("Error creating request: %v", err)
return
}

// set the domain
req.Host = domain

// Set up cookies for the request, "token" and "occupi-sessions-store"
token, _, err := authenticator.GenerateToken("[email protected]", constants.Basic)
if err != nil {
t.Errorf("Error generating token: %s", err)
}
cookie1 := http.Cookie{
Name: "token",
Value: token,
}
req.AddCookie(&cookie1)

// Record the HTTP response
rr := httptest.NewRecorder()

// Serve the request
ginRouter.ServeHTTP(rr, req)

// Check the status code is what we expect
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code for domain %s: got %v want %v", domain, status, http.StatusOK)
}

// ensure that protected route cannot be accessed like ping-auth
req, err = http.NewRequest("GET", "/ping-auth", nil)

if err != nil {
t.Errorf("Error creating request: %s", err)
}

// record the HTTP response
rr = httptest.NewRecorder()

// serve the request
ginRouter.ServeHTTP(rr, req)

// check the status code is what we expect
if status := rr.Code; status != http.StatusUnauthorized {
t.Errorf("handler returned wrong status code: got %v want %v for domain: %s", status, http.StatusUnauthorized, domain)
}
}(domain)
}

// Wait for all goroutines to finish
wg.Wait()
}

/*
// Test reverifyUsersEmail handler
func TestReverifyUsersEmail(t *testing.T) {
// Set Gin to test mode
gin.SetMode(gin.TestMode)
// Create a new HTTP request with the POST method
req, _ := http.NewRequest("POST", "/", nil)
// Create a new ResponseRecorder to record the response
w := httptest.NewRecorder()
// Create a new context with the Request and ResponseWriter
ctx, _ := gin.CreateTestContext(w)
ctx.Request = req
// Set any values in the context
ctx.Set("test", "test")
// Mock AppSession
db := configs.ConnectToDatabase()
appSession := models.New(db)
// Test cases
tests := []struct {
name string
mockOTPGenError error
mockDBError error
mockMailError error
expectedStatus int
expectedBody gin.H
}{
{
name: "OTP generation error",
mockOTPGenError: errors.New("OTP generation failed"),
expectedStatus: http.StatusInternalServerError,
expectedBody: utils.InternalServerError(),
},
{
name: "Database save error",
mockDBError: errors.New("Database save failed"),
expectedStatus: http.StatusInternalServerError,
expectedBody: utils.InternalServerError(),
},
{
name: "Mail send error",
mockMailError: errors.New("Mail send failed"),
expectedStatus: http.StatusInternalServerError,
expectedBody: utils.InternalServerError(),
},
{
name: "Success",
expectedStatus: http.StatusOK,
expectedBody: gin.H{
"code": http.StatusOK,
"message": "Please check your email for the OTP to re-verify your account.",
"data": nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Mock OTP generation
utils.GenerateOTP = func() (string, error) {
if tt.mockOTPGenError != nil {
return "", tt.mockOTPGenError
}
return "123456", nil
}
// Mock database AddOTP method
database.AddOTP = func(ctx *gin.Context, db *mongo.Client, email string, otp string) (interface{}, error) {
if tt.mockDBError != nil {
return nil, tt.mockDBError
}
return nil, nil
}
// Mock mail SendMail method
mail.SendMail = func(email string, subject string, body string) error {
if tt.mockMailError != nil {
return tt.mockMailError
}
return nil
}
// Call the handler
handlers.ReverifyUsersEmail(ctx, appSession, "[email protected]")
// Assert the response
assert.Equal(t, tt.expectedStatus, w.Code)
assert.JSONEq(t, tt.expectedBody, w.Body.String())
})
}
}
*/
47 changes: 45 additions & 2 deletions occupi-backend/tests/database_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package tests

import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
Expand All @@ -9,17 +10,59 @@ import (
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"

//"github.com/stretchr/testify/mock"
"go.mongodb.org/mongo-driver/bson"
//"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/integration/mtest"

"github.com/COS301-SE-2024/occupi/occupi-backend/configs"
"github.com/COS301-SE-2024/occupi/occupi-backend/pkg/authenticator"
"github.com/COS301-SE-2024/occupi/occupi-backend/pkg/constants"
"github.com/COS301-SE-2024/occupi/occupi-backend/pkg/database"
"github.com/COS301-SE-2024/occupi/occupi-backend/pkg/models"
"github.com/COS301-SE-2024/occupi/occupi-backend/pkg/router"
)

func TestMockDatabase(t *testing.T) {
// connect to the database
db := configs.ConnectToDatabase(constants.AdminDBAccessOption)

// set gin run mode
gin.SetMode(configs.GetGinRunMode())

// Create a Gin router
r := gin.Default()

// Register the route
router.OccupiRouter(r, db)

token, _, _ := authenticator.GenerateToken("[email protected]", constants.Basic)

w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/resource-auth", nil)
req.AddCookie(&http.Cookie{Name: "token", Value: token})

r.ServeHTTP(w, req)

assert.Equal(t, http.StatusOK, w.Code)

/*
Expected response body:
{
"data": [], -> array of data
"message": "Successfully fetched resource!", -> message
"status": 200 -> status code
*/
// check that the data length is greater than 0 after converting the response body to a map
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
if err != nil {
t.Errorf("could not unmarshal response: %v", err)
}

// check that the data length is greater than 0
data := response["data"].([]interface{})
assert.Greater(t, len(data), 0)
}

func TestGetAllData(t *testing.T) {
// Setup mock MongoDB instance
mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock))
Expand Down
Loading

0 comments on commit 6e48460

Please sign in to comment.