import 'dart:convert'; import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; void main() { final manifest = File('android/app/src/main/AndroidManifest.xml'); final matrix = File('tool/android_permissions.json'); test('every Android manifest permission is present in the policy matrix', () { final declared = RegExp(r' match.group(1)!) .toSet(); final entries = (jsonDecode(matrix.readAsStringSync()) as List) .cast>(); final documented = entries .map((entry) => entry['permission'] as String) .toSet(); expect(entries.length, documented.length); expect(documented, declared); for (final entry in entries) { expect((entry['owner'] as String).trim(), isNotEmpty); expect((entry['feature'] as String).trim(), isNotEmpty); expect((entry['trigger'] as String).trim(), isNotEmpty); } }); test('runtime-sensitive permissions require a user action', () { final entries = (jsonDecode(matrix.readAsStringSync()) as List) .cast>(); final byPermission = { for (final entry in entries) entry['permission'] as String: entry, }; final sensitive = { 'android.permission.REQUEST_INSTALL_PACKAGES', 'android.permission.CAMERA', 'android.permission.ACCESS_FINE_LOCATION', 'android.permission.ACCESS_COARSE_LOCATION', 'android.permission.RECORD_AUDIO', 'android.permission.BLUETOOTH_CONNECT', 'android.permission.BLUETOOTH_ADVERTISE', 'android.permission.BLUETOOTH_SCAN', 'android.permission.POST_NOTIFICATIONS', 'android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS', 'android.permission.USE_FULL_SCREEN_INTENT', 'android.permission.READ_EXTERNAL_STORAGE', 'android.permission.READ_MEDIA_IMAGES', 'android.permission.READ_MEDIA_VIDEO', 'android.permission.READ_MEDIA_VISUAL_USER_SELECTED', 'android.permission.READ_CONTACTS', }; for (final permission in sensitive) { expect(byPermission[permission]?['trigger'], 'user_action'); } }); test('startup paths do not request notification or full-screen access', () { final pushSource = File( 'lib/core/push/push_service.dart', ).readAsStringSync(); final mainSource = File('lib/main.dart').readAsStringSync(); final initBody = RegExp( r'Future init\([\s\S]*?Future requestPermissionFromUser', ).firstMatch(pushSource)?.group(0); expect(initBody, isNotNull); expect(initBody, isNot(contains('requestPermission()'))); expect(mainSource, isNot(contains('_ensureFullScreenIntentPermission'))); }); }