import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:test/test.dart'; import 'package:sqlite3/sqlite3.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; import '../lib/src/database/database_manager.dart'; import '../lib/src/web/dashboard_server.dart'; import '../lib/src/web/websocket_manager.dart'; import '../lib/src/models/activity_event.dart'; void main() { group('WebSocket Integration Tests', () { late Database inMemoryDb; late DatabaseManager dbManager; late DashboardServer server; late WebSocketChannel wsChannel; final int testPort = 8081; setUpAll(() async { // Create in-memory database inMemoryDb = sqlite3.openInMemory(); dbManager = DatabaseManager(inMemoryDb); dbManager.initDatabase(); // Populate database with test data await _populateTestData(dbManager); // Start server with test database server = DashboardServer.withDatabase(dbManager); await server.start(testPort); // Give server time to start await Future.delayed(Duration(milliseconds: 100)); }); tearDownAll(() async { await wsChannel.sink.close(); await server.stop(); inMemoryDb.dispose(); }); test('WebSocket connection and message types', () async { // Connect to WebSocket wsChannel = WebSocketChannel.connect( Uri.parse('ws://localhost:$testPort/ws'), ); final receivedMessages = >[]; // Listen for messages wsChannel.stream.listen( (message) { final data = jsonDecode(message as String) as Map; receivedMessages.add(data); }, onError: (error) { fail('WebSocket error: $error'); }, ); // Test 1: Ping/Pong print('Testing ping/pong...'); wsChannel.sink.add(jsonEncode({ 'type': 'ping', 'timestamp': DateTime.now().millisecondsSinceEpoch, })); // Wait for pong response await Future.delayed(Duration(milliseconds: 200)); expect(receivedMessages.length, equals(1)); expect(receivedMessages[0]['type'], equals('pong')); expect(receivedMessages[0]['timestamp'], isA()); receivedMessages.clear(); // Test 2: Stats Update Message print('Testing stats update broadcast...'); final wsManager = WebSocketManager.instance; // Create and broadcast stats update final statsData = { 'today': { 'level': 5, 'xp': 450, 'focus_time': 7200, 'meeting_time': 3600, 'focus_sessions': 3, }, 'streaks': { 'current_streak': 7, 'longest_streak': 15, }, 'recent_activity': [ { 'type': 'coding', 'application': 'vscode', 'timestamp': DateTime.now().millisecondsSinceEpoch, 'duration_seconds': 1800, } ], 'timestamp': DateTime.now().millisecondsSinceEpoch, }; wsManager.broadcast({ 'type': 'stats_update', 'data': statsData, 'timestamp': DateTime.now().millisecondsSinceEpoch, }); await Future.delayed(Duration(milliseconds: 100)); expect(receivedMessages.length, equals(1)); expect(receivedMessages[0]['type'], equals('stats_update')); expect(receivedMessages[0]['data']['today']['level'], equals(5)); expect(receivedMessages[0]['data']['today']['xp'], equals(450)); receivedMessages.clear(); // Test 3: XP Breakdown Update print('Testing XP breakdown update...'); final xpBreakdown = { 'coding': 300, 'focused_browsing': 120, 'collaboration': 80, 'meetings': 60, 'focus_session': 100, }; wsManager.broadcast({ 'type': 'xp_breakdown_update', 'data': xpBreakdown, 'timestamp': DateTime.now().millisecondsSinceEpoch, }); await Future.delayed(Duration(milliseconds: 100)); expect(receivedMessages.length, equals(1)); expect(receivedMessages[0]['type'], equals('xp_breakdown_update')); expect(receivedMessages[0]['data']['coding'], equals(300)); expect(receivedMessages[0]['data']['focused_browsing'], equals(120)); receivedMessages.clear(); // Test 4: Achievement Unlocked print('Testing achievement unlocked...'); final achievement = { 'id': 1, 'name': 'Code Warrior', 'description': 'Spent 10 hours coding in a single day', 'xp_reward': 100, 'achieved_at': DateTime.now().millisecondsSinceEpoch, 'level_at_achievement': 5, }; wsManager.broadcast({ 'type': 'achievement_unlocked', 'data': achievement, 'timestamp': DateTime.now().millisecondsSinceEpoch, }); await Future.delayed(Duration(milliseconds: 100)); expect(receivedMessages.length, equals(1)); expect(receivedMessages[0]['type'], equals('achievement_unlocked')); expect(receivedMessages[0]['data']['name'], equals('Code Warrior')); expect(receivedMessages[0]['data']['xp_reward'], equals(100)); receivedMessages.clear(); // Test 5: Level Up print('Testing level up...'); wsManager.broadcast({ 'type': 'level_up', 'data': {'level': 6}, 'timestamp': DateTime.now().millisecondsSinceEpoch, }); await Future.delayed(Duration(milliseconds: 100)); expect(receivedMessages.length, equals(1)); expect(receivedMessages[0]['type'], equals('level_up')); expect(receivedMessages[0]['data']['level'], equals(6)); receivedMessages.clear(); // Test 6: Focus Session Complete print('Testing focus session complete...'); final focusSession = { 'id': 1, 'date': DateTime.now().toIso8601String().substring(0, 10), 'duration_minutes': 45, 'bonus_xp': 50, 'timestamp': DateTime.now().millisecondsSinceEpoch, }; wsManager.broadcast({ 'type': 'focus_session_complete', 'data': focusSession, 'timestamp': DateTime.now().millisecondsSinceEpoch, }); await Future.delayed(Duration(milliseconds: 100)); expect(receivedMessages.length, equals(1)); expect(receivedMessages[0]['type'], equals('focus_session_complete')); expect(receivedMessages[0]['data']['duration_minutes'], equals(45)); expect(receivedMessages[0]['data']['bonus_xp'], equals(50)); receivedMessages.clear(); print('All WebSocket message types tested successfully!'); }); test('Database integration with WebSocket messages', () async { // Test that database queries work correctly and can feed WebSocket messages // Test today's stats final todayStats = dbManager.getTodayStats(); expect(todayStats['level'], greaterThan(0)); expect(todayStats['xp'], greaterThan(0)); // Test XP breakdown final xpBreakdown = dbManager.getTodayXPBreakdown(); expect(xpBreakdown, isNotEmpty); expect(xpBreakdown.containsKey('coding'), isTrue); // Test achievements final achievements = dbManager.getAllAchievements(); expect(achievements.length, greaterThan(0)); // Test recent activities final activities = dbManager.getRecentActivity(); expect(activities.length, greaterThan(0)); // Test focus sessions final focusSessions = dbManager.getRecentFocusSessions(); expect(focusSessions.length, greaterThan(0)); print('Database integration verified successfully!'); }); test('WebSocket connection management', () async { final wsManager = WebSocketManager.instance; final initialConnectionCount = wsManager.connectionCount; // Connect additional WebSocket final wsChannel2 = WebSocketChannel.connect( Uri.parse('ws://localhost:$testPort/ws'), ); await Future.delayed(Duration(milliseconds: 100)); expect(wsManager.connectionCount, equals(initialConnectionCount + 1)); // Test broadcasting to multiple connections final receivedMessages2 = >[]; // Only listen to the new channel to avoid "already listened" error wsChannel2.stream.listen((message) { receivedMessages2.add(jsonDecode(message as String)); }); // Broadcast a message wsManager.broadcast({ 'type': 'test_broadcast', 'data': {'message': 'Hello all connections!'}, 'timestamp': DateTime.now().millisecondsSinceEpoch, }); await Future.delayed(Duration(milliseconds: 100)); // Verify the new connection received the broadcast expect(receivedMessages2.length, greaterThan(0)); expect(receivedMessages2.last['type'], equals('test_broadcast')); // Close second connection await wsChannel2.sink.close(); await Future.delayed(Duration(milliseconds: 100)); expect(wsManager.connectionCount, equals(initialConnectionCount)); print('WebSocket connection management tested successfully!'); }); }); } Future _populateTestData(DatabaseManager dbManager) async { final now = DateTime.now(); final today = now.toIso8601String().substring(0, 10); final timestamp = now.millisecondsSinceEpoch; // Add activity events dbManager.saveActivityEvent('coding', 'vscode', '{"project": "test"}', timestamp - 3600000, 1800); dbManager.saveActivityEvent('focused_browsing', 'firefox', '{"url": "docs.flutter.dev"}', timestamp - 7200000, 1200); dbManager.saveActivityEvent('collaboration', 'slack', '{"channel": "dev-team"}', timestamp - 10800000, 900); dbManager.saveActivityEvent('meetings', 'zoom', '{"meeting": "standup"}', timestamp - 14400000, 1800); dbManager.saveActivityEvent('misc', 'keepassxc', null, timestamp - 18000000, 300); // Update daily stats dbManager.updateDailyStats(450, 7200, 3600); // Add focus sessions dbManager.saveFocusSession(today, 45, 50, timestamp - 3600000); dbManager.saveFocusSession(today, 30, 30, timestamp - 7200000); dbManager.saveFocusSession(today, 60, 75, timestamp - 10800000); // Add achievements dbManager.saveAchievement( 'First Steps', 'Complete your first activity tracking session', 25, timestamp - 86400000, 1, ); dbManager.saveAchievement( 'Code Warrior', 'Spend 10 hours coding in a single day', 100, timestamp - 43200000, 3, ); dbManager.saveAchievement( 'Focus Master', 'Complete 5 focus sessions in one day', 75, timestamp - 21600000, 4, ); // Add application classifications dbManager.saveApplicationClassification('vscode', 'coding'); dbManager.saveApplicationClassification('firefox', 'focused_browsing'); dbManager.saveApplicationClassification('slack', 'collaboration'); dbManager.saveApplicationClassification('zoom', 'meetings'); // Add some unclassified applications dbManager.trackUnclassifiedApplication('unknown_app_1'); dbManager.trackUnclassifiedApplication('unknown_app_2'); print('Test data populated successfully'); }