import 'package:flutter_test/flutter_test.dart'; import 'package:http/testing.dart'; import 'package:http/http.dart' as http; import 'package:melo/services/logger_service.dart'; import 'package:melo/settings/app_settings.dart'; import 'package:shared_preferences/shared_preferences.dart'; /// Übertragung eines Befunds aus dem Security-Audit der alten Melo-App /// (21.08.2026): Dort stand die Diagnose-Einstellung in einer statischen /// Variablen mit Default `true`, die beim Start nie aus den Einstellungen /// gefüllt wurde — ein Opt-out überlebte keinen Neustart, während der /// Schalter weiterhin "aus" anzeigte. /// /// Hier wird festgehalten, dass genau das nicht passieren kann. void main() { TestWidgetsFlutterBinding.ensureInitialized(); // Opt-in ist der Ruhezustand: nach jedem Test wieder aus. tearDown(() => logger.sendeDiagnosedaten = false); group('AppSettings.init', () { test('übernimmt ein gespeichertes Opt-out und reicht es an den Logger', () async { SharedPreferences.setMockInitialValues({LoggerService.diagnoseKey: false}); logger.sendeDiagnosedaten = true; // Zustand nach frischem App-Start final settings = AppSettings(); await settings.init(); expect(settings.sendeDiagnosedaten, isFalse); expect(logger.sendeDiagnosedaten, isFalse, reason: 'Anzeige und Sendeverhalten dürfen nicht auseinanderlaufen'); }); test('ohne gespeicherte Entscheidung bleibt der Versand AUS (Opt-in)', () async { SharedPreferences.setMockInitialValues({}); logger.sendeDiagnosedaten = true; // absichtlich "falsch" vorbelegt final settings = AppSettings(); await settings.init(); expect(settings.sendeDiagnosedaten, isFalse, reason: 'Ohne ausdrueckliche Zustimmung darf nichts gesendet werden'); expect(logger.sendeDiagnosedaten, isFalse); }); test('eine gespeicherte Zustimmung wird respektiert', () async { SharedPreferences.setMockInitialValues({LoggerService.diagnoseKey: true}); final settings = AppSettings(); await settings.init(); expect(settings.sendeDiagnosedaten, isTrue); expect(logger.sendeDiagnosedaten, isTrue); }); test('eine erteilte Zustimmung überlebt den Neustart', () async { SharedPreferences.setMockInitialValues({}); final ersteSitzung = AppSettings(); await ersteSitzung.init(); await ersteSitzung.setSendeDiagnosedaten(true); // Neustart: neue Instanzen, gleiche Ablage. logger.sendeDiagnosedaten = false; final zweiteSitzung = AppSettings(); await zweiteSitzung.init(); expect(zweiteSitzung.sendeDiagnosedaten, isTrue); expect(logger.sendeDiagnosedaten, isTrue); }); test('ein Widerruf überlebt den Neustart', () async { SharedPreferences.setMockInitialValues({LoggerService.diagnoseKey: true}); final ersteSitzung = AppSettings(); await ersteSitzung.init(); await ersteSitzung.setSendeDiagnosedaten(false); logger.sendeDiagnosedaten = true; final zweiteSitzung = AppSettings(); await zweiteSitzung.init(); expect(zweiteSitzung.sendeDiagnosedaten, isFalse); expect(logger.sendeDiagnosedaten, isFalse); }); }); group('AppSettings.setSendeDiagnosedaten', () { test('schaltet den Logger sofort um und speichert', () async { SharedPreferences.setMockInitialValues({}); final settings = AppSettings(); await settings.init(); // Ausgangspunkt ist jetzt AUS (Opt-in), also zuerst zustimmen. expect(settings.sendeDiagnosedaten, isFalse); await settings.setSendeDiagnosedaten(true); expect(logger.sendeDiagnosedaten, isTrue); final prefs = await SharedPreferences.getInstance(); expect(prefs.getBool(LoggerService.diagnoseKey), isTrue); await settings.setSendeDiagnosedaten(false); expect(logger.sendeDiagnosedaten, isFalse); expect(prefs.getBool(LoggerService.diagnoseKey), isFalse); }); test('benachrichtigt Zuhörer', () async { SharedPreferences.setMockInitialValues({}); final settings = AppSettings(); await settings.init(); var rufe = 0; settings.addListener(() => rufe++); await settings.setSendeDiagnosedaten(true); await settings.setSendeDiagnosedaten(true); // unverändert → kein Ruf expect(rufe, 1); }); }); group('LoggerService – Versand-Sperre', () { /// Der Kern des Opt-outs: bei Widerspruch darf KEIN Request das Gerät /// verlassen. Ohne diesen Test wäre genau die Sperre ungeprüft. test('sendet nichts, wenn der Nutzer widersprochen hat', () async { SharedPreferences.setMockInitialValues({LoggerService.diagnoseKey: false}); var requests = 0; final dienst = LoggerService( client: MockClient((_) async { requests++; return http.Response('{}', 200); }), ); await dienst.init(); await dienst.error('Absturz mit Details'); await dienst.flush(); expect(requests, 0, reason: 'Bei Opt-out darf nichts hochgeladen werden'); expect(dienst.logCount, 0, reason: 'Puffer darf nicht unbegrenzt wachsen'); }); test('sendet, wenn der Nutzer zugestimmt hat', () async { SharedPreferences.setMockInitialValues({LoggerService.diagnoseKey: true}); var requests = 0; final dienst = LoggerService( client: MockClient((_) async { requests++; return http.Response('{}', 200); }), ); await dienst.init(); await dienst.error('Absturz mit Details'); expect(requests, greaterThan(0)); }); test('das Umschalten auf aus stoppt den Versand sofort', () async { SharedPreferences.setMockInitialValues({LoggerService.diagnoseKey: true}); var requests = 0; final dienst = LoggerService( client: MockClient((_) async { requests++; return http.Response('{}', 200); }), ); await dienst.init(); dienst.sendeDiagnosedaten = false; await dienst.error('Absturz nach dem Abschalten'); await dienst.flush(); expect(requests, 0); }); }); group('LoggerService.init', () { test('liest das Opt-out selbst, ohne auf AppSettings zu warten', () async { // main() initialisiert den Logger VOR den Einstellungen — auch dieses // Zeitfenster darf nichts senden. SharedPreferences.setMockInitialValues({LoggerService.diagnoseKey: false}); final eigener = LoggerService(); await eigener.init(); expect(eigener.sendeDiagnosedaten, isFalse); }); test('ein frischer Logger sendet ohne gespeicherte Zustimmung nicht', () async { SharedPreferences.setMockInitialValues({}); var requests = 0; final dienst = LoggerService( client: MockClient((_) async { requests++; return http.Response('{}', 200); }), ); await dienst.init(); await dienst.error('Absturz beim allerersten Start'); await dienst.flush(); expect(dienst.sendeDiagnosedaten, isFalse); expect(requests, 0, reason: 'Erststart ohne Zustimmung darf nichts hochladen'); }); }); }