diet-guard/app/test/services/sync_state_factory_test.dart
Krzysztof kuhy Rudnicki c77e7d9acf Split the last four Dart lib files; file-length gate now reports zero
Final batch of the 250-line sweep. `check_file_length.sh --all` exits 0: every
file in the repo is at or under 250 lines.

  github_mirror_screen.dart  391 -> 205  (+ actions, client_id_dialog parts)
  foodbank_service.dart      314 -> 209  (+ foodbank_service_manual part)
  settings_screen.dart       286 -> 237  (+ settings_kcal_goal part)
  firebase_backend.dart      282 -> 100  (+ firebase_client.dart)

Three of the four are `part` files rather than separate libraries, and that is
forced rather than stylistic: the moved code drives private state
(`_busy`, `_status`, the text controllers) and calls private helpers
(`_readBank`, `_normalize`), none of which are visible across a library
boundary in Dart. Widening them to public just to split a file would leak
internals the rest of the app has no business calling.

Within those parts, mixins rather than extensions, for a concrete reason: an
extension is not a subclass of `State`, so calling `setState` from one is an
`invalid_use_of_protected_member`. Fields the State must own (the controllers
it creates and disposes) stay declared there and are reached through abstract
getters.

`firebase_client.dart` is a real library split -- opening an authenticated
client has no private coupling to the keystore-backed account record, only to
its public API. The one exception, dropping the account marker without setting
the opt-out flag, became `forgetAccountMarker()` rather than exposing the
secure store.

Verified: flutter test 555 (unchanged), flutter analyze clean, 707 Python
tests / 100% branch coverage, check_patch_targets 0, pre-commit clean,
check_file_length.sh --all exit 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RA9ynjqXPnexRQnR5oAuCC
2026-08-15 21:07:24 +02:00

90 lines
2.7 KiB
Dart

/// Tests for the revision cache and the backend choice.
///
/// The cache is what keeps a sync tick from re-downloading every peer's whole
/// food log when nothing changed, so "does it survive a restart" and "does a
/// dead platform channel take sync down with it" are the behaviours worth
/// pinning.
@TestOn('vm')
library;
import 'dart:io';
import 'package:crdt_sync/crdt_sync.dart';
import 'package:diet_guard_app/services/firebase_client.dart';
import 'package:diet_guard_app/services/sync_state_factory_io.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:path/path.dart' as p;
/// A do-nothing [RemoteStore], standing in for the GitHub client.
class _StubRemote implements RemoteStore {
@override
Future<List<String>> listDirectory(String path) async => [];
@override
Future<String?> getFileText(String path) async => null;
@override
Future<void> putFileText(
String path,
String text, {
required String message,
}) async {}
@override
Future<void> deleteFile(String path, {String message = ''}) async {}
@override
Future<bool> canAccessRemote() async => true;
@override
void close() {}
}
void main() {
late Directory dir;
setUp(() async {
dir = await Directory.systemTemp.createTemp('dg_state_');
});
tearDown(() async {
if (dir.existsSync()) await dir.delete(recursive: true);
});
test('persists revisions across a restart', () async {
// A fresh store instance stands in for the next app launch: an in-memory
// cache would forget every peer and re-download all of them.
await openSyncStateStoreIn(dir.path).save(
const SyncState(pushedRev: 'mine', peerRevs: {'pc': 'theirs'}),
);
final reloaded = await openSyncStateStoreIn(dir.path).load();
expect(reloaded.pushedRev, 'mine');
expect(reloaded.peerRevs, {'pc': 'theirs'});
});
test('writes beside the log it describes', () async {
await openSyncStateStoreIn(dir.path).save(const SyncState(pushedRev: 'x'));
expect(File(p.join(dir.path, kSyncStateFileName)).existsSync(), isTrue);
});
test('degrades to memory when the platform channel is unavailable', () async {
// No binding under `flutter test`. Losing the cache costs one tick of
// extra traffic; failing would take sync down entirely.
final store = await openSyncStateStore();
expect(store, isA<SyncStateStore>());
expect((await store.load()).pushedRev, isNull);
});
test('stays on GitHub when Firebase is not set up', () async {
// The pre-migration path, and the rollback. Reaching the keystore without
// a binding must read as "not configured", not as an error.
final github = _StubRemote();
expect(await syncBackend(github), same(github));
});
}