Repository navigation
fix(llc): retry a message request the app was closed during (FLU-1136) #3135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
xsahil03x
wants to merge
2
commits into
master
Choose a base branch
from
sahil/flu-1136-android-parity-retry-messages-left-sending-updating-or
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
159 changes: 159 additions & 0 deletions
159
packages/stream_chat/test/src/client/channel/channel_interrupted_requests_test.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| import 'package:mocktail/mocktail.dart'; | ||
| import 'package:stream_chat/stream_chat.dart'; | ||
| import 'package:test/test.dart'; | ||
|
|
||
| import '../../fakes.dart'; | ||
| import '../../mocks.dart'; | ||
|
|
||
| void main() { | ||
| setUpAll(() => registerFallbackValue(FakeMessage())); | ||
|
|
||
| group('Channel.fromState', () { | ||
| test('marks a message left sending as failed to send', () { | ||
| final channel = _channel(messages: [_message(state: MessageState.sending)]); | ||
|
|
||
| expect( | ||
| channel.state!.messages.single.state, | ||
| MessageState.sendingFailed(skipPush: false, skipEnrichUrl: false), | ||
| ); | ||
| }); | ||
|
|
||
| test('settles a message left updating, so it no longer shows as in progress', () { | ||
| final channel = _channel(messages: [_message(state: MessageState.updating)]); | ||
|
|
||
| expect(channel.state!.messages.single.state, MessageState.updated); | ||
| }); | ||
|
|
||
| test('does not send a message left updating again', () async { | ||
| final client = MockStreamChatClientWithPersistence(); | ||
| when(() => client.chatPersistenceClient.getChannelThreads(any())).thenAnswer((_) async => {}); | ||
|
|
||
| _channel( | ||
| client: client, | ||
| messages: [_message(state: MessageState.updating)], | ||
| ); | ||
| await pumpEventQueue(); | ||
|
|
||
| verifyNever( | ||
| () => client.updateMessage( | ||
| any(), | ||
| skipPush: any(named: 'skipPush'), | ||
| skipEnrichUrl: any(named: 'skipEnrichUrl'), | ||
| ), | ||
| ); | ||
| verifyNever( | ||
| () => client.partialUpdateMessage( | ||
| any(), | ||
| set: any(named: 'set'), | ||
| unset: any(named: 'unset'), | ||
| skipEnrichUrl: any(named: 'skipEnrichUrl'), | ||
| ), | ||
| ); | ||
| }); | ||
|
|
||
| test('marks a message left deleting as failed to delete for the same scope', () { | ||
| final channel = _channel( | ||
| messages: [_message(state: MessageState.deleting(scope: const MessageDeleteScope.deleteForMe()))], | ||
| ); | ||
|
|
||
| expect( | ||
| channel.state!.messages.single.state, | ||
| MessageState.deletingFailed(scope: const MessageDeleteScope.deleteForMe()), | ||
| ); | ||
| }); | ||
|
|
||
| test('keeps the messages as they are when none was left in progress', () { | ||
| final messages = [_message(), _message(id: 'other-message')]; | ||
|
|
||
| final channel = _channel(messages: messages); | ||
|
|
||
| expect(channel.state!.messages, same(messages)); | ||
| }); | ||
|
|
||
| test('sends a stored thread reply left sending again', () async { | ||
| final client = MockStreamChatClientWithPersistence(); | ||
| final reply = _message(id: 'reply', parentId: 'parent', state: MessageState.sending); | ||
| when(() => client.chatPersistenceClient.getChannelThreads(any())).thenAnswer( | ||
| (_) async => { | ||
| 'parent': [reply], | ||
| }, | ||
| ); | ||
| _stubSend(client); | ||
|
|
||
| final channel = _channel(client: client); | ||
| await pumpEventQueue(); | ||
|
|
||
| verify( | ||
| () => client.sendMessage(any(that: isA<Message>().having((it) => it.id, 'id', 'reply')), any(), any()), | ||
| ).called(1); | ||
| expect(channel.state!.threads['parent']?.single.state.isCompleted, isTrue); | ||
| }); | ||
|
|
||
| test('sends a message left sending again', () async { | ||
| final client = MockStreamChatClientWithPersistence(); | ||
| when(() => client.chatPersistenceClient.getChannelThreads(any())).thenAnswer((_) async => {}); | ||
| _stubSend(client); | ||
|
|
||
| final channel = _channel( | ||
| client: client, | ||
| messages: [_message(state: MessageState.sending)], | ||
| ); | ||
| await pumpEventQueue(); | ||
|
|
||
| verify(() => client.sendMessage(any(), channel.id!, channel.type)).called(1); | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| Message _message({ | ||
| String id = 'message', | ||
| String? parentId, | ||
| MessageState state = MessageState.sent, | ||
| }) { | ||
| return Message( | ||
| id: id, | ||
| text: 'Hello', | ||
| parentId: parentId, | ||
| user: User(id: 'current-user'), | ||
| createdAt: DateTime(2026, 10, 29, 12), | ||
| state: state, | ||
| ); | ||
| } | ||
|
|
||
| void _stubSend(MockStreamChatClient client) { | ||
| when( | ||
| () => client.sendMessage( | ||
| any(), | ||
| any(), | ||
| any(), | ||
| skipPush: any(named: 'skipPush'), | ||
| skipEnrichUrl: any(named: 'skipEnrichUrl'), | ||
| ), | ||
| ).thenAnswer((invocation) async { | ||
| final message = invocation.positionalArguments.first as Message; | ||
| return SendMessageResponse()..message = message.copyWith(state: MessageState.sent); | ||
| }); | ||
| } | ||
|
|
||
| Channel _channel({MockStreamChatClient? client, List<Message> messages = const []}) { | ||
| final chatClient = client ?? MockStreamChatClient(); | ||
| when( | ||
| () => chatClient.detachedLogger(any()), | ||
| ).thenAnswer((invocation) => Logger.detached(invocation.positionalArguments.first as String)); | ||
| when(() => chatClient.logger).thenReturn(Logger.detached('client')); | ||
| when(() => chatClient.state).thenReturn(FakeClientState()); | ||
| when(() => chatClient.channelDeliveryReporter.submitForDelivery(any())).thenAnswer((_) async {}); | ||
| when(() => chatClient.retryPolicy).thenReturn( | ||
| RetryPolicy(shouldRetry: (_, __, ___) => false, delayFactor: Duration.zero), | ||
| ); | ||
|
|
||
| final channel = Channel.fromState( | ||
| chatClient, | ||
| ChannelState( | ||
| channel: ChannelModel(type: 'messaging', id: 'interrupted'), | ||
| messages: messages, | ||
| ), | ||
| ); | ||
| addTearDown(channel.dispose); | ||
| return channel; | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Script executed:
Repository: GetStream/stream-chat-flutter
Length of output: 7881
Script executed:
Repository: GetStream/stream-chat-flutter
Length of output: 6960
Script executed:
Repository: GetStream/stream-chat-flutter
Length of output: 7654
Script executed:
Repository: GetStream/stream-chat-flutter
Length of output: 6815
Script executed:
Repository: GetStream/stream-chat-flutter
Length of output: 5757
Script executed:
Repository: GetStream/stream-chat-flutter
Length of output: 1536
Keep the original send options when an interrupted send is retried.
_failInterruptedRequestssetsskipPush: falseandskipEnrichUrl: falsefor every interrupted send. TheSendingstate stores neither option. A send made withskipPush: truethat is interrupted and then retried after restart sends a push notification. Persist the send options with the outgoing state, or restore them before the retry.Why no edit is proposed
A localized edit is not established. Preserving the options needs a change to the
OutgoingState.sendingmodel and its serialization, not a one-line change inchannel_client_state.dart.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Valid, but out of scope for this PR. The
Sendingstate doesn't keepskipPushorskipEnrichUrl, so keeping them through a restart means changing the outgoing state model and how it's stored. The case is narrow (askipPushsend interrupted by the app being killed), so I'm leaving it as is.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@xsahil03x Understood. I accept deferring send-option persistence outside this PR. The finding remains valid, but I will not press for a change here.
Would you like me to open a follow-up GitHub issue for preserving
skipPushandskipEnrichUrlacross interrupted sends?You are interacting with an AI system.