diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ee12b1..de00334 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.0.0 +- Update dependencies +- [Breaking] Hide `MPAnalyticsOptionsWeb` and `MPAnalyticsOptionsMobile` from public API in favor of `MPAnalyticsOptions` factory constructors. +- [Breaking] Update `MPAnalytics` initialization approach. +- Add validations to ensure that the events and user data are valid. +- Fix `engagement_time_msec` calculation before sending an event. + + ## 0.1.3 - Update dependencies diff --git a/build/test_cache/build/c075001b96339384a97db4862b8ab8db.cache.dill.track.dill b/build/test_cache/build/c075001b96339384a97db4862b8ab8db.cache.dill.track.dill index 7d36ebe..d591759 100644 Binary files a/build/test_cache/build/c075001b96339384a97db4862b8ab8db.cache.dill.track.dill and b/build/test_cache/build/c075001b96339384a97db4862b8ab8db.cache.dill.track.dill differ diff --git a/coverage/lcov.info b/coverage/lcov.info index c18a442..8278b48 100644 --- a/coverage/lcov.info +++ b/coverage/lcov.info @@ -1,4 +1,30 @@ -SF:lib\src\models\mp_analytics_options_mobile.dart +SF:lib/src/services/default_metadata_service.dart +DA:10,9 +DA:16,2 +DA:18,1 +DA:20,1 +DA:22,2 +DA:23,1 +DA:24,1 +DA:25,1 +DA:28,2 +DA:29,2 +DA:30,2 +DA:35,1 +DA:36,2 +DA:39,1 +DA:40,3 +DA:43,1 +DA:44,2 +LF:17 +LH:17 +end_of_record +SF:lib/src/models/mp_analytics_options.dart +DA:14,2 +LF:1 +LH:1 +end_of_record +SF:lib/src/models/mp_analytics_options_mobile.dart DA:6,1 DA:21,1 DA:22,1 @@ -10,12 +36,7 @@ DA:29,1 LF:8 LH:8 end_of_record -SF:lib\src\models\mp_analytics_options.dart -DA:14,2 -LF:1 -LH:1 -end_of_record -SF:lib\src\models\mp_analytics_options_web.dart +SF:lib/src/models/mp_analytics_options_web.dart DA:6,1 DA:21,1 DA:22,1 @@ -27,114 +48,103 @@ DA:29,1 LF:8 LH:8 end_of_record -SF:lib\src\models\mp_analytics_user.dart -DA:11,1 -DA:18,1 -DA:19,1 -DA:20,4 -DA:21,4 -DA:26,1 -DA:27,1 -DA:31,1 -DA:32,1 -DA:36,1 -DA:37,3 -DA:43,1 -DA:44,2 -LF:13 -LH:13 -end_of_record -SF:lib\src\services\default_metadata_service.dart -DA:10,4 -DA:16,2 -DA:18,1 -DA:20,1 -DA:22,2 -DA:23,1 -DA:24,1 -DA:25,1 -DA:28,2 -DA:29,2 -DA:30,2 -DA:35,1 -DA:36,2 +SF:lib/src/mp_analytics.dart DA:39,1 -DA:40,3 -DA:43,1 -DA:44,2 -LF:17 -LH:17 -end_of_record -SF:lib\src\mp_analytics.dart -DA:37,1 -DA:47,1 -DA:87,1 -DA:92,1 -DA:94,1 -DA:95,1 +DA:82,2 +DA:90,1 +DA:96,3 +DA:98,1 DA:99,1 -DA:100,1 -DA:101,1 -DA:102,1 -DA:103,2 +DA:103,1 +DA:104,1 DA:105,1 -DA:108,1 +DA:106,2 +DA:107,3 DA:109,1 -DA:110,0 -DA:113,1 -DA:114,2 -DA:115,1 -DA:116,1 -DA:117,0 -DA:118,0 -DA:121,1 +DA:110,1 +DA:114,1 +DA:115,3 +DA:117,1 +DA:118,1 +DA:122,1 +DA:123,2 +DA:124,4 +DA:125,2 +DA:126,3 +DA:127,1 DA:128,1 -DA:129,1 -DA:130,2 -DA:133,2 -DA:134,2 +DA:131,1 +DA:132,1 +DA:133,0 +DA:137,1 +DA:138,2 DA:139,1 DA:140,1 -DA:141,1 -DA:144,2 -DA:145,1 -DA:152,1 +DA:141,2 +DA:142,0 +DA:143,0 +DA:146,1 DA:153,1 DA:154,1 -DA:155,1 -DA:159,2 -DA:160,2 -DA:165,1 -DA:166,1 -DA:167,2 -DA:170,2 -DA:171,2 +DA:155,2 +DA:159,1 +DA:161,2 +DA:164,0 +DA:168,2 +DA:173,1 +DA:174,1 +DA:175,1 +DA:179,1 +DA:181,2 DA:182,1 -DA:186,1 -DA:187,2 +DA:189,1 +DA:190,1 DA:191,1 -DA:192,3 -DA:195,1 +DA:192,1 DA:197,1 -DA:200,1 -DA:201,2 -DA:202,1 -DA:203,1 -DA:207,1 -DA:208,2 -DA:209,2 -DA:210,2 +DA:199,2 +DA:202,0 +DA:203,0 +DA:207,2 +DA:212,1 DA:213,1 -DA:215,2 -DA:216,3 -DA:217,2 -DA:220,1 -DA:221,1 -DA:222,2 -LF:65 -LH:62 +DA:214,2 +DA:218,1 +DA:219,2 +DA:220,2 +DA:231,1 +DA:235,1 +DA:236,2 +DA:240,1 +DA:242,2 +DA:243,2 +DA:247,0 +DA:251,1 +DA:252,3 +DA:256,5 +DA:258,1 +DA:260,1 +DA:263,1 +DA:264,2 +DA:265,1 +DA:266,1 +DA:270,1 +DA:271,2 +DA:272,2 +DA:273,2 +DA:276,1 +DA:278,2 +DA:279,3 +DA:280,2 +DA:283,1 +DA:284,1 +DA:287,1 +DA:288,1 +DA:289,1 +LF:92 +LH:85 end_of_record -SF:lib\src\mp_analytics_client.dart +SF:lib/src/mp_analytics_client.dart DA:12,2 DA:16,2 DA:27,2 @@ -143,18 +153,84 @@ DA:32,2 DA:34,4 DA:38,2 DA:41,6 -DA:60,2 +DA:60,1 DA:65,1 -DA:68,4 -DA:69,2 +DA:68,2 +DA:69,1 DA:75,1 DA:76,2 DA:77,1 LF:15 LH:15 end_of_record -SF:lib\src\services\metadata_service.dart -DA:7,4 +SF:lib/src/services/metadata_service.dart +DA:7,9 LF:1 LH:1 end_of_record +SF:lib/src/core/event_validator.dart +DA:11,2 +DA:13,4 +DA:14,1 +DA:17,2 +DA:20,6 +DA:21,1 +DA:24,4 +DA:27,4 +DA:28,1 +DA:31,2 +DA:34,6 +DA:39,4 +DA:51,2 +DA:53,6 +DA:57,4 +DA:60,12 +DA:64,10 +DA:67,4 +DA:68,2 +DA:69,8 +DA:73,2 +DA:74,2 +DA:75,8 +LF:23 +LH:23 +end_of_record +SF:lib/src/core/user_data_validator.dart +DA:4,2 +DA:6,6 +DA:10,4 +DA:18,2 +DA:19,2 +DA:20,2 +DA:23,6 +DA:24,2 +DA:28,8 +DA:29,1 +DA:32,10 +DA:42,2 +DA:44,6 +DA:45,2 +DA:48,4 +LF:15 +LH:15 +end_of_record +SF:lib/src/mp_analytics_user.dart +DA:13,2 +DA:21,1 +DA:22,1 +DA:23,4 +DA:24,4 +DA:29,1 +DA:30,2 +DA:32,1 +DA:38,1 +DA:39,1 +DA:43,1 +DA:44,3 +DA:45,3 +DA:47,3 +DA:53,1 +DA:54,2 +LF:16 +LH:16 +end_of_record diff --git a/example/main.dart b/example/lib/main.dart similarity index 87% rename from example/main.dart rename to example/lib/main.dart index 6487535..a1afd48 100644 --- a/example/main.dart +++ b/example/lib/main.dart @@ -1,8 +1,11 @@ +import 'dart:io'; + import 'package:dart_mp_analytics/dart_mp_analytics.dart'; void main() async { // Initialize MPAnalytics options - const options = MPAnalyticsOptionsWeb( + // or use MPAnalyticsOptions.mobileStream() + const options = MPAnalyticsOptions.webStream( clientId: 'your_client_id', measurementId: 'your_measurement_id', apiSecret: 'your_api_secret', @@ -13,7 +16,7 @@ void main() async { options: options, debugAnalytics: true, // Enable debug mode for testing verbose: true, // Enable verbose logging - ); + )..initialize(); // Log an event await analytics.logEvent( @@ -41,4 +44,6 @@ void main() async { analytics ..clearUserId() ..removeUserProperty('membership'); + + exit(0); } diff --git a/example/pubspec.lock b/example/pubspec.lock new file mode 100644 index 0000000..b18cc4b --- /dev/null +++ b/example/pubspec.lock @@ -0,0 +1,188 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + dart_mp_analytics: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.1.3" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + http: + dependency: transitive + description: + name: http + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + logger: + dependency: transitive + description: + name: logger + sha256: "8c94b8c219e7e50194efc8771cd0e9f10807d8d3e219af473d89b06cc2ee4e04" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + meta: + dependency: transitive + description: + name: meta + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + url: "https://pub.dev" + source: hosted + version: "1.15.0" + mocktail: + dependency: transitive + description: + name: mocktail + sha256: c4b5007d91ca4f67256e720cb1b6d704e79a510183a12fa551021f652577dce6 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + platform_info: + dependency: transitive + description: + name: platform_info + sha256: "61f3bd25642d6624b3d3111892b517cdb17c37a1837189fee346fcc6dd09b052" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "2419f20b0c8677b2d67c8ac4d1ac7372d862dc6c460cdbb052b40155408cd794" + url: "https://pub.dev" + source: hosted + version: "0.7.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + uuid: + dependency: transitive + description: + name: uuid + sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" + url: "https://pub.dev" + source: hosted + version: "4.4.0" +sdks: + dart: ">=3.0.0 <4.0.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 0000000..adfd9d6 --- /dev/null +++ b/example/pubspec.yaml @@ -0,0 +1,11 @@ +name: example +description: An example of how to use the dart_mp_analytics package. +version: 0.0.1 +publish_to: "none" + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + dart_mp_analytics: + path: ../ diff --git a/lib/dart_mp_analytics.dart b/lib/dart_mp_analytics.dart index 2041412..0639212 100644 --- a/lib/dart_mp_analytics.dart +++ b/lib/dart_mp_analytics.dart @@ -2,9 +2,6 @@ library dart_mp_analytics; export 'src/models/mp_analytics_options.dart'; -export 'src/models/mp_analytics_options_mobile.dart'; -export 'src/models/mp_analytics_options_web.dart'; export 'src/mp_analytics.dart'; -export 'src/mp_analytics_client.dart'; export 'src/services/default_metadata_service.dart'; export 'src/services/metadata_service.dart'; diff --git a/lib/src/mp_analytics.dart b/lib/src/mp_analytics.dart index f315a70..c3a1e01 100644 --- a/lib/src/mp_analytics.dart +++ b/lib/src/mp_analytics.dart @@ -3,11 +3,12 @@ import 'dart:convert'; import 'package:dart_mp_analytics/src/core/event_validator.dart'; import 'package:dart_mp_analytics/src/models/mp_analytics_options.dart'; -import 'package:dart_mp_analytics/src/models/mp_analytics_user.dart'; import 'package:dart_mp_analytics/src/mp_analytics_client.dart'; +import 'package:dart_mp_analytics/src/mp_analytics_user.dart'; import 'package:dart_mp_analytics/src/services/default_metadata_service.dart'; import 'package:dart_mp_analytics/src/services/metadata_service.dart'; import 'package:logger/logger.dart'; +import 'package:meta/meta.dart'; import 'package:uuid/uuid.dart'; /// {@template mp_analytics} @@ -41,14 +42,7 @@ class MPAnalytics { this.debugAnalytics = false, this.enabled = true, this.verbose = false, - Logger? logger, - MPAnalyticsClient? client, - }) { - _initialize( - logger: logger, - client: client, - ); - } + }); /// The options used to configure the [MPAnalytics] instance. This must be an /// instance of correct [MPAnalyticsOptions] subclass for the platform you are @@ -72,29 +66,50 @@ class MPAnalytics { /// Whether or not to log debug messages to the console. final bool verbose; - /// The user associated with this [MPAnalytics] instance. late final MPAnalyticsUser _user; - /// The client used to send requests to the Google Analytics Measurement late final MPAnalyticsClient _client; - /// The session ID for this [MPAnalytics] instance. - String? _sessionId; - late final Logger _logger; - final EventValidator _validator = EventValidator(); + late final EventValidator _validator; + + late final DateTime _sessionStartTime; + + bool _isInitialized = false; - void _initialize({ + /// Whether or not the [MPAnalytics] instance has been initialized. + bool get isInitialized => _isInitialized; + + String? _sessionId; + Timer? _sessionTimer; + + /// Initializes the [MPAnalytics] instance with mock objects for testing. + /// This method is only meant to be used for testing purposes and should not + /// be used in production code. + @visibleForTesting + void initializeMock({ + MPAnalyticsUser? user, MPAnalyticsClient? client, - Logger? logger, + EventValidator? validator, }) { - _logger = logger ?? - Logger( - printer: PrettyPrinter( - methodCount: 0, - ), - ); + _logger = Logger(printer: PrettyPrinter(methodCount: 0)); + + if (!enabled) { + return; + } + + _user = user ?? MPAnalyticsUser(); + _client = client ?? MPAnalyticsClient(urlParameters: options.urlParameters); + _validator = EventValidator(); + _sessionStartTime = DateTime.now().toUtc(); + + _isInitialized = true; + } + + /// Initializes the [MPAnalytics] instance. + void initialize() { + _logger = Logger(printer: PrettyPrinter(methodCount: 0)); if (!enabled) { _verboseLog('MPAnalytics disabled, not initializing'); @@ -103,10 +118,10 @@ class MPAnalytics { _verboseLog('Initializing MPAnalytics'); _user = MPAnalyticsUser(); - _client = client ?? - MPAnalyticsClient( - urlParameters: options.urlParameters, - ); + _client = MPAnalyticsClient(urlParameters: options.urlParameters); + _validator = EventValidator(); + _sessionStartTime = DateTime.now().toUtc(); + _isInitialized = true; _verboseLog('MPAnalytics initialized'); } @@ -115,10 +130,12 @@ class MPAnalytics { _verboseLog('MPAnalytics disabled, not creating session ID'); return ''; } + if (_sessionId == null) { _sessionId = const Uuid().v4(); _verboseLog('Created new session ID'); - Timer(const Duration(minutes: 30), () { + _sessionTimer?.cancel(); + _sessionTimer = Timer(const Duration(minutes: 30), () { _sessionId = null; _verboseLog('Session ID expired'); }); @@ -135,6 +152,9 @@ class MPAnalytics { _verboseLog('MPAnalytics disabled, not setting user ID: $id'); return; } + + _verifyInitialized(); + final isValid = _user.setId(id); if (!isValid) { @@ -152,6 +172,9 @@ class MPAnalytics { _verboseLog('MPAnalytics disabled, not clearing user ID'); return; } + + _verifyInitialized(); + _user.clearId(); _verboseLog('Cleared user ID'); } @@ -167,6 +190,9 @@ class MPAnalytics { ); return; } + + _verifyInitialized(); + final isValid = _user.setProperty(key, value); if (!isValid) { @@ -185,6 +211,8 @@ class MPAnalytics { _verboseLog('MPAnalytics disabled, not removing user property: $key'); return; } + + _verifyInitialized(); _user.removeProperty(key); _verboseLog('Removed user property: $key'); } @@ -206,6 +234,7 @@ class MPAnalytics { return; } + _verifyInitialized(); final (isValidEventName, isValidEventParameter) = ( _validator.validateEventName(name), _validator.validateEventParameters(parameters), @@ -220,12 +249,15 @@ class MPAnalytics { for (final service in metadataServiceList) ...await service.getMetadata(), }; + final engagementTime = + DateTime.now().toUtc().difference(_sessionStartTime).inMilliseconds; + final eventData = { 'name': name, 'params': { // Required parameters to show up in Firebase Analytics // https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?hl=pt-br&client_type=gtag#recommended_parameters_for_reports - 'engagement_time_msec': '100', + 'engagement_time_msec': engagementTime, 'session_id': _getOrCreateSessionId(), ...metadata, ...parameters, @@ -246,8 +278,15 @@ class MPAnalytics { } void _verboseLog(String message) { - if (verbose) { - _logger.log(Level.info, message); + if (verbose) _logger.log(Level.info, message); + } + + void _verifyInitialized() { + if (!isInitialized) { + throw StateError( + 'MPAnalytics instance must be initialized first. Make sure to call the ' + 'initialize method before calling any other methods.', + ); } } } diff --git a/lib/src/models/mp_analytics_user.dart b/lib/src/mp_analytics_user.dart similarity index 100% rename from lib/src/models/mp_analytics_user.dart rename to lib/src/mp_analytics_user.dart diff --git a/pubspec.yaml b/pubspec.yaml index 958adbb..230c5a7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: dart_mp_analytics description: A dart only package for wrapper around the Firebase Measurement Protocol API. -version: 0.1.3 +version: 1.0.0 repository: https://github.com/alvarobcprado/dart_mp_analytics issue_tracker: https://github.com/alvarobcprado/dart_mp_analytics/issues @@ -8,13 +8,13 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: - http: ">=0.13.4 <2.0.0" - logger: ^2.0.2+1 - meta: ^1.3.0 - mocktail: ^1.0.2 + http: ">=1.1.0 <2.0.0" + logger: ^2.2.0 + meta: ^1.15.0 + mocktail: ^1.0.3 platform_info: ^4.0.2 - uuid: ^4.3.1 + uuid: ^4.4.0 dev_dependencies: - test: ^1.21.0 + test: ^1.25.5 very_good_analysis: ^5.1.0 diff --git a/test/src/models/mp_analytics_user_test.dart b/test/src/models/mp_analytics_user_test.dart index 21c40f1..28d9262 100644 --- a/test/src/models/mp_analytics_user_test.dart +++ b/test/src/models/mp_analytics_user_test.dart @@ -1,4 +1,4 @@ -import 'package:dart_mp_analytics/src/models/mp_analytics_user.dart'; +import 'package:dart_mp_analytics/src/mp_analytics_user.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/src/mp_analytics_client_test.dart b/test/src/mp_analytics_client_test.dart index 991af22..7836bf8 100644 --- a/test/src/mp_analytics_client_test.dart +++ b/test/src/mp_analytics_client_test.dart @@ -1,4 +1,4 @@ -import 'package:dart_mp_analytics/dart_mp_analytics.dart'; +import 'package:dart_mp_analytics/src/mp_analytics_client.dart'; import 'package:http/http.dart' as http; import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; diff --git a/test/src/mp_analytics_test.dart b/test/src/mp_analytics_test.dart index 1d57572..ec20272 100644 --- a/test/src/mp_analytics_test.dart +++ b/test/src/mp_analytics_test.dart @@ -1,13 +1,14 @@ // ignore_for_file: inference_failure_on_function_invocation import 'package:dart_mp_analytics/dart_mp_analytics.dart'; -import 'package:logger/logger.dart'; +import 'package:dart_mp_analytics/src/mp_analytics_client.dart'; +import 'package:dart_mp_analytics/src/mp_analytics_user.dart'; import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; class MockMPAnalyticsClient extends Mock implements MPAnalyticsClient {} -class MockLogger extends Mock implements Logger {} +class MockMPAnalyticsUser extends Mock implements MPAnalyticsUser {} class MockMPAnalyticsOptions extends Mock implements MPAnalyticsOptions {} @@ -19,266 +20,291 @@ void main() { late List metadataServiceList; late bool debugAnalytics; late bool enabled; - late bool verbose; - late Logger logger; late MPAnalyticsClient client; + late MPAnalyticsUser user; - setUpAll(() { - registerFallbackValue(Level.info); - }); - - setUp(() { - options = MockMPAnalyticsOptions(); - metadataServiceList = [MockMetadataService()]; - debugAnalytics = false; - enabled = true; - verbose = true; - logger = MockLogger(); - client = MockMPAnalyticsClient(); - - analytics = MPAnalytics( - options: options, - metadataServiceList: metadataServiceList, - debugAnalytics: debugAnalytics, - enabled: enabled, - verbose: verbose, - logger: logger, - client: client, - ); - }); - - test('setUserId sets the user ID', () { - const userId = 'user123'; - - analytics.setUserId(userId); - - verify(() => logger.log(Level.info, 'Set user ID: $userId')).called(1); - }); - - test('clearUserId clears the user ID', () { - analytics.clearUserId(); - - verify(() => logger.log(Level.info, 'Cleared user ID')).called(1); - }); - - test('setUserProperty sets the user property', () { - const key = 'propertyKey'; - const value = 'propertyValue'; - - analytics.setUserProperty(key, value); - - verify(() => logger.log(Level.info, 'Set user property: $key = $value')) - .called(1); - }); - - test('removeUserProperty removes the user property', () { - const key = 'propertyKey'; - - analytics.removeUserProperty(key); - - verify(() => logger.log(Level.info, 'Removed user property: $key')) - .called(1); - }); - - test('logEvent logs an event', () async { - const eventName = 'event'; - const eventParameters = {'param1': 'value1', 'param2': 'value2'}; - const optionsBodyParameters = {'bodyKey': 'bodyValue'}; - const metadata = {'metadataKey': 'metadataValue'}; - - when(() => options.bodyParameters).thenReturn(optionsBodyParameters); - when(() => metadataServiceList[0].getMetadata()) - .thenAnswer((_) async => metadata); - when(() => client.logEvent(any(), debug: any(named: 'debug'))) - .thenAnswer((_) async => null); - - await analytics.logEvent(eventName, parameters: eventParameters); - - verify( - () => logger.log( - Level.info, - 'Logging event: $eventName with parameters: $eventParameters', - ), - ).called(1); - verify(() => metadataServiceList[0].getMetadata()).called(1); - verify( - () => client.logEvent( - any(), - debug: debugAnalytics, - ), - ).called(1); - verify(() => logger.log(Level.info, 'Response: null')).called(1); - }); - - test('logEvent does not log event when disabled', () async { - analytics = MPAnalytics( - options: options, - metadataServiceList: metadataServiceList, - debugAnalytics: debugAnalytics, - enabled: false, - verbose: verbose, - logger: logger, - client: client, - ); - - const eventName = 'event'; - const parameters = {'param1': 'value1', 'param2': 'value2'}; - - await analytics.logEvent(eventName, parameters: parameters); - - verify( - () => logger.log( - Level.info, - 'MPAnalytics disabled, not logging event: $eventName', - ), - ).called(1); - verifyNever(() => metadataServiceList[0].getMetadata()); - verifyNever(() => client.logEvent(any(), debug: any(named: 'debug'))) - .called(0); - }); - - test( - 'MPAnalytics use default client when not provided', - () async { - const eventName = 'event'; - const eventParameters = {'param1': 'value1', 'param2': 'value2'}; - const optionsBodyParameters = {'bodyKey': 'bodyValue'}; - const optionsUrlParameters = {'urlKey': 'urlValue'}; - const metadata = {'metadataKey': 'metadataValue'}; - - when(() => options.bodyParameters).thenReturn(optionsBodyParameters); - when(() => options.urlParameters).thenReturn(optionsUrlParameters); - when(() => metadataServiceList[0].getMetadata()) - .thenAnswer((_) async => metadata); - when(() => client.logEvent(any(), debug: any(named: 'debug'))) - .thenAnswer((_) async => null); + setUp( + () { + options = MockMPAnalyticsOptions(); + metadataServiceList = [MockMetadataService()]; + debugAnalytics = false; + enabled = true; + client = MockMPAnalyticsClient(); + user = MockMPAnalyticsUser(); analytics = MPAnalytics( options: options, metadataServiceList: metadataServiceList, debugAnalytics: debugAnalytics, enabled: enabled, - verbose: verbose, - logger: logger, + )..initializeMock( + user: user, + client: client, + ); + }, + ); + + group( + 'initialization', + () { + test( + 'initializes the MPAnalytics instance', + () { + when(() => options.bodyParameters).thenReturn({}); + when(() => options.urlParameters).thenReturn({}); + + analytics = MPAnalytics( + options: options, + metadataServiceList: metadataServiceList, + debugAnalytics: debugAnalytics, + enabled: enabled, + )..initialize(); + + expect(analytics.isInitialized, isTrue); + }, ); - await analytics.logEvent(eventName, parameters: eventParameters); + test( + 'does not initialize the MPAnalytics instance when disabled', + () { + analytics = MPAnalytics( + options: options, + metadataServiceList: metadataServiceList, + debugAnalytics: debugAnalytics, + enabled: false, + )..initialize(); + + expect(analytics.isInitialized, isFalse); + }, + ); - verifyNever( - () => client.logEvent( - any(), - debug: debugAnalytics, - ), + test( + 'throws a StateError when trying to use the MPAnalytics before ' + 'initializing', + () { + analytics = MPAnalytics( + options: options, + metadataServiceList: metadataServiceList, + debugAnalytics: debugAnalytics, + enabled: enabled, + ); + + expect( + () => analytics.logEvent('event'), + throwsA(isA()), + ); + }, ); }, ); - test( - 'removeUserProperty do not remove property when disabled', - () async { - when(() => logger.log(any(), any())).thenReturn(null); + group( + 'User', + () { + test( + 'setUserId sets the user ID', + () { + const userId = 'user123'; - analytics = MPAnalytics( - options: options, - metadataServiceList: metadataServiceList, - debugAnalytics: debugAnalytics, - enabled: false, - verbose: verbose, - logger: logger, - client: client, + when(() => user.setId(any())).thenReturn(true); + + analytics.setUserId(userId); + + verify(() => user.setId(userId)).called(1); + }, ); - const key = 'propertyKey'; + test( + 'clearUserId clears the user ID', + () { + analytics.clearUserId(); + + verify(() => user.clearId()).called(1); + }, + ); - analytics.removeUserProperty(key); + test( + 'setUserProperty sets the user property', + () { + const key = 'propertyKey'; + const value = 'propertyValue'; - verify( - () => logger.log( - Level.info, - 'MPAnalytics disabled, not removing user property: $key', - ), - ).called(1); - }, - ); + when(() => user.setProperty(any(), any())).thenReturn(true); - test( - 'setUserProperty do not set property when disabled', - () async { - when(() => logger.log(any(), any())).thenReturn(null); + analytics.setUserProperty(key, value); - analytics = MPAnalytics( - options: options, - metadataServiceList: metadataServiceList, - debugAnalytics: debugAnalytics, - enabled: false, - verbose: verbose, - logger: logger, - client: client, + verify(() => user.setProperty(key, value)).called(1); + }, ); - const key = 'propertyKey'; - const value = 'propertyValue'; + test( + 'removeUserProperty removes the user property', + () { + const key = 'propertyKey'; - analytics.setUserProperty(key, value); + analytics.removeUserProperty(key); - verify( - () => logger.log( - Level.info, - 'MPAnalytics disabled, not setting user property: {$key : $value}', - ), - ).called(1); - }, - ); + verify(() => user.removeProperty(key)).called(1); + }, + ); - test( - 'setUserId do not set user ID when disabled', - () async { - when(() => logger.log(any(), any())).thenReturn(null); + test( + 'removeUserProperty do not remove property when disabled', + () async { + analytics = MPAnalytics( + options: options, + metadataServiceList: metadataServiceList, + debugAnalytics: debugAnalytics, + enabled: false, + )..initializeMock(user: user, client: client); - analytics = MPAnalytics( - options: options, - metadataServiceList: metadataServiceList, - debugAnalytics: debugAnalytics, - enabled: false, - verbose: verbose, - logger: logger, - client: client, + const key = 'propertyKey'; + + analytics.removeUserProperty(key); + + verifyNever(() => user.removeProperty(key)); + }, ); - const userId = 'userId'; + test( + 'setUserProperty do not set property when disabled', + () async { + analytics = MPAnalytics( + options: options, + metadataServiceList: metadataServiceList, + debugAnalytics: debugAnalytics, + enabled: false, + )..initializeMock(user: user, client: client); + + const key = 'propertyKey'; + const value = 'propertyValue'; + + analytics.setUserProperty(key, value); + + verifyNever(() => user.setProperty(key, value)); + }, + ); + + test( + 'setUserId do not set user ID when disabled', + () async { + analytics = MPAnalytics( + options: options, + metadataServiceList: metadataServiceList, + debugAnalytics: debugAnalytics, + enabled: false, + )..initializeMock(user: user, client: client); + + const userId = 'userId'; - analytics.setUserId(userId); + analytics.setUserId(userId); - verify( - () => logger.log( - Level.info, - 'MPAnalytics disabled, not setting user ID: $userId', - ), - ).called(1); + verifyNever(() => user.setId(userId)); + }, + ); + + test( + 'clearUserId do not clear user ID when disabled', + () async { + MPAnalytics( + options: options, + metadataServiceList: metadataServiceList, + debugAnalytics: debugAnalytics, + enabled: false, + ) + ..initializeMock(user: user, client: client) + ..clearUserId(); + + verifyNever(() => user.clearId()); + }, + ); }, ); - test( - 'clearUserId do not clear user ID when disabled', - () async { - when(() => logger.log(any(), any())).thenReturn(null); + group( + 'logEvent', + () { + test( + 'logs an event', + () async { + const eventName = 'event'; + const eventParameters = {'param1': 'value1', 'param2': 'value2'}; + const optionsBodyParameters = {'bodyKey': 'bodyValue'}; + const metadata = {'metadataKey': 'metadataValue'}; + const userData = {'userIdKey': 'userIdValue'}; + + when(() => user.data).thenReturn(userData); + when(() => options.bodyParameters).thenReturn(optionsBodyParameters); + when(() => metadataServiceList[0].getMetadata()) + .thenAnswer((_) async => metadata); + when(() => client.logEvent(any(), debug: any(named: 'debug'))) + .thenAnswer((_) async => null); + + await analytics.logEvent(eventName, parameters: eventParameters); + + verify(() => metadataServiceList[0].getMetadata()).called(1); + verify(() => user.data).called(1); + verify( + () => client.logEvent( + any(), + debug: debugAnalytics, + ), + ).called(1); + }, + ); - MPAnalytics( - options: options, - metadataServiceList: metadataServiceList, - debugAnalytics: debugAnalytics, - enabled: false, - verbose: verbose, - logger: logger, - client: client, - ).clearUserId(); - - verify( - () => logger.log( - Level.info, - 'MPAnalytics disabled, not clearing user ID', - ), - ).called(1); + test( + 'does not log event when disabled', + () async { + analytics = MPAnalytics( + options: options, + metadataServiceList: metadataServiceList, + debugAnalytics: debugAnalytics, + enabled: false, + )..initializeMock(user: user, client: client); + + const eventName = 'event'; + const parameters = {'param1': 'value1', 'param2': 'value2'}; + + await analytics.logEvent(eventName, parameters: parameters); + + verifyNever(() => metadataServiceList[0].getMetadata()); + verifyNever(() => user.data).called(0); + verifyNever(() => client.logEvent(any(), debug: any(named: 'debug'))) + .called(0); + }, + ); + + test( + 'throws a AssertionError if try to log invalid event', + () { + const invalidEventName = 'invalid event'; + const validParameters = {'param1': 'value1', 'param2': 'value2'}; + + expect( + () => analytics.logEvent( + invalidEventName, + parameters: validParameters, + ), + throwsA(isA()), + ); + + const validEventName = 'valid_event'; + final invalidParameters = { + 'param1': 'value1', + 'param2': 'value2', + 'invalid': 'a' * 101, + }; + + expect( + () => analytics.logEvent( + validEventName, + parameters: invalidParameters, + ), + throwsA(isA()), + ); + }, + ); }, ); }