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:
dahoud
2026-01-10 10:43:17 +00:00
parent 06031b01f2
commit 92612abbd7
321 changed files with 43137 additions and 4285 deletions

View 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'));
});
});
});
}