Make the sync-account route testable, and cover every branch
Some checks are pending
release-apk / release (APK) (push) Waiting to run

The route read its own configuration from Platform.environment and $HOME, so
the only branch a test could reach was "disabled" -- the serving path, the
missing-config path and the malformed-email path were all unreachable without
mutating the process environment.

Both are now constructor parameters defaulting to the environment, so
production behaviour is unchanged and tests drive all four outcomes: 404 when
disabled, 200 with the account when enabled, 404 when ~/.config/crdt-sync/ is
incomplete, and 404 when firebase.json has no usable email. The password's
trailing newline is stripped, which the test pins.

256 tests, analyzer clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0177xwzcpfpWG2qca8Mr39q9
This commit is contained in:
Krzysztof Rudnicki 2026-08-09 21:49:16 +02:00
parent 3c6c071513
commit be376fd240
2 changed files with 80 additions and 6 deletions

View File

@ -28,7 +28,20 @@ class WrapperServer {
required this.webRoot,
required this.backlogPath,
required this.logPath,
});
bool? serveSyncAccount,
String? syncConfigDir,
}) : serveSyncAccount =
serveSyncAccount ??
(Platform.environment[kSyncAccountEnvVar] ?? '').isNotEmpty,
syncConfigDir =
syncConfigDir ??
p.join(Platform.environment['HOME'] ?? '', '.config', 'crdt-sync');
/// Whether the sync-account route answers; off unless explicitly enabled.
final bool serveSyncAccount;
/// Directory holding `firebase.json` and `password`.
final String syncConfigDir;
/// Directory holding the built Flutter web assets.
final String webRoot;
@ -98,14 +111,12 @@ class WrapperServer {
/// daemons already use, so there is one source of truth per machine rather
/// than a per-app copy.
Future<void> _syncAccount(HttpRequest request) async {
if ((Platform.environment[kSyncAccountEnvVar] ?? '').isEmpty) {
if (!serveSyncAccount) {
request.response.statusCode = HttpStatus.notFound;
return;
}
final home = Platform.environment['HOME'] ?? '';
final configDir = p.join(home, '.config', 'crdt-sync');
final configFile = File(p.join(configDir, 'firebase.json'));
final passwordFile = File(p.join(configDir, 'password'));
final configFile = File(p.join(syncConfigDir, 'firebase.json'));
final passwordFile = File(p.join(syncConfigDir, 'password'));
if (!configFile.existsSync() || !passwordFile.existsSync()) {
stderr.writeln(
'Sync account requested but ~/.config/crdt-sync/ is incomplete — '

View File

@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:io';
import 'package:crdt_sync/crdt_sync.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as p;
@ -149,4 +150,66 @@ void main() {
expect(kind('a.bin'), 'application/octet-stream');
expect(kind('a.unknown'), 'application/octet-stream');
});
group('sync-account provisioning', () {
/// Starts a second server with provisioning enabled against [files].
Future<String> enabledOrigin(Map<String, String> files) async {
final configDir = Directory(p.join(root.path, 'crdt-sync'))
..createSync(recursive: true);
files.forEach((name, contents) {
File(p.join(configDir.path, name)).writeAsStringSync(contents);
});
final enabled = WrapperServer(
webRoot: p.join(root.path, 'web'),
backlogPath: p.join(root.path, 'todo', 'BACKLOG.md'),
logPath: p.join(root.path, 'state', 'todo_notes.json'),
serveSyncAccount: true,
syncConfigDir: configDir.path,
);
await enabled.start(0);
addTearDown(enabled.stop);
return 'http://localhost:${enabled.port}';
}
test('is 404 when not enabled', () async {
// The default, and the whole security posture: a credential route must
// not be reachable just because the app is running.
final response = await http.get(Uri.parse('$base$kSyncAccountPath'));
expect(response.statusCode, HttpStatus.notFound);
});
test('serves the account when enabled', () async {
final origin = await enabledOrigin({
'firebase.json': '{"email":"a@b.c"}',
'password': 'pw\n',
});
final response = await http.get(Uri.parse('$origin$kSyncAccountPath'));
final account = FirebaseAccount.tryParse(response.body);
expect(response.statusCode, HttpStatus.ok);
expect(account?.email, 'a@b.c');
expect(account?.password, 'pw');
});
test('is 404 when the config files are absent', () async {
final origin = await enabledOrigin({});
final response = await http.get(Uri.parse('$origin$kSyncAccountPath'));
expect(response.statusCode, HttpStatus.notFound);
});
test('is 404 when firebase.json has no usable email', () async {
final origin = await enabledOrigin({
'firebase.json': '{"apiKey":"x"}',
'password': 'pw',
});
final response = await http.get(Uri.parse('$origin$kSyncAccountPath'));
expect(response.statusCode, HttpStatus.notFound);
});
});
}