diff --git a/samples/DocsExamples/UserPermissions.cs b/samples/DocsExamples/UserPermissions.cs
new file mode 100644
index 0000000..233ec16
--- /dev/null
+++ b/samples/DocsExamples/UserPermissions.cs
@@ -0,0 +1,149 @@
+using StreamChat.Clients;
+using StreamChat.Models;
+
+namespace DocsExamples;
+
+///
+/// Code examples for
+///
+internal class UserPermissions
+{
+ private readonly IUserClient _userClient;
+ private readonly IChannelClient _channelClient;
+ private readonly IPermissionClient _permissionClient;
+ private readonly IChannelTypeClient _channelTypeClient;
+ private readonly IAppClient _appClient;
+
+ public UserPermissions()
+ {
+ var factory = new StreamClientFactory("{{ api_key }}", "{{ api_secret }}");
+ _userClient = factory.GetUserClient();
+ _channelClient = factory.GetChannelClient();
+ _permissionClient = factory.GetPermissionClient();
+ _channelTypeClient = factory.GetChannelTypeClient();
+ _appClient = factory.GetAppClient();
+ }
+
+ internal async Task ChangeUserRole()
+ {
+ var upsertResponse = await _userClient.UpdatePartialAsync(new UserPartialRequest
+ {
+ Id = "user-id",
+ Set = new Dictionary
+ {
+ { "role", "special_agent" }
+ }
+ });
+ }
+
+ internal async Task VerifyChannelMemberRoleAssigned()
+ {
+ var addMembersResponse
+ = await _channelClient.AddMembersAsync("channel-type", "channel-id", new[] { "user-id" });
+ Console.WriteLine(addMembersResponse.Members[0].ChannelRole); // channel role is equal to "channel_member"
+ }
+
+ internal async Task AssignRoles()
+ {
+ // User must be a member of the channel before you can assign channel role
+ var resp = await _channelClient.AssignRolesAsync("channel-type", "channel-id", new AssignRoleRequest
+ {
+ AssignRoles = new List
+ {
+ new RoleAssignment { UserId = "user-id", ChannelRole = Role.ChannelModerator }
+ }
+ });
+ }
+
+ internal async Task CreateRole()
+ {
+ await _permissionClient.CreateRoleAsync("special_agent");
+ }
+
+ internal async Task DeleteRole()
+ {
+ await _permissionClient.DeleteRoleAsync("special_agent");
+ }
+
+ internal async Task ListPermissions()
+ {
+ var response = await _permissionClient.ListPermissionsAsync();
+ }
+
+ internal async Task UpdateGrantedPermissions()
+ {
+ // observe current grants of the channel type
+ var channelType = await _channelTypeClient.GetChannelTypeAsync("messaging");
+ Console.WriteLine(channelType.Grants);
+
+ // update "channel_member" role grants in "messaging" scope
+ var update = new ChannelTypeWithStringCommandsRequest
+ {
+ Grants = new Dictionary>
+ {
+ {
+ // This will replace all existing grants of "channel_member" role
+ "channel_member", new List
+ {
+ "read-channel", // allow access to the channel
+ "create-message", // create messages in the channel
+ "update-message-owner", // update own user messages
+ "delete-message-owner", // delete own user messages
+ }
+ },
+ }
+ };
+ await _channelTypeClient.UpdateChannelTypeAsync("messaging", update);
+ }
+
+ internal async Task RemoveGrantedPermissionsByCategory()
+ {
+ var update = new ChannelTypeWithStringCommandsRequest
+ {
+ Grants = new Dictionary>
+ {
+ { "guest", new List() }, // removes all grants of "guest" role
+ { "anonymous", new List() }, // removes all grants of "anonymous" role
+ }
+ };
+ await _channelTypeClient.UpdateChannelTypeAsync("messaging", update);
+ }
+
+ internal async Task ResetGrantsToDefaultSettings()
+ {
+ var update = new ChannelTypeWithStringCommandsRequest
+ {
+ Grants = new Dictionary>()
+ };
+ await _channelTypeClient.UpdateChannelTypeAsync("messaging", update);
+ }
+
+ internal async Task UpdateAppScopedGrants()
+ {
+ var settings = new AppSettingsRequest
+ {
+ Grants = new Dictionary>
+ {
+ { "anonymous", new List() },
+ { "guest", new List() },
+ { "user", new List { "search-user", "mute-user" } },
+ { "admin", new List { "search-user", "mute-user", "ban-user" } },
+ }
+ };
+ await _appClient.UpdateAppSettingsAsync(settings);
+ }
+
+ internal async Task UpdateChannelLevelPermissions()
+ {
+ var grants = new Dictionary { { "user", new List { "!add-links", "create-reaction" } } };
+ var overrides = new Dictionary { { "grants", grants } };
+ var request = new PartialUpdateChannelRequest
+ {
+ Set = new Dictionary
+ {
+ { "config_overrides", overrides }
+ }
+ };
+ var resp = await _channelClient.PartialUpdateAsync("channel-type", "channel-id", request);
+ }
+}
\ No newline at end of file
diff --git a/src/Clients/ChannelClient.Members.cs b/src/Clients/ChannelClient.Members.cs
index e7f5504..fcc9a01 100644
--- a/src/Clients/ChannelClient.Members.cs
+++ b/src/Clients/ChannelClient.Members.cs
@@ -8,11 +8,11 @@ namespace StreamChat.Clients
{
public partial class ChannelClient
{
- public async Task AddMembersAsync(string channelType, string channelId, params string[] userIds)
+ public async Task AddMembersAsync(string channelType, string channelId, params string[] userIds)
=> await AddMembersAsync(channelType, channelId, userIds, null, null);
- public async Task AddMembersAsync(string channelType, string channelId, IEnumerable userIds, MessageRequest msg, AddMemberOptions options)
- => await ExecuteRequestAsync($"channels/{channelType}/{channelId}",
+ public async Task AddMembersAsync(string channelType, string channelId, IEnumerable userIds, MessageRequest msg, AddMemberOptions options)
+ => await ExecuteRequestAsync($"channels/{channelType}/{channelId}",
HttpMethod.POST,
HttpStatusCode.Created,
new ChannelUpdateRequest
diff --git a/src/Clients/IChannelClient.cs b/src/Clients/IChannelClient.cs
index 021dc5b..eeb55b8 100644
--- a/src/Clients/IChannelClient.cs
+++ b/src/Clients/IChannelClient.cs
@@ -14,13 +14,13 @@ public interface IChannelClient
/// Adds members to a channel.
///
/// https://getstream.io/chat/docs/dotnet-csharp/channel_members/?language=csharp
- Task AddMembersAsync(string channelType, string channelId, params string[] userIds);
+ Task AddMembersAsync(string channelType, string channelId, params string[] userIds);
///
/// Adds members to a channel.
///
/// https://getstream.io/chat/docs/dotnet-csharp/channel_members/?language=csharp
- Task AddMembersAsync(string channelType, string channelId, IEnumerable userIds,
+ Task AddMembersAsync(string channelType, string channelId, IEnumerable userIds,
MessageRequest msg, AddMemberOptions options);
///
diff --git a/src/Models/Channel.cs b/src/Models/Channel.cs
index 1f01d7f..a890afb 100644
--- a/src/Models/Channel.cs
+++ b/src/Models/Channel.cs
@@ -51,6 +51,7 @@ public class UpdateChannelResponse : ApiResponse
{
public ChannelWithConfig Channel { get; set; }
public Message Message { get; set; }
+ public List Members { get; set; }
}
public class PartialUpdateChannelRequest
diff --git a/src/Models/ChannelConfig.cs b/src/Models/ChannelConfig.cs
index 3fe85dd..ffceb1b 100644
--- a/src/Models/ChannelConfig.cs
+++ b/src/Models/ChannelConfig.cs
@@ -18,6 +18,7 @@ public abstract class ChannelConfigBase
public string MessageRetention { get; set; }
public int MaxMessageLength { get; set; }
public string Automod { get; set; }
+ public Dictionary> Grants { get; set; }
}
public class ChannelConfig : ChannelConfigBase
diff --git a/src/Models/ChannelMember.cs b/src/Models/ChannelMember.cs
index 8bfb8ac..8aeb43e 100644
--- a/src/Models/ChannelMember.cs
+++ b/src/Models/ChannelMember.cs
@@ -19,6 +19,7 @@ public class ChannelMember : CustomDataBase
public DateTimeOffset? InviteAcceptedAt { get; set; }
public DateTimeOffset? InviteRejectedAt { get; set; }
public string Role { get; set; }
+ public string ChannelRole { get; set; }
public DateTimeOffset? CreatedAt { get; set; }
public DateTimeOffset? UpdatedAt { get; set; }
public bool? Banned { get; set; }
diff --git a/src/Models/ChannelType.cs b/src/Models/ChannelType.cs
index e94c752..d95f78a 100644
--- a/src/Models/ChannelType.cs
+++ b/src/Models/ChannelType.cs
@@ -62,7 +62,12 @@ public abstract class ChannelTypeRequestBase
[Obsolete("Use V2 Permissions APIs instead. " +
"See https://getstream.io/chat/docs/dotnet-csharp/migrating_from_legacy/?language=csharp")]
public List Permissions { get; set; }
- public Dictionary> Grants { get; set; }
+
+ // JsonProperty is needed because passing NULL is a special case where API resets the grants to the default settings.
+ // Empty Dictionary as a default value is needed in order for the default object to not reset the grants
+ [JsonProperty(NullValueHandling = NullValueHandling.Include,
+ DefaultValueHandling = DefaultValueHandling.Include)]
+ public Dictionary> Grants { get; set; } = new Dictionary>();
}
public class ChannelTypeWithCommandsRequest : ChannelTypeRequestBase
diff --git a/tests/BlocklistClientTests.cs b/tests/BlocklistClientTests.cs
index 0391004..90f380f 100644
--- a/tests/BlocklistClientTests.cs
+++ b/tests/BlocklistClientTests.cs
@@ -36,7 +36,7 @@ public async Task TearDownAsync()
}
[Test]
- public Task TestGetAsync() => TryMultiple(async () =>
+ public Task TestGetAsync() => TryMultipleAsync(async () =>
{
var resp = await _blocklistClient.GetAsync(_blocklistName);
@@ -47,7 +47,7 @@ public Task TestGetAsync() => TryMultiple(async () =>
});
[Test]
- public Task TestListAsync() => TryMultiple(async () =>
+ public Task TestListAsync() => TryMultipleAsync(async () =>
{
var resp = await _blocklistClient.ListAsync();
@@ -59,12 +59,12 @@ public async Task TestUpdateAsync()
{
var expectedWords = new[] { "test", "test2" };
- await TryMultiple(async () =>
+ await TryMultipleAsync(async () =>
{
await _blocklistClient.UpdateAsync(_blocklistName, expectedWords);
});
- await TryMultiple(async () =>
+ await TryMultipleAsync(async () =>
{
var updated = await _blocklistClient.GetAsync(_blocklistName);
updated.Blocklist.Words.Should().BeEquivalentTo(expectedWords);
diff --git a/tests/ChannelTypeClientTests.cs b/tests/ChannelTypeClientTests.cs
index 4f94b45..5cd6960 100644
--- a/tests/ChannelTypeClientTests.cs
+++ b/tests/ChannelTypeClientTests.cs
@@ -52,7 +52,7 @@ await WaitForAsync(async () =>
[Test]
public Task TestGetChannelTypeAsync()
- => TryMultiple(testBody: async () =>
+ => TryMultipleAsync(testBody: async () =>
{
var actualChannelType = await _channelTypeClient.GetChannelTypeAsync(_channelType.Name);
actualChannelType.Name.Should().BeEquivalentTo(_channelType.Name);
diff --git a/tests/CommandClientTests.cs b/tests/CommandClientTests.cs
index 0fc4d04..0ee82ed 100644
--- a/tests/CommandClientTests.cs
+++ b/tests/CommandClientTests.cs
@@ -38,7 +38,7 @@ public async Task TeardownAsync()
[Test]
public Task TestGetCommandAsync()
- => TryMultiple(async () =>
+ => TryMultipleAsync(async () =>
{
var command = await _commandClient.GetAsync(_command.Name);
@@ -47,7 +47,7 @@ public Task TestGetCommandAsync()
[Test]
public Task TestListCommandsAsync()
- => TryMultiple(async () =>
+ => TryMultipleAsync(async () =>
{
var resp = await _commandClient.ListAsync();
diff --git a/tests/ImportClientTests.cs b/tests/ImportClientTests.cs
index 8e6bb51..e8183f9 100644
--- a/tests/ImportClientTests.cs
+++ b/tests/ImportClientTests.cs
@@ -1,7 +1,6 @@
using System;
using System.Net.Http;
using System.Net.Http.Headers;
-using System.Text;
using System.Threading.Tasks;
using FluentAssertions;
using NUnit.Framework;
diff --git a/tests/PermissionTests.cs b/tests/PermissionTests.cs
index 10d7965..fd58384 100644
--- a/tests/PermissionTests.cs
+++ b/tests/PermissionTests.cs
@@ -4,7 +4,7 @@
using System.Threading.Tasks;
using FluentAssertions;
using NUnit.Framework;
-using StreamChat;
+using StreamChat.Clients;
using StreamChat.Exceptions;
using StreamChat.Models;
@@ -24,12 +24,27 @@ public class PermissionTests : TestBase
{
private const string TestPermissionDescription = "Test Permission";
+ private UserRequest _user1;
+ private UserRequest _user2;
+
[OneTimeSetUp]
[OneTimeTearDown]
public async Task CleanupAsync()
{
await DeleteCustomRolesAsync();
- await DeleteCustomPermissonsAsync();
+ await DeleteCustomPermissionsAsync();
+ }
+
+ [SetUp]
+ public async Task SetupAsync()
+ {
+ (_user1, _user2) = (await UpsertNewUserAsync(), await UpsertNewUserAsync());
+ }
+
+ [TearDown]
+ public async Task TeardownAsync()
+ {
+ await TryDeleteUsersAsync(_user1.Id, _user2.Id);
}
private async Task DeleteCustomRolesAsync()
@@ -49,7 +64,7 @@ private async Task DeleteCustomRolesAsync()
}
}
- private async Task DeleteCustomPermissonsAsync()
+ private async Task DeleteCustomPermissionsAsync()
{
var permResp = await _permissionClient.ListPermissionsAsync();
foreach (var perm in permResp.Permissions.Where(x => x.Description == TestPermissionDescription))
@@ -67,7 +82,7 @@ private async Task DeleteCustomPermissonsAsync()
}
[Test]
- public async Task TestRolesEnd2endAsync()
+ public async Task TestRolesEnd2EndAsync()
{
// Test create
var roleResp = await _permissionClient.CreateRoleAsync(Guid.NewGuid().ToString());
@@ -100,7 +115,7 @@ public async Task TestRolesEnd2endAsync()
}
[Test]
- public async Task TestPermissionsEnd2endAsync()
+ public async Task TestPermissionsEnd2EndAsync()
{
var permission = new Permission
{
@@ -143,7 +158,7 @@ public async Task TestPermissionsEnd2endAsync()
{
if (ex.Message.Contains("not found"))
{
- // Unfortounatly, the backend is too slow to propagate the permission creation
+ // Unfortunately, the backend is too slow to propagate the permission creation
// so this error message is expected. Facepalm.
return;
}
@@ -151,5 +166,174 @@ public async Task TestPermissionsEnd2endAsync()
throw;
}
}
+
+ [Test]
+ public async Task WhenUpdatingChannelGrantsExpectChannelGrantsChangedAsync()
+ {
+ ChannelTypeWithStringCommandsResponse tempChannelType = null;
+ try
+ {
+ tempChannelType = await CreateChannelTypeAsync();
+
+ // Expect delete-message-owner to not be present by default
+ tempChannelType.Grants.First(g => g.Key == "channel_member").Value.Should()
+ .NotContain("delete-message-owner");
+
+ var update = new ChannelTypeWithStringCommandsRequest
+ {
+ Grants = new Dictionary>
+ {
+ {
+ "channel_member", new List
+ {
+ "delete-message-owner",
+ }
+ },
+ },
+ };
+ await TryMultipleAsync(() => _channelTypeClient.UpdateChannelTypeAsync(tempChannelType.Name, update));
+
+ var getChannelType2 = await _channelTypeClient.GetChannelTypeAsync(tempChannelType.Name);
+
+ // Expect delete-message-owner to not be present by default
+ var channelMemberGrants = getChannelType2.Grants.First(g => g.Key == "channel_member").Value;
+ channelMemberGrants.Should().HaveCount(1);
+ channelMemberGrants.Should().Contain("delete-message-owner");
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ throw;
+ }
+ finally
+ {
+ try
+ {
+ if (tempChannelType != null)
+ {
+ await _channelTypeClient.DeleteChannelTypeAsync(tempChannelType.Name);
+ }
+ }
+ catch (Exception)
+ {
+ // ignored
+ }
+ }
+ }
+
+ [Test]
+ public async Task WhenUpdatingGrantsWithEmptyListExpectResetToDefaultAsync()
+ {
+ var tempChannelType = await CreateChannelTypeAsync();
+
+ var channelMemberInitialGrantsCounts
+ = tempChannelType.Grants.First(g => g.Key == "channel_member").Value.Count;
+
+ // We expect more than 1 grant by default
+ channelMemberInitialGrantsCounts.Should().NotBe(1);
+
+ // Wait for data propagation - channel type is sometimes not present immediately after creation
+ await TryMultipleAsync(async () =>
+ {
+ await _channelTypeClient.GetChannelTypeAsync(tempChannelType.Name);
+ });
+
+ // Override channel_members grants to replace with a single grant
+ var updateGrants = new ChannelTypeWithStringCommandsRequest
+ {
+ Grants = new Dictionary>
+ {
+ {
+ "channel_member", new List
+ {
+ "delete-message-owner",
+ }
+ },
+ },
+ };
+
+ // Try multiple times because it may fail due to data propagation
+ await TryMultipleAsync(async () =>
+ {
+ var updateChannelTypeResponse
+ = await _channelTypeClient.UpdateChannelTypeAsync(tempChannelType.Name, updateGrants);
+
+ // Confirm a single grant is present
+ updateChannelTypeResponse.Grants.First(g => g.Key == "channel_member").Value.Should().HaveCount(1);
+ });
+
+ // Try multiple times because it may fail due to data propagation
+ await TryMultipleAsync(async () =>
+ {
+ // Restore grants
+ var restoreGrantsRequest
+ = new ChannelTypeWithStringCommandsRequest
+ {
+ Grants = null,
+ };
+ var restoreGrantsResponse
+ = await _channelTypeClient.UpdateChannelTypeAsync(tempChannelType.Name, restoreGrantsRequest);
+
+ // Assert more than 1 grant is present
+ restoreGrantsResponse.Grants.First(g => g.Key == "channel_member").Value.Should().HaveCountGreaterThan(1);
+ });
+ }
+
+ [Test]
+ public async Task WhenAssigningAppScopedPermissionsExpectAppGrantsMatchingAsync()
+ {
+ var settings = new AppSettingsRequest
+ {
+ Grants = new Dictionary>
+ {
+ { "anonymous", new List() },
+ { "guest", new List() },
+ { "user", new List { "search-user", "mute-user" } },
+ { "admin", new List { "search-user", "mute-user", "ban-user" } },
+ },
+ };
+ await _appClient.UpdateAppSettingsAsync(settings);
+
+ var getAppResponse = await _appClient.GetAppSettingsAsync();
+ getAppResponse.App.Grants.Should().NotBeNull();
+ getAppResponse.App.Grants["anonymous"].Should().BeEmpty();
+ getAppResponse.App.Grants["guest"].Should().BeEmpty();
+ getAppResponse.App.Grants["user"].Should().BeEquivalentTo(new[] { "search-user", "mute-user" });
+ getAppResponse.App.Grants["admin"].Should()
+ .BeEquivalentTo(new[] { "search-user", "mute-user", "ban-user" });
+ }
+
+ [Test]
+ public async Task WhenUpdatingChannelConfigGrantsOverridesExpectGrantsOverridenAsync()
+ {
+ var channel = await CreateChannelAsync(createdByUserId: _user1.Id);
+ await _channelClient.AddMembersAsync(channel.Type, channel.Id, new[] { _user2.Id });
+
+ var request = new PartialUpdateChannelRequest
+ {
+ Set = new Dictionary
+ {
+ {
+ "config_overrides", new Dictionary
+ {
+ {
+ "grants", new Dictionary
+ {
+ {
+ "user", new List { "!add-links", "create-reaction" }
+ },
+ }
+ },
+ }
+ },
+ },
+ };
+ var partialUpdateChannelResponse
+ = await _channelClient.PartialUpdateAsync(channel.Type, channel.Id, request);
+
+ var channelResp = await _channelClient.GetOrCreateAsync(channel.Type, channel.Id, new ChannelGetRequest());
+ channelResp.Channel.Config.Grants["user"].Should()
+ .BeEquivalentTo(new List { "!add-links", "create-reaction" });
+ }
}
-}
+}
\ No newline at end of file
diff --git a/tests/TestBase.cs b/tests/TestBase.cs
index 78c0c5d..b62b457 100644
--- a/tests/TestBase.cs
+++ b/tests/TestBase.cs
@@ -26,6 +26,7 @@ public abstract class TestBase
protected static readonly ITaskClient _taskClient = TestClientFactory.GetTaskClient();
private readonly List _testChannels = new List();
+ private readonly List _testChannelTypes = new List();
[OneTimeTearDown]
public async Task OneTimeTearDown()
@@ -33,13 +34,36 @@ public async Task OneTimeTearDown()
const int chunkSize = 50;
var cids = _testChannels.Select(x => x.Cid).ToArray();
- for (int i = 0; i < cids.Length; i += chunkSize)
+ for (var i = 0; i < cids.Length; i += chunkSize)
{
var chunk = cids.Skip(i).Take(chunkSize).ToArray();
- var resp = await _channelClient.DeleteChannelsAsync(chunk, hardDelete: true);
- await WaitUntilTaskSucceedsAsync(resp.TaskId);
+ try
+ {
+ var resp = await _channelClient.DeleteChannelsAsync(chunk, hardDelete: true);
+ await WaitUntilTaskSucceedsAsync(resp.TaskId);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"Exception thrown while deleting channels: {e.Message}. Channels to delete: {string.Join(", ", chunk)}");
+ }
+ }
+
+ _testChannels.Clear();
+
+ foreach (var channelType in _testChannelTypes)
+ {
+ try
+ {
+ await _channelTypeClient.DeleteChannelTypeAsync(channelType);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"Exception thrown while deleting channel type: {e.Message}. Channel type to delete: {channelType}");
+ }
}
+
+ _testChannelTypes.Clear();
}
protected async Task WaitForAsync(Func> condition, int timeout = 5000, int delay = 500)
@@ -105,6 +129,27 @@ protected async Task TryDeleteChannelAsync(string cid)
await WaitUntilTaskSucceedsAsync(resp.TaskId);
}
+ protected async Task CreateChannelTypeAsync(string name = null, bool autoDelete = true)
+ {
+ if (string.IsNullOrEmpty(name))
+ {
+ name = Guid.NewGuid().ToString();
+ }
+
+ var channelType = await _channelTypeClient.CreateChannelTypeAsync(
+ new ChannelTypeWithStringCommandsRequest
+ {
+ Name = name,
+ });
+
+ if (autoDelete)
+ {
+ _testChannelTypes.Add(channelType.Name);
+ }
+
+ return channelType;
+ }
+
protected async Task TryDeleteUsersAsync(params string[] userIds)
{
try
@@ -128,7 +173,7 @@ await _userClient.DeleteManyAsync(
/// How many times to try
/// delay between a failed try
/// Throws ArgumentException If max attempts or timeout exceeds the limit
- protected async Task TryMultiple(Func testBody,
+ protected async Task TryMultipleAsync(Func testBody,
int attempts = 5, int attemptTimeoutMs = 500)
{
const int maxAttempts = 20;
diff --git a/tests/UserRolesTests.cs b/tests/UserRolesTests.cs
new file mode 100644
index 0000000..3974ec7
--- /dev/null
+++ b/tests/UserRolesTests.cs
@@ -0,0 +1,56 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using FluentAssertions;
+using NUnit.Framework;
+using StreamChat.Models;
+
+namespace StreamChatTests;
+
+internal class UserRolesTests : TestBase
+{
+ private UserRequest _user1;
+ private UserRequest _user2;
+
+ [SetUp]
+ public async Task SetupAsync()
+ {
+ (_user1, _user2) = (await UpsertNewUserAsync(), await UpsertNewUserAsync());
+ }
+
+ [TearDown]
+ public async Task TeardownAsync()
+ {
+ await TryDeleteUsersAsync(_user1.Id, _user2.Id);
+ }
+
+ [Test]
+ public async Task WhenUserIsAddedToChannelExpectChannelMemberRoleAssignedAsync()
+ {
+ var channel = await CreateChannelAsync(createdByUserId: _user1.Id);
+
+ var addMembersResponse = await _channelClient.AddMembersAsync(channel.Type, channel.Id, new[] { _user2.Id });
+ addMembersResponse.Members.First(m => m.UserId == _user2.Id).ChannelRole.Should()
+ .BeEquivalentTo("channel_member");
+
+ var getChannel = await _channelClient.GetOrCreateAsync(channel.Type, channel.Id, new ChannelGetRequest());
+
+ getChannel.Members.First(m => m.UserId == _user2.Id).ChannelRole.Should().BeEquivalentTo("channel_member");
+ }
+
+ [Test]
+ public async Task WhenAssigningARoleExpectRoleAssignedAsync()
+ {
+ var channel = await CreateChannelAsync(createdByUserId: _user1.Id);
+ await _channelClient.AddMembersAsync(channel.Type, channel.Id, new[] { _user2.Id });
+
+ var resp = await _channelClient.AssignRolesAsync(channel.Type, channel.Id, new AssignRoleRequest
+ {
+ AssignRoles = new List
+ {
+ new RoleAssignment { UserId = _user2.Id, ChannelRole = Role.ChannelModerator },
+ },
+ });
+ resp.Members.First().ChannelRole.Should().BeEquivalentTo("channel_moderator");
+ }
+}
\ No newline at end of file