Skip to content

Commit

Permalink
Merge pull request #32 from ls1intum/application-server-tests
Browse files Browse the repository at this point in the history
Application server tests
  • Loading branch information
niclasheun authored Dec 24, 2024
2 parents 5cde6d7 + 15b0871 commit 836f783
Show file tree
Hide file tree
Showing 8 changed files with 659 additions and 15 deletions.
3 changes: 0 additions & 3 deletions server/applicationAdministration/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ func setupApplicationRouter(router *gin.RouterGroup, authMiddleware func() gin.H
application.GET("/:coursePhaseID/form", permissionIDMiddleware(keycloak.CourseLecturer, keycloak.CourseEditor), getApplicationForm)
application.PUT("/:coursePhaseID/form", permissionIDMiddleware(keycloak.CourseLecturer), updateApplicationForm)

// Application Endpoints
// application.GET("/:coursePhaseID/applications", permissionIDMiddleware("CourseLecturer", "CourseEditor"), getApplications)

}

func getApplicationForm(c *gin.Context) {
Expand Down
147 changes: 147 additions & 0 deletions server/applicationAdministration/router_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package applicationAdministration

import (
"bytes"
"context"
"encoding/json"
"log"
"net/http"
"net/http/httptest"
"testing"

"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/niclasheun/prompt2.0/applicationAdministration/applicationDTO"
"github.com/niclasheun/prompt2.0/testutils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)

type ApplicationAdminRouterTestSuite struct {
suite.Suite
router *gin.Engine
ctx context.Context
cleanup func()
applicationAdminService ApplicationService
}

func (suite *ApplicationAdminRouterTestSuite) SetupSuite() {
suite.ctx = context.Background()

// Set up PostgreSQL container
testDB, cleanup, err := testutils.SetupTestDB(suite.ctx, "../database_dumps/application_administration.sql")
if err != nil {
log.Fatalf("Failed to set up test database: %v", err)
}

suite.cleanup = cleanup
suite.applicationAdminService = ApplicationService{
queries: *testDB.Queries,
conn: testDB.Conn,
}

ApplicationServiceSingleton = &suite.applicationAdminService
suite.router = gin.Default()
api := suite.router.Group("/api")
setupApplicationRouter(api, func() gin.HandlerFunc {
return testutils.MockAuthMiddleware([]string{"PROMPT_Admin", "iPraktikum-ios24245-Lecturer"})
}, testutils.MockPermissionMiddleware)

}

func (suite *ApplicationAdminRouterTestSuite) TearDownSuite() {
suite.cleanup()
}

func (suite *ApplicationAdminRouterTestSuite) TestGetApplicationFormEndpoint_Success() {
coursePhaseID := "4179d58a-d00d-4fa7-94a5-397bc69fab02"
req := httptest.NewRequest(http.MethodGet, "/api/applications/"+coursePhaseID+"/form", nil)
resp := httptest.NewRecorder()

suite.router.ServeHTTP(resp, req)

assert.Equal(suite.T(), http.StatusOK, resp.Code)
var form applicationDTO.Form
err := json.Unmarshal(resp.Body.Bytes(), &form)
assert.NoError(suite.T(), err)
assert.NotNil(suite.T(), form)
assert.NotEmpty(suite.T(), form.QuestionsText)
assert.NotEmpty(suite.T(), form.QuestionsMultiSelect)
}

func (suite *ApplicationAdminRouterTestSuite) TestUpdateApplicationFormEndpoint_Success() {
coursePhaseID := "4179d58a-d00d-4fa7-94a5-397bc69fab02"
updateForm := applicationDTO.UpdateForm{
DeleteQuestionsText: []uuid.UUID{uuid.MustParse("a6a04042-95d1-4765-8592-caf9560c8c3c")},
DeleteQuestionsMultiSelect: []uuid.UUID{uuid.MustParse("65e25b73-ce47-4536-b651-a1632347d733")},
CreateQuestionsText: []applicationDTO.CreateQuestionText{
{
CoursePhaseID: uuid.MustParse(coursePhaseID),
Title: "New Motivation",
AllowedLength: 300,
},
},
CreateQuestionsMultiSelect: []applicationDTO.CreateQuestionMultiSelect{
{
CoursePhaseID: uuid.MustParse(coursePhaseID),
Title: "New Devices",
MinSelect: 1,
MaxSelect: 5,
Options: []string{"Option1", "Option2"},
},
},
}

jsonBody, err := json.Marshal(updateForm)
assert.NoError(suite.T(), err)

req := httptest.NewRequest(http.MethodPut, "/api/applications/"+coursePhaseID+"/form", bytes.NewReader(jsonBody))
req.Header.Set("Content-Type", "application/json")
resp := httptest.NewRecorder()

suite.router.ServeHTTP(resp, req)

assert.Equal(suite.T(), http.StatusOK, resp.Code)
var responseBody map[string]string
err = json.Unmarshal(resp.Body.Bytes(), &responseBody)
assert.NoError(suite.T(), err)
assert.Equal(suite.T(), "application form updated", responseBody["message"])

// Verify updates via GET
getReq := httptest.NewRequest(http.MethodGet, "/api/applications/"+coursePhaseID+"/form", nil)
getResp := httptest.NewRecorder()
suite.router.ServeHTTP(getResp, getReq)

assert.Equal(suite.T(), http.StatusOK, getResp.Code)
var updatedForm applicationDTO.Form
err = json.Unmarshal(getResp.Body.Bytes(), &updatedForm)
assert.NoError(suite.T(), err)

// Verify QuestionsText
assert.Equal(suite.T(), 2, len(updatedForm.QuestionsText))
for _, question := range updatedForm.QuestionsText {
if question.Title == "New Motivation" {
assert.Equal(suite.T(), 300, question.AllowedLength)
} else if question.Title == "Expierence" {
assert.Equal(suite.T(), 500, question.AllowedLength)
} else {
suite.T().Errorf("Unexpected question title: %s", question.Title)
}
}

// Verify QuestionsMultiSelect
assert.Equal(suite.T(), 2, len(updatedForm.QuestionsMultiSelect))
for _, question := range updatedForm.QuestionsMultiSelect {
if question.Title == "New Devices" {
assert.ElementsMatch(suite.T(), []string{"Option1", "Option2"}, question.Options)
} else if question.Title == "Available Devices" {
assert.ElementsMatch(suite.T(), []string{"iPhone", "iPad", "MacBook", "Vision"}, question.Options)
} else {
suite.T().Errorf("Unexpected question title: %s", question.Title)
}
}
}

func TestApplicationAdminRouterTestSuite(t *testing.T) {
suite.Run(t, new(ApplicationAdminRouterTestSuite))
}
7 changes: 0 additions & 7 deletions server/applicationAdministration/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/jackc/pgx/v5/pgxpool"
"github.com/niclasheun/prompt2.0/applicationAdministration/applicationDTO"
db "github.com/niclasheun/prompt2.0/db/sqlc"
"github.com/sirupsen/logrus"
)

type ApplicationService struct {
Expand Down Expand Up @@ -64,7 +63,6 @@ func UpdateApplicationForm(ctx context.Context, coursePhaseId uuid.UUID, form ap

// Delete all questions to be deleted
for _, questionID := range form.DeleteQuestionsMultiSelect {
logrus.Info("questionID", questionID)
err := ApplicationServiceSingleton.queries.DeleteApplicationQuestionMultiSelect(ctx, questionID)
if err != nil {
return errors.New("could not delete question")
Expand Down Expand Up @@ -127,8 +125,3 @@ func UpdateApplicationForm(ctx context.Context, coursePhaseId uuid.UUID, form ap
return nil

}

// TODO
func GetApplication() {

}
156 changes: 156 additions & 0 deletions server/applicationAdministration/service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package applicationAdministration

import (
"context"
"log"
"testing"

"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/niclasheun/prompt2.0/applicationAdministration/applicationDTO"
"github.com/niclasheun/prompt2.0/testutils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)

type ApplicationAdminServiceTestSuite struct {
suite.Suite
router *gin.Engine
ctx context.Context
cleanup func()
applicationAdminService ApplicationService
}

func (suite *ApplicationAdminServiceTestSuite) SetupSuite() {
suite.ctx = context.Background()

// Set up PostgreSQL container
testDB, cleanup, err := testutils.SetupTestDB(suite.ctx, "../database_dumps/application_administration.sql")
if err != nil {
log.Fatalf("Failed to set up test database: %v", err)
}

suite.cleanup = cleanup
suite.applicationAdminService = ApplicationService{
queries: *testDB.Queries,
conn: testDB.Conn,
}

ApplicationServiceSingleton = &suite.applicationAdminService
suite.router = gin.Default()
}

func (suite *ApplicationAdminServiceTestSuite) TearDownSuite() {
suite.cleanup()
}

func (suite *ApplicationAdminServiceTestSuite) TestGetApplicationForm_Success() {
coursePhaseID := uuid.MustParse("4179d58a-d00d-4fa7-94a5-397bc69fab02")

form, err := GetApplicationForm(suite.ctx, coursePhaseID)
assert.NoError(suite.T(), err)
assert.NotNil(suite.T(), form)
assert.NotEmpty(suite.T(), form.QuestionsText)
assert.NotEmpty(suite.T(), form.QuestionsMultiSelect)

// Verify QuestionsText
assert.Equal(suite.T(), 2, len(form.QuestionsText))
for _, question := range form.QuestionsText {
if question.Title == "Motivation" {
assert.Equal(suite.T(), 500, question.AllowedLength)
} else if question.Title == "Expierence" {
assert.Equal(suite.T(), 500, question.AllowedLength)
} else {
suite.T().Errorf("Unexpected question title: %s", question.Title)
}
}

// Verify QuestionsMultiSelect
assert.Equal(suite.T(), 2, len(form.QuestionsMultiSelect))
for _, question := range form.QuestionsMultiSelect {
if question.Title == "Taken Courses" {
assert.ElementsMatch(suite.T(), []string{"Ferienakademie", "Patterns", "Interactive Learning"}, question.Options)
} else if question.Title == "Available Devices" {
assert.ElementsMatch(suite.T(), []string{"iPhone", "iPad", "MacBook", "Vision"}, question.Options)
} else {
suite.T().Errorf("Unexpected question title: %s", question.Title)
}
}
}

func (suite *ApplicationAdminServiceTestSuite) TestGetApplicationForm_NotApplicationPhase() {
nonApplicationPhaseID := uuid.MustParse("7062236a-e290-487c-be41-29b24e0afc64")

_, err := GetApplicationForm(suite.ctx, nonApplicationPhaseID)
assert.Error(suite.T(), err)
assert.Equal(suite.T(), "course phase is not an application phase", err.Error())
}

func (suite *ApplicationAdminServiceTestSuite) TestUpdateApplicationForm_Success() {
coursePhaseID := uuid.MustParse("4179d58a-d00d-4fa7-94a5-397bc69fab02")
updateForm := applicationDTO.UpdateForm{
DeleteQuestionsText: []uuid.UUID{uuid.MustParse("a6a04042-95d1-4765-8592-caf9560c8c3c")},
DeleteQuestionsMultiSelect: []uuid.UUID{uuid.MustParse("383a9590-fba2-4e6b-a32b-88895d55fb9b")},
CreateQuestionsText: []applicationDTO.CreateQuestionText{
{
CoursePhaseID: coursePhaseID,
Title: "New Motivation",
AllowedLength: 300,
},
},
CreateQuestionsMultiSelect: []applicationDTO.CreateQuestionMultiSelect{
{
CoursePhaseID: coursePhaseID,
Title: "New Devices",
MinSelect: 1,
MaxSelect: 5,
Options: []string{"Option1", "Option2"},
},
},
}

err := UpdateApplicationForm(suite.ctx, coursePhaseID, updateForm)
assert.NoError(suite.T(), err)

// Verify updates
form, err := GetApplicationForm(suite.ctx, coursePhaseID)
assert.NoError(suite.T(), err)
assert.NotNil(suite.T(), form)

// Verify QuestionsText
assert.Equal(suite.T(), 2, len(form.QuestionsText))
for _, question := range form.QuestionsText {
if question.Title == "New Motivation" {
assert.Equal(suite.T(), 300, question.AllowedLength)
} else if question.Title == "Expierence" {
assert.Equal(suite.T(), 500, question.AllowedLength)
} else {
suite.T().Errorf("Unexpected question title: %s", question.Title)
}
}

// Verify QuestionsMultiSelect
assert.Equal(suite.T(), 2, len(form.QuestionsMultiSelect))
for _, question := range form.QuestionsMultiSelect {
if question.Title == "New Devices" {
assert.ElementsMatch(suite.T(), []string{"Option1", "Option2"}, question.Options)
} else if question.Title == "Taken Courses" {
assert.ElementsMatch(suite.T(), []string{"Ferienakademie", "Patterns", "Interactive Learning"}, question.Options)
} else {
suite.T().Errorf("Unexpected question title: %s", question.Title)
}
}
}

func (suite *ApplicationAdminServiceTestSuite) TestUpdateApplicationForm_NotApplicationPhase() {
nonApplicationPhaseID := uuid.MustParse("7062236a-e290-487c-be41-29b24e0afc64")
updateForm := applicationDTO.UpdateForm{}

err := UpdateApplicationForm(suite.ctx, nonApplicationPhaseID, updateForm)
assert.Error(suite.T(), err)
assert.Equal(suite.T(), "course phase is not an application phase", err.Error())
}

func TestApplicationAdminServiceTestSuite(t *testing.T) {
suite.Run(t, new(ApplicationAdminServiceTestSuite))
}
24 changes: 20 additions & 4 deletions server/applicationAdministration/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,27 @@ import (
"github.com/google/uuid"
"github.com/niclasheun/prompt2.0/applicationAdministration/applicationDTO"
db "github.com/niclasheun/prompt2.0/db/sqlc"
"github.com/sirupsen/logrus"
)

func validateUpdateForm(ctx context.Context, coursePhaseID uuid.UUID, updateForm applicationDTO.UpdateForm) error {
ctxWithTimeout, cancel := db.GetTimeoutContext(ctx)
defer cancel()

// Check if course phase is application phase
isApplicationPhase, err := ApplicationServiceSingleton.queries.CheckIfCoursePhaseIsApplicationPhase(ctxWithTimeout, coursePhaseID)
if err != nil {
logrus.Error("could not validate application form: ", err)
return errors.New("could not validate the application form")
}
if !isApplicationPhase {
return errors.New("course phase is not an application phase")
}

// Get all questions for the course phase
applicationQuestionsText, err := ApplicationServiceSingleton.queries.GetApplicationQuestionsTextForCoursePhase(ctxWithTimeout, coursePhaseID)
if err != nil {
logrus.Error("could not validate application form: ", err)
return errors.New("could not validate the application form")
}

Expand All @@ -38,14 +50,14 @@ func validateUpdateForm(ctx context.Context, coursePhaseID uuid.UUID, updateForm
}

// 1. DELETE: Check that all deleted questions are from this course
for _, question := range updateForm.DeleteQuestionsText {
if !textQuestionsMap[question] {
for _, questionID := range updateForm.DeleteQuestionsText {
if !textQuestionsMap[questionID] {
return errors.New("question does not belong to this course")
}
}

for _, question := range updateForm.DeleteQuestionsMultiSelect {
if !multiSelectQuestionsMap[question] {
for _, questionID := range updateForm.DeleteQuestionsMultiSelect {
if !multiSelectQuestionsMap[questionID] {
return errors.New("question does not belong to this course")
}
}
Expand Down Expand Up @@ -135,6 +147,10 @@ func validateQuestionMultiSelect(title string, minSelect, maxSelect int, options
return errors.New("maximum selection must be at least 1")
}

if maxSelect < minSelect {
return errors.New("maximum selection must be greater than or equal to minimum selection")
}

// Ensure options are not empty
if len(options) == 0 {
return errors.New("options cannot be empty")
Expand Down
Loading

0 comments on commit 836f783

Please sign in to comment.