mattermost-community-enterp.../channels/api4/shared_channel_test.go
Claude ec1f89217a Merge: Complete Mattermost Server with Community Enterprise
Full Mattermost server source with integrated Community Enterprise features.
Includes vendor directory for offline/air-gapped builds.

Structure:
- enterprise-impl/: Enterprise feature implementations
- enterprise-community/: Init files that register implementations
- enterprise/: Bridge imports (community_imports.go)
- vendor/: All dependencies for offline builds

Build (online):
  go build ./cmd/mattermost

Build (offline/air-gapped):
  go build -mod=vendor ./cmd/mattermost

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-17 23:59:07 +09:00

629 lines
20 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"context"
"fmt"
"math/rand"
"net/http"
"sort"
"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 TestGetAllSharedChannels(t *testing.T) {
mainHelper.Parallel(t)
th := setupForSharedChannels(t).InitBasic()
defer th.TearDown()
const pages = 3
const pageSize = 7
savedIds := make([]string, 0, pages*pageSize)
// make some shared channels
for i := range pages * pageSize {
channel := th.CreateChannelWithClientAndTeam(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)
}
sort.Strings(savedIds)
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)...)
}
sort.Strings(channelIds)
// ids lists should now match
assert.Equal(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()
defer th.TearDown()
// 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()
defer th.TearDown()
client := th.Client
defer func() {
_, err := client.Logout(context.Background())
require.NoError(t, err)
}()
localUser := th.BasicUser
remoteUser := th.CreateUser()
remoteUser.RemoteId = model.NewPointer(model.NewId())
remoteUser, appErr := th.App.UpdateUser(th.Context, remoteUser, false)
require.Nil(t, appErr)
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()
defer th.TearDown()
client := th.Client
defer func() {
_, err := client.Logout(context.Background())
require.NoError(t, err)
}()
localUser := th.BasicUser
remoteUser := th.CreateUser()
remoteUser.RemoteId = model.NewPointer(model.NewId())
remoteUser, appErr := th.App.UpdateUser(th.Context, remoteUser, false)
require.Nil(t, appErr)
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()
defer th.TearDown()
client := th.Client
defer func() {
_, err := client.Logout(context.Background())
require.NoError(t, err)
}()
localUser := th.BasicUser
remoteUser := th.CreateUser()
rc := &model.RemoteCluster{
Name: "test",
Token: model.NewId(),
CreatorId: localUser.Id,
}
rc, appErr := th.App.AddRemoteCluster(rc)
require.Nil(t, appErr)
remoteUser.RemoteId = model.NewPointer(rc.RemoteId)
remoteUser, appErr = th.App.UpdateUser(th.Context, remoteUser, false)
require.Nil(t, appErr)
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()
defer th.TearDown()
client := th.Client
defer func() {
_, err := client.Logout(context.Background())
require.NoError(t, err)
}()
localUser := th.BasicUser
remoteUser := th.CreateUser()
rc := &model.RemoteCluster{
Name: "test",
Token: model.NewId(),
CreatorId: localUser.Id,
}
rc, appErr := th.App.AddRemoteCluster(rc)
require.Nil(t, appErr)
remoteUser.RemoteId = model.NewPointer(rc.RemoteId)
remoteUser, appErr = th.App.UpdateUser(th.Context, remoteUser, false)
require.Nil(t, appErr)
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)
defer th.TearDown()
resp, err := th.SystemAdminClient.DeleteRemoteCluster(context.Background(), model.NewId())
CheckNotImplementedStatus(t, resp)
require.Error(t, err)
})
th := setupForSharedChannels(t).InitBasic()
defer th.TearDown()
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(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(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(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(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(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)
sort.Slice(sharedChannelRemotesFromRC1, func(i, j int) bool {
return sharedChannelRemotesFromRC1[i].Id < sharedChannelRemotesFromRC1[j].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)
defer th.TearDown()
resp, err := th.SystemAdminClient.InviteRemoteClusterToChannel(context.Background(), model.NewId(), model.NewId())
CheckNotImplementedStatus(t, resp)
require.Error(t, err)
})
th := setupForSharedChannels(t).InitBasic()
defer th.TearDown()
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)
defer th.TearDown()
resp, err := th.SystemAdminClient.UninviteRemoteClusterToChannel(context.Background(), model.NewId(), model.NewId())
CheckNotImplementedStatus(t, resp)
require.Error(t, err)
})
th := setupForSharedChannels(t).InitBasic()
defer th.TearDown()
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")
})
}