TF-4136 Add config Sentry
This commit is contained in:
+6
-1
@@ -60,6 +60,8 @@ export 'utils/preview_eml_file_utils.dart';
|
||||
export 'utils/logger/log_tracking.dart';
|
||||
export 'utils/html/html_utils.dart';
|
||||
export 'utils/web_link_generator.dart';
|
||||
export 'utils/sentry/sentry_manager.dart';
|
||||
export 'utils/config/env_loader.dart';
|
||||
|
||||
// Views
|
||||
export 'presentation/views/text/slogan_builder.dart';
|
||||
@@ -138,4 +140,7 @@ export 'data/model/source_type/data_source_type.dart';
|
||||
export 'data/model/query/query_parameter.dart';
|
||||
|
||||
// Action
|
||||
export 'presentation/action/action_callback_define.dart';
|
||||
export 'presentation/action/action_callback_define.dart';
|
||||
|
||||
// Library
|
||||
export 'package:package_info_plus/package_info_plus.dart';
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'package:universal_html/html.dart' as html;
|
||||
import 'package:core/utils/build_utils.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:core/utils/sentry/sentry_manager.dart';
|
||||
import 'package:universal_html/html.dart' as html;
|
||||
|
||||
/// ANSI escape colors (Web only)
|
||||
const ansiReset = '\x1B[0m';
|
||||
@@ -73,6 +72,15 @@ void _internalLog(
|
||||
// ignore: avoid_print
|
||||
print('$appLogName $formattedMessage');
|
||||
}
|
||||
|
||||
if (_shouldReportToSentry(level)) {
|
||||
SentryManager.instance.captureException(
|
||||
exception ?? rawMessage,
|
||||
stackTrace: stackTrace,
|
||||
message: rawMessage,
|
||||
extras: extras,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _buildRawMessage(
|
||||
@@ -114,6 +122,10 @@ void _printWebConsole(Level level, String value) {
|
||||
}
|
||||
}
|
||||
|
||||
bool _shouldReportToSentry(Level level) {
|
||||
return level == Level.error || level == Level.critical;
|
||||
}
|
||||
|
||||
void logError(
|
||||
String? message, {
|
||||
Object? exception,
|
||||
@@ -205,18 +217,4 @@ enum Level {
|
||||
info,
|
||||
debug,
|
||||
trace,
|
||||
}
|
||||
|
||||
// Take from: https://flutter.dev/docs/testing/errors
|
||||
void initLogger(VoidCallback runApp) {
|
||||
runZonedGuarded(() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
FlutterError.onError = (details) {
|
||||
FlutterError.dumpErrorToConsole(details);
|
||||
logWarning('AppLogger::initLogger::runZonedGuarded:FlutterError.onError: ${details.stack.toString()}');
|
||||
};
|
||||
runApp.call();
|
||||
}, (error, stack) {
|
||||
logWarning('AppLogger::initLogger::runZonedGuarded:onError: $error | stack: $stack');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,23 @@
|
||||
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:fk_user_agent/fk_user_agent.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
class ApplicationManager {
|
||||
static final ApplicationManager _instance = ApplicationManager._internal();
|
||||
|
||||
final DeviceInfoPlugin _deviceInfoPlugin;
|
||||
factory ApplicationManager() => _instance;
|
||||
|
||||
ApplicationManager(this._deviceInfoPlugin);
|
||||
ApplicationManager._internal();
|
||||
|
||||
// Allow overriding in unit tests
|
||||
@visibleForTesting
|
||||
static DeviceInfoPlugin? debugDeviceInfoOverride;
|
||||
|
||||
DeviceInfoPlugin get _deviceInfoPlugin =>
|
||||
debugDeviceInfoOverride ?? DeviceInfoPlugin();
|
||||
|
||||
Future<PackageInfo> getPackageInfo() async {
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
@@ -18,9 +26,13 @@ class ApplicationManager {
|
||||
}
|
||||
|
||||
Future<String> getVersion() async {
|
||||
final version = (await getPackageInfo()).version;
|
||||
log('ApplicationManager::getVersion: $version');
|
||||
return version;
|
||||
try {
|
||||
final version = (await getPackageInfo()).version;
|
||||
log('ApplicationManager::getVersion: $version');
|
||||
return version;
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> getUserAgent() async {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
|
||||
class EnvLoader {
|
||||
const EnvLoader._();
|
||||
|
||||
static const String envFileName = 'env.file';
|
||||
static const String appFCMConfigurationPath = "configurations/env.fcm";
|
||||
|
||||
static Future<void> loadEnvFile() async {
|
||||
await loadConfigFromEnv();
|
||||
final mapEnvData = Map<String, String>.from(dotenv.env);
|
||||
await loadFcmConfigFileToEnv(
|
||||
currentMapEnvData: mapEnvData,
|
||||
onCallBack: () async {
|
||||
await loadConfigFromEnv();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> loadFcmConfigFileToEnv({
|
||||
Map<String, String>? currentMapEnvData,
|
||||
VoidCallback? onCallBack,
|
||||
}) async {
|
||||
try {
|
||||
await dotenv.load(
|
||||
fileName: appFCMConfigurationPath,
|
||||
mergeWith: currentMapEnvData ?? {},
|
||||
);
|
||||
} catch (e) {
|
||||
logWarning('EnvLoader::loadFcmConfigFileToEnv: Exception = $e');
|
||||
onCallBack?.call();
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> loadConfigFromEnv() async {
|
||||
try {
|
||||
await dotenv.load(fileName: envFileName);
|
||||
} catch (e) {
|
||||
logWarning('EnvLoader::loadConfigFromEnv:Exception = $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import 'package:core/utils/application_manager.dart';
|
||||
import 'package:core/utils/build_utils.dart';
|
||||
import 'package:core/utils/config/env_loader.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
|
||||
/// Holds configuration values for initializing Sentry.
|
||||
class SentryConfig {
|
||||
// DSN (Data Source Name) endpoint for the Sentry project
|
||||
final String dsn;
|
||||
|
||||
// Running environment (production/staging/dev)
|
||||
final String environment;
|
||||
|
||||
// Current app release version
|
||||
final String release;
|
||||
|
||||
// // Performance monitoring: Set tracesSampleRate to 1.0 to capture 100% of transactions for tracing
|
||||
final double tracesSampleRate;
|
||||
|
||||
// Optional profiling
|
||||
final double profilesSampleRate;
|
||||
|
||||
// Enable logs to be sent to Sentry. To use Sentry.logger.fmt
|
||||
final bool enableLogs;
|
||||
|
||||
// Debug logs during development
|
||||
final bool isDebug;
|
||||
|
||||
// Automatically attaches a screenshot when capturing an error or exception.
|
||||
final bool attachScreenshot;
|
||||
|
||||
// Check if Sentry is available
|
||||
final bool isAvailable;
|
||||
|
||||
SentryConfig({
|
||||
required this.dsn,
|
||||
required this.environment,
|
||||
required this.release,
|
||||
this.tracesSampleRate = 1.0,
|
||||
this.profilesSampleRate = 1.0,
|
||||
this.enableLogs = true,
|
||||
this.isDebug = BuildUtils.isDebugMode,
|
||||
this.attachScreenshot = false,
|
||||
this.isAvailable = false,
|
||||
});
|
||||
|
||||
/// Load configuration from an env file.
|
||||
static Future<SentryConfig> load() async {
|
||||
await EnvLoader.loadConfigFromEnv();
|
||||
|
||||
final sentryAvailable = dotenv.get('SENTRY_ENABLED', fallback: 'false');
|
||||
|
||||
final isAvailable = sentryAvailable == 'true';
|
||||
final sentryDSN = dotenv.get('SENTRY_DSN', fallback: '');
|
||||
final sentryEnvironment = dotenv.get('SENTRY_ENVIRONMENT', fallback: '');
|
||||
|
||||
if (!isAvailable) {
|
||||
throw Exception('Sentry is not available');
|
||||
}
|
||||
|
||||
if (sentryDSN.trim().isEmpty || sentryEnvironment.trim().isEmpty) {
|
||||
throw Exception('Sentry configuration is missing');
|
||||
}
|
||||
|
||||
final appVersion = await ApplicationManager().getVersion();
|
||||
|
||||
return SentryConfig(
|
||||
dsn: sentryDSN,
|
||||
environment: sentryEnvironment,
|
||||
release: appVersion,
|
||||
isAvailable: isAvailable,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:core/utils/sentry/sentry_config.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
class SentryInitializer {
|
||||
static Future<bool> init(FutureOr<void> Function() appRunner) async {
|
||||
final config = await SentryConfig.load();
|
||||
|
||||
await SentryFlutter.init(
|
||||
(options) {
|
||||
options.dsn = config.dsn;
|
||||
options.environment = config.environment;
|
||||
options.release = config.release;
|
||||
options.tracesSampleRate = config.tracesSampleRate;
|
||||
options.profilesSampleRate = config.profilesSampleRate;
|
||||
options.enableLogs = config.enableLogs;
|
||||
options.debug = config.isDebug;
|
||||
options.attachScreenshot = config.attachScreenshot;
|
||||
options.maxRequestBodySize = MaxRequestBodySize.small;
|
||||
|
||||
// Automatically enable breadcrumbs that are appropriate for the current platform
|
||||
options.enableBreadcrumbTrackingForCurrentPlatform();
|
||||
|
||||
// Assign the callback to process events before sending them to Sentry
|
||||
options.beforeSend = _beforeSendHandler;
|
||||
},
|
||||
appRunner: appRunner,
|
||||
);
|
||||
|
||||
return config.isAvailable;
|
||||
}
|
||||
|
||||
/// Handler executed before sending an event to Sentry
|
||||
static Future<SentryEvent?> _beforeSendHandler(
|
||||
SentryEvent event,
|
||||
Hint? hint,
|
||||
) async {
|
||||
// Ignore AssertionError events
|
||||
if (event.throwable is AssertionError) {
|
||||
return null;
|
||||
}
|
||||
return event;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:core/utils/sentry/sentry_initializer.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
/// Controls Sentry initialization and error reporting.
|
||||
class SentryManager {
|
||||
SentryManager._();
|
||||
|
||||
static final SentryManager instance = SentryManager._();
|
||||
|
||||
bool _isSentryAvailable = false;
|
||||
|
||||
bool get isSentryAvailable => _isSentryAvailable;
|
||||
|
||||
/// Initialize Sentry. App still runs if init fails.
|
||||
Future<void> initialize({
|
||||
required FutureOr<void> Function() appRunner,
|
||||
required FutureOr<void> Function() fallBackRunner,
|
||||
}) async {
|
||||
try {
|
||||
_isSentryAvailable = await SentryInitializer.init(appRunner);
|
||||
log('[SentryManager] Sentry initialized: $_isSentryAvailable');
|
||||
} catch (e, st) {
|
||||
logError('[SentryManager] Init failed', exception: e, stackTrace: st);
|
||||
await fallBackRunner();
|
||||
}
|
||||
}
|
||||
|
||||
/// Capture an exception. Metadata is attached as breadcrumbs.
|
||||
Future<void> captureException(
|
||||
dynamic exception, {
|
||||
StackTrace? stackTrace,
|
||||
String? message,
|
||||
Map<String, dynamic>? extras,
|
||||
}) async {
|
||||
if (!_isSentryAvailable) return;
|
||||
|
||||
await Sentry.captureException(
|
||||
exception,
|
||||
stackTrace: stackTrace,
|
||||
withScope: (scope) {
|
||||
scope.addBreadcrumb(
|
||||
Breadcrumb(
|
||||
message: message ?? exception.toString(),
|
||||
data: extras,
|
||||
level: SentryLevel.error,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Capture a text message. Metadata also goes into breadcrumbs.
|
||||
Future<void> captureMessage(
|
||||
String message, {
|
||||
SentryLevel level = SentryLevel.info,
|
||||
Map<String, dynamic>? extras,
|
||||
}) async {
|
||||
if (!_isSentryAvailable) return;
|
||||
|
||||
await Sentry.captureMessage(
|
||||
message,
|
||||
level: level,
|
||||
withScope: (scope) {
|
||||
scope.addBreadcrumb(
|
||||
Breadcrumb(
|
||||
message: message,
|
||||
data: extras,
|
||||
level: SentryLevel.info,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> setUser(SentryUser user) async {
|
||||
if (!_isSentryAvailable) return;
|
||||
|
||||
try {
|
||||
await Sentry.configureScope((scope) => scope.setUser(user));
|
||||
log('[SentryManager] User set: ${user.email}');
|
||||
} catch (e, st) {
|
||||
logError('[SentryManager] Set user failed', exception: e, stackTrace: st);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> clearUser() async {
|
||||
if (!_isSentryAvailable) return;
|
||||
|
||||
try {
|
||||
await Sentry.configureScope((scope) => scope.setUser(null));
|
||||
log('[SentryManager] User cleared');
|
||||
} catch (e, st) {
|
||||
logError(
|
||||
'[SentryManager] Clear user failed',
|
||||
exception: e,
|
||||
stackTrace: st,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
-4
@@ -29,10 +29,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
|
||||
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
version: "2.7.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -261,10 +261,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio
|
||||
sha256: "9fdbf71baeb250fc9da847f6cb2052196f62c19906a3657adfc18631a667d316"
|
||||
sha256: e44ce32c01f02c54a101551def8586f15d6189b4f999d4420aad38995ac62e6d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
version: "5.2.0"
|
||||
equatable:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -374,6 +374,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
flutter_dotenv:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_dotenv
|
||||
sha256: d9283d92059a22e9834bc0a31336658ffba77089fb6f3cc36751f1fc7c6661a3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.2"
|
||||
flutter_image_compress:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -656,6 +664,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
jni:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jni
|
||||
sha256: d2c361082d554d4593c3012e26f6b188f902acd291330f13d6427641a92b3da1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.14.2"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -760,6 +776,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.4.4"
|
||||
objective_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: objective_c
|
||||
sha256: "64e35e1e2e79da4e83f2ace3bf4e5437cef523f46c7db2eba9a1419c49573790"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.0.0"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -977,6 +1001,22 @@ packages:
|
||||
url: "https://github.com/linagora/dart-neats.git"
|
||||
source: git
|
||||
version: "3.0.1"
|
||||
sentry:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sentry
|
||||
sha256: "10a0bc25f5f21468e3beeae44e561825aaa02cdc6829438e73b9b64658ff88d9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.8.0"
|
||||
sentry_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sentry_flutter
|
||||
sha256: aafbf41c63c98a30b17bdbf3313424d5102db62b08735c44bff810f277e786a5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.8.0"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1166,6 +1206,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.2"
|
||||
vector_graphics:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
+5
-1
@@ -41,7 +41,7 @@ dependencies:
|
||||
|
||||
flutter_svg: 2.1.0
|
||||
|
||||
dio: 5.0.0
|
||||
dio: 5.2.0
|
||||
|
||||
dartz: 0.10.1
|
||||
|
||||
@@ -101,6 +101,10 @@ dependencies:
|
||||
|
||||
web: 1.1.1
|
||||
|
||||
flutter_dotenv: 5.0.2
|
||||
|
||||
sentry_flutter: 9.8.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
@@ -10,9 +10,7 @@ import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'application_manager_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([
|
||||
MockSpec<DeviceInfoPlugin>()
|
||||
])
|
||||
@GenerateNiceMocks([MockSpec<DeviceInfoPlugin>()])
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
@@ -21,55 +19,73 @@ void main() {
|
||||
|
||||
setUp(() {
|
||||
mockDeviceInfoPlugin = MockDeviceInfoPlugin();
|
||||
applicationManager = ApplicationManager(mockDeviceInfoPlugin);
|
||||
ApplicationManager.debugDeviceInfoOverride = mockDeviceInfoPlugin;
|
||||
|
||||
applicationManager = ApplicationManager();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
ApplicationManager.debugDeviceInfoOverride = null;
|
||||
PlatformInfo.isTestingForWeb = false;
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(
|
||||
const MethodChannel('fk_user_agent'),
|
||||
null,
|
||||
);
|
||||
|
||||
FkUserAgent.release();
|
||||
});
|
||||
|
||||
group('ApplicationManager::getUserAgent test', () {
|
||||
test('WHEN platform is Web THEN getUserAgent should be return user agent for web', () async {
|
||||
test(
|
||||
'WHEN platform is Web THEN getUserAgent should return user agent for web',
|
||||
() async {
|
||||
const webUserAgent = 'User-Agent-Twake-Mail-Web';
|
||||
|
||||
PlatformInfo.isTestingForWeb = true;
|
||||
|
||||
when(mockDeviceInfoPlugin.webBrowserInfo)
|
||||
.thenAnswer((_) async => WebBrowserInfo(
|
||||
userAgent: webUserAgent,
|
||||
appCodeName: '',
|
||||
appName: '',
|
||||
appVersion: '',
|
||||
deviceMemory: null,
|
||||
language: '',
|
||||
languages: [],
|
||||
platform: '',
|
||||
product: '',
|
||||
productSub: '',
|
||||
vendor: '',
|
||||
vendorSub: '',
|
||||
maxTouchPoints: null,
|
||||
hardwareConcurrency: null,
|
||||
));
|
||||
when(mockDeviceInfoPlugin.webBrowserInfo).thenAnswer(
|
||||
(_) async => WebBrowserInfo(
|
||||
userAgent: webUserAgent,
|
||||
appCodeName: '',
|
||||
appName: '',
|
||||
appVersion: '',
|
||||
deviceMemory: null,
|
||||
language: '',
|
||||
languages: [],
|
||||
platform: '',
|
||||
product: '',
|
||||
productSub: '',
|
||||
vendor: '',
|
||||
vendorSub: '',
|
||||
maxTouchPoints: null,
|
||||
hardwareConcurrency: null,
|
||||
),
|
||||
);
|
||||
|
||||
final userAgent = await applicationManager.getUserAgent();
|
||||
|
||||
expect(userAgent, webUserAgent);
|
||||
|
||||
PlatformInfo.isTestingForWeb = false;
|
||||
});
|
||||
|
||||
test('WHEN platform is Android THEN getUserAgent should be return user agent for Android', () async {
|
||||
test(
|
||||
'WHEN platform is Android THEN getUserAgent should return user agent for Android',
|
||||
() async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
|
||||
const androidUserAgent = 'User-Agent-Twake-Mail-Android';
|
||||
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(
|
||||
const MethodChannel('fk_user_agent'),
|
||||
(message) async {
|
||||
if (message.method == 'getProperties') {
|
||||
return {
|
||||
'userAgent': androidUserAgent
|
||||
};
|
||||
return {'userAgent': androidUserAgent};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
await FkUserAgent.init();
|
||||
@@ -77,42 +93,35 @@ void main() {
|
||||
final userAgent = await applicationManager.getUserAgent();
|
||||
|
||||
expect(userAgent, androidUserAgent);
|
||||
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
FkUserAgent.release();
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
const MethodChannel('fk_user_agent'),
|
||||
null);
|
||||
});
|
||||
|
||||
test('WHEN platform is Web\n'
|
||||
'AND mockDeviceInfoPlugin.webBrowserInfo throw exception\n'
|
||||
'THEN getUserAgent should be return empty string',
|
||||
() async {
|
||||
test(
|
||||
'WHEN platform is Web AND mockDeviceInfoPlugin.webBrowserInfo throws exception THEN return empty string',
|
||||
() async {
|
||||
PlatformInfo.isTestingForWeb = true;
|
||||
|
||||
when(mockDeviceInfoPlugin.webBrowserInfo).thenThrow(Exception('Failed to get web browser info'));
|
||||
when(mockDeviceInfoPlugin.webBrowserInfo)
|
||||
.thenThrow(Exception('Failed to get web browser info'));
|
||||
|
||||
final userAgent = await applicationManager.getUserAgent();
|
||||
|
||||
expect(userAgent, '');
|
||||
|
||||
PlatformInfo.isTestingForWeb = false;
|
||||
});
|
||||
|
||||
test('WHEN platform is Android\n'
|
||||
'AND FkUserAgent.userAgent return empty string\n'
|
||||
'THEN getUserAgent should be return empty string',
|
||||
() async {
|
||||
test(
|
||||
'WHEN platform is Android AND FkUserAgent.userAgent empty THEN return empty string',
|
||||
() async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(
|
||||
const MethodChannel('fk_user_agent'),
|
||||
(message) async {
|
||||
if (message.method == 'getProperties') {
|
||||
return {};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
await FkUserAgent.init();
|
||||
@@ -120,12 +129,6 @@ void main() {
|
||||
final userAgent = await applicationManager.getUserAgent();
|
||||
|
||||
expect(userAgent, '');
|
||||
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
FkUserAgent.release();
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
const MethodChannel('fk_user_agent'),
|
||||
null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user