import 'dart:async'; import 'package:flutter_test/flutter_test.dart'; import 'package:app_boilerplate/ui/relay_management/relay_management_controller.dart'; import 'package:app_boilerplate/data/nostr/nostr_service.dart'; import 'package:app_boilerplate/data/sync/sync_engine.dart'; import 'package:app_boilerplate/data/local/local_storage_service.dart'; import 'package:path/path.dart' as path; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'dart:io'; void main() { // Initialize Flutter bindings and sqflite for testing TestWidgetsFlutterBinding.ensureInitialized(); sqfliteFfiInit(); databaseFactory = databaseFactoryFfi; late NostrService nostrService; late SyncEngine syncEngine; late LocalStorageService localStorage; late Directory testDir; late String testDbPath; late Directory testCacheDir; late RelayManagementController controller; setUp(() async { // Create temporary directory for testing testDir = await Directory.systemTemp.createTemp('relay_test_'); testDbPath = path.join(testDir.path, 'test_local_storage.db'); testCacheDir = Directory(path.join(testDir.path, 'image_cache')); // Initialize local storage localStorage = LocalStorageService( testDbPath: testDbPath, testCacheDir: testCacheDir, ); await localStorage.initialize(); // Create services nostrService = NostrService(); syncEngine = SyncEngine( localStorage: localStorage, nostrService: nostrService, ); // Create controller controller = RelayManagementController( nostrService: nostrService, syncEngine: syncEngine, ); }); tearDown(() async { controller.dispose(); syncEngine.dispose(); nostrService.dispose(); await localStorage.close(); try { if (await testDir.exists()) { await testDir.delete(recursive: true); } } catch (_) { // Ignore cleanup errors } }); group('RelayManagementController', () { test('initial state - empty relay list', () { expect(controller.relays, isEmpty); expect(controller.isSyncing, isFalse); expect(controller.error, isNull); expect(controller.isCheckingHealth, isFalse); }); test('addRelay - success', () { final url = 'wss://relay.example.com'; final result = controller.addRelay(url); expect(result, isTrue); expect(controller.relays.length, equals(1)); expect(controller.relays[0].url, equals(url)); expect(controller.error, isNull); }); test('addRelay - invalid URL format', () { final result = controller.addRelay('invalid-url'); expect(result, isFalse); expect(controller.relays, isEmpty); expect(controller.error, isNotNull); expect(controller.error, contains('Invalid relay URL')); }); test('addRelay - duplicate relay', () { final url = 'wss://relay.example.com'; controller.addRelay(url); final result = controller.addRelay(url); expect(result, isTrue); // Still returns true, but doesn't add duplicate expect(controller.relays.length, equals(1)); }); test('removeRelay - success', () { final url = 'wss://relay.example.com'; controller.addRelay(url); expect(controller.relays.length, equals(1)); controller.removeRelay(url); expect(controller.relays, isEmpty); expect(controller.error, isNull); }); test('removeRelay - non-existent relay', () { controller.removeRelay('wss://nonexistent.com'); expect(controller.relays, isEmpty); expect(controller.error, isNull); }); test('clearError - clears error message', () { controller.addRelay('invalid-url'); expect(controller.error, isNotNull); controller.clearError(); expect(controller.error, isNull); }); test('checkRelayHealth - attempts to connect to relays', () async { // Add a relay (but don't connect to real relay in tests) controller.addRelay('wss://relay.example.com'); expect(controller.relays.length, equals(1)); expect(controller.relays[0].isConnected, isFalse); // Health check will attempt to connect (will fail in test environment, but that's OK) // The method should complete without throwing - it handles connection failures gracefully // Use runZoned to catch any unhandled exceptions that might escape await runZonedGuarded( () async { await controller.checkRelayHealth(); }, (error, stack) { // Swallow any unhandled errors - connection failures are expected }, ); // Verify the health check completed expect(controller.isCheckingHealth, isFalse); // Relay should still be in the list (even if disconnected) expect(controller.relays.length, equals(1)); }); test('triggerManualSync - without sync engine', () async { final controllerWithoutSync = RelayManagementController( nostrService: nostrService, syncEngine: null, ); final result = await controllerWithoutSync.triggerManualSync(); expect(result, isFalse); expect(controllerWithoutSync.error, isNotNull); expect( controllerWithoutSync.error, contains('Sync engine not configured')); controllerWithoutSync.dispose(); }); }); }