todo-app/test/wrapper_server_provisioning_test.dart
Krzysztof kuhy Rudnicki 10a546f6d6 Split nine oversized test files under the cap
Every split is by test-group/concern, with the shared setup moved to a
sibling helper the splits import. All 313 tests are preserved -- the count
is unchanged and `test('…')` names are byte-identical to the originals
(verified by diffing the sorted name lists).

  note_repository_test      677 -> 4 files + note_repository_helpers
  capture_screen_test       660 -> 4 files + capture_screen_harness
  note_editor_test          502 -> 3 files + note_editor_harness
  port_guard_test           405 -> 3 files + port_guard_fakes
  wrapper_server_test       402 -> 2 files + wrapper_server_harness
  settings_screen_test      391 -> 2 files + settings_screen_harness
  github_mirror_screen_test 365 -> 2 files + github_mirror_harness
  notes_list_screen_test    347 -> 2 files + notes_list_harness
  analytics_service_test    300 -> 2 files + analytics_helpers

Helpers are deliberately NOT named `*_test.dart`; the runner would collect
them and fail on the missing `main()`.

Three things did not survive a mechanical split and were handled explicitly:

  * wrapper_server's `enabledOrigin` closes over the `late root` that setUp
    assigns, so a top-level function cannot see it. It is now a
    WrapperHarness class with an install() that registers the setUp/tearDown
    pair -- the only structural change in this commit.
  * note_repository's splits each repeat `setUpAll(sqfliteFfiInit)`, and
    analytics' each repeat `@TestOn('vm')`. Omitting either fails quietly
    (uninitialised sqflite / wrong platform) rather than loudly.
  * capture_screen's `recordingMock` was declared mid-file between two
    tests, so it moved to the harness rather than being dropped.

Verified: 313 tests pass (same as before the refactor), 100% coverage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 16:42:13 +02:00

222 lines
7.5 KiB
Dart

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;
import 'package:todo/desktop/wrapper_server.dart';
import 'wrapper_server_harness.dart';
void main() {
final h = WrapperHarness()..install();
group('sync-account provisioning', () {
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('${h.base}$kSyncAccountPath'));
expect(response.statusCode, HttpStatus.notFound);
});
test('serves the account when enabled', () async {
final origin = await h.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 h.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 h.enabledOrigin({
'firebase.json': '{"apiKey":"x"}',
'password': 'pw',
});
final response = await http.get(Uri.parse('$origin$kSyncAccountPath'));
expect(response.statusCode, HttpStatus.notFound);
});
});
group('sync-credentials provisioning', () {
// Unlike /sync-account, this route needs no CRDT_SYNC_SERVE_ACCOUNT: a
// normal launch must self-provision with nothing to remember. [h.base]'s
// h.server was built with no credentials file at the default path, so this
// exercises the file-absent 404, not a disabled-route 404 -- there is no
// "disabled" state for this route anymore.
test(
'is 404 when no credentials file exists at the default path',
() async {
final response = await http.get(
Uri.parse('${h.base}$kSyncCredentialsPath'),
);
expect(response.statusCode, HttpStatus.notFound);
},
);
test('serves once, then 404s for the rest of the process', () async {
final credentialsDir = Directory(p.join(h.root.path, 'todo-config-once'))
..createSync(recursive: true);
final credentialsPath = p.join(credentialsDir.path, 'firebase_auth.json');
File(credentialsPath).writeAsStringSync(
jsonEncode({
'id_token': 'id',
'refresh_token': 'refresh',
'expires_at': '2026-01-01T00:00:00.000Z',
}),
);
final enabled = WrapperServer(
webRoot: p.join(h.root.path, 'web'),
backlogPath: p.join(h.root.path, 'todo', 'BACKLOG.md'),
logPath: p.join(h.root.path, 'state', 'todo_notes.json'),
todoCredentialsPath: credentialsPath,
);
await enabled.start(0);
addTearDown(enabled.stop);
final origin = 'http://localhost:${enabled.port}';
final first = await http.get(Uri.parse('$origin$kSyncCredentialsPath'));
final second = await http.get(Uri.parse('$origin$kSyncCredentialsPath'));
expect(first.statusCode, HttpStatus.ok);
expect(second.statusCode, HttpStatus.notFound);
});
test(
'serves the seeded credentials, with email from firebase.json',
() async {
final configDir = Directory(p.join(h.root.path, 'crdt-sync'))
..createSync(recursive: true);
File(
p.join(configDir.path, 'firebase.json'),
).writeAsStringSync('{"email":"seeded@example.com"}');
final credentialsDir = Directory(p.join(h.root.path, 'todo-config'))
..createSync(recursive: true);
final credentialsPath = p.join(
credentialsDir.path,
'firebase_auth.json',
);
File(credentialsPath).writeAsStringSync(
jsonEncode({
'id_token': 'id',
'refresh_token': 'refresh',
'expires_at': '2026-01-01T00:00:00.000Z',
}),
);
final enabled = WrapperServer(
webRoot: p.join(h.root.path, 'web'),
backlogPath: p.join(h.root.path, 'todo', 'BACKLOG.md'),
logPath: p.join(h.root.path, 'state', 'todo_notes.json'),
serveSyncAccount: true,
syncConfigDir: configDir.path,
todoCredentialsPath: credentialsPath,
);
await enabled.start(0);
addTearDown(enabled.stop);
final response = await http.get(
Uri.parse('http://localhost:${enabled.port}$kSyncCredentialsPath'),
);
final body = jsonDecode(response.body) as Map<String, dynamic>;
expect(response.statusCode, HttpStatus.ok);
expect(body['id_token'], 'id');
expect(body['refresh_token'], 'refresh');
expect(body['expires_at'], '2026-01-01T00:00:00.000Z');
expect(body['email'], 'seeded@example.com');
},
);
test(
'serves credentials with a null email when firebase.json is absent',
() async {
final credentialsDir = Directory(p.join(h.root.path, 'todo-config-2'))
..createSync(recursive: true);
final credentialsPath = p.join(
credentialsDir.path,
'firebase_auth.json',
);
File(credentialsPath).writeAsStringSync(
jsonEncode({
'id_token': 'id',
'refresh_token': 'refresh',
'expires_at': '2026-01-01T00:00:00.000Z',
}),
);
final enabled = WrapperServer(
webRoot: p.join(h.root.path, 'web'),
backlogPath: p.join(h.root.path, 'todo', 'BACKLOG.md'),
logPath: p.join(h.root.path, 'state', 'todo_notes.json'),
serveSyncAccount: true,
syncConfigDir: p.join(h.root.path, 'crdt-sync-absent'),
todoCredentialsPath: credentialsPath,
);
await enabled.start(0);
addTearDown(enabled.stop);
final response = await http.get(
Uri.parse('http://localhost:${enabled.port}$kSyncCredentialsPath'),
);
final body = jsonDecode(response.body) as Map<String, dynamic>;
expect(response.statusCode, HttpStatus.ok);
expect(body['email'], isNull);
},
);
test('is 404 when the credentials file is absent', () async {
final origin = await h.enabledOrigin({});
final response = await http.get(
Uri.parse('$origin$kSyncCredentialsPath'),
);
expect(response.statusCode, HttpStatus.notFound);
});
test('is 404 when the credentials file is not valid JSON', () async {
final origin = await h.enabledOrigin({}, credentialsJson: 'not json');
final response = await http.get(
Uri.parse('$origin$kSyncCredentialsPath'),
);
expect(response.statusCode, HttpStatus.notFound);
});
test(
'is 404 when the credentials file is missing required fields',
() async {
final origin = await h.enabledOrigin(
{},
credentialsJson: jsonEncode({'id_token': 'id'}),
);
final response = await http.get(
Uri.parse('$origin$kSyncCredentialsPath'),
);
expect(response.statusCode, HttpStatus.notFound);
},
);
});
}