The Talk room list crashed for a new account because the server omits
lastMessage for rooms without a visible message and the model required it.
Beyond that single field, a review of the Talk models against the OpenAPI
spec of the installed version (23.0.8) found the same crash class in several
places: closed enums over open server values (inCall bitmask, rich-object
types, actor and message types), a deleted parent sent as {id, deleted},
and the autocomplete status arriving as an object.
- json_serializable `checked: true`: decode errors now name class and field
- lists (rooms, messages, participants, autocomplete) decode per entry;
a malformed entry is skipped and reported via ClientErrorReporter
- unknownEnumValue fallbacks for all response enums of Talk and breakers
- lastMessage nullable; inCall as bitmask; deleted parent placeholder;
autocomplete status object/string; fixed shareWithDisplayNameUnique key
- GetRoomParams no longer sends null query parameters
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HrepDTL4qDe35TuS29sLW3
123 lines
3.5 KiB
Dart
123 lines
3.5 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:marianum_mobile/api/json_lenient.dart';
|
|
import 'package:marianum_mobile/api/marianumcloud/talk/chat/get_chat_response.dart';
|
|
import 'package:marianum_mobile/api/marianumcloud/talk/room/get_room_response.dart';
|
|
|
|
Map<String, dynamic> _room({bool withLastMessage = true}) => {
|
|
'id': 1,
|
|
'token': 'abc',
|
|
'type': 2,
|
|
'name': 'room',
|
|
'displayName': 'Room',
|
|
'description': '',
|
|
'participantType': 3,
|
|
'participantFlags': 0,
|
|
'readOnly': 0,
|
|
'listable': 0,
|
|
'lastPing': 0,
|
|
'sessionId': '0',
|
|
'hasPassword': false,
|
|
'hasCall': false,
|
|
'callFlag': 0,
|
|
'canStartCall': true,
|
|
'canDeleteConversation': false,
|
|
'canLeaveConversation': true,
|
|
'lastActivity': 1700000000,
|
|
'isFavorite': false,
|
|
'notificationLevel': 0,
|
|
'unreadMessages': 0,
|
|
'unreadMention': false,
|
|
'unreadMentionDirect': false,
|
|
'lastReadMessage': 0,
|
|
'lastCommonReadMessage': 0,
|
|
if (withLastMessage)
|
|
'lastMessage': {
|
|
'id': 7,
|
|
'token': 'abc',
|
|
'actorType': 'users',
|
|
'actorId': 'u',
|
|
'actorDisplayName': 'U',
|
|
'timestamp': 1700000000,
|
|
'systemMessage': '',
|
|
'messageType': 'comment',
|
|
'isReplyable': true,
|
|
'referenceId': '',
|
|
'message': 'hi',
|
|
},
|
|
};
|
|
|
|
void main() {
|
|
setUp(drainDecodeWarnings);
|
|
|
|
test('skips a room that does not parse and records class and key', () {
|
|
final broken = _room()..remove('displayName');
|
|
final response = GetRoomResponse.fromJson({
|
|
'data': [broken, _room()],
|
|
});
|
|
expect(response.data.length, 1);
|
|
final warnings = drainDecodeWarnings();
|
|
expect(warnings, hasLength(1));
|
|
expect(warnings.single, contains('GetRoomResponseObject'));
|
|
expect(warnings.single, contains('"displayName"'));
|
|
});
|
|
|
|
test('parses a room whose lastMessage the server omitted', () {
|
|
final response = GetRoomResponse.fromJson({
|
|
'data': [_room(withLastMessage: false), _room()],
|
|
});
|
|
final rooms = response.data.toList();
|
|
expect(rooms[0].lastMessage, isNull);
|
|
expect(rooms[1].lastMessage?.id, 7);
|
|
expect(drainDecodeWarnings(), isEmpty);
|
|
});
|
|
|
|
test('maps unknown enum values to their fallbacks', () {
|
|
final room = _room()
|
|
..['type'] = 99
|
|
..['notificationLevel'] = 42;
|
|
(room['lastMessage'] as Map<String, dynamic>)
|
|
..['actorType'] = 'emails'
|
|
..['messageType'] = 'record-video'
|
|
..['messageParameters'] = {
|
|
'actor': {'type': 'deck-card', 'id': '1', 'name': 'Card'},
|
|
};
|
|
final parsed = GetRoomResponse.fromJson({
|
|
'data': [room],
|
|
}).data.single;
|
|
expect(parsed.type, GetRoomResponseObjectConversationType.unknown);
|
|
expect(
|
|
parsed.notificationLevel,
|
|
GetRoomResponseObjectParticipantNotificationLevel.defaultLevel,
|
|
);
|
|
expect(
|
|
parsed.lastMessage?.actorType,
|
|
GetRoomResponseObjectMessageActorType.email,
|
|
);
|
|
expect(
|
|
parsed.lastMessage?.messageType,
|
|
GetRoomResponseObjectMessageType.unknown,
|
|
);
|
|
expect(
|
|
parsed.lastMessage?.messageParameters?['actor']?.type,
|
|
RichObjectStringObjectType.unknown,
|
|
);
|
|
expect(drainDecodeWarnings(), isEmpty);
|
|
});
|
|
|
|
test('turns a deleted parent into a placeholder', () {
|
|
final room = _room();
|
|
(room['lastMessage'] as Map<String, dynamic>)['parent'] = {
|
|
'id': 3,
|
|
'deleted': true,
|
|
};
|
|
final parent = GetRoomResponse.fromJson({
|
|
'data': [room],
|
|
}).data.single.lastMessage?.parent;
|
|
expect(parent?.id, 3);
|
|
expect(
|
|
parent?.messageType,
|
|
GetRoomResponseObjectMessageType.deletedComment,
|
|
);
|
|
});
|
|
}
|