fix(stream_chat_persistence): tests

This commit is contained in:
Felix Angelov
2021-03-24 00:49:11 -05:00
67 changed files with 5543 additions and 251 deletions
@@ -19,10 +19,10 @@ class ChannelDao extends DatabaseAccessor<MoorChatDatabase>
(select(channels)..where((c) => c.cid.equals(cid))).join([
leftOuterJoin(users, channels.createdById.equalsExp(users.id)),
]).map((rows) {
final channel = rows.readTable(channels);
final createdBy = rows.readTable(users);
final channel = rows.readTableOrNull(channels);
final createdBy = rows.readTableOrNull(users);
return channel.toChannelModel(createdBy: createdBy?.toUser());
}).getSingle();
}).getSingleOrNull();
/// Delete all channels by matching cid in [cids]
///
@@ -32,7 +32,7 @@ class ChannelQueryDao extends DatabaseAccessor<MoorChatDatabase>
Future<void> updateChannelQueries(
Map<String, dynamic> filter,
List<String> cids, {
bool clearQueryCache,
bool clearQueryCache = false,
}) async =>
transaction(() async {
final hash = _computeHash(filter);
@@ -57,6 +57,14 @@ class ChannelQueryDao extends DatabaseAccessor<MoorChatDatabase>
});
});
///
Future<List<String>> getCachedChannelCids(Map<String, dynamic> filter) {
final hash = _computeHash(filter);
return (select(channelQueries)..where((c) => c.queryHash.equals(hash)))
.map((c) => c.channelCid)
.get();
}
/// Get list of channels by filter, sort and paginationParams
Future<List<ChannelModel>> getChannels({
Map<String, dynamic> filter,
@@ -72,12 +80,7 @@ class ChannelQueryDao extends DatabaseAccessor<MoorChatDatabase>
return true;
}(), '');
final hash = _computeHash(filter);
final cachedChannelCids = await (select(channelQueries)
..where((c) => c.queryHash.equals(hash)))
.map((c) => c.channelCid)
.get();
final cachedChannelCids = await getCachedChannelCids(filter);
final query = select(channels)..where((c) => c.cid.isIn(cachedChannelCids));
final cachedChannels = await (query.join([
@@ -2,14 +2,13 @@ import 'package:moor/moor.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/entity/connection_events.dart';
import 'package:stream_chat_persistence/src/entity/users.dart';
import 'package:stream_chat_persistence/src/mapper/mapper.dart';
part 'connection_event_dao.g.dart';
/// The Data Access Object for operations in [ConnectionEvents] table.
@UseDao(tables: [ConnectionEvents, Users])
@UseDao(tables: [ConnectionEvents])
class ConnectionEventDao extends DatabaseAccessor<MoorChatDatabase>
with _$ConnectionEventDaoMixin {
/// Creates a new connection event dao instance
@@ -18,16 +17,16 @@ class ConnectionEventDao extends DatabaseAccessor<MoorChatDatabase>
/// Get the latest stored connection event
Future<Event> get connectionEvent => select(connectionEvents)
.map((eventEntity) => eventEntity.toEvent())
.getSingle();
.getSingleOrNull();
/// Get the latest stored lastSyncAt
Future<DateTime> get lastSyncAt =>
select(connectionEvents).getSingle().then((r) => r?.lastSyncAt);
select(connectionEvents).getSingleOrNull().then((r) => r?.lastSyncAt);
/// Update stored connection event with latest data
Future<void> updateConnectionEvent(Event event) async =>
transaction(() async {
final connectionInfo = await select(connectionEvents).getSingle();
final connectionInfo = await select(connectionEvents).getSingleOrNull();
await into(connectionEvents).insert(
ConnectionEventEntity(
id: 1,
@@ -9,5 +9,4 @@ part of 'connection_event_dao.dart';
mixin _$ConnectionEventDaoMixin on DatabaseAccessor<MoorChatDatabase> {
$ConnectionEventsTable get connectionEvents =>
attachedDatabase.connectionEvents;
$UsersTable get users => attachedDatabase.users;
}
@@ -36,9 +36,9 @@ class MessageDao extends DatabaseAccessor<MoorChatDatabase>
(delete(messages)..where((tbl) => tbl.channelCid.isIn(cids))).go();
Future<Message> _messageFromJoinRow(TypedResult rows) async {
final userEntity = rows.readTable(_users);
final pinnedByEntity = rows.readTable(_pinnedByUsers);
final msgEntity = rows.readTable(messages);
final userEntity = rows.readTableOrNull(_users);
final pinnedByEntity = rows.readTableOrNull(_pinnedByUsers);
final msgEntity = rows.readTableOrNull(messages);
final latestReactions = await _db.reactionDao.getReactions(msgEntity.id);
final ownReactions = await _db.reactionDao.getReactionsByUserId(
msgEntity.id,
@@ -66,13 +66,13 @@ class MessageDao extends DatabaseAccessor<MoorChatDatabase>
])
..where(messages.id.equals(id)))
.map(_messageFromJoinRow)
.getSingle();
.getSingleOrNull();
/// Returns all the messages of a particular thread by matching
/// [Messages.channelCid] with [cid]
Future<List<Message>> getThreadMessages(String cid) async =>
Future.wait(await (select(messages).join([
leftOuterJoin(users, messages.userId.equalsExp(_users.id)),
leftOuterJoin(_users, messages.userId.equalsExp(_users.id)),
leftOuterJoin(_pinnedByUsers,
messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)),
])
@@ -89,18 +89,36 @@ class MessageDao extends DatabaseAccessor<MoorChatDatabase>
PaginationParams options,
}) async {
final msgList = await Future.wait(await (select(messages).join([
innerJoin(_users, messages.userId.equalsExp(_users.id)),
innerJoin(
leftOuterJoin(_users, messages.userId.equalsExp(_users.id)),
leftOuterJoin(
_pinnedByUsers, messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)),
])
..where(isNotNull(messages.parentId))
..where(messages.parentId.equals(parentId))
..orderBy([OrderingTerm.asc(messages.createdAt)]))
.map(_messageFromJoinRow)
.get());
if (options?.lessThan != null && msgList.isNotEmpty) {
final lessThanIndex = msgList.indexWhere((m) => m.id == options.lessThan);
msgList.removeRange(lessThanIndex, msgList.length);
if (msgList.isNotEmpty) {
if (options?.lessThan != null) {
final lessThanIndex = msgList.indexWhere(
(m) => m.id == options.lessThan,
);
if (lessThanIndex != -1) {
msgList.removeRange(lessThanIndex, msgList.length);
}
}
if (options?.greaterThanOrEqual != null) {
final greaterThanIndex = msgList.indexWhere(
(m) => m.id == options.greaterThanOrEqual,
);
if (greaterThanIndex != -1) {
msgList.removeRange(0, greaterThanIndex);
}
}
if (options?.limit != null) {
return msgList.take(options.limit).toList();
}
}
return msgList;
}
@@ -123,24 +141,26 @@ class MessageDao extends DatabaseAccessor<MoorChatDatabase>
.map(_messageFromJoinRow)
.get());
if (messagePagination?.lessThan != null) {
final lessThanIndex = msgList.indexWhere(
(m) => m.id == messagePagination.lessThan,
);
if (lessThanIndex != -1) {
msgList.removeRange(lessThanIndex, msgList.length);
if (msgList.isNotEmpty) {
if (messagePagination?.lessThan != null) {
final lessThanIndex = msgList.indexWhere(
(m) => m.id == messagePagination.lessThan,
);
if (lessThanIndex != -1) {
msgList.removeRange(lessThanIndex, msgList.length);
}
}
}
if (messagePagination?.greaterThanOrEqual != null) {
final greaterThanIndex = msgList.indexWhere(
(m) => m.id == messagePagination.greaterThanOrEqual,
);
if (greaterThanIndex != -1) {
msgList.removeRange(0, greaterThanIndex);
if (messagePagination?.greaterThanOrEqual != null) {
final greaterThanIndex = msgList.indexWhere(
(m) => m.id == messagePagination.greaterThanOrEqual,
);
if (greaterThanIndex != -1) {
msgList.removeRange(0, greaterThanIndex);
}
}
if (messagePagination?.limit != null) {
return msgList.take(messagePagination.limit).toList();
}
}
if (messagePagination?.limit != null) {
return msgList.take(messagePagination.limit).toList();
}
return msgList;
}
@@ -36,9 +36,9 @@ class PinnedMessageDao extends DatabaseAccessor<MoorChatDatabase>
(delete(pinnedMessages)..where((tbl) => tbl.channelCid.isIn(cids))).go();
Future<Message> _messageFromJoinRow(TypedResult rows) async {
final userEntity = rows.readTable(users);
final pinnedByEntity = rows.readTable(_pinnedByUsers);
final msgEntity = rows.readTable(pinnedMessages);
final userEntity = rows.readTableOrNull(users);
final pinnedByEntity = rows.readTableOrNull(_pinnedByUsers);
final msgEntity = rows.readTableOrNull(pinnedMessages);
final latestReactions = await _db.reactionDao.getReactions(msgEntity.id);
final ownReactions = await _db.reactionDao.getReactionsByUserId(
msgEntity.id,
@@ -66,7 +66,7 @@ class PinnedMessageDao extends DatabaseAccessor<MoorChatDatabase>
])
..where(pinnedMessages.id.equals(id)))
.map(_messageFromJoinRow)
.getSingle();
.getSingleOrNull();
/// Returns all the messages of a particular thread by matching
/// [PinnedMessages.channelCid] with [cid]
@@ -89,18 +89,36 @@ class PinnedMessageDao extends DatabaseAccessor<MoorChatDatabase>
PaginationParams options,
}) async {
final msgList = await Future.wait(await (select(pinnedMessages).join([
innerJoin(_users, pinnedMessages.userId.equalsExp(_users.id)),
innerJoin(_pinnedByUsers,
leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)),
leftOuterJoin(_pinnedByUsers,
pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id)),
])
..where(isNotNull(pinnedMessages.parentId))
..where(pinnedMessages.parentId.equals(parentId))
..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)]))
.map(_messageFromJoinRow)
.get());
if (options?.lessThan != null) {
final lessThanIndex = msgList.indexWhere((m) => m.id == options.lessThan);
msgList.removeRange(lessThanIndex, msgList.length);
if (msgList.isNotEmpty) {
if (options?.lessThan != null) {
final lessThanIndex = msgList.indexWhere(
(m) => m.id == options.lessThan,
);
if (lessThanIndex != -1) {
msgList.removeRange(lessThanIndex, msgList.length);
}
}
if (options?.greaterThanOrEqual != null) {
final greaterThanIndex = msgList.indexWhere(
(m) => m.id == options.greaterThanOrEqual,
);
if (greaterThanIndex != -1) {
msgList.removeRange(0, greaterThanIndex);
}
}
if (options?.limit != null) {
return msgList.take(options.limit).toList();
}
}
return msgList;
}
@@ -123,24 +141,26 @@ class PinnedMessageDao extends DatabaseAccessor<MoorChatDatabase>
.map(_messageFromJoinRow)
.get());
if (messagePagination?.lessThan != null) {
final lessThanIndex = msgList.indexWhere(
(m) => m.id == messagePagination.lessThan,
);
if (lessThanIndex != -1) {
msgList.removeRange(lessThanIndex, msgList.length);
if (msgList.isNotEmpty) {
if (messagePagination?.lessThan != null) {
final lessThanIndex = msgList.indexWhere(
(m) => m.id == messagePagination.lessThan,
);
if (lessThanIndex != -1) {
msgList.removeRange(lessThanIndex, msgList.length);
}
}
}
if (messagePagination?.greaterThanOrEqual != null) {
final greaterThanIndex = msgList.indexWhere(
(m) => m.id == messagePagination.greaterThanOrEqual,
);
if (greaterThanIndex != -1) {
msgList.removeRange(0, greaterThanIndex);
if (messagePagination?.greaterThanOrEqual != null) {
final greaterThanIndex = msgList.indexWhere(
(m) => m.id == messagePagination.greaterThanOrEqual,
);
if (greaterThanIndex != -1) {
msgList.removeRange(0, greaterThanIndex);
}
}
if (messagePagination?.limit != null) {
return msgList.take(messagePagination.limit).toList();
}
}
if (messagePagination?.limit != null) {
return msgList.take(messagePagination.limit).toList();
}
return msgList;
}
@@ -23,8 +23,8 @@ class ReactionDao extends DatabaseAccessor<MoorChatDatabase>
..where(reactions.messageId.equals(messageId))
..orderBy([OrderingTerm.asc(reactions.createdAt)]))
.map((rows) {
final userEntity = rows.readTable(users);
final reactionEntity = rows.readTable(reactions);
final userEntity = rows.readTableOrNull(users);
final reactionEntity = rows.readTableOrNull(reactions);
return reactionEntity.toReaction(user: userEntity?.toUser());
}).get();
@@ -20,4 +20,10 @@ class UserDao extends DatabaseAccessor<MoorChatDatabase> with _$UserDaoMixin {
mode: InsertMode.insertOrReplace,
),
);
/// Returns the list of all the users stored in db
Future<List<User>> getUsers() =>
(select(users)..orderBy([(u) => OrderingTerm.desc(u.createdAt)]))
.map((it) => it.toUser())
.get();
}
@@ -1,22 +1,15 @@
import 'package:meta/meta.dart';
import 'package:moor/ffi.dart';
import 'package:moor/moor.dart';
import 'package:stream_chat/stream_chat.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/db/shared/shared_db.dart';
import 'package:stream_chat_persistence/src/entity/entity.dart';
part 'moor_chat_database.g.dart';
export 'shared/shared_db.dart';
LazyDatabase _openConnection(
String userId, {
bool logStatements = false,
bool persistOnDisk = true,
}) =>
LazyDatabase(() async => SharedDB.constructDatabase(
userId,
logStatements: logStatements,
persistOnDisk: persistOnDisk,
));
part 'moor_chat_database.g.dart';
/// A chat database implemented using moor
@UseMoor(tables: [
@@ -43,14 +36,9 @@ LazyDatabase _openConnection(
class MoorChatDatabase extends _$MoorChatDatabase {
/// Creates a new moor chat database instance
MoorChatDatabase(
this._userId, {
logStatements = false,
bool persistOnDisk = true,
}) : super(_openConnection(
_userId,
logStatements: logStatements,
persistOnDisk: persistOnDisk,
));
this._userId,
QueryExecutor executor,
) : super(executor);
/// Instantiate a new database instance
MoorChatDatabase.connect(
@@ -58,6 +46,10 @@ class MoorChatDatabase extends _$MoorChatDatabase {
DatabaseConnection connection,
) : super.connect(connection);
/// Custom constructor used only for testing
@visibleForTesting
MoorChatDatabase.testable(this._userId) : super(VmDatabase.memory());
final String _userId;
/// User id to which the database is connected
@@ -1,3 +1,4 @@
// coverage:ignore-file
import 'dart:io';
import 'dart:isolate';
@@ -14,27 +15,49 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
/// A Helper class to construct new instances of [MoorChatDatabase] specifically
/// for native platform applications
class SharedDB {
/// Returns a new instance of [VmDatabase] created using [userId]
/// on a regular isolate.
///
/// Generally used with [ConnectionMode.regular].
static Future<VmDatabase> constructDatabase(
/// Returns a new instance of [MoorChatDatabase].
static MoorChatDatabase constructDatabase(
String userId, {
bool logStatements = false,
bool persistOnDisk = true,
}) async {
ConnectionMode connectionMode = ConnectionMode.regular,
}) {
final dbName = 'db_$userId';
if (persistOnDisk) {
if (Platform.isIOS || Platform.isAndroid) {
final dir = await getApplicationDocumentsDirectory();
final path = join(dir.path, '$dbName.sqlite');
final file = File(path);
return VmDatabase(file, logStatements: logStatements);
}
if (Platform.isMacOS || Platform.isLinux) {
final file = File('$dbName.sqlite');
return VmDatabase(file, logStatements: logStatements);
}
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) {
final dir = await getApplicationDocumentsDirectory();
final path = join(dir.path, '$dbName.sqlite');
final file = File(path);
return VmDatabase(file, logStatements: logStatements);
}
if (Platform.isMacOS || Platform.isLinux) {
final file = File('$dbName.sqlite');
return VmDatabase(file, logStatements: logStatements);
}
return VmDatabase.memory(logStatements: logStatements);
}
@@ -69,27 +92,6 @@ class SharedDB {
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 {
@@ -1,29 +1,17 @@
import 'package:moor/backends.dart';
// coverage:ignore-file
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
import 'package:stream_chat_persistence/stream_chat_persistence.dart';
/// A Helper class to construct new instances of [MoorChatDatabase]
class SharedDB {
/// Returns a new instance of database.
///
/// Generally used with [ConnectionMode.regular].
static Future<DelegatedDatabase> constructDatabase(
/// Returns a new instance of [MoorChatDatabase].
static MoorChatDatabase constructDatabase(
String userId, {
bool logStatements = false,
bool persistOnDisk = true,
ConnectionMode connectionMode = ConnectionMode.regular,
}) {
throw UnsupportedError(
'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');
'No implementation of the constructDatabase api provided',
);
}
}
@@ -1,3 +1,4 @@
// coverage:ignore-file
import 'package:moor/moor_web.dart';
import 'package:stream_chat_persistence/src/stream_chat_persistence_client.dart';
@@ -6,27 +7,14 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
/// A Helper class to construct new instances of [MoorChatDatabase] specifically
/// for Web applications
class SharedDB {
/// Returns a new instance of [WebDatabase] created using [userId].
///
/// Generally used with [ConnectionMode.regular].
static Future<WebDatabase> constructDatabase(
String userId, {
bool logStatements = false,
bool persistOnDisk = true, // ignored on web
}) 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(
/// Returns a new instance of [MoorChatDatabase].
static MoorChatDatabase constructDatabase(
String userId, {
bool logStatements = false,
ConnectionMode connectionMode = ConnectionMode.regular, // Ignored on web
}) {
final dbName = 'db_$userId';
return MoorChatDatabase(dbName, logStatements: logStatements);
final queryExecutor = WebDatabase(dbName, logStatements: logStatements);
return MoorChatDatabase(userId, queryExecutor);
}
}
@@ -1,3 +1,4 @@
// coverage:ignore-file
import 'package:moor/moor.dart';
/// Represents a [ChannelQueries] table in [MoorChatDatabase].
@@ -1,3 +1,4 @@
// coverage:ignore-file
import 'package:moor/moor.dart';
import 'package:stream_chat_persistence/src/converter/map_converter.dart';
@@ -1,3 +1,4 @@
// coverage:ignore-file
import 'package:moor/moor.dart';
import 'package:stream_chat_persistence/src/converter/map_converter.dart';
@@ -1,3 +1,4 @@
// coverage:ignore-file
import 'package:moor/moor.dart';
/// Represents a [Members] table in [MoorChatDatabase].
@@ -1,3 +1,4 @@
// coverage:ignore-file
import 'package:moor/moor.dart';
import 'package:stream_chat_persistence/src/converter/list_converter.dart';
import 'package:stream_chat_persistence/src/converter/map_converter.dart';
@@ -1,3 +1,4 @@
// coverage:ignore-file
import 'package:moor/moor.dart';
import 'package:stream_chat_persistence/src/entity/messages.dart';
@@ -1,3 +1,4 @@
// coverage:ignore-file
import 'package:moor/moor.dart';
import 'package:stream_chat_persistence/src/converter/map_converter.dart';
@@ -1,3 +1,4 @@
// coverage:ignore-file
import 'package:moor/moor.dart';
/// Represents a [Reads] table in [MoorChatDatabase].
@@ -1,3 +1,4 @@
// coverage:ignore-file
import 'package:moor/moor.dart';
import 'package:stream_chat_persistence/src/converter/map_converter.dart';
@@ -46,14 +46,14 @@ extension ChannelModelX on ChannelModel {
id: id,
type: type,
cid: cid,
config: config.toJson(),
config: config?.toJson(),
frozen: frozen,
lastMessageAt: lastMessageAt,
createdAt: createdAt,
updatedAt: updatedAt,
deletedAt: deletedAt,
memberCount: memberCount,
createdById: createdBy.id,
createdById: createdBy?.id,
extraData: extraData,
);
}
@@ -4,7 +4,6 @@ import 'package:mutex/mutex.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/shared/shared_db.dart';
/// Various connection modes on which [StreamChatPersistenceClient] can work
enum ConnectionMode {
@@ -15,6 +14,9 @@ enum ConnectionMode {
background,
}
/// Signature for a function which provides instance of [MoorChatDatabase]
typedef DatabaseProvider = MoorChatDatabase Function(String, ConnectionMode);
final _levelEmojiMapper = {
Level.INFO: '',
Level.WARNING: '⚠️',
@@ -36,26 +38,6 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
_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.
@visibleForTesting
MoorChatDatabase db;
@@ -84,24 +66,25 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
return ret;
}
MoorChatDatabase _defaultDatabaseProvider(
String userId,
ConnectionMode mode,
) =>
SharedDB.constructDatabase(userId, connectionMode: mode);
@override
Future<void> connect(String userId) async {
Future<void> connect(
String userId, {
DatabaseProvider databaseProvider, // Used only for testing
}) async {
if (db != null) {
throw Exception(
'An instance of StreamChatDatabase is already connected.\n'
'disconnect the previous instance before connecting again.',
);
}
switch (_connectionMode) {
case ConnectionMode.regular:
_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;
}
db = databaseProvider?.call(userId, _connectionMode) ??
_defaultDatabaseProvider(userId, _connectionMode);
}
@override
@@ -259,9 +242,9 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
@override
Future<void> updateChannelQueries(
Map<String, dynamic> filter,
List<String> cids,
bool clearQueryCache,
) =>
List<String> cids, {
bool clearQueryCache = false,
}) =>
_readProtected(() async {
_logger.info('updateChannelQueries');
return db.channelQueryDao.updateChannelQueries(
@@ -0,0 +1,798 @@
{
"_meta": {
"description": "This file contains a serialized version of schema entities for moor.",
"version": "0.1.0-dev-preview"
},
"entities": [
{
"id": 0,
"references": [],
"type": "table",
"data": {
"name": "channels",
"was_declared_in_moor": false,
"columns": [
{
"name": "id",
"moor_type": "ColumnType.text",
"nullable": false,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "type",
"moor_type": "ColumnType.text",
"nullable": false,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "cid",
"moor_type": "ColumnType.text",
"nullable": false,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "config",
"moor_type": "ColumnType.text",
"nullable": false,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": [],
"type_converter": {
"dart_expr": "MapConverter<Object>()",
"dart_type_name": "Map<String, Object>"
}
},
{
"name": "frozen",
"moor_type": "ColumnType.boolean",
"nullable": false,
"customConstraints": null,
"default_dart": "Constant(false)",
"default_client_dart": null,
"dsl_features": []
},
{
"name": "last_message_at",
"moor_type": "ColumnType.datetime",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "created_at",
"moor_type": "ColumnType.datetime",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "updated_at",
"moor_type": "ColumnType.datetime",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "deleted_at",
"moor_type": "ColumnType.datetime",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "member_count",
"moor_type": "ColumnType.integer",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "created_by_id",
"moor_type": "ColumnType.text",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "extra_data",
"moor_type": "ColumnType.text",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": [],
"type_converter": {
"dart_expr": "MapConverter<Object>()",
"dart_type_name": "Map<String, Object>"
}
}
],
"is_virtual": false,
"explicit_pk": [
"cid"
]
}
},
{
"id": 1,
"references": [],
"type": "table",
"data": {
"name": "messages",
"was_declared_in_moor": false,
"columns": [
{
"name": "id",
"moor_type": "ColumnType.text",
"nullable": false,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "message_text",
"moor_type": "ColumnType.text",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "attachments",
"moor_type": "ColumnType.text",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": [],
"type_converter": {
"dart_expr": "ListConverter<String>()",
"dart_type_name": "List<String>"
}
},
{
"name": "status",
"moor_type": "ColumnType.integer",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": [],
"type_converter": {
"dart_expr": "MessageSendingStatusConverter()",
"dart_type_name": "MessageSendingStatus"
}
},
{
"name": "type",
"moor_type": "ColumnType.text",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "mentioned_users",
"moor_type": "ColumnType.text",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": [],
"type_converter": {
"dart_expr": "ListConverter<String>()",
"dart_type_name": "List<String>"
}
},
{
"name": "reaction_counts",
"moor_type": "ColumnType.text",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": [],
"type_converter": {
"dart_expr": "MapConverter<int>()",
"dart_type_name": "Map<String, int>"
}
},
{
"name": "reaction_scores",
"moor_type": "ColumnType.text",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": [],
"type_converter": {
"dart_expr": "MapConverter<int>()",
"dart_type_name": "Map<String, int>"
}
},
{
"name": "parent_id",
"moor_type": "ColumnType.text",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "quoted_message_id",
"moor_type": "ColumnType.text",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "reply_count",
"moor_type": "ColumnType.integer",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "show_in_channel",
"moor_type": "ColumnType.boolean",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "shadowed",
"moor_type": "ColumnType.boolean",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "command",
"moor_type": "ColumnType.text",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "created_at",
"moor_type": "ColumnType.datetime",
"nullable": false,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "updated_at",
"moor_type": "ColumnType.datetime",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "deleted_at",
"moor_type": "ColumnType.datetime",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "user_id",
"moor_type": "ColumnType.text",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "channel_cid",
"moor_type": "ColumnType.text",
"nullable": true,
"customConstraints": "NULLABLE REFERENCES channels(cid) ON DELETE CASCADE",
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "extra_data",
"moor_type": "ColumnType.text",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": [],
"type_converter": {
"dart_expr": "MapConverter<Object>()",
"dart_type_name": "Map<String, Object>"
}
}
],
"is_virtual": false,
"explicit_pk": [
"id"
]
}
},
{
"id": 2,
"references": [],
"type": "table",
"data": {
"name": "reactions",
"was_declared_in_moor": false,
"columns": [
{
"name": "user_id",
"moor_type": "ColumnType.text",
"nullable": false,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "message_id",
"moor_type": "ColumnType.text",
"nullable": false,
"customConstraints": "REFERENCES messages(id) ON DELETE CASCADE",
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "type",
"moor_type": "ColumnType.text",
"nullable": false,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "created_at",
"moor_type": "ColumnType.datetime",
"nullable": false,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "score",
"moor_type": "ColumnType.integer",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "extra_data",
"moor_type": "ColumnType.text",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": [],
"type_converter": {
"dart_expr": "MapConverter<Object>()",
"dart_type_name": "Map<String, Object>"
}
}
],
"is_virtual": false,
"explicit_pk": [
"message_id",
"type",
"user_id"
]
}
},
{
"id": 3,
"references": [],
"type": "table",
"data": {
"name": "users",
"was_declared_in_moor": false,
"columns": [
{
"name": "id",
"moor_type": "ColumnType.text",
"nullable": false,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "role",
"moor_type": "ColumnType.text",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "created_at",
"moor_type": "ColumnType.datetime",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "updated_at",
"moor_type": "ColumnType.datetime",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "last_active",
"moor_type": "ColumnType.datetime",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "online",
"moor_type": "ColumnType.boolean",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "banned",
"moor_type": "ColumnType.boolean",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "extra_data",
"moor_type": "ColumnType.text",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": [],
"type_converter": {
"dart_expr": "MapConverter<Object>()",
"dart_type_name": "Map<String, Object>"
}
}
],
"is_virtual": false,
"explicit_pk": [
"id"
]
}
},
{
"id": 4,
"references": [],
"type": "table",
"data": {
"name": "members",
"was_declared_in_moor": false,
"columns": [
{
"name": "user_id",
"moor_type": "ColumnType.text",
"nullable": false,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "channel_cid",
"moor_type": "ColumnType.text",
"nullable": false,
"customConstraints": "REFERENCES channels(cid) ON DELETE CASCADE",
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "role",
"moor_type": "ColumnType.text",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "invite_accepted_at",
"moor_type": "ColumnType.datetime",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "invite_rejected_at",
"moor_type": "ColumnType.datetime",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "invited",
"moor_type": "ColumnType.boolean",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "banned",
"moor_type": "ColumnType.boolean",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "shadow_banned",
"moor_type": "ColumnType.boolean",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "is_moderator",
"moor_type": "ColumnType.boolean",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "created_at",
"moor_type": "ColumnType.datetime",
"nullable": false,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "updated_at",
"moor_type": "ColumnType.datetime",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
}
],
"is_virtual": false,
"explicit_pk": [
"user_id",
"channel_cid"
]
}
},
{
"id": 5,
"references": [],
"type": "table",
"data": {
"name": "reads",
"was_declared_in_moor": false,
"columns": [
{
"name": "last_read",
"moor_type": "ColumnType.datetime",
"nullable": false,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "user_id",
"moor_type": "ColumnType.text",
"nullable": false,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "channel_cid",
"moor_type": "ColumnType.text",
"nullable": false,
"customConstraints": "REFERENCES channels(cid) ON DELETE CASCADE",
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "unread_messages",
"moor_type": "ColumnType.integer",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
}
],
"is_virtual": false,
"explicit_pk": [
"user_id",
"channel_cid"
]
}
},
{
"id": 6,
"references": [],
"type": "table",
"data": {
"name": "channel_queries",
"was_declared_in_moor": false,
"columns": [
{
"name": "query_hash",
"moor_type": "ColumnType.text",
"nullable": false,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "channel_cid",
"moor_type": "ColumnType.text",
"nullable": false,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
}
],
"is_virtual": false,
"explicit_pk": [
"query_hash",
"channel_cid"
]
}
},
{
"id": 7,
"references": [],
"type": "table",
"data": {
"name": "connection_events",
"was_declared_in_moor": false,
"columns": [
{
"name": "id",
"moor_type": "ColumnType.integer",
"nullable": false,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "own_user",
"moor_type": "ColumnType.text",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": [],
"type_converter": {
"dart_expr": "MapConverter<Object>()",
"dart_type_name": "Map<String, Object>"
}
},
{
"name": "total_unread_count",
"moor_type": "ColumnType.integer",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "unread_channels",
"moor_type": "ColumnType.integer",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "last_event_at",
"moor_type": "ColumnType.datetime",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
},
{
"name": "last_sync_at",
"moor_type": "ColumnType.datetime",
"nullable": true,
"customConstraints": null,
"default_dart": null,
"default_client_dart": null,
"dsl_features": []
}
],
"is_virtual": false,
"explicit_pk": [
"id"
]
}
}
]
}
File diff suppressed because it is too large Load Diff
@@ -21,8 +21,13 @@ dependencies:
stream_chat:
path: ../stream_chat
dependency_overrides:
stream_chat:
path: ../stream_chat
dev_dependencies:
build_runner: ^1.11.0
mocktail: ^0.1.0
moor_generator: ^4.2.0
pedantic: ^1.11.0
test: ^1.16.0
@@ -0,0 +1,71 @@
import 'package:mocktail/mocktail.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 {}
@@ -0,0 +1,53 @@
import 'dart:convert';
import 'package:test/test.dart';
import 'package:stream_chat_persistence/src/converter/list_converter.dart';
void main() {
group('mapToDart', () {
final listConverter = ListConverter<String>();
test('should return null if nothing is provided', () {
final res = listConverter.mapToDart(null);
expect(res, isNull);
});
test('should throw type error if the provided json is not a list', () {
final json = {'test_key': 'testData'};
expect(
() => listConverter.mapToDart(jsonEncode(json)),
throwsA(isA<TypeError>()),
);
});
test('should throw type error if the provided json is not a list of String',
() {
final json = [22, 33, 44];
expect(
() => listConverter.mapToDart(jsonEncode(json)),
throwsA(isA<TypeError>()),
);
});
test('should return list of String if json data list is provided', () {
final data = ['data1', 'data2', 'data3'];
final res = listConverter.mapToDart(jsonEncode(data));
expect(res.length, data.length);
});
});
group('mapToSql', () {
final listConverter = ListConverter<String>();
test('should return null if nothing is provided', () {
final res = listConverter.mapToSql(null);
expect(res, isNull);
});
test('should return json string if data list is provided', () {
final data = ['data1', 'data2', 'data3'];
final res = listConverter.mapToSql(data);
expect(res, jsonEncode(data));
});
});
}
@@ -0,0 +1,64 @@
import 'dart:convert';
import 'package:test/test.dart';
import 'package:stream_chat_persistence/src/converter/map_converter.dart';
void main() {
group('mapToDart', () {
final mapConverter = MapConverter<String>();
test('should return null if nothing is provided', () {
final res = mapConverter.mapToDart(null);
expect(res, isNull);
});
test('should throw type error if the provided json is not a map', () {
const json = ['testData1', 'testData2', 'testData3'];
expect(
() => mapConverter.mapToDart(jsonEncode(json)),
throwsA(isA<TypeError>()),
);
});
test(
'should throw type error if the provided json is not a '
'map of String, String',
() {
const json = {'test_key': 22, 'test_key2': 33, 'test_key3': 44};
expect(
() => mapConverter.mapToDart(jsonEncode(json)),
throwsA(isA<TypeError>()),
);
},
);
test('should return map of String, String if json data is provided', () {
const data = {
'test_key': 'testValue',
'test_key2': 'testValue2',
'test_key3': 'testValue3',
};
final res = mapConverter.mapToDart(jsonEncode(data));
expect(res, data);
});
});
group('mapToSql', () {
final mapConverter = MapConverter<String>();
test('should return null if nothing is provided', () {
final res = mapConverter.mapToSql(null);
expect(res, isNull);
});
test('should return json string if data map is provided', () {
const data = {
'test_key': 'testValue',
'test_key2': 'testValue2',
'test_key3': 'testValue3',
};
final res = mapConverter.mapToSql(data);
expect(res, jsonEncode(data));
});
});
}
@@ -0,0 +1,33 @@
import 'package:test/test.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/src/converter/message_sending_status_converter.dart';
void main() {
group('mapToDart', () {
final statusConverter = MessageSendingStatusConverter();
test('should return null if nothing is provided', () {
final res = statusConverter.mapToDart(null);
expect(res, isNull);
});
test('should return expected status if status code is provided', () {
final res = statusConverter.mapToDart(3);
expect(res, MessageSendingStatus.updating);
});
});
group('mapToSql', () {
final statusConverter = MessageSendingStatusConverter();
test('should return null if nothing is provided', () {
final res = statusConverter.mapToSql(null);
expect(res, isNull);
});
test('should return expected code if the status is provided', () {
final res = statusConverter.mapToSql(MessageSendingStatus.updating);
expect(res, 3);
});
});
}
@@ -0,0 +1,130 @@
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/src/dao/channel_dao.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
import 'package:test/test.dart';
void main() {
ChannelDao channelDao;
MoorChatDatabase database;
setUp(() {
database = MoorChatDatabase.testable('testUserId');
channelDao = database.channelDao;
});
test('getChannelByCid', () async {
const id = 'testId';
const cid = 'testCid';
const type = 'testType';
// Should be null initially
final channel = await channelDao.getChannelByCid(cid);
expect(channel, isNull);
// Saving a dummy channel
final dummyChannel = ChannelModel(
id: id,
type: type,
cid: cid,
config: ChannelConfig(),
);
await channelDao.updateChannels([dummyChannel]);
// Should match the dummy channel
final updatedChannel = await channelDao.getChannelByCid(cid);
expect(updatedChannel.id, id);
expect(updatedChannel.cid, cid);
expect(updatedChannel.type, type);
});
test('deleteChannelByCids', () async {
const id = 'testId';
const cid = 'testCid';
const type = 'testType';
// Saving a dummy channel
final dummyChannel = ChannelModel(
id: id,
type: type,
cid: cid,
config: ChannelConfig(),
);
await channelDao.updateChannels([dummyChannel]);
// Should match the dummy channel
final updatedChannel = await channelDao.getChannelByCid(cid);
expect(updatedChannel.id, id);
expect(updatedChannel.cid, cid);
expect(updatedChannel.type, type);
// Deleting the dummyChannel using cid
await channelDao.deleteChannelByCids([cid]);
// Fetched channel Should be null
final channel = await channelDao.getChannelByCid(cid);
expect(channel, isNull);
});
test('cids', () async {
// Should be empty initially
final cids = await channelDao.cids;
expect(cids, []);
const id = 'testId';
const cid = 'testCid';
const type = 'testType';
// Saving a dummy channel
final dummyChannel = ChannelModel(
id: id,
type: type,
cid: cid,
config: ChannelConfig(),
);
await channelDao.updateChannels([dummyChannel]);
// Should return the cid of the dummy channel
final updatedCids = await channelDao.cids;
expect(updatedCids, [cid]);
});
test('updateChannels', () async {
const id = 'testId';
const cid = 'testCid';
const type = 'testType';
// Should be null initially
final channel = await channelDao.getChannelByCid(cid);
expect(channel, isNull);
// Saving a dummy channel
final dummyChannel = ChannelModel(
id: id,
type: type,
cid: cid,
config: ChannelConfig(),
);
await channelDao.updateChannels([dummyChannel]);
// Should match the dummy channel
final updatedChannel = await channelDao.getChannelByCid(cid);
expect(updatedChannel.id, id);
expect(updatedChannel.cid, cid);
expect(updatedChannel.type, type);
// Updating the previously saved channel
const newType = 'newTestType';
final newChannel = dummyChannel.copyWith(type: newType);
await channelDao.updateChannels([newChannel]);
// Should match the new channel
final newUpdatedChannel = await channelDao.getChannelByCid(cid);
expect(newUpdatedChannel.id, id);
expect(newUpdatedChannel.cid, cid);
expect(newUpdatedChannel.type, newType);
});
tearDown(() async {
await database.disconnect();
});
}
@@ -0,0 +1,273 @@
import 'dart:math' as math;
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/src/dao/channel_query_dao.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
import 'package:test/test.dart';
import '../utils/date_matcher.dart';
void main() {
MoorChatDatabase database;
ChannelQueryDao channelQueryDao;
setUp(() {
database = MoorChatDatabase.testable('testUserId');
channelQueryDao = database.channelQueryDao;
});
test('updateChannelQueries', () async {
const filter = {
'members': {
r'$in': ['testUserId'],
},
};
const cids = ['testCid1', 'testCid2', 'testCid3'];
final cachedCids = await channelQueryDao.getCachedChannelCids(filter);
expect(cachedCids, isEmpty);
// Updating channel queries
await channelQueryDao.updateChannelQueries(filter, cids);
final updatedCids = await channelQueryDao.getCachedChannelCids(filter);
expect(updatedCids, cids);
});
test('clear queryCache before updateChannelQueries', () async {
const filter = {
'members': {
r'$in': ['testUserId'],
},
};
const cids = ['testCid1', 'testCid2', 'testCid3'];
final cachedCids = await channelQueryDao.getCachedChannelCids(filter);
expect(cachedCids, isEmpty);
// Updating channel queries
await channelQueryDao.updateChannelQueries(
filter,
cids,
clearQueryCache: true,
);
final updatedCids = await channelQueryDao.getCachedChannelCids(filter);
expect(updatedCids, cids);
});
test('getCachedChannelCids', () async {
const filter = {
'members': {
r'$in': ['testUserId'],
},
};
const cids = ['testCid1', 'testCid2', 'testCid3'];
final cachedCids = await channelQueryDao.getCachedChannelCids(filter);
expect(cachedCids, isEmpty);
// Updating channel queries
await channelQueryDao.updateChannelQueries(filter, cids);
final updatedCids = await channelQueryDao.getCachedChannelCids(filter);
expect(updatedCids, cids);
});
Future<List<ChannelModel>> _insertTestDataForGetChannel(
Map<String, Object> filter, {
int count = 3,
}) async {
final now = DateTime.now();
final userDao = database.userDao;
final channelDao = database.channelDao;
final cids = List.generate(count, (index) => 'testCid$index');
final users = List.generate(count, (index) => User(id: 'testId$index'));
final channels = List.generate(
count,
(index) => ChannelModel(
id: 'testId$index',
type: 'testType$index',
cid: cids[index],
createdBy: users[index],
config: ChannelConfig(),
extraData: {'test_custom_field': math.Random().nextInt(100)},
createdAt: now,
memberCount: math.Random().nextInt(100),
lastMessageAt: now.add(Duration(hours: index)),
),
).reversed.toList(growable: false);
await userDao.updateUsers(users);
await channelDao.updateChannels(channels);
await channelQueryDao.updateChannelQueries(filter, cids);
return channels;
}
group('getChannels', () {
const filter = {
'members': {
r'$in': ['testUserId'],
},
};
test('should return empty list of channels', () async {
final channels = await channelQueryDao.getChannels(filter: filter);
expect(channels, isEmpty);
});
test('should return all the inserted channels', () async {
// Inserting test data for get channels
final insertedChannels = await _insertTestDataForGetChannel(filter);
// Should match with the inserted channels
final updatedChannels = await channelQueryDao.getChannels(filter: filter);
expect(updatedChannels.length, insertedChannels.length);
for (var i = 0; i < updatedChannels.length; i++) {
final updatedChannel = updatedChannels[i];
final insertedChannel = insertedChannels[i];
// Should match all the basic details
expect(updatedChannel.id, insertedChannel.id);
expect(updatedChannel.type, insertedChannel.type);
expect(updatedChannel.cid, insertedChannel.cid);
expect(updatedChannel.memberCount, insertedChannel.memberCount);
// Should match createdAt date
expect(
updatedChannel.createdAt,
isSameDateAs(insertedChannel.createdAt),
);
// Should match lastMessageAt date
expect(
updatedChannel.lastMessageAt,
isSameDateAs(insertedChannel.lastMessageAt),
);
}
});
test(
'should return all the inserted channels along with pagination applied',
() async {
const offset = 5;
const limit = 15;
const pagination = PaginationParams(offset: offset, limit: limit);
// Inserting test data for get channels
final insertedChannels = await _insertTestDataForGetChannel(
filter,
count: 30,
);
// Should match with the inserted channels
final updatedChannels = await channelQueryDao.getChannels(
filter: filter,
paginationParams: pagination,
);
expect(updatedChannels.length, limit);
expect(updatedChannels.first.id, 'testId24');
expect(updatedChannels.first.cid, 'testCid24');
},
);
test('should return sorted channels using member count', () async {
int sortComparator(ChannelModel a, ChannelModel b) =>
b.memberCount.compareTo(a.memberCount);
// Inserting test data for get channels
final insertedChannels = await _insertTestDataForGetChannel(filter);
insertedChannels.sort(sortComparator);
// Should match with the inserted channels
final updatedChannels = await channelQueryDao.getChannels(
filter: filter,
sort: [SortOption('member_count', comparator: sortComparator)],
);
expect(updatedChannels.length, insertedChannels.length);
for (var i = 0; i < updatedChannels.length; i++) {
final updatedChannel = updatedChannels[i];
final insertedChannel = insertedChannels[i];
// Should match all the basic details
expect(updatedChannel.id, insertedChannel.id);
expect(updatedChannel.type, insertedChannel.type);
expect(updatedChannel.cid, insertedChannel.cid);
expect(updatedChannel.memberCount, insertedChannel.memberCount);
// Should match createdAt date
expect(
updatedChannel.createdAt,
isSameDateAs(insertedChannel.createdAt),
);
// Should match lastMessageAt date
expect(
updatedChannel.lastMessageAt,
isSameDateAs(insertedChannel.lastMessageAt),
);
}
});
test('should throw if comparator is not provided in sort list', () {
expect(
() => channelQueryDao.getChannels(
sort: [const SortOption('test_custom_field')],
),
throwsArgumentError,
);
});
test('should return sorted channels using custom field', () async {
int sortComparator(ChannelModel a, ChannelModel b) {
final aData = a.extraData['test_custom_field'] as int;
final bData = b.extraData['test_custom_field'] as int;
return bData.compareTo(aData);
}
// Inserting test data for get channels
final insertedChannels = await _insertTestDataForGetChannel(filter);
insertedChannels.sort(sortComparator);
// Should match with the inserted channels
final updatedChannels = await channelQueryDao.getChannels(
filter: filter,
sort: [SortOption('test_custom_field', comparator: sortComparator)],
);
expect(updatedChannels.length, insertedChannels.length);
for (var i = 0; i < updatedChannels.length; i++) {
final updatedChannel = updatedChannels[i];
final insertedChannel = insertedChannels[i];
// Should match all the basic details
expect(updatedChannel.id, insertedChannel.id);
expect(updatedChannel.type, insertedChannel.type);
expect(updatedChannel.cid, insertedChannel.cid);
expect(updatedChannel.memberCount, insertedChannel.memberCount);
// Should match createdAt date
expect(
updatedChannel.createdAt,
isSameDateAs(insertedChannel.createdAt),
);
// Should match lastMessageAt date
expect(
updatedChannel.lastMessageAt,
isSameDateAs(insertedChannel.lastMessageAt),
);
}
});
});
tearDown(() async {
await database.disconnect();
});
}
@@ -0,0 +1,114 @@
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/src/dao/connection_event_dao.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
import 'package:test/test.dart';
import '../utils/date_matcher.dart';
void main() {
ConnectionEventDao eventDao;
MoorChatDatabase database;
setUp(() {
database = MoorChatDatabase.testable('testUserId');
eventDao = database.connectionEventDao;
});
test('connectionEvent', () async {
// Should be null initially
final event = await eventDao.connectionEvent;
expect(event, isNull);
// Adding a new event
final newEvent = Event(
createdAt: DateTime.now(),
totalUnreadCount: 33,
unreadChannels: 3,
me: OwnUser(id: 'testUserId'),
);
await eventDao.updateConnectionEvent(newEvent);
// Should match the added event
final updatedEvent = await eventDao.connectionEvent;
expect(updatedEvent.me.id, newEvent.me.id);
expect(updatedEvent.totalUnreadCount, newEvent.totalUnreadCount);
expect(updatedEvent.unreadChannels, newEvent.unreadChannels);
});
test('lastSyncAt', () async {
// Should be null initially
final lastSyncAt = await eventDao.lastSyncAt;
expect(lastSyncAt, isNull);
// Adding an event for testing
final event = Event(
createdAt: DateTime.now(),
totalUnreadCount: 33,
unreadChannels: 3,
me: OwnUser(id: 'testUserId'),
);
await eventDao.updateConnectionEvent(event);
// Updating it's last sync
final now = DateTime.now();
await eventDao.updateLastSyncAt(now);
// Should match the updated last sync
final updatedLastSyncAt = await eventDao.lastSyncAt;
expect(updatedLastSyncAt, isSameDateAs(now));
});
test('updateConnectionEvent', () async {
// Adding and event for testing
final event = Event(
createdAt: DateTime.now(),
totalUnreadCount: 33,
unreadChannels: 3,
me: OwnUser(id: 'testUserId'),
);
await eventDao.updateConnectionEvent(event);
// Should match the previously added event
final fetchedEvent = await eventDao.connectionEvent;
expect(fetchedEvent.me.id, event.me.id);
expect(fetchedEvent.totalUnreadCount, event.totalUnreadCount);
expect(fetchedEvent.unreadChannels, event.unreadChannels);
// Updating the added event
final newEvent = event.copyWith(unreadChannels: 4);
await eventDao.updateConnectionEvent(newEvent);
// Should match the updated event
final fetchedNewEvent = await eventDao.connectionEvent;
expect(fetchedNewEvent.me.id, event.me.id);
expect(fetchedNewEvent.totalUnreadCount, event.totalUnreadCount);
expect(fetchedNewEvent.unreadChannels, newEvent.unreadChannels);
});
test('updateLastSyncAt', () async {
// Should be null initially
final lastSyncAt = await eventDao.lastSyncAt;
expect(lastSyncAt, isNull);
// Adding an event just for testing
final event = Event(
createdAt: DateTime.now(),
totalUnreadCount: 33,
unreadChannels: 3,
me: OwnUser(id: 'testUserId'),
);
await eventDao.updateConnectionEvent(event);
// Updating it's last sync
final now = DateTime.now();
await eventDao.updateLastSyncAt(now);
// Should match the last sync
final updatedLastSyncAt = await eventDao.lastSyncAt;
expect(updatedLastSyncAt, isSameDateAs(now));
});
tearDown(() async {
await database.disconnect();
});
}
@@ -0,0 +1,152 @@
import 'dart:math' as math;
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/src/dao/dao.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
import 'package:test/test.dart';
import '../utils/date_matcher.dart';
void main() {
MemberDao memberDao;
MoorChatDatabase database;
setUp(() {
database = MoorChatDatabase.testable('testUserId');
memberDao = database.memberDao;
});
Future<List<Member>> _prepareTestData(String cid) async {
final users = List.generate(3, (index) => User(id: 'testUserId$index'));
final memberList = List.generate(
3,
(index) => Member(
user: users[index],
banned: math.Random().nextBool(),
shadowBanned: math.Random().nextBool(),
createdAt: DateTime.now(),
isModerator: math.Random().nextBool(),
invited: math.Random().nextBool(),
inviteAcceptedAt: DateTime.now(),
role: 'testRole',
updatedAt: DateTime.now(),
),
);
await database.userDao.updateUsers(users);
await memberDao.updateMembers(cid, memberList);
return memberList;
}
test('getMembersByCid', () async {
const cid = 'testCid';
// Should be empty initially
final members = await memberDao.getMembersByCid(cid);
expect(members, isEmpty);
// Preparing test data
final memberList = await _prepareTestData(cid);
// Should match the previous test data
final fetchedMembers = await memberDao.getMembersByCid(cid);
expect(fetchedMembers.length, memberList.length);
for (var i = 0; i < fetchedMembers.length; i++) {
final member = memberList[i];
final fetchedMember = fetchedMembers[i];
expect(fetchedMember.user.id, member.user.id);
expect(fetchedMember.banned, member.banned);
expect(fetchedMember.shadowBanned, member.shadowBanned);
expect(fetchedMember.createdAt, isSameDateAs(member.createdAt));
expect(fetchedMember.isModerator, member.isModerator);
expect(fetchedMember.invited, member.invited);
expect(fetchedMember.role, member.role);
expect(fetchedMember.updatedAt, isSameDateAs(member.updatedAt));
expect(
fetchedMember.inviteAcceptedAt,
isSameDateAs(member.inviteAcceptedAt),
);
}
});
test('updateMembers', () async {
const cid = 'testCid';
// Preparing test data
final memberList = await _prepareTestData(cid);
// Should match the previous test data
final fetchedMembers = await memberDao.getMembersByCid(cid);
expect(fetchedMembers.length, memberList.length);
for (var i = 0; i < fetchedMembers.length; i++) {
final member = memberList[i];
final fetchedMember = fetchedMembers[i];
expect(fetchedMember.user.id, member.user.id);
expect(fetchedMember.banned, member.banned);
expect(fetchedMember.shadowBanned, member.shadowBanned);
expect(fetchedMember.createdAt, isSameDateAs(member.createdAt));
expect(fetchedMember.isModerator, member.isModerator);
expect(fetchedMember.invited, member.invited);
expect(fetchedMember.role, member.role);
expect(fetchedMember.updatedAt, isSameDateAs(member.updatedAt));
expect(
fetchedMember.inviteAcceptedAt,
isSameDateAs(member.inviteAcceptedAt),
);
}
// Modifying one of the member and also adding one new
final copyMember = fetchedMembers.first.copyWith(banned: true);
final newUser = User(id: 'testUserId3');
final newMember = Member(
user: newUser,
banned: math.Random().nextBool(),
shadowBanned: math.Random().nextBool(),
createdAt: DateTime.now(),
isModerator: math.Random().nextBool(),
invited: math.Random().nextBool(),
inviteAcceptedAt: DateTime.now(),
role: 'testRole',
updatedAt: DateTime.now(),
);
await database.userDao.updateUsers([newUser]);
await memberDao.updateMembers(cid, [copyMember, newMember]);
// Fetched member length should be one more than inserted members.
// copyMember `banned` modified field should be true.
// Fetched members should contain the newMember.
final newFetchedMembers = await memberDao.getMembersByCid(cid);
expect(newFetchedMembers.length, fetchedMembers.length + 1);
expect(
newFetchedMembers
.firstWhere((it) => it.user.id == copyMember.user.id)
.banned,
true,
);
expect(
newFetchedMembers
.where((it) => it.user.id == newMember.user.id)
.isNotEmpty,
true,
);
});
test('deleteMemberByCids', () async {
const cid = 'testCid';
// Preparing test data
final members = await _prepareTestData(cid);
final fetchedMembers = await memberDao.getMembersByCid(cid);
expect(members.length, fetchedMembers.length);
// Deleting all the members
await memberDao.deleteMemberByCids([cid]);
// Fetched member list should be empty
final newFetchedMembers = await memberDao.getMembersByCid(cid);
expect(newFetchedMembers, isEmpty);
});
tearDown(() async {
await database.disconnect();
});
}
@@ -0,0 +1,362 @@
import 'dart:math' as math;
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/src/dao/dao.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
import 'package:test/test.dart';
void main() {
MessageDao messageDao;
MoorChatDatabase database;
setUp(() {
database = MoorChatDatabase.testable('testUserId');
messageDao = database.messageDao;
});
Future<List<Message>> _prepareTestData(
String cid, {
bool quoted = false,
bool threads = false,
bool mapAllThreadToFirstMessage = false,
int count = 3,
}) async {
final users = List.generate(count, (index) => User(id: 'testUserId$index'));
final messages = List.generate(
count,
(index) => Message(
id: 'testMessageId$cid$index',
type: 'testType',
user: users[index],
createdAt: DateTime.now(),
shadowed: math.Random().nextBool(),
replyCount: index,
updatedAt: DateTime.now(),
extraData: {'extra_test_field': 'extraTestData'},
text: 'Dummy text #$index',
pinned: math.Random().nextBool(),
pinnedAt: DateTime.now(),
pinnedBy: User(id: 'testUserId$index'),
),
);
final quotedMessages = List.generate(
count,
(index) => Message(
id: 'testQuotedMessageId$cid$index',
type: 'testType',
user: users[index],
createdAt: DateTime.now(),
shadowed: math.Random().nextBool(),
replyCount: index,
updatedAt: DateTime.now(),
extraData: {'extra_test_field': 'extraTestData'},
text: 'Dummy text #$index',
quotedMessageId: messages[index].id,
pinned: math.Random().nextBool(),
pinnedAt: DateTime.now(),
pinnedBy: User(id: 'testUserId$index'),
),
);
final threadMessages = List.generate(
count,
(index) => Message(
id: 'testThreadMessageId$cid$index',
type: 'testType',
user: users[index],
parentId:
mapAllThreadToFirstMessage ? messages[0].id : messages[index].id,
createdAt: DateTime.now(),
shadowed: math.Random().nextBool(),
replyCount: index,
updatedAt: DateTime.now(),
extraData: {'extra_test_field': 'extraTestData'},
text: 'Dummy text #$index',
pinned: math.Random().nextBool(),
pinnedAt: DateTime.now(),
pinnedBy: User(id: 'testUserId$index'),
),
);
final allMessages = [
...messages,
if (quoted) ...quotedMessages,
if (threads) ...threadMessages
];
await database.userDao.updateUsers(users);
await messageDao.updateMessages(cid, allMessages);
return allMessages;
}
test('deleteMessageByIds', () async {
const cid = 'testCid';
// Preparing test data
final insertedMessages = await _prepareTestData(cid);
// Fetched message list should match the test message list length
final messages = await messageDao.getMessagesByCid(cid);
expect(messages.length, insertedMessages.length);
// Deleting 2 messages from DB
await messageDao.deleteMessageByIds(
['testMessageId${cid}0', 'testMessageId${cid}1'],
);
// New fetched messages length should 2 less than the
// previous fetched messages
final newMessages = await messageDao.getMessagesByCid(cid);
expect(newMessages.length, messages.length - 2);
});
group('deleteMessageByCids', () {
const cid1 = 'testCid1';
const cid2 = 'testCid2';
test('should delete all the messages of first channel', () async {
// Preparing test data
final cid1InsertedMessages = await _prepareTestData(cid1);
final cid2InsertedMessages = await _prepareTestData(cid2);
// Fetched message list should match the test message list length
final cid1Messages = await messageDao.getMessagesByCid(cid1);
final cid2Messages = await messageDao.getMessagesByCid(cid2);
expect(cid1Messages.length, cid1InsertedMessages.length);
expect(cid2Messages.length, cid2InsertedMessages.length);
// Deleting all the messages of cid1
await messageDao.deleteMessageByCids([cid1]);
// Fetched messages length of only cid1 should be empty
final cid1FetchedMessages = await messageDao.getMessagesByCid(cid1);
final cid2FetchedMessages = await messageDao.getMessagesByCid(cid2);
expect(cid1FetchedMessages, isEmpty);
expect(cid2FetchedMessages, isNotEmpty);
});
test('should delete all the messages of both channel', () async {
// Preparing test data
final cid1InsertedMessages = await _prepareTestData(cid1);
final cid2InsertedMessages = await _prepareTestData(cid2);
// Fetched message list should match the test message list length
final cid1Messages = await messageDao.getMessagesByCid(cid1);
final cid2Messages = await messageDao.getMessagesByCid(cid2);
expect(cid1Messages.length, cid1InsertedMessages.length);
expect(cid2Messages.length, cid2InsertedMessages.length);
// Deleting all the messages of cid1
await messageDao.deleteMessageByCids([cid1, cid2]);
// Fetched messages length of both cid1 and cid2 should be empty
final cid1FetchedMessages = await messageDao.getMessagesByCid(cid1);
final cid2FetchedMessages = await messageDao.getMessagesByCid(cid2);
expect(cid1FetchedMessages, isEmpty);
expect(cid2FetchedMessages, isEmpty);
});
});
test('getMessageById', () async {
const cid = 'testCid';
const id = 'testMessageId${cid}0';
// Should be null initially
final message = await messageDao.getMessageById(id);
expect(message, isNull);
// Adding test message with the cid and id
final insertedMessages = await _prepareTestData(cid, count: 1);
expect(insertedMessages.first.id, id);
// Fetched message id should match the inserted message id
final fetchedMessage = await messageDao.getMessageById(id);
expect(fetchedMessage.id, insertedMessages.first.id);
});
test('getThreadMessages', () async {
const cid = 'testCid';
// Messages should be empty initially
final messages = await messageDao.getThreadMessages(cid);
expect(messages, isEmpty);
// Preparing test data
final insertedMessages = await _prepareTestData(cid, threads: true);
expect(insertedMessages, isNotEmpty);
// Should fetch all the thread messages of cid
final threadMessages = await messageDao.getThreadMessages(cid);
expect(threadMessages, isNotEmpty);
for (final message in threadMessages) {
expect(message.parentId, isNotNull);
}
});
test('getThreadMessagesByParentId', () async {
const cid = 'testCid';
const parentId = 'testMessageId${cid}0';
// Messages should be empty initially
final messages = await messageDao.getThreadMessagesByParentId(parentId);
expect(messages, isEmpty);
// Preparing test data
final insertedMessages = await _prepareTestData(cid, threads: true);
expect(insertedMessages, isNotEmpty);
// Should fetch all the thread messages of parentId
final threadMessages =
await messageDao.getThreadMessagesByParentId(parentId);
expect(threadMessages.length, 1);
expect(threadMessages.first.parentId, parentId);
});
test('getThreadMessagesByParentId along with pagination', () async {
const cid = 'testCid';
const parentId = 'testMessageId${cid}0';
const options = PaginationParams(
limit: 15,
lessThan: 'testThreadMessageId${cid}25',
greaterThanOrEqual: 'testThreadMessageId${cid}5',
);
// Messages should be empty initially
final messages = await messageDao.getThreadMessagesByParentId(
parentId,
options: options,
);
expect(messages, isEmpty);
// Preparing test data
final insertedMessages = await _prepareTestData(
cid,
threads: true,
mapAllThreadToFirstMessage: true,
count: 30,
);
expect(insertedMessages, isNotEmpty);
// Should fetch all the thread messages of parentId and apply the pagination
final threadMessages = await messageDao.getThreadMessagesByParentId(
parentId,
options: options,
);
expect(threadMessages.length, 15);
expect(threadMessages.first.parentId, parentId);
});
test('getMessagesByCid', () async {
const cid = 'testCid';
// Should be empty initially
final messages = await messageDao.getMessagesByCid(cid);
expect(messages, isEmpty);
// Preparing test data
final insertedMessages = await _prepareTestData(cid);
expect(insertedMessages, isNotEmpty);
// Fetched message should match the inserted messages
final fetchedMessages = await messageDao.getMessagesByCid(cid);
expect(fetchedMessages.length, insertedMessages.length);
for (var i = 0; i < fetchedMessages.length; i++) {
final fetchedMessage = fetchedMessages[i];
final insertedMessage = insertedMessages[i];
expect(fetchedMessage.id, insertedMessage.id);
}
});
test('getMessagesByCid along with quotedMessage', () async {
const cid = 'testCid';
// Should be empty initially
final messages = await messageDao.getMessagesByCid(cid);
expect(messages, isEmpty);
// Preparing test data
final insertedMessages = await _prepareTestData(cid, quoted: true);
expect(insertedMessages, isNotEmpty);
// Fetched message should match the inserted messages
final fetchedMessages = await messageDao.getMessagesByCid(cid);
expect(fetchedMessages.length, insertedMessages.length);
final quoted = fetchedMessages.where((it) => it.quotedMessage != null);
expect(quoted.length, insertedMessages.length / 2);
});
test('getMessagesByCid along with pagination', () async {
const cid = 'testCid';
const limit = 15;
const lessThan = 'testMessageId${cid}25';
const greaterThanOrEqual = 'testMessageId${cid}5';
const pagination = PaginationParams(
limit: limit,
lessThan: lessThan,
greaterThanOrEqual: greaterThanOrEqual,
);
// Should be empty initially
final messages = await messageDao.getMessagesByCid(
cid,
messagePagination: pagination,
);
expect(messages, isEmpty);
// Preparing test data
final insertedMessages = await _prepareTestData(cid, count: 30);
expect(insertedMessages, isNotEmpty);
// Fetched message should match the inserted messages
final fetchedMessages = await messageDao.getMessagesByCid(
cid,
messagePagination: pagination,
);
expect(fetchedMessages.length, limit);
expect(fetchedMessages.first.id, greaterThanOrEqual);
expect(fetchedMessages.last.id != lessThan, true);
});
test('updateMessages', () async {
const cid = 'testCid';
// Preparing test data
final insertedMessages = await _prepareTestData(cid);
expect(insertedMessages, isNotEmpty);
// Modifying one of the message and also adding one new
final copyMessage = insertedMessages.first.copyWith(showInChannel: false);
final newMessage = Message(
id: 'testMessageId${cid}4',
type: 'testType',
user: User(id: 'testUserId4'),
createdAt: DateTime.now(),
shadowed: math.Random().nextBool(),
showInChannel: math.Random().nextBool(),
replyCount: 4,
updatedAt: DateTime.now(),
extraData: {'extra_test_field': 'extraTestData'},
text: 'Dummy text #4',
pinned: math.Random().nextBool(),
pinnedAt: DateTime.now(),
pinnedBy: User(id: 'testUserId4'),
);
await messageDao.updateMessages(cid, [copyMessage, newMessage]);
// Fetched messages length should be one more than inserted message.
// copyMessage `showInChannel` modified field should be false.
// Fetched messages should contain the newMessage.
final fetchedMessages = await messageDao.getMessagesByCid(cid);
expect(fetchedMessages.length, insertedMessages.length + 1);
expect(
fetchedMessages.firstWhere((it) => it.id == copyMessage.id).showInChannel,
false,
);
expect(
fetchedMessages.map((it) => it.id).contains(newMessage.id),
true,
);
});
tearDown(() async {
await database.disconnect();
});
}
@@ -0,0 +1,363 @@
import 'dart:math' as math;
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/src/dao/dao.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
import 'package:test/test.dart';
void main() {
PinnedMessageDao pinnedMessageDao;
MoorChatDatabase database;
setUp(() {
database = MoorChatDatabase.testable('testUserId');
pinnedMessageDao = database.pinnedMessageDao;
});
Future<List<Message>> _prepareTestData(
String cid, {
bool quoted = false,
bool threads = false,
bool mapAllThreadToFirstMessage = false,
int count = 3,
}) async {
final users = List.generate(count, (index) => User(id: 'testUserId$index'));
final messages = List.generate(
count,
(index) => Message(
id: 'testMessageId$cid$index',
type: 'testType',
user: users[index],
createdAt: DateTime.now(),
shadowed: math.Random().nextBool(),
replyCount: index,
updatedAt: DateTime.now(),
extraData: {'extra_test_field': 'extraTestData'},
text: 'Dummy text #$index',
pinned: math.Random().nextBool(),
pinnedAt: DateTime.now(),
pinnedBy: User(id: 'testUserId$index'),
),
);
final quotedMessages = List.generate(
count,
(index) => Message(
id: 'testQuotedMessageId$cid$index',
type: 'testType',
user: users[index],
createdAt: DateTime.now(),
shadowed: math.Random().nextBool(),
replyCount: index,
updatedAt: DateTime.now(),
extraData: {'extra_test_field': 'extraTestData'},
text: 'Dummy text #$index',
quotedMessageId: messages[index].id,
pinned: math.Random().nextBool(),
pinnedAt: DateTime.now(),
pinnedBy: User(id: 'testUserId$index'),
),
);
final threadMessages = List.generate(
count,
(index) => Message(
id: 'testThreadMessageId$cid$index',
type: 'testType',
user: users[index],
parentId:
mapAllThreadToFirstMessage ? messages[0].id : messages[index].id,
createdAt: DateTime.now(),
shadowed: math.Random().nextBool(),
replyCount: index,
updatedAt: DateTime.now(),
extraData: {'extra_test_field': 'extraTestData'},
text: 'Dummy text #$index',
pinned: math.Random().nextBool(),
pinnedAt: DateTime.now(),
pinnedBy: User(id: 'testUserId$index'),
),
);
final allMessages = [
...messages,
if (quoted) ...quotedMessages,
if (threads) ...threadMessages
];
await database.userDao.updateUsers(users);
await pinnedMessageDao.updateMessages(cid, allMessages);
return allMessages;
}
test('deleteMessageByIds', () async {
const cid = 'testCid';
// Preparing test data
final insertedMessages = await _prepareTestData(cid);
// Fetched message list should match the test message list length
final messages = await pinnedMessageDao.getMessagesByCid(cid);
expect(messages.length, insertedMessages.length);
// Deleting 2 messages from DB
await pinnedMessageDao.deleteMessageByIds(
['testMessageId${cid}0', 'testMessageId${cid}1'],
);
// New fetched messages length should 2 less than the
// previous fetched messages
final newMessages = await pinnedMessageDao.getMessagesByCid(cid);
expect(newMessages.length, messages.length - 2);
});
group('deleteMessageByCids', () {
const cid1 = 'testCid1';
const cid2 = 'testCid2';
test('should delete all the messages of first channel', () async {
// Preparing test data
final cid1InsertedMessages = await _prepareTestData(cid1);
final cid2InsertedMessages = await _prepareTestData(cid2);
// Fetched message list should match the test message list length
final cid1Messages = await pinnedMessageDao.getMessagesByCid(cid1);
final cid2Messages = await pinnedMessageDao.getMessagesByCid(cid2);
expect(cid1Messages.length, cid1InsertedMessages.length);
expect(cid2Messages.length, cid2InsertedMessages.length);
// Deleting all the messages of cid1
await pinnedMessageDao.deleteMessageByCids([cid1]);
// Fetched messages length of only cid1 should be empty
final cid1FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid1);
final cid2FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid2);
expect(cid1FetchedMessages, isEmpty);
expect(cid2FetchedMessages, isNotEmpty);
});
test('should delete all the messages of both channel', () async {
// Preparing test data
final cid1InsertedMessages = await _prepareTestData(cid1);
final cid2InsertedMessages = await _prepareTestData(cid2);
// Fetched message list should match the test message list length
final cid1Messages = await pinnedMessageDao.getMessagesByCid(cid1);
final cid2Messages = await pinnedMessageDao.getMessagesByCid(cid2);
expect(cid1Messages.length, cid1InsertedMessages.length);
expect(cid2Messages.length, cid2InsertedMessages.length);
// Deleting all the messages of cid1
await pinnedMessageDao.deleteMessageByCids([cid1, cid2]);
// Fetched messages length of both cid1 and cid2 should be empty
final cid1FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid1);
final cid2FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid2);
expect(cid1FetchedMessages, isEmpty);
expect(cid2FetchedMessages, isEmpty);
});
});
test('getMessageById', () async {
const cid = 'testCid';
const id = 'testMessageId${cid}0';
// Should be null initially
final message = await pinnedMessageDao.getMessageById(id);
expect(message, isNull);
// Adding test message with the cid and id
final insertedMessages = await _prepareTestData(cid, count: 1);
expect(insertedMessages.first.id, id);
// Fetched message id should match the inserted message id
final fetchedMessage = await pinnedMessageDao.getMessageById(id);
expect(fetchedMessage.id, insertedMessages.first.id);
});
test('getThreadMessages', () async {
const cid = 'testCid';
// Messages should be empty initially
final messages = await pinnedMessageDao.getThreadMessages(cid);
expect(messages, isEmpty);
// Preparing test data
final insertedMessages = await _prepareTestData(cid, threads: true);
expect(insertedMessages, isNotEmpty);
// Should fetch all the thread messages of cid
final threadMessages = await pinnedMessageDao.getThreadMessages(cid);
expect(threadMessages, isNotEmpty);
for (final message in threadMessages) {
expect(message.parentId, isNotNull);
}
});
test('getThreadMessagesByParentId', () async {
const cid = 'testCid';
const parentId = 'testMessageId${cid}0';
// Messages should be empty initially
final messages =
await pinnedMessageDao.getThreadMessagesByParentId(parentId);
expect(messages, isEmpty);
// Preparing test data
final insertedMessages = await _prepareTestData(cid, threads: true);
expect(insertedMessages, isNotEmpty);
// Should fetch all the thread messages of parentId
final threadMessages =
await pinnedMessageDao.getThreadMessagesByParentId(parentId);
expect(threadMessages.length, 1);
expect(threadMessages.first.parentId, parentId);
});
test('getThreadMessagesByParentId along with pagination', () async {
const cid = 'testCid';
const parentId = 'testMessageId${cid}0';
const options = PaginationParams(
limit: 15,
lessThan: 'testThreadMessageId${cid}25',
greaterThanOrEqual: 'testThreadMessageId${cid}5',
);
// Messages should be empty initially
final messages = await pinnedMessageDao.getThreadMessagesByParentId(
parentId,
options: options,
);
expect(messages, isEmpty);
// Preparing test data
final insertedMessages = await _prepareTestData(
cid,
threads: true,
mapAllThreadToFirstMessage: true,
count: 30,
);
expect(insertedMessages, isNotEmpty);
// Should fetch all the thread messages of parentId and apply the pagination
final threadMessages = await pinnedMessageDao.getThreadMessagesByParentId(
parentId,
options: options,
);
expect(threadMessages.length, 15);
expect(threadMessages.first.parentId, parentId);
});
test('getMessagesByCid', () async {
const cid = 'testCid';
// Should be empty initially
final messages = await pinnedMessageDao.getMessagesByCid(cid);
expect(messages, isEmpty);
// Preparing test data
final insertedMessages = await _prepareTestData(cid);
expect(insertedMessages, isNotEmpty);
// Fetched message should match the inserted messages
final fetchedMessages = await pinnedMessageDao.getMessagesByCid(cid);
expect(fetchedMessages.length, insertedMessages.length);
for (var i = 0; i < fetchedMessages.length; i++) {
final fetchedMessage = fetchedMessages[i];
final insertedMessage = insertedMessages[i];
expect(fetchedMessage.id, insertedMessage.id);
}
});
test('getMessagesByCid along with quotedMessage', () async {
const cid = 'testCid';
// Should be empty initially
final messages = await pinnedMessageDao.getMessagesByCid(cid);
expect(messages, isEmpty);
// Preparing test data
final insertedMessages = await _prepareTestData(cid, quoted: true);
expect(insertedMessages, isNotEmpty);
// Fetched message should match the inserted messages
final fetchedMessages = await pinnedMessageDao.getMessagesByCid(cid);
expect(fetchedMessages.length, insertedMessages.length);
final quoted = fetchedMessages.where((it) => it.quotedMessage != null);
expect(quoted.length, insertedMessages.length / 2);
});
test('getMessagesByCid along with pagination', () async {
const cid = 'testCid';
const limit = 15;
const lessThan = 'testMessageId${cid}25';
const greaterThanOrEqual = 'testMessageId${cid}5';
const pagination = PaginationParams(
limit: limit,
lessThan: lessThan,
greaterThanOrEqual: greaterThanOrEqual,
);
// Should be empty initially
final messages = await pinnedMessageDao.getMessagesByCid(
cid,
messagePagination: pagination,
);
expect(messages, isEmpty);
// Preparing test data
final insertedMessages = await _prepareTestData(cid, count: 30);
expect(insertedMessages, isNotEmpty);
// Fetched message should match the inserted messages
final fetchedMessages = await pinnedMessageDao.getMessagesByCid(
cid,
messagePagination: pagination,
);
expect(fetchedMessages.length, limit);
expect(fetchedMessages.first.id, greaterThanOrEqual);
expect(fetchedMessages.last.id != lessThan, true);
});
test('updateMessages', () async {
const cid = 'testCid';
// Preparing test data
final insertedMessages = await _prepareTestData(cid);
expect(insertedMessages, isNotEmpty);
// Modifying one of the message and also adding one new
final copyMessage = insertedMessages.first.copyWith(showInChannel: false);
final newMessage = Message(
id: 'testMessageId${cid}4',
type: 'testType',
user: User(id: 'testUserId4'),
createdAt: DateTime.now(),
shadowed: math.Random().nextBool(),
showInChannel: math.Random().nextBool(),
replyCount: 4,
updatedAt: DateTime.now(),
extraData: {'extra_test_field': 'extraTestData'},
text: 'Dummy text #4',
pinned: math.Random().nextBool(),
pinnedAt: DateTime.now(),
pinnedBy: User(id: 'testUserId4'),
);
await pinnedMessageDao.updateMessages(cid, [copyMessage, newMessage]);
// Fetched messages length should be one more than inserted message.
// copyMessage `showInChannel` modified field should be false.
// Fetched messages should contain the newMessage.
final fetchedMessages = await pinnedMessageDao.getMessagesByCid(cid);
expect(fetchedMessages.length, insertedMessages.length + 1);
expect(
fetchedMessages.firstWhere((it) => it.id == copyMessage.id).showInChannel,
false,
);
expect(
fetchedMessages.map((it) => it.id).contains(newMessage.id),
true,
);
});
tearDown(() async {
await database.disconnect();
});
}
@@ -0,0 +1,188 @@
import 'dart:math' as math;
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/src/dao/reaction_dao.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
import 'package:test/test.dart';
void main() {
ReactionDao reactionDao;
MoorChatDatabase database;
setUp(() {
database = MoorChatDatabase.testable('testUserId');
reactionDao = database.reactionDao;
});
Future<List<Reaction>> _prepareReactionData(
String messageId, {
String userId,
int count = 3,
}) async {
final users = List.generate(count, (index) => User(id: 'testUserId$index'));
final message = Message(
id: messageId,
type: 'testType',
user: users.first,
createdAt: DateTime.now(),
shadowed: math.Random().nextBool(),
showInChannel: math.Random().nextBool(),
replyCount: 3,
updatedAt: DateTime.now(),
extraData: {'extra_test_field': 'extraTestData'},
text: 'Dummy text',
pinned: math.Random().nextBool(),
pinnedAt: DateTime.now(),
pinnedBy: users.first,
);
final reactions = List.generate(
count,
(index) => Reaction(
type: 'testType$index',
createdAt: DateTime.now(),
userId: userId ?? users[index].id,
messageId: message.id,
score: count + 3,
extraData: {'extra_test_field': 'extraTestData'},
),
);
await database.userDao.updateUsers(users);
await database.messageDao.updateMessages('testCid', [message]);
await reactionDao.updateReactions(reactions);
return reactions;
}
test('getReactions', () async {
const messageId = 'testMessageId';
// Should be empty initially
final reactions = await reactionDao.getReactions(messageId);
expect(reactions, isEmpty);
// Adding sample reactions
final insertedReactions = await _prepareReactionData(messageId);
expect(insertedReactions, isNotEmpty);
// Fetched reaction length should match inserted reactions length.
// Every reaction messageId should match the provided messageId.
final fetchedReactions = await reactionDao.getReactions(messageId);
expect(fetchedReactions.length, insertedReactions.length);
expect(fetchedReactions.every((it) => it.messageId == messageId), true);
});
test('getReactionsByUserId', () async {
const messageId = 'testMessageId';
const userId = 'testUserId';
// Should be empty initially
final reactions = await reactionDao.getReactionsByUserId(messageId, userId);
expect(reactions, isEmpty);
// Adding sample reactions
final insertedReactions =
await _prepareReactionData(messageId, userId: userId);
expect(insertedReactions, isNotEmpty);
// Fetched reaction length should match inserted reactions length.
// Every reaction messageId should match the provided messageId.
// Every reaction userId should match the provided userId.
final fetchedReactions =
await reactionDao.getReactionsByUserId(messageId, userId);
expect(fetchedReactions.length, insertedReactions.length);
expect(fetchedReactions.every((it) => it.messageId == messageId), true);
expect(fetchedReactions.every((it) => it.userId == userId), true);
});
test('updateReactions', () async {
const messageId = 'testMessageId';
// Preparing test data
final reactions = await _prepareReactionData(messageId);
// Modifying one of the reaction and also adding one new
final copyReaction = reactions.first.copyWith(score: 33);
final newReaction = Reaction(
type: 'testType3',
createdAt: DateTime.now(),
userId: 'testUserId3',
messageId: messageId,
score: 30,
extraData: {'extra_test_field': 'extraTestData'},
);
await reactionDao.updateReactions([copyReaction, newReaction]);
// Fetched reaction length should be one more than inserted reactions.
// copyReaction `score` modified field should be 33.
// Fetched reactions should contain the newReaction.
final fetchedReactions = await reactionDao.getReactions(messageId);
expect(fetchedReactions.length, reactions.length + 1);
expect(
fetchedReactions
.firstWhere((it) =>
it.userId == copyReaction.userId && it.type == copyReaction.type)
.score,
33,
);
expect(
fetchedReactions
.where((it) =>
it.userId == newReaction.userId && it.type == newReaction.type)
.isNotEmpty,
true,
);
});
group('deleteReactionsByMessageIds', () {
const messageId1 = 'testMessageId1';
const messageId2 = 'testMessageId2';
test('should delete all the reactions of first message', () async {
// Preparing test data
final insertedReactions1 = await _prepareReactionData(messageId1);
final insertedReactions2 = await _prepareReactionData(messageId2);
// Fetched reaction list length should match
// the inserted reactions list length
final reactions1 = await reactionDao.getReactions(messageId1);
final reactions2 = await reactionDao.getReactions(messageId2);
expect(reactions1.length, insertedReactions1.length);
expect(reactions2.length, insertedReactions2.length);
// Deleting all the reactions of messageId1
await reactionDao.deleteReactionsByMessageIds([messageId1]);
// Fetched reactions length of only messageId1 should be empty
final fetchedReactions1 = await reactionDao.getReactions(messageId1);
final fetchedReactions2 = await reactionDao.getReactions(messageId2);
expect(fetchedReactions1, isEmpty);
expect(fetchedReactions2, isNotEmpty);
});
test('should delete all the messages of both message', () async {
// Preparing test data
final insertedReactions1 = await _prepareReactionData(messageId1);
final insertedReactions2 = await _prepareReactionData(messageId2);
// Fetched reaction list length should match
// the inserted reactions list length
final reactions1 = await reactionDao.getReactions(messageId1);
final reactions2 = await reactionDao.getReactions(messageId2);
expect(reactions1.length, insertedReactions1.length);
expect(reactions2.length, insertedReactions2.length);
// Deleting all the reactions of messageId1 and messageId2
await reactionDao.deleteReactionsByMessageIds([messageId1, messageId2]);
// Fetched reactions length of both messages should be empty
final fetchedReactions1 = await reactionDao.getReactions(messageId1);
final fetchedReactions2 = await reactionDao.getReactions(messageId2);
expect(fetchedReactions1, isEmpty);
expect(fetchedReactions2, isEmpty);
});
});
tearDown(() async {
await database.disconnect();
});
}
@@ -0,0 +1,97 @@
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/src/dao/dao.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
import 'package:test/test.dart';
import '../utils/date_matcher.dart';
void main() {
ReadDao readDao;
MoorChatDatabase database;
setUp(() {
database = MoorChatDatabase.testable('testUserId');
readDao = database.readDao;
});
Future<List<Read>> _prepareReadData(String cid, {int count = 3}) async {
final users = List.generate(count, (index) => User(id: 'testUserId$index'));
final reads = List.generate(
count,
(index) => Read(
lastRead: DateTime.now(),
user: users[index],
unreadMessages: index + 10,
),
);
await database.userDao.updateUsers(users);
await readDao.updateReads(cid, reads);
return reads;
}
test('getReadsByCid', () async {
const cid = 'testCid';
// Should be empty initially
final reads = await readDao.getReadsByCid(cid);
expect(reads, isEmpty);
// Preparing test data
final insertedReads = await _prepareReadData(cid);
expect(insertedReads, isNotEmpty);
// Fetched reads should be equal to inserted reads
final fetchedReads = await readDao.getReadsByCid(cid);
expect(fetchedReads.length, insertedReads.length);
for (var i = 0; i < fetchedReads.length; i++) {
final fetchedRead = fetchedReads[i];
final insertedRead = insertedReads[i];
expect(fetchedRead.user.id, insertedRead.user.id);
expect(fetchedRead.lastRead, isSameDateAs(insertedRead.lastRead));
expect(fetchedRead.unreadMessages, insertedRead.unreadMessages);
}
});
test('updateReads', () async {
const cid = 'testCid';
// Preparing test data
final insertedReads = await _prepareReadData(cid);
// Modifying one of the read and also adding one new
final copyRead = insertedReads.first.copyWith(unreadMessages: 33);
final newUser = User(id: 'testUserId3');
final newRead = Read(
lastRead: DateTime.now(),
user: newUser,
unreadMessages: 30,
);
await database.userDao.updateUsers([newUser]);
await readDao.updateReads(cid, [copyRead, newRead]);
// Fetched reads length should be one more than inserted reads.
// copyRead `unreadMessages` modified field should be 33.
// Fetched reads should contain the newRead.
final fetchedReads = await readDao.getReadsByCid(cid);
expect(fetchedReads.length, insertedReads.length + 1);
expect(
fetchedReads
.firstWhere((it) => it.user.id == copyRead.user.id)
.unreadMessages,
33,
);
expect(
fetchedReads
.where((it) =>
it.user.id == newRead.user.id &&
it.unreadMessages == newRead.unreadMessages)
.isNotEmpty,
true,
);
});
tearDown(() async {
await database.disconnect();
});
}
@@ -0,0 +1,77 @@
import 'dart:math' as math;
import 'package:stream_chat_persistence/src/dao/dao.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
import 'package:test/test.dart';
import 'package:stream_chat/stream_chat.dart';
void main() {
UserDao userDao;
MoorChatDatabase database;
setUp(() {
database = MoorChatDatabase.testable('testUserId');
userDao = database.userDao;
});
Future<List<User>> _prepareUserData({int count = 3}) async {
final users = List.generate(
count,
(index) => User(
id: 'testUserId$index',
role: 'testRole',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
lastActive: DateTime.now(),
online: math.Random().nextBool(),
banned: math.Random().nextBool(),
),
);
await userDao.updateUsers(users);
return users;
}
test('updateUsers', () async {
// Preparing test data
final insertedUsers = await _prepareUserData();
// Modifying one of the user and also adding one new
final copyUser = insertedUsers.first.copyWith(online: false);
final newUser = User(
id: 'testUserId3',
role: 'testRole',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
lastActive: DateTime.now(),
online: math.Random().nextBool(),
banned: math.Random().nextBool(),
);
await userDao.updateUsers([copyUser, newUser]);
// Fetched users length should be one more than inserted users.
// copyUser `online` modified field should be `false`.
// Fetched users should contain the newUser.
final fetchedUsers = await userDao.getUsers();
expect(fetchedUsers.length, insertedUsers.length + 1);
expect(fetchedUsers.firstWhere((it) => it.id == copyUser.id).online, false);
expect(fetchedUsers.contains(newUser), true);
});
test('getUsers', () async {
// Should be empty initially
final users = await userDao.getUsers();
expect(users, isEmpty);
// Preparing test data
final insertedUsers = await _prepareUserData();
expect(insertedUsers, isNotEmpty);
// Fetched user list should match inserted user list length
final fetchedUsers = await userDao.getUsers();
expect(fetchedUsers.length, insertedUsers.length);
});
tearDown(() async {
await database.disconnect();
});
}
@@ -0,0 +1,43 @@
import 'package:test/test.dart';
import 'package:moor/ffi.dart';
import 'package:moor/isolate.dart';
import 'package:moor/moor.dart' hide isNotNull;
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
DatabaseConnection _backgroundConnection() =>
DatabaseConnection.fromExecutor(VmDatabase.memory());
void main() {
test(
'default constructor should create a new instance of MoorChatDatabase',
() async {
const userId = 'testUserId';
final executor = VmDatabase.memory();
final database = MoorChatDatabase(userId, executor);
expect(database, isNotNull);
expect(database.userId, userId);
addTearDown(() async {
await database.disconnect();
});
},
);
test(
'connect constructor should create a new instance of MoorChatDatabase',
() async {
const userId = 'testUserId';
final isolate = await MoorIsolate.spawn(_backgroundConnection);
final connection = DatabaseConnection.delayed(isolate.connect());
final database = MoorChatDatabase.connect(userId, connection);
expect(database, isNotNull);
expect(database.userId, userId);
addTearDown(() async {
await database.disconnect();
await isolate.shutdownAll();
});
},
);
}
@@ -0,0 +1,111 @@
import 'dart:math' as math;
import 'package:test/test.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/mapper/channel_mapper.dart';
import '../utils/date_matcher.dart';
void main() {
group('ChannelEntity', () {
final user = User(id: 'testUserId');
final entity = ChannelEntity(
id: 'testId',
type: 'testType',
cid: 'testCid',
config: {'max_message_length': 33},
frozen: math.Random().nextBool(),
lastMessageAt: DateTime.now(),
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
deletedAt: DateTime.now(),
memberCount: 33,
createdById: user.id,
extraData: {'test_extra_data': 'testData'},
);
test('toChannelModel should map entity into ChannelModel', () {
final channelModel = entity.toChannelModel(createdBy: user);
expect(channelModel, isA<ChannelModel>());
expect(channelModel.id, entity.id);
expect(channelModel.config.toJson()['max_message_length'], 33);
expect(channelModel.frozen, entity.frozen);
expect(channelModel.createdAt, isSameDateAs(entity.createdAt));
expect(channelModel.updatedAt, isSameDateAs(entity.updatedAt));
expect(channelModel.memberCount, entity.memberCount);
expect(channelModel.cid, entity.cid);
expect(channelModel.lastMessageAt, isSameDateAs(entity.lastMessageAt));
expect(channelModel.deletedAt, isSameDateAs(entity.deletedAt));
expect(channelModel.extraData, entity.extraData);
expect(channelModel.createdBy.id, entity.createdById);
});
test('toChannelState should map entity into ChannelState ', () {
final members = List.generate(3, (index) => Member());
final reads = List.generate(3, (index) => Read());
final messages = List.generate(3, (index) => Message());
final channelState = entity.toChannelState(
createdBy: user,
members: members,
reads: reads,
messages: messages,
pinnedMessages: messages,
);
expect(channelState, isA<ChannelState>());
expect(channelState.members.length, members.length);
expect(channelState.read.length, reads.length);
expect(channelState.messages.length, messages.length);
expect(channelState.pinnedMessages.length, messages.length);
final channelModel = channelState.channel;
expect(channelModel.id, entity.id);
expect(channelModel.config.toJson()['max_message_length'], 33);
expect(channelModel.frozen, entity.frozen);
expect(channelModel.createdAt, isSameDateAs(entity.createdAt));
expect(channelModel.updatedAt, isSameDateAs(entity.updatedAt));
expect(channelModel.memberCount, entity.memberCount);
expect(channelModel.cid, entity.cid);
expect(channelModel.lastMessageAt, isSameDateAs(entity.lastMessageAt));
expect(channelModel.deletedAt, isSameDateAs(entity.deletedAt));
expect(channelModel.extraData, entity.extraData);
expect(channelModel.createdBy.id, entity.createdById);
});
});
test('toEntity should map model into ChannelEntity', () {
final createdBy = User(id: 'testUserId');
final model = ChannelModel(
id: 'testId',
type: 'testType',
cid: 'testCid',
config: ChannelConfig(maxMessageLength: 33),
frozen: math.Random().nextBool(),
lastMessageAt: DateTime.now(),
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
deletedAt: DateTime.now(),
memberCount: 33,
createdBy: createdBy,
extraData: {'test_extra_data': 'testData'},
);
final channelEntity = model.toEntity();
expect(channelEntity, isA<ChannelEntity>());
expect(channelEntity.id, model.id);
expect(
channelEntity.config['max_message_length'],
model.config.maxMessageLength,
);
expect(channelEntity.frozen, model.frozen);
expect(channelEntity.createdAt, isSameDateAs(model.createdAt));
expect(channelEntity.updatedAt, isSameDateAs(model.updatedAt));
expect(channelEntity.memberCount, model.memberCount);
expect(channelEntity.cid, model.cid);
expect(channelEntity.lastMessageAt, isSameDateAs(model.lastMessageAt));
expect(channelEntity.deletedAt, isSameDateAs(model.deletedAt));
expect(channelEntity.extraData, model.extraData);
expect(channelEntity.createdById, model.createdBy.id);
});
}
@@ -0,0 +1,23 @@
import 'package:test/test.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/mapper/event_mapper.dart';
void main() {
test('toEvent should map entity into Event', () {
final ownUser = OwnUser(id: 'testUserId');
final entity = ConnectionEventEntity(
id: 3,
ownUser: ownUser.toJson(),
totalUnreadCount: 33,
unreadChannels: 33,
lastSyncAt: DateTime.now(),
lastEventAt: DateTime.now(),
);
final event = entity.toEvent();
expect(event, isA<Event>());
expect(event.me.id, ownUser.id);
expect(event.totalUnreadCount, entity.totalUnreadCount);
expect(event.unreadChannels, entity.unreadChannels);
});
}
@@ -0,0 +1,69 @@
import 'dart:math' as math;
import 'package:test/test.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/mapper/member_mapper.dart';
import '../utils/date_matcher.dart';
void main() {
test('toMember should map entity into Member', () {
final user = User(id: 'testUserId');
final entity = MemberEntity(
userId: user.id,
channelCid: 'testCid',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
role: 'testRole',
inviteAcceptedAt: DateTime.now(),
inviteRejectedAt: DateTime.now(),
invited: math.Random().nextBool(),
banned: math.Random().nextBool(),
shadowBanned: math.Random().nextBool(),
isModerator: math.Random().nextBool(),
);
final member = entity.toMember(user: user);
expect(member, isA<Member>());
expect(member.user.id, entity.userId);
expect(member.createdAt, isSameDateAs(entity.createdAt));
expect(member.updatedAt, isSameDateAs(entity.updatedAt));
expect(member.role, entity.role);
expect(member.inviteAcceptedAt, isSameDateAs(entity.inviteAcceptedAt));
expect(member.inviteRejectedAt, isSameDateAs(entity.inviteRejectedAt));
expect(member.invited, entity.invited);
expect(member.banned, entity.banned);
expect(member.shadowBanned, entity.shadowBanned);
expect(member.isModerator, entity.isModerator);
});
test('toEntity show map member into MemberEntity', () {
const cid = 'testCid';
final user = User(id: 'testUserId');
final member = Member(
user: user,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
role: 'testRole',
inviteAcceptedAt: DateTime.now(),
inviteRejectedAt: DateTime.now(),
invited: math.Random().nextBool(),
banned: math.Random().nextBool(),
shadowBanned: math.Random().nextBool(),
isModerator: math.Random().nextBool(),
);
final entity = member.toEntity(cid: cid);
expect(entity, isA<MemberEntity>());
expect(entity.channelCid, cid);
expect(entity.userId, member.user.id);
expect(entity.createdAt, isSameDateAs(member.createdAt));
expect(entity.updatedAt, isSameDateAs(member.updatedAt));
expect(entity.role, member.role);
expect(entity.inviteAcceptedAt, isSameDateAs(member.inviteAcceptedAt));
expect(entity.inviteRejectedAt, isSameDateAs(member.inviteRejectedAt));
expect(entity.invited, member.invited);
expect(entity.banned, member.banned);
expect(entity.shadowBanned, member.shadowBanned);
expect(entity.isModerator, member.isModerator);
});
}
@@ -0,0 +1,184 @@
import 'dart:convert';
import 'dart:math' as math;
import 'package:test/test.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/mapper/message_mapper.dart';
import '../utils/date_matcher.dart';
void main() {
test('toMessage should map the entity into Message', () {
final user = User(id: 'testUserId');
final quotedMessage = Message(id: 'testQuotedMessageId');
final reactions = List.generate(
3,
(index) => Reaction(
messageId: 'testMessageId',
createdAt: DateTime.now(),
type: 'testType$index',
user: user,
score: math.Random().nextInt(50),
),
);
final attachments = List.generate(
3,
(index) => Attachment(
id: 'testAttachmentId',
type: 'testAttachmentType',
assetUrl: 'testAssetUrl',
),
);
final entity = MessageEntity(
id: 'testMessageId',
attachments: attachments?.map((it) => jsonEncode(it.toData()))?.toList(),
channelCid: 'testCid',
type: 'testType',
parentId: 'testParentId',
quotedMessageId: quotedMessage.id,
command: 'testCommand',
createdAt: DateTime.now(),
shadowed: math.Random().nextBool(),
showInChannel: math.Random().nextBool(),
replyCount: 33,
reactionScores: {for (final r in reactions) r.type: r.score},
reactionCounts: reactions.fold(
{},
(prev, curr) =>
prev..update(curr.type, (value) => value + 1, ifAbsent: () => 1),
),
status: MessageSendingStatus.sent,
updatedAt: DateTime.now(),
extraData: {'extra_test_data': 'extraData'},
userId: user.id,
deletedAt: DateTime.now(),
messageText: 'dummy text',
pinned: true,
pinExpires: DateTime.now().toUtc(),
pinnedAt: DateTime.now(),
pinnedByUserId: user.id,
);
final message = entity.toMessage(
user: user,
pinnedBy: user,
latestReactions: reactions,
ownReactions: reactions,
quotedMessage: quotedMessage,
);
expect(message, isA<Message>());
expect(message.id, entity.id);
expect(message.type, entity.type);
expect(message.parentId, entity.parentId);
expect(message.quotedMessageId, entity.quotedMessageId);
expect(message.command, entity.command);
expect(message.createdAt, isSameDateAs(entity.createdAt));
expect(message.shadowed, entity.shadowed);
expect(message.showInChannel, entity.showInChannel);
expect(message.replyCount, entity.replyCount);
expect(message.reactionScores, entity.reactionScores);
expect(message.reactionCounts, entity.reactionCounts);
expect(message.status, entity.status);
expect(message.updatedAt, isSameDateAs(entity.updatedAt));
expect(message.extraData, entity.extraData);
expect(message.user.id, entity.userId);
expect(message.deletedAt, isSameDateAs(entity.deletedAt));
expect(message.text, entity.messageText);
expect(message.pinned, entity.pinned);
expect(message.pinExpires, isSameDateAs(entity.pinExpires));
expect(message.pinnedAt, isSameDateAs(entity.pinnedAt));
expect(message.pinnedBy.id, entity.pinnedByUserId);
expect(message.reactionCounts, entity.reactionCounts);
expect(message.reactionScores, entity.reactionScores);
for (var i = 0; i < message.attachments.length; i++) {
final messageAttachment = message.attachments[i];
final entityAttachmentData = jsonDecode(entity.attachments[i]);
final entityAttachment = Attachment.fromData(entityAttachmentData);
expect(messageAttachment.id, entityAttachment.id);
expect(messageAttachment.type, entityAttachment.type);
expect(messageAttachment.assetUrl, entityAttachment.assetUrl);
}
});
test('toEntity should map message into MessageEntity', () {
const cid = 'testCid';
final user = User(id: 'testUserId');
final quotedMessage = Message(id: 'testQuotedMessageId');
final reactions = List.generate(
3,
(index) => Reaction(
messageId: 'testMessageId',
createdAt: DateTime.now(),
type: 'testType$index',
user: user,
score: math.Random().nextInt(50),
),
);
final attachments = List.generate(
3,
(index) => Attachment(
id: 'testAttachmentId',
type: 'testAttachmentType',
assetUrl: 'testAssetUrl',
),
);
final message = Message(
id: 'testMessageId',
attachments: attachments,
type: 'testType',
parentId: 'testParentId',
quotedMessageId: quotedMessage.id,
command: 'testCommand',
createdAt: DateTime.now(),
shadowed: math.Random().nextBool(),
showInChannel: math.Random().nextBool(),
replyCount: 33,
reactionScores: {for (final r in reactions) r.type: r.score},
reactionCounts: reactions.fold(
{},
(prev, curr) =>
prev..update(curr.type, (value) => value + 1, ifAbsent: () => 1),
),
status: MessageSendingStatus.sending,
updatedAt: DateTime.now(),
extraData: {'extra_test_data': 'extraData'},
user: user,
deletedAt: DateTime.now(),
text: 'dummy text',
pinned: true,
pinExpires: DateTime.now(),
pinnedAt: DateTime.now(),
pinnedBy: user,
);
final entity = message.toEntity(cid: cid);
expect(entity, isA<MessageEntity>());
expect(entity.id, message.id);
expect(entity.type, message.type);
expect(entity.parentId, message.parentId);
expect(entity.quotedMessageId, message.quotedMessageId);
expect(entity.command, message.command);
expect(entity.createdAt, isSameDateAs(message.createdAt));
expect(entity.shadowed, message.shadowed);
expect(entity.showInChannel, message.showInChannel);
expect(entity.replyCount, message.replyCount);
expect(entity.reactionScores, message.reactionScores);
expect(entity.reactionCounts, message.reactionCounts);
expect(entity.status, message.status);
expect(entity.updatedAt, isSameDateAs(message.updatedAt));
expect(entity.extraData, message.extraData);
expect(entity.userId, message.user.id);
expect(entity.deletedAt, isSameDateAs(message.deletedAt));
expect(entity.messageText, message.text);
expect(entity.pinned, message.pinned);
expect(entity.pinExpires, isSameDateAs(message.pinExpires));
expect(entity.pinnedAt, isSameDateAs(message.pinnedAt));
expect(entity.pinnedByUserId, message.pinnedBy.id);
expect(entity.reactionCounts, message.reactionCounts);
expect(entity.reactionScores, message.reactionScores);
expect(
entity.attachments,
message.attachments?.map((it) => jsonEncode(it.toData()))?.toList(),
);
});
}
@@ -0,0 +1,184 @@
import 'dart:convert';
import 'dart:math' as math;
import 'package:test/test.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/mapper/pinned_message_mapper.dart';
import '../utils/date_matcher.dart';
void main() {
test('toMessage should map the entity into Message', () {
final user = User(id: 'testUserId');
final quotedMessage = Message(id: 'testQuotedMessageId');
final reactions = List.generate(
3,
(index) => Reaction(
messageId: 'testMessageId',
createdAt: DateTime.now(),
type: 'testType$index',
user: user,
score: math.Random().nextInt(50),
),
);
final attachments = List.generate(
3,
(index) => Attachment(
id: 'testAttachmentId',
type: 'testAttachmentType',
assetUrl: 'testAssetUrl',
),
);
final entity = PinnedMessageEntity(
id: 'testMessageId',
attachments: attachments?.map((it) => jsonEncode(it.toData()))?.toList(),
channelCid: 'testCid',
type: 'testType',
parentId: 'testParentId',
quotedMessageId: quotedMessage.id,
command: 'testCommand',
createdAt: DateTime.now(),
shadowed: math.Random().nextBool(),
showInChannel: math.Random().nextBool(),
replyCount: 33,
reactionScores: {for (final r in reactions) r.type: r.score},
reactionCounts: reactions.fold(
{},
(prev, curr) =>
prev..update(curr.type, (value) => value + 1, ifAbsent: () => 1),
),
status: MessageSendingStatus.sent,
updatedAt: DateTime.now(),
extraData: {'extra_test_data': 'extraData'},
userId: user.id,
deletedAt: DateTime.now(),
messageText: 'dummy text',
pinned: true,
pinExpires: DateTime.now().toUtc(),
pinnedAt: DateTime.now(),
pinnedByUserId: user.id,
);
final message = entity.toMessage(
user: user,
pinnedBy: user,
latestReactions: reactions,
ownReactions: reactions,
quotedMessage: quotedMessage,
);
expect(message, isA<Message>());
expect(message.id, entity.id);
expect(message.type, entity.type);
expect(message.parentId, entity.parentId);
expect(message.quotedMessageId, entity.quotedMessageId);
expect(message.command, entity.command);
expect(message.createdAt, isSameDateAs(entity.createdAt));
expect(message.shadowed, entity.shadowed);
expect(message.showInChannel, entity.showInChannel);
expect(message.replyCount, entity.replyCount);
expect(message.reactionScores, entity.reactionScores);
expect(message.reactionCounts, entity.reactionCounts);
expect(message.status, entity.status);
expect(message.updatedAt, isSameDateAs(entity.updatedAt));
expect(message.extraData, entity.extraData);
expect(message.user.id, entity.userId);
expect(message.deletedAt, isSameDateAs(entity.deletedAt));
expect(message.text, entity.messageText);
expect(message.pinned, entity.pinned);
expect(message.pinExpires, isSameDateAs(entity.pinExpires));
expect(message.pinnedAt, isSameDateAs(entity.pinnedAt));
expect(message.pinnedBy.id, entity.pinnedByUserId);
expect(message.reactionCounts, entity.reactionCounts);
expect(message.reactionScores, entity.reactionScores);
for (var i = 0; i < message.attachments.length; i++) {
final messageAttachment = message.attachments[i];
final entityAttachmentData = jsonDecode(entity.attachments[i]);
final entityAttachment = Attachment.fromData(entityAttachmentData);
expect(messageAttachment.id, entityAttachment.id);
expect(messageAttachment.type, entityAttachment.type);
expect(messageAttachment.assetUrl, entityAttachment.assetUrl);
}
});
test('toPinnedEntity should map message into PinnedMessageEntity', () {
const cid = 'testCid';
final user = User(id: 'testUserId');
final quotedMessage = Message(id: 'testQuotedMessageId');
final reactions = List.generate(
3,
(index) => Reaction(
messageId: 'testMessageId',
createdAt: DateTime.now(),
type: 'testType$index',
user: user,
score: math.Random().nextInt(50),
),
);
final attachments = List.generate(
3,
(index) => Attachment(
id: 'testAttachmentId',
type: 'testAttachmentType',
assetUrl: 'testAssetUrl',
),
);
final message = Message(
id: 'testMessageId',
attachments: attachments,
type: 'testType',
parentId: 'testParentId',
quotedMessageId: quotedMessage.id,
command: 'testCommand',
createdAt: DateTime.now(),
shadowed: math.Random().nextBool(),
showInChannel: math.Random().nextBool(),
replyCount: 33,
reactionScores: {for (final r in reactions) r.type: r.score},
reactionCounts: reactions.fold(
{},
(prev, curr) =>
prev..update(curr.type, (value) => value + 1, ifAbsent: () => 1),
),
status: MessageSendingStatus.sending,
updatedAt: DateTime.now(),
extraData: {'extra_test_data': 'extraData'},
user: user,
deletedAt: DateTime.now(),
text: 'dummy text',
pinned: true,
pinExpires: DateTime.now(),
pinnedAt: DateTime.now(),
pinnedBy: user,
);
final entity = message.toPinnedEntity(cid: cid);
expect(entity, isA<PinnedMessageEntity>());
expect(entity.id, message.id);
expect(entity.type, message.type);
expect(entity.parentId, message.parentId);
expect(entity.quotedMessageId, message.quotedMessageId);
expect(entity.command, message.command);
expect(entity.createdAt, isSameDateAs(message.createdAt));
expect(entity.shadowed, message.shadowed);
expect(entity.showInChannel, message.showInChannel);
expect(entity.replyCount, message.replyCount);
expect(entity.reactionScores, message.reactionScores);
expect(entity.reactionCounts, message.reactionCounts);
expect(entity.status, message.status);
expect(entity.updatedAt, isSameDateAs(message.updatedAt));
expect(entity.extraData, message.extraData);
expect(entity.userId, message.user.id);
expect(entity.deletedAt, isSameDateAs(message.deletedAt));
expect(entity.messageText, message.text);
expect(entity.pinned, message.pinned);
expect(entity.pinExpires, isSameDateAs(message.pinExpires));
expect(entity.pinnedAt, isSameDateAs(message.pinnedAt));
expect(entity.pinnedByUserId, message.pinnedBy.id);
expect(entity.reactionCounts, message.reactionCounts);
expect(entity.reactionScores, message.reactionScores);
expect(
entity.attachments,
message.attachments?.map((it) => jsonEncode(it.toData()))?.toList(),
);
});
}
@@ -0,0 +1,52 @@
import 'package:test/test.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/mapper/reaction_mapper.dart';
import '../utils/date_matcher.dart';
void main() {
test('toReaction should map the entity into Reaction', () {
final user = User(id: 'testUserId');
final message = Message(id: 'testMessageId');
final entity = ReactionEntity(
userId: user.id,
messageId: message.id,
type: 'haha',
score: 33,
createdAt: DateTime.now(),
extraData: {'extra_test_data': 'extraData'},
);
final reaction = entity.toReaction(user: user);
expect(reaction, isA<Reaction>());
expect(reaction.userId, entity.userId);
expect(reaction.messageId, entity.messageId);
expect(reaction.type, entity.type);
expect(reaction.score, entity.score);
expect(reaction.createdAt, isSameDateAs(entity.createdAt));
expect(reaction.extraData, entity.extraData);
});
test('toEntity should map reaction into ReactionEntity', () {
final user = User(id: 'testUserId');
final message = Message(id: 'testMessageId');
final reaction = Reaction(
userId: user.id,
messageId: message.id,
type: 'haha',
score: 33,
createdAt: DateTime.now(),
extraData: {'extra_test_data': 'extraData'},
);
final entity = reaction.toEntity();
expect(entity, isA<ReactionEntity>());
expect(entity.userId, reaction.userId);
expect(entity.messageId, reaction.messageId);
expect(entity.type, reaction.type);
expect(entity.score, reaction.score);
expect(entity.createdAt, isSameDateAs(reaction.createdAt));
expect(entity.extraData, reaction.extraData);
});
}
@@ -0,0 +1,41 @@
import 'package:test/test.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/mapper/read_mapper.dart';
import '../utils/date_matcher.dart';
void main() {
test('toRead should map entity into Read', () {
const cid = 'testCid';
final user = User(id: 'testUserId');
final entity = ReadEntity(
lastRead: DateTime.now(),
userId: user.id,
channelCid: cid,
unreadMessages: 33,
);
final read = entity.toRead(user: user);
expect(read, isA<Read>());
expect(read.lastRead, isSameDateAs(entity.lastRead));
expect(read.user.id, entity.userId);
expect(read.unreadMessages, entity.unreadMessages);
});
test('toEntity should map read into ReadEntity', () {
const cid = 'testCid';
final user = User(id: 'testUserId');
final read = Read(
lastRead: DateTime.now(),
user: user,
unreadMessages: 33,
);
final entity = read.toEntity(cid: cid);
expect(entity, isA<ReadEntity>());
expect(entity.lastRead, isSameDateAs(read.lastRead));
expect(entity.userId, read.user.id);
expect(entity.unreadMessages, read.unreadMessages);
});
}
@@ -0,0 +1,55 @@
import 'dart:math' as math;
import 'package:test/test.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/mapper/user_mapper.dart';
import '../utils/date_matcher.dart';
void main() {
test('toUser should map entity into User', () {
final entity = UserEntity(
id: 'testUserId',
role: 'testType',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
lastActive: DateTime.now(),
online: math.Random().nextBool(),
banned: math.Random().nextBool(),
extraData: {'test_extra_data': 'extraData'},
);
final user = entity.toUser();
expect(user, isA<User>());
expect(user.id, entity.id);
expect(user.role, entity.role);
expect(user.createdAt, isSameDateAs(entity.createdAt));
expect(user.updatedAt, isSameDateAs(entity.updatedAt));
expect(user.lastActive, isSameDateAs(entity.lastActive));
expect(user.online, entity.online);
expect(user.banned, entity.banned);
expect(user.extraData, entity.extraData);
});
test('toEntity should map user into UserEntity', () {
final user = User(
id: 'testUserId',
role: 'testType',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
lastActive: DateTime.now(),
online: math.Random().nextBool(),
banned: math.Random().nextBool(),
extraData: {'test_extra_data': 'extraData'},
);
final entity = user.toEntity();
expect(entity, isA<UserEntity>());
expect(entity.id, user.id);
expect(entity.role, user.role);
expect(entity.createdAt, isSameDateAs(user.createdAt));
expect(entity.updatedAt, isSameDateAs(user.updatedAt));
expect(entity.lastActive, isSameDateAs(user.lastActive));
expect(entity.online, user.online);
expect(entity.banned, user.banned);
expect(entity.extraData, user.extraData);
});
}
@@ -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,26 @@
import 'package:test/test.dart';
import 'package:meta/meta.dart';
Matcher isSameDateAs(DateTime targetDate) =>
_IsSameDateAs(targetDate: targetDate);
class _IsSameDateAs extends Matcher {
const _IsSameDateAs({
@required this.targetDate,
}) : assert(targetDate != null, '');
final DateTime targetDate;
@override
bool matches(covariant DateTime date, Map matchState) =>
date.year == targetDate.year &&
date.month == targetDate.month &&
date.day == targetDate.day &&
date.hour == targetDate.hour &&
date.minute == targetDate.minute &&
date.second == targetDate.second;
@override
Description describe(Description description) =>
description.add('is same date as $targetDate');
}
@@ -0,0 +1,558 @@
import 'package:mocktail/mocktail.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('client constructor', () {
test('throws assertion error if null connectionMode is provided', () {
expect(
() => StreamChatPersistenceClient(connectionMode: null),
throwsA(isA<AssertionError>()),
);
});
test('throws assertion error if null logLevel is provided', () {
expect(
() => StreamChatPersistenceClient(logLevel: null),
throwsA(isA<AssertionError>()),
);
});
});
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);
});
});
}