Skip to content

Commit 1e3bfa0

Browse files
Implement Channel and User soft deletion + client-side muting (#18)
* implement soft delete for channel and user entities * Implement client side mute on C# side * Add missing delays to tests * Update test delay * unread messages test tweak * Another test fix * Unread messages test * One more unreads fix * Apply changes in Unity package, add samples for MutedUsersManager * Add soft parameters to Chat entity methods, add Restore() samples for User and Channel * Add missing field to Unity config asset * Soft delete samples * PubNub SDK v1.1.0 release. --------- Co-authored-by: PubNub Release Bot <[email protected]>
1 parent a69f435 commit 1e3bfa0

30 files changed

+1108
-53
lines changed

.pubnub.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
---
2-
version: v1.0.0
2+
version: v1.1.0
33
changelog:
4+
- date: 2025-10-29
5+
version: v1.1.0
6+
changes:
7+
- type: feature
8+
text: "Added the option of performing reversible soft deletion on Channel and User entities."
9+
- type: feature
10+
text: "Added a MutedUsersManager class that allows for muting specific user IDs on client side."
411
- date: 2025-10-02
512
version: v1.0.0
613
changes:

c-sharp-chat/PubnubChatApi/PubNubChatApi.Tests/ChannelTests.cs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public async Task TestUpdateChannel()
6666
}
6767

6868
[Test]
69-
public async Task TestDeleteChannel()
69+
public async Task TestHardDeleteChannel()
7070
{
7171
var channel = TestUtils.AssertOperation(await chat.CreatePublicConversation());
7272

@@ -75,14 +75,42 @@ public async Task TestDeleteChannel()
7575
var channelExists = await chat.GetChannel(channel.Id);
7676
Assert.False(channelExists.Error, "Couldn't fetch created channel from chat");
7777

78-
await channel.Delete();
78+
await channel.Delete(false);
7979

8080
await Task.Delay(3000);
8181

8282
var channelAfterDelete = await chat.GetChannel(channel.Id);
8383
Assert.True(channelAfterDelete.Error, "Fetched the supposedly-deleted channel from chat");
8484
}
8585

86+
[Test]
87+
public async Task TestSoftDeleteAndRestoreChannel()
88+
{
89+
var channel = TestUtils.AssertOperation(await chat.CreatePublicConversation());
90+
91+
await Task.Delay(3000);
92+
93+
var channelExists = await chat.GetChannel(channel.Id);
94+
Assert.False(channelExists.Error, "Couldn't fetch created channel from chat");
95+
96+
await channel.Delete(true);
97+
98+
await Task.Delay(3000);
99+
100+
var channelAfterDelete = await chat.GetChannel(channel.Id);
101+
Assert.False(channelAfterDelete.Error, "Channel should still exist after soft-delete");
102+
Assert.True(channelAfterDelete.Result.IsDeleted, "Channel should be marked as soft-deleted");
103+
104+
await channelAfterDelete.Result.Restore();
105+
Assert.False(channelAfterDelete.Result.IsDeleted, "Channel should be restored");
106+
107+
await Task.Delay(3000);
108+
109+
var channelAfterRestore = await chat.GetChannel(channel.Id);
110+
Assert.False(channelAfterRestore.Error, "Channel should still exist after restore");
111+
Assert.False(channelAfterRestore.Result.IsDeleted, "Channel fetched from server again should be marked as not-deleted after restore");
112+
}
113+
86114
[Test]
87115
public async Task TestLeaveChannel()
88116
{

c-sharp-chat/PubnubChatApi/PubNubChatApi.Tests/ChatTests.cs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public async Task TestCreateDirectConversation()
107107
directConversation.InviteesMemberships.First().UserId == convoUser.Id);
108108

109109
//Cleanup
110-
await directConversation.CreatedChannel.Delete();
110+
await directConversation.CreatedChannel.Delete(false);
111111
}
112112

113113
[Test]
@@ -126,7 +126,7 @@ public async Task TestCreateGroupConversation()
126126
x.UserId == convoUser1.Id && x.ChannelId == id));
127127

128128
//Cleanup
129-
await groupConversation.CreatedChannel.Delete();
129+
await groupConversation.CreatedChannel.Delete(false);
130130
}
131131

132132
[Test]
@@ -172,11 +172,20 @@ public async Task TestEmitEvent()
172172
[Test]
173173
public async Task TestGetUnreadMessagesCounts()
174174
{
175-
await channel.SendText("wololo");
175+
var testChannel = TestUtils.AssertOperation(await chat.CreatePublicConversation());
176+
await testChannel.Join();
177+
await testChannel.SendText("wololo");
178+
await testChannel.SendText("wololo1");
179+
await testChannel.SendText("wololo2");
180+
await testChannel.SendText("wololo3");
176181

177-
await Task.Delay(3000);
182+
await Task.Delay(6000);
183+
184+
var unreads =
185+
TestUtils.AssertOperation(await chat.GetUnreadMessagesCounts(filter:$"channel.id LIKE \"{testChannel.Id}\""));
186+
Assert.True(unreads.Any(x => x.ChannelId == testChannel.Id && x.Count == 4));
178187

179-
Assert.True(TestUtils.AssertOperation(await chat.GetUnreadMessagesCounts(limit: 50)).Any(x => x.ChannelId == channel.Id && x.Count > 0));
188+
await testChannel.Delete(false);
180189
}
181190

182191
[Test]
@@ -200,7 +209,7 @@ public async Task TestMarkAllMessagesAsRead()
200209
var counts = TestUtils.AssertOperation(await chat.GetUnreadMessagesCounts());
201210

202211
markTestChannel.Leave();
203-
await markTestChannel.Delete();
212+
await markTestChannel.Delete(false);
204213

205214
Assert.False(counts.Any(x => x.ChannelId == markTestChannel.Id && x.Count > 0));
206215
}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
using PubnubApi;
2+
using PubnubChatApi;
3+
using Channel = PubnubChatApi.Channel;
4+
5+
namespace PubNubChatApi.Tests;
6+
7+
public class ClientSideMuteTests
8+
{
9+
private Chat chat1;
10+
private User user1;
11+
12+
private Chat chat2;
13+
private User user2;
14+
15+
private Channel channel1;
16+
private Channel channel2;
17+
18+
[SetUp]
19+
public async Task Setup()
20+
{
21+
chat1 = TestUtils.AssertOperation(await Chat.CreateInstance(new PubnubChatConfig(),
22+
new PNConfiguration(new UserId("client_side_mute_test_user_1"))
23+
{
24+
PublishKey = PubnubTestsParameters.PublishKey,
25+
SubscribeKey = PubnubTestsParameters.SubscribeKey
26+
}));
27+
chat2 = TestUtils.AssertOperation(await Chat.CreateInstance(new PubnubChatConfig(),
28+
new PNConfiguration(new UserId("client_side_mute_test_user_2"))
29+
{
30+
PublishKey = PubnubTestsParameters.PublishKey,
31+
SubscribeKey = PubnubTestsParameters.SubscribeKey
32+
}));
33+
user1 = TestUtils.AssertOperation(await chat1.GetCurrentUser());
34+
user2 = TestUtils.AssertOperation(await chat2.GetCurrentUser());
35+
channel1 = TestUtils.AssertOperation(await chat1.CreatePublicConversation("mute_tests_channel"));
36+
await Task.Delay(3000);
37+
channel2 = TestUtils.AssertOperation(await chat2.GetChannel("mute_tests_channel"));
38+
}
39+
40+
[TearDown]
41+
public async Task CleanUp()
42+
{
43+
await channel1.Leave();
44+
await channel2.Leave();
45+
await user1.DeleteUser(false);
46+
chat1.Destroy();
47+
await user2.DeleteUser(false);
48+
chat2.Destroy();
49+
await channel1.Delete(false);
50+
await channel2.Delete(false);
51+
await Task.Delay(4000);
52+
}
53+
54+
[Test]
55+
public async Task TestMuteInMessages()
56+
{
57+
var messageReset = new ManualResetEvent(false);
58+
channel1.OnMessageReceived += message =>
59+
{
60+
messageReset.Set();
61+
};
62+
await channel1.Join();
63+
64+
await Task.Delay(3000);
65+
66+
await channel2.SendText("This message should not be muted.");
67+
var received = messageReset.WaitOne(10000);
68+
Assert.True(received, "Didn't receive message from not-yet-muted user.");
69+
70+
messageReset = new ManualResetEvent(false);
71+
await chat1.MutedUsersManager.MuteUser(user2.Id);
72+
await channel2.SendText("This message should be muted.");
73+
received = messageReset.WaitOne(10000);
74+
Assert.False(received, "Received message from muted user.");
75+
76+
messageReset = new ManualResetEvent(false);
77+
await chat1.MutedUsersManager.UnMuteUser(user2.Id);
78+
await channel2.SendText("This message shouldn't be muted now.");
79+
received = messageReset.WaitOne(10000);
80+
Assert.True(received, "Didn't receive message from un-muted user.");
81+
}
82+
83+
[Test]
84+
public async Task TestMuteInMessageHistory()
85+
{
86+
await channel2.SendText("One");
87+
await channel2.SendText("Two");
88+
await channel2.SendText("Three");
89+
90+
await Task.Delay(6000);
91+
92+
var history = TestUtils.AssertOperation(await channel1.GetMessageHistory("99999999999999999", "00000000000000000", 3));
93+
Assert.True(history.Count == 3, "Didn't get message history for non-muted user");
94+
95+
await chat1.MutedUsersManager.MuteUser(user2.Id);
96+
97+
history = TestUtils.AssertOperation(await channel1.GetMessageHistory("99999999999999999", "00000000000000000", 3));
98+
Assert.True(history.Count == 0, "Got message history for muted user");
99+
100+
await chat1.MutedUsersManager.UnMuteUser(user2.Id);
101+
102+
history = TestUtils.AssertOperation(await channel1.GetMessageHistory("99999999999999999", "00000000000000000", 3));
103+
Assert.True(history.Count == 3, "Didn't get message history for un-muted user");
104+
}
105+
106+
[Test]
107+
public async Task TestMuteInEvents()
108+
{
109+
var eventReset = new ManualResetEvent(false);
110+
channel1.OnCustomEvent += chatEvent =>
111+
{
112+
if (chatEvent.Type != PubnubChatEventType.Custom)
113+
{
114+
return;
115+
}
116+
eventReset.Set();
117+
};
118+
channel1.SetListeningForCustomEvents(true);
119+
120+
await Task.Delay(3000);
121+
122+
await chat2.EmitEvent(PubnubChatEventType.Custom, channel2.Id, "{\"test\":\"not-muted\"}");
123+
var received = eventReset.WaitOne(10000);
124+
Assert.True(received, "Didn't receive event from not-yet-muted user.");
125+
126+
eventReset = new ManualResetEvent(false);
127+
await chat1.MutedUsersManager.MuteUser(user2.Id);
128+
await chat2.EmitEvent(PubnubChatEventType.Custom, channel2.Id, "{\"test\":\"muted\"}");
129+
received = eventReset.WaitOne(10000);
130+
Assert.False(received, "Received event from muted user.");
131+
132+
eventReset = new ManualResetEvent(false);
133+
await chat1.MutedUsersManager.UnMuteUser(user2.Id);
134+
await chat2.EmitEvent(PubnubChatEventType.Custom, channel2.Id, "{\"test\":\"un-muted\"}");
135+
received = eventReset.WaitOne(10000);
136+
Assert.True(received, "Didn't receive event from un-muted user.");
137+
}
138+
139+
[Test]
140+
public async Task TestMuteInEventsHistory()
141+
{
142+
await chat2.EmitEvent(PubnubChatEventType.Custom, channel2.Id, "{\"test\":\"one\"}");
143+
await chat2.EmitEvent(PubnubChatEventType.Custom, channel2.Id, "{\"test\":\"two\"}");
144+
await chat2.EmitEvent(PubnubChatEventType.Custom, channel2.Id, "{\"test\":\"three\"}");
145+
146+
var history = TestUtils.AssertOperation(await chat1.GetEventsHistory(channel1.Id,"99999999999999999", "00000000000000000", 3));
147+
Assert.True(history.Events.Count == 3, "Didn't get events history for non-muted user");
148+
149+
await chat1.MutedUsersManager.MuteUser(user2.Id);
150+
151+
history = TestUtils.AssertOperation(await chat1.GetEventsHistory(channel1.Id,"99999999999999999", "00000000000000000", 3));
152+
Assert.True(history.Events.Count == 0, "Got events history for muted user");
153+
154+
await chat1.MutedUsersManager.UnMuteUser(user2.Id);
155+
156+
history = TestUtils.AssertOperation(await chat1.GetEventsHistory(channel1.Id,"99999999999999999", "00000000000000000", 3));
157+
Assert.True(history.Events.Count == 3, "Didn't get events history for un-muted user");
158+
}
159+
160+
[Test]
161+
public async Task TestMuteListSyncing()
162+
{
163+
var userId = Guid.NewGuid().ToString();
164+
var chatWithSync = TestUtils.AssertOperation(await Chat.CreateInstance(new PubnubChatConfig(syncMutedUsers:true),
165+
new PNConfiguration(new UserId(userId))
166+
{
167+
PublishKey = PubnubTestsParameters.PublishKey,
168+
SubscribeKey = PubnubTestsParameters.SubscribeKey
169+
}));
170+
TestUtils.AssertOperation(await chatWithSync.MutedUsersManager.MuteUser(user1.Id));
171+
172+
chatWithSync.Destroy();
173+
174+
await Task.Delay(3000);
175+
var chatWithSyncSecondInstance = TestUtils.AssertOperation(await Chat.CreateInstance(new PubnubChatConfig(syncMutedUsers:true),
176+
new PNConfiguration(new UserId(userId))
177+
{
178+
PublishKey = PubnubTestsParameters.PublishKey,
179+
SubscribeKey = PubnubTestsParameters.SubscribeKey
180+
}));
181+
await Task.Delay(5000);
182+
Assert.True(chatWithSyncSecondInstance.MutedUsersManager.MutedUsers.Contains(user1.Id), "Second instance of chat didn't have synced mute list");
183+
184+
chatWithSyncSecondInstance.Destroy();
185+
await chatWithSyncSecondInstance.DeleteUser(userId);
186+
}
187+
}

c-sharp-chat/PubnubChatApi/PubNubChatApi.Tests/MessageTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ public async Task TestDeleteMessage()
160160
var manualReceivedEvent = new ManualResetEvent(false);
161161
channel.OnMessageReceived += async message =>
162162
{
163-
message.Delete(true);
163+
await message.Delete(true);
164164

165165
await Task.Delay(2000);
166166

c-sharp-chat/PubnubChatApi/PubNubChatApi.Tests/PubnubTestsParameters.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ public static class PubnubTestsParameters
66
private static readonly string EnvSubscribeKey = Environment.GetEnvironmentVariable("PN_SUB_KEY");
77
private static readonly string EnvSecretKey = Environment.GetEnvironmentVariable("PN_SEC_KEY");
88

9-
public static readonly string PublishKey = string.IsNullOrEmpty(EnvPublishKey) ? "pub-c-79c582a2-d7a4-4ee7-9f28-7a6f1b7fa11c" : EnvPublishKey;
10-
public static readonly string SubscribeKey = string.IsNullOrEmpty(EnvSubscribeKey) ? "sub-c-ca0af928-f4f9-474c-b56e-d6be81bf8ed0" : EnvSubscribeKey;
9+
public static readonly string PublishKey = string.IsNullOrEmpty(EnvPublishKey) ? "demo-36" : EnvPublishKey;
10+
public static readonly string SubscribeKey = string.IsNullOrEmpty(EnvSubscribeKey) ? "demo-36" : EnvSubscribeKey;
1111
public static readonly string SecretKey = string.IsNullOrEmpty(EnvSecretKey) ? "demo-36" : EnvSecretKey;
1212
}

c-sharp-chat/PubnubChatApi/PubNubChatApi.Tests/ThreadsTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public async Task CleanUp()
3232
{
3333
channel.Leave();
3434
await Task.Delay(3000);
35-
await channel.Delete();
35+
await channel.Delete(false);
3636
chat.Destroy();
3737
await Task.Delay(3000);
3838
}

c-sharp-chat/PubnubChatApi/PubNubChatApi.Tests/UserTests.cs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,17 +89,17 @@ await testUser.Update(new ChatUserData()
8989
Assert.True(updated);
9090

9191
//Cleanup
92-
await testUser.DeleteUser();
92+
await testUser.DeleteUser(false);
9393
}
9494

9595
[Test]
96-
public async Task TestUserDelete()
96+
public async Task TestHardUserDelete()
9797
{
9898
var someUser = TestUtils.AssertOperation(await chat.CreateUser(Guid.NewGuid().ToString()));
9999

100100
TestUtils.AssertOperation(await chat.GetUser(someUser.Id));
101101

102-
await someUser.DeleteUser();
102+
await someUser.DeleteUser(false);
103103

104104
await Task.Delay(3000);
105105

@@ -109,6 +109,34 @@ public async Task TestUserDelete()
109109
Assert.Fail("Got the freshly deleted user");
110110
}
111111
}
112+
113+
[Test]
114+
public async Task TestSoftDeleteAndRestoreUser()
115+
{
116+
var testUser = TestUtils.AssertOperation(await chat.CreateUser(Guid.NewGuid().ToString()));
117+
118+
await Task.Delay(3000);
119+
120+
var userExists = await chat.GetUser(testUser.Id);
121+
Assert.False(userExists.Error, "Couldn't fetch created user from chat");
122+
123+
await testUser.DeleteUser(true);
124+
125+
await Task.Delay(3000);
126+
127+
var userAfterDelete = await chat.GetUser(testUser.Id);
128+
Assert.False(userAfterDelete.Error, "User should still exist after soft-delete");
129+
Assert.True(userAfterDelete.Result.IsDeleted, "user should be marked as soft-deleted");
130+
131+
await userAfterDelete.Result.Restore();
132+
Assert.False(userAfterDelete.Result.IsDeleted, "User should be restored");
133+
134+
await Task.Delay(3000);
135+
136+
var userAfterRestore = await chat.GetUser(testUser.Id);
137+
Assert.False(userAfterRestore.Error, "User should still exist after restore");
138+
Assert.False(userAfterRestore.Result.IsDeleted, "User fetched from server again should be marked as not-deleted after restore");
139+
}
112140

113141
[Test]
114142
public async Task TestUserWherePresent()

0 commit comments

Comments
 (0)