snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
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 Cache Warm / Warm Go Module and Build Cache (push) Failing after 1s
Server CI Nightly Race / Build Environment (push) Failing after 1s
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 Cache Warm / Warm Go Module and Build Cache (push) Failing after 1s
Server CI Nightly Race / Build Environment (push) Failing after 1s
This commit is contained in:
701
server/channels/api4/shared_channel_test.go
Normal file
701
server/channels/api4/shared_channel_test.go
Normal file
@@ -0,0 +1,701 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
func setupForSharedChannels(tb testing.TB) *TestHelper {
|
||||
th := SetupConfig(tb, func(cfg *model.Config) {
|
||||
*cfg.ConnectedWorkspacesSettings.EnableRemoteClusterService = true
|
||||
*cfg.ConnectedWorkspacesSettings.EnableSharedChannels = true
|
||||
})
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.SiteURL = fmt.Sprintf("http://localhost:%d", th.Server.ListenAddr.Port)
|
||||
})
|
||||
|
||||
return th
|
||||
}
|
||||
|
||||
func TestGetAllSharedChannelsWithSharedChannelManagerRole(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
th := setupForSharedChannels(t).InitBasic(t)
|
||||
|
||||
// Create a shared channel that the test user is NOT a member of
|
||||
otherChannel := th.CreateChannelWithClientAndTeam(t, th.SystemAdminClient, model.ChannelTypeOpen, th.BasicTeam.Id)
|
||||
sc := &model.SharedChannel{
|
||||
ChannelId: otherChannel.Id,
|
||||
TeamId: otherChannel.TeamId,
|
||||
Home: true,
|
||||
ShareName: "test_share_other",
|
||||
CreatorId: th.SystemAdminUser.Id,
|
||||
RemoteId: model.NewId(),
|
||||
}
|
||||
_, err := th.App.ShareChannel(th.Context, sc)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a user with the system_shared_channel_manager role
|
||||
scmUser := th.CreateUser(t)
|
||||
_, appErr := th.App.UpdateUserRoles(th.Context, scmUser.Id, model.SystemUserRoleId+" "+model.SharedChannelManagerRoleId, false)
|
||||
require.Nil(t, appErr)
|
||||
th.LinkUserToTeam(t, scmUser, th.BasicTeam)
|
||||
|
||||
scmClient := th.CreateClient()
|
||||
_, _, loginErr := scmClient.Login(context.Background(), scmUser.Email, scmUser.Password)
|
||||
require.NoError(t, loginErr)
|
||||
|
||||
t.Run("regular user only sees shared channels they are a member of", func(t *testing.T) {
|
||||
// BasicUser is not a member of otherChannel
|
||||
channels, _, err := th.Client.GetAllSharedChannels(context.Background(), th.BasicTeam.Id, 0, 100)
|
||||
require.NoError(t, err)
|
||||
for _, ch := range channels {
|
||||
assert.NotEqual(t, otherChannel.Id, ch.ChannelId,
|
||||
"regular user should not see shared channels they are not a member of")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("system_shared_channel_manager user sees all shared channels", func(t *testing.T) {
|
||||
// scmUser is NOT a member of otherChannel, but has manage_shared_channels
|
||||
channels, _, err := scmClient.GetAllSharedChannels(context.Background(), th.BasicTeam.Id, 0, 100)
|
||||
require.NoError(t, err)
|
||||
|
||||
found := false
|
||||
for _, ch := range channels {
|
||||
if ch.ChannelId == otherChannel.Id {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, found,
|
||||
"system_shared_channel_manager user should see all shared channels, including ones they are not a member of")
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetAllSharedChannels(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
th := setupForSharedChannels(t).InitBasic(t)
|
||||
|
||||
const pages = 3
|
||||
const pageSize = 7
|
||||
|
||||
savedIds := make([]string, 0, pages*pageSize)
|
||||
|
||||
// make some shared channels
|
||||
for i := range pages * pageSize {
|
||||
channel := th.CreateChannelWithClientAndTeam(t, th.Client, model.ChannelTypeOpen, th.BasicTeam.Id)
|
||||
sc := &model.SharedChannel{
|
||||
ChannelId: channel.Id,
|
||||
TeamId: channel.TeamId,
|
||||
Home: randomBool(),
|
||||
ShareName: fmt.Sprintf("test_share_%d", i),
|
||||
CreatorId: th.BasicChannel.CreatorId,
|
||||
RemoteId: model.NewId(),
|
||||
}
|
||||
|
||||
_, err := th.App.ShareChannel(th.Context, sc)
|
||||
require.NoError(t, err)
|
||||
savedIds = append(savedIds, channel.Id)
|
||||
}
|
||||
t.Run("get shared channels paginated", func(t *testing.T) {
|
||||
channelIds := make([]string, 0, 21)
|
||||
for i := range pages {
|
||||
channels, _, err := th.Client.GetAllSharedChannels(context.Background(), th.BasicTeam.Id, i, pageSize)
|
||||
require.NoError(t, err)
|
||||
channelIds = append(channelIds, getIds(channels)...)
|
||||
}
|
||||
|
||||
// ids lists should now match
|
||||
assert.ElementsMatch(t, savedIds, channelIds, "id lists should match")
|
||||
})
|
||||
|
||||
t.Run("get shared channels for invalid team", func(t *testing.T) {
|
||||
_, _, err := th.Client.GetAllSharedChannels(context.Background(), model.NewId(), 0, 100)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("get shared channels, user not member of team", func(t *testing.T) {
|
||||
team := &model.Team{
|
||||
DisplayName: "tteam",
|
||||
Name: GenerateTestTeamName(),
|
||||
Type: model.TeamOpen,
|
||||
}
|
||||
team, _, err := th.SystemAdminClient.CreateTeam(context.Background(), team)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = th.Client.GetAllSharedChannels(context.Background(), team.Id, 0, 100)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func getIds(channels []*model.SharedChannel) []string {
|
||||
ids := make([]string, 0, len(channels))
|
||||
for _, c := range channels {
|
||||
ids = append(ids, c.ChannelId)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func randomBool() bool {
|
||||
return rnd.Intn(2) != 0
|
||||
}
|
||||
|
||||
func TestGetRemoteClusterById(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
th := setupForSharedChannels(t).InitBasic(t)
|
||||
|
||||
// for this test we need a user that belongs to a channel that
|
||||
// is shared with the requested remote id.
|
||||
|
||||
// create a remote cluster
|
||||
rc := &model.RemoteCluster{
|
||||
RemoteId: model.NewId(),
|
||||
Name: "Test1",
|
||||
SiteURL: model.NewId(),
|
||||
CreatorId: model.NewId(),
|
||||
}
|
||||
rc, appErr := th.App.AddRemoteCluster(rc)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// create a shared channel
|
||||
sc := &model.SharedChannel{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
TeamId: th.BasicChannel.TeamId,
|
||||
Home: false,
|
||||
ShareName: "test_share",
|
||||
CreatorId: th.BasicChannel.CreatorId,
|
||||
RemoteId: rc.RemoteId,
|
||||
}
|
||||
sc, err := th.App.ShareChannel(th.Context, sc)
|
||||
require.NoError(t, err)
|
||||
|
||||
// create a shared channel remote to connect them
|
||||
scr := &model.SharedChannelRemote{
|
||||
Id: model.NewId(),
|
||||
ChannelId: sc.ChannelId,
|
||||
CreatorId: sc.CreatorId,
|
||||
IsInviteAccepted: true,
|
||||
IsInviteConfirmed: true,
|
||||
RemoteId: sc.RemoteId,
|
||||
}
|
||||
_, err = th.App.SaveSharedChannelRemote(scr)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("valid remote, user is member", func(t *testing.T) {
|
||||
rcInfo, _, err := th.Client.GetRemoteClusterInfo(context.Background(), rc.RemoteId)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, rc.Name, rcInfo.Name)
|
||||
})
|
||||
|
||||
t.Run("invalid remote", func(t *testing.T) {
|
||||
_, resp, err := th.Client.GetRemoteClusterInfo(context.Background(), model.NewId())
|
||||
require.Error(t, err)
|
||||
CheckNotFoundStatus(t, resp)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateDirectChannelWithRemoteUser(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
t.Run("should not create a local DM channel that is shared", func(t *testing.T) {
|
||||
th := setupForSharedChannels(t).InitBasic(t)
|
||||
|
||||
client := th.Client
|
||||
defer func() {
|
||||
_, err := client.Logout(context.Background())
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
localUser := th.BasicUser
|
||||
remoteUser := th.SetUserRemoteID(t, th.CreateUser(t).Id, model.NewId())
|
||||
|
||||
dm, _, err := client.CreateDirectChannel(context.Background(), localUser.Id, remoteUser.Id)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, dm)
|
||||
})
|
||||
|
||||
t.Run("creates a local DM channel that is shared", func(t *testing.T) {
|
||||
t.Skip("Remote DMs are currently disabled")
|
||||
|
||||
th := setupForSharedChannels(t).InitBasic(t)
|
||||
|
||||
client := th.Client
|
||||
defer func() {
|
||||
_, err := client.Logout(context.Background())
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
localUser := th.BasicUser
|
||||
remoteUser := th.SetUserRemoteID(t, th.CreateUser(t).Id, model.NewId())
|
||||
|
||||
dm, _, err := client.CreateDirectChannel(context.Background(), localUser.Id, remoteUser.Id)
|
||||
require.NoError(t, err)
|
||||
|
||||
channelName := model.GetDMNameFromIds(localUser.Id, remoteUser.Id)
|
||||
require.Equal(t, channelName, dm.Name, "dm name didn't match")
|
||||
assert.True(t, dm.IsShared())
|
||||
})
|
||||
|
||||
t.Run("sends a shared channel invitation to the remote", func(t *testing.T) {
|
||||
t.Skip("Remote DMs are currently disabled")
|
||||
|
||||
th := setupForSharedChannels(t).InitBasic(t)
|
||||
|
||||
client := th.Client
|
||||
defer func() {
|
||||
_, err := client.Logout(context.Background())
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
localUser := th.BasicUser
|
||||
remoteUser := th.CreateUser(t)
|
||||
|
||||
rc := &model.RemoteCluster{
|
||||
Name: "test",
|
||||
Token: model.NewId(),
|
||||
CreatorId: localUser.Id,
|
||||
}
|
||||
rc, appErr := th.App.AddRemoteCluster(rc)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
th.SetUserRemoteID(t, remoteUser.Id, rc.RemoteId)
|
||||
|
||||
dm, _, err := client.CreateDirectChannel(context.Background(), localUser.Id, remoteUser.Id)
|
||||
require.NoError(t, err)
|
||||
|
||||
channelName := model.GetDMNameFromIds(localUser.Id, remoteUser.Id)
|
||||
require.Equal(t, channelName, dm.Name, "dm name didn't match")
|
||||
require.True(t, dm.IsShared())
|
||||
})
|
||||
|
||||
t.Run("does not send a shared channel invitation to the remote when creator is remote", func(t *testing.T) {
|
||||
t.Skip("Remote DMs are currently disabled")
|
||||
|
||||
th := setupForSharedChannels(t).InitBasic(t)
|
||||
|
||||
client := th.Client
|
||||
defer func() {
|
||||
_, err := client.Logout(context.Background())
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
localUser := th.BasicUser
|
||||
remoteUser := th.CreateUser(t)
|
||||
|
||||
rc := &model.RemoteCluster{
|
||||
Name: "test",
|
||||
Token: model.NewId(),
|
||||
CreatorId: localUser.Id,
|
||||
}
|
||||
rc, appErr := th.App.AddRemoteCluster(rc)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
th.SetUserRemoteID(t, remoteUser.Id, rc.RemoteId)
|
||||
|
||||
dm, _, err := client.CreateDirectChannel(context.Background(), remoteUser.Id, localUser.Id)
|
||||
require.NoError(t, err)
|
||||
|
||||
channelName := model.GetDMNameFromIds(localUser.Id, remoteUser.Id)
|
||||
require.Equal(t, channelName, dm.Name, "dm name didn't match")
|
||||
require.True(t, dm.IsShared())
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetSharedChannelRemotesByRemoteCluster(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
t.Run("Should not work if the remote cluster service is not enabled", func(t *testing.T) {
|
||||
th := Setup(t)
|
||||
|
||||
resp, err := th.SystemAdminClient.DeleteRemoteCluster(context.Background(), model.NewId())
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
th := setupForSharedChannels(t).InitBasic(t)
|
||||
|
||||
newRC1 := &model.RemoteCluster{Name: "rc1", SiteURL: "http://example1.com", CreatorId: th.SystemAdminUser.Id}
|
||||
newRC2 := &model.RemoteCluster{Name: "rc2", SiteURL: "http://example2.com", CreatorId: th.SystemAdminUser.Id}
|
||||
|
||||
rc1, appErr := th.App.AddRemoteCluster(newRC1)
|
||||
require.Nil(t, appErr)
|
||||
rc2, appErr := th.App.AddRemoteCluster(newRC2)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
c1 := th.CreateChannelWithClientAndTeam(t, th.Client, model.ChannelTypeOpen, th.BasicTeam.Id)
|
||||
sc1 := &model.SharedChannel{
|
||||
ChannelId: c1.Id,
|
||||
TeamId: th.BasicTeam.Id,
|
||||
ShareName: "shared_1",
|
||||
ShareDisplayName: "Shared Channel 1", // for sorting purposes
|
||||
CreatorId: th.BasicUser.Id,
|
||||
RemoteId: rc1.RemoteId,
|
||||
Home: true,
|
||||
}
|
||||
_, err := th.App.ShareChannel(th.Context, sc1)
|
||||
require.NoError(t, err)
|
||||
|
||||
c2 := th.CreateChannelWithClientAndTeam(t, th.Client, model.ChannelTypeOpen, th.BasicTeam.Id)
|
||||
sc2 := &model.SharedChannel{
|
||||
ChannelId: c2.Id,
|
||||
TeamId: th.BasicTeam.Id,
|
||||
ShareName: "shared_2",
|
||||
ShareDisplayName: "Shared Channel 2",
|
||||
CreatorId: th.BasicUser.Id,
|
||||
RemoteId: rc1.RemoteId,
|
||||
Home: false,
|
||||
}
|
||||
|
||||
_, err = th.App.ShareChannel(th.Context, sc2)
|
||||
require.NoError(t, err)
|
||||
|
||||
c3 := th.CreateChannelWithClientAndTeam(t, th.Client, model.ChannelTypeOpen, th.BasicTeam.Id)
|
||||
sc3 := &model.SharedChannel{
|
||||
ChannelId: c3.Id,
|
||||
TeamId: th.BasicTeam.Id,
|
||||
ShareName: "shared_3",
|
||||
CreatorId: th.BasicUser.Id,
|
||||
RemoteId: rc2.RemoteId,
|
||||
}
|
||||
_, err = th.App.ShareChannel(th.Context, sc3)
|
||||
require.NoError(t, err)
|
||||
|
||||
c4 := th.CreateChannelWithClientAndTeam(t, th.Client, model.ChannelTypeOpen, th.BasicTeam.Id)
|
||||
sc4 := &model.SharedChannel{
|
||||
ChannelId: c4.Id,
|
||||
TeamId: th.BasicTeam.Id,
|
||||
ShareName: "shared_4",
|
||||
ShareDisplayName: "Shared Channel 4",
|
||||
CreatorId: th.BasicUser.Id,
|
||||
RemoteId: rc1.RemoteId,
|
||||
Home: false,
|
||||
}
|
||||
|
||||
_, err = th.App.ShareChannel(th.Context, sc4)
|
||||
require.NoError(t, err)
|
||||
|
||||
c5 := th.CreateChannelWithClientAndTeam(t, th.Client, model.ChannelTypeOpen, th.BasicTeam.Id)
|
||||
sc5 := &model.SharedChannel{
|
||||
ChannelId: c5.Id,
|
||||
TeamId: th.BasicTeam.Id,
|
||||
ShareName: "shared_5",
|
||||
ShareDisplayName: "Shared Channel 5",
|
||||
CreatorId: th.BasicUser.Id,
|
||||
RemoteId: rc1.RemoteId,
|
||||
Home: false,
|
||||
}
|
||||
|
||||
_, err = th.App.ShareChannel(th.Context, sc5)
|
||||
require.NoError(t, err)
|
||||
|
||||
// for the pagination test, we need to get the channelId of the
|
||||
// second SharedChannelRemote that belongs to RC1, sorted by ID,
|
||||
// so we accumulate those SharedChannelRemotes on creation and
|
||||
// later sort them to be able to get the right one for the test
|
||||
// result
|
||||
sharedChannelRemotesFromRC1 := []*model.SharedChannelRemote{}
|
||||
|
||||
// create the shared channel remotes
|
||||
for _, sc := range []*model.SharedChannel{sc1, sc2, sc3, sc4, sc5} {
|
||||
scr := &model.SharedChannelRemote{
|
||||
Id: model.NewId(),
|
||||
ChannelId: sc.ChannelId,
|
||||
CreatorId: sc.CreatorId,
|
||||
IsInviteAccepted: true,
|
||||
IsInviteConfirmed: true,
|
||||
RemoteId: sc.RemoteId,
|
||||
}
|
||||
// scr for c5 is not confirmed yet
|
||||
if sc.ChannelId == sc5.ChannelId {
|
||||
scr.IsInviteConfirmed = false
|
||||
}
|
||||
_, err = th.App.SaveSharedChannelRemote(scr)
|
||||
require.NoError(t, err)
|
||||
|
||||
if scr.RemoteId == rc1.RemoteId {
|
||||
sharedChannelRemotesFromRC1 = append(sharedChannelRemotesFromRC1, scr)
|
||||
}
|
||||
}
|
||||
|
||||
// we delete the shared channel remote for sc4
|
||||
scr4, err := th.App.GetSharedChannelRemoteByIds(sc4.ChannelId, sc4.RemoteId)
|
||||
require.NoError(t, err)
|
||||
|
||||
deleted, err := th.App.DeleteSharedChannelRemote(scr4.Id)
|
||||
require.NoError(t, err)
|
||||
require.True(t, deleted)
|
||||
|
||||
slices.SortFunc(sharedChannelRemotesFromRC1, func(a, b *model.SharedChannelRemote) int {
|
||||
return cmp.Compare(a.Id, b.Id)
|
||||
})
|
||||
|
||||
t.Run("should return the expected shared channels", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Name string
|
||||
Client *model.Client4
|
||||
RemoteId string
|
||||
Filter model.SharedChannelRemoteFilterOpts
|
||||
Page int
|
||||
PerPage int
|
||||
ExpectedStatusCode int
|
||||
ExpectedError bool
|
||||
ExpectedIds []string
|
||||
}{
|
||||
{
|
||||
Name: "should not work if the user doesn't have the right permissions",
|
||||
Client: th.Client,
|
||||
RemoteId: rc1.RemoteId,
|
||||
Page: 0,
|
||||
PerPage: 100,
|
||||
ExpectedStatusCode: http.StatusForbidden,
|
||||
ExpectedError: true,
|
||||
},
|
||||
{
|
||||
Name: "should not work if the remote cluster is nonexistent",
|
||||
Client: th.SystemAdminClient,
|
||||
RemoteId: model.NewId(),
|
||||
Page: 0,
|
||||
PerPage: 100,
|
||||
ExpectedStatusCode: http.StatusNotFound,
|
||||
ExpectedError: true,
|
||||
},
|
||||
{
|
||||
Name: "should return the complete list of shared channel remotes for a remote cluster",
|
||||
Client: th.SystemAdminClient,
|
||||
RemoteId: rc1.RemoteId,
|
||||
Page: 0,
|
||||
PerPage: 100,
|
||||
ExpectedStatusCode: http.StatusOK,
|
||||
ExpectedError: false,
|
||||
ExpectedIds: []string{sc1.ChannelId, sc2.ChannelId},
|
||||
},
|
||||
{
|
||||
Name: "should return the complete list of shared channel remotes for a remote cluster, including deleted",
|
||||
Client: th.SystemAdminClient,
|
||||
RemoteId: rc1.RemoteId,
|
||||
Filter: model.SharedChannelRemoteFilterOpts{IncludeDeleted: true},
|
||||
Page: 0,
|
||||
PerPage: 100,
|
||||
ExpectedStatusCode: http.StatusOK,
|
||||
ExpectedError: false,
|
||||
ExpectedIds: []string{sc1.ChannelId, sc2.ChannelId, sc4.ChannelId},
|
||||
},
|
||||
{
|
||||
Name: "should return only the shared channel remotes homed localy",
|
||||
Client: th.SystemAdminClient,
|
||||
RemoteId: rc1.RemoteId,
|
||||
Filter: model.SharedChannelRemoteFilterOpts{ExcludeRemote: true},
|
||||
Page: 0,
|
||||
PerPage: 100,
|
||||
ExpectedStatusCode: http.StatusOK,
|
||||
ExpectedError: false,
|
||||
ExpectedIds: []string{sc1.ChannelId},
|
||||
},
|
||||
{
|
||||
Name: "should return only the shared channel remotes homed remotely",
|
||||
Client: th.SystemAdminClient,
|
||||
RemoteId: rc1.RemoteId,
|
||||
Filter: model.SharedChannelRemoteFilterOpts{ExcludeHome: true},
|
||||
Page: 0,
|
||||
PerPage: 100,
|
||||
ExpectedStatusCode: http.StatusOK,
|
||||
ExpectedError: false,
|
||||
ExpectedIds: []string{sc2.ChannelId},
|
||||
},
|
||||
{
|
||||
Name: "should return the complete list of shared channel remotes for a remote cluster including unconfirmed",
|
||||
Client: th.SystemAdminClient,
|
||||
RemoteId: rc1.RemoteId,
|
||||
Filter: model.SharedChannelRemoteFilterOpts{IncludeUnconfirmed: true},
|
||||
Page: 0,
|
||||
PerPage: 100,
|
||||
ExpectedStatusCode: http.StatusOK,
|
||||
ExpectedError: false,
|
||||
ExpectedIds: []string{sc1.ChannelId, sc2.ChannelId, sc5.ChannelId},
|
||||
},
|
||||
{
|
||||
Name: "should return only the unconfirmed shared channel remotes for a remote cluster",
|
||||
Client: th.SystemAdminClient,
|
||||
RemoteId: rc1.RemoteId,
|
||||
Filter: model.SharedChannelRemoteFilterOpts{ExcludeConfirmed: true},
|
||||
Page: 0,
|
||||
PerPage: 100,
|
||||
ExpectedStatusCode: http.StatusOK,
|
||||
ExpectedError: false,
|
||||
ExpectedIds: []string{sc5.ChannelId},
|
||||
},
|
||||
{
|
||||
Name: "should correctly paginate the results",
|
||||
Client: th.SystemAdminClient,
|
||||
RemoteId: rc1.RemoteId,
|
||||
Filter: model.SharedChannelRemoteFilterOpts{IncludeDeleted: true, IncludeUnconfirmed: true},
|
||||
Page: 1,
|
||||
PerPage: 1,
|
||||
ExpectedStatusCode: http.StatusOK,
|
||||
ExpectedError: false,
|
||||
ExpectedIds: []string{sharedChannelRemotesFromRC1[1].ChannelId},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
scrs, resp, err := tc.Client.GetSharedChannelRemotesByRemoteCluster(context.Background(), tc.RemoteId, tc.Filter, tc.Page, tc.PerPage)
|
||||
checkHTTPStatus(t, resp, tc.ExpectedStatusCode)
|
||||
if tc.ExpectedError {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Len(t, scrs, len(tc.ExpectedIds))
|
||||
|
||||
foundIds := []string{}
|
||||
for _, scr := range scrs {
|
||||
require.Equal(t, tc.RemoteId, scr.RemoteId)
|
||||
foundIds = append(foundIds, scr.ChannelId)
|
||||
}
|
||||
require.ElementsMatch(t, tc.ExpectedIds, foundIds)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestInviteRemoteClusterToChannel(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
t.Run("Should not work if the remote cluster service is not enabled", func(t *testing.T) {
|
||||
th := Setup(t)
|
||||
|
||||
resp, err := th.SystemAdminClient.InviteRemoteClusterToChannel(context.Background(), model.NewId(), model.NewId())
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
th := setupForSharedChannels(t).InitBasic(t)
|
||||
|
||||
newRC := &model.RemoteCluster{Name: "rc", SiteURL: "http://example.com", CreatorId: th.SystemAdminUser.Id}
|
||||
|
||||
rc, appErr := th.App.AddRemoteCluster(newRC)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
t.Run("Should not work if the user doesn't have the right permissions", func(t *testing.T) {
|
||||
resp, err := th.Client.InviteRemoteClusterToChannel(context.Background(), model.NewId(), model.NewId())
|
||||
CheckForbiddenStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("should not work if the remote cluster is nonexistent", func(t *testing.T) {
|
||||
resp, err := th.SystemAdminClient.InviteRemoteClusterToChannel(context.Background(), model.NewId(), model.NewId())
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("should not work if the channel is nonexistent", func(t *testing.T) {
|
||||
resp, err := th.SystemAdminClient.InviteRemoteClusterToChannel(context.Background(), rc.RemoteId, model.NewId())
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("should correctly invite the remote cluster to the channel", func(t *testing.T) {
|
||||
t.Skip("Requires server2server communication: ToBeImplemented")
|
||||
})
|
||||
|
||||
t.Run("should do nothing but return 204 if the remote cluster is already invited to the channel", func(t *testing.T) {
|
||||
t.Skip("Requires server2server communication: ToBeImplemented")
|
||||
})
|
||||
}
|
||||
|
||||
func TestUninviteRemoteClusterToChannel(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
t.Run("Should not work if the remote cluster service is not enabled", func(t *testing.T) {
|
||||
th := Setup(t)
|
||||
|
||||
resp, err := th.SystemAdminClient.UninviteRemoteClusterToChannel(context.Background(), model.NewId(), model.NewId())
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
th := setupForSharedChannels(t).InitBasic(t)
|
||||
|
||||
newRC := &model.RemoteCluster{Name: "rc", SiteURL: "http://example.com", CreatorId: th.SystemAdminUser.Id}
|
||||
|
||||
rc, appErr := th.App.AddRemoteCluster(newRC)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
t.Run("Should not work if the user doesn't have the right permissions", func(t *testing.T) {
|
||||
resp, err := th.Client.UninviteRemoteClusterToChannel(context.Background(), model.NewId(), model.NewId())
|
||||
CheckForbiddenStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("should not work if the remote cluster is nonexistent", func(t *testing.T) {
|
||||
resp, err := th.SystemAdminClient.UninviteRemoteClusterToChannel(context.Background(), model.NewId(), model.NewId())
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("should not work if the channel is nonexistent", func(t *testing.T) {
|
||||
resp, err := th.SystemAdminClient.UninviteRemoteClusterToChannel(context.Background(), rc.RemoteId, model.NewId())
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("should correctly uninvite the remote cluster to the channel", func(t *testing.T) {
|
||||
t.Skip("Requires server2server communication: ToBeImplemented")
|
||||
})
|
||||
|
||||
t.Run("should do nothing but return 204 if the remote cluster is not sharing the channel", func(t *testing.T) {
|
||||
t.Skip("Requires server2server communication: ToBeImplemented")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSharedChannelEndpointsWithSharedChannelManagerRole(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
th := setupForSharedChannels(t).InitBasic(t)
|
||||
|
||||
newRC := &model.RemoteCluster{Name: "rc", SiteURL: "http://example.com", CreatorId: th.SystemAdminUser.Id}
|
||||
rc, appErr := th.App.AddRemoteCluster(newRC)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Create a user with only the shared_channel_manager role
|
||||
scmUser := th.CreateUser(t)
|
||||
_, appErr = th.App.UpdateUserRoles(th.Context, scmUser.Id, model.SystemUserRoleId+" "+model.SharedChannelManagerRoleId, false)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
scmClient := th.CreateClient()
|
||||
_, _, err := scmClient.Login(context.Background(), scmUser.Email, scmUser.Password)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("getSharedChannelRemotesByRemoteCluster should allow shared_channel_manager", func(t *testing.T) {
|
||||
_, resp, err := scmClient.GetSharedChannelRemotesByRemoteCluster(context.Background(), rc.RemoteId, model.SharedChannelRemoteFilterOpts{}, 0, 100)
|
||||
CheckOKStatus(t, resp)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("inviteRemoteClusterToChannel should allow shared_channel_manager", func(t *testing.T) {
|
||||
// This will fail with a bad request (nonexistent channel) rather than forbidden,
|
||||
// which proves the permission check passed.
|
||||
resp, err := scmClient.InviteRemoteClusterToChannel(context.Background(), rc.RemoteId, model.NewId())
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("uninviteRemoteClusterToChannel should allow shared_channel_manager", func(t *testing.T) {
|
||||
// Same as invite — a bad request proves the permission check passed.
|
||||
resp, err := scmClient.UninviteRemoteClusterToChannel(context.Background(), rc.RemoteId, model.NewId())
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user