From 098ccdd4a959fb3a9f721c9aa4e984fd8a63478b Mon Sep 17 00:00:00 2001 From: Mateo Date: Fri, 24 May 2024 14:26:28 +0200 Subject: [PATCH] add unit tests for board --- server/src/api/boards_test.go | 550 +++++++++++++++++++++++++++++++++- server/src/api/notes_test.go | 38 +-- 2 files changed, 562 insertions(+), 26 deletions(-) diff --git a/server/src/api/boards_test.go b/server/src/api/boards_test.go index 3c747debd7..2cf968f92f 100644 --- a/server/src/api/boards_test.go +++ b/server/src/api/boards_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/go-chi/chi/v5" "github.com/google/uuid" "github.com/stretchr/testify/suite" "net/http" @@ -14,6 +15,7 @@ import ( "scrumlr.io/server/identifiers" "strings" "testing" + "time" ) func (m *BoardMock) Create(ctx context.Context, req dto.CreateBoardRequest) (*dto.Board, error) { @@ -36,6 +38,11 @@ func (m *BoardMock) Get(ctx context.Context, boardID uuid.UUID) (*dto.Board, err return args.Get(0).(*dto.Board), args.Error(1) } +func (m *BoardMock) GetBoards(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error) { + args := m.Called(userID) + return args.Get(0).([]uuid.UUID), args.Error(1) +} + func (m *BoardMock) Update(ctx context.Context, req dto.BoardUpdateRequest) (*dto.Board, error) { args := m.Called(req) return args.Get(0).(*dto.Board), args.Error(1) @@ -213,18 +220,120 @@ func (suite *BoardTestSuite) TestGetBoards() { err error }{ { - name: "Successfully deleted board", - expectedCode: http.StatusNoContent, + name: "Successfully received boards", + expectedCode: http.StatusOK, err: nil, }, { - name: "Failed deleting board", + name: "Failed receiving boards", expectedCode: http.StatusInternalServerError, err: &common.APIError{ - Err: errors.New("failed to delete board"), + Err: errors.New("failed to receive boards"), StatusCode: http.StatusInternalServerError, StatusText: "no", - ErrorText: "Could not delete board", + ErrorText: "Could not receive boards", + }, + }, + } + + for _, tt := range tests { + suite.Run(tt.name, func() { + s := new(Server) + mock := new(BoardMock) + s.boards = mock + + userID, _ := uuid.NewRandom() + + boardName := "Test Name" + boardDescription := "Test Description" + firstBoard := &dto.Board{ + ID: uuid.New(), + Name: &boardName, + Description: &boardDescription, + AccessPolicy: "", + ShowAuthors: true, + ShowNotesOfOtherUsers: true, + ShowNoteReactions: true, + AllowStacking: true, + AllowEditing: true, + TimerStart: nil, + TimerEnd: nil, + SharedNote: uuid.NullUUID{}, + ShowVoting: uuid.NullUUID{}, + Passphrase: nil, + Salt: nil, + } + + boardName = "Test Board" + boardDescription = "Description for second board" + secondBoard := &dto.Board{ + ID: uuid.New(), + Name: &boardName, + Description: &boardDescription, + AccessPolicy: "", + ShowAuthors: true, + ShowNotesOfOtherUsers: true, + ShowNoteReactions: true, + AllowStacking: true, + AllowEditing: true, + TimerStart: nil, + TimerEnd: nil, + SharedNote: uuid.NullUUID{}, + ShowVoting: uuid.NullUUID{}, + Passphrase: nil, + Salt: nil, + } + boardIDs := []uuid.UUID{firstBoard.ID, secondBoard.ID} + + mock.On("GetBoards", userID).Return(boardIDs, tt.err) + if tt.err == nil { + mock.On("BoardOverview", boardIDs, userID).Return([]*dto.BoardOverview{{ + Board: firstBoard, + Columns: 1, + CreatedAt: time.Time{}, + Participants: 3, + }, + { + Board: secondBoard, + Columns: 2, + CreatedAt: time.Time{}, + Participants: 4, + }, + }, tt.err) + } + + req := NewTestRequestBuilder("POST", "/", nil). + AddToContext(identifiers.UserIdentifier, userID) + + rr := httptest.NewRecorder() + + s.getBoards(rr, req.Request()) + + suite.Equal(tt.expectedCode, rr.Result().StatusCode) + mock.AssertExpectations(suite.T()) + }) + } +} + +func (suite *BoardTestSuite) TestGetBoard() { + tests := []struct { + name string + expectedCode int + err error + }{ + { + name: "Successfully received boards", + expectedCode: http.StatusOK, + err: nil, + }, + { + name: "Failed receiving boards", + expectedCode: http.StatusInternalServerError, + err: &common.APIError{ + Err: errors.New("failed to receive boards"), + StatusCode: http.StatusInternalServerError, + StatusText: "no", + ErrorText: "Could not receive boards", }, }, } @@ -237,14 +346,441 @@ func (suite *BoardTestSuite) TestGetBoards() { boardID, _ := uuid.NewRandom() - mock.On("Delete", boardID).Return(tt.err) + boardName := "Test Name" + boardDescription := "Test Description" + board := &dto.Board{ + ID: uuid.New(), + Name: &boardName, + Description: &boardDescription, + AccessPolicy: "", + ShowAuthors: true, + ShowNotesOfOtherUsers: true, + ShowNoteReactions: true, + AllowStacking: true, + AllowEditing: true, + TimerStart: nil, + TimerEnd: nil, + SharedNote: uuid.NullUUID{}, + ShowVoting: uuid.NullUUID{}, + Passphrase: nil, + Salt: nil, + } + + mock.On("Get", boardID).Return(board, tt.err) req := NewTestRequestBuilder("POST", "/", nil). AddToContext(identifiers.BoardIdentifier, boardID) rr := httptest.NewRecorder() - s.deleteBoard(rr, req.Request()) + s.getBoard(rr, req.Request()) + + suite.Equal(tt.expectedCode, rr.Result().StatusCode) + mock.AssertExpectations(suite.T()) + }) + } +} + +func (suite *BoardTestSuite) TestJoinBoard() { + boardName := "Test Name" + boardDescription := "Test Description" + salt := "z9YcpBno6azI2ueA" + passphrase := common.Sha512BySalt("123", salt) + + tests := []struct { + name string + expectedCode int + err error + sessionExists bool + sessionRequestExists bool + board *dto.Board + }{ + { + name: "Successfully join board", + expectedCode: http.StatusSeeOther, + err: nil, + sessionExists: true, + board: &dto.Board{ + ID: uuid.New(), + Name: &boardName, + Description: &boardDescription, + AccessPolicy: types.AccessPolicyPublic, + ShowAuthors: true, + ShowNotesOfOtherUsers: true, + ShowNoteReactions: true, + AllowStacking: true, + AllowEditing: true, + TimerStart: nil, + TimerEnd: nil, + SharedNote: uuid.NullUUID{}, + ShowVoting: uuid.NullUUID{}, + Passphrase: nil, + Salt: nil, + }, + }, + { + name: "Failed joining board", + expectedCode: http.StatusInternalServerError, + err: &common.APIError{ + Err: errors.New("failed to join board"), + StatusCode: http.StatusInternalServerError, + StatusText: "no", + ErrorText: "Could not join board", + }, + sessionExists: true, + board: &dto.Board{ + ID: uuid.New(), + Name: &boardName, + Description: &boardDescription, + AccessPolicy: types.AccessPolicyPublic, + ShowAuthors: true, + ShowNotesOfOtherUsers: true, + ShowNoteReactions: true, + AllowStacking: true, + AllowEditing: true, + TimerStart: nil, + TimerEnd: nil, + SharedNote: uuid.NullUUID{}, + ShowVoting: uuid.NullUUID{}, + Passphrase: nil, + Salt: nil, + }, + }, + { + name: "Successfully joined board without session", + expectedCode: http.StatusCreated, + err: nil, + sessionExists: false, + board: &dto.Board{ + ID: uuid.New(), + Name: &boardName, + Description: &boardDescription, + AccessPolicy: types.AccessPolicyPublic, + ShowAuthors: true, + ShowNotesOfOtherUsers: true, + ShowNoteReactions: true, + AllowStacking: true, + AllowEditing: true, + TimerStart: nil, + TimerEnd: nil, + SharedNote: uuid.NullUUID{}, + ShowVoting: uuid.NullUUID{}, + Passphrase: nil, + Salt: nil, + }, + }, { + name: "Successfully joined board with passphrase", + expectedCode: http.StatusCreated, + err: nil, + sessionExists: false, + board: &dto.Board{ + ID: uuid.New(), + Name: &boardName, + Description: &boardDescription, + AccessPolicy: types.AccessPolicyByPassphrase, + ShowAuthors: true, + ShowNotesOfOtherUsers: true, + ShowNoteReactions: true, + AllowStacking: true, + AllowEditing: true, + TimerStart: nil, + TimerEnd: nil, + SharedNote: uuid.NullUUID{}, + ShowVoting: uuid.NullUUID{}, + Passphrase: &passphrase, + Salt: &salt, + }, + }, + { + name: "Successfully join board by invite with existing session request", + expectedCode: http.StatusSeeOther, + err: nil, + sessionExists: false, + board: &dto.Board{ + ID: uuid.New(), + Name: &boardName, + Description: &boardDescription, + AccessPolicy: types.AccessPolicyByInvite, + ShowAuthors: true, + ShowNotesOfOtherUsers: true, + ShowNoteReactions: true, + AllowStacking: true, + AllowEditing: true, + TimerStart: nil, + TimerEnd: nil, + SharedNote: uuid.NullUUID{}, + ShowVoting: uuid.NullUUID{}, + Passphrase: &passphrase, + Salt: &salt, + }, + sessionRequestExists: true, + }, + { + name: "Successfully join board by invite with existing session request", + expectedCode: http.StatusSeeOther, + err: nil, + sessionExists: false, + board: &dto.Board{ + ID: uuid.New(), + Name: &boardName, + Description: &boardDescription, + AccessPolicy: types.AccessPolicyByInvite, + ShowAuthors: true, + ShowNotesOfOtherUsers: true, + ShowNoteReactions: true, + AllowStacking: true, + AllowEditing: true, + TimerStart: nil, + TimerEnd: nil, + SharedNote: uuid.NullUUID{}, + ShowVoting: uuid.NullUUID{}, + Passphrase: &passphrase, + Salt: &salt, + }, + sessionRequestExists: false, + }, + } + + for _, tt := range tests { + suite.Run(tt.name, func() { + s := new(Server) + mock := new(BoardMock) + sessionMock := new(SessionsMock) + s.boards = mock + s.sessions = sessionMock + + boardID, _ := uuid.NewRandom() + userID, _ := uuid.NewRandom() + sessionMock.On("SessionExists", boardID, userID).Return(tt.sessionExists, nil) + if tt.sessionExists { + sessionMock.On("ParticipantBanned", boardID, userID).Return(false, tt.err) + } else { + //sessionMock.On("SessionExists", boardID, userID).Return(false, nil) + mock.On("Get", boardID).Return(tt.board, tt.err) + } + + if tt.board.AccessPolicy == types.AccessPolicyByInvite { + sessionMock.On("SessionRequestExists", boardID, userID).Return(tt.sessionRequestExists, tt.err) + if !tt.sessionRequestExists { + sessionMock.On("CreateSessionRequest", boardID, userID).Return(new(dto.BoardSessionRequest), tt.err) + } + } else { + if !tt.sessionExists { + sessionMock.On("Create", boardID, userID).Return(new(dto.BoardSession), tt.err) + } + + } + + req := NewTestRequestBuilder("POST", fmt.Sprintf("/%s", boardID), strings.NewReader(`{"passphrase": "123"}`)). + AddToContext(identifiers.UserIdentifier, userID) + rctx := chi.NewRouteContext() + rctx.URLParams.Add("id", boardID.String()) + req.AddToContext(chi.RouteCtxKey, rctx) + + rr := httptest.NewRecorder() + + s.joinBoard(rr, req.Request()) + + suite.Equal(tt.expectedCode, rr.Result().StatusCode) + mock.AssertExpectations(suite.T()) + sessionMock.AssertExpectations(suite.T()) + }) + } +} + +func (suite *BoardTestSuite) TestUpdateBoards() { + tests := []struct { + name string + expectedCode int + err error + }{ + { + name: "Successfully updated boards", + expectedCode: http.StatusOK, + err: nil, + }, + { + name: "Failed updating board", + expectedCode: http.StatusInternalServerError, + err: &common.APIError{ + Err: errors.New("failed to update board"), + StatusCode: http.StatusInternalServerError, + StatusText: "no", + ErrorText: "Could not update board", + }, + }, + } + + for _, tt := range tests { + suite.Run(tt.name, func() { + s := new(Server) + mock := new(BoardMock) + s.boards = mock + + newName := "UpdatedName" + newDescription := "UpdatedDescription" + boardID, _ := uuid.NewRandom() + accessPolicy := types.AccessPolicyPublic + boardReq := dto.BoardUpdateRequest{ + Name: &newName, + Description: &newDescription, + AccessPolicy: &accessPolicy, + ID: boardID, + } + + mock.On("Update", boardReq).Return(new(dto.Board), tt.err) + + req := NewTestRequestBuilder("PUT", fmt.Sprintf("/%s", boardID), strings.NewReader(fmt.Sprintf(`{ + "id": "%s", + "name": "%s", + "description": "%s", + "accessPolicy": "PUBLIC" + }`, boardID, newName, newDescription))). + AddToContext(identifiers.BoardIdentifier, boardID) + + rr := httptest.NewRecorder() + + s.updateBoard(rr, req.Request()) + + suite.Equal(tt.expectedCode, rr.Result().StatusCode) + mock.AssertExpectations(suite.T()) + }) + } +} + +func (suite *BoardTestSuite) TestSetTimer() { + tests := []struct { + name string + expectedCode int + err error + }{ + { + name: "Successfully set timer", + expectedCode: http.StatusOK, + err: nil, + }, + { + name: "Failed set timer", + expectedCode: http.StatusInternalServerError, + err: &common.APIError{ + Err: errors.New("failed to set timer"), + StatusCode: http.StatusInternalServerError, + StatusText: "no", + ErrorText: "Could not set timer", + }, + }, + } + + for _, tt := range tests { + suite.Run(tt.name, func() { + s := new(Server) + mock := new(BoardMock) + s.boards = mock + + boardID, _ := uuid.NewRandom() + + minutes := uint8(4) + + mock.On("SetTimer", boardID, minutes).Return(new(dto.Board), tt.err) + + req := NewTestRequestBuilder("PUT", "/timer", strings.NewReader(fmt.Sprintf(`{"minutes": %d}`, minutes))). + AddToContext(identifiers.BoardIdentifier, boardID) + + rr := httptest.NewRecorder() + + s.setTimer(rr, req.Request()) + + suite.Equal(tt.expectedCode, rr.Result().StatusCode) + mock.AssertExpectations(suite.T()) + }) + } +} + +func (suite *BoardTestSuite) TestDeleteTimer() { + tests := []struct { + name string + expectedCode int + err error + }{ + { + name: "Successfully deleted timer", + expectedCode: http.StatusOK, + err: nil, + }, + { + name: "Failed deleting timer", + expectedCode: http.StatusInternalServerError, + err: &common.APIError{ + Err: errors.New("failed to delete timer"), + StatusCode: http.StatusInternalServerError, + StatusText: "no", + ErrorText: "Could not delete timer", + }, + }, + } + + for _, tt := range tests { + suite.Run(tt.name, func() { + s := new(Server) + mock := new(BoardMock) + s.boards = mock + + boardID, _ := uuid.NewRandom() + + mock.On("DeleteTimer", boardID).Return(new(dto.Board), tt.err) + + req := NewTestRequestBuilder("DEL", "/timer", nil). + AddToContext(identifiers.BoardIdentifier, boardID) + + rr := httptest.NewRecorder() + + s.deleteTimer(rr, req.Request()) + + suite.Equal(tt.expectedCode, rr.Result().StatusCode) + mock.AssertExpectations(suite.T()) + }) + } +} + +func (suite *BoardTestSuite) TestIncrementTimer() { + tests := []struct { + name string + expectedCode int + err error + }{ + { + name: "Successfully increment timer", + expectedCode: http.StatusOK, + err: nil, + }, + { + name: "Failed incrementing timer", + expectedCode: http.StatusInternalServerError, + err: &common.APIError{ + Err: errors.New("failed to increment timer"), + StatusCode: http.StatusInternalServerError, + StatusText: "no", + ErrorText: "Could not increment timer", + }, + }, + } + + for _, tt := range tests { + suite.Run(tt.name, func() { + s := new(Server) + mock := new(BoardMock) + s.boards = mock + + boardID, _ := uuid.NewRandom() + + mock.On("IncrementTimer", boardID).Return(new(dto.Board), tt.err) + + req := NewTestRequestBuilder("POST", "/timer/increment", nil). + AddToContext(identifiers.BoardIdentifier, boardID) + + rr := httptest.NewRecorder() + + s.incrementTimer(rr, req.Request()) suite.Equal(tt.expectedCode, rr.Result().StatusCode) mock.AssertExpectations(suite.T()) diff --git a/server/src/api/notes_test.go b/server/src/api/notes_test.go index ec4cecda5c..ff22c04b2d 100644 --- a/server/src/api/notes_test.go +++ b/server/src/api/notes_test.go @@ -49,78 +49,78 @@ type SessionsMock struct { } func (m *SessionsMock) SessionExists(ctx context.Context, boardID, userID uuid.UUID) (bool, error) { - args := m.Called(ctx, boardID, userID) + args := m.Called(boardID, userID) return args.Bool(0), args.Error(1) } func (m *SessionsMock) ParticipantBanned(ctx context.Context, boardID, userID uuid.UUID) (bool, error) { - args := m.Called(ctx, boardID, userID) + args := m.Called(boardID, userID) return args.Bool(0), args.Error(1) } func (m *SessionsMock) Connect(ctx context.Context, boardID, userID uuid.UUID) error { - args := m.Called(ctx, boardID, userID) + args := m.Called(boardID, userID) return args.Error(0) } func (m *SessionsMock) Create(ctx context.Context, boardID, userID uuid.UUID) (*dto.BoardSession, error) { - args := m.Called(ctx, boardID, userID) + args := m.Called(boardID, userID) return args.Get(0).(*dto.BoardSession), args.Error(1) } // Add other missing methods here func (m *SessionsMock) Get(ctx context.Context, boardID, userID uuid.UUID) (*dto.BoardSession, error) { - args := m.Called(ctx, boardID, userID) + args := m.Called(boardID, userID) return args.Get(0).(*dto.BoardSession), args.Error(1) } func (m *SessionsMock) Update(ctx context.Context, body dto.BoardSessionUpdateRequest) (*dto.BoardSession, error) { - args := m.Called(ctx, body) + args := m.Called(body) return args.Get(0).(*dto.BoardSession), args.Error(1) } func (m *SessionsMock) UpdateAll(ctx context.Context, body dto.BoardSessionsUpdateRequest) ([]*dto.BoardSession, error) { - args := m.Called(ctx, body) + args := m.Called(body) return args.Get(0).([]*dto.BoardSession), args.Error(1) } func (m *SessionsMock) List(ctx context.Context, boardID uuid.UUID, f filter.BoardSessionFilter) ([]*dto.BoardSession, error) { - args := m.Called(ctx, boardID, f) + args := m.Called(boardID, f) return args.Get(0).([]*dto.BoardSession), args.Error(1) } func (m *SessionsMock) Disconnect(ctx context.Context, boardID, userID uuid.UUID) error { - args := m.Called(ctx, boardID, userID) + args := m.Called(boardID, userID) return args.Error(0) } func (m *SessionsMock) GetSessionRequest(ctx context.Context, boardID, userID uuid.UUID) (*dto.BoardSessionRequest, error) { - args := m.Called(ctx, boardID, userID) + args := m.Called(boardID, userID) return args.Get(0).(*dto.BoardSessionRequest), args.Error(1) } func (m *SessionsMock) CreateSessionRequest(ctx context.Context, boardID, userID uuid.UUID) (*dto.BoardSessionRequest, error) { - args := m.Called(ctx, boardID, userID) + args := m.Called(boardID, userID) return args.Get(0).(*dto.BoardSessionRequest), args.Error(1) } func (m *SessionsMock) ListSessionRequest(ctx context.Context, boardID uuid.UUID, statusQuery string) ([]*dto.BoardSessionRequest, error) { - args := m.Called(ctx, boardID, statusQuery) + args := m.Called(boardID, statusQuery) return args.Get(0).([]*dto.BoardSessionRequest), args.Error(1) } func (m *SessionsMock) UpdateSessionRequest(ctx context.Context, body dto.BoardSessionRequestUpdate) (*dto.BoardSessionRequest, error) { - args := m.Called(ctx, body) + args := m.Called(body) return args.Get(0).(*dto.BoardSessionRequest), args.Error(1) } func (m *SessionsMock) ModeratorSessionExists(ctx context.Context, boardID, userID uuid.UUID) (bool, error) { - args := m.Called(ctx, boardID, userID) + args := m.Called(boardID, userID) return args.Bool(0), args.Error(1) } func (m *SessionsMock) SessionRequestExists(ctx context.Context, boardID, userID uuid.UUID) (bool, error) { - args := m.Called(ctx, boardID, userID) + args := m.Called(boardID, userID) return args.Bool(0), args.Error(1) } @@ -298,13 +298,13 @@ func (suite *NotesTestSuite) TestDeleteNote() { }, nil) // Mock the SessionExists method - sessionMock.On("SessionExists", mock.Anything, boardID, userID).Return(true, nil) + sessionMock.On("SessionExists", boardID, userID).Return(true, nil) // Mock the ModeratorSessionExists method - sessionMock.On("ModeratorSessionExists", mock.Anything, boardID, userID).Return(true, nil) + sessionMock.On("ModeratorSessionExists", boardID, userID).Return(true, nil) // Mock the ParticipantBanned method - sessionMock.On("ParticipantBanned", mock.Anything, boardID, userID).Return(false, nil) + sessionMock.On("ParticipantBanned", boardID, userID).Return(false, nil) if tt.isLocked { noteMock.On("Delete", mock.Anything, mock.Anything).Return(nil) @@ -313,7 +313,7 @@ func (suite *NotesTestSuite) TestDeleteNote() { ID: boardID, IsLocked: tt.isLocked, }, tt.err) - noteMock.On("Delete", mock.Anything, mock.Anything).Return(tt.err) + noteMock.On("Delete", mock.Anything).Return(tt.err) } req := NewTestRequestBuilder("DELETE", fmt.Sprintf("/notes/%s", noteID.String()), strings.NewReader(`{"deleteStack": false}`))