337 lines
11 KiB
Dart
337 lines
11 KiB
Dart
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 = <Map<String, dynamic>>[];
|
|
|
|
// Listen for messages
|
|
wsChannel.stream.listen(
|
|
(message) {
|
|
final data = jsonDecode(message as String) as Map<String, dynamic>;
|
|
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<int>());
|
|
|
|
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 = <Map<String, dynamic>>[];
|
|
|
|
// 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<void> _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');
|
|
}
|