mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-11-10 19:27:01 +01:00
Some checks are pending
release-apk / release (APK) (push) Waiting to run
Firebase becomes the primary backend when this device has been set up for it;
GitHub stays as a mirror so a device that has not moved yet still converges.
Not being set up is a normal state, not an error -- sync runs over GitHub
exactly as before, which is also the rollback.
This app hand-rolled its own pull-merge-push instead of calling syncLog, so it
had no revision tracking at all: every sync downloaded all four peer note logs
-- roughly 150 KB each -- whether or not anything had changed. It now reads one
small revision map first and skips any peer whose hash is unchanged, and
suppresses its own push when its log is unchanged. That is the single largest
traffic saving in the fleet and what keeps this inside the free tier.
sync() now takes a RemoteStore rather than a GitHubClient, so choosing a
backend is a change at the call site and nothing more.
runSync() is the one place that decides how a sync runs. The capture screen
syncs on a timer and on demand and the settings screen on a button; each used
to build its own client, so a backend change meant editing four call sites and
hoping none was missed.
Credentials are split by what is safe to publish, because this repo is public:
the Web API key and database URL ship in source (both already ship inside the
APK, and the security rules protect the data), while the account email and
password are entered per device and live in the OS keystore.
247 tests at 100% line coverage. Two assertions changed from `contains('PUT')`
to `isNotEmpty`: those tests sync twice, and the second push is now correctly
suppressed as a no-op -- that suppression is the point.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJ9sxEw3ymzXNjL7SHNpJc
55 lines
1.6 KiB
Dart
55 lines
1.6 KiB
Dart
/// Tests for the file-backed revision cache.
|
|
///
|
|
/// The cache is what keeps a sync tick from re-downloading every peer's whole
|
|
/// log when nothing has changed, so "does it actually survive a restart" is
|
|
/// the behaviour worth pinning.
|
|
@TestOn('vm')
|
|
library;
|
|
|
|
import 'dart:io';
|
|
|
|
import 'package:crdt_sync/crdt_sync.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:todo/sync/sync_state_factory_io.dart';
|
|
import 'package:path/path.dart' as p;
|
|
|
|
void main() {
|
|
late Directory dir;
|
|
|
|
setUp(() async {
|
|
dir = await Directory.systemTemp.createTemp('todo_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 everything.
|
|
await openSyncStateStoreIn(dir.path).save(
|
|
const SyncState(pushedRev: 'mine', peerRevs: {'phone': 'theirs'}),
|
|
);
|
|
|
|
final reloaded = await openSyncStateStoreIn(dir.path).load();
|
|
|
|
expect(reloaded.pushedRev, 'mine');
|
|
expect(reloaded.peerRevs, {'phone': '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('reports nothing remembered before the first sync', () async {
|
|
final state = await openSyncStateStoreIn(dir.path).load();
|
|
|
|
expect(state.pushedRev, isNull);
|
|
expect(state.peerRevs, isEmpty);
|
|
});
|
|
}
|