test: AuthLog-Regressionstests (wirft nie, 500-Zeilen-Kappung)

AuthLog.write ist der einzige Aufruf im catch-Pfad des Soft-Logout-Guards;
ein Throw dort würde im SDK zu logout()+clear() eskalieren. 4 neue Tests
sichern genau das ab (kaputte Plattform, leeres Log, Zeitstempel, Kappung).
path_provider_platform_interface als dev_dependency (war schon transitiv).
Alle 14 Tests grün auf dem Pi, flutter analyze unverändert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Bernd Steckmeister
2026-07-03 22:51:35 +02:00
parent eebf8ae7dd
commit e0ac5cb9fd
5 changed files with 137 additions and 7 deletions
+82
View File
@@ -0,0 +1,82 @@
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
import 'package:pyramid/core/auth_log.dart';
/// Plattform kaputt/nicht verfügbar (z. B. sehr früher App-Start, exotischer
/// Fehler): genau der Fall, in dem AuthLog.write NIEMALS werfen darf der
/// Soft-Logout-Guard ruft es in seinem catch-Pfad auf, und ein Throw dort
/// würde im SDK zu logout()+clear() eskalieren (der Uta-Bug).
class _BrokenPathProvider extends PathProviderPlatform {
@override
Future<String?> getApplicationSupportPath() async =>
throw StateError('Plattform nicht verfügbar');
}
class _TempPathProvider extends PathProviderPlatform {
_TempPathProvider(this.path);
final String path;
@override
Future<String?> getApplicationSupportPath() async => path;
}
void main() {
// WICHTIG: Reihenfolge der Tests ist Absicht. AuthLog cached sein File-Handle
// statisch; der Kaputt-Plattform-Test muss laufen, BEVOR ein Test mit
// funktionierender Plattform das Handle in den Cache legt.
late Directory tempDir;
setUpAll(() {
tempDir = Directory.systemTemp.createTempSync('pyramid_auth_log_test');
});
tearDownAll(() {
tempDir.deleteSync(recursive: true);
});
test(
'write/read werfen NIE, auch wenn die Plattform fehlt '
'(CLAUDE.md: Fehlerpfade dürfen nie in einem stillen Logout enden)',
() async {
PathProviderPlatform.instance = _BrokenPathProvider();
// Darf normal zurückkehren, obwohl getApplicationSupportPath wirft.
await AuthLog.write('darf nicht werfen');
expect(await AuthLog.read(), startsWith('Log konnte nicht gelesen werden'));
});
test('read ohne bisherige Einträge liefert Platzhalter statt Fehler',
() async {
PathProviderPlatform.instance = _TempPathProvider(tempDir.path);
expect(await AuthLog.read(), '(noch keine Einträge)');
});
test('write hängt Zeile mit ISO-Zeitstempel an, read liefert sie zurück',
() async {
PathProviderPlatform.instance = _TempPathProvider(tempDir.path);
await AuthLog.write('Hallo Log');
final content = await AuthLog.read();
expect(content, contains('Hallo Log'));
expect(
RegExp(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}').hasMatch(content),
isTrue,
reason: 'jede Zeile beginnt mit einem ISO-8601-Zeitstempel',
);
});
test(
'Log wird auf 500 Zeilen gekappt (älteste fliegen raus), '
'damit es nie unbegrenzt wächst', () async {
PathProviderPlatform.instance = _TempPathProvider(tempDir.path);
for (var i = 0; i < 520; i++) {
await AuthLog.write('Zeile $i');
}
final lines = (await AuthLog.read()).trimRight().split('\n');
expect(lines.length, 500);
expect(lines.last, contains('Zeile 519'),
reason: 'neueste Einträge müssen überleben');
expect((await AuthLog.read()).contains('Hallo Log'), isFalse,
reason: 'ältester Eintrag (aus dem Test davor) muss weggekappt sein');
});
}