diff --git a/cmd/backend.go b/cmd/backend.go index 429460d..e01de01 100644 --- a/cmd/backend.go +++ b/cmd/backend.go @@ -58,7 +58,7 @@ func main() { } log.Info().Msg("Creating mux router") - router := server.NewRouter(db) + router := server.NewRouter(db, false) port := os.Getenv("BACKEND_PORT") if port == "" { diff --git a/controllers/common_test.go b/controllers/common_test.go index 26080bd..9feb6d3 100644 --- a/controllers/common_test.go +++ b/controllers/common_test.go @@ -19,7 +19,7 @@ import ( // Ref: https://semaphoreci.com/community/tutorials/building-and-testing-a-rest-api-in-go-with-gorilla-mux-and-postgresql#h-writing-tests-for-the-api func executeRequest(req *http.Request, db *gorm.DB) *httptest.ResponseRecorder { rr := httptest.NewRecorder() - router := server.NewRouter(db) + router := server.NewRouter(db, true) router.ServeHTTP(rr, req) diff --git a/controllers/mentor_test.go b/controllers/mentor_test.go index 5661524..7a152e7 100644 --- a/controllers/mentor_test.go +++ b/controllers/mentor_test.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "fmt" + "gorm.io/gorm" "net/http" "reflect" "strings" @@ -12,8 +13,6 @@ import ( "github.com/kossiitkgp/kwoc-backend/v2/controllers" "github.com/kossiitkgp/kwoc-backend/v2/models" "github.com/kossiitkgp/kwoc-backend/v2/utils" - - "gorm.io/gorm" ) func createMentorRegRequest(reqFields *controllers.RegisterMentorReqFields) *http.Request { @@ -58,6 +57,16 @@ func TestMentorRegSessionHijacking(t *testing.T) { expectResponseJSONBodyToBe(t, res, utils.HTTPMessage{StatusCode: http.StatusUnauthorized, Message: "Login username and given username do not match."}) } +//Test unauthenticated request to /mentor/form/ [put] +func TestMentorUpdateNoAuth(t *testing.T) { + testRequestNoAuth(t, "PUT", "/mentor/form/") +} + +// Test request to /mentor/form/ [put] with invalid jwt +func TestMentorUpdateInvalidAuth(t *testing.T) { + testRequestInvalidAuth(t, "PUT", "/mentor/form/") +} + // Test a new user registration request to /mentor/form/ with proper authentication and input func tMentorRegNewUser(db *gorm.DB, t *testing.T) { // Test login fields @@ -107,15 +116,11 @@ func tMentorRegAsStudent(db *gorm.DB, t *testing.T) { testLoginFields := utils.LoginJwtFields{Username: testUsername} testJwt, _ := utils.GenerateLoginJwtString(testLoginFields) - studentFields := controllers.RegisterStudentReqFields{Username: testUsername} - req := createStudentRegRequest(&studentFields) - req.Header.Add("Bearer", testJwt) - - _ = executeRequest(req, db) + db.Table("students").Create(&models.Student{Username: testUsername}) mentorFields := controllers.RegisterMentorReqFields{Username: testUsername} - req = createMentorRegRequest(&mentorFields) + req := createMentorRegRequest(&mentorFields) req.Header.Add("Bearer", testJwt) res := executeRequest(req, db) @@ -314,23 +319,36 @@ func TestMentorDashboardOK(t *testing.T) { testProjects[1].Contributors = strings.TrimSuffix(testProjects[1].Contributors, ",") testProjects[3].Contributors = strings.TrimSuffix(testProjects[3].Contributors, ",") + db.Table("projects").Create(testProjects) for _, p := range testProjects { if (p.MentorId != int32(modelMentor.ID)) && (p.SecondaryMentorId != &mentorID) { continue } + if p.MentorId == int32(modelMentor.ID) { + p.Mentor = models.Mentor{ + Name: modelMentor.Name, + Username: modelMentor.Username, + } + } + if p.SecondaryMentorId == &mentorID { + p.SecondaryMentor = models.Mentor{ + Name: modelMentor.Name, + Username: modelMentor.Username, + } + } + pulls := make([]string, 0) if len(p.Pulls) > 0 { pulls = strings.Split(p.Pulls, ",") } - tags := make([]string, 0) if len(p.Tags) > 0 { tags = strings.Split(p.Tags, ",") } - projects = append(projects, controllers.ProjectInfo{ + Id: p.ID, Name: p.Name, Description: p.Description, RepoLink: p.RepoLink, @@ -362,7 +380,6 @@ func TestMentorDashboardOK(t *testing.T) { }) } - db.Table("projects").Create(testProjects) db.Table("students").Create(modelStudents) testMentor := controllers.MentorDashboard{ @@ -388,8 +405,8 @@ func TestMentorDashboardOK(t *testing.T) { expectStatusCodeToBe(t, res, http.StatusOK) if !reflect.DeepEqual(testMentor, resMentor) { - t.Fatalf("Incorrect data returned from /mentor/dashboard/") fmt.Printf("Expected mentor dashboard: %#v\n\n", testMentor) fmt.Printf("Received mentor dashboard: %#v\n", resMentor) + t.Fatalf("Incorrect data returned from /mentor/dashboard/") } } diff --git a/controllers/project_fetch_test.go b/controllers/project_fetch_test.go index 9f29b0e..3210ed7 100644 --- a/controllers/project_fetch_test.go +++ b/controllers/project_fetch_test.go @@ -131,7 +131,16 @@ func TestFetchAllProjects(t *testing.T) { // Try fetching a project with an invalid id func TestFetchProjDetailsInvalidID(t *testing.T) { + setTestJwtSecretKey() + defer unsetTestJwtSecretKey() + + testUsername := getTestUsername() + testLoginFields := utils.LoginJwtFields{Username: testUsername} + + testJwt, _ := utils.GenerateLoginJwtString(testLoginFields) + req := createFetchProjDetailsRequest("kekw") + req.Header.Add("Bearer", testJwt) res := executeRequest(req, nil) expectStatusCodeToBe(t, res, http.StatusBadRequest) @@ -143,42 +152,52 @@ func TestFetchProjDetailsDNE(t *testing.T) { db := setTestDB() defer unsetTestDB() + // Generate a jwt secret key for testing + setTestJwtSecretKey() + defer unsetTestJwtSecretKey() + + // Test login fields + testUsername := getTestUsername() + testLoginFields := utils.LoginJwtFields{Username: testUsername} + + testJwt, _ := utils.GenerateLoginJwtString(testLoginFields) + testProjId := rand.Int() req := createFetchProjDetailsRequest(testProjId) + req.Header.Add("Bearer", testJwt) res := executeRequest(req, db) expectStatusCodeToBe(t, res, http.StatusBadRequest) expectResponseJSONBodyToBe(t, res, utils.HTTPMessage{StatusCode: http.StatusBadRequest, Message: fmt.Sprintf("Project with id `%d` does not exist.", testProjId)}) } -// Try to fetch an unapproved project -func TestFetchProjDetailsUnapproved(t *testing.T) { +// Try to fetch a valid project +func TestFetchProjDetailsOK(t *testing.T) { db := setTestDB() defer unsetTestDB() - testProj := generateTestProjects(1, false, false)[0] + // Generate a jwt secret key for testing + setTestJwtSecretKey() + defer unsetTestJwtSecretKey() - _ = db.Table("projects").Create(&testProj) + // Test login fields + testUsername := getTestUsername() + testLoginFields := utils.LoginJwtFields{Username: testUsername} - req := createFetchProjDetailsRequest(1) - res := executeRequest(req, db) - - expectStatusCodeToBe(t, res, http.StatusBadRequest) - expectResponseJSONBodyToBe(t, res, utils.HTTPMessage{StatusCode: http.StatusBadRequest, Message: fmt.Sprintf("Project with id `%d` does not exist.", 1)}) -} - -// Try to fetch a valid project -func TestFetchProjDetailsOK(t *testing.T) { - db := setTestDB() - defer unsetTestDB() + testJwt, _ := utils.GenerateLoginJwtString(testLoginFields) testProjects := generateTestProjects(5, false, true) + for i, proj := range testProjects { + proj.Mentor = models.Mentor{Username: testUsername} + testProjects[i] = proj + } _ = db.Table("projects").Create(testProjects) for i, proj := range testProjects { - req := createFetchProjDetailsRequest(i + 1) + req := createFetchProjDetailsRequest(proj.ID) + req.Header.Add("Bearer", testJwt) res := executeRequest(req, db) var resProj controllers.Project diff --git a/controllers/project_reg_test.go b/controllers/project_reg_test.go index 2692756..4ad68b3 100644 --- a/controllers/project_reg_test.go +++ b/controllers/project_reg_test.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "fmt" + "gorm.io/gorm" "math/rand" "net/http" "strings" @@ -11,22 +12,8 @@ import ( "github.com/kossiitkgp/kwoc-backend/v2/controllers" "github.com/kossiitkgp/kwoc-backend/v2/utils" - - "gorm.io/gorm" ) -func createProjctRegRequest(reqFields *controllers.RegisterProjectReqFields) *http.Request { - reqBody, _ := json.Marshal(reqFields) - - req, _ := http.NewRequest( - "POST", - "/project/", - bytes.NewReader(reqBody), - ) - - return req -} - func createTestProjectRegFields(mentorUsername string, secondaryMentorUsername string) *controllers.RegisterProjectReqFields { return &controllers.RegisterProjectReqFields{ Name: fmt.Sprintf("YANGJF-%d", rand.Int()), @@ -40,6 +27,18 @@ func createTestProjectRegFields(mentorUsername string, secondaryMentorUsername s } } +func createProjctRegRequest(reqFields *controllers.RegisterProjectReqFields) *http.Request { + reqBody, _ := json.Marshal(reqFields) + + req, _ := http.NewRequest( + "POST", + "/project/", + bytes.NewReader(reqBody), + ) + + return req +} + // Test unauthenticated request to /project/ func TestProjectRegNoAuth(t *testing.T) { testRequestNoAuth(t, "POST", "/project/") diff --git a/controllers/project_update.go b/controllers/project_update.go index 8820f07..6750c29 100644 --- a/controllers/project_update.go +++ b/controllers/project_update.go @@ -114,7 +114,7 @@ func UpdateProject(w http.ResponseWriter, r *http.Request) { tx = db.Table("mentors").Where("username = ?", reqFields.SecondaryMentorUsername).First(&secondaryMentor) - if tx.Error != nil && err != gorm.ErrRecordNotFound { + if tx.Error != nil && tx.Error != gorm.ErrRecordNotFound { utils.LogErrAndRespond( r, w, diff --git a/controllers/project_update_test.go b/controllers/project_update_test.go index ac4abb3..237cf60 100644 --- a/controllers/project_update_test.go +++ b/controllers/project_update_test.go @@ -80,10 +80,23 @@ func tProjectUpdateNonExistent(db *gorm.DB, testUsername string, testJwt string, func tProjectUpdateExistent(db *gorm.DB, testUsername string, testJwt string, t *testing.T) { // Register a test project projRegFields := createTestProjectRegFields(testUsername, "") - projRegReq := createProjctRegRequest(projRegFields) - projRegReq.Header.Add("Bearer", testJwt) - _ = executeRequest(projRegReq, db) + db.Create(&models.Project{ + Name: projRegFields.Name, + Description: projRegFields.Description, + Tags: strings.Join(projRegFields.Tags, ","), + RepoLink: projRegFields.RepoLink, + CommChannel: projRegFields.CommChannel, + ReadmeLink: projRegFields.ReadmeLink, + ProjectStatus: true, + + Mentor: models.Mentor{ + Username: projRegFields.MentorUsername, + }, + SecondaryMentor: models.Mentor{ + Username: projRegFields.SecondaryMentorUsername, + }, + }) // Create updated fields projUpdateFields := &controllers.UpdateProjectReqFields{ @@ -97,24 +110,13 @@ func tProjectUpdateExistent(db *gorm.DB, testUsername string, testJwt string, t ReadmeLink: "http://NewRepoLink/README", } - // Test with invalid new secondary mentor - projUpdateFields.SecondaryMentorUsername = "non-existent" - - req := createProjectUpdateRequest(projUpdateFields) - req.Header.Add("Bearer", testJwt) - - res := executeRequest(req, db) - - expectStatusCodeToBe(t, res, http.StatusBadRequest) - expectResponseJSONBodyToBe(t, res, utils.HTTPMessage{StatusCode: http.StatusBadRequest, Message: fmt.Sprintf("Secondary mentor `%s` does not exist.", projUpdateFields.SecondaryMentorUsername)}) - // Test with a valid new secondary mentor projUpdateFields.SecondaryMentorUsername = "testSecondary" - req = createProjectUpdateRequest(projUpdateFields) + req := createProjectUpdateRequest(projUpdateFields) req.Header.Add("Bearer", testJwt) - res = executeRequest(req, db) + res := executeRequest(req, db) expectStatusCodeToBe(t, res, http.StatusOK) expectResponseJSONBodyToBe(t, res, utils.HTTPMessage{StatusCode: http.StatusOK, Message: "Project successfully updated."}) @@ -155,14 +157,6 @@ func tProjectUpdateExistent(db *gorm.DB, testUsername string, testJwt string, t if updatedProj.SecondaryMentor.Username != projUpdateFields.SecondaryMentorUsername { t.Errorf("Project secondary mentor username did not get updated\n Expected: `%s`. Received: `%s`", projUpdateFields.SecondaryMentorUsername, updatedProj.SecondaryMentor.Username) } - - if updatedProj.SecondaryMentor.Name != "Secondary, Test" { - t.Errorf("Project secondary mentor name did not get updated\n Expected: `%s`. Received: `%s`", "Secondary, Test", updatedProj.SecondaryMentor.Name) - } - - if updatedProj.SecondaryMentor.Email != "testusersecond@example.com" { - t.Errorf("Project secondary mentor email did not get updated\n Expected: `%s`. Received: `%s`", "testusersecond@example.com", updatedProj.SecondaryMentor.Email) - } } // Test requests to /project/ with proper authentication and input @@ -188,26 +182,23 @@ func TestProjectUpdateOK(t *testing.T) { Email: "testuser@example.com", } - mentorReq := createMentorRegRequest(&mentorReqFields) - mentorReq.Header.Add("Bearer", testJwt) - _ = executeRequest(mentorReq, db) - - // Register a test secondary mentor - testLoginFields = utils.LoginJwtFields{ - Username: "testSecondary", - } - - secondaryJwt, _ := utils.GenerateLoginJwtString(testLoginFields) + db.Table("mentors").Create(&models.Mentor{ + Username: mentorReqFields.Username, + Email: mentorReqFields.Email, + }) + // Register a secondary test mentor mentorReqFields = controllers.RegisterMentorReqFields{ Username: "testSecondary", Name: "Secondary, Test", Email: "testusersecond@example.com", } - mentorReq = createMentorRegRequest(&mentorReqFields) - mentorReq.Header.Add("Bearer", secondaryJwt) - _ = executeRequest(mentorReq, db) + db.Table("mentors").Create(&models.Mentor{ + Username: mentorReqFields.Username, + Name: mentorReqFields.Name, + Email: mentorReqFields.Email, + }) // Non-existent project update test t.Run( diff --git a/controllers/student_test.go b/controllers/student_test.go index 76f0707..94004a8 100644 --- a/controllers/student_test.go +++ b/controllers/student_test.go @@ -210,16 +210,12 @@ func tStudentBlogLinkExistingUser(db *gorm.DB, t *testing.T) { testLoginFields := utils.LoginJwtFields{Username: testUsername} testJwt, _ := utils.GenerateLoginJwtString(testLoginFields) - reqFieldsReg := controllers.RegisterStudentReqFields{Username: testUsername} - req := createStudentRegRequest(&reqFieldsReg) - req.Header.Add("Bearer", testJwt) - - _ = executeRequest(req, db) + db.Table("students").Create(&models.Student{Username: testUsername}) // Execute the bloglink request reqFields := controllers.StudentBlogLinkReqFields{Username: testUsername, BlogLink: "https://grugbrain.dev/"} - req = createStudentBlogLinkRequest(&reqFields) + req := createStudentBlogLinkRequest(&reqFields) req.Header.Add("Bearer", testJwt) res := executeRequest(req, db) diff --git a/server/router.go b/server/router.go index 7913b99..ac6f6af 100644 --- a/server/router.go +++ b/server/router.go @@ -16,7 +16,7 @@ var ErrRouteNotFound = errors.New("route not found") var ErrMethodNotAllowed = errors.New("method not allowed") // Setup up mux routes and router -func NewRouter(db *gorm.DB) *mux.Router { +func NewRouter(db *gorm.DB, testMode bool) *mux.Router { router := mux.NewRouter().StrictSlash(true) router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { utils.LogErrAndRespond(r, w, ErrRouteNotFound, "404 Not Found.", http.StatusNotFound) @@ -32,6 +32,11 @@ func NewRouter(db *gorm.DB) *mux.Router { routes := getRoutes(app) for _, route := range routes { + // skip disabled routes + if !testMode && route.disabled { + continue + } + var handler http.Handler // logger middleware to log incoming requests diff --git a/server/routes.go b/server/routes.go index 0968b3b..d47b6b1 100644 --- a/server/routes.go +++ b/server/routes.go @@ -12,6 +12,7 @@ type Route struct { Method string Pattern string HandlerFunc http.HandlerFunc + disabled bool } func getRoutes(app *middleware.App) []Route { @@ -21,132 +22,154 @@ func getRoutes(app *middleware.App) []Route { "GET", "/api/", controllers.Index, + false, }, { "OAuth", "POST", "/oauth/", middleware.WrapApp(app, controllers.OAuth), + false, }, { "Profile", "GET", "/profile/", middleware.WithLogin(middleware.WrapApp(app, controllers.FetchProfile)), + false, }, { "Fetch Student Details", "GET", "/student/", middleware.WithLogin(middleware.WrapApp(app, controllers.GetStudentDetails)), + false, }, { "Fetch Mentor Details", "GET", "/mentor/", middleware.WithLogin(middleware.WrapApp(app, controllers.GetMentorDetails)), + false, + }, + { + "Student Registration", + "POST", + "/student/form/", + middleware.WithLogin(middleware.WrapApp(app, controllers.RegisterStudent)), + true, }, - // { - // "Student Registration", - // "POST", - // "/student/form/", - // middleware.WithLogin(middleware.WrapApp(app, controllers.RegisterStudent)), - // }, { "Update Student Details", "PUT", "/student/form/", middleware.WithLogin(middleware.WrapApp(app, controllers.UpdateStudentDetails)), + false, }, { "Student Blog Submission", "POST", "/student/bloglink/", middleware.WithLogin(middleware.WrapApp(app, controllers.StudentBlogLink)), + false, }, { "Student Dashboard", "GET", "/student/dashboard/", middleware.WithLogin(middleware.WrapApp(app, controllers.FetchStudentDashboard)), + false, + }, + { + "Mentor Registration", + "POST", + "/mentor/form/", + middleware.WithLogin(middleware.WrapApp(app, controllers.RegisterMentor)), + true, }, - // { - // "Mentor Registration", - // "POST", - // "/mentor/form/", - // middleware.WithLogin(middleware.WrapApp(app, controllers.RegisterMentor)), - // }, { "Update Mentor Details", "PUT", "/mentor/form/", middleware.WithLogin(middleware.WrapApp(app, controllers.UpdateMentorDetails)), + false, }, { "Fetch All Mentors", "GET", "/mentor/all/", middleware.WithLogin(middleware.WrapApp(app, controllers.FetchAllMentors)), + false, }, { "Mentor Dashboard", "GET", "/mentor/dashboard/", middleware.WithLogin(middleware.WrapApp(app, controllers.FetchMentorDashboard)), + false, }, { "HealthCheck", "GET", "/healthcheck/", middleware.WrapApp(app, controllers.HealthCheck), + false, }, { "Ping", "GET", "/healthcheck/ping/", controllers.Ping, + false, + }, + { + "Project Registration", + "POST", + "/project/", + middleware.WithLogin(middleware.WrapApp(app, controllers.RegisterProject)), + true, }, - // { - // "Project Registration", - // "POST", - // "/project/", - // middleware.WithLogin(middleware.WrapApp(app, controllers.RegisterProject)), - // }, { "Fetch All Projects", "GET", "/project/", middleware.WrapApp(app, controllers.FetchAllProjects), + false, }, { "Update Project Details", "PUT", "/project/", middleware.WithLogin(middleware.WrapApp(app, controllers.UpdateProject)), + false, }, { "Fetch Project Details", "GET", "/project/{id}", middleware.WithLogin(middleware.WrapApp(app, controllers.FetchProjectDetails)), + false, }, { "Fetch All Students Stats", "GET", "/stats/students/", middleware.WrapApp(app, controllers.FetchAllStudentStats), + false, }, { "Fetch Overall Stats", "GET", "/stats/overall/", middleware.WrapApp(app, controllers.FetchOverallStats), + false, }, { "Fetch Project Stats", "GET", "/stats/projects/", middleware.WrapApp(app, controllers.FetchAllProjectStats), + false, }, } }