diet-guard/app/test/fake_secure_storage.dart
Krzysztof kuhy Rudnicki a82047502f Add Flutter half of cross-device sync (Milestone 3)
Ports github_client.dart and sync_settings.dart from ~/todo (PAT-paste
instead of OAuth device flow), and writes a new (non-CRDT) sync_merge.dart
and sync_service.dart matching diet_guard's Python _sync_merge.py/_sync.py
algorithm exactly. Adds a settings screen for the PAT plus manual "Sync
now", and wires lifecycle-triggered auto-sync (launch + resumed/paused)
into the main logging screen, silent on failure per plan decision 4.

Also adds Linux desktop platform scaffolding so this and future UI changes
can be visually verified without a connected phone.

Verified end-to-end against the real kuhyx/diet-guard-sync GitHub API on a
Linux desktop build: Test connection and Sync now both round-trip to GitHub
and surface real auth errors correctly via SnackBar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RH2BHCKbDTiYJUMG3rb9nq
2026-06-22 22:42:27 +02:00

53 lines
1.6 KiB
Dart

import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
/// In-memory fake for the `flutter_secure_storage` platform channel so tests
/// never touch the real OS keystore. Install it from a test (or `setUp`) and
/// it auto-removes on tear down.
///
/// Pass [throwing] to simulate a host with no secret service: every call
/// raises a [PlatformException], which exercises the plaintext-fallback
/// paths in [SyncSettings].
void installFakeSecureStorage({
Map<String, String>? initial,
bool throwing = false,
}) {
const channel = MethodChannel(
'plugins.it_nomads.com/flutter_secure_storage',
);
final store = <String, String>{...?initial};
final messenger =
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
messenger.setMockMethodCallHandler(channel, (call) async {
if (throwing) {
throw PlatformException(code: 'unavailable');
}
final args = (call.arguments as Map?) ?? const <Object?, Object?>{};
final key = args['key'] as String?;
switch (call.method) {
case 'read':
return store[key];
case 'write':
store[key!] = args['value'] as String;
return null;
case 'delete':
store.remove(key);
return null;
case 'containsKey':
return store.containsKey(key);
case 'readAll':
return Map<String, String>.from(store);
case 'deleteAll':
store.clear();
return null;
default:
return null;
}
});
addTearDown(() {
messenger.setMockMethodCallHandler(channel, null);
});
}