V3-12: crash reporting (code only)

shouldInitializeCrashReporting() and scrubExtra() are pure, fully unit-tested decision/scrubbing functions wired into sentry_flutter via beforeSend/beforeBreadcrumb. Config.crashReportingEnabled defaults off; the DSN is a compile-time --dart-define, not a preference. RecordingEngine gained an injected onUnexpectedStop callback, firing once when restoreAfterProcessDeath finds a trip still 'recording' at launch -- the process died without a user stop.

Marked partially done: the ticket's real acceptance criteria (a forced crash landing in a real Sentry project from a release build, inspecting a real payload) need a Sentry account and a release build this environment can't produce.
This commit is contained in:
2026-08-17 19:32:54 -05:00
parent 0e605ef174
commit be50448917
14 changed files with 443 additions and 9 deletions

View File

@@ -336,6 +336,50 @@ void main() {
reason: 'the dead time is a real gap and must render as one');
});
test('a recording trip found at restart reports an unexpected stop (V3-12)',
() async {
await engine.start();
await ride(5);
await engine.dispose();
String? reportedReason;
final revived = RecordingEngine(
repository: repo,
locationSource: source,
clock: () => fakeNow,
onUnexpectedStop: (reason) => reportedReason = reason,
);
addTearDown(revived.dispose);
fakeNow = 20000;
await revived.restoreAfterProcessDeath();
expect(reportedReason, isNotNull,
reason: 'the recording stopped without anyone choosing that -- the exact '
'failure V3-12 exists to surface');
});
test('a cleanly-completed trip reports nothing at restart', () async {
await engine.start();
await ride(5);
await engine.stop();
await engine.dispose();
var called = false;
final revived = RecordingEngine(
repository: repo,
locationSource: source,
clock: () => fakeNow,
onUnexpectedStop: (_) => called = true,
);
addTearDown(revived.dispose);
await revived.restoreAfterProcessDeath();
expect(called, isFalse,
reason: 'a rider who pressed Stop is not a crash and must not be reported');
});
test('the dead time is never measured as distance', () async {
// The bug this guards is in the native app: after a crash the open segment is
// adopted rather than closed, so computeSummary -- the authoritative pass at trip