fa6de94070
* Adds limits implementation to the server * Add test for deleted boards on active card count
642 lines
16 KiB
Go
642 lines
16 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"database/sql"
|
|
"testing"
|
|
|
|
"github.com/golang/mock/gomock"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
mmModel "github.com/mattermost/mattermost-server/v6/model"
|
|
|
|
"github.com/mattermost/focalboard/server/model"
|
|
)
|
|
|
|
func TestIsCloud(t *testing.T) {
|
|
t.Run("if it's not running on plugin mode", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
th.Store.EXPECT().GetLicense().Return(nil)
|
|
require.False(t, th.App.IsCloud())
|
|
})
|
|
|
|
t.Run("if it's running on plugin mode but the license is incomplete", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
fakeLicense := &mmModel.License{}
|
|
|
|
th.Store.EXPECT().GetLicense().Return(fakeLicense)
|
|
require.False(t, th.App.IsCloud())
|
|
|
|
fakeLicense = &mmModel.License{Features: &mmModel.Features{}}
|
|
|
|
th.Store.EXPECT().GetLicense().Return(fakeLicense)
|
|
require.False(t, th.App.IsCloud())
|
|
})
|
|
|
|
t.Run("if it's running on plugin mode, with a non-cloud license", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
fakeLicense := &mmModel.License{
|
|
Features: &mmModel.Features{Cloud: mmModel.NewBool(false)},
|
|
}
|
|
|
|
th.Store.EXPECT().GetLicense().Return(fakeLicense)
|
|
require.False(t, th.App.IsCloud())
|
|
})
|
|
|
|
t.Run("if it's running on plugin mode with a cloud license", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
fakeLicense := &mmModel.License{
|
|
Features: &mmModel.Features{Cloud: mmModel.NewBool(true)},
|
|
}
|
|
|
|
th.Store.EXPECT().GetLicense().Return(fakeLicense)
|
|
require.True(t, th.App.IsCloud())
|
|
})
|
|
}
|
|
|
|
func TestIsCloudLimited(t *testing.T) {
|
|
t.Run("if no limit has been set, it should be false", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
require.Zero(t, th.App.CardLimit())
|
|
require.False(t, th.App.IsCloudLimited())
|
|
})
|
|
|
|
t.Run("if the limit is set, it should be true", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
fakeLicense := &mmModel.License{
|
|
Features: &mmModel.Features{Cloud: mmModel.NewBool(true)},
|
|
}
|
|
th.Store.EXPECT().GetLicense().Return(fakeLicense)
|
|
|
|
th.App.SetCardLimit(5)
|
|
require.True(t, th.App.IsCloudLimited())
|
|
})
|
|
}
|
|
|
|
func TestSetCloudLimits(t *testing.T) {
|
|
t.Run("if the limits are empty, it should do nothing", func(t *testing.T) {
|
|
t.Run("limits empty", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
require.Zero(t, th.App.CardLimit())
|
|
|
|
require.NoError(t, th.App.SetCloudLimits(nil))
|
|
require.Zero(t, th.App.CardLimit())
|
|
})
|
|
|
|
t.Run("limits not empty but board limits empty", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
require.Zero(t, th.App.CardLimit())
|
|
|
|
limits := &mmModel.ProductLimits{}
|
|
|
|
require.NoError(t, th.App.SetCloudLimits(limits))
|
|
require.Zero(t, th.App.CardLimit())
|
|
})
|
|
|
|
t.Run("limits not empty but board limits values empty", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
require.Zero(t, th.App.CardLimit())
|
|
|
|
limits := &mmModel.ProductLimits{
|
|
Boards: &mmModel.BoardsLimits{},
|
|
}
|
|
|
|
require.NoError(t, th.App.SetCloudLimits(limits))
|
|
require.Zero(t, th.App.CardLimit())
|
|
})
|
|
})
|
|
|
|
t.Run("if the limits are not empty, it should update them and calculate the new timestamp", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
require.Zero(t, th.App.CardLimit())
|
|
|
|
newCardLimitTimestamp := int64(27)
|
|
th.Store.EXPECT().UpdateCardLimitTimestamp(5).Return(newCardLimitTimestamp, nil)
|
|
|
|
limits := &mmModel.ProductLimits{
|
|
Boards: &mmModel.BoardsLimits{Cards: mmModel.NewInt(5)},
|
|
}
|
|
|
|
require.NoError(t, th.App.SetCloudLimits(limits))
|
|
require.Equal(t, 5, th.App.CardLimit())
|
|
})
|
|
|
|
t.Run("if the limits are already set and we unset them, the timestamp will be unset too", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
th.App.SetCardLimit(20)
|
|
|
|
th.Store.EXPECT().UpdateCardLimitTimestamp(0)
|
|
|
|
require.NoError(t, th.App.SetCloudLimits(nil))
|
|
|
|
require.Zero(t, th.App.CardLimit())
|
|
})
|
|
|
|
t.Run("if the limits are already set and we try to set the same ones again", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
th.App.SetCardLimit(20)
|
|
|
|
// the call to update card limit timestamp should not happen
|
|
// as the limits didn't change
|
|
th.Store.EXPECT().UpdateCardLimitTimestamp(gomock.Any()).Times(0)
|
|
|
|
limits := &mmModel.ProductLimits{
|
|
Boards: &mmModel.BoardsLimits{Cards: mmModel.NewInt(20)},
|
|
}
|
|
|
|
require.NoError(t, th.App.SetCloudLimits(limits))
|
|
require.Equal(t, 20, th.App.CardLimit())
|
|
})
|
|
}
|
|
|
|
func TestUpdateCardLimitTimestamp(t *testing.T) {
|
|
fakeLicense := &mmModel.License{
|
|
Features: &mmModel.Features{Cloud: mmModel.NewBool(true)},
|
|
}
|
|
|
|
t.Run("if the server is a cloud instance but not limited, it should do nothing", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
require.Zero(t, th.App.CardLimit())
|
|
|
|
// the license check will not be done as the limit not being
|
|
// set is enough for the method to return
|
|
th.Store.EXPECT().GetLicense().Times(0)
|
|
// no call to UpdateCardLimitTimestamp should happen as the
|
|
// method should shortcircuit if not cloud limited
|
|
th.Store.EXPECT().UpdateCardLimitTimestamp(gomock.Any()).Times(0)
|
|
|
|
require.NoError(t, th.App.UpdateCardLimitTimestamp())
|
|
})
|
|
|
|
t.Run("if the server is a cloud instance and the timestamp is set, it should run the update", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
th.App.SetCardLimit(5)
|
|
|
|
th.Store.EXPECT().GetLicense().Return(fakeLicense)
|
|
// no call to UpdateCardLimitTimestamp should happen as the
|
|
// method should shortcircuit if not cloud limited
|
|
th.Store.EXPECT().UpdateCardLimitTimestamp(5)
|
|
|
|
require.NoError(t, th.App.UpdateCardLimitTimestamp())
|
|
})
|
|
}
|
|
|
|
func TestGetTemplateMapForBlocks(t *testing.T) {
|
|
t.Run("should fetch the necessary boards from the database", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
board1 := &model.Board{
|
|
ID: "board1",
|
|
Type: model.BoardTypeOpen,
|
|
IsTemplate: true,
|
|
}
|
|
|
|
board2 := &model.Board{
|
|
ID: "board2",
|
|
Type: model.BoardTypeOpen,
|
|
IsTemplate: false,
|
|
}
|
|
|
|
blocks := []model.Block{
|
|
{
|
|
ID: "card1",
|
|
Type: model.TypeCard,
|
|
ParentID: "board1",
|
|
BoardID: "board1",
|
|
},
|
|
{
|
|
ID: "card2",
|
|
Type: model.TypeCard,
|
|
ParentID: "board2",
|
|
BoardID: "board2",
|
|
},
|
|
{
|
|
ID: "text2",
|
|
Type: model.TypeText,
|
|
ParentID: "card2",
|
|
BoardID: "board2",
|
|
},
|
|
}
|
|
|
|
th.Store.EXPECT().
|
|
GetBoard("board1").
|
|
Return(board1, nil).
|
|
Times(1)
|
|
th.Store.EXPECT().
|
|
GetBoard("board2").
|
|
Return(board2, nil).
|
|
Times(1)
|
|
|
|
templateMap, err := th.App.getTemplateMapForBlocks(blocks)
|
|
require.NoError(t, err)
|
|
require.Len(t, templateMap, 2)
|
|
require.Contains(t, templateMap, "board1")
|
|
require.True(t, templateMap["board1"])
|
|
require.Contains(t, templateMap, "board2")
|
|
require.False(t, templateMap["board2"])
|
|
})
|
|
|
|
t.Run("should fail if the board is not in the database", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
blocks := []model.Block{
|
|
{
|
|
ID: "card1",
|
|
Type: model.TypeCard,
|
|
ParentID: "board1",
|
|
BoardID: "board1",
|
|
},
|
|
{
|
|
ID: "card2",
|
|
Type: model.TypeCard,
|
|
ParentID: "board2",
|
|
BoardID: "board2",
|
|
},
|
|
}
|
|
|
|
th.Store.EXPECT().
|
|
GetBoard("board1").
|
|
Return(nil, sql.ErrNoRows).
|
|
Times(1)
|
|
|
|
templateMap, err := th.App.getTemplateMapForBlocks(blocks)
|
|
require.ErrorIs(t, err, sql.ErrNoRows)
|
|
require.Empty(t, templateMap)
|
|
})
|
|
}
|
|
|
|
func TestApplyCloudLimits(t *testing.T) {
|
|
fakeLicense := &mmModel.License{
|
|
Features: &mmModel.Features{Cloud: mmModel.NewBool(true)},
|
|
}
|
|
|
|
board1 := &model.Board{
|
|
ID: "board1",
|
|
Type: model.BoardTypeOpen,
|
|
IsTemplate: false,
|
|
}
|
|
|
|
template := &model.Board{
|
|
ID: "template",
|
|
Type: model.BoardTypeOpen,
|
|
IsTemplate: true,
|
|
}
|
|
|
|
blocks := []model.Block{
|
|
{
|
|
ID: "card1",
|
|
Type: model.TypeCard,
|
|
ParentID: "board1",
|
|
BoardID: "board1",
|
|
UpdateAt: 100,
|
|
},
|
|
{
|
|
ID: "text1",
|
|
Type: model.TypeText,
|
|
ParentID: "card1",
|
|
BoardID: "board1",
|
|
UpdateAt: 100,
|
|
},
|
|
{
|
|
ID: "card2",
|
|
Type: model.TypeCard,
|
|
ParentID: "board1",
|
|
BoardID: "board1",
|
|
UpdateAt: 200,
|
|
},
|
|
{
|
|
ID: "card-from-template",
|
|
Type: model.TypeCard,
|
|
ParentID: "template",
|
|
BoardID: "template",
|
|
UpdateAt: 1,
|
|
},
|
|
}
|
|
|
|
t.Run("if the server is not limited, it should return the blocks untouched", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
require.Zero(t, th.App.CardLimit())
|
|
|
|
newBlocks, err := th.App.ApplyCloudLimits(blocks)
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, blocks, newBlocks)
|
|
})
|
|
|
|
t.Run("if the server is limited, it should limit the blocks that are beyond the card limit timestamp", func(t *testing.T) {
|
|
findBlock := func(blocks []model.Block, id string) model.Block {
|
|
for _, block := range blocks {
|
|
if block.ID == id {
|
|
return block
|
|
}
|
|
}
|
|
require.FailNow(t, "block %s not found", id)
|
|
return model.Block{} // this should never be reached
|
|
}
|
|
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
th.App.SetCardLimit(5)
|
|
|
|
th.Store.EXPECT().GetLicense().Return(fakeLicense)
|
|
th.Store.EXPECT().GetCardLimitTimestamp().Return(int64(150), nil)
|
|
th.Store.EXPECT().GetBoard("board1").Return(board1, nil).Times(1)
|
|
th.Store.EXPECT().GetBoard("template").Return(template, nil).Times(1)
|
|
|
|
newBlocks, err := th.App.ApplyCloudLimits(blocks)
|
|
require.NoError(t, err)
|
|
|
|
// should be limited as it's beyond the threshold
|
|
require.True(t, findBlock(newBlocks, "card1").Limited)
|
|
// only cards are limited
|
|
require.False(t, findBlock(newBlocks, "text1").Limited)
|
|
// should not be limited as it's not beyond the threshold
|
|
require.False(t, findBlock(newBlocks, "card2").Limited)
|
|
// cards belonging to templates are never limited
|
|
require.False(t, findBlock(newBlocks, "card-from-template").Limited)
|
|
})
|
|
}
|
|
|
|
func TestContainsLimitedBlocks(t *testing.T) {
|
|
// for all the following tests, the timestamp will be set to 150,
|
|
// which means that blocks with an UpdateAt set to 100 will be
|
|
// outside the active window and possibly limited, and blocks with
|
|
// UpdateAt set to 200 will not
|
|
|
|
t.Run("should return false if the card limit timestamp is zero", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
blocks := []model.Block{
|
|
{
|
|
ID: "card1",
|
|
Type: model.TypeCard,
|
|
ParentID: "board1",
|
|
BoardID: "board1",
|
|
UpdateAt: 100,
|
|
},
|
|
}
|
|
|
|
th.Store.EXPECT().GetCardLimitTimestamp().Return(int64(0), nil)
|
|
|
|
containsLimitedBlocks, err := th.App.ContainsLimitedBlocks(blocks)
|
|
require.NoError(t, err)
|
|
require.False(t, containsLimitedBlocks)
|
|
})
|
|
|
|
t.Run("should return true if the block set contains a card that is limited", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
blocks := []model.Block{
|
|
{
|
|
ID: "card1",
|
|
Type: model.TypeCard,
|
|
ParentID: "board1",
|
|
BoardID: "board1",
|
|
UpdateAt: 100,
|
|
},
|
|
}
|
|
|
|
board1 := &model.Board{
|
|
ID: "board1",
|
|
Type: model.BoardTypePrivate,
|
|
}
|
|
|
|
th.App.SetCardLimit(500)
|
|
cardLimitTimestamp := int64(150)
|
|
th.Store.EXPECT().GetCardLimitTimestamp().Return(cardLimitTimestamp, nil)
|
|
th.Store.EXPECT().GetBoard("board1").Return(board1, nil)
|
|
|
|
containsLimitedBlocks, err := th.App.ContainsLimitedBlocks(blocks)
|
|
require.NoError(t, err)
|
|
require.True(t, containsLimitedBlocks)
|
|
})
|
|
|
|
t.Run("should return false if that same block belongs to a template", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
blocks := []model.Block{
|
|
{
|
|
ID: "card1",
|
|
Type: model.TypeCard,
|
|
ParentID: "board1",
|
|
BoardID: "board1",
|
|
UpdateAt: 100,
|
|
},
|
|
}
|
|
|
|
board1 := &model.Board{
|
|
ID: "board1",
|
|
Type: model.BoardTypeOpen,
|
|
IsTemplate: true,
|
|
}
|
|
|
|
th.App.SetCardLimit(500)
|
|
cardLimitTimestamp := int64(150)
|
|
th.Store.EXPECT().GetCardLimitTimestamp().Return(cardLimitTimestamp, nil)
|
|
th.Store.EXPECT().GetBoard("board1").Return(board1, nil)
|
|
|
|
containsLimitedBlocks, err := th.App.ContainsLimitedBlocks(blocks)
|
|
require.NoError(t, err)
|
|
require.False(t, containsLimitedBlocks)
|
|
})
|
|
|
|
t.Run("should return true if the block contains a content block that belongs to a card that should be limited", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
blocks := []model.Block{
|
|
{
|
|
ID: "text1",
|
|
Type: model.TypeText,
|
|
ParentID: "card1",
|
|
BoardID: "board1",
|
|
UpdateAt: 200,
|
|
},
|
|
}
|
|
|
|
card1 := model.Block{
|
|
ID: "card1",
|
|
Type: model.TypeCard,
|
|
ParentID: "board1",
|
|
BoardID: "board1",
|
|
UpdateAt: 100,
|
|
}
|
|
|
|
board1 := &model.Board{
|
|
ID: "board1",
|
|
Type: model.BoardTypeOpen,
|
|
}
|
|
|
|
th.App.SetCardLimit(500)
|
|
cardLimitTimestamp := int64(150)
|
|
th.Store.EXPECT().GetCardLimitTimestamp().Return(cardLimitTimestamp, nil)
|
|
th.Store.EXPECT().GetBlocksByIDs([]string{"card1"}).Return([]model.Block{card1}, nil)
|
|
th.Store.EXPECT().GetBoard("board1").Return(board1, nil)
|
|
|
|
containsLimitedBlocks, err := th.App.ContainsLimitedBlocks(blocks)
|
|
require.NoError(t, err)
|
|
require.True(t, containsLimitedBlocks)
|
|
})
|
|
|
|
t.Run("should return false if that same block belongs to a card that is inside the active window", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
blocks := []model.Block{
|
|
{
|
|
ID: "text1",
|
|
Type: model.TypeText,
|
|
ParentID: "card1",
|
|
BoardID: "board1",
|
|
UpdateAt: 200,
|
|
},
|
|
}
|
|
|
|
card1 := model.Block{
|
|
ID: "card1",
|
|
Type: model.TypeCard,
|
|
ParentID: "board1",
|
|
BoardID: "board1",
|
|
UpdateAt: 200,
|
|
}
|
|
|
|
board1 := &model.Board{
|
|
ID: "board1",
|
|
Type: model.BoardTypeOpen,
|
|
}
|
|
|
|
th.App.SetCardLimit(500)
|
|
cardLimitTimestamp := int64(150)
|
|
th.Store.EXPECT().GetCardLimitTimestamp().Return(cardLimitTimestamp, nil)
|
|
th.Store.EXPECT().GetBlocksByIDs([]string{"card1"}).Return([]model.Block{card1}, nil)
|
|
th.Store.EXPECT().GetBoard("board1").Return(board1, nil)
|
|
|
|
containsLimitedBlocks, err := th.App.ContainsLimitedBlocks(blocks)
|
|
require.NoError(t, err)
|
|
require.False(t, containsLimitedBlocks)
|
|
})
|
|
|
|
t.Run("should reach to the database to fetch the necessary information only in an efficient way", func(t *testing.T) {
|
|
th, tearDown := SetupTestHelper(t)
|
|
defer tearDown()
|
|
|
|
blocks := []model.Block{
|
|
// a content block that references a card that needs
|
|
// fetching
|
|
{
|
|
ID: "text1",
|
|
Type: model.TypeText,
|
|
ParentID: "card1",
|
|
BoardID: "board1",
|
|
UpdateAt: 100,
|
|
},
|
|
// a board that needs fetching referenced by a card and a content block
|
|
{
|
|
ID: "card2",
|
|
Type: model.TypeCard,
|
|
ParentID: "board2",
|
|
BoardID: "board2",
|
|
// per timestamp should be limited but the board is a
|
|
// template
|
|
UpdateAt: 100,
|
|
},
|
|
{
|
|
ID: "text2",
|
|
Type: model.TypeText,
|
|
ParentID: "card2",
|
|
BoardID: "board2",
|
|
UpdateAt: 200,
|
|
},
|
|
// a content block that references a card and a board,
|
|
// both absent
|
|
{
|
|
ID: "image3",
|
|
Type: model.TypeImage,
|
|
ParentID: "card3",
|
|
BoardID: "board3",
|
|
UpdateAt: 100,
|
|
},
|
|
}
|
|
|
|
card1 := model.Block{
|
|
ID: "card1",
|
|
Type: model.TypeCard,
|
|
ParentID: "board1",
|
|
BoardID: "board1",
|
|
UpdateAt: 200,
|
|
}
|
|
|
|
card3 := model.Block{
|
|
ID: "card3",
|
|
Type: model.TypeCard,
|
|
ParentID: "board3",
|
|
BoardID: "board3",
|
|
UpdateAt: 200,
|
|
}
|
|
|
|
board1 := &model.Board{
|
|
ID: "board1",
|
|
Type: model.BoardTypeOpen,
|
|
}
|
|
|
|
board2 := &model.Board{
|
|
ID: "board2",
|
|
Type: model.BoardTypeOpen,
|
|
IsTemplate: true,
|
|
}
|
|
|
|
board3 := &model.Board{
|
|
ID: "board3",
|
|
Type: model.BoardTypePrivate,
|
|
}
|
|
|
|
th.App.SetCardLimit(500)
|
|
cardLimitTimestamp := int64(150)
|
|
th.Store.EXPECT().GetCardLimitTimestamp().Return(cardLimitTimestamp, nil)
|
|
th.Store.EXPECT().GetBlocksByIDs(gomock.InAnyOrder([]string{"card1", "card3"})).Return([]model.Block{card1, card3}, nil)
|
|
th.Store.EXPECT().GetBoard("board1").Return(board1, nil)
|
|
th.Store.EXPECT().GetBoard("board2").Return(board2, nil)
|
|
th.Store.EXPECT().GetBoard("board3").Return(board3, nil)
|
|
|
|
containsLimitedBlocks, err := th.App.ContainsLimitedBlocks(blocks)
|
|
require.NoError(t, err)
|
|
require.False(t, containsLimitedBlocks)
|
|
})
|
|
}
|