Files
stream-chat-flutter/packages/stream_chat/test/src/client/client_test.dart
T
2021-08-20 11:03:04 +02:00

2318 lines
75 KiB
Dart

import 'package:mocktail/mocktail.dart';
import 'package:stream_chat/src/client/client.dart';
import 'package:stream_chat/src/core/api/device_api.dart';
import 'package:stream_chat/src/core/api/requests.dart';
import 'package:stream_chat/src/core/api/responses.dart';
import 'package:stream_chat/src/core/error/error.dart';
import 'package:stream_chat/src/core/http/token.dart';
import 'package:stream_chat/src/core/models/channel_model.dart';
import 'package:stream_chat/src/core/models/event.dart';
import 'package:stream_chat/src/core/models/filter.dart';
import 'package:stream_chat/src/core/models/message.dart';
import 'package:stream_chat/src/core/models/own_user.dart';
import 'package:stream_chat/src/core/models/user.dart';
import 'package:stream_chat/src/event_type.dart';
import 'package:stream_chat/src/ws/connection_status.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:test/scaffolding.dart';
import 'package:test/test.dart';
import '../fakes.dart';
import '../matchers.dart';
import '../mocks.dart';
import '../utils.dart';
void main() {
group('Fake web-socket connection functions', () {
const apiKey = 'test-api-key';
late final api = FakeChatApi();
late StreamChatClient client;
setUpAll(() {
// fallback values
registerFallbackValue<User>(FakeUser());
});
setUp(() {
final ws = FakeWebSocket();
client = StreamChatClient(apiKey, ws: ws, chatApi: api);
});
tearDown(() {
client.dispose();
});
test('`.connectUser` should work fine', () async {
final user = User(id: 'test-user-id');
final token = Token.development(user.id).rawValue;
expectLater(
// skipping first seed status -> ConnectionStatus.disconnected
client.wsConnectionStatusStream.skip(1),
emitsInOrder([
ConnectionStatus.connecting,
ConnectionStatus.connected,
]),
);
final res = await client.connectUser(user, token);
expect(res, isNotNull);
expect(res, isSameUserAs(user));
});
test('`.connectUserWithProvider` should work fine', () async {
final user = User(id: 'test-user-id');
Future<String> tokenProvider(String userId) async {
expect(userId, user.id);
return Token.development(userId).rawValue;
}
expectLater(
// skipping first seed status -> ConnectionStatus.disconnected
client.wsConnectionStatusStream.skip(1),
emitsInOrder([
ConnectionStatus.connecting,
ConnectionStatus.connected,
]),
);
final res = await client.connectUserWithProvider(user, tokenProvider);
expect(res, isNotNull);
expect(res, isSameUserAs(user));
});
group('`.connectGuestUser`', () {
test('should work fine', () async {
final user = User(id: 'test-user-id');
final token = Token.development(user.id).rawValue;
when(() => api.guest.getGuestUser(any(that: isSameUserAs(user))))
.thenAnswer(
(_) async => ConnectGuestUserResponse()
..user = user
..accessToken = token,
);
expectLater(
// skipping first seed status -> ConnectionStatus.disconnected
client.wsConnectionStatusStream.skip(1),
emitsInOrder([
ConnectionStatus.connecting,
ConnectionStatus.connected,
]),
);
final res = await client.connectGuestUser(user);
expect(res, isNotNull);
expect(res, isSameUserAs(user));
verify(
() => api.guest.getGuestUser(any(that: isSameUserAs(user))),
).called(1);
});
test('should throw if `.getGuestUser` fails', () async {
final user = User(id: 'test-user-id');
when(() => api.guest.getGuestUser(any(that: isSameUserAs(user))))
.thenThrow(StreamChatNetworkError(ChatErrorCode.inputError));
expectLater(
client.wsConnectionStatusStream,
emitsInOrder([
// only emits the seed -> disconnected status
// as the call never reaches `ws.connect`
ConnectionStatus.disconnected,
]),
);
try {
await client.connectGuestUser(user);
} catch (e) {
expect(e, isA<StreamChatNetworkError>());
}
verify(
() => api.guest.getGuestUser(any(that: isSameUserAs(user))),
).called(1);
});
});
test('`.connectAnonymousUser` should work fine', () async {
expectLater(
// skipping first seed status -> ConnectionStatus.disconnected
client.wsConnectionStatusStream.skip(1),
emitsInOrder([
ConnectionStatus.connecting,
ConnectionStatus.connected,
]),
);
final res = await client.connectAnonymousUser();
expect(res, isNotNull);
});
group('`.openConnection`', () {
test('should throw if state does not contain user', () async {
expect(client.state.currentUser, isNull);
try {
await client.openConnection();
} catch (e) {
expect(e, isA<AssertionError>());
}
});
test('should throw if connection is already in progress', () async {
expect(client.state.currentUser, isNull);
try {
await client.connectAnonymousUser();
await client.openConnection();
} catch (e) {
expect(e, isA<StreamChatError>());
final err = e as StreamChatError;
expect(
err.message.contains('Connection already in progress for'),
isTrue,
);
}
});
test('should throw if connection is already available', () async {
expect(client.state.currentUser, isNull);
try {
await client.connectAnonymousUser();
// waiting 300ms for `wsConnectionStatusStream` to emit
await delay(300);
await client.openConnection();
} catch (e) {
expect(e, isA<StreamChatError>());
final err = e as StreamChatError;
expect(
err.message.contains('Connection already available for'),
isTrue,
);
}
});
test('should open connection for closed connection', () async {
expectLater(
client.wsConnectionStatusStream.skip(1),
emitsInOrder([
// initial connectUser
ConnectionStatus.connecting,
ConnectionStatus.connected,
// close connection
ConnectionStatus.disconnected,
// open connection
ConnectionStatus.connecting,
ConnectionStatus.connected,
]),
);
await client.connectAnonymousUser();
// waiting 300ms for `wsConnectionStatusStream` to emit
await delay(300);
client.closeConnection();
await client.openConnection();
});
});
});
group('Fake web-socket connection functions failure', () {
const apiKey = 'test-api-key';
late final api = FakeChatApi();
late StreamChatClient client;
setUpAll(() {
// fallback values
registerFallbackValue<User>(FakeUser());
});
setUp(() {
final ws = FakeWebSocketWithConnectionError();
client = StreamChatClient(apiKey, chatApi: api, ws: ws);
});
tearDown(() {
client.dispose();
});
test('`.connectUser` should throw if `ws.connect` fails', () async {
final user = User(id: 'test-user-id');
final token = Token.development(user.id).rawValue;
try {
await client.connectUser(user, token);
} catch (e) {
expect(e, isA<StreamWebSocketError>());
}
});
test(
'`.connectUserWithProvider` should throw if `ws.connect` fails',
() async {
final user = User(id: 'test-user-id');
Future<String> tokenProvider(String userId) async {
expect(userId, user.id);
return Token.development(userId).rawValue;
}
try {
await client.connectUserWithProvider(user, tokenProvider);
} catch (e) {
expect(e, isA<StreamWebSocketError>());
}
},
);
test('`.connectGuestUser` should throw if `ws.connect` fails', () async {
final user = User(id: 'test-user-id');
final token = Token.development(user.id).rawValue;
when(() => api.guest.getGuestUser(any(that: isSameUserAs(user))))
.thenAnswer(
(_) async => ConnectGuestUserResponse()
..user = user
..accessToken = token,
);
try {
await client.connectGuestUser(user);
} catch (e) {
expect(e, isA<StreamWebSocketError>());
}
verify(
() => api.guest.getGuestUser(any(that: isSameUserAs(user))),
).called(1);
});
test(
'`.connectAnonymousUser` should throw if `ws.connect` fails',
() async {
try {
await client.connectAnonymousUser();
} catch (e) {
expect(e, isA<StreamWebSocketError>());
}
},
);
});
group('Connect user calls with `connectWebSocket`: false', () {
const apiKey = 'test-api-key';
late final api = FakeChatApi();
late StreamChatClient client;
setUpAll(() {
// fallback values
registerFallbackValue<User>(FakeUser());
});
setUp(() {
client = StreamChatClient(apiKey, chatApi: api);
});
tearDown(() {
client.dispose();
});
test('`.connectUser` should succeed without connecting', () async {
final user = User(id: 'test-user-id');
final token = Token.development(user.id).rawValue;
final res = await client.connectUser(
user,
token,
connectWebSocket: false,
);
expect(res, isSameUserAs(user));
expect(client.wsConnectionStatus, ConnectionStatus.disconnected);
});
test(
'`.connectUserWithProvider` should succeed without connecting',
() async {
final user = User(id: 'test-user-id');
Future<String> tokenProvider(String userId) async {
expect(userId, user.id);
return Token.development(userId).rawValue;
}
final res = await client.connectUserWithProvider(
user,
tokenProvider,
connectWebSocket: false,
);
expect(res, isSameUserAs(user));
expect(client.wsConnectionStatus, ConnectionStatus.disconnected);
},
);
test('`.connectGuestUser` should succeed without connecting', () async {
final user = User(id: 'test-user-id');
final token = Token.development(user.id).rawValue;
when(() => api.guest.getGuestUser(any(that: isSameUserAs(user))))
.thenAnswer(
(_) async => ConnectGuestUserResponse()
..user = user
..accessToken = token,
);
final res = await client.connectGuestUser(
user,
connectWebSocket: false,
);
expect(res, isSameUserAs(user));
expect(client.wsConnectionStatus, ConnectionStatus.disconnected);
verify(
() => api.guest.getGuestUser(any(that: isSameUserAs(user))),
).called(1);
});
test(
'`.connectAnonymousUser` should succeed without connecting',
() async {
final res = await client.connectAnonymousUser(
connectWebSocket: false,
);
expect(res, isNotNull);
expect(client.wsConnectionStatus, ConnectionStatus.disconnected);
},
);
});
group('Fake web-socket connection function with failure and persistence', () {
const apiKey = 'test-api-key';
late final api = FakeChatApi();
late final persistence = MockPersistenceClient();
late StreamChatClient client;
setUpAll(() {
// fallback values
registerFallbackValue<User>(FakeUser());
});
setUp(() {
final ws = FakeWebSocketWithConnectionError();
client = StreamChatClient(apiKey, chatApi: api, ws: ws)
..chatPersistenceClient = persistence;
});
tearDown(() {
client.dispose();
});
test(
'''`.connectUser` should connect successfully if persistence contains event''',
() async {
final user = User(id: 'test-user-id');
final token = Token.development(user.id).rawValue;
final event = Event(
type: EventType.healthCheck,
connectionId: 'test-connection-id',
me: OwnUser.fromUser(user));
when(persistence.getConnectionInfo).thenAnswer((_) async => event);
final res = await client.connectUser(user, token);
expect(res, isNotNull);
expect(res, isSameUserAs(user));
verify(persistence.getConnectionInfo).called(1);
verifyNoMoreInteractions(persistence);
},
);
test(
'''`.connectUserWithProvider` should connect successfully if persistence contains event''',
() async {
final user = User(id: 'test-user-id');
Future<String> tokenProvider(String userId) async {
expect(userId, user.id);
return Token.development(userId).rawValue;
}
final event = Event(
type: EventType.healthCheck,
connectionId: 'test-connection-id',
me: OwnUser.fromUser(user));
when(persistence.getConnectionInfo).thenAnswer((_) async => event);
final res = await client.connectUserWithProvider(user, tokenProvider);
expect(res, isNotNull);
expect(res, isSameUserAs(user));
verify(persistence.getConnectionInfo).called(1);
verifyNoMoreInteractions(persistence);
},
);
test(
'''`.connectGuestUser` should connect successfully if persistence contains event''',
() async {
final user = User(id: 'test-user-id');
final token = Token.development(user.id).rawValue;
final event = Event(
type: EventType.healthCheck,
connectionId: 'test-connection-id',
me: OwnUser.fromUser(user));
when(persistence.getConnectionInfo).thenAnswer((_) async => event);
when(() => api.guest.getGuestUser(any(that: isSameUserAs(user))))
.thenAnswer(
(_) async => ConnectGuestUserResponse()
..user = user
..accessToken = token,
);
final res = await client.connectGuestUser(user);
expect(res, isNotNull);
expect(res, isSameUserAs(user));
verify(persistence.getConnectionInfo).called(1);
verifyNoMoreInteractions(persistence);
verify(() => api.guest.getGuestUser(any(that: isSameUserAs(user))))
.called(1);
verifyNoMoreInteractions(api.guest);
},
);
test(
'''`.connectAnonymousUser` should connect successfully if persistence contains event''',
() async {
final user = User(id: 'test-user-id');
when(persistence.getConnectionInfo).thenAnswer(
(invocation) async => Event(
type: EventType.healthCheck,
connectionId: 'test-connection-id',
me: OwnUser.fromUser(user),
),
);
final res = await client.connectAnonymousUser();
expect(res, isNotNull);
verify(persistence.getConnectionInfo).called(1);
verifyNoMoreInteractions(persistence);
},
);
});
group('Client with connected user with persistence', () {
const apiKey = 'test-api-key';
late final api = FakeChatApi();
late final ws = FakeWebSocket();
late final persistence = MockPersistenceClient();
final user = User(id: 'test-user-id');
final token = Token.development(user.id).rawValue;
late StreamChatClient client;
setUpAll(() {
// fallback values
registerFallbackValue<Event>(FakeEvent());
registerFallbackValue<PaginationParams>(const PaginationParams());
registerFallbackValue<ChannelState>(FakeChannelState());
});
setUp(() async {
client = StreamChatClient(apiKey, chatApi: api, ws: ws)
..chatPersistenceClient = persistence;
await client.connectUser(user, token);
await delay(300);
expect(client.persistenceEnabled, isTrue);
expect(client.wsConnectionStatus, ConnectionStatus.connected);
});
tearDown(() {
client.dispose();
});
group('`.sync`', () {
test(
'''should update persistence connectionInfo and lastSync when sync succeeds''',
() async {
const cids = ['test-cid-1', 'test-cid-2', 'test-cid-3'];
final lastSyncAt = DateTime.now();
when(() => api.general.sync(cids, lastSyncAt))
.thenAnswer((_) async => SyncResponse()
..events = [
Event(
isLocal: false,
type: EventType.healthCheck,
connectionId: 'test-connection-id',
me: OwnUser.fromUser(user),
),
Event(
isLocal: false,
type: EventType.messageDeleted,
message: Message(id: 'test-message-id'),
),
]);
when(() => persistence.updateConnectionInfo(any()))
.thenAnswer((_) => Future.value());
when(() => persistence.updateLastSyncAt(any()))
.thenAnswer((_) => Future.value());
await client.sync(cids: cids, lastSyncAt: lastSyncAt);
verify(() => persistence.updateConnectionInfo(any())).called(1);
verify(() => persistence.updateLastSyncAt(any())).called(1);
verify(() => api.general.sync(cids, lastSyncAt)).called(1);
},
);
test(
'should work fine if persistence contains sync params',
() async {
const cids = ['test-cid-1', 'test-cid-2', 'test-cid-3'];
final lastSyncAt = DateTime.now();
when(persistence.getChannelCids).thenAnswer((_) async => cids);
when(persistence.getLastSyncAt).thenAnswer((_) async => lastSyncAt);
when(() => api.general.sync(cids, lastSyncAt))
.thenAnswer((_) async => SyncResponse()
..events = [
Event(
isLocal: false,
type: EventType.healthCheck,
connectionId: 'test-connection-id',
me: OwnUser.fromUser(user),
),
Event(
isLocal: false,
type: EventType.messageDeleted,
message: Message(id: 'test-message-id', text: 'Hey!'),
),
]);
when(() => persistence.updateConnectionInfo(any()))
.thenAnswer((_) => Future.value());
when(() => persistence.updateLastSyncAt(any()))
.thenAnswer((_) => Future.value());
await client.sync();
verify(() => persistence.updateConnectionInfo(any())).called(1);
verify(() => persistence.updateLastSyncAt(any())).called(1);
verify(() => api.general.sync(cids, lastSyncAt)).called(1);
verify(persistence.getChannelCids).called(1);
verify(persistence.getLastSyncAt).called(1);
},
);
});
group('`.queryChannels`', () {
test(
'should emit channels twice if persistence contains some channels',
() async {
final persistentChannelStates = List.generate(
3,
(index) => ChannelState(
channel: ChannelModel(cid: 'p-test-type-$index:p-test-id-$index'),
),
);
when(() => persistence.getChannelStates(
filter: any(named: 'filter'),
sort: any(named: 'sort'),
paginationParams: any(named: 'paginationParams'),
)).thenAnswer((_) async => persistentChannelStates);
final channelStates = List.generate(
3,
(index) => ChannelState(
channel: ChannelModel(cid: 'test-type-$index:test-id-$index'),
),
);
when(() => api.channel.queryChannels(
filter: any(named: 'filter'),
sort: any(named: 'sort'),
state: any(named: 'state'),
watch: any(named: 'watch'),
presence: any(named: 'presence'),
memberLimit: any(named: 'memberLimit'),
messageLimit: any(named: 'messageLimit'),
paginationParams: any(named: 'paginationParams'),
)).thenAnswer(
(_) async => QueryChannelsResponse()..channels = channelStates,
);
when(() => persistence.getChannelThreads(any()))
.thenAnswer((_) async => {});
when(() => persistence.updateMessages(any(), any()))
.thenAnswer((_) => Future.value());
when(() => persistence.getChannelStateByCid(any(),
messagePagination: any(named: 'messagePagination'),
pinnedMessagePagination:
any(named: 'pinnedMessagePagination'))).thenAnswer(
(invocation) async => ChannelState(
channel: ChannelModel(cid: invocation.positionalArguments.first),
),
);
when(() => persistence.updateChannelQueries(any(), any(),
clearQueryCache: any(named: 'clearQueryCache')))
.thenAnswer((_) => Future.value());
expectLater(
client.queryChannels(),
emitsInOrder([
// emits persistent channels first
persistentChannelStates.map(isCorrectChannelFor),
// makes api call and emits network fetched channels
channelStates.map(isCorrectChannelFor),
]),
);
// Hack as `teardown` gets called even
// before our stream starts emitting data
await delay(300);
verify(() => persistence.getChannelStates(
filter: any(named: 'filter'),
sort: any(named: 'sort'),
paginationParams: any(named: 'paginationParams'),
)).called(1);
verify(() => api.channel.queryChannels(
filter: any(named: 'filter'),
sort: any(named: 'sort'),
state: any(named: 'state'),
watch: any(named: 'watch'),
presence: any(named: 'presence'),
memberLimit: any(named: 'memberLimit'),
messageLimit: any(named: 'messageLimit'),
paginationParams: any(named: 'paginationParams'),
)).called(1);
verify(() => persistence.getChannelThreads(any()))
.called((persistentChannelStates + channelStates).length);
verify(() => persistence.updateMessages(any(), any()))
.called((persistentChannelStates + channelStates).length);
verify(
() => persistence.getChannelStateByCid(any(),
messagePagination: any(named: 'messagePagination'),
pinnedMessagePagination: any(named: 'pinnedMessagePagination')),
).called((persistentChannelStates + channelStates).length);
verify(() => persistence.updateChannelQueries(any(), any(),
clearQueryCache: any(named: 'clearQueryCache'))).called(1);
},
);
test(
'''should never rethrow network call if persistence already emitted some channels''',
() async {
final persistentChannelStates = List.generate(
3,
(index) => ChannelState(
channel: ChannelModel(cid: 'p-test-type-$index:p-test-id-$index'),
),
);
when(() => persistence.getChannelStates(
filter: any(named: 'filter'),
sort: any(named: 'sort'),
paginationParams: any(named: 'paginationParams'),
)).thenAnswer((_) async => persistentChannelStates);
when(() => api.channel.queryChannels(
filter: any(named: 'filter'),
sort: any(named: 'sort'),
state: any(named: 'state'),
watch: any(named: 'watch'),
presence: any(named: 'presence'),
memberLimit: any(named: 'memberLimit'),
messageLimit: any(named: 'messageLimit'),
paginationParams: any(named: 'paginationParams'),
)).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError));
when(() => persistence.getChannelThreads(any()))
.thenAnswer((_) async => {});
when(() => persistence.updateMessages(any(), any()))
.thenAnswer((_) => Future.value());
when(() => persistence.getChannelStateByCid(any(),
messagePagination: any(named: 'messagePagination'),
pinnedMessagePagination:
any(named: 'pinnedMessagePagination'))).thenAnswer(
(invocation) async => ChannelState(
channel: ChannelModel(cid: invocation.positionalArguments.first),
),
);
expectLater(
client.queryChannels(),
emitsInOrder([
// emits persistent channels
persistentChannelStates.map(isCorrectChannelFor),
]),
);
// Hack as `teardown` gets called even
// before our stream starts emitting data
await delay(300);
verify(() => persistence.getChannelStates(
filter: any(named: 'filter'),
sort: any(named: 'sort'),
paginationParams: any(named: 'paginationParams'),
)).called(1);
verify(() => api.channel.queryChannels(
filter: any(named: 'filter'),
sort: any(named: 'sort'),
state: any(named: 'state'),
watch: any(named: 'watch'),
presence: any(named: 'presence'),
memberLimit: any(named: 'memberLimit'),
messageLimit: any(named: 'messageLimit'),
paginationParams: any(named: 'paginationParams'),
)).called(1);
verify(() => persistence.getChannelThreads(any()))
.called(persistentChannelStates.length);
verify(() => persistence.updateMessages(any(), any()))
.called(persistentChannelStates.length);
verify(
() => persistence.getChannelStateByCid(any(),
messagePagination: any(named: 'messagePagination'),
pinnedMessagePagination: any(named: 'pinnedMessagePagination')),
).called(persistentChannelStates.length);
},
);
});
test('`.disconnectUser` should reset state and user', () async {
expect(client.state.currentUser, isNotNull);
expect(client.wsConnectionStatus, ConnectionStatus.connected);
expectLater(
// skipping initial connected value
client.wsConnectionStatusStream.skip(1),
emits(ConnectionStatus.disconnected),
);
await client.disconnectUser();
expect(client.state.currentUser, isNull);
expect(client.wsConnectionStatus, ConnectionStatus.disconnected);
});
});
group('Client with connected user without persistence', () {
const apiKey = 'test-api-key';
late final api = FakeChatApi();
late final ws = FakeWebSocket();
final user = User(id: 'test-user-id');
final token = Token.development(user.id).rawValue;
late StreamChatClient client;
setUpAll(() {
// fallback values
registerFallbackValue<Event>(FakeEvent());
registerFallbackValue<Message>(FakeMessage());
registerFallbackValue<PaginationParams>(const PaginationParams());
});
setUp(() async {
client = StreamChatClient(apiKey, chatApi: api, ws: ws);
await client.connectUser(user, token);
await delay(300);
expect(client.persistenceEnabled, isFalse);
expect(client.wsConnectionStatus, ConnectionStatus.connected);
});
tearDown(() {
client.dispose();
});
group('`.sync`', () {
test('should work fine', () async {
const cids = ['test-cid-1', 'test-cid-2', 'test-cid-3'];
final lastSyncAt = DateTime.now();
when(() => api.general.sync(cids, lastSyncAt))
.thenAnswer((_) async => SyncResponse()
..events = [
Event(
isLocal: false,
type: EventType.healthCheck,
connectionId: 'test-connection-id',
me: OwnUser.fromUser(user),
),
Event(
isLocal: false,
type: EventType.messageDeleted,
message: Message(id: 'test-message-id'),
),
]);
await client.sync(cids: cids, lastSyncAt: lastSyncAt);
verify(() => api.general.sync(cids, lastSyncAt)).called(1);
});
test('should return if `cids` is not available', () async {
expect(client.sync, returnsNormally);
verifyNever(() => api.general.sync(any(), any()));
});
test('should return if `lastSyncAt` is not available', () async {
expect(() => client.sync(cids: ['test-cid-1']), returnsNormally);
verifyNever(() => api.general.sync(any(), any()));
});
});
group('`.queryChannels`', () {
test('should work fine without persistent channels', () async {
final channelStates = List.generate(
3,
(index) => ChannelState(
channel: ChannelModel(cid: 'test-type-$index:test-id-$index'),
),
);
when(() => api.channel.queryChannels(
filter: any(named: 'filter'),
sort: any(named: 'sort'),
state: any(named: 'state'),
watch: any(named: 'watch'),
presence: any(named: 'presence'),
memberLimit: any(named: 'memberLimit'),
messageLimit: any(named: 'messageLimit'),
paginationParams: any(named: 'paginationParams'),
)).thenAnswer(
(_) async => QueryChannelsResponse()..channels = channelStates,
);
expectLater(
client.queryChannels(),
emitsInOrder([channelStates.map(isCorrectChannelFor)]),
);
// Hack as `teardown` gets called even
// before our stream starts emitting data
await delay(300);
verify(() => api.channel.queryChannels(
filter: any(named: 'filter'),
sort: any(named: 'sort'),
state: any(named: 'state'),
watch: any(named: 'watch'),
presence: any(named: 'presence'),
memberLimit: any(named: 'memberLimit'),
messageLimit: any(named: 'messageLimit'),
paginationParams: any(named: 'paginationParams'),
)).called(1);
});
test(
'''should rethrow if `.queryChannelsOnline` throws and persistence channels are empty''',
() async {
when(() => api.channel.queryChannels(
filter: any(named: 'filter'),
sort: any(named: 'sort'),
state: any(named: 'state'),
watch: any(named: 'watch'),
presence: any(named: 'presence'),
memberLimit: any(named: 'memberLimit'),
messageLimit: any(named: 'messageLimit'),
paginationParams: any(named: 'paginationParams'),
)).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError));
expectLater(
client.queryChannels(),
emitsError(isA<StreamChatNetworkError>()),
);
// Hack as `teardown` gets called even
// before our stream starts emitting data
await delay(300);
verify(() => api.channel.queryChannels(
filter: any(named: 'filter'),
sort: any(named: 'sort'),
state: any(named: 'state'),
watch: any(named: 'watch'),
presence: any(named: 'presence'),
memberLimit: any(named: 'memberLimit'),
messageLimit: any(named: 'messageLimit'),
paginationParams: any(named: 'paginationParams'),
)).called(1);
},
);
});
test('`.queryUsers`', () async {
final users = List.generate(
3,
(index) => User(id: 'test-user-id-$index'),
);
when(() => api.user.queryUsers(
presence: any(named: 'presence'),
filter: any(named: 'filter'),
sort: any(named: 'sort'),
pagination: any(named: 'pagination'),
)).thenAnswer((_) async => QueryUsersResponse()..users = users);
expectLater(
// skipping initial seed event -> {} users
client.state.usersStream.skip(1),
emitsInOrder([
{for (var user in users) user.id: user},
]),
);
final res = await client.queryUsers();
expect(res, isNotNull);
expect(res.users.length, users.length);
verify(() => api.user.queryUsers(
presence: any(named: 'presence'),
filter: any(named: 'filter'),
sort: any(named: 'sort'),
pagination: any(named: 'pagination'),
)).called(1);
verifyNoMoreInteractions(api.user);
});
test('`.search`', () async {
const cid = 'test-type:test-id';
final filter = Filter.in_('cid', const [cid]);
final messages = List.generate(
3,
(index) => GetMessageResponse()
..channel = ChannelModel(cid: cid)
..message = Message(id: 'test-message-id-$index'),
);
when(() => api.general.searchMessages(filter,
query: any(named: 'query'),
sort: any(named: 'sort'),
pagination: any(named: 'pagination'),
messageFilters: any(named: 'messageFilters')))
.thenAnswer(
(_) async => SearchMessagesResponse()..results = messages);
final res = await client.search(filter);
expect(res, isNotNull);
expect(res.results.length, messages.length);
verify(() => api.general.searchMessages(filter,
query: any(named: 'query'),
sort: any(named: 'sort'),
pagination: any(named: 'pagination'),
messageFilters: any(named: 'messageFilters'))).called(1);
verifyNoMoreInteractions(api.general);
});
test('`.sendFile`', () async {
const channelId = 'test-channel-id';
const channelType = 'test-channel-type';
final file = AttachmentFile(size: 33, path: 'test-file-path');
const fileUrl = 'test-file-url';
when(() => api.fileUploader.sendFile(file, channelId, channelType))
.thenAnswer((_) async => SendFileResponse()..file = fileUrl);
final res = await client.sendFile(file, channelId, channelType);
expect(res, isNotNull);
expect(res.file, fileUrl);
verify(() => api.fileUploader.sendFile(file, channelId, channelType))
.called(1);
verifyNoMoreInteractions(api.fileUploader);
});
test('`.sendImage`', () async {
const channelId = 'test-channel-id';
const channelType = 'test-channel-type';
final image = AttachmentFile(size: 33, path: 'test-image-path');
const fileUrl = 'test-image-url';
when(() => api.fileUploader.sendImage(image, channelId, channelType))
.thenAnswer((_) async => SendImageResponse()..file = fileUrl);
final res = await client.sendImage(image, channelId, channelType);
expect(res, isNotNull);
expect(res.file, fileUrl);
verify(() => api.fileUploader.sendImage(image, channelId, channelType))
.called(1);
verifyNoMoreInteractions(api.fileUploader);
});
test('`.deleteFile`', () async {
const channelId = 'test-channel-id';
const channelType = 'test-channel-type';
const fileUrl = 'test-file-url';
when(() => api.fileUploader.deleteFile(fileUrl, channelId, channelType))
.thenAnswer((_) async => EmptyResponse());
final res = await client.deleteFile(fileUrl, channelId, channelType);
expect(res, isNotNull);
verify(() => api.fileUploader.deleteFile(fileUrl, channelId, channelType))
.called(1);
verifyNoMoreInteractions(api.fileUploader);
});
test('`.deleteImage`', () async {
const channelId = 'test-channel-id';
const channelType = 'test-channel-type';
const imageUrl = 'test-image-url';
when(() => api.fileUploader.deleteImage(imageUrl, channelId, channelType))
.thenAnswer((_) async => EmptyResponse());
final res = await client.deleteImage(imageUrl, channelId, channelType);
expect(res, isNotNull);
verify(
() => api.fileUploader.deleteImage(imageUrl, channelId, channelType),
).called(1);
verifyNoMoreInteractions(api.fileUploader);
});
test('`.updateChannel`', () async {
const channelId = 'test-channel-id';
const channelType = 'test-channel-type';
const data = {'name': 'test-channel'};
when(() => api.channel.updateChannel(channelId, channelType, data))
.thenAnswer((invocation) async => UpdateChannelResponse()
..channel = ChannelModel(
id: channelId,
type: channelType,
extraData: {...data},
));
final res = await client.updateChannel(channelId, channelType, data);
expect(res, isNotNull);
expect(res.channel.cid, '$channelType:$channelId');
expect(res.channel.extraData['name'], 'test-channel');
verify(() => api.channel.updateChannel(channelId, channelType, data))
.called(1);
verifyNoMoreInteractions(api.channel);
});
test('`.updateChannelPartial`', () async {
const channelId = 'test-channel-id';
const channelType = 'test-channel-type';
const set = {
'name': 'Stream Team',
'profile_image': 'test-profile-image',
};
const unset = ['tag', 'last_name'];
when(() => api.channel.updateChannelPartial(channelId, channelType,
set: set, unset: unset))
.thenAnswer((invocation) async => PartialUpdateChannelResponse()
..channel = ChannelModel(
id: channelId,
type: channelType,
extraData: {...set},
));
final res = await client.updateChannelPartial(
channelId,
channelType,
set: set,
unset: unset,
);
expect(res, isNotNull);
expect(res.channel.cid, '$channelType:$channelId');
expect(res.channel.extraData, set);
verify(() => api.channel.updateChannelPartial(channelId, channelType,
set: set, unset: unset)).called(1);
verifyNoMoreInteractions(api.channel);
});
test('`.addDevice`', () async {
const id = 'test-device-id';
const provider = PushProvider.firebase;
when(() => api.device.addDevice(id, provider))
.thenAnswer((_) async => EmptyResponse());
final res = await client.addDevice(id, provider);
expect(res, isNotNull);
verify(() => api.device.addDevice(id, provider)).called(1);
verifyNoMoreInteractions(api.device);
});
test('`.getDevices`', () async {
final devices = List.generate(
3,
(index) => Device(
id: 'test-device-id-$index',
pushProvider: PushProvider.firebase.name,
),
);
when(() => api.device.getDevices())
.thenAnswer((_) async => ListDevicesResponse()..devices = devices);
final res = await client.getDevices();
expect(res, isNotNull);
expect(res.devices.length, devices.length);
verify(() => api.device.getDevices()).called(1);
verifyNoMoreInteractions(api.device);
});
test('`.removeDevice`', () async {
const deviceId = 'test-device-id';
when(() => api.device.removeDevice(deviceId))
.thenAnswer((_) async => EmptyResponse());
final res = await client.removeDevice(deviceId);
expect(res, isNotNull);
verify(() => api.device.removeDevice(deviceId)).called(1);
verifyNoMoreInteractions(api.device);
});
test('`.devToken`', () async {
const userId = 'test-user-id';
final token = client.devToken(userId);
expect(token, isNotNull);
expect(token.userId, userId);
expect(token.authType, AuthType.jwt);
});
group('`.channel`', () {
test('should return back a new channel instance', () {
const channelType = 'test-channel-type';
const channelId = 'test-channel-id';
const channelData = {'name': 'test-channel-name'};
final channel = client.channel(
channelType,
id: channelId,
extraData: channelData,
);
expect(channel, isNotNull);
expect(channel.type, channelType);
expect(channel.id, channelId);
expect(channel.cid, '$channelType:$channelId');
expect(channel.extraData, channelData);
});
test('should return back in memory channel instance if available',
() async {
const channelType = 'test-channel-type';
const channelId = 'test-channel-id';
const channelData = {'name': 'test-channel-name'};
const channelCid = '$channelType:$channelId';
final channel = client.channel(
channelType,
id: channelId,
extraData: channelData,
);
final channelState = ChannelState(
channel: ChannelModel(cid: channelCid),
);
when(() => api.channel.queryChannel(
channelType,
channelId: channelId,
channelData: channelData,
state: any(named: 'state'),
watch: any(named: 'watch'),
presence: any(named: 'presence'),
messagesPagination: any(named: 'messagesPagination'),
membersPagination: any(named: 'membersPagination'),
watchersPagination: any(named: 'watchersPagination'),
)).thenAnswer((_) async => channelState);
expectLater(
client.state.channelsStream.skip(1),
emitsInOrder([
{channelCid: isCorrectChannelFor(channelState)}
]),
);
await channel.watch();
final newChannel = client.channel(channelType, id: channelId);
expect(newChannel, channel);
verify(() => api.channel.queryChannel(
channelType,
channelId: channelId,
channelData: channelData,
state: any(named: 'state'),
watch: any(named: 'watch'),
presence: any(named: 'presence'),
messagesPagination: any(named: 'messagesPagination'),
membersPagination: any(named: 'membersPagination'),
watchersPagination: any(named: 'watchersPagination'),
)).called(1);
});
});
test('`.createChannel`', () async {
const channelType = 'test-channel-type';
const channelId = 'test-channel-id';
const channelData = {'name': 'test-channel-name'};
const channelCid = '$channelType:$channelId';
final channelState = ChannelState(
channel: ChannelModel(cid: channelCid, extraData: channelData),
);
when(() => api.channel.queryChannel(
channelType,
channelId: channelId,
channelData: channelData,
state: any(named: 'state'),
watch: any(named: 'watch'),
presence: any(named: 'presence'),
messagesPagination: any(named: 'messagesPagination'),
membersPagination: any(named: 'membersPagination'),
watchersPagination: any(named: 'watchersPagination'),
)).thenAnswer((_) async => channelState);
final res = await client.createChannel(
channelType,
channelId: channelId,
channelData: channelData,
);
expect(res, isNotNull);
expect(res.channel, isNotNull);
final channel = res.channel!;
expect(channel.type, channelType);
expect(channel.id, channelId);
expect(channel.cid, '$channelType:$channelId');
expect(channel.extraData, channelData);
verify(() => api.channel.queryChannel(
channelType,
channelId: channelId,
channelData: channelData,
state: any(named: 'state'),
watch: any(named: 'watch'),
presence: any(named: 'presence'),
messagesPagination: any(named: 'messagesPagination'),
membersPagination: any(named: 'membersPagination'),
watchersPagination: any(named: 'watchersPagination'),
)).called(1);
verifyNoMoreInteractions(api.channel);
});
test('`.watchChannel`', () async {
const channelType = 'test-channel-type';
const channelId = 'test-channel-id';
const channelData = {'name': 'test-channel-name'};
const channelCid = '$channelType:$channelId';
final channelState = ChannelState(
channel: ChannelModel(cid: channelCid, extraData: channelData),
);
when(() => api.channel.queryChannel(
channelType,
channelId: channelId,
channelData: channelData,
state: any(named: 'state'),
watch: any(named: 'watch'),
presence: any(named: 'presence'),
messagesPagination: any(named: 'messagesPagination'),
membersPagination: any(named: 'membersPagination'),
watchersPagination: any(named: 'watchersPagination'),
)).thenAnswer((_) async => channelState);
final res = await client.watchChannel(
channelType,
channelId: channelId,
channelData: channelData,
);
expect(res, isNotNull);
expect(res.channel, isNotNull);
final channel = res.channel!;
expect(channel.type, channelType);
expect(channel.id, channelId);
expect(channel.cid, '$channelType:$channelId');
expect(channel.extraData, channelData);
verify(() => api.channel.queryChannel(
channelType,
channelId: channelId,
channelData: channelData,
state: any(named: 'state'),
watch: any(named: 'watch'),
presence: any(named: 'presence'),
messagesPagination: any(named: 'messagesPagination'),
membersPagination: any(named: 'membersPagination'),
watchersPagination: any(named: 'watchersPagination'),
)).called(1);
verifyNoMoreInteractions(api.channel);
});
test('`.queryChannel`', () async {
const channelType = 'test-channel-type';
const channelId = 'test-channel-id';
const channelData = {'name': 'test-channel-name'};
const channelCid = '$channelType:$channelId';
final channelState = ChannelState(
channel: ChannelModel(cid: channelCid, extraData: channelData),
);
when(() => api.channel.queryChannel(
channelType,
channelId: channelId,
channelData: channelData,
state: any(named: 'state'),
watch: any(named: 'watch'),
presence: any(named: 'presence'),
messagesPagination: any(named: 'messagesPagination'),
membersPagination: any(named: 'membersPagination'),
watchersPagination: any(named: 'watchersPagination'),
)).thenAnswer((_) async => channelState);
final res = await client.queryChannel(
channelType,
channelId: channelId,
channelData: channelData,
);
expect(res, isNotNull);
expect(res.channel, isNotNull);
final channel = res.channel!;
expect(channel.type, channelType);
expect(channel.id, channelId);
expect(channel.cid, '$channelType:$channelId');
expect(channel.extraData, channelData);
verify(() => api.channel.queryChannel(
channelType,
channelId: channelId,
channelData: channelData,
state: any(named: 'state'),
watch: any(named: 'watch'),
presence: any(named: 'presence'),
messagesPagination: any(named: 'messagesPagination'),
membersPagination: any(named: 'membersPagination'),
watchersPagination: any(named: 'watchersPagination'),
)).called(1);
verifyNoMoreInteractions(api.channel);
});
test('`.queryMembers`', () async {
const channelType = 'test-channel-type';
final members = List.generate(
3,
(index) => Member(userId: 'test-user-id-$index'),
);
when(() => api.general.queryMembers(channelType)).thenAnswer(
(_) async => QueryMembersResponse()..members = members,
);
final res = await client.queryMembers(channelType);
expect(res, isNotNull);
expect(res.members.length, members.length);
verify(() => api.general.queryMembers(channelType)).called(1);
verifyNoMoreInteractions(api.general);
});
test('`.hideChannel`', () async {
const channelType = 'test-channel-type';
const channelId = 'test-channel-id';
when(() => api.channel.hideChannel(channelId, channelType))
.thenAnswer((_) async => EmptyResponse());
final res = await client.hideChannel(channelId, channelType);
expect(res, isNotNull);
verify(() => api.channel.hideChannel(channelId, channelType)).called(1);
verifyNoMoreInteractions(api.channel);
});
test('`.showChannel`', () async {
const channelType = 'test-channel-type';
const channelId = 'test-channel-id';
when(() => api.channel.showChannel(channelId, channelType))
.thenAnswer((_) async => EmptyResponse());
final res = await client.showChannel(channelId, channelType);
expect(res, isNotNull);
verify(() => api.channel.showChannel(channelId, channelType)).called(1);
verifyNoMoreInteractions(api.channel);
});
test('`.deleteChannel`', () async {
const channelType = 'test-channel-type';
const channelId = 'test-channel-id';
when(() => api.channel.deleteChannel(channelId, channelType))
.thenAnswer((_) async => EmptyResponse());
final res = await client.deleteChannel(channelId, channelType);
expect(res, isNotNull);
verify(() => api.channel.deleteChannel(channelId, channelType)).called(1);
verifyNoMoreInteractions(api.channel);
});
test('`.truncateChannel`', () async {
const channelType = 'test-channel-type';
const channelId = 'test-channel-id';
when(() => api.channel.truncateChannel(channelId, channelType))
.thenAnswer((_) async => EmptyResponse());
final res = await client.truncateChannel(channelId, channelType);
expect(res, isNotNull);
verify(
() => api.channel.truncateChannel(channelId, channelType),
).called(1);
verifyNoMoreInteractions(api.channel);
});
test('`.muteChannel`', () async {
const channelType = 'test-channel-type';
const channelId = 'test-channel-id';
const channelCid = '$channelType:$channelId';
when(() => api.moderation.muteChannel(channelCid))
.thenAnswer((_) async => EmptyResponse());
final res = await client.muteChannel(channelCid);
expect(res, isNotNull);
verify(() => api.moderation.muteChannel(channelCid)).called(1);
verifyNoMoreInteractions(api.moderation);
});
test('`.unmuteChannel`', () async {
const channelType = 'test-channel-type';
const channelId = 'test-channel-id';
const channelCid = '$channelType:$channelId';
when(() => api.moderation.unmuteChannel(channelCid))
.thenAnswer((_) async => EmptyResponse());
final res = await client.unmuteChannel(channelCid);
expect(res, isNotNull);
verify(() => api.moderation.unmuteChannel(channelCid)).called(1);
verifyNoMoreInteractions(api.moderation);
});
test('`.acceptChannelInvite`', () async {
const channelType = 'test-channel-type';
const channelId = 'test-channel-id';
const channelCid = '$channelType:$channelId';
when(() => api.channel.acceptChannelInvite(channelId, channelType))
.thenAnswer((_) async =>
AcceptInviteResponse()..channel = ChannelModel(cid: channelCid));
final res = await client.acceptChannelInvite(channelId, channelType);
expect(res, isNotNull);
expect(res.channel.cid, channelCid);
verify(() => api.channel.acceptChannelInvite(channelId, channelType))
.called(1);
verifyNoMoreInteractions(api.channel);
});
test('`.rejectChannelInvite`', () async {
const channelType = 'test-channel-type';
const channelId = 'test-channel-id';
const channelCid = '$channelType:$channelId';
when(() => api.channel.rejectChannelInvite(channelId, channelType))
.thenAnswer((_) async =>
RejectInviteResponse()..channel = ChannelModel(cid: channelCid));
final res = await client.rejectChannelInvite(channelId, channelType);
expect(res, isNotNull);
expect(res.channel.cid, channelCid);
verify(() => api.channel.rejectChannelInvite(channelId, channelType))
.called(1);
verifyNoMoreInteractions(api.channel);
});
test('`.addChannelMembers`', () async {
const channelType = 'test-channel-type';
const channelId = 'test-channel-id';
const channelCid = '$channelType:$channelId';
final members = List.generate(
3,
(index) => Member(userId: 'test-user-id-$index'),
);
final memberIds = members.map((e) => e.userId!).toList(growable: false);
when(() => api.channel.addMembers(channelId, channelType, memberIds))
.thenAnswer((_) async => AddMembersResponse()
..channel = ChannelModel(cid: channelCid)
..members = members);
final res = await client.addChannelMembers(
channelId,
channelType,
memberIds,
);
expect(res, isNotNull);
expect(res.channel.cid, channelCid);
expect(res.members.length, memberIds.length);
verify(
() => api.channel.addMembers(channelId, channelType, memberIds),
).called(1);
verifyNoMoreInteractions(api.channel);
});
test('`.removeChannelMembers`', () async {
const channelType = 'test-channel-type';
const channelId = 'test-channel-id';
const channelCid = '$channelType:$channelId';
final members = List.generate(
3,
(index) => Member(userId: 'test-user-id-$index'),
);
final memberIds = members.map((e) => e.userId!).toList(growable: false);
when(() => api.channel.removeMembers(channelId, channelType, memberIds))
.thenAnswer((_) async => RemoveMembersResponse()
..channel = ChannelModel(cid: channelCid)
..members = members);
final res = await client.removeChannelMembers(
channelId,
channelType,
memberIds,
);
expect(res, isNotNull);
expect(res.channel.cid, channelCid);
expect(res.members.length, memberIds.length);
verify(
() => api.channel.removeMembers(channelId, channelType, memberIds),
).called(1);
verifyNoMoreInteractions(api.channel);
});
test('`.inviteChannelMembers`', () async {
const channelType = 'test-channel-type';
const channelId = 'test-channel-id';
const channelCid = '$channelType:$channelId';
final members = List.generate(
3,
(index) => Member(userId: 'test-user-id-$index'),
);
final memberIds = members.map((e) => e.userId!).toList(growable: false);
when(() => api.channel
.inviteChannelMembers(channelId, channelType, memberIds))
.thenAnswer((_) async => InviteMembersResponse()
..channel = ChannelModel(cid: channelCid)
..members = members);
final res = await client.inviteChannelMembers(
channelId,
channelType,
memberIds,
);
expect(res, isNotNull);
expect(res.channel.cid, channelCid);
expect(res.members.length, memberIds.length);
verify(() => api.channel
.inviteChannelMembers(channelId, channelType, memberIds)).called(1);
verifyNoMoreInteractions(api.channel);
});
test('`.stopChannelWatching`', () async {
const channelType = 'test-channel-type';
const channelId = 'test-channel-id';
when(() => api.channel.stopWatching(channelId, channelType))
.thenAnswer((_) async => EmptyResponse());
final res = await client.stopChannelWatching(channelId, channelType);
expect(res, isNotNull);
verify(() => api.channel.stopWatching(channelId, channelType)).called(1);
verifyNoMoreInteractions(api.channel);
});
test('`.sendAction`', () async {
const channelType = 'test-channel-type';
const channelId = 'test-channel-id';
const messageId = 'test-message-id';
const formData = {'key': 'value'};
when(() => api.message
.sendAction(channelId, channelType, messageId, formData))
.thenAnswer((_) async => SendActionResponse());
final res = await client.sendAction(
channelId,
channelType,
messageId,
formData,
);
expect(res, isNotNull);
verify(() => api.message
.sendAction(channelId, channelType, messageId, formData)).called(1);
verifyNoMoreInteractions(api.message);
});
test('`.markChannelRead`', () async {
const channelType = 'test-channel-type';
const channelId = 'test-channel-id';
when(() => api.channel.markRead(channelId, channelType))
.thenAnswer((_) async => EmptyResponse());
final res = await client.markChannelRead(channelId, channelType);
expect(res, isNotNull);
verify(() => api.channel.markRead(channelId, channelType)).called(1);
verifyNoMoreInteractions(api.channel);
});
test('`.updateUser`', () async {
final user = User(
id: 'test-user-id',
extraData: const {'name': 'test-user'},
);
when(() => api.user.updateUsers([user])).thenAnswer(
(_) async => UpdateUsersResponse()..users = {user.id: user});
final res = await client.updateUser(user);
expect(res, isNotNull);
expect(res.users, {user.id: user});
verify(() => api.user.updateUsers([user])).called(1);
verifyNoMoreInteractions(api.user);
});
test('`.banUser`', () async {
const userId = 'test-user-id';
when(() => api.moderation.banUser(userId, options: any(named: 'options')))
.thenAnswer((_) async => EmptyResponse());
final res = await client.banUser(userId);
expect(res, isNotNull);
verify(
() => api.moderation.banUser(userId, options: any(named: 'options')),
).called(1);
verifyNoMoreInteractions(api.moderation);
});
test('`.unbanUser`', () async {
const userId = 'test-user-id';
when(() =>
api.moderation.unbanUser(userId, options: any(named: 'options')))
.thenAnswer((_) async => EmptyResponse());
final res = await client.unbanUser(userId);
expect(res, isNotNull);
verify(
() => api.moderation.unbanUser(userId, options: any(named: 'options')),
).called(1);
verifyNoMoreInteractions(api.moderation);
});
test('`.shadowBan`', () async {
const userId = 'test-user-id';
when(() => api.moderation.banUser(userId, options: {'shadow': true}))
.thenAnswer((_) async => EmptyResponse());
final res = await client.shadowBan(userId);
expect(res, isNotNull);
verify(
() => api.moderation.banUser(userId, options: {'shadow': true}),
).called(1);
verifyNoMoreInteractions(api.moderation);
});
test('`.removeShadowBan`', () async {
const userId = 'test-user-id';
when(() => api.moderation.unbanUser(userId, options: {'shadow': true}))
.thenAnswer((_) async => EmptyResponse());
final res = await client.removeShadowBan(userId);
expect(res, isNotNull);
verify(
() => api.moderation.unbanUser(userId, options: {'shadow': true}),
).called(1);
verifyNoMoreInteractions(api.moderation);
});
test('`.muteUser`', () async {
const userId = 'test-user-id';
when(() => api.moderation.muteUser(userId))
.thenAnswer((_) async => EmptyResponse());
final res = await client.muteUser(userId);
expect(res, isNotNull);
verify(() => api.moderation.muteUser(userId)).called(1);
verifyNoMoreInteractions(api.moderation);
});
test('`.unmuteUser`', () async {
const userId = 'test-user-id';
when(() => api.moderation.unmuteUser(userId))
.thenAnswer((_) async => EmptyResponse());
final res = await client.unmuteUser(userId);
expect(res, isNotNull);
verify(() => api.moderation.unmuteUser(userId)).called(1);
verifyNoMoreInteractions(api.moderation);
});
test('`.flagMessage`', () async {
const messageId = 'test-message-id';
when(() => api.moderation.flagMessage(messageId))
.thenAnswer((_) async => EmptyResponse());
final res = await client.flagMessage(messageId);
expect(res, isNotNull);
verify(() => api.moderation.flagMessage(messageId)).called(1);
verifyNoMoreInteractions(api.moderation);
});
test('`.unflagMessage`', () async {
const messageId = 'test-message-id';
when(() => api.moderation.unflagMessage(messageId))
.thenAnswer((_) async => EmptyResponse());
final res = await client.unflagMessage(messageId);
expect(res, isNotNull);
verify(() => api.moderation.unflagMessage(messageId)).called(1);
verifyNoMoreInteractions(api.moderation);
});
test('`.flagUser`', () async {
const userId = 'test-message-id';
when(() => api.moderation.flagUser(userId))
.thenAnswer((_) async => EmptyResponse());
final res = await client.flagUser(userId);
expect(res, isNotNull);
verify(() => api.moderation.flagUser(userId)).called(1);
verifyNoMoreInteractions(api.moderation);
});
test('`.unflagUser`', () async {
const userId = 'test-message-id';
when(() => api.moderation.unflagUser(userId))
.thenAnswer((_) async => EmptyResponse());
final res = await client.unflagUser(userId);
expect(res, isNotNull);
verify(() => api.moderation.unflagUser(userId)).called(1);
verifyNoMoreInteractions(api.moderation);
});
test('`.markAllRead`', () async {
when(() => api.channel.markAllRead())
.thenAnswer((_) async => EmptyResponse());
final res = await client.markAllRead();
expect(res, isNotNull);
verify(() => api.channel.markAllRead()).called(1);
verifyNoMoreInteractions(api.channel);
});
test('`.sendEvent`', () async {
const channelType = 'test-channel-type';
const channelId = 'test-channel-id';
final event = Event(type: EventType.any);
when(
() => api.channel.sendEvent(
channelId,
channelType,
any(that: isSameEventAs(event)),
),
).thenAnswer((_) async => EmptyResponse());
final res = await client.sendEvent(channelId, channelType, event);
expect(res, isNotNull);
verify(() => api.channel.sendEvent(
channelId,
channelType,
any(that: isSameEventAs(event)),
)).called(1);
verifyNoMoreInteractions(api.channel);
});
test('`.sendReaction`', () async {
const messageId = 'test-message-id';
const reactionType = 'like';
when(() => api.message.sendReaction(messageId, reactionType))
.thenAnswer((_) async => SendReactionResponse()
..message = Message(id: messageId)
..reaction = Reaction(type: reactionType, messageId: messageId));
final res = await client.sendReaction(messageId, reactionType);
expect(res, isNotNull);
expect(res.message.id, messageId);
expect(res.reaction.type, reactionType);
expect(res.reaction.messageId, messageId);
verify(() => api.message.sendReaction(messageId, reactionType)).called(1);
verifyNoMoreInteractions(api.message);
});
test('`.deleteReaction`', () async {
const messageId = 'test-message-id';
const reactionType = 'like';
when(() => api.message.deleteReaction(messageId, reactionType))
.thenAnswer((_) async => EmptyResponse());
final res = await client.deleteReaction(messageId, reactionType);
expect(res, isNotNull);
verify(
() => api.message.deleteReaction(messageId, reactionType),
).called(1);
verifyNoMoreInteractions(api.message);
});
test('`.sendMessage`', () async {
final message = Message(id: 'test-message-id');
const channelId = 'test-channel-id';
const channelType = 'test-channel-type';
when(() => api.message.sendMessage(
channelId, channelType, any(that: isSameMessageAs(message))))
.thenAnswer((_) async => SendMessageResponse()..message = message);
final res = await client.sendMessage(message, channelId, channelType);
expect(res, isNotNull);
expect(res.message, isSameMessageAs(message));
verify(() => api.message.sendMessage(
channelId,
channelType,
any(that: isSameMessageAs(message)),
)).called(1);
verifyNoMoreInteractions(api.message);
});
test('`.getReplies`', () async {
const parentId = 'test-parent-id';
final messages = List.generate(
3,
(index) => Message(id: 'test-message-id-$index'),
);
when(() => api.message.getReplies(parentId))
.thenAnswer((_) async => QueryRepliesResponse()..messages = messages);
final res = await client.getReplies(parentId);
expect(res, isNotNull);
expect(res.messages.length, messages.length);
verify(() => api.message.getReplies(parentId)).called(1);
verifyNoMoreInteractions(api.message);
});
test('`.getReactions`', () async {
const messageId = 'test-parent-id';
final reactions = List.generate(
3,
(index) => Reaction(
type: 'test-reactions-type-$index',
messageId: messageId,
),
);
when(() => api.message.getReactions(messageId)).thenAnswer(
(_) async => QueryReactionsResponse()..reactions = reactions);
final res = await client.getReactions(messageId);
expect(res, isNotNull);
expect(res.reactions.length, reactions.length);
expect(res.reactions.every((it) => it.messageId == messageId), isTrue);
verify(() => api.message.getReactions(messageId)).called(1);
verifyNoMoreInteractions(api.message);
});
test('`.updateMessage`', () async {
final message = Message(id: 'test-message-id', text: 'Hello!');
when(() => api.message.updateMessage(any(that: isSameMessageAs(message))))
.thenAnswer((_) async => UpdateMessageResponse()..message = message);
final res = await client.updateMessage(message);
expect(res, isNotNull);
expect(res.message, isSameMessageAs(message));
verify(
() => api.message.updateMessage(any(that: isSameMessageAs(message))),
).called(1);
verifyNoMoreInteractions(api.message);
});
test('`.deleteMessage`', () async {
const messageId = 'test-message-id';
when(() => api.message.deleteMessage(messageId))
.thenAnswer((_) async => EmptyResponse());
final res = await client.deleteMessage(messageId);
expect(res, isNotNull);
verify(() => api.message.deleteMessage(messageId)).called(1);
verifyNoMoreInteractions(api.message);
});
test('`.getMessage`', () async {
const messageId = 'test-message-id';
final message = Message(id: messageId);
when(() => api.message.getMessage(messageId))
.thenAnswer((_) async => GetMessageResponse()..message = message);
final res = await client.getMessage(messageId);
expect(res, isNotNull);
expect(res.message.id, messageId);
verify(() => api.message.getMessage(messageId)).called(1);
verifyNoMoreInteractions(api.message);
});
test('`.getMessagesById`', () async {
const channelId = 'test-channel-id';
const channelType = 'test-channel-type';
const messageIds = ['test-message-id'];
final messages = messageIds.map((id) => Message(id: id)).toList();
when(
() => api.message.getMessagesById(channelId, channelType, messageIds),
).thenAnswer((_) async => GetMessagesByIdResponse()..messages = messages);
final res = await client.getMessagesById(
channelId,
channelType,
messageIds,
);
expect(res, isNotNull);
expect(res.messages.length, messageIds.length);
verify(
() => api.message.getMessagesById(channelId, channelType, messageIds),
).called(1);
verifyNoMoreInteractions(api.message);
});
test('`.translateMessage`', () async {
const messageId = 'test-message-id';
const language = 'hi'; // Hindi
const translatedMessageText = 'नमस्ते';
final translatedMessage = Message(
i18n: const {
language: translatedMessageText,
},
);
when(() => api.message.translateMessage(messageId, language)).thenAnswer(
(_) async => TranslateMessageResponse()..message = translatedMessage,
);
final res = await client.translateMessage(messageId, language);
expect(res, isNotNull);
expect(res.message.i18n, translatedMessage.i18n);
verify(() => api.message.translateMessage(messageId, language)).called(1);
verifyNoMoreInteractions(api.message);
});
test('`.partialUpdateMessage`', () async {
const messageId = 'test-message-id';
final message = Message(id: messageId);
const set = {'text': 'Update Message text'};
const unset = ['pinExpires'];
final updateMessageResponse = UpdateMessageResponse()
..message = message.copyWith(text: set['text'], pinExpires: null);
when(() => api.message.partialUpdateMessage(
message.id,
set: set,
unset: unset,
)).thenAnswer((_) async => updateMessageResponse);
final res = await client.partialUpdateMessage(
messageId,
set: set,
unset: unset,
);
expect(res, isNotNull);
expect(res.message.id, message.id);
expect(res.message.id, message.id);
expect(res.message.text, set['text']);
expect(res.message.pinExpires, isNull);
verify(() => api.message.partialUpdateMessage(
message.id,
set: set,
unset: unset,
)).called(1);
verifyNoMoreInteractions(api.message);
});
group('`.pinMessage`', () {
test('should work fine without passing timeoutOrExpirationDate',
() async {
const messageId = 'test-message-id';
final message = Message(id: messageId);
when(() => api.message.partialUpdateMessage(
messageId,
set: any(named: 'set'),
unset: any(named: 'unset'),
)).thenAnswer((_) async => UpdateMessageResponse()
..message = message.copyWith(
pinned: true,
pinExpires: null,
status: MessageSendingStatus.sent,
));
final res = await client.pinMessage(messageId);
expect(res, isNotNull);
expect(res.message.pinned, isTrue);
expect(res.message.pinExpires, isNull);
verify(() => api.message.partialUpdateMessage(
messageId,
set: any(named: 'set'),
unset: any(named: 'unset'),
)).called(1);
verifyNoMoreInteractions(api.message);
});
test(
'should work fine if passed timeoutOrExpirationDate as num(seconds)',
() async {
const messageId = 'test-message-id';
final message = Message(id: messageId);
const timeoutOrExpirationDate = 300; // 300 seconds
when(() => api.message.partialUpdateMessage(
message.id,
set: any(named: 'set'),
unset: any(named: 'unset'),
)).thenAnswer((_) async => UpdateMessageResponse()
..message = message.copyWith(
pinned: true,
pinExpires: DateTime.now().add(
const Duration(seconds: timeoutOrExpirationDate),
),
status: MessageSendingStatus.sent,
));
final res = await client.pinMessage(
messageId,
timeoutOrExpirationDate: timeoutOrExpirationDate,
);
expect(res, isNotNull);
expect(res.message.pinned, isTrue);
expect(res.message.pinExpires, isNotNull);
verify(() => api.message.partialUpdateMessage(
messageId,
set: any(named: 'set'),
unset: any(named: 'unset'),
)).called(1);
verifyNoMoreInteractions(api.message);
},
);
test(
'should work fine if passed timeoutOrExpirationDate as DateTime',
() async {
const messageId = 'test-message-id';
final message = Message(id: messageId);
final timeoutOrExpirationDate =
DateTime.now().add(const Duration(days: 3)); // 3 days
when(() => api.message.partialUpdateMessage(
messageId,
set: any(named: 'set'),
unset: any(named: 'unset'),
)).thenAnswer((_) async => UpdateMessageResponse()
..message = message.copyWith(
pinned: true,
pinExpires: timeoutOrExpirationDate,
status: MessageSendingStatus.sent,
));
final res = await client.pinMessage(
messageId,
timeoutOrExpirationDate: timeoutOrExpirationDate,
);
expect(res, isNotNull);
expect(res.message.pinned, isTrue);
expect(res.message.pinExpires, isNotNull);
expect(res.message.pinExpires, timeoutOrExpirationDate.toUtc());
verify(() => api.message.partialUpdateMessage(
messageId,
set: any(named: 'set'),
unset: any(named: 'unset'),
)).called(1);
verifyNoMoreInteractions(api.message);
},
);
test(
'should throw if invalid timeoutOrExpirationDate is passed',
() async {
const messageId = 'test-message-id';
const timeoutOrExpirationDate = 'invalid-value';
try {
await client.pinMessage(
messageId,
timeoutOrExpirationDate: timeoutOrExpirationDate,
);
} catch (e) {
expect(e, isA<ArgumentError>());
}
},
);
});
test('`.unpinMessage`', () async {
const messageId = 'test-message-id';
final message = Message(id: messageId, pinned: true);
when(() => api.message.partialUpdateMessage(
messageId,
set: {'pinned': false},
)).thenAnswer((_) async => UpdateMessageResponse()
..message = message.copyWith(
pinned: false,
status: MessageSendingStatus.sent,
));
final res = await client.unpinMessage(messageId);
expect(res, isNotNull);
expect(res.message.pinned, isFalse);
verify(() => api.message.partialUpdateMessage(
messageId,
set: {'pinned': false},
)).called(1);
verifyNoMoreInteractions(api.message);
});
});
}