test(persistence): add tests for stream chat persistence client

Signed-off-by: Sahil Kumar <[email protected]>
This commit is contained in:
Sahil Kumar
2021-03-18 20:38:26 +05:30
parent 827e6a5dbd
commit bea545b154
9 changed files with 662 additions and 141 deletions
@@ -6,19 +6,11 @@ import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/src/converter/converter.dart'; import 'package:stream_chat_persistence/src/converter/converter.dart';
import 'package:stream_chat_persistence/src/dao/dao.dart'; import 'package:stream_chat_persistence/src/dao/dao.dart';
import 'package:stream_chat_persistence/src/entity/entity.dart'; import 'package:stream_chat_persistence/src/entity/entity.dart';
import 'package:stream_chat_persistence/src/db/shared/shared_db.dart';
export 'shared/shared_db.dart';
part 'moor_chat_database.g.dart'; part 'moor_chat_database.g.dart';
LazyDatabase _openConnection(
String userId, {
bool logStatements = false,
}) =>
LazyDatabase(() async => SharedDB.constructDatabase(
userId,
logStatements: logStatements,
));
/// A chat database implemented using moor /// A chat database implemented using moor
@UseMoor(tables: [ @UseMoor(tables: [
Channels, Channels,
@@ -44,12 +36,9 @@ LazyDatabase _openConnection(
class MoorChatDatabase extends _$MoorChatDatabase { class MoorChatDatabase extends _$MoorChatDatabase {
/// Creates a new moor chat database instance /// Creates a new moor chat database instance
MoorChatDatabase( MoorChatDatabase(
this._userId, { this._userId,
logStatements = false, QueryExecutor executor,
}) : super(_openConnection( ) : super(executor);
_userId,
logStatements: logStatements,
));
/// Instantiate a new database instance /// Instantiate a new database instance
MoorChatDatabase.connect( MoorChatDatabase.connect(
@@ -14,15 +14,40 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
/// A Helper class to construct new instances of [MoorChatDatabase] specifically /// A Helper class to construct new instances of [MoorChatDatabase] specifically
/// for native platform applications /// for native platform applications
class SharedDB { class SharedDB {
/// Returns a new instance of [VmDatabase] created using [userId] /// Returns a new instance of [MoorChatDatabase].
/// on a regular isolate. static MoorChatDatabase constructDatabase(
///
/// Generally used with [ConnectionMode.regular].
static Future<VmDatabase> constructDatabase(
String userId, { String userId, {
bool logStatements = false, bool logStatements = false,
}) async { ConnectionMode connectionMode = ConnectionMode.regular,
}) {
final dbName = 'db_$userId'; final dbName = 'db_$userId';
if (connectionMode == ConnectionMode.background) {
return MoorChatDatabase.connect(
userId,
DatabaseConnection.delayed(Future(() async {
final isolate = await _createMoorIsolate(
dbName,
logStatements: logStatements,
);
return isolate.connect();
})),
);
}
return MoorChatDatabase(
userId,
LazyDatabase(
() async => _constructDatabase(
dbName,
logStatements: logStatements,
),
),
);
}
static Future<VmDatabase> _constructDatabase(
String dbName, {
bool logStatements = false,
}) async {
if (Platform.isIOS || Platform.isAndroid) { if (Platform.isIOS || Platform.isAndroid) {
final dir = await getApplicationDocumentsDirectory(); final dir = await getApplicationDocumentsDirectory();
final path = join(dir.path, '$dbName.sqlite'); final path = join(dir.path, '$dbName.sqlite');
@@ -66,27 +91,6 @@ class SharedDB {
return await receivePort.first as MoorIsolate; return await receivePort.first as MoorIsolate;
} }
/// Returns a new instance of [MoorChatDatabase] using the factory constructor
/// [MoorChatDatabase.connect] created on a background isolate.
///
/// Generally used with [ConnectionMode.background].
static MoorChatDatabase constructMoorChatDatabase(
String userId, {
bool logStatements = false,
}) {
final dbName = 'db_$userId';
return MoorChatDatabase.connect(
userId,
DatabaseConnection.delayed(Future(() async {
final isolate = await _createMoorIsolate(
dbName,
logStatements: logStatements,
);
return isolate.connect();
})),
);
}
} }
class _IsolateStartRequest { class _IsolateStartRequest {
@@ -1,28 +1,16 @@
import 'package:moor/backends.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
import 'package:stream_chat_persistence/stream_chat_persistence.dart'; import 'package:stream_chat_persistence/stream_chat_persistence.dart';
/// A Helper class to construct new instances of [MoorChatDatabase] /// A Helper class to construct new instances of [MoorChatDatabase]
class SharedDB { class SharedDB {
/// Returns a new instance of database. /// Returns a new instance of [MoorChatDatabase].
/// static MoorChatDatabase constructDatabase(
/// Generally used with [ConnectionMode.regular].
static Future<DelegatedDatabase> constructDatabase(
String userId, { String userId, {
bool logStatements = false, bool logStatements = false,
ConnectionMode connectionMode = ConnectionMode.regular,
}) { }) {
throw UnsupportedError( throw UnsupportedError(
'No implementation of the constructDatabase api provided'); 'No implementation of the constructDatabase api provided',
} );
/// Return a new instance of moor chat database.
///
/// Generally used with [ConnectionMode.background].
static MoorChatDatabase constructMoorChatDatabase(
String userId, {
bool logStatements = false,
}) {
throw UnsupportedError(
'No implementation of the constructMoorChatDatabase api provided');
} }
} }
@@ -6,26 +6,14 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
/// A Helper class to construct new instances of [MoorChatDatabase] specifically /// A Helper class to construct new instances of [MoorChatDatabase] specifically
/// for Web applications /// for Web applications
class SharedDB { class SharedDB {
/// Returns a new instance of [WebDatabase] created using [userId]. /// Returns a new instance of [MoorChatDatabase].
/// static MoorChatDatabase constructDatabase(
/// Generally used with [ConnectionMode.regular].
static Future<WebDatabase> constructDatabase(
String userId, {
bool logStatements = false,
}) async {
final dbName = 'db_$userId';
return WebDatabase(dbName, logStatements: logStatements);
}
/// Returns a new instance of [MoorChatDatabase] creating using the
/// default constructor.
///
/// Generally used with [ConnectionMode.background].
static MoorChatDatabase constructMoorChatDatabase(
String userId, { String userId, {
bool logStatements = false, bool logStatements = false,
ConnectionMode connectionMode = ConnectionMode.regular, // Ignored on web
}) { }) {
final dbName = 'db_$userId'; final dbName = 'db_$userId';
return MoorChatDatabase(dbName, logStatements: logStatements); final queryExecutor = WebDatabase(dbName, logStatements: logStatements);
return MoorChatDatabase(userId, queryExecutor);
} }
} }
@@ -4,7 +4,6 @@ import 'package:mutex/mutex.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
import 'package:stream_chat_persistence/src/db/shared/shared_db.dart';
/// Various connection modes on which [StreamChatPersistenceClient] can work /// Various connection modes on which [StreamChatPersistenceClient] can work
enum ConnectionMode { enum ConnectionMode {
@@ -15,6 +14,9 @@ enum ConnectionMode {
background, background,
} }
/// Signature for a function which provides instance of [MoorChatDatabase]
typedef DatabaseProvider = MoorChatDatabase Function(String, ConnectionMode);
final _levelEmojiMapper = { final _levelEmojiMapper = {
Level.INFO: '', Level.INFO: '',
Level.WARNING: '⚠️', Level.WARNING: '⚠️',
@@ -36,26 +38,6 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
_logger.onRecord.listen(logHandlerFunction ?? _defaultLogHandler); _logger.onRecord.listen(logHandlerFunction ?? _defaultLogHandler);
} }
/// A function that has a parameter of type [LogRecord].
/// This is called on every new log record.
/// By default the client will use the handler returned by
/// [_getDefaultLogHandler].
/// Setting it you can handle the log messages directly instead of have them
/// written to stdout,
/// this is very convenient if you use an error tracking tool or if you want
/// to centralize your logs into one facility.
///
/// ```dart
/// myLogHandlerFunction = (LogRecord record) {
/// // do something with the record (ie. send it to Sentry or Fabric)
/// }
///
/// final client = StreamChatPersistenceClient(
/// logHandlerFunction: myLogHandlerFunction,
/// );
///```
LogHandlerFunction logHandlerFunction;
/// [MoorChatDatabase] instance used by this client. /// [MoorChatDatabase] instance used by this client.
@visibleForTesting @visibleForTesting
MoorChatDatabase db; MoorChatDatabase db;
@@ -84,24 +66,25 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
return ret; return ret;
} }
MoorChatDatabase _defaultDatabaseProvider(
String userId,
ConnectionMode mode,
) =>
SharedDB.constructDatabase(userId, connectionMode: mode);
@override @override
Future<void> connect(String userId) async { Future<void> connect(
String userId, {
DatabaseProvider databaseProvider, // Used only for testing
}) async {
if (db != null) { if (db != null) {
throw Exception( throw Exception(
'An instance of StreamChatDatabase is already connected.\n' 'An instance of StreamChatDatabase is already connected.\n'
'disconnect the previous instance before connecting again.', 'disconnect the previous instance before connecting again.',
); );
} }
switch (_connectionMode) { db = databaseProvider?.call(userId, _connectionMode) ??
case ConnectionMode.regular: _defaultDatabaseProvider(userId, _connectionMode);
_logger.info('Connecting on a regular isolate');
db = MoorChatDatabase(userId);
return;
case ConnectionMode.background:
_logger.info('Connecting on background isolate');
db = SharedDB.constructMoorChatDatabase(userId);
return;
}
} }
@override @override
@@ -259,9 +242,9 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
@override @override
Future<void> updateChannelQueries( Future<void> updateChannelQueries(
Map<String, dynamic> filter, Map<String, dynamic> filter,
List<String> cids, List<String> cids, {
bool clearQueryCache, bool clearQueryCache = false,
) => }) =>
_readProtected(() async { _readProtected(() async {
_logger.info('updateChannelQueries'); _logger.info('updateChannelQueries');
return db.channelQueryDao.updateChannelQueries( return db.channelQueryDao.updateChannelQueries(
@@ -27,6 +27,7 @@ dependency_overrides:
path: ../stream_chat path: ../stream_chat
dev_dependencies: dev_dependencies:
test: ^1.15.7
build_runner: ^1.11.0 build_runner: ^1.11.0
mockito: ^4.1.3
moor_generator: ^3.4.1 moor_generator: ^3.4.1
test: ^1.15.7
@@ -0,0 +1,71 @@
import 'package:mockito/mockito.dart';
import 'package:stream_chat_persistence/src/dao/dao.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
class MockChatDatabase extends Mock implements MoorChatDatabase {
UserDao _userDao;
@override
UserDao get userDao => _userDao ??= MockUserDao();
ChannelDao _channelDao;
@override
ChannelDao get channelDao => _channelDao ??= MockChannelDao();
MessageDao _messageDao;
@override
MessageDao get messageDao => _messageDao ??= MockMessageDao();
PinnedMessageDao _pinnedMessageDao;
@override
PinnedMessageDao get pinnedMessageDao =>
_pinnedMessageDao ??= MockPinnedMessageDao();
MemberDao _memberDao;
@override
MemberDao get memberDao => _memberDao ??= MockMemberDao();
ReactionDao _reactionDao;
@override
ReactionDao get reactionDao => _reactionDao ??= MockReactionDao();
ReadDao _readDao;
@override
ReadDao get readDao => _readDao ??= MockReadDao();
ChannelQueryDao _channelQueryDao;
@override
ChannelQueryDao get channelQueryDao =>
_channelQueryDao ??= MockChannelQueryDao();
ConnectionEventDao _connectionEventDao;
@override
ConnectionEventDao get connectionEventDao =>
_connectionEventDao ??= MockConnectionEventDao();
}
class MockUserDao extends Mock implements UserDao {}
class MockChannelDao extends Mock implements ChannelDao {}
class MockMessageDao extends Mock implements MessageDao {}
class MockPinnedMessageDao extends Mock implements PinnedMessageDao {}
class MockMemberDao extends Mock implements MemberDao {}
class MockReactionDao extends Mock implements ReactionDao {}
class MockReadDao extends Mock implements ReadDao {}
class MockChannelQueryDao extends Mock implements ChannelQueryDao {}
class MockConnectionEventDao extends Mock implements ConnectionEventDao {}
@@ -1,27 +0,0 @@
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
import 'package:stream_chat_persistence/src/stream_chat_persistence_client.dart';
import 'package:test/test.dart';
void main() {
group('connect', () {
test('throws exception because already connected', () {
final streamChatPersistenceClient = StreamChatPersistenceClient(
connectionMode: ConnectionMode.background,
logLevel: Level.INFO,
)..db = MoorChatDatabase(
'test',
persistOnDisk: false,
);
expect(
() => streamChatPersistenceClient.connect('test'),
throwsA(allOf(isException, predicate((e) {
return e.message ==
'An instance of StreamChatDatabase is already connected.\n'
'disconnect the previous instance before connecting again.';
}))),
);
});
});
}
@@ -0,0 +1,524 @@
import 'package:mockito/mockito.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
import 'package:stream_chat_persistence/src/stream_chat_persistence_client.dart';
import 'package:test/test.dart';
import 'mock_chat_database.dart';
import 'src/utils/date_matcher.dart';
MoorChatDatabase _testDatabaseProvider(String userId, ConnectionMode mode) =>
MoorChatDatabase.testable(userId);
void main() {
group('connect', () {
const userId = 'testUserId';
test('successfully connects with the Database', () async {
final client = StreamChatPersistenceClient(logLevel: Level.ALL);
expect(client.db, isNull);
await client.connect(userId, databaseProvider: _testDatabaseProvider);
expect(client.db, isNotNull);
expect(client.db, isA<MoorChatDatabase>());
expect(client.db.userId, userId);
addTearDown(() async {
await client.disconnect();
});
});
test('throws if already connected', () async {
final client = StreamChatPersistenceClient(logLevel: Level.ALL);
expect(client.db, isNull);
await client.connect(userId, databaseProvider: _testDatabaseProvider);
expect(client.db, isNotNull);
expect(client.db, isNotNull);
expect(client.db, isA<MoorChatDatabase>());
expect(client.db.userId, userId);
expect(
() => client.connect(userId, databaseProvider: _testDatabaseProvider),
throwsException,
);
addTearDown(() async {
await client.disconnect();
});
});
});
test('disconnect', () async {
const userId = 'testUserId';
final client = StreamChatPersistenceClient(logLevel: Level.ALL);
await client.connect(userId, databaseProvider: _testDatabaseProvider);
expect(client.db, isNotNull);
await client.disconnect(flush: true);
expect(client.db, isNull);
});
group('client functions', () {
const userId = 'testUserId';
final mockDatabase = MockChatDatabase();
MoorChatDatabase _mockDatabaseProvider(_, __) => mockDatabase;
StreamChatPersistenceClient client;
setUp(() async {
client = StreamChatPersistenceClient(logLevel: Level.ALL);
await client.connect(userId, databaseProvider: _mockDatabaseProvider);
});
test('getReplies', () async {
const parentId = 'testParentId';
final replies = List.generate(3, (index) => Message(id: 'testId$index'));
when(mockDatabase.messageDao.getThreadMessagesByParentId(parentId))
.thenAnswer((_) async => replies);
final fetchedReplies = await client.getReplies(parentId);
expect(fetchedReplies.length, replies.length);
verify(mockDatabase.messageDao.getThreadMessagesByParentId(parentId))
.called(1);
});
test('getConnectionInfo', () async {
final event = Event(type: 'testEvent');
when(mockDatabase.connectionEventDao.connectionEvent)
.thenAnswer((_) async => event);
final fetchedEvent = await client.getConnectionInfo();
expect(fetchedEvent.type, event.type);
verify(mockDatabase.connectionEventDao.connectionEvent).called(1);
});
test('getLastSyncAt', () async {
final lastSync = DateTime.now();
when(mockDatabase.connectionEventDao.lastSyncAt)
.thenAnswer((_) async => lastSync);
final fetchedLastSync = await client.getLastSyncAt();
expect(fetchedLastSync, isSameDateAs(lastSync));
verify(mockDatabase.connectionEventDao.lastSyncAt).called(1);
});
test('updateConnectionInfo', () async {
final event = Event(type: 'testEvent');
when(mockDatabase.connectionEventDao.updateConnectionEvent(event))
.thenAnswer((_) async {
return;
});
await client.updateConnectionInfo(event);
verify(mockDatabase.connectionEventDao.updateConnectionEvent(event))
.called(1);
});
test('updateLastSyncAt', () async {
final lastSync = DateTime.now();
when(mockDatabase.connectionEventDao.updateLastSyncAt(lastSync))
.thenAnswer((_) {
return;
});
await client.updateLastSyncAt(lastSync);
verify(mockDatabase.connectionEventDao.updateLastSyncAt(lastSync))
.called(1);
});
test('getChannelCids', () async {
final channelCids = List.generate(3, (index) => 'testCid$index');
when(mockDatabase.channelDao.cids).thenAnswer((_) async => channelCids);
final fetchedChannelCids = await client.getChannelCids();
expect(fetchedChannelCids.length, channelCids.length);
verify(mockDatabase.channelDao.cids).called(1);
});
test('getChannelByCid', () async {
const cid = 'testCid';
final channelModel = ChannelModel(cid: cid);
when(mockDatabase.channelDao.getChannelByCid(cid))
.thenAnswer((_) async => channelModel);
final fetchedChannelModel = await client.getChannelByCid(cid);
expect(fetchedChannelModel.cid, channelModel.cid);
verify(mockDatabase.channelDao.getChannelByCid(cid)).called(1);
});
test('getMembersByCid', () async {
const cid = 'testCid';
final members = List.generate(3, (index) => Member());
when(mockDatabase.memberDao.getMembersByCid(cid))
.thenAnswer((_) async => members);
final fetchedMembers = await client.getMembersByCid(cid);
expect(fetchedMembers.length, members.length);
verify(mockDatabase.memberDao.getMembersByCid(cid)).called(1);
});
test('getReadsByCid', () async {
const cid = 'testCid';
final reads = List.generate(3, (index) => Read());
when(mockDatabase.readDao.getReadsByCid(cid))
.thenAnswer((_) async => reads);
final fetchedReads = await client.getReadsByCid(cid);
expect(fetchedReads.length, reads.length);
verify(mockDatabase.readDao.getReadsByCid(cid)).called(1);
});
test('getMessagesByCid', () async {
const cid = 'testCid';
final messages = List.generate(3, (index) => Message());
when(mockDatabase.messageDao.getMessagesByCid(cid))
.thenAnswer((_) async => messages);
final fetchedMessages = await client.getMessagesByCid(cid);
expect(fetchedMessages.length, messages.length);
verify(mockDatabase.messageDao.getMessagesByCid(cid)).called(1);
});
test('getPinnedMessagesByCid', () async {
const cid = 'testCid';
final messages = List.generate(3, (index) => Message());
when(mockDatabase.pinnedMessageDao.getMessagesByCid(cid))
.thenAnswer((_) async => messages);
final fetchedMessages = await client.getPinnedMessagesByCid(cid);
expect(fetchedMessages.length, messages.length);
verify(mockDatabase.pinnedMessageDao.getMessagesByCid(cid)).called(1);
});
test('getChannelStateByCid', () async {
const cid = 'testCid';
final messages = List.generate(3, (index) => Message());
final members = List.generate(3, (index) => Member());
final reads = List.generate(3, (index) => Read());
final channel = ChannelModel(cid: cid);
when(mockDatabase.memberDao.getMembersByCid(cid))
.thenAnswer((_) async => members);
when(mockDatabase.readDao.getReadsByCid(cid))
.thenAnswer((_) async => reads);
when(mockDatabase.channelDao.getChannelByCid(cid))
.thenAnswer((_) async => channel);
when(mockDatabase.messageDao.getMessagesByCid(cid))
.thenAnswer((_) async => messages);
when(mockDatabase.pinnedMessageDao.getMessagesByCid(cid))
.thenAnswer((_) async => messages);
final fetchedChannelState = await client.getChannelStateByCid(cid);
expect(fetchedChannelState.messages.length, messages.length);
expect(fetchedChannelState.pinnedMessages.length, messages.length);
expect(fetchedChannelState.members.length, members.length);
expect(fetchedChannelState.read.length, reads.length);
expect(fetchedChannelState.channel.cid, channel.cid);
verify(mockDatabase.memberDao.getMembersByCid(cid)).called(1);
verify(mockDatabase.readDao.getReadsByCid(cid)).called(1);
verify(mockDatabase.channelDao.getChannelByCid(cid)).called(1);
verify(mockDatabase.messageDao.getMessagesByCid(cid)).called(1);
verify(mockDatabase.pinnedMessageDao.getMessagesByCid(cid)).called(1);
});
test('getChannelStates', () async {
const cid = 'testCid';
final channels = List.generate(3, (index) => ChannelModel(cid: cid));
final messages = List.generate(3, (index) => Message());
final members = List.generate(3, (index) => Member());
final reads = List.generate(3, (index) => Read());
final channel = ChannelModel(cid: cid);
final channelStates = channels
.map(
(channel) => ChannelState(
channel: channel,
messages: messages,
pinnedMessages: messages,
members: members,
read: reads,
),
)
.toList(growable: false);
when(mockDatabase.channelQueryDao.getChannels())
.thenAnswer((_) async => channels);
when(mockDatabase.memberDao.getMembersByCid(cid))
.thenAnswer((_) async => members);
when(mockDatabase.readDao.getReadsByCid(cid))
.thenAnswer((_) async => reads);
when(mockDatabase.channelDao.getChannelByCid(cid))
.thenAnswer((_) async => channel);
when(mockDatabase.messageDao.getMessagesByCid(cid))
.thenAnswer((_) async => messages);
when(mockDatabase.pinnedMessageDao.getMessagesByCid(cid))
.thenAnswer((_) async => messages);
final fetchedChannelStates = await client.getChannelStates();
expect(fetchedChannelStates.length, channelStates.length);
for (var i = 0; i < fetchedChannelStates.length; i++) {
final original = channelStates[i];
final fetched = fetchedChannelStates[i];
expect(fetched.members.length, original.members.length);
expect(fetched.messages.length, original.messages.length);
expect(fetched.pinnedMessages.length, original.pinnedMessages.length);
expect(fetched.read.length, original.read.length);
expect(fetched.channel.cid, original.channel.cid);
}
verify(mockDatabase.channelQueryDao.getChannels()).called(1);
verify(mockDatabase.memberDao.getMembersByCid(cid)).called(3);
verify(mockDatabase.readDao.getReadsByCid(cid)).called(3);
verify(mockDatabase.channelDao.getChannelByCid(cid)).called(3);
verify(mockDatabase.messageDao.getMessagesByCid(cid)).called(3);
verify(mockDatabase.pinnedMessageDao.getMessagesByCid(cid)).called(3);
});
test('updateChannelQueries', () async {
const filter = <String, dynamic>{};
const cids = <String>[];
when(mockDatabase.channelQueryDao.updateChannelQueries(filter, cids))
.thenAnswer((realInvocation) async {
return;
});
await client.updateChannelQueries(filter, cids);
verify(mockDatabase.channelQueryDao.updateChannelQueries(filter, cids))
.called(1);
});
test('deleteMessageById', () async {
const messageId = 'testMessageId';
when(mockDatabase.messageDao.deleteMessageByIds([messageId]))
.thenAnswer((_) async {
return;
});
await client.deleteMessageById(messageId);
verify(mockDatabase.messageDao.deleteMessageByIds([messageId])).called(1);
});
test('deletePinnedMessageById', () async {
const messageId = 'testMessageId';
when(mockDatabase.pinnedMessageDao.deleteMessageByIds([messageId]))
.thenAnswer((_) async {
return;
});
await client.deletePinnedMessageById(messageId);
verify(mockDatabase.pinnedMessageDao.deleteMessageByIds([messageId]))
.called(1);
});
test('deleteMessageByIds', () async {
const messageIds = <String>[];
when(mockDatabase.messageDao.deleteMessageByIds(messageIds))
.thenAnswer((_) async {
return;
});
await client.deleteMessageByIds(messageIds);
verify(mockDatabase.messageDao.deleteMessageByIds(messageIds)).called(1);
});
test('deletePinnedMessageByIds', () async {
const messageIds = <String>[];
when(mockDatabase.pinnedMessageDao.deleteMessageByIds(messageIds))
.thenAnswer((_) async {
return;
});
await client.deletePinnedMessageByIds(messageIds);
verify(mockDatabase.pinnedMessageDao.deleteMessageByIds(messageIds))
.called(1);
});
test('deleteMessageByCid', () async {
const cid = 'testCid';
when(mockDatabase.messageDao.deleteMessageByCids([cid]))
.thenAnswer((_) async {
return;
});
await client.deleteMessageByCid(cid);
verify(mockDatabase.messageDao.deleteMessageByCids([cid])).called(1);
});
test('deletePinnedMessageByCid', () async {
const cid = 'testCid';
when(mockDatabase.pinnedMessageDao.deleteMessageByCids([cid]))
.thenAnswer((_) async {
return;
});
await client.deletePinnedMessageByCid(cid);
verify(mockDatabase.pinnedMessageDao.deleteMessageByCids([cid]))
.called(1);
});
test('deleteMessageByCids', () async {
const cids = <String>[];
when(mockDatabase.messageDao.deleteMessageByCids(cids))
.thenAnswer((_) async {
return;
});
await client.deleteMessageByCids(cids);
verify(mockDatabase.messageDao.deleteMessageByCids(cids)).called(1);
});
test('deletePinnedMessageByCids', () async {
const cids = <String>[];
when(mockDatabase.pinnedMessageDao.deleteMessageByCids(cids))
.thenAnswer((_) async {
return;
});
await client.deletePinnedMessageByCids(cids);
verify(mockDatabase.pinnedMessageDao.deleteMessageByCids(cids)).called(1);
});
test('deleteChannels', () async {
const cids = <String>[];
when(mockDatabase.channelDao.deleteChannelByCids(cids))
.thenAnswer((_) async {
return;
});
await client.deleteChannels(cids);
verify(mockDatabase.channelDao.deleteChannelByCids(cids)).called(1);
});
test('updateMessages', () async {
const cid = 'testCid';
final messages = List.generate(3, (index) => Message());
when(mockDatabase.messageDao.updateMessages(cid, messages))
.thenAnswer((_) async {
return;
});
await client.updateMessages(cid, messages);
verify(mockDatabase.messageDao.updateMessages(cid, messages)).called(1);
});
test('updatePinnedMessages', () async {
const cid = 'testCid';
final messages = List.generate(3, (index) => Message());
when(mockDatabase.pinnedMessageDao.updateMessages(cid, messages))
.thenAnswer((_) async {
return;
});
await client.updatePinnedMessages(cid, messages);
verify(mockDatabase.pinnedMessageDao.updateMessages(cid, messages))
.called(1);
});
test('getChannelThreads', () async {
const cid = 'testCid';
final messages =
List.generate(3, (index) => Message(parentId: 'testParentId$index'));
final threads = messages.fold<Map<String, List<Message>>>(
{},
(prev, curr) {
return prev
..update(
curr.parentId,
(value) => [...value, curr],
ifAbsent: () => [],
);
},
);
when(mockDatabase.messageDao.getThreadMessages(cid))
.thenAnswer((realInvocation) async => messages);
final fetchedThreads = await client.getChannelThreads(cid);
expect(fetchedThreads.length, threads.length);
for (var i = 0; i < fetchedThreads.length; i++) {
final original = threads.entries.elementAt(i);
final fetched = fetchedThreads.entries.elementAt(i);
expect(fetched.key, original.key);
}
verify(mockDatabase.messageDao.getThreadMessages(cid)).called(1);
});
test('updateChannels', () async {
final channels = List.generate(3, (index) => ChannelModel());
when(mockDatabase.channelDao.updateChannels(channels))
.thenAnswer((_) async {
return;
});
await client.updateChannels(channels);
verify(mockDatabase.channelDao.updateChannels(channels)).called(1);
});
test('updateMembers', () async {
const cid = 'testCid';
final members = List.generate(3, (index) => Member());
when(mockDatabase.memberDao.updateMembers(cid, members))
.thenAnswer((_) async {
return;
});
await client.updateMembers(cid, members);
verify(mockDatabase.memberDao.updateMembers(cid, members)).called(1);
});
test('updateReads', () async {
const cid = 'testCid';
final reads = List.generate(3, (index) => Read());
when(mockDatabase.readDao.updateReads(cid, reads)).thenAnswer((_) async {
return;
});
await client.updateReads(cid, reads);
verify(mockDatabase.readDao.updateReads(cid, reads)).called(1);
});
test('updateUsers', () async {
final users = List.generate(3, (index) => User());
when(mockDatabase.userDao.updateUsers(users)).thenAnswer((_) async {
return;
});
await client.updateUsers(users);
verify(mockDatabase.userDao.updateUsers(users)).called(1);
});
test('updateReactions', () async {
final reactions = List.generate(3, (index) => Reaction());
when(mockDatabase.reactionDao.updateReactions(reactions))
.thenAnswer((_) async {
return;
});
await client.updateReactions(reactions);
verify(mockDatabase.reactionDao.updateReactions(reactions)).called(1);
});
test('deleteReactionsByMessageId', () async {
final messageIds = <String>[];
when(mockDatabase.reactionDao.deleteReactionsByMessageIds(messageIds))
.thenAnswer((_) async {
return;
});
await client.deleteReactionsByMessageId(messageIds);
verify(mockDatabase.reactionDao.deleteReactionsByMessageIds(messageIds))
.called(1);
});
test('deleteMembersByCids', () async {
final cids = <String>[];
when(mockDatabase.memberDao.deleteMemberByCids(cids))
.thenAnswer((_) async {
return;
});
await client.deleteMembersByCids(cids);
verify(mockDatabase.memberDao.deleteMemberByCids(cids)).called(1);
});
tearDown(() async {
await client.disconnect(flush: true);
});
});
}