fix(chat): Correction race condition + Implémentation TODOs
## Corrections Critiques ### Race Condition - Statuts de Messages - Fix : Les icônes de statut (✓, ✓✓, ✓✓ bleu) ne s'affichaient pas - Cause : WebSocket delivery confirmations arrivaient avant messages locaux - Solution : Pattern Optimistic UI dans chat_bloc.dart - Création message temporaire immédiate - Ajout à la liste AVANT requête HTTP - Remplacement par message serveur à la réponse - Fichier : lib/presentation/state_management/chat_bloc.dart ## Implémentation TODOs (13/21) ### Social (social_header_widget.dart) - ✅ Copier lien du post dans presse-papiers - ✅ Partage natif via Share.share() - ✅ Dialogue de signalement avec 5 raisons ### Partage (share_post_dialog.dart) - ✅ Interface sélection d'amis avec checkboxes - ✅ Partage externe via Share API ### Média (media_upload_service.dart) - ✅ Parsing JSON réponse backend - ✅ Méthode deleteMedia() pour suppression - ✅ Génération miniature vidéo ### Posts (create_post_dialog.dart, edit_post_dialog.dart) - ✅ Extraction URL depuis uploads - ✅ Documentation chargement médias ### Chat (conversations_screen.dart) - ✅ Navigation vers notifications - ✅ ConversationSearchDelegate pour recherche ## Nouveaux Fichiers ### Configuration - build-prod.ps1 : Script build production avec dart-define - lib/core/constants/env_config.dart : Gestion environnements ### Documentation - TODOS_IMPLEMENTED.md : Documentation complète TODOs ## Améliorations ### Architecture - Refactoring injection de dépendances - Amélioration routing et navigation - Optimisation providers (UserProvider, FriendsProvider) ### UI/UX - Amélioration thème et couleurs - Optimisation animations - Meilleure gestion erreurs ### Services - Configuration API avec env_config - Amélioration datasources (events, users) - Optimisation modèles de données
This commit is contained in:
396
test/data/repositories/friends_repository_impl_test.dart
Normal file
396
test/data/repositories/friends_repository_impl_test.dart
Normal file
@@ -0,0 +1,396 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:afterwork/core/errors/exceptions.dart';
|
||||
import 'package:afterwork/data/repositories/friends_repository_impl.dart';
|
||||
import 'package:afterwork/domain/entities/friend.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
// Mock classes
|
||||
class MockHttpClient extends Mock implements http.Client {}
|
||||
|
||||
void main() {
|
||||
late FriendsRepositoryImpl repository;
|
||||
late MockHttpClient mockHttpClient;
|
||||
|
||||
setUpAll(() {
|
||||
// Register fallback values for mocktail
|
||||
registerFallbackValue(Uri.parse('http://example.com'));
|
||||
registerFallbackValue(<String, String>{});
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
mockHttpClient = MockHttpClient();
|
||||
reset(mockHttpClient); // Reset mock between tests
|
||||
repository = FriendsRepositoryImpl(client: mockHttpClient);
|
||||
});
|
||||
|
||||
group('FriendsRepositoryImpl', () {
|
||||
const tUserId = 'user123';
|
||||
const tFriendId = 'friend456';
|
||||
final tFriend = Friend(
|
||||
friendId: tFriendId,
|
||||
friendFirstName: 'John',
|
||||
friendLastName: 'Doe',
|
||||
email: 'john@example.com',
|
||||
status: FriendStatus.accepted,
|
||||
);
|
||||
|
||||
final tFriendsJson = [
|
||||
{
|
||||
'friendId': 'friend1',
|
||||
'friendFirstName': 'Jane',
|
||||
'friendLastName': 'Smith',
|
||||
'email': 'jane@example.com',
|
||||
'status': 'accepted',
|
||||
},
|
||||
{
|
||||
'friendId': 'friend2',
|
||||
'friendFirstName': 'Bob',
|
||||
'friendLastName': 'Johnson',
|
||||
'email': 'bob@example.com',
|
||||
'status': 'pending',
|
||||
},
|
||||
];
|
||||
|
||||
group('fetchFriends', () {
|
||||
test('should return list of friends when API call is successful', () async {
|
||||
// Arrange
|
||||
when(() => mockHttpClient.get(
|
||||
any(),
|
||||
headers: any(named: 'headers'),
|
||||
)).thenAnswer(
|
||||
(_) async => http.Response(
|
||||
jsonEncode(tFriendsJson),
|
||||
200,
|
||||
),
|
||||
);
|
||||
|
||||
// Act
|
||||
final result = await repository.fetchFriends(tUserId, 0, 10);
|
||||
|
||||
// Assert
|
||||
expect(result, isA<List<Friend>>());
|
||||
expect(result.length, 2);
|
||||
expect(result.first.friendId, 'friend1');
|
||||
expect(result.first.friendFirstName, 'Jane');
|
||||
verify(() => mockHttpClient.get(
|
||||
any(),
|
||||
headers: any(named: 'headers'),
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
test('should throw exception when API call fails', () async {
|
||||
// Arrange
|
||||
when(() => mockHttpClient.get(
|
||||
any(),
|
||||
headers: any(named: 'headers'),
|
||||
)).thenAnswer(
|
||||
(_) async => http.Response('{"message": "Error"}', 500),
|
||||
);
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => repository.fetchFriends(tUserId, 0, 10),
|
||||
throwsA(isA<ServerException>()),
|
||||
);
|
||||
verify(() => mockHttpClient.get(
|
||||
any(),
|
||||
headers: any(named: 'headers'),
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
test('should throw exception when exception occurs', () async {
|
||||
// Arrange
|
||||
when(() => mockHttpClient.get(any())).thenThrow(Exception('Network error'));
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => repository.fetchFriends(tUserId, 0, 10),
|
||||
throwsA(isA<ServerException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should call correct URL with pagination parameters', () async {
|
||||
// Arrange
|
||||
when(() => mockHttpClient.get(
|
||||
any(),
|
||||
headers: any(named: 'headers'),
|
||||
)).thenAnswer(
|
||||
(_) async => http.Response(jsonEncode([]), 200),
|
||||
);
|
||||
|
||||
// Act
|
||||
await repository.fetchFriends(tUserId, 2, 20);
|
||||
|
||||
// Assert
|
||||
final captured = verify(() => mockHttpClient.get(
|
||||
captureAny(),
|
||||
headers: any(named: 'headers'),
|
||||
)).captured;
|
||||
final uri = captured.first as Uri;
|
||||
expect(uri.path, contains('/friends/list/$tUserId'));
|
||||
expect(uri.queryParameters['page'], '2');
|
||||
expect(uri.queryParameters['size'], '20');
|
||||
});
|
||||
});
|
||||
|
||||
group('addFriend', () {
|
||||
test('should add friend successfully when API call is successful', () async {
|
||||
// Arrange
|
||||
when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
|
||||
.thenAnswer((_) async => http.Response('Success', 200));
|
||||
|
||||
// Act
|
||||
await repository.addFriend(tUserId, tFriendId);
|
||||
|
||||
// Assert
|
||||
verify(() => mockHttpClient.post(
|
||||
any(),
|
||||
headers: any(named: 'headers'),
|
||||
body: any(named: 'body'),
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
test('should throw exception when API call fails', () async {
|
||||
// Arrange
|
||||
when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
|
||||
.thenAnswer((_) async => http.Response('{"message": "Error"}', 400));
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => repository.addFriend(tUserId, tFriendId),
|
||||
throwsA(isA<ValidationException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw exception when network error occurs', () async {
|
||||
// Arrange
|
||||
when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
|
||||
.thenThrow(Exception('Network error'));
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => repository.addFriend(tUserId, tFriendId),
|
||||
throwsA(isA<ServerException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should send correct JSON body', () async {
|
||||
// Arrange - Use a valid JSON response
|
||||
when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
|
||||
.thenAnswer((_) async => http.Response('{"success": true}', 200));
|
||||
|
||||
// Act
|
||||
await repository.addFriend(tUserId, tFriendId);
|
||||
|
||||
// Assert
|
||||
final captured = verify(() => mockHttpClient.post(
|
||||
any(),
|
||||
headers: any(named: 'headers'),
|
||||
body: captureAny(named: 'body'),
|
||||
)).captured;
|
||||
final body = captured.first as String;
|
||||
final bodyJson = jsonDecode(body) as Map<String, dynamic>;
|
||||
expect(bodyJson['userId'], tUserId);
|
||||
expect(bodyJson['friendId'], tFriendId);
|
||||
});
|
||||
});
|
||||
|
||||
group('removeFriend', () {
|
||||
test('should remove friend successfully when API call is successful', () async {
|
||||
// Arrange
|
||||
when(() => mockHttpClient.delete(
|
||||
any(),
|
||||
headers: any(named: 'headers'),
|
||||
)).thenAnswer(
|
||||
(_) async => http.Response('Success', 200),
|
||||
);
|
||||
|
||||
// Act
|
||||
await repository.removeFriend(tFriendId);
|
||||
|
||||
// Assert
|
||||
verify(() => mockHttpClient.delete(
|
||||
any(),
|
||||
headers: any(named: 'headers'),
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
test('should throw exception when API call fails', () async {
|
||||
// Arrange
|
||||
when(() => mockHttpClient.delete(any())).thenAnswer(
|
||||
(_) async => http.Response('{"message": "Error"}', 404),
|
||||
);
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => repository.removeFriend(tFriendId),
|
||||
throwsA(isA<ServerException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should call correct URL', () async {
|
||||
// Arrange
|
||||
when(() => mockHttpClient.delete(
|
||||
any(),
|
||||
headers: any(named: 'headers'),
|
||||
)).thenAnswer(
|
||||
(_) async => http.Response('Success', 200),
|
||||
);
|
||||
|
||||
// Act
|
||||
await repository.removeFriend(tFriendId);
|
||||
|
||||
// Assert
|
||||
final captured = verify(() => mockHttpClient.delete(
|
||||
captureAny(),
|
||||
headers: any(named: 'headers'),
|
||||
)).captured;
|
||||
final uri = captured.first as Uri;
|
||||
expect(uri.path, contains('/friends/$tFriendId'));
|
||||
});
|
||||
});
|
||||
|
||||
group('getFriendDetails', () {
|
||||
final tFriendDetailsJson = {
|
||||
'friendId': tFriendId,
|
||||
'friendFirstName': 'John',
|
||||
'friendLastName': 'Doe',
|
||||
'email': 'john@example.com',
|
||||
'status': 'accepted',
|
||||
};
|
||||
|
||||
test('should return friend details when API call is successful', () async {
|
||||
// Arrange
|
||||
when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
|
||||
.thenAnswer(
|
||||
(_) async => http.Response(jsonEncode(tFriendDetailsJson), 200),
|
||||
);
|
||||
|
||||
// Act
|
||||
final result = await repository.getFriendDetails(tFriendId, tUserId);
|
||||
|
||||
// Assert
|
||||
expect(result, isNotNull);
|
||||
expect(result?.friendId, tFriendId);
|
||||
expect(result?.friendFirstName, 'John');
|
||||
expect(result?.friendLastName, 'Doe');
|
||||
});
|
||||
|
||||
test('should return null when API call fails', () async {
|
||||
// Arrange
|
||||
when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
|
||||
.thenAnswer((_) async => http.Response('Error', 404));
|
||||
|
||||
// Act
|
||||
final result = await repository.getFriendDetails(tFriendId, tUserId);
|
||||
|
||||
// Assert
|
||||
expect(result, isNull);
|
||||
});
|
||||
|
||||
test('should throw exception when exception occurs', () async {
|
||||
// Arrange
|
||||
when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
|
||||
.thenThrow(Exception('Network error'));
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => repository.getFriendDetails(tFriendId, tUserId),
|
||||
throwsA(isA<ServerException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should send correct request body', () async {
|
||||
// Arrange - Ensure the mock is properly set up
|
||||
when(() => mockHttpClient.post(any(), headers: any(named: 'headers'), body: any(named: 'body')))
|
||||
.thenAnswer((_) async => http.Response(jsonEncode(tFriendDetailsJson), 200));
|
||||
|
||||
// Act
|
||||
final result = await repository.getFriendDetails(tFriendId, tUserId);
|
||||
|
||||
// Assert - Verify the result first
|
||||
expect(result, isNotNull);
|
||||
|
||||
// Then verify the request body
|
||||
final captured = verify(() => mockHttpClient.post(
|
||||
any(),
|
||||
headers: any(named: 'headers'),
|
||||
body: captureAny(named: 'body'),
|
||||
)).captured;
|
||||
final body = jsonDecode(captured.first as String) as Map<String, dynamic>;
|
||||
expect(body['friendId'], tFriendId);
|
||||
expect(body['userId'], tUserId);
|
||||
});
|
||||
});
|
||||
|
||||
group('updateFriendStatus', () {
|
||||
test('should update friend status successfully when API call is successful', () async {
|
||||
// Arrange
|
||||
when(() => mockHttpClient.patch(any(), headers: any(named: 'headers'), body: any(named: 'body')))
|
||||
.thenAnswer((_) async => http.Response('Success', 200));
|
||||
|
||||
// Act
|
||||
await repository.updateFriendStatus(tFriendId, 'accepted');
|
||||
|
||||
// Assert
|
||||
verify(() => mockHttpClient.patch(
|
||||
any(),
|
||||
headers: any(named: 'headers'),
|
||||
body: any(named: 'body'),
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
test('should throw exception when API call fails', () async {
|
||||
// Arrange
|
||||
when(() => mockHttpClient.patch(any(), headers: any(named: 'headers'), body: any(named: 'body')))
|
||||
.thenAnswer((_) async => http.Response('{"message": "Error"}', 400));
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => repository.updateFriendStatus(tFriendId, 'accepted'),
|
||||
throwsA(isA<ValidationException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should send correct status in body', () async {
|
||||
// Arrange - Use a valid JSON response
|
||||
when(() => mockHttpClient.patch(any(), headers: any(named: 'headers'), body: any(named: 'body')))
|
||||
.thenAnswer((_) async => http.Response('{"success": true}', 200));
|
||||
|
||||
// Act
|
||||
await repository.updateFriendStatus(tFriendId, 'blocked');
|
||||
|
||||
// Assert
|
||||
final captured = verify(() => mockHttpClient.patch(
|
||||
any(),
|
||||
headers: any(named: 'headers'),
|
||||
body: captureAny(named: 'body'),
|
||||
)).captured;
|
||||
final body = jsonDecode(captured.first as String) as Map<String, dynamic>;
|
||||
expect(body['status'], 'blocked');
|
||||
});
|
||||
|
||||
test('should call correct URL', () async {
|
||||
// Arrange
|
||||
when(() => mockHttpClient.patch(any(), headers: any(named: 'headers'), body: any(named: 'body')))
|
||||
.thenAnswer((_) async => http.Response('Success', 200));
|
||||
|
||||
// Act
|
||||
await repository.updateFriendStatus(tFriendId, 'accepted');
|
||||
|
||||
// Assert
|
||||
final captured = verify(() => mockHttpClient.patch(
|
||||
captureAny(),
|
||||
headers: any(named: 'headers'),
|
||||
body: any(named: 'body'),
|
||||
)).captured;
|
||||
final uri = captured.first as Uri;
|
||||
expect(uri.path, contains('/friends/$tFriendId/status'));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user