Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
470 lines
11 KiB
Go
470 lines
11 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package model
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func validKanbanProps() StringInterface {
|
|
kanban := &KanbanProps{
|
|
GroupBy: KanbanGroupBy{
|
|
FieldID: NewId(),
|
|
Columns: []KanbanColumn{
|
|
{ID: NewId(), Name: "Todo", OptionIDs: []string{NewId()}},
|
|
{ID: NewId(), Name: "Done", OptionIDs: []string{NewId()}},
|
|
},
|
|
},
|
|
}
|
|
props, _ := kanban.ToProps()
|
|
return props
|
|
}
|
|
|
|
func validView() *View {
|
|
return &View{
|
|
Id: NewId(),
|
|
ChannelId: NewId(),
|
|
CreatorId: NewId(),
|
|
Type: ViewTypeKanban,
|
|
Title: "My Kanban",
|
|
Props: validKanbanProps(),
|
|
CreateAt: 1,
|
|
UpdateAt: 1,
|
|
}
|
|
}
|
|
|
|
func TestViewIsValid(t *testing.T) {
|
|
testCases := []struct {
|
|
Description string
|
|
View *View
|
|
Valid bool
|
|
}{
|
|
{
|
|
"valid view",
|
|
validView(),
|
|
true,
|
|
},
|
|
{
|
|
"missing id",
|
|
&View{
|
|
ChannelId: NewId(),
|
|
CreatorId: NewId(),
|
|
Type: ViewTypeKanban,
|
|
Title: "My Kanban",
|
|
CreateAt: 1,
|
|
UpdateAt: 1,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"invalid id",
|
|
func() *View { v := validView(); v.Id = "invalid"; return v }(),
|
|
false,
|
|
},
|
|
{
|
|
"missing channel id",
|
|
func() *View { v := validView(); v.ChannelId = ""; return v }(),
|
|
false,
|
|
},
|
|
{
|
|
"invalid channel id",
|
|
func() *View { v := validView(); v.ChannelId = "invalid"; return v }(),
|
|
false,
|
|
},
|
|
{
|
|
"missing creator id",
|
|
func() *View { v := validView(); v.CreatorId = ""; return v }(),
|
|
false,
|
|
},
|
|
{
|
|
"invalid creator id",
|
|
func() *View { v := validView(); v.CreatorId = "invalid"; return v }(),
|
|
false,
|
|
},
|
|
{
|
|
"invalid type",
|
|
func() *View { v := validView(); v.Type = "invalid"; return v }(),
|
|
false,
|
|
},
|
|
{
|
|
"empty title",
|
|
func() *View { v := validView(); v.Title = ""; return v }(),
|
|
false,
|
|
},
|
|
{
|
|
"title at max length",
|
|
func() *View { v := validView(); v.Title = strings.Repeat("a", ViewTitleMaxRunes); return v }(),
|
|
true,
|
|
},
|
|
{
|
|
"title exceeds max length",
|
|
func() *View { v := validView(); v.Title = strings.Repeat("a", ViewTitleMaxRunes+1); return v }(),
|
|
false,
|
|
},
|
|
{
|
|
"description at max length",
|
|
func() *View {
|
|
v := validView()
|
|
v.Description = strings.Repeat("a", ViewDescriptionMaxRunes)
|
|
return v
|
|
}(),
|
|
true,
|
|
},
|
|
{
|
|
"description exceeds max length",
|
|
func() *View {
|
|
v := validView()
|
|
v.Description = strings.Repeat("a", ViewDescriptionMaxRunes+1)
|
|
return v
|
|
}(),
|
|
false,
|
|
},
|
|
{
|
|
"missing create_at",
|
|
func() *View { v := validView(); v.CreateAt = 0; return v }(),
|
|
false,
|
|
},
|
|
{
|
|
"missing update_at",
|
|
func() *View { v := validView(); v.UpdateAt = 0; return v }(),
|
|
false,
|
|
},
|
|
{
|
|
"valid view with custom kanban props",
|
|
func() *View {
|
|
v := validView()
|
|
v.Props = validKanbanProps()
|
|
return v
|
|
}(),
|
|
true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.Description, func(t *testing.T) {
|
|
if tc.Valid {
|
|
require.Nil(t, tc.View.IsValid())
|
|
} else {
|
|
require.NotNil(t, tc.View.IsValid())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestViewPreSave(t *testing.T) {
|
|
t.Run("generates id if empty", func(t *testing.T) {
|
|
v := &View{Type: ViewTypeKanban, Title: "Kanban"}
|
|
v.PreSave()
|
|
assert.True(t, IsValidId(v.Id))
|
|
})
|
|
|
|
t.Run("keeps existing id", func(t *testing.T) {
|
|
id := NewId()
|
|
v := &View{Id: id, Type: ViewTypeKanban, Title: "Kanban"}
|
|
v.PreSave()
|
|
assert.Equal(t, id, v.Id)
|
|
})
|
|
|
|
t.Run("sets create_at and update_at", func(t *testing.T) {
|
|
v := &View{Type: ViewTypeKanban, Title: "Kanban"}
|
|
v.PreSave()
|
|
assert.NotZero(t, v.CreateAt)
|
|
assert.Equal(t, v.CreateAt, v.UpdateAt)
|
|
})
|
|
|
|
t.Run("keeps existing create_at", func(t *testing.T) {
|
|
v := &View{Type: ViewTypeKanban, Title: "Kanban", CreateAt: 42}
|
|
v.PreSave()
|
|
assert.Equal(t, int64(42), v.CreateAt)
|
|
})
|
|
|
|
t.Run("sets delete_at to zero", func(t *testing.T) {
|
|
v := &View{Type: ViewTypeKanban, Title: "Kanban", DeleteAt: 99}
|
|
v.PreSave()
|
|
assert.Zero(t, v.DeleteAt)
|
|
})
|
|
}
|
|
|
|
func TestViewPreUpdate(t *testing.T) {
|
|
v := validView()
|
|
originalUpdateAt := v.UpdateAt
|
|
|
|
v.PreUpdate()
|
|
|
|
assert.Greater(t, v.UpdateAt, originalUpdateAt)
|
|
}
|
|
|
|
func TestViewClone(t *testing.T) {
|
|
t.Run("nil props clones without panicking", func(t *testing.T) {
|
|
v := validView()
|
|
v.Props = nil
|
|
clone := v.Clone()
|
|
require.NotNil(t, clone)
|
|
assert.Nil(t, clone.Props)
|
|
})
|
|
|
|
t.Run("scalar fields are equal", func(t *testing.T) {
|
|
v := validView()
|
|
clone := v.Clone()
|
|
assert.Equal(t, v.Id, clone.Id)
|
|
assert.Equal(t, v.ChannelId, clone.ChannelId)
|
|
assert.Equal(t, v.Title, clone.Title)
|
|
})
|
|
|
|
t.Run("props are independently copied", func(t *testing.T) {
|
|
v := validView()
|
|
v.Props = StringInterface{"key": "value"}
|
|
clone := v.Clone()
|
|
require.NotNil(t, clone.Props)
|
|
assert.NotSame(t, &v.Props, &clone.Props)
|
|
assert.Equal(t, v.Props["key"], clone.Props["key"])
|
|
|
|
clone.Props["key"] = "mutated"
|
|
assert.Equal(t, "value", v.Props["key"], "original Props must not be affected")
|
|
})
|
|
}
|
|
|
|
func TestViewPatch(t *testing.T) {
|
|
testCases := []struct {
|
|
Description string
|
|
Patch *ViewPatch
|
|
Check func(t *testing.T, v *View)
|
|
}{
|
|
{
|
|
"patches title",
|
|
&ViewPatch{Title: new("New Title")},
|
|
func(t *testing.T, v *View) {
|
|
assert.Equal(t, "New Title", v.Title)
|
|
},
|
|
},
|
|
{
|
|
"patches description",
|
|
&ViewPatch{Description: new("A description")},
|
|
func(t *testing.T, v *View) {
|
|
assert.Equal(t, "A description", v.Description)
|
|
},
|
|
},
|
|
{
|
|
"patches sort_order",
|
|
&ViewPatch{SortOrder: new(5)},
|
|
func(t *testing.T, v *View) {
|
|
assert.Equal(t, 5, v.SortOrder)
|
|
},
|
|
},
|
|
{
|
|
"patches props",
|
|
&ViewPatch{Props: &StringInterface{"foo": "bar"}},
|
|
func(t *testing.T, v *View) {
|
|
require.NotNil(t, v.Props)
|
|
assert.Equal(t, "bar", v.Props["foo"])
|
|
},
|
|
},
|
|
{
|
|
"nil fields are not applied",
|
|
&ViewPatch{},
|
|
func(t *testing.T, v *View) {
|
|
assert.Equal(t, "My Kanban", v.Title)
|
|
assert.Empty(t, v.Description)
|
|
assert.Zero(t, v.SortOrder)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.Description, func(t *testing.T) {
|
|
v := validView()
|
|
v.Patch(tc.Patch)
|
|
tc.Check(t, v)
|
|
})
|
|
}
|
|
|
|
t.Run("patched props are independent from patch source", func(t *testing.T) {
|
|
patchProps := &StringInterface{"key": "value"}
|
|
v := validView()
|
|
v.Patch(&ViewPatch{Props: patchProps})
|
|
require.NotNil(t, v.Props)
|
|
|
|
(*patchProps)["key"] = "mutated"
|
|
assert.Equal(t, "value", v.Props["key"], "view Props must not be affected by patch mutation")
|
|
})
|
|
}
|
|
|
|
func TestKanbanPropsValidation(t *testing.T) {
|
|
t.Run("nil props rejected for kanban", func(t *testing.T) {
|
|
v := validView()
|
|
v.Props = nil
|
|
require.NotNil(t, v.IsValid())
|
|
})
|
|
|
|
t.Run("empty props rejected for kanban", func(t *testing.T) {
|
|
v := validView()
|
|
v.Props = StringInterface{}
|
|
require.NotNil(t, v.IsValid())
|
|
})
|
|
|
|
t.Run("missing field_id rejected", func(t *testing.T) {
|
|
kanban := &KanbanProps{
|
|
GroupBy: KanbanGroupBy{
|
|
FieldID: "",
|
|
Columns: []KanbanColumn{
|
|
{ID: NewId(), Name: "Todo", OptionIDs: []string{NewId()}},
|
|
},
|
|
},
|
|
}
|
|
props, _ := kanban.ToProps()
|
|
v := validView()
|
|
v.Props = props
|
|
require.NotNil(t, v.IsValid())
|
|
})
|
|
|
|
t.Run("invalid field_id rejected", func(t *testing.T) {
|
|
kanban := &KanbanProps{
|
|
GroupBy: KanbanGroupBy{
|
|
FieldID: "not-a-valid-id",
|
|
Columns: []KanbanColumn{
|
|
{ID: NewId(), Name: "Todo", OptionIDs: []string{NewId()}},
|
|
},
|
|
},
|
|
}
|
|
props, _ := kanban.ToProps()
|
|
v := validView()
|
|
v.Props = props
|
|
require.NotNil(t, v.IsValid())
|
|
})
|
|
|
|
t.Run("empty columns rejected", func(t *testing.T) {
|
|
kanban := &KanbanProps{
|
|
GroupBy: KanbanGroupBy{
|
|
FieldID: NewId(),
|
|
Columns: []KanbanColumn{},
|
|
},
|
|
}
|
|
props, _ := kanban.ToProps()
|
|
v := validView()
|
|
v.Props = props
|
|
require.NotNil(t, v.IsValid())
|
|
})
|
|
|
|
t.Run("too many columns rejected", func(t *testing.T) {
|
|
columns := make([]KanbanColumn, MaxKanbanColumns+1)
|
|
for i := range columns {
|
|
columns[i] = KanbanColumn{ID: NewId(), Name: "Col", OptionIDs: []string{NewId()}}
|
|
}
|
|
kanban := &KanbanProps{
|
|
GroupBy: KanbanGroupBy{
|
|
FieldID: NewId(),
|
|
Columns: columns,
|
|
},
|
|
}
|
|
props, _ := kanban.ToProps()
|
|
v := validView()
|
|
v.Props = props
|
|
require.NotNil(t, v.IsValid())
|
|
})
|
|
|
|
t.Run("max columns accepted", func(t *testing.T) {
|
|
columns := make([]KanbanColumn, MaxKanbanColumns)
|
|
for i := range columns {
|
|
columns[i] = KanbanColumn{ID: NewId(), Name: "Col", OptionIDs: []string{NewId()}}
|
|
}
|
|
kanban := &KanbanProps{
|
|
GroupBy: KanbanGroupBy{
|
|
FieldID: NewId(),
|
|
Columns: columns,
|
|
},
|
|
}
|
|
props, _ := kanban.ToProps()
|
|
v := validView()
|
|
v.Props = props
|
|
require.Nil(t, v.IsValid())
|
|
})
|
|
|
|
t.Run("column missing id rejected", func(t *testing.T) {
|
|
kanban := &KanbanProps{
|
|
GroupBy: KanbanGroupBy{
|
|
FieldID: NewId(),
|
|
Columns: []KanbanColumn{
|
|
{ID: "", Name: "Todo", OptionIDs: []string{NewId()}},
|
|
},
|
|
},
|
|
}
|
|
props, _ := kanban.ToProps()
|
|
v := validView()
|
|
v.Props = props
|
|
require.NotNil(t, v.IsValid())
|
|
})
|
|
|
|
t.Run("column empty name rejected", func(t *testing.T) {
|
|
kanban := &KanbanProps{
|
|
GroupBy: KanbanGroupBy{
|
|
FieldID: NewId(),
|
|
Columns: []KanbanColumn{
|
|
{ID: NewId(), Name: "", OptionIDs: []string{NewId()}},
|
|
},
|
|
},
|
|
}
|
|
props, _ := kanban.ToProps()
|
|
v := validView()
|
|
v.Props = props
|
|
require.NotNil(t, v.IsValid())
|
|
})
|
|
|
|
t.Run("column empty option_ids rejected", func(t *testing.T) {
|
|
kanban := &KanbanProps{
|
|
GroupBy: KanbanGroupBy{
|
|
FieldID: NewId(),
|
|
Columns: []KanbanColumn{
|
|
{ID: NewId(), Name: "Todo", OptionIDs: []string{}},
|
|
},
|
|
},
|
|
}
|
|
props, _ := kanban.ToProps()
|
|
v := validView()
|
|
v.Props = props
|
|
require.NotNil(t, v.IsValid())
|
|
})
|
|
|
|
t.Run("column with multiple option_ids accepted", func(t *testing.T) {
|
|
kanban := &KanbanProps{
|
|
GroupBy: KanbanGroupBy{
|
|
FieldID: NewId(),
|
|
Columns: []KanbanColumn{
|
|
{ID: NewId(), Name: "Todo", OptionIDs: []string{NewId(), NewId()}},
|
|
},
|
|
},
|
|
}
|
|
props, _ := kanban.ToProps()
|
|
v := validView()
|
|
v.Props = props
|
|
require.Nil(t, v.IsValid())
|
|
})
|
|
}
|
|
|
|
func TestKanbanPropsRoundTrip(t *testing.T) {
|
|
original := &KanbanProps{
|
|
GroupBy: KanbanGroupBy{
|
|
FieldID: NewId(),
|
|
Columns: []KanbanColumn{
|
|
{ID: NewId(), Name: "Todo", OptionIDs: []string{NewId()}},
|
|
{ID: NewId(), Name: "Done", OptionIDs: []string{NewId(), NewId()}},
|
|
},
|
|
},
|
|
}
|
|
|
|
props, err := original.ToProps()
|
|
require.NoError(t, err)
|
|
|
|
parsed, err := KanbanPropsFromProps(props)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, original.GroupBy.FieldID, parsed.GroupBy.FieldID)
|
|
require.Len(t, parsed.GroupBy.Columns, 2)
|
|
assert.Equal(t, original.GroupBy.Columns[0].Name, parsed.GroupBy.Columns[0].Name)
|
|
assert.Equal(t, original.GroupBy.Columns[1].OptionIDs, parsed.GroupBy.Columns[1].OptionIDs)
|
|
}
|