focalboard/server/app/cards_test.go
Doug Lauder 4652a15bab
Card APIs (#3760)
* cards apis wip

* create card API

* validate cards when creating

* create card fixes

* patch card wip

* wip

* unit test for createCard; CardPatch2BlockPatch

* unit test for PatchCard

* more APIs

* unit tests for GetCardByID

* register GetCard API

* Set FOCALBOARD_UNIT_TESTING for integration tests

* integration tests for CreateCard

* more integration tests for CreateCard

* integtration tests for PatchCard

* fix integration tests for PatchCard

* integration tests for GetCard

* GetCards API wip

* fix merge conflict

* GetCards API and unit tests

* fix linter issues

* fix flaky unit test for mySQL

* Update server/api/api.go

Co-authored-by: Miguel de la Cruz <mgdelacroix@gmail.com>

* Update server/api/api.go

Co-authored-by: Miguel de la Cruz <mgdelacroix@gmail.com>

* address review comments

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Co-authored-by: Miguel de la Cruz <mgdelacroix@gmail.com>
2022-09-08 13:01:33 +02:00

266 lines
7.2 KiB
Go

package app
import (
"fmt"
"reflect"
"testing"
"github.com/golang/mock/gomock"
"github.com/mattermost/focalboard/server/model"
"github.com/mattermost/focalboard/server/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCreateCard(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
board := &model.Board{
ID: utils.NewID(utils.IDTypeBoard),
}
userID := utils.NewID(utils.IDTypeUser)
props := makeProps(3)
card := &model.Card{
BoardID: board.ID,
CreatedBy: userID,
ModifiedBy: userID,
Title: "test card",
ContentOrder: []string{utils.NewID(utils.IDTypeBlock), utils.NewID(utils.IDTypeBlock)},
Properties: props,
}
block := model.Card2Block(card)
t.Run("success scenario", func(t *testing.T) {
th.Store.EXPECT().GetBoard(board.ID).Return(board, nil)
th.Store.EXPECT().InsertBlock(gomock.AssignableToTypeOf(reflect.TypeOf(block)), userID).Return(nil)
th.Store.EXPECT().GetMembersForBoard(board.ID).Return([]*model.BoardMember{}, nil)
newCard, err := th.App.CreateCard(card, board.ID, userID, false)
require.NoError(t, err)
require.Equal(t, card.BoardID, newCard.BoardID)
require.Equal(t, card.Title, newCard.Title)
require.Equal(t, card.ContentOrder, newCard.ContentOrder)
require.EqualValues(t, card.Properties, newCard.Properties)
})
t.Run("error scenario", func(t *testing.T) {
th.Store.EXPECT().GetBoard(board.ID).Return(board, nil)
th.Store.EXPECT().InsertBlock(gomock.AssignableToTypeOf(reflect.TypeOf(block)), userID).Return(blockError{"error"})
newCard, err := th.App.CreateCard(card, board.ID, userID, false)
require.Error(t, err, "error")
require.Nil(t, newCard)
})
}
func TestGetCards(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
board := &model.Board{
ID: utils.NewID(utils.IDTypeBoard),
}
const cardCount = 25
// make some cards
blocks := make([]model.Block, 0, cardCount)
for i := 0; i < cardCount; i++ {
card := model.Block{
ID: utils.NewID(utils.IDTypeBlock),
ParentID: board.ID,
Schema: 1,
Type: model.TypeCard,
Title: fmt.Sprintf("card %d", i),
BoardID: board.ID,
}
blocks = append(blocks, card)
}
t.Run("success scenario", func(t *testing.T) {
opts := model.QueryBlocksOptions{
BoardID: board.ID,
BlockType: model.TypeCard,
}
th.Store.EXPECT().GetBlocks(opts).Return(blocks, nil)
cards, err := th.App.GetCardsForBoard(board.ID, 0, 0)
require.NoError(t, err)
assert.Len(t, cards, cardCount)
})
t.Run("error scenario", func(t *testing.T) {
opts := model.QueryBlocksOptions{
BoardID: board.ID,
BlockType: model.TypeCard,
}
th.Store.EXPECT().GetBlocks(opts).Return(nil, blockError{"error"})
cards, err := th.App.GetCardsForBoard(board.ID, 0, 0)
require.Error(t, err)
require.Nil(t, cards)
})
}
func TestPatchCard(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
board := &model.Board{
ID: utils.NewID(utils.IDTypeBoard),
}
userID := utils.NewID(utils.IDTypeUser)
props := makeProps(3)
card := &model.Card{
BoardID: board.ID,
CreatedBy: userID,
ModifiedBy: userID,
Title: "test card for patch",
ContentOrder: []string{utils.NewID(utils.IDTypeBlock), utils.NewID(utils.IDTypeBlock)},
Properties: copyProps(props),
}
newTitle := "patched"
newIcon := "😀"
newContentOrder := reverse(card.ContentOrder)
cardPatch := &model.CardPatch{
Title: &newTitle,
ContentOrder: &newContentOrder,
Icon: &newIcon,
UpdatedProperties: modifyProps(props),
}
t.Run("success scenario", func(t *testing.T) {
expectedPatchedCard := cardPatch.Patch(card)
expectedPatchedBlock := model.Card2Block(expectedPatchedCard)
var blockPatch *model.BlockPatch
th.Store.EXPECT().GetBoard(board.ID).Return(board, nil)
th.Store.EXPECT().PatchBlock(card.ID, gomock.AssignableToTypeOf(reflect.TypeOf(blockPatch)), userID).Return(nil)
th.Store.EXPECT().GetMembersForBoard(board.ID).Return([]*model.BoardMember{}, nil)
th.Store.EXPECT().GetBlock(card.ID).Return(expectedPatchedBlock, nil).AnyTimes()
patchedCard, err := th.App.PatchCard(cardPatch, card.ID, userID, false)
require.NoError(t, err)
require.Equal(t, board.ID, patchedCard.BoardID)
require.Equal(t, newTitle, patchedCard.Title)
require.Equal(t, newIcon, patchedCard.Icon)
require.Equal(t, newContentOrder, patchedCard.ContentOrder)
require.EqualValues(t, expectedPatchedCard.Properties, patchedCard.Properties)
})
t.Run("error scenario", func(t *testing.T) {
var blockPatch *model.BlockPatch
th.Store.EXPECT().GetBoard(board.ID).Return(board, nil)
th.Store.EXPECT().PatchBlock(card.ID, gomock.AssignableToTypeOf(reflect.TypeOf(blockPatch)), userID).Return(blockError{"error"})
patchedCard, err := th.App.PatchCard(cardPatch, card.ID, userID, false)
require.Error(t, err, "error")
require.Nil(t, patchedCard)
})
}
func TestGetCard(t *testing.T) {
th, tearDown := SetupTestHelper(t)
defer tearDown()
boardID := utils.NewID(utils.IDTypeBoard)
userID := utils.NewID(utils.IDTypeUser)
props := makeProps(5)
contentOrder := []string{utils.NewID(utils.IDTypeUser), utils.NewID(utils.IDTypeUser)}
fields := make(map[string]any)
fields["contentOrder"] = contentOrder
fields["properties"] = props
fields["icon"] = "😀"
fields["isTemplate"] = true
block := &model.Block{
ID: utils.NewID(utils.IDTypeBlock),
ParentID: boardID,
Type: model.TypeCard,
Title: "test card",
BoardID: boardID,
Fields: fields,
CreatedBy: userID,
ModifiedBy: userID,
}
t.Run("success scenario", func(t *testing.T) {
th.Store.EXPECT().GetBlock(block.ID).Return(block, nil)
card, err := th.App.GetCardByID(block.ID)
require.NoError(t, err)
require.Equal(t, boardID, card.BoardID)
require.Equal(t, block.Title, card.Title)
require.Equal(t, "😀", card.Icon)
require.Equal(t, true, card.IsTemplate)
require.Equal(t, contentOrder, card.ContentOrder)
require.EqualValues(t, props, card.Properties)
})
t.Run("not found", func(t *testing.T) {
bogusID := utils.NewID(utils.IDTypeBlock)
th.Store.EXPECT().GetBlock(bogusID).Return(nil, model.NewErrNotFound(bogusID))
card, err := th.App.GetCardByID(bogusID)
require.Error(t, err, "error")
require.True(t, model.IsErrNotFound(err))
require.Nil(t, card)
})
t.Run("error scenario", func(t *testing.T) {
th.Store.EXPECT().GetBlock(block.ID).Return(nil, blockError{"error"})
card, err := th.App.GetCardByID(block.ID)
require.Error(t, err, "error")
require.Nil(t, card)
})
}
// reverse is a helper function to copy and reverse a slice of strings.
func reverse(src []string) []string {
out := make([]string, 0, len(src))
for i := len(src) - 1; i >= 0; i-- {
out = append(out, src[i])
}
return out
}
func makeProps(count int) map[string]any {
props := make(map[string]any)
for i := 0; i < count; i++ {
props[utils.NewID(utils.IDTypeBlock)] = utils.NewID(utils.IDTypeBlock)
}
return props
}
func copyProps(m map[string]any) map[string]any {
out := make(map[string]any)
for k, v := range m {
out[k] = v
}
return out
}
func modifyProps(m map[string]any) map[string]any {
out := make(map[string]any)
for k := range m {
out[k] = utils.NewID(utils.IDTypeBlock)
}
return out
}