Files
rippr/test/crash_reporter_test.dart
uhryniuk be50448917 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.
2026-08-17 19:32:54 -05:00

115 lines
3.9 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:rippr/src/crash/crash_reporter.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
/// V3-12: a crash reporter in a location app is a privacy surface. Every test here
/// exists to make a specific claim provable rather than assumed -- "the scrubber strips
/// coordinates" and "disabled means never initialised" are both named directly in the
/// ticket's Tests section.
void main() {
group('shouldInitializeCrashReporting', () {
test('requires enabled, release, and a configured dsn all at once', () {
expect(
shouldInitializeCrashReporting(enabled: true, isDebug: false, dsn: 'https://x'),
isTrue,
);
});
test('the user toggle being off means the client is never initialised', () {
expect(
shouldInitializeCrashReporting(enabled: false, isDebug: false, dsn: 'https://x'),
isFalse,
);
});
test('debug builds send nothing, even if enabled', () {
expect(
shouldInitializeCrashReporting(enabled: true, isDebug: true, dsn: 'https://x'),
isFalse,
);
});
test('no dsn configured means no client, regardless of the toggle', () {
expect(
shouldInitializeCrashReporting(enabled: true, isDebug: false, dsn: ''),
isFalse,
);
});
});
group('scrubExtra', () {
test('strips every coordinate-shaped key', () {
final scrubbed = scrubExtra({
'latitude': 51.0447,
'longitude': -114.0719,
'lat': 51.0,
'lon': -114.0,
'startLat': 51.0,
'gps_coords': '51.0,-114.0',
'altitude': 1045.0,
'safe_field': 'kept',
})!;
expect(scrubbed.containsKey('safe_field'), isTrue);
expect(scrubbed, hasLength(1),
reason: 'no field describing a location may survive: $scrubbed');
});
test('strips device and trip identity', () {
final scrubbed = scrubExtra({
'device_id': 'abc-123',
'deviceId': 'abc-123',
'trip_id': 42,
'tripId': 42,
'screen': 'record',
})!;
expect(scrubbed, {'screen': 'record'});
});
test('matching is case-insensitive and matches substrings', () {
final scrubbed = scrubExtra({'Latitude': 1.0, 'nested.longitude': 2.0})!;
expect(scrubbed, isEmpty);
});
test('null in, null out', () {
expect(scrubExtra(null), isNull);
});
test('an already-clean map is returned unchanged in content', () {
final scrubbed = scrubExtra({'screen': 'settings', 'action': 'tap'})!;
expect(scrubbed, {'screen': 'settings', 'action': 'tap'});
});
});
group('event scrubbing end to end', () {
test('a representative event with coordinates in extra and a breadcrumb comes '
'out clean', () {
final event = SentryEvent(
// ignore: deprecated_member_use
extra: {'latitude': 51.0447, 'longitude': -114.0719, 'screen': 'record'},
breadcrumbs: [
Breadcrumb(
message: 'fix received',
data: {'lat': 51.0, 'lon': -114.0, 'accuracy': 5.0},
),
],
);
// Exercises the same path SentryOptions.beforeSend is configured with in
// maybeInitCrashReporting, without needing a real Sentry client.
// ignore: deprecated_member_use
final scrubbedExtra = scrubExtra(event.extra)!;
final scrubbedBreadcrumbData = scrubExtra(event.breadcrumbs!.single.data)!;
expect(scrubbedExtra.containsKey('latitude'), isFalse);
expect(scrubbedExtra.containsKey('longitude'), isFalse);
expect(scrubbedExtra['screen'], 'record');
expect(scrubbedBreadcrumbData.containsKey('lat'), isFalse);
expect(scrubbedBreadcrumbData.containsKey('lon'), isFalse);
expect(scrubbedBreadcrumbData['accuracy'], 5.0,
reason: 'accuracy is not a coordinate and carries no location on its own');
});
});
}