|
| 1 | +# frozen_string_literal: true |
| 2 | + |
| 3 | +require 'securerandom' |
| 4 | +require 'stream-chat' |
| 5 | + |
| 6 | +describe StreamChat::ChannelBatchUpdater do |
| 7 | + def loop_times(times) |
| 8 | + loop do |
| 9 | + begin |
| 10 | + yield() |
| 11 | + return |
| 12 | + rescue StandardError, RSpec::Expectations::ExpectationNotMetError |
| 13 | + raise if times.zero? |
| 14 | + end |
| 15 | + |
| 16 | + sleep(1) |
| 17 | + times -= 1 |
| 18 | + end |
| 19 | + end |
| 20 | + |
| 21 | + def wait_for_task(task_id, timeout_seconds: 120) |
| 22 | + sleep(2) # Initial delay |
| 23 | + |
| 24 | + timeout_seconds.times do |i| |
| 25 | + begin |
| 26 | + task = @client.get_task(task_id) |
| 27 | + rescue StandardError => e |
| 28 | + if i < 10 |
| 29 | + sleep(1) |
| 30 | + next |
| 31 | + end |
| 32 | + raise e |
| 33 | + end |
| 34 | + |
| 35 | + expect(task['id']).to eq(task_id) |
| 36 | + |
| 37 | + case task['status'] |
| 38 | + when 'waiting', 'pending', 'running' |
| 39 | + sleep(1) |
| 40 | + when 'completed' |
| 41 | + return task |
| 42 | + when 'failed' |
| 43 | + if task['result']&.dig('description')&.downcase&.include?('rate limit') |
| 44 | + sleep(2) |
| 45 | + next |
| 46 | + end |
| 47 | + raise "Task failed with result: #{task['result']}" |
| 48 | + end |
| 49 | + end |
| 50 | + |
| 51 | + raise "Task did not complete within #{timeout_seconds} seconds" |
| 52 | + end |
| 53 | + |
| 54 | + before(:all) do |
| 55 | + @client = StreamChat::Client.from_env |
| 56 | + @created_users = [] |
| 57 | + end |
| 58 | + |
| 59 | + before(:each) do |
| 60 | + @random_users = [{ id: SecureRandom.uuid, name: 'user1' }, { id: SecureRandom.uuid, name: 'user2' }] |
| 61 | + @random_user = { id: SecureRandom.uuid } |
| 62 | + |
| 63 | + users_to_insert = [@random_users[0], @random_users[1], @random_user] |
| 64 | + |
| 65 | + @created_users.push(*users_to_insert.map { |u| u[:id] }) |
| 66 | + @client.upsert_users(users_to_insert) |
| 67 | + |
| 68 | + @channel1 = @client.channel('messaging', channel_id: SecureRandom.uuid, data: { test: true }) |
| 69 | + @channel1.create(@random_user[:id]) |
| 70 | + |
| 71 | + @channel2 = @client.channel('messaging', channel_id: SecureRandom.uuid, data: { test: true }) |
| 72 | + @channel2.create(@random_user[:id]) |
| 73 | + end |
| 74 | + |
| 75 | + after(:each) do |
| 76 | + @channel1&.delete |
| 77 | + rescue StreamChat::StreamAPIException |
| 78 | + # Ignore if channel already deleted |
| 79 | + ensure |
| 80 | + begin |
| 81 | + @channel2&.delete |
| 82 | + rescue StreamChat::StreamAPIException |
| 83 | + # Ignore if channel already deleted |
| 84 | + end |
| 85 | + end |
| 86 | + |
| 87 | + after(:all) do |
| 88 | + curr_idx = 0 |
| 89 | + batch_size = 25 |
| 90 | + |
| 91 | + slice = @created_users.slice(0, batch_size) |
| 92 | + |
| 93 | + while !slice.nil? && !slice.empty? |
| 94 | + @client.delete_users(slice, user: StreamChat::HARD_DELETE, messages: StreamChat::HARD_DELETE) |
| 95 | + |
| 96 | + curr_idx += batch_size |
| 97 | + slice = @created_users.slice(curr_idx, batch_size) |
| 98 | + end |
| 99 | + end |
| 100 | + |
| 101 | + describe 'Client#update_channels_batch' do |
| 102 | + it 'returns error if options is empty' do |
| 103 | + expect { @client.update_channels_batch({}) }.to raise_error(StreamChat::StreamAPIException) |
| 104 | + end |
| 105 | + |
| 106 | + it 'batch updates channels with valid options' do |
| 107 | + response = @client.update_channels_batch( |
| 108 | + { |
| 109 | + operation: 'addMembers', |
| 110 | + filter: { cid: { '$in' => [@channel1.cid, @channel2.cid] } }, |
| 111 | + members: [@random_users[0][:id]] |
| 112 | + } |
| 113 | + ) |
| 114 | + |
| 115 | + expect(response['task_id']).not_to be_empty |
| 116 | + end |
| 117 | + end |
| 118 | + |
| 119 | + describe 'ChannelBatchUpdater#add_members' do |
| 120 | + it 'adds members to channels matching filter' do |
| 121 | + updater = @client.channel_batch_updater |
| 122 | + |
| 123 | + members = @random_users.map { |u| u[:id] } |
| 124 | + response = updater.add_members( |
| 125 | + { cid: { '$in' => [@channel1.cid, @channel2.cid] } }, |
| 126 | + members |
| 127 | + ) |
| 128 | + |
| 129 | + expect(response['task_id']).not_to be_empty |
| 130 | + task_id = response['task_id'] |
| 131 | + |
| 132 | + wait_for_task(task_id) |
| 133 | + |
| 134 | + # Verify members were added |
| 135 | + loop_times(120) do |
| 136 | + ch1_state = @channel1.query |
| 137 | + ch1_member_ids = ch1_state['members'].map { |m| m['user_id'] } |
| 138 | + |
| 139 | + members.each do |member_id| |
| 140 | + expect(ch1_member_ids).to include(member_id) |
| 141 | + end |
| 142 | + end |
| 143 | + end |
| 144 | + end |
| 145 | + |
| 146 | + describe 'ChannelBatchUpdater#remove_members' do |
| 147 | + it 'removes members from channels matching filter' do |
| 148 | + # First add both users as members to both channels |
| 149 | + members_to_add = @random_users.map { |u| u[:id] } |
| 150 | + @channel1.add_members(members_to_add) |
| 151 | + @channel2.add_members(members_to_add) |
| 152 | + |
| 153 | + # Verify members were added |
| 154 | + loop_times(60) do |
| 155 | + ch1_state = @channel1.query |
| 156 | + expect(ch1_state['members'].length).to eq(2) |
| 157 | + |
| 158 | + ch2_state = @channel2.query |
| 159 | + expect(ch2_state['members'].length).to eq(2) |
| 160 | + end |
| 161 | + |
| 162 | + # Verify member IDs match |
| 163 | + ch1_state = @channel1.query |
| 164 | + ch1_member_ids = ch1_state['members'].map { |m| m['user_id'] } |
| 165 | + expect(ch1_member_ids).to match_array(members_to_add) |
| 166 | + |
| 167 | + ch2_state = @channel2.query |
| 168 | + ch2_member_ids = ch2_state['members'].map { |m| m['user_id'] } |
| 169 | + expect(ch2_member_ids).to match_array(members_to_add) |
| 170 | + |
| 171 | + # Now remove one member using batch updater |
| 172 | + updater = @client.channel_batch_updater |
| 173 | + member_to_remove = members_to_add[0] |
| 174 | + |
| 175 | + response = updater.remove_members( |
| 176 | + { cid: { '$in' => [@channel1.cid, @channel2.cid] } }, |
| 177 | + [member_to_remove] |
| 178 | + ) |
| 179 | + |
| 180 | + expect(response['task_id']).not_to be_empty |
| 181 | + task_id = response['task_id'] |
| 182 | + |
| 183 | + wait_for_task(task_id) |
| 184 | + |
| 185 | + # Verify member was removed |
| 186 | + loop_times(120) do |
| 187 | + ch1_state = @channel1.query |
| 188 | + ch1_member_ids = ch1_state['members'].map { |m| m['user_id'] } |
| 189 | + |
| 190 | + expect(ch1_member_ids).not_to include(member_to_remove) |
| 191 | + end |
| 192 | + end |
| 193 | + end |
| 194 | + |
| 195 | + describe 'ChannelBatchUpdater#archive' do |
| 196 | + it 'archives channels for specified members' do |
| 197 | + # First add both users as members to both channels |
| 198 | + members_to_add = @random_users.map { |u| u[:id] } |
| 199 | + @channel1.add_members(members_to_add) |
| 200 | + @channel2.add_members(members_to_add) |
| 201 | + |
| 202 | + # Wait for members to be added |
| 203 | + loop_times(60) do |
| 204 | + ch1_state = @channel1.query |
| 205 | + expect(ch1_state['members'].length).to eq(2) |
| 206 | + end |
| 207 | + |
| 208 | + # Archive channels for one member |
| 209 | + updater = @client.channel_batch_updater |
| 210 | + member_to_archive = members_to_add[0] |
| 211 | + |
| 212 | + response = updater.archive( |
| 213 | + { cid: { '$in' => [@channel1.cid, @channel2.cid] } }, |
| 214 | + [member_to_archive] |
| 215 | + ) |
| 216 | + |
| 217 | + expect(response['task_id']).not_to be_empty |
| 218 | + task_id = response['task_id'] |
| 219 | + |
| 220 | + wait_for_task(task_id) |
| 221 | + |
| 222 | + # Verify archived_at is set for the member |
| 223 | + loop_times(120) do |
| 224 | + ch1_state = @channel1.query |
| 225 | + member = ch1_state['members'].find { |m| m['user_id'] == member_to_archive } |
| 226 | + |
| 227 | + expect(member).not_to be_nil |
| 228 | + expect(member['archived_at']).not_to be_nil |
| 229 | + end |
| 230 | + end |
| 231 | + end |
| 232 | +end |
0 commit comments