Files
team/server/channels/api4/channel_category_test.go
9ed85b6850 Cherry-pick Go 1.25.8 upgrade and FIPS password fixes to release-11.4 (#36123)
* ci: shard server Postgres tests into 4 parallel runners (#35739)

Cherry-pick of 00296f9b49 from master.

* updated go to version 1.25.8 (#35817)

Cherry-pick of 32a10bac62 from master.

* Move password hashers from server/v8 to server/public (#35805)

Cherry-pick of cca3f08574 from master.

* Generate instead of hard-coding test passwords, enforce new minimum for FIPS, shard CI, fix FIPS builds (#35905)

Cherry-pick of 1bdd985b41 from master.

* upgrade golangci-lint (#35845)

Cherry-pick of 157e0400b2 from master.

On release-11.4 (unlike 11.5), UpdatePostsChannelTypeByChannelId and
BackfillPostsChannelType exist in the elasticsearch/opensearch files,
so the imports they require (fmt, updatebyquery, scriptlanguage) must
be added — they were not in the cherry-pick diff since they already
existed on master.

Also updates html2text/tablewriter/olekukonko deps to fix a workspace
conflict where enterprise's Go 1.25.8 bump pulled in a newer html2text
that requires tablewriter ≥ 1.1 (was pinned to 0.0.5 via a replace
directive). Matches the subset of 866f759e4b (Mm 66662 bump deps)
relevant to this incompatibility.

* Fix FIPS-incompatible passwords and config in e2e test suites (#36001)

Cherry-pick of b51b4a8937 from master.

release-11.4 differences vs release-11.5:
- demo_plugin_installation.spec.ts also deleted (modify/delete conflict, git rm)
- authentication_2_spec.ts conflict: HEAD missing check() call; took
  incoming (check + FIPS-length passwords)
- member_invitation_ui_spec.ts: HEAD has 'Create Account' (capital A)
  matching release-11.4 button text; kept HEAD's button text with
  incoming's newTestPassword()

* fix(cypress): demo plugin (#36056)

Cherry-pick of 39f1979aa5 from master.

demo_plugin_installation.spec.ts was deleted in step 10 (modify/delete);
re-created here with both the FIPS-password change and the URL fix.

* test: clean up channel store data after TestChannelStore (#36066)

TestChannelStore sub-tests create channels, members, and team members
using fake TeamIds and UserIds (model.NewId() for non-existent rows).
These records are left in the database and cause integrity tests
(TestCheck*) running in the same binary to fail their full-table scans.

Register a t.Cleanup on TestChannelStore that purges the affected
tables entirely. A blanket purge is safe: the schema enforces no FK
constraints, and every test suite creates its own data independently.

* Fix command injection in server-test-template workflow (#36080)

Cherry-pick of 9223935f16 from master.

* fix(ci): restore testname format in sharded gotestsum runs (#36078)

run-shard-tests.sh called gotestsum directly without --format, so it
fell back to gotestsum's default (pkgname) instead of the testname
format set by the Makefile. Pass --format "${GOTESTSUM_FORMAT:-testname}"
to match the Makefile default.

Co-authored-by: Mattermost Build <[email protected]>

* fixup: drop demo_plugin playwright specs — master-only APIs

demo_plugin/helpers.ts and demo_plugin_installation.spec.ts use
installAndEnablePlugin and isPluginActive which don't exist on
release-11.4's PlaywrightExtended type. These files were re-created
by the step-11 cherry-pick of 39f1979aa5, but they don't compile on
this branch (same class of problem as actions.spec.ts in step 10).

* fixup: add missing await in PluginLinkTooltip test

The test added in 39f1979aa5 is missing an await on renderWithContext,
causing the pointer-events assertion to race. Fix from 0c7d3f27c6
(on a feature branch, not yet on master).

* fixup: drop pointer-events test incompatible with jsdom@20

The 'should not block interaction with elements outside the tooltip' test
checks getComputedStyle for pointer-events: none, which is set only in SCSS.
release-11.4 uses jest-environment-jsdom@29 (jsdom@20), which does not apply
stylesheets, so getComputedStyle always returns "". The test passes on
release-11.5+ only because they use jest-environment-jsdom@30 (jsdom@25+).

Drop the test rather than backport the jsdom upgrade.

* fixup: add missing i18n keys for elasticsearch/opensearch functions

BackfillPostsChannelType and UpdatePostsChannelTypeByChannelId exist on
release-11.4 (unlike release-11.5+). The golangci-lint cherry-pick added
new error strings to those functions referencing i18n keys that weren't
in en.json.

* fixup: run make i18n-extract to reorder keys correctly

* fixup: drop terms-and-privacy checkbox from MM-T1774 on release-11.4

The #signup-body-card-form-check-terms-and-privacy element did not exist
on release-11.4's signup page. It was added to master after this branch
was cut, so the check() call from the incoming cherry-pick causes the test
to fail with "element never found".

* fixup: remove UpdatePostsChannelTypeByChannelId/BackfillPostsChannelType

These functions were introduced by MM-67540 (allow searching public channel
messages without channel membership), which never landed on release-11.4.
The golangci-lint cherry-pick only modernizes existing code; the rerere
resolution incorrectly added the full function bodies from master.

Remove both functions and their now-unused imports (fmt, updatebyquery,
scriptlanguage) from elasticsearch.go and opensearch.go. Also remove the
two i18n keys added to support them.

Same treatment as release-11.5, where these functions were also absent.

---------

Co-authored-by: Mattermost Build <[email protected]>
2026-04-16 12:10:03 -03:00

1633 lines
62 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"context"
"encoding/json"
"fmt"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost/server/public/model"
)
func TestCreateCategoryForTeamForUser(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
t.Run("should silently prevent the user from creating a category with an invalid channel ID", func(t *testing.T) {
user, client := setupUserForSubtest(t, th)
categories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.Len(t, categories.Categories, 3)
require.Len(t, categories.Order, 3)
// Attempt to create the category
category := &model.SidebarCategoryWithChannels{
SidebarCategory: model.SidebarCategory{
UserId: user.Id,
TeamId: th.BasicTeam.Id,
DisplayName: "test",
},
Channels: []string{th.BasicChannel.Id, "notachannel", th.BasicChannel2.Id},
}
received, _, err := client.CreateSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, category)
require.NoError(t, err)
assert.NotContains(t, received.Channels, "notachannel")
assert.Equal(t, []string{th.BasicChannel.Id, th.BasicChannel2.Id}, received.Channels)
})
t.Run("should silently prevent the user from creating a category with a channel that they're not a member of", func(t *testing.T) {
user, client := setupUserForSubtest(t, th)
categories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.Len(t, categories.Categories, 3)
require.Len(t, categories.Order, 3)
// Have another user create a channel that user isn't a part of
channel, _, err := th.SystemAdminClient.CreateChannel(context.Background(), &model.Channel{
TeamId: th.BasicTeam.Id,
Type: model.ChannelTypeOpen,
Name: "testchannel",
DisplayName: "testchannel",
})
require.NoError(t, err)
// Attempt to create the category
category := &model.SidebarCategoryWithChannels{
SidebarCategory: model.SidebarCategory{
UserId: user.Id,
TeamId: th.BasicTeam.Id,
DisplayName: "test",
},
Channels: []string{th.BasicChannel.Id, channel.Id},
}
received, _, err := client.CreateSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, category)
require.NoError(t, err)
assert.NotContains(t, received.Channels, channel.Id)
assert.Equal(t, []string{th.BasicChannel.Id}, received.Channels)
})
t.Run("should return expected sort order value", func(t *testing.T) {
user, client := setupUserForSubtest(t, th)
customCategory, _, err := client.CreateSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, &model.SidebarCategoryWithChannels{
SidebarCategory: model.SidebarCategory{
UserId: user.Id,
TeamId: th.BasicTeam.Id,
DisplayName: "custom123",
},
})
require.NoError(t, err)
// Initial new category sort order is 10 (first)
require.Equal(t, int64(10), customCategory.SortOrder)
})
t.Run("should not crash with null input", func(t *testing.T) {
require.NotPanics(t, func() {
user, client := setupUserForSubtest(t, th)
payload := `null`
route := fmt.Sprintf("/users/%s/teams/%s/channels/categories", user.Id, th.BasicTeam.Id)
r, err := client.DoAPIPost(context.Background(), route, payload)
require.Error(t, err)
closeBody(r)
})
})
t.Run("should return error when user tries to create a category for a team they're not a member of", func(t *testing.T) {
// Create a user
password := model.NewTestPassword()
user, appErr := th.App.CreateUser(th.Context, &model.User{
Email: th.GenerateTestEmail(),
Username: "user_" + model.NewId(),
Password: password,
})
require.Nil(t, appErr)
// Create a team and add the user to it
team, appErr := th.App.CreateTeam(th.Context, &model.Team{
DisplayName: "Team for testing",
Name: "test-team-" + model.NewId(),
Email: th.GenerateTestEmail(),
Type: model.TeamOpen,
})
require.Nil(t, appErr)
th.LinkUserToTeam(t, user, team)
// Create a client and log in
client := th.CreateClient()
_, _, err := client.Login(context.Background(), user.Email, password)
require.NoError(t, err)
// Now remove the user from the team
appErr = th.App.RemoveUserFromTeam(th.Context, team.Id, user.Id, th.SystemAdminUser.Id)
require.Nil(t, appErr)
// Attempt to create a category for the team the user is no longer a member of
category := &model.SidebarCategoryWithChannels{
SidebarCategory: model.SidebarCategory{
UserId: user.Id,
TeamId: team.Id,
DisplayName: "test category",
},
}
_, resp, err := client.CreateSidebarCategoryForTeamForUser(context.Background(), user.Id, team.Id, category)
require.Error(t, err)
require.Equal(t, http.StatusForbidden, resp.StatusCode)
})
t.Run("should publish expected WS payload", func(t *testing.T) {
userWSClient := th.CreateConnectedWebSocketClient(t)
category := &model.SidebarCategoryWithChannels{
SidebarCategory: model.SidebarCategory{
UserId: th.BasicUser.Id,
TeamId: th.BasicTeam.Id,
DisplayName: "test",
},
Channels: []string{th.BasicChannel.Id, "notachannel", th.BasicChannel2.Id},
}
received, _, err := th.Client.CreateSidebarCategoryForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, category)
require.NoError(t, err)
testCategories := []*model.SidebarCategoryWithChannels{
{
SidebarCategory: model.SidebarCategory{
Id: received.Id,
UserId: th.BasicUser.Id,
TeamId: th.BasicTeam.Id,
Sorting: model.SidebarCategorySortRecent,
Muted: true,
},
Channels: []string{th.BasicChannel.Id},
},
}
testCategories, _, err = th.Client.UpdateSidebarCategoriesForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, testCategories)
require.NoError(t, err)
b, err := json.Marshal(testCategories)
require.NoError(t, err)
expected := string(b)
var caught bool
func() {
for {
select {
case ev := <-userWSClient.EventChannel:
if ev.EventType() == model.WebsocketEventSidebarCategoryUpdated {
caught = true
data := ev.GetData()
updatedCategoriesData, ok := data["updatedCategories"]
require.True(t, ok)
require.EqualValues(t, expected, updatedCategoriesData)
}
case <-time.After(2 * time.Second):
return
}
}
}()
require.Truef(t, caught, "User should have received %s event", model.WebsocketEventSidebarCategoryUpdated)
})
}
func TestUpdateCategoryForTeamForUser(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
t.Run("should update the channel order of the Channels category", func(t *testing.T) {
user, client := setupUserForSubtest(t, th)
categories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.Len(t, categories.Categories, 3)
require.Len(t, categories.Order, 3)
channelsCategory := categories.Categories[1]
require.Equal(t, model.SidebarCategoryChannels, channelsCategory.Type)
require.Len(t, channelsCategory.Channels, 5) // Town Square, Off Topic, and the 3 channels created by InitBasic
// Should return the correct values from the API
updatedCategory := &model.SidebarCategoryWithChannels{
SidebarCategory: channelsCategory.SidebarCategory,
Channels: []string{channelsCategory.Channels[1], channelsCategory.Channels[0], channelsCategory.Channels[4], channelsCategory.Channels[3], channelsCategory.Channels[2]},
}
received, _, err := client.UpdateSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, channelsCategory.Id, updatedCategory)
assert.NoError(t, err)
assert.Equal(t, channelsCategory.Id, received.Id)
assert.Equal(t, updatedCategory.Channels, received.Channels)
// And when requesting the category later
received, _, err = client.GetSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, channelsCategory.Id, "")
assert.NoError(t, err)
assert.Equal(t, channelsCategory.Id, received.Id)
assert.Equal(t, updatedCategory.Channels, received.Channels)
})
t.Run("should update the sort order of the DM category", func(t *testing.T) {
user, client := setupUserForSubtest(t, th)
categories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.Len(t, categories.Categories, 3)
require.Len(t, categories.Order, 3)
dmsCategory := categories.Categories[2]
require.Equal(t, model.SidebarCategoryDirectMessages, dmsCategory.Type)
require.Equal(t, model.SidebarCategorySortRecent, dmsCategory.Sorting)
// Should return the correct values from the API
updatedCategory := &model.SidebarCategoryWithChannels{
SidebarCategory: dmsCategory.SidebarCategory,
Channels: dmsCategory.Channels,
}
updatedCategory.Sorting = model.SidebarCategorySortAlphabetical
received, _, err := client.UpdateSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, dmsCategory.Id, updatedCategory)
assert.NoError(t, err)
assert.Equal(t, dmsCategory.Id, received.Id)
assert.Equal(t, model.SidebarCategorySortAlphabetical, received.Sorting)
// And when requesting the category later
received, _, err = client.GetSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, dmsCategory.Id, "")
assert.NoError(t, err)
assert.Equal(t, dmsCategory.Id, received.Id)
assert.Equal(t, model.SidebarCategorySortAlphabetical, received.Sorting)
})
t.Run("should update the display name of a custom category", func(t *testing.T) {
user, client := setupUserForSubtest(t, th)
customCategory, _, err := client.CreateSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, &model.SidebarCategoryWithChannels{
SidebarCategory: model.SidebarCategory{
UserId: user.Id,
TeamId: th.BasicTeam.Id,
DisplayName: "custom123",
},
})
require.NoError(t, err)
require.Equal(t, "custom123", customCategory.DisplayName)
// Should return the correct values from the API
updatedCategory := &model.SidebarCategoryWithChannels{
SidebarCategory: customCategory.SidebarCategory,
Channels: customCategory.Channels,
}
updatedCategory.DisplayName = "abcCustom"
received, _, err := client.UpdateSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, customCategory.Id, updatedCategory)
assert.NoError(t, err)
assert.Equal(t, customCategory.Id, received.Id)
assert.Equal(t, updatedCategory.DisplayName, received.DisplayName)
// And when requesting the category later
received, _, err = client.GetSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, customCategory.Id, "")
assert.NoError(t, err)
assert.Equal(t, customCategory.Id, received.Id)
assert.Equal(t, updatedCategory.DisplayName, received.DisplayName)
})
t.Run("should update the channel order of the category even if it contains archived channels", func(t *testing.T) {
user, client := setupUserForSubtest(t, th)
categories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.Len(t, categories.Categories, 3)
require.Len(t, categories.Order, 3)
channelsCategory := categories.Categories[1]
require.Equal(t, model.SidebarCategoryChannels, channelsCategory.Type)
require.Len(t, channelsCategory.Channels, 5) // Town Square, Off Topic, and the 3 channels created by InitBasic
// Delete one of the channels
_, err = client.DeleteChannel(context.Background(), th.BasicChannel.Id)
require.NoError(t, err)
// Should still be able to reorder the channels
updatedCategory := &model.SidebarCategoryWithChannels{
SidebarCategory: channelsCategory.SidebarCategory,
Channels: []string{channelsCategory.Channels[1], channelsCategory.Channels[0], channelsCategory.Channels[4], channelsCategory.Channels[3], channelsCategory.Channels[2]},
}
received, _, err := client.UpdateSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, channelsCategory.Id, updatedCategory)
require.NoError(t, err)
assert.Equal(t, channelsCategory.Id, received.Id)
assert.Equal(t, updatedCategory.Channels, received.Channels)
})
t.Run("should silently prevent the user from adding an invalid channel ID", func(t *testing.T) {
user, client := setupUserForSubtest(t, th)
categories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.Len(t, categories.Categories, 3)
require.Len(t, categories.Order, 3)
channelsCategory := categories.Categories[1]
require.Equal(t, model.SidebarCategoryChannels, channelsCategory.Type)
updatedCategory := &model.SidebarCategoryWithChannels{
SidebarCategory: channelsCategory.SidebarCategory,
Channels: append(channelsCategory.Channels, "notachannel"),
}
received, _, err := client.UpdateSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, channelsCategory.Id, updatedCategory)
require.NoError(t, err)
assert.Equal(t, channelsCategory.Id, received.Id)
assert.NotContains(t, received.Channels, "notachannel")
assert.Equal(t, channelsCategory.Channels, received.Channels)
})
t.Run("should silently prevent the user from adding a channel that they're not a member of", func(t *testing.T) {
user, client := setupUserForSubtest(t, th)
categories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.Len(t, categories.Categories, 3)
require.Len(t, categories.Order, 3)
channelsCategory := categories.Categories[1]
require.Equal(t, model.SidebarCategoryChannels, channelsCategory.Type)
// Have another user create a channel that user isn't a part of
channel, _, err := th.SystemAdminClient.CreateChannel(context.Background(), &model.Channel{
TeamId: th.BasicTeam.Id,
Type: model.ChannelTypeOpen,
Name: "testchannel",
DisplayName: "testchannel",
})
require.NoError(t, err)
// Attempt to update the category
updatedCategory := &model.SidebarCategoryWithChannels{
SidebarCategory: channelsCategory.SidebarCategory,
Channels: append(channelsCategory.Channels, channel.Id),
}
received, _, err := client.UpdateSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, channelsCategory.Id, updatedCategory)
require.NoError(t, err)
assert.Equal(t, channelsCategory.Id, received.Id)
assert.NotContains(t, received.Channels, channel.Id)
assert.Equal(t, channelsCategory.Channels, received.Channels)
})
t.Run("muting a category should mute all of its channels", func(t *testing.T) {
user, client := setupUserForSubtest(t, th)
categories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.Len(t, categories.Categories, 3)
require.Len(t, categories.Order, 3)
channelsCategory := categories.Categories[1]
require.Equal(t, model.SidebarCategoryChannels, channelsCategory.Type)
require.True(t, len(channelsCategory.Channels) > 0)
// Mute the category
updatedCategory := &model.SidebarCategoryWithChannels{
SidebarCategory: model.SidebarCategory{
Id: channelsCategory.Id,
UserId: user.Id,
TeamId: th.BasicTeam.Id,
Sorting: channelsCategory.Sorting,
Muted: true,
},
Channels: channelsCategory.Channels,
}
received, _, err := client.UpdateSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, channelsCategory.Id, updatedCategory)
require.NoError(t, err)
assert.Equal(t, channelsCategory.Id, received.Id)
assert.True(t, received.Muted)
// Check that the muted category was saved in the database
received, _, err = client.GetSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, channelsCategory.Id, "")
require.NoError(t, err)
assert.Equal(t, channelsCategory.Id, received.Id)
assert.True(t, received.Muted)
// Confirm that the channels in the category were muted
member, _, err := client.GetChannelMember(context.Background(), channelsCategory.Channels[0], user.Id, "")
require.NoError(t, err)
assert.True(t, member.IsChannelMuted())
})
t.Run("should not be able to mute DM category", func(t *testing.T) {
user, client := setupUserForSubtest(t, th)
categories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.Len(t, categories.Categories, 3)
require.Len(t, categories.Order, 3)
dmsCategory := categories.Categories[2]
require.Equal(t, model.SidebarCategoryDirectMessages, dmsCategory.Type)
require.Len(t, dmsCategory.Channels, 0)
// Ensure a DM channel exists
dmChannel, _, err := client.CreateDirectChannel(context.Background(), user.Id, th.BasicUser.Id)
require.NoError(t, err)
// Attempt to mute the category
updatedCategory := &model.SidebarCategoryWithChannels{
SidebarCategory: model.SidebarCategory{
Id: dmsCategory.Id,
UserId: user.Id,
TeamId: th.BasicTeam.Id,
Sorting: dmsCategory.Sorting,
Muted: true,
},
Channels: []string{dmChannel.Id},
}
received, _, err := client.UpdateSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, dmsCategory.Id, updatedCategory)
require.NoError(t, err)
assert.Equal(t, dmsCategory.Id, received.Id)
assert.False(t, received.Muted)
// Check that the muted category was not saved in the database
received, _, err = client.GetSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, dmsCategory.Id, "")
require.NoError(t, err)
assert.Equal(t, dmsCategory.Id, received.Id)
assert.False(t, received.Muted)
// Confirm that the channels in the category were not muted
member, _, err := client.GetChannelMember(context.Background(), dmChannel.Id, user.Id, "")
require.NoError(t, err)
assert.False(t, member.IsChannelMuted())
})
t.Run("should not crash with null input", func(t *testing.T) {
require.NotPanics(t, func() {
user, client := setupUserForSubtest(t, th)
categories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.Len(t, categories.Categories, 3)
require.Len(t, categories.Order, 3)
dmsCategory := categories.Categories[2]
payload := `null`
route := fmt.Sprintf("/users/%s/teams/%s/channels/categories/%s", user.Id, th.BasicTeam.Id, dmsCategory.Id)
r, err := client.DoAPIPut(context.Background(), route, payload)
require.Error(t, err)
closeBody(r)
})
})
t.Run("should return error when user tries to update a category for a team they're not a member of", func(t *testing.T) {
// Create a user
password := model.NewTestPassword()
user, appErr := th.App.CreateUser(th.Context, &model.User{
Email: th.GenerateTestEmail(),
Username: "user_" + model.NewId(),
Password: password,
})
require.Nil(t, appErr)
// Create a team and add the user to it
team, appErr := th.App.CreateTeam(th.Context, &model.Team{
DisplayName: "Team for testing",
Name: "test-team-" + model.NewId(),
Email: th.GenerateTestEmail(),
Type: model.TeamOpen,
})
require.Nil(t, appErr)
th.LinkUserToTeam(t, user, team)
// Create a client and log in
client := th.CreateClient()
_, _, err := client.Login(context.Background(), user.Email, password)
require.NoError(t, err)
// Get categories to have valid category IDs
categories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, team.Id, "")
require.NoError(t, err)
require.NotEmpty(t, categories.Categories)
// Store a category to use after team membership is revoked
categoryToUpdate := &model.SidebarCategoryWithChannels{
SidebarCategory: model.SidebarCategory{
Id: categories.Categories[0].Id,
UserId: user.Id,
TeamId: team.Id,
DisplayName: "Updated Category",
Type: categories.Categories[0].Type,
},
Channels: categories.Categories[0].Channels,
}
// Remove the user from the team
appErr = th.App.RemoveUserFromTeam(th.Context, team.Id, user.Id, th.SystemAdminUser.Id)
require.Nil(t, appErr)
// Attempt to update a category for the team after being removed
_, resp, err := client.UpdateSidebarCategoryForTeamForUser(context.Background(), user.Id, team.Id, categoryToUpdate.Id, categoryToUpdate)
require.Error(t, err)
require.Equal(t, http.StatusForbidden, resp.StatusCode)
})
}
func TestUpdateCategoriesForTeamForUser(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
t.Run("should silently prevent the user from adding an invalid channel ID", func(t *testing.T) {
user, client := setupUserForSubtest(t, th)
categories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.Len(t, categories.Categories, 3)
require.Len(t, categories.Order, 3)
channelsCategory := categories.Categories[1]
require.Equal(t, model.SidebarCategoryChannels, channelsCategory.Type)
updatedCategory := &model.SidebarCategoryWithChannels{
SidebarCategory: channelsCategory.SidebarCategory,
Channels: append(channelsCategory.Channels, "notachannel"),
}
received, _, err := client.UpdateSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, []*model.SidebarCategoryWithChannels{updatedCategory})
require.NoError(t, err)
assert.Equal(t, channelsCategory.Id, received[0].Id)
assert.NotContains(t, received[0].Channels, "notachannel")
assert.Equal(t, channelsCategory.Channels, received[0].Channels)
})
t.Run("should silently prevent the user from adding a channel that they're not a member of", func(t *testing.T) {
user, client := setupUserForSubtest(t, th)
categories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.Len(t, categories.Categories, 3)
require.Len(t, categories.Order, 3)
channelsCategory := categories.Categories[1]
require.Equal(t, model.SidebarCategoryChannels, channelsCategory.Type)
// Have another user create a channel that user isn't a part of
channel, _, err := th.SystemAdminClient.CreateChannel(context.Background(), &model.Channel{
TeamId: th.BasicTeam.Id,
Type: model.ChannelTypeOpen,
Name: "testchannel",
DisplayName: "testchannel",
})
require.NoError(t, err)
// Attempt to update the category
updatedCategory := &model.SidebarCategoryWithChannels{
SidebarCategory: channelsCategory.SidebarCategory,
Channels: append(channelsCategory.Channels, channel.Id),
}
received, _, err := client.UpdateSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, []*model.SidebarCategoryWithChannels{updatedCategory})
require.NoError(t, err)
assert.Equal(t, channelsCategory.Id, received[0].Id)
assert.NotContains(t, received[0].Channels, channel.Id)
assert.Equal(t, channelsCategory.Channels, received[0].Channels)
})
t.Run("should update order", func(t *testing.T) {
user, client := setupUserForSubtest(t, th)
categories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.Len(t, categories.Categories, 3)
require.Len(t, categories.Order, 3)
channelsCategory := categories.Categories[1]
require.Equal(t, model.SidebarCategoryChannels, channelsCategory.Type)
_, _, err = client.UpdateSidebarCategoryOrderForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, []string{categories.Order[1], categories.Order[0], categories.Order[2]})
require.NoError(t, err)
categories, _, err = client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.Len(t, categories.Categories, 3)
require.Len(t, categories.Order, 3)
channelsCategory = categories.Categories[0]
require.Equal(t, model.SidebarCategoryChannels, channelsCategory.Type)
// validate order
newOrder, _, err := client.GetSidebarCategoryOrderForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.EqualValues(t, newOrder, categories.Order)
// try to update with missing category
_, _, err = client.UpdateSidebarCategoryOrderForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, []string{categories.Order[1], categories.Order[0]})
require.Error(t, err)
// try to update with invalid category
_, _, err = client.UpdateSidebarCategoryOrderForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, []string{categories.Order[1], categories.Order[0], "asd"})
require.Error(t, err)
})
t.Run("should return error when user tries to update categories for a team they're not a member of", func(t *testing.T) {
// Create a user
password := model.NewTestPassword()
user, appErr := th.App.CreateUser(th.Context, &model.User{
Email: th.GenerateTestEmail(),
Username: "user_" + model.NewId(),
Password: password,
})
require.Nil(t, appErr)
// Create a team and add the user to it
team, appErr := th.App.CreateTeam(th.Context, &model.Team{
DisplayName: "Team for testing",
Name: "test-team-" + model.NewId(),
Email: th.GenerateTestEmail(),
Type: model.TeamOpen,
})
require.Nil(t, appErr)
th.LinkUserToTeam(t, user, team)
// Create a client and log in
client := th.CreateClient()
_, _, err := client.Login(context.Background(), user.Email, password)
require.NoError(t, err)
// Get categories to have valid category IDs
existingCategories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, team.Id, "")
require.NoError(t, err)
require.NotEmpty(t, existingCategories.Categories)
// Prepare categories to update after team membership is revoked
categoriesToUpdate := []*model.SidebarCategoryWithChannels{
{
SidebarCategory: model.SidebarCategory{
Id: existingCategories.Categories[0].Id,
UserId: user.Id,
TeamId: team.Id,
DisplayName: "Updated Category",
Type: existingCategories.Categories[0].Type,
},
Channels: existingCategories.Categories[0].Channels,
},
}
// Remove the user from the team
appErr = th.App.RemoveUserFromTeam(th.Context, team.Id, user.Id, th.SystemAdminUser.Id)
require.Nil(t, appErr)
// Attempt to update categories for the team after being removed
_, resp, err := client.UpdateSidebarCategoriesForTeamForUser(context.Background(), user.Id, team.Id, categoriesToUpdate)
require.Error(t, err)
require.Equal(t, http.StatusForbidden, resp.StatusCode)
})
}
func TestGetCategoriesForTeamForUser(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
t.Run("should return categories when user has permission", func(t *testing.T) {
// Get categories for the basic user
categories, _, err := th.Client.GetSidebarCategoriesForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.NotNil(t, categories)
require.Len(t, categories.Categories, 3) // Default categories: Channels, Favorites, Direct Messages
require.Len(t, categories.Order, 3)
})
t.Run("should return error when user doesn't have permission", func(t *testing.T) {
// Create a new user that's not on the team
password := model.NewTestPassword()
user, appErr := th.App.CreateUser(th.Context, &model.User{
Email: th.GenerateTestEmail(),
Username: "user_" + model.NewId(),
Password: password,
})
require.Nil(t, appErr)
client := th.CreateClient()
_, _, err := client.Login(context.Background(), user.Email, password)
require.NoError(t, err)
// Attempt to get categories for the basic user
_, resp, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "")
require.Error(t, err)
require.Equal(t, http.StatusForbidden, resp.StatusCode)
})
t.Run("should return error for a team the user is not a member of", func(t *testing.T) {
// Create a new user and team
password := model.NewTestPassword()
user, appErr := th.App.CreateUser(th.Context, &model.User{
Email: th.GenerateTestEmail(),
Username: "user_" + model.NewId(),
Password: password,
})
require.Nil(t, appErr)
team, appErr := th.App.CreateTeam(th.Context, &model.Team{
DisplayName: "Team for testing",
Name: "test-team-" + model.NewId(),
Email: th.GenerateTestEmail(),
Type: model.TeamOpen,
})
require.Nil(t, appErr)
// Log in as the new user
client := th.CreateClient()
_, _, err := client.Login(context.Background(), user.Email, password)
require.NoError(t, err)
// Attempt to get categories for a team the user is not a member of
_, resp, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, team.Id, "")
require.Error(t, err)
require.Equal(t, http.StatusForbidden, resp.StatusCode)
})
t.Run("should return error with invalid user id", func(t *testing.T) {
_, resp, err := th.Client.GetSidebarCategoriesForTeamForUser(context.Background(), "invalid_user_id", th.BasicTeam.Id, "")
require.Error(t, err)
require.Equal(t, http.StatusNotFound, resp.StatusCode)
})
t.Run("should return error with invalid team id", func(t *testing.T) {
_, resp, err := th.Client.GetSidebarCategoriesForTeamForUser(context.Background(), th.BasicUser.Id, "invalid_team_id", "")
require.Error(t, err)
require.Equal(t, http.StatusNotFound, resp.StatusCode)
})
t.Run("should return error when user is not logged in", func(t *testing.T) {
client := th.CreateClient()
_, resp, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "")
require.Error(t, err)
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
})
}
func TestGetCategoryOrderForTeamForUser(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
t.Run("should return category order when user has permission", func(t *testing.T) {
// Get categories first to ensure order exists
categories, _, err := th.Client.GetSidebarCategoriesForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.NotNil(t, categories)
require.Len(t, categories.Order, 3) // Default categories: Channels, Favorites, Direct Messages
// Get order
order, _, err := th.Client.GetSidebarCategoryOrderForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.NotNil(t, order)
require.Len(t, order, 3)
require.ElementsMatch(t, categories.Order, order)
})
t.Run("should return error when user doesn't have permission", func(t *testing.T) {
// Create a new user that's not on the team
password := model.NewTestPassword()
user, appErr := th.App.CreateUser(th.Context, &model.User{
Email: th.GenerateTestEmail(),
Username: "user_" + model.NewId(),
Password: password,
})
require.Nil(t, appErr)
client := th.CreateClient()
_, _, err := client.Login(context.Background(), user.Email, password)
require.NoError(t, err)
// Attempt to get order for the basic user
_, resp, err := client.GetSidebarCategoryOrderForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "")
require.Error(t, err)
require.Equal(t, http.StatusForbidden, resp.StatusCode)
})
t.Run("should return error with invalid user id", func(t *testing.T) {
_, resp, err := th.Client.GetSidebarCategoryOrderForTeamForUser(context.Background(), "invalid_user_id", th.BasicTeam.Id, "")
require.Error(t, err)
require.Equal(t, http.StatusNotFound, resp.StatusCode)
})
t.Run("should return error with invalid team id", func(t *testing.T) {
_, resp, err := th.Client.GetSidebarCategoryOrderForTeamForUser(context.Background(), th.BasicUser.Id, "invalid_team_id", "")
require.Error(t, err)
require.Equal(t, http.StatusNotFound, resp.StatusCode)
})
t.Run("should return error when user is not logged in", func(t *testing.T) {
client := th.CreateClient()
_, resp, err := client.GetSidebarCategoryOrderForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "")
require.Error(t, err)
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
})
t.Run("should return error when user tries to get category order for a team they're not a member of", func(t *testing.T) {
// Create a user
password := model.NewTestPassword()
user, appErr := th.App.CreateUser(th.Context, &model.User{
Email: th.GenerateTestEmail(),
Username: "user_" + model.NewId(),
Password: password,
})
require.Nil(t, appErr)
// Create a team and add the user to it
team, appErr := th.App.CreateTeam(th.Context, &model.Team{
DisplayName: "Team for testing",
Name: "test-team-" + model.NewId(),
Email: th.GenerateTestEmail(),
Type: model.TeamOpen,
})
require.Nil(t, appErr)
th.LinkUserToTeam(t, user, team)
// Create a client and log in
client := th.CreateClient()
_, _, err := client.Login(context.Background(), user.Email, password)
require.NoError(t, err)
// Verify the user can access categories initially
_, _, err = client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, team.Id, "")
require.NoError(t, err)
// Remove the user from the team
appErr = th.App.RemoveUserFromTeam(th.Context, team.Id, user.Id, th.SystemAdminUser.Id)
require.Nil(t, appErr)
// Attempt to get the category order for the team after being removed
_, resp, err := client.GetSidebarCategoryOrderForTeamForUser(context.Background(), user.Id, team.Id, "")
require.Error(t, err)
require.Equal(t, http.StatusForbidden, resp.StatusCode)
})
}
func TestUpdateCategoryOrderForTeamForUser(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
t.Run("should update order", func(t *testing.T) {
user, client := setupUserForSubtest(t, th)
categories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.Len(t, categories.Categories, 3)
require.Len(t, categories.Order, 3)
channelsCategory := categories.Categories[1]
require.Equal(t, model.SidebarCategoryChannels, channelsCategory.Type)
// Update order
newOrder := []string{categories.Order[1], categories.Order[0], categories.Order[2]}
updatedOrder, _, err := client.UpdateSidebarCategoryOrderForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, newOrder)
require.NoError(t, err)
require.EqualValues(t, newOrder, updatedOrder)
// Verify order was updated
categories, _, err = client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.Len(t, categories.Categories, 3)
require.Len(t, categories.Order, 3)
require.EqualValues(t, newOrder, categories.Order)
})
t.Run("should return error when user doesn't have permission", func(t *testing.T) {
// Create a new user that's not on the team
password := model.NewTestPassword()
user, appErr := th.App.CreateUser(th.Context, &model.User{
Email: th.GenerateTestEmail(),
Username: "user_" + model.NewId(),
Password: password,
})
require.Nil(t, appErr)
client := th.CreateClient()
_, _, err := client.Login(context.Background(), user.Email, password)
require.NoError(t, err)
// Get categories for basic user to try to update
categories, _, err := th.Client.GetSidebarCategoriesForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
// Attempt to update order for the basic user
newOrder := []string{categories.Order[1], categories.Order[0], categories.Order[2]}
_, resp, err := client.UpdateSidebarCategoryOrderForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, newOrder)
require.Error(t, err)
require.Equal(t, http.StatusForbidden, resp.StatusCode)
})
t.Run("should return error with invalid category id", func(t *testing.T) {
categories, _, err := th.Client.GetSidebarCategoriesForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
// Try to update with an invalid category ID
newOrder := []string{categories.Order[0], "invalid_category_id", categories.Order[2]}
_, resp, err := th.Client.UpdateSidebarCategoryOrderForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, newOrder)
require.Error(t, err)
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
})
t.Run("should return error with missing category", func(t *testing.T) {
categories, _, err := th.Client.GetSidebarCategoriesForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
// Try to update with a missing category
newOrder := []string{categories.Order[1], categories.Order[0]} // Missing one category
_, resp, err := th.Client.UpdateSidebarCategoryOrderForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, newOrder)
require.Error(t, err)
require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
})
t.Run("should return error with invalid user id", func(t *testing.T) {
categories, _, err := th.Client.GetSidebarCategoriesForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
newOrder := []string{categories.Order[1], categories.Order[0], categories.Order[2]}
_, resp, err := th.Client.UpdateSidebarCategoryOrderForTeamForUser(context.Background(), "invalid_user_id", th.BasicTeam.Id, newOrder)
require.Error(t, err)
require.Equal(t, http.StatusNotFound, resp.StatusCode)
})
t.Run("should return error with invalid team id", func(t *testing.T) {
categories, _, err := th.Client.GetSidebarCategoriesForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
newOrder := []string{categories.Order[1], categories.Order[0], categories.Order[2]}
_, resp, err := th.Client.UpdateSidebarCategoryOrderForTeamForUser(context.Background(), th.BasicUser.Id, "invalid_team_id", newOrder)
require.Error(t, err)
require.Equal(t, http.StatusNotFound, resp.StatusCode)
})
t.Run("should return error when user is not logged in", func(t *testing.T) {
categories, _, err := th.Client.GetSidebarCategoriesForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
client := th.CreateClient()
newOrder := []string{categories.Order[1], categories.Order[0], categories.Order[2]}
_, resp, err := client.UpdateSidebarCategoryOrderForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, newOrder)
require.Error(t, err)
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
})
t.Run("should not crash with null input", func(t *testing.T) {
require.NotPanics(t, func() {
user, client := setupUserForSubtest(t, th)
payload := `null`
route := fmt.Sprintf("/users/%s/teams/%s/channels/categories/order", user.Id, th.BasicTeam.Id)
r, err := client.DoAPIPut(context.Background(), route, payload)
require.Error(t, err)
closeBody(r)
})
})
t.Run("should return error when user tries to update category order for a team they're not a member of", func(t *testing.T) {
// Create a user
password := model.NewTestPassword()
user, appErr := th.App.CreateUser(th.Context, &model.User{
Email: th.GenerateTestEmail(),
Username: "user_" + model.NewId(),
Password: password,
})
require.Nil(t, appErr)
// Create a team and add the user to it
team, appErr := th.App.CreateTeam(th.Context, &model.Team{
DisplayName: "Team for testing",
Name: "test-team-" + model.NewId(),
Email: th.GenerateTestEmail(),
Type: model.TeamOpen,
})
require.Nil(t, appErr)
th.LinkUserToTeam(t, user, team)
// Create a client and log in
client := th.CreateClient()
_, _, err := client.Login(context.Background(), user.Email, password)
require.NoError(t, err)
// Get categories to have a valid order
categories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, team.Id, "")
require.NoError(t, err)
require.NotEmpty(t, categories.Order)
// Remove the user from the team
appErr = th.App.RemoveUserFromTeam(th.Context, team.Id, user.Id, th.SystemAdminUser.Id)
require.Nil(t, appErr)
// Attempt to update the category order for the team after being removed
_, resp, err := client.UpdateSidebarCategoryOrderForTeamForUser(context.Background(), user.Id, team.Id, categories.Order)
require.Error(t, err)
require.Equal(t, http.StatusForbidden, resp.StatusCode)
})
}
func TestGetCategoryForTeamForUser(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
t.Run("should return category when user has permission", func(t *testing.T) {
user, client := setupUserForSubtest(t, th)
// Get categories first to get a valid category ID
categories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.NotEmpty(t, categories.Categories)
// Get specific category
category := categories.Categories[0]
received, _, err := client.GetSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, category.Id, "")
require.NoError(t, err)
require.NotNil(t, received)
require.Equal(t, category.Id, received.Id)
require.Equal(t, category.DisplayName, received.DisplayName)
require.Equal(t, category.Type, received.Type)
})
t.Run("should return error when user doesn't have permission", func(t *testing.T) {
// Create a new user that's not on the team
password := model.NewTestPassword()
user, appErr := th.App.CreateUser(th.Context, &model.User{
Email: th.GenerateTestEmail(),
Username: "user_" + model.NewId(),
Password: password,
})
require.Nil(t, appErr)
client := th.CreateClient()
_, _, err := client.Login(context.Background(), user.Email, password)
require.NoError(t, err)
// Get categories for basic user to get a valid category ID
categories, _, err := th.Client.GetSidebarCategoriesForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.NotEmpty(t, categories.Categories)
// Attempt to get category for the basic user
_, resp, err := client.GetSidebarCategoryForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, categories.Categories[0].Id, "")
require.Error(t, err)
require.Equal(t, http.StatusForbidden, resp.StatusCode)
})
t.Run("should return error with invalid user id", func(t *testing.T) {
categories, _, err := th.Client.GetSidebarCategoriesForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.NotEmpty(t, categories.Categories)
_, resp, err := th.Client.GetSidebarCategoryForTeamForUser(context.Background(), "invalid_user_id", th.BasicTeam.Id, categories.Categories[0].Id, "")
require.Error(t, err)
require.Equal(t, http.StatusNotFound, resp.StatusCode)
})
t.Run("should return error with invalid team id", func(t *testing.T) {
categories, _, err := th.Client.GetSidebarCategoriesForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.NotEmpty(t, categories.Categories)
_, resp, err := th.Client.GetSidebarCategoryForTeamForUser(context.Background(), th.BasicUser.Id, "invalid_team_id", categories.Categories[0].Id, "")
require.Error(t, err)
require.Equal(t, http.StatusNotFound, resp.StatusCode)
})
t.Run("should return error with invalid category id", func(t *testing.T) {
_, resp, err := th.Client.GetSidebarCategoryForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "invalid_category_id", "")
require.Error(t, err)
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
})
t.Run("should return error when user is not logged in", func(t *testing.T) {
categories, _, err := th.Client.GetSidebarCategoriesForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.NotEmpty(t, categories.Categories)
client := th.CreateClient()
_, resp, err := client.GetSidebarCategoryForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, categories.Categories[0].Id, "")
require.Error(t, err)
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
})
t.Run("should not crash with null input", func(t *testing.T) {
require.NotPanics(t, func() {
user, client := setupUserForSubtest(t, th)
categories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.NotEmpty(t, categories.Categories)
route := fmt.Sprintf("/users/%s/teams/%s/channels/categories/%s", user.Id, th.BasicTeam.Id, categories.Categories[0].Id)
r, err := client.DoAPIGet(context.Background(), route, "")
require.NoError(t, err)
closeBody(r)
})
})
t.Run("should return error when user tries to get category for a team they're not a member of", func(t *testing.T) {
// Create a user
password := model.NewTestPassword()
user, appErr := th.App.CreateUser(th.Context, &model.User{
Email: th.GenerateTestEmail(),
Username: "user_" + model.NewId(),
Password: password,
})
require.Nil(t, appErr)
// Create a team and add the user to it
team, appErr := th.App.CreateTeam(th.Context, &model.Team{
DisplayName: "Team for testing",
Name: "test-team-" + model.NewId(),
Email: th.GenerateTestEmail(),
Type: model.TeamOpen,
})
require.Nil(t, appErr)
th.LinkUserToTeam(t, user, team)
// Create a client and log in
client := th.CreateClient()
_, _, err := client.Login(context.Background(), user.Email, password)
require.NoError(t, err)
// Get categories to have valid category IDs
categories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, team.Id, "")
require.NoError(t, err)
require.NotEmpty(t, categories.Categories)
// Store a category ID to use after team membership is revoked
categoryID := categories.Categories[0].Id
// Remove the user from the team
appErr = th.App.RemoveUserFromTeam(th.Context, team.Id, user.Id, th.SystemAdminUser.Id)
require.Nil(t, appErr)
// Attempt to get a category for the team after being removed
_, resp, err := client.GetSidebarCategoryForTeamForUser(context.Background(), user.Id, team.Id, categoryID, "")
require.Error(t, err)
require.Equal(t, http.StatusForbidden, resp.StatusCode)
})
}
func TestValidateSidebarCategory(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
// Create a test context with logger once for all subtests
c := &Context{
App: th.App,
AppContext: th.Context,
Logger: th.App.Log(),
}
t.Run("should validate category with valid channels", func(t *testing.T) {
user, _ := setupUserForSubtest(t, th)
// Create a category with channels the user is a member of
category := &model.SidebarCategoryWithChannels{
SidebarCategory: model.SidebarCategory{
UserId: user.Id,
TeamId: th.BasicTeam.Id,
},
Channels: []string{th.BasicChannel.Id, th.BasicChannel2.Id},
}
err := validateSidebarCategory(c, th.BasicTeam.Id, user.Id, category)
require.Nil(t, err)
require.Len(t, category.Channels, 2)
require.Contains(t, category.Channels, th.BasicChannel.Id)
require.Contains(t, category.Channels, th.BasicChannel2.Id)
})
t.Run("should filter out invalid channel IDs", func(t *testing.T) {
user, _ := setupUserForSubtest(t, th)
category := &model.SidebarCategoryWithChannels{
SidebarCategory: model.SidebarCategory{
UserId: user.Id,
TeamId: th.BasicTeam.Id,
},
Channels: []string{th.BasicChannel.Id, "invalid_channel_id"},
}
err := validateSidebarCategory(c, th.BasicTeam.Id, user.Id, category)
require.Nil(t, err)
require.Len(t, category.Channels, 1)
require.Contains(t, category.Channels, th.BasicChannel.Id)
require.NotContains(t, category.Channels, "invalid_channel_id")
})
t.Run("should filter out channels user is not a member of", func(t *testing.T) {
user, _ := setupUserForSubtest(t, th)
// Create a channel that the user is not a member of
channel, appErr := th.App.CreateChannel(th.Context, &model.Channel{
TeamId: th.BasicTeam.Id,
Type: model.ChannelTypeOpen,
Name: "testchannel",
DisplayName: "Test Channel",
}, false)
require.Nil(t, appErr)
category := &model.SidebarCategoryWithChannels{
SidebarCategory: model.SidebarCategory{
UserId: user.Id,
TeamId: th.BasicTeam.Id,
},
Channels: []string{th.BasicChannel.Id, channel.Id},
}
err := validateSidebarCategory(c, th.BasicTeam.Id, user.Id, category)
require.Nil(t, err)
require.Len(t, category.Channels, 1)
require.Contains(t, category.Channels, th.BasicChannel.Id)
require.NotContains(t, category.Channels, channel.Id)
})
t.Run("should return error with invalid team id", func(t *testing.T) {
user, _ := setupUserForSubtest(t, th)
category := &model.SidebarCategoryWithChannels{
SidebarCategory: model.SidebarCategory{
UserId: user.Id,
TeamId: "invalid_team_id",
},
Channels: []string{th.BasicChannel.Id},
}
err := validateSidebarCategory(c, "invalid_team_id", user.Id, category)
require.NotNil(t, err)
require.Equal(t, http.StatusBadRequest, err.StatusCode)
})
t.Run("should return error with invalid user id", func(t *testing.T) {
category := &model.SidebarCategoryWithChannels{
SidebarCategory: model.SidebarCategory{
UserId: "invalid_user_id",
TeamId: th.BasicTeam.Id,
},
Channels: []string{th.BasicChannel.Id},
}
err := validateSidebarCategory(c, th.BasicTeam.Id, "invalid_user_id", category)
require.NotNil(t, err)
require.Equal(t, http.StatusBadRequest, err.StatusCode)
})
t.Run("should handle empty channel list", func(t *testing.T) {
user, _ := setupUserForSubtest(t, th)
category := &model.SidebarCategoryWithChannels{
SidebarCategory: model.SidebarCategory{
UserId: user.Id,
TeamId: th.BasicTeam.Id,
},
Channels: []string{},
}
err := validateSidebarCategory(c, th.BasicTeam.Id, user.Id, category)
require.Nil(t, err)
require.Empty(t, category.Channels)
})
}
func TestValidateSidebarCategoryChannels(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
// Create a test context with logger once for all subtests
c := &Context{
App: th.App,
AppContext: th.Context,
Logger: th.App.Log(),
}
t.Run("should filter valid channels", func(t *testing.T) {
// Create test channelMap
channelMap := channelListToMap(model.ChannelList{
th.BasicChannel,
th.BasicChannel2,
})
// Test with valid channel IDs
channelIds := []string{
th.BasicChannel.Id,
th.BasicChannel2.Id,
}
filtered := validateSidebarCategoryChannels(c, th.BasicUser.Id, channelIds, channelMap)
require.Len(t, filtered, 2)
require.ElementsMatch(t, channelIds, filtered)
})
t.Run("should filter out invalid channels", func(t *testing.T) {
channelMap := channelListToMap(model.ChannelList{
th.BasicChannel,
})
channelIds := []string{
th.BasicChannel.Id,
"invalid_channel_id",
}
filtered := validateSidebarCategoryChannels(c, th.BasicUser.Id, channelIds, channelMap)
require.Len(t, filtered, 1)
require.Contains(t, filtered, th.BasicChannel.Id)
require.NotContains(t, filtered, "invalid_channel_id")
})
t.Run("should handle empty channel list", func(t *testing.T) {
channelMap := channelListToMap(model.ChannelList{})
channelIds := []string{th.BasicChannel.Id}
filtered := validateSidebarCategoryChannels(c, th.BasicUser.Id, channelIds, channelMap)
require.Empty(t, filtered)
})
t.Run("should handle empty channelIds", func(t *testing.T) {
channelMap := channelListToMap(model.ChannelList{
th.BasicChannel,
th.BasicChannel2,
})
filtered := validateSidebarCategoryChannels(c, th.BasicUser.Id, []string{}, channelMap)
require.Empty(t, filtered)
})
t.Run("should handle nil inputs", func(t *testing.T) {
filtered := validateSidebarCategoryChannels(c, th.BasicUser.Id, nil, nil)
require.Empty(t, filtered)
})
t.Run("should prevent duplicate channel IDs", func(t *testing.T) {
channelMap := channelListToMap(model.ChannelList{
th.BasicChannel,
})
// Include duplicate channel IDs
channelIds := []string{
th.BasicChannel.Id,
th.BasicChannel.Id,
}
filtered := validateSidebarCategoryChannels(c, th.BasicUser.Id, channelIds, channelMap)
require.Len(t, filtered, 1)
require.Equal(t, []string{th.BasicChannel.Id}, filtered)
})
}
func TestDeleteCategoryForTeamForUser(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
t.Run("should move channels to default categories when custom category is deleted", func(t *testing.T) {
user, client := setupUserForSubtest(t, th)
// Create a custom category with different types of channels
customCategory, _, err := client.CreateSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, &model.SidebarCategoryWithChannels{
SidebarCategory: model.SidebarCategory{
UserId: user.Id,
TeamId: th.BasicTeam.Id,
DisplayName: "Custom Category",
Type: model.SidebarCategoryCustom,
},
// Add public, private channels and DM
Channels: []string{th.BasicChannel.Id, th.BasicPrivateChannel.Id},
})
require.NoError(t, err)
require.NotNil(t, customCategory)
// Create a DM channel
dmChannel, _, err := client.CreateDirectChannel(context.Background(), user.Id, th.BasicUser2.Id)
require.NoError(t, err)
// Add DM to custom category
customCategory.Channels = append(customCategory.Channels, dmChannel.Id)
updatedCategory, _, err := client.UpdateSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, customCategory.Id, customCategory)
require.NoError(t, err)
require.ElementsMatch(t, []string{th.BasicChannel.Id, th.BasicPrivateChannel.Id, dmChannel.Id}, updatedCategory.Channels)
// Delete the custom category
resp, err := client.DeleteSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, customCategory.Id)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
// Get all categories to verify channel redistribution
categories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
// Find default categories
var channelsCategory, dmsCategory *model.SidebarCategoryWithChannels
for _, cat := range categories.Categories {
switch cat.Type {
case model.SidebarCategoryChannels:
channelsCategory = cat
case model.SidebarCategoryDirectMessages:
dmsCategory = cat
}
}
require.NotNil(t, channelsCategory, "Channels category should exist")
require.NotNil(t, dmsCategory, "DMs category should exist")
// Verify public and private channels moved to channels category
require.Contains(t, channelsCategory.Channels, th.BasicChannel.Id, "Public channel should be in channels category")
require.Contains(t, channelsCategory.Channels, th.BasicPrivateChannel.Id, "Private channel should be in channels category")
// Verify DM moved to DMs category
require.Contains(t, dmsCategory.Channels, dmChannel.Id, "DM should be in direct messages category")
})
t.Run("should delete category when user has permission", func(t *testing.T) {
user, client := setupUserForSubtest(t, th)
// Create a new custom category to delete (don't delete default categories)
category, _, err := client.CreateSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, &model.SidebarCategoryWithChannels{
SidebarCategory: model.SidebarCategory{
UserId: user.Id,
TeamId: th.BasicTeam.Id,
DisplayName: "Custom Category",
Type: model.SidebarCategoryCustom,
},
})
require.NoError(t, err)
require.NotNil(t, category)
// Delete the category
resp, err := client.DeleteSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, category.Id)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
// Verify category was deleted
categories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
for _, cat := range categories.Categories {
require.NotEqual(t, category.Id, cat.Id)
}
})
t.Run("should return error when user doesn't have permission", func(t *testing.T) {
// Create a new user that's not on the team
password := model.NewTestPassword()
user, appErr := th.App.CreateUser(th.Context, &model.User{
Email: th.GenerateTestEmail(),
Username: "user_" + model.NewId(),
Password: password,
})
require.Nil(t, appErr)
client := th.CreateClient()
_, _, err := client.Login(context.Background(), user.Email, password)
require.NoError(t, err)
// Get categories for basic user to get a valid category ID
categories, _, err := th.Client.GetSidebarCategoriesForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.NotEmpty(t, categories.Categories)
// Attempt to delete category for the basic user
resp, err := client.DeleteSidebarCategoryForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, categories.Categories[0].Id)
require.Error(t, err)
require.Equal(t, http.StatusForbidden, resp.StatusCode)
})
t.Run("should return error with invalid user id", func(t *testing.T) {
// Get a valid category ID first
categories, _, err := th.Client.GetSidebarCategoriesForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.NotEmpty(t, categories.Categories)
resp, err := th.Client.DeleteSidebarCategoryForTeamForUser(context.Background(), "invalid_user_id", th.BasicTeam.Id, categories.Categories[0].Id)
require.Error(t, err)
require.Equal(t, http.StatusNotFound, resp.StatusCode)
})
t.Run("should return error with invalid team id", func(t *testing.T) {
// Get a valid category ID first
categories, _, err := th.Client.GetSidebarCategoriesForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.NotEmpty(t, categories.Categories)
resp, err := th.Client.DeleteSidebarCategoryForTeamForUser(context.Background(), th.BasicUser.Id, "invalid_team_id", categories.Categories[0].Id)
require.Error(t, err)
require.Equal(t, http.StatusNotFound, resp.StatusCode)
})
t.Run("should return error with invalid category id", func(t *testing.T) {
resp, err := th.Client.DeleteSidebarCategoryForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "invalid_category_id")
require.Error(t, err)
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
})
t.Run("should return error when user is not logged in", func(t *testing.T) {
// Get a valid category ID first
categories, _, err := th.Client.GetSidebarCategoriesForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.NotEmpty(t, categories.Categories)
client := th.CreateClient()
resp, err := client.DeleteSidebarCategoryForTeamForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, categories.Categories[0].Id)
require.Error(t, err)
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
})
t.Run("should not allow deletion of default categories", func(t *testing.T) {
user, client := setupUserForSubtest(t, th)
categories, _, err := client.GetSidebarCategoriesForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, "")
require.NoError(t, err)
require.NotEmpty(t, categories.Categories)
// Try to delete each default category
for _, category := range categories.Categories {
if category.Type != model.SidebarCategoryCustom {
resp, err := client.DeleteSidebarCategoryForTeamForUser(context.Background(), user.Id, th.BasicTeam.Id, category.Id)
require.Error(t, err)
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
}
}
})
t.Run("should return error when user tries to delete a category for a team they're not a member of", func(t *testing.T) {
// Create a user
password := model.NewTestPassword()
user, appErr := th.App.CreateUser(th.Context, &model.User{
Email: th.GenerateTestEmail(),
Username: "user_" + model.NewId(),
Password: password,
})
require.Nil(t, appErr)
// Create a team and add the user to it
team, appErr := th.App.CreateTeam(th.Context, &model.Team{
DisplayName: "Team for testing",
Name: "test-team-" + model.NewId(),
Email: th.GenerateTestEmail(),
Type: model.TeamOpen,
})
require.Nil(t, appErr)
th.LinkUserToTeam(t, user, team)
// Create a client and log in
client := th.CreateClient()
_, _, err := client.Login(context.Background(), user.Email, password)
require.NoError(t, err)
// Create a custom category
customCategory, _, err := client.CreateSidebarCategoryForTeamForUser(context.Background(), user.Id, team.Id, &model.SidebarCategoryWithChannels{
SidebarCategory: model.SidebarCategory{
UserId: user.Id,
TeamId: team.Id,
DisplayName: "Custom Category",
Type: model.SidebarCategoryCustom,
},
})
require.NoError(t, err)
require.NotNil(t, customCategory)
// Remove the user from the team
appErr = th.App.RemoveUserFromTeam(th.Context, team.Id, user.Id, th.SystemAdminUser.Id)
require.Nil(t, appErr)
// Attempt to delete the category for the team after being removed
resp, err := client.DeleteSidebarCategoryForTeamForUser(context.Background(), user.Id, team.Id, customCategory.Id)
require.Error(t, err)
require.Equal(t, http.StatusForbidden, resp.StatusCode)
})
}
func setupUserForSubtest(t *testing.T, th *TestHelper) (*model.User, *model.Client4) {
password := model.NewTestPassword()
user, appErr := th.App.CreateUser(th.Context, &model.User{
Email: th.GenerateTestEmail(),
Username: "user_" + model.NewId(),
Password: password,
})
require.Nil(t, appErr)
th.LinkUserToTeam(t, user, th.BasicTeam)
th.AddUserToChannel(t, user, th.BasicChannel)
th.AddUserToChannel(t, user, th.BasicChannel2)
th.AddUserToChannel(t, user, th.BasicPrivateChannel)
client := th.CreateClient()
user, _, err := client.Login(context.Background(), user.Email, password)
require.NoError(t, err)
return user, client
}