From b68a7db44635317072510e0ddfda5c918ddaae84 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 26 Jun 2025 09:32:11 +0300 Subject: [PATCH 01/64] feat: sync SDKs --- android/build.gradle | 2 +- ios/Classes/CountlyiOS/CHANGELOG.md | 4 ++ ios/Classes/CountlyiOS/Countly-PL.podspec | 2 +- ios/Classes/CountlyiOS/Countly.podspec | 2 +- ios/Classes/CountlyiOS/CountlyCommon.m | 2 +- ios/Classes/CountlyiOS/CountlyPersistency.h | 4 +- ios/Classes/CountlyiOS/CountlyPersistency.m | 13 ++-- ios/Classes/CountlyiOS/CountlyServerConfig.m | 69 +++++++++++++++++--- 8 files changed, 77 insertions(+), 21 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index a0078e33..424dfc3b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -38,6 +38,6 @@ android { } dependencies { - implementation 'ly.count.android:sdk:25.4.1' + implementation 'ly.count.android:sdk:25.4.2' implementation 'com.google.firebase:firebase-messaging:24.0.3' } diff --git a/ios/Classes/CountlyiOS/CHANGELOG.md b/ios/Classes/CountlyiOS/CHANGELOG.md index e89b332d..792822e5 100644 --- a/ios/Classes/CountlyiOS/CHANGELOG.md +++ b/ios/Classes/CountlyiOS/CHANGELOG.md @@ -1,3 +1,7 @@ +## 25.4.3 +* Mitigated an issue where SDK behavior settings were set to default when fetching for new config. +* Mitigated an issue where latest fetched behavior settings were replacing the current settings instead of merging. + ## 25.4.2 * Added fullscreen support for feedback widgets. * Added "disableSDKBehaviorSettingsUpdates" init config parameter to disable server config updates. diff --git a/ios/Classes/CountlyiOS/Countly-PL.podspec b/ios/Classes/CountlyiOS/Countly-PL.podspec index e3822374..990bea43 100644 --- a/ios/Classes/CountlyiOS/Countly-PL.podspec +++ b/ios/Classes/CountlyiOS/Countly-PL.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Countly-PL' - s.version = '25.4.2' + s.version = '25.4.3' s.license = { :type => 'MIT', :file => 'LICENSE' } s.summary = 'Countly is an innovative, real-time, open source mobile analytics platform.' s.homepage = 'https://github.com/Countly/countly-sdk-ios' diff --git a/ios/Classes/CountlyiOS/Countly.podspec b/ios/Classes/CountlyiOS/Countly.podspec index 17bd75f0..9def31c1 100644 --- a/ios/Classes/CountlyiOS/Countly.podspec +++ b/ios/Classes/CountlyiOS/Countly.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Countly' - s.version = '25.4.2' + s.version = '25.4.3' s.license = { :type => 'MIT', :file => 'LICENSE' } s.summary = 'Countly is an innovative, real-time, open source mobile analytics platform.' s.homepage = 'https://github.com/Countly/countly-sdk-ios' diff --git a/ios/Classes/CountlyiOS/CountlyCommon.m b/ios/Classes/CountlyiOS/CountlyCommon.m index e9f8bd45..63c99d42 100644 --- a/ios/Classes/CountlyiOS/CountlyCommon.m +++ b/ios/Classes/CountlyiOS/CountlyCommon.m @@ -29,7 +29,7 @@ @interface CountlyCommon () #endif @end -NSString* const kCountlySDKVersion = @"25.4.2"; +NSString* const kCountlySDKVersion = @"25.4.3"; NSString* const kCountlySDKName = @"objc-native-ios"; NSString* const kCountlyErrorDomain = @"ly.count.ErrorDomain"; diff --git a/ios/Classes/CountlyiOS/CountlyPersistency.h b/ios/Classes/CountlyiOS/CountlyPersistency.h index 675e87fc..4e17aaa5 100644 --- a/ios/Classes/CountlyiOS/CountlyPersistency.h +++ b/ios/Classes/CountlyiOS/CountlyPersistency.h @@ -58,8 +58,8 @@ - (NSDictionary *)retrieveRemoteConfig; - (void)storeRemoteConfig:(NSDictionary *)remoteConfig; -- (NSDictionary *)retrieveServerConfig; -- (void)storeServerConfig:(NSDictionary *)serverConfig; +- (NSMutableDictionary *)retrieveServerConfig; +- (void)storeServerConfig:(NSMutableDictionary *)serverConfig; - (NSDictionary *)retrieveHealthCheckTrackerState; - (void)storeHealthCheckTrackerState:(NSDictionary *)healthCheckTrackerState; diff --git a/ios/Classes/CountlyiOS/CountlyPersistency.m b/ios/Classes/CountlyiOS/CountlyPersistency.m index 52f0b6ee..0f04d476 100644 --- a/ios/Classes/CountlyiOS/CountlyPersistency.m +++ b/ios/Classes/CountlyiOS/CountlyPersistency.m @@ -593,16 +593,17 @@ - (void)storeRemoteConfig:(NSDictionary *)remoteConfig [NSUserDefaults.standardUserDefaults synchronize]; } -- (NSDictionary *)retrieveServerConfig +- (NSMutableDictionary *)retrieveServerConfig { NSDictionary* serverConfig = [NSUserDefaults.standardUserDefaults objectForKey:kCountlyServerConfigPersistencyKey]; - if (!serverConfig) - serverConfig = NSDictionary.new; - - return serverConfig; + if ([serverConfig isKindOfClass:[NSDictionary class]]) { + return [serverConfig mutableCopy]; + } + + return [NSMutableDictionary new]; } -- (void)storeServerConfig:(NSDictionary *)serverConfig +- (void)storeServerConfig:(NSMutableDictionary *)serverConfig { [NSUserDefaults.standardUserDefaults setObject:serverConfig forKey:kCountlyServerConfigPersistencyKey]; [NSUserDefaults.standardUserDefaults synchronize]; diff --git a/ios/Classes/CountlyiOS/CountlyServerConfig.m b/ios/Classes/CountlyiOS/CountlyServerConfig.m index ea3129c7..8cc11a93 100644 --- a/ios/Classes/CountlyiOS/CountlyServerConfig.m +++ b/ios/Classes/CountlyiOS/CountlyServerConfig.m @@ -116,16 +116,66 @@ - (instancetype)init - (void)retrieveServerConfigFromStorage:(NSString *)sdkBehaviorSettings { - NSError *error = nil; - NSDictionary *serverConfigObject = [CountlyPersistency.sharedInstance retrieveServerConfig]; - if (serverConfigObject.count == 0 && sdkBehaviorSettings) + NSMutableDictionary *persistentBehaviorSettings = [CountlyPersistency.sharedInstance retrieveServerConfig]; + if (persistentBehaviorSettings.count == 0 && sdkBehaviorSettings) { - serverConfigObject = [NSJSONSerialization JSONObjectWithData:[sdkBehaviorSettings cly_dataUTF8] options:0 error:&error]; + NSError *error = nil; + id parsed = [NSJSONSerialization JSONObjectWithData:[sdkBehaviorSettings cly_dataUTF8] options:0 error:&error]; + + if ([parsed isKindOfClass:[NSDictionary class]]) { + persistentBehaviorSettings = [(NSDictionary *)parsed mutableCopy]; + [CountlyPersistency.sharedInstance storeServerConfig:persistentBehaviorSettings]; + } else { + CLY_LOG_W(@"%s, Failed to parse sdkBehaviorSettings or not a dictionary: %@", __FUNCTION__, error); + } } - if (serverConfigObject.count > 0 && !error) + [self populateServerConfig:persistentBehaviorSettings]; +} + +- (void)mergeBehaviorSettings:(NSMutableDictionary *)baseConfig + withConfig:(NSDictionary *)newConfig +{ + // c, t and v paramters must exist + if(newConfig.count != 3 || !newConfig[kRConfig]) { + CLY_LOG_D(@"%s, missing entries for a behavior settings omitting", __FUNCTION__); + return; + } + + if (!newConfig[kRVersion] || !newConfig[kRTimestamp]) { - [self populateServerConfig:serverConfigObject]; + CLY_LOG_D(@"%s, version or timestamp is missing in the behavioır settings omitting", __FUNCTION__); + return; + } + + if(!([newConfig[kRConfig] isKindOfClass:[NSDictionary class]]) || ((NSDictionary *)newConfig[kRConfig]).count == 0){ + CLY_LOG_D(@"%s, invalid behavior settings omitting", __FUNCTION__); + return; + } + + id timestamp = newConfig[kRTimestamp]; + if (timestamp) { + baseConfig[kRTimestamp] = timestamp; + } + + id version = newConfig[kRVersion]; + if (version) { + baseConfig[kRVersion] = version; + } + + NSDictionary *cBase = baseConfig[kRConfig] ?: NSMutableDictionary.new; + NSDictionary *cNew = newConfig[kRConfig]; + + if ([cBase isKindOfClass:[NSDictionary class]] || [cNew isKindOfClass:[NSDictionary class]]) { + NSMutableDictionary *cMerged = [cBase mutableCopy]; + + [cNew enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + if (obj != nil && obj != [NSNull null]) { + cMerged[key] = obj; + } + }]; + + baseConfig[kRConfig] = cMerged; } } @@ -379,9 +429,10 @@ - (void)fetchServerConfig:(CountlyConfig *)config if (serverConfigResponse[kRConfig] != nil) { - [CountlyPersistency.sharedInstance storeServerConfig:serverConfigResponse]; - [self setDefaultValues]; - [self populateServerConfig:serverConfigResponse]; + NSMutableDictionary *persistentBehaviorSettings = [CountlyPersistency.sharedInstance retrieveServerConfig]; + [self mergeBehaviorSettings:persistentBehaviorSettings withConfig:serverConfigResponse]; + [CountlyPersistency.sharedInstance storeServerConfig:persistentBehaviorSettings]; + [self populateServerConfig:persistentBehaviorSettings]; } [self notifySdkConfigChange:config]; // if no config let stored ones to be set From ca9b42c4398e09cdbc36b719467ea5da563ec095 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 26 Jun 2025 09:33:04 +0300 Subject: [PATCH 02/64] feat: helper method to set sc before sdk init --- .../dart/countly_flutter/CountlyFlutterPlugin.java | 11 +++++++++-- example/integration_test/utils.dart | 4 ++++ ios/Classes/CountlyFlutterPlugin.m | 9 ++++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/ly/count/dart/countly_flutter/CountlyFlutterPlugin.java b/android/src/main/java/ly/count/dart/countly_flutter/CountlyFlutterPlugin.java index 7405a3a8..a524bf5e 100644 --- a/android/src/main/java/ly/count/dart/countly_flutter/CountlyFlutterPlugin.java +++ b/android/src/main/java/ly/count/dart/countly_flutter/CountlyFlutterPlugin.java @@ -1408,6 +1408,11 @@ else if ("getRequestQueue".equals(call.method)) { CountlyStore countlyStore = new CountlyStore(context, new ModuleLog()); countlyStore.addRequest(args.getString(0), true); result.success("storeRequest: success"); + } else if ("setServerConfig".equals(call.method)) { + CountlyStore countlyStore = new CountlyStore(context, new ModuleLog()); + JSONObject jsonObject = args.getJSONObject(0); + countlyStore.setServerConfig(jsonObject.toString()); + result.success("setServerConfig: success"); } else if ("addDirectRequest".equals(call.method)) { JSONObject jsonObject = args.getJSONObject(0); Map requestMap = new HashMap<>(); @@ -1420,7 +1425,10 @@ else if ("getRequestQueue".equals(call.method)) { } else if ("halt".equals(call.method)) { Countly.sharedInstance().halt(); result.success("halt: success"); - } else if ("enterContentZone".equals(call.method)) { + } + //------------------End------------------------------------ + + else if ("enterContentZone".equals(call.method)) { Countly.sharedInstance().contents().enterContentZone(); result.success(null); } else if ("exitContentZone".equals(call.method)) { @@ -1430,7 +1438,6 @@ else if ("getRequestQueue".equals(call.method)) { Countly.sharedInstance().contents().refreshContentZone(); result.success(null); } - //------------------End------------------------------------ else { result.notImplemented(); diff --git a/example/integration_test/utils.dart b/example/integration_test/utils.dart index e49b39f5..9e169acd 100644 --- a/example/integration_test/utils.dart +++ b/example/integration_test/utils.dart @@ -35,6 +35,10 @@ void addDirectRequest(Map request) async { await _channelTest.invokeMethod('addDirectRequest', {'data': json.encode([request])}); } +void setServerConfig(Map serverConfig) async { + await _channelTest.invokeMethod('setServerConfig', {'data': json.encode([serverConfig])}); +} + /// Verify the common request queue parameters void testCommonRequestParams(Map> requestObject) { expect(requestObject['app_key']?[0], APP_KEY); diff --git a/ios/Classes/CountlyFlutterPlugin.m b/ios/Classes/CountlyFlutterPlugin.m index f2788107..757717e3 100644 --- a/ios/Classes/CountlyFlutterPlugin.m +++ b/ios/Classes/CountlyFlutterPlugin.m @@ -144,7 +144,14 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result [Countly.sharedInstance addDirectRequest:requestMap]; result(@"added request to queue"); }); - } else if ([@"recordEvent" isEqualToString:call.method]) { + } else if ([@"setServerConfig" isEqualToString:call.method]) { + dispatch_async(dispatch_get_main_queue(), ^{ + NSMutableDictionary *serverConfig = [command objectAtIndex:0]; + [NSUserDefaults.standardUserDefaults setObject:serverConfig forKey:@"kCountlyServerConfigPersistencyKey"]; + [NSUserDefaults.standardUserDefaults synchronize]; + result(@"setServerConfig: success"); + }); + }else if ([@"recordEvent" isEqualToString:call.method]) { dispatch_async(dispatch_get_main_queue(), ^{ NSString *key = [command objectAtIndex:0]; NSString *countString = [command objectAtIndex:1]; From d1102f4ac1873d4e2afa52fdb964601ae4263a92 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 26 Jun 2025 10:09:56 +0300 Subject: [PATCH 03/64] feat: add post support to mock test server --- example/integration_test/utils.dart | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/example/integration_test/utils.dart b/example/integration_test/utils.dart index 9e169acd..0d0b2314 100644 --- a/example/integration_test/utils.dart +++ b/example/integration_test/utils.dart @@ -28,15 +28,21 @@ Future> getEventQueue() async { /// Add request to native sides void storeRequest(Map request) async { - await _channelTest.invokeMethod('storeRequest', {'data': json.encode([Uri(queryParameters: request).query])}); + await _channelTest.invokeMethod('storeRequest', { + 'data': json.encode([Uri(queryParameters: request).query]) + }); } void addDirectRequest(Map request) async { - await _channelTest.invokeMethod('addDirectRequest', {'data': json.encode([request])}); + await _channelTest.invokeMethod('addDirectRequest', { + 'data': json.encode([request]) + }); } void setServerConfig(Map serverConfig) async { - await _channelTest.invokeMethod('setServerConfig', {'data': json.encode([serverConfig])}); + await _channelTest.invokeMethod('setServerConfig', { + 'data': json.encode([serverConfig]) + }); } /// Verify the common request queue parameters @@ -136,9 +142,14 @@ void createServer(List>> requestArray, {int delay = 0, final requestTime = DateTime.now(); print('[Test Server][${requestTime.toIso8601String()}] Request received: ${request.method} ${request.uri}'); - final queryParams = request.uri.queryParametersAll; - print(queryParams.toString()); + var queryParams = request.uri.queryParametersAll; + + if (request.method == 'POST') { + final body = await utf8.decodeStream(request); + queryParams = Uri.parse('?$body').queryParametersAll; // Update queryParams with POST body + } + print('[Test Server] queryParams: ${queryParams.toString()}'); // Store the request parameters for later verification requestArray.add(queryParams); From b92325b197f706aa97974d6abdfcc06f5bf04d24 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 26 Jun 2025 15:33:57 +0300 Subject: [PATCH 04/64] feat: some util functions to test --- example/integration_test/utils.dart | 98 ++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 2 deletions(-) diff --git a/example/integration_test/utils.dart b/example/integration_test/utils.dart index 0d0b2314..835aea3c 100644 --- a/example/integration_test/utils.dart +++ b/example/integration_test/utils.dart @@ -6,6 +6,8 @@ import 'package:flutter/services.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'event_tests/event_utils.dart'; + const MethodChannel _channelTest = MethodChannel('countly_flutter'); // Base config options for tests @@ -134,7 +136,7 @@ var _serverDelay = 0; /// Use http://0.0.0.0:8080 as the server url. /// You can specify a delay in seconds for the server response. /// You can also provide a custom handler for the server response. -void createServer(List>> requestArray, {int delay = 0, Future Function(HttpRequest, HttpResponse)? customHandler}) async { +void createServer(List>> requestArray, {int delay = 0, Future Function(HttpRequest, Map>, HttpResponse)? customHandler}) async { var server = await HttpServer.bind(InternetAddress.anyIPv4, 8080); print('[Test Server]Server running on http://${server.address.address}:${server.port}'); _serverDelay = delay; @@ -154,7 +156,7 @@ void createServer(List>> requestArray, {int delay = 0, requestArray.add(queryParams); if (customHandler != null) { - await customHandler(request, request.response); + await customHandler(request, queryParams, request.response); } else { if (_serverDelay > 0) { print('[Test Server][${DateTime.now().toIso8601String()}] Applying delay of ${_serverDelay} seconds'); @@ -193,6 +195,48 @@ Future halt() async { await _channelTest.invokeMethod('halt'); } +void validateImmediateCounts(Map immediates, List>> requestArray) { + Map actualImmediates = {}; + + // key is method values are the switch cases + for (var request in requestArray) { + if (request.containsKey('method')) { + String method = request['method']![0]; + actualImmediates[method] = (actualImmediates[method] ?? 0) + 1; + } else if (request.containsKey('hc')) { + actualImmediates['hc'] = (actualImmediates['hc'] ?? 0) + 1; + } + } + + expect(actualImmediates.length, immediates.length, reason: 'Mismatch in number of immediate methods'); + // Validate the counts + for (var entry in immediates.entries) { + expect(actualImmediates[entry.key], entry.value, reason: 'Mismatch for method ${entry.key}'); + } +} + +void validateInternalEventCounts(Map internalEventsCounts, List>> requestArray) { + Map actualCounts = {}; + + // key is method values are the switch cases + for (var request in requestArray) { + if (request.containsKey('events')) { + List> events = (jsonDecode(request['events']![0]) as List).cast>(); + for (var event in events) { + if (event['key'].toString().startsWith('[CLY]')) { + actualCounts[event['key']] = (actualCounts[event['key']] ?? 0) + 1; + } + } + } + } + + expect(actualCounts.length, internalEventsCounts.length, reason: 'Mismatch in number of internal event methods actual: $actualCounts, expected: $internalEventsCounts'); + // Validate the counts + for (var entry in internalEventsCounts.entries) { + expect(actualCounts['[CLY]_${entry.key}'], entry.value, reason: 'Mismatch for method ${entry.key}'); + } +} + /// Get and print elements with wanted param from event queue /// [String param] - wanted param Future> getAndPrintWantedElementsWithParamFromEventQueue(String param) async { @@ -285,6 +329,56 @@ Future createMixViewsAndEvents({bool inForeground = true}) async { await Countly.recordEvent(event3); } +Future callAllFeatures() async { + await Countly.getAvailableFeedbackWidgets(); + + await Countly.giveAllConsent(); + await Countly.instance.sessions.beginSession(); + await createTruncableEvents(); + await generateEvents(); + await Countly.setUserLocation(countryCode: 'TR', city: 'Istanbul', gpsCoordinates: '41.0082,28.9784', ipAddress: '10.2.33.12'); + await Countly.instance.events.recordEvent('Event With Sum And Segment', {'Country': 'Turkey', 'Age': 28884}, 1, 0.99); // not legacy code + Map segmentation = { + "country": "Germany", + "app_version": "1.0", + "rating": 10, + "precision": 324.54678, + "timestamp": 1234567890, + "clicked": false, + "languages": ["en", "de", "fr"], + "sub_names": ["John", "Doe", "Jane"] + }; + final String? viewID = await Countly.instance.views.startView("Dashboard", segmentation); + + // IMMEDIATE CALLS + await Countly.instance.content.enterContentZone(); + await Countly.instance.remoteConfig.downloadAllKeys((rResult, error, fullValueUpdate, downloadedValues) { + if (rResult == RequestResult.success) { + // do sth + } else { + // do sth + } + }); + + await Countly.instance.remoteConfig.enrollIntoABTestsForKeys(['key1', 'key2']); + await Countly.instance.remoteConfig.exitABTestsForKeys(['key1', 'key2']); + + // END IMMEDIATE CALLS + await Countly.reportFeedbackWidgetManually(CountlyPresentableFeedback('test', 'nps', 'test'), {}, {}); + + await Future.delayed(const Duration(seconds: 2)); + await Countly.instance.sessions.updateSession(); + await Countly.instance.views.stopViewWithID(viewID!); + await Countly.instance.content.refreshContentZone(); + + await Future.delayed(const Duration(seconds: 2)); + await Countly.instance.sessions.endSession(); + + await Countly.instance.attemptToSendStoredRequests(); + // check queues are empty and all requests are sent + await Future.delayed(const Duration(seconds: 10)); +} + /// Creates truncable events (which covers all possible truncable situations) /// - Events with segmentation /// - Views with segmentation From 8fcf844dcc69aa8a7ae3709a0f0971872e48e18f Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 26 Jun 2025 15:34:15 +0300 Subject: [PATCH 05/64] feat: base test for sbs --- .../sbs_tests/SBS_000_base_test.dart | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 example/integration_test/sbs_tests/SBS_000_base_test.dart diff --git a/example/integration_test/sbs_tests/SBS_000_base_test.dart b/example/integration_test/sbs_tests/SBS_000_base_test.dart new file mode 100644 index 00000000..cd303b44 --- /dev/null +++ b/example/integration_test/sbs_tests/SBS_000_base_test.dart @@ -0,0 +1,45 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:countly_flutter/countly_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../utils.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + testWidgets('SBS_000_base', (WidgetTester tester) async { + List>> requestArray = >>[]; + createServer(requestArray, customHandler: (request, queryParams, response) async { + Map responseJson = {'result': 'Success'}; + if (queryParams.containsKey('method') && queryParams['method']!.first == 'feedback') { + responseJson = {'result': []}; + } + response + ..statusCode = HttpStatus.ok + ..headers.contentType = ContentType.json + ..headers.set('Access-Control-Allow-Origin', '*') + ..write(jsonEncode(responseJson)); + }); + // Initialize the SDK + CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); + await Countly.initWithConfig(config); + + await callAllFeatures(); + + print(requestArray); + expect(true, requestArray.any((item) => item.containsKey('events'))); + expect(true, requestArray.any((item) => item.containsKey('location'))); //x2 + expect(true, requestArray.any((item) => item.containsKey('crash'))); //x2 + expect(true, requestArray.any((item) => item.containsKey('begin_session'))); + expect(true, requestArray.any((item) => item.containsKey('end_session'))); + expect(true, requestArray.any((item) => item.containsKey('session_duration') && !item.containsKey('end_session'))); + expect(true, requestArray.any((item) => item.containsKey('session_duration') && item.containsKey('end_session'))); + expect(true, requestArray.any((item) => item.containsKey('apm'))); //x2 + expect(true, requestArray.any((item) => item.containsKey('user_details'))); //x2 + + validateInternalEventCounts({'orientation': 1, 'view': 6}, requestArray); + validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); + }); +} From 1721ce40fac214fc690a4c1927ceb281f3a340cb Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Fri, 27 Jun 2025 15:04:25 +0300 Subject: [PATCH 06/64] feat: initial SBS tests notes --- example/integration_test/sbs_tests/notes.md | 105 ++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 example/integration_test/sbs_tests/notes.md diff --git a/example/integration_test/sbs_tests/notes.md b/example/integration_test/sbs_tests/notes.md new file mode 100644 index 00000000..221e2186 --- /dev/null +++ b/example/integration_test/sbs_tests/notes.md @@ -0,0 +1,105 @@ +SDK Behavior Settings tests + +And where to check values, which is affected: +from_server +storage +provided +dev_provided + +Order validation +200X tests are feature validation tests +where +A = SDK internal limits +B = Tracking - will not affect SC request +C = Networking - will not affect SC request +D = Request Queue + Event Queue + Session Update Interval +E = Session Tracking + Crash Tracking + Location Tracking +F = Custom Event Tracking + View Tracking +G = Consent + Drop old request + Server config update interval +H = Content Zone + Content Zone Interval + Refresh Content Zone +I = Backoff + +Tests + +- A variants: from_server, provided, dev_provided, storage +Change all SDK internal limits, respond them from the ${variant} and validate that all truncable things cool +at the end validate all queues are empty + +- B +Provide SBS through config, that it disables tracking +call all functions, validate server request list only contains server config and queues are empty + +Stop the SDK, clear stored SBS, provide SDK config through config again with tracking disabled +but let server respond tracking enabled, call all functions, validate queues are empty, all requests are sent + +This also validates server response sbs > provided sbs + +- C +Provide SBS through config, that it disables networking +call all functions, validate server request list only contains server config and queues are containing all features' requests + +Stop the SDK, clear stored SBS, provide SDK config through config again with networking disabled +but let server respond networking enabled, call all functions, validate queues are empty, all requests are sent + +This also validates server response sbs > provided sbs + +- D variants: from_server, provided, dev_provided, storage +Provide SBS through ${variant}, validate that: +Request queue is clipped with new limit +Event queue is clipped with new limit +Session update times are same as new limit + +- E variants: from_server, provided, storage +Call all features, respond from the ${variant} with disabling features +Validate that session and crash are not existing in the server request list +And location is existing as location disabled request +Validate RQ is not containing any crash and session requests + +To validate location works with/without session +stop the SDK, re init it with enabling session and validate begin_session contains location which is empty strinh + +- F +Call all functions validate that all called views and custom events are no containig in the EQ, RQ and server request list + +Stop the SDK, re init with views enabled, now validate views are existing in all the queues because they must not be affected +with custom event tracking + +- G variants: from_server, provided, dev_provided, storage +Add some old requests to the storage +Validate RQ containing old requests +Call all functions, validate that all consent required features are disabled and do not generate any request. +And validate that RQ is not containing old requests +???Somehow give server config update time very low and validate SC is triggered again + +- H variants: from_server, provided, dev_provided, storage +Call all functions, validate that content zone is entered automatically in the init +And validate that refresh content zone is not called +And validate content zone called again after configured time + +- I variants: from_server, provided, dev_provided, storage +call all functions with a delay where backoff would be triggered with changed parameters. +First do not disable backoff, and try other parameters + +stop the sdk and re init it with disabling backoff. Now try that backoff mechanism is not triggered. + +201X tests order validation +where +- A +configure all configurables via CountlyConfig validate that all set +then trigger server fetch and validate that server values changed the dev_provided configs + +- B +configure all configurables and provide SBS via CountlyConfig. +Validate that all dev_provided is overridden by provided. + +- C +configure all configurables, and provide SBS via CountlyConfig +and respond from the server, validate that server values are precedence. + +- D +configure all configurables, store SBS before initializing, validate that stored values have the precedence +trigger server fetch and validate that all values from_server + +- E +configure all configurables, store SBS before initializing, provide SBS via CountlyConfig, validate that provided values have the precedence +trigger server fetch and validate that all values from_server From de26a22278586cd34b45851a1d9e93031d63826a Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Fri, 27 Jun 2025 15:09:36 +0300 Subject: [PATCH 07/64] feat: SBS tests order validation --- example/integration_test/sbs_tests/notes.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/example/integration_test/sbs_tests/notes.md b/example/integration_test/sbs_tests/notes.md index 221e2186..9b61d589 100644 --- a/example/integration_test/sbs_tests/notes.md +++ b/example/integration_test/sbs_tests/notes.md @@ -1,10 +1,10 @@ SDK Behavior Settings tests And where to check values, which is affected: -from_server -storage -provided -dev_provided +from_server = FS +storage = S +provided = P +dev_provided = DP Order validation 200X tests are feature validation tests @@ -82,6 +82,8 @@ First do not disable backoff, and try other parameters stop the sdk and re init it with disabling backoff. Now try that backoff mechanism is not triggered. +--------------------------------------------------------------------------------------------------------------------------------- + 201X tests order validation where - A @@ -103,3 +105,10 @@ trigger server fetch and validate that all values from_server - E configure all configurables, store SBS before initializing, provide SBS via CountlyConfig, validate that provided values have the precedence trigger server fetch and validate that all values from_server + +tests are: +- 201A_DP_FS +- 201B_DP_P +- 201C_DP_P_FS +- 201D_DP_S_FS +- 201E_DP_S_P_FS From 8b19c1ad7bcad9cef840dab5e5c611bfafeebd63 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Mon, 30 Jun 2025 12:34:04 +0300 Subject: [PATCH 08/64] feat: move sbs related utils to related utils --- .../sbs_tests/SBS_000_base_test.dart | 1 + .../integration_test/sbs_tests/sbs_utils.dart | 118 ++++++++++++++++++ example/integration_test/utils.dart | 96 +------------- 3 files changed, 120 insertions(+), 95 deletions(-) create mode 100644 example/integration_test/sbs_tests/sbs_utils.dart diff --git a/example/integration_test/sbs_tests/SBS_000_base_test.dart b/example/integration_test/sbs_tests/SBS_000_base_test.dart index cd303b44..4173a286 100644 --- a/example/integration_test/sbs_tests/SBS_000_base_test.dart +++ b/example/integration_test/sbs_tests/SBS_000_base_test.dart @@ -6,6 +6,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import '../utils.dart'; +import 'sbs_utils.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); diff --git a/example/integration_test/sbs_tests/sbs_utils.dart b/example/integration_test/sbs_tests/sbs_utils.dart new file mode 100644 index 00000000..6887ff1f --- /dev/null +++ b/example/integration_test/sbs_tests/sbs_utils.dart @@ -0,0 +1,118 @@ +import 'dart:convert'; +import 'package:countly_flutter/countly_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../event_tests/event_utils.dart'; +import '../utils.dart'; + +/// Validates the immediate counts in the request array. +/// This function checks the number of immediate methods recorded in the request array +/// and compares them with the expected counts provided in the `immediates` map. +/// It expects the keys in the `immediates` map to be the method names (like 'hc', 'sc', 'feedback', etc.) +/// and the values to be the expected counts of those methods. +/// @param immediates A map where keys are the names of the immediate methods and values are the expected counts. like {'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1} +/// @param requestArray The array of requests to validate against. +void validateImmediateCounts(Map immediates, List>> requestArray) { + Map actualImmediates = {}; + + // key is method values are the switch cases + for (var request in requestArray) { + if (request.containsKey('method')) { + String method = request['method']![0]; + actualImmediates[method] = (actualImmediates[method] ?? 0) + 1; + } else if (request.containsKey('hc')) { + actualImmediates['hc'] = (actualImmediates['hc'] ?? 0) + 1; + } + } + + expect(actualImmediates.length, immediates.length, reason: 'Mismatch in number of immediate methods'); + // Validate the counts + for (var entry in immediates.entries) { + expect(actualImmediates[entry.key], entry.value, reason: 'Mismatch for method ${entry.key}'); + } +} + +/// Validates the internal event counts in the request array. +/// This function checks the number of internal events recorded in the request array +/// and compares them with the expected counts provided in the `internalEventsCounts` map. +/// It expects the keys in the `internalEventsCounts` map to be not prefixed with '[CLY]_'. +/// The function will throw an error if the counts do not match. +/// @param internalEventsCounts A map where keys are the names of the internal events (without '[CLY]_') and values are the expected counts. like {'orientation': 1, 'view': 6} +/// @param requestArray The array of requests to validate against. +/// +void validateInternalEventCounts(Map internalEventsCounts, List>> requestArray) { + Map actualCounts = {}; + + // key is method values are the switch cases + for (var request in requestArray) { + if (request.containsKey('events')) { + List> events = (jsonDecode(request['events']![0]) as List).cast>(); + for (var event in events) { + if (event['key'].toString().startsWith('[CLY]')) { + actualCounts[event['key']] = (actualCounts[event['key']] ?? 0) + 1; + } + } + } + } + + expect(actualCounts.length, internalEventsCounts.length, reason: 'Mismatch in number of internal event methods actual: $actualCounts, expected: $internalEventsCounts'); + // Validate the counts + for (var entry in internalEventsCounts.entries) { + expect(actualCounts['[CLY]_${entry.key}'], entry.value, reason: 'Mismatch for method ${entry.key}'); + } +} + +/// Calls all features of Countly SDK to ensure they are working correctly. +/// This includes events, views, sessions, user location, user profile, crash, feedback widgets, remote config, A/B testing, consent, and content zone. +/// It also includes the things that are affected by the SDK internal limits, such as truncable events +/// This function is used in integration tests to validate the functionality of the Countly SDK with the SBS +/// At the end of the function, it triggers sending requests to the queue and waits for 10 seconds to ensure all requests are sent and queues are empty +Future callAllFeatures() async { + await Countly.getAvailableFeedbackWidgets(); + + await Countly.giveAllConsent(); + await Countly.instance.sessions.beginSession(); + await createTruncableEvents(); + await generateEvents(); + await Countly.setUserLocation(countryCode: 'TR', city: 'Istanbul', gpsCoordinates: '41.0082,28.9784', ipAddress: '10.2.33.12'); + await Countly.instance.events.recordEvent('Event With Sum And Segment', {'Country': 'Turkey', 'Age': 28884}, 1, 0.99); // not legacy code + Map segmentation = { + "country": "Germany", + "app_version": "1.0", + "rating": 10, + "precision": 324.54678, + "timestamp": 1234567890, + "clicked": false, + "languages": ["en", "de", "fr"], + "sub_names": ["John", "Doe", "Jane"] + }; + final String? viewID = await Countly.instance.views.startView("Dashboard", segmentation); + + // IMMEDIATE CALLS + await Countly.instance.content.enterContentZone(); + await Countly.instance.remoteConfig.downloadAllKeys((rResult, error, fullValueUpdate, downloadedValues) { + if (rResult == RequestResult.success) { + // do sth + } else { + // do sth + } + }); + + await Countly.instance.remoteConfig.enrollIntoABTestsForKeys(['key1', 'key2']); + await Countly.instance.remoteConfig.exitABTestsForKeys(['key1', 'key2']); + + // END IMMEDIATE CALLS + await Countly.reportFeedbackWidgetManually(CountlyPresentableFeedback('test', 'nps', 'test'), {}, {}); + + await Future.delayed(const Duration(seconds: 2)); + await Countly.instance.sessions.updateSession(); + await Countly.instance.views.stopViewWithID(viewID!); + await Countly.instance.content.refreshContentZone(); + + await Future.delayed(const Duration(seconds: 2)); + await Countly.instance.sessions.endSession(); + + await Countly.instance.attemptToSendStoredRequests(); + // check queues are empty and all requests are sent + await Future.delayed(const Duration(seconds: 10)); +} diff --git a/example/integration_test/utils.dart b/example/integration_test/utils.dart index 835aea3c..66b86d15 100644 --- a/example/integration_test/utils.dart +++ b/example/integration_test/utils.dart @@ -6,8 +6,6 @@ import 'package:flutter/services.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'event_tests/event_utils.dart'; - const MethodChannel _channelTest = MethodChannel('countly_flutter'); // Base config options for tests @@ -135,7 +133,7 @@ var _serverDelay = 0; /// Start a server to receive the requests from the SDK and store them in a provided List. /// Use http://0.0.0.0:8080 as the server url. /// You can specify a delay in seconds for the server response. -/// You can also provide a custom handler for the server response. +/// You can also provide a custom handler for the server response. It takes the HttpRequest, query parameters, and HttpResponse as arguments. void createServer(List>> requestArray, {int delay = 0, Future Function(HttpRequest, Map>, HttpResponse)? customHandler}) async { var server = await HttpServer.bind(InternetAddress.anyIPv4, 8080); print('[Test Server]Server running on http://${server.address.address}:${server.port}'); @@ -195,48 +193,6 @@ Future halt() async { await _channelTest.invokeMethod('halt'); } -void validateImmediateCounts(Map immediates, List>> requestArray) { - Map actualImmediates = {}; - - // key is method values are the switch cases - for (var request in requestArray) { - if (request.containsKey('method')) { - String method = request['method']![0]; - actualImmediates[method] = (actualImmediates[method] ?? 0) + 1; - } else if (request.containsKey('hc')) { - actualImmediates['hc'] = (actualImmediates['hc'] ?? 0) + 1; - } - } - - expect(actualImmediates.length, immediates.length, reason: 'Mismatch in number of immediate methods'); - // Validate the counts - for (var entry in immediates.entries) { - expect(actualImmediates[entry.key], entry.value, reason: 'Mismatch for method ${entry.key}'); - } -} - -void validateInternalEventCounts(Map internalEventsCounts, List>> requestArray) { - Map actualCounts = {}; - - // key is method values are the switch cases - for (var request in requestArray) { - if (request.containsKey('events')) { - List> events = (jsonDecode(request['events']![0]) as List).cast>(); - for (var event in events) { - if (event['key'].toString().startsWith('[CLY]')) { - actualCounts[event['key']] = (actualCounts[event['key']] ?? 0) + 1; - } - } - } - } - - expect(actualCounts.length, internalEventsCounts.length, reason: 'Mismatch in number of internal event methods actual: $actualCounts, expected: $internalEventsCounts'); - // Validate the counts - for (var entry in internalEventsCounts.entries) { - expect(actualCounts['[CLY]_${entry.key}'], entry.value, reason: 'Mismatch for method ${entry.key}'); - } -} - /// Get and print elements with wanted param from event queue /// [String param] - wanted param Future> getAndPrintWantedElementsWithParamFromEventQueue(String param) async { @@ -329,56 +285,6 @@ Future createMixViewsAndEvents({bool inForeground = true}) async { await Countly.recordEvent(event3); } -Future callAllFeatures() async { - await Countly.getAvailableFeedbackWidgets(); - - await Countly.giveAllConsent(); - await Countly.instance.sessions.beginSession(); - await createTruncableEvents(); - await generateEvents(); - await Countly.setUserLocation(countryCode: 'TR', city: 'Istanbul', gpsCoordinates: '41.0082,28.9784', ipAddress: '10.2.33.12'); - await Countly.instance.events.recordEvent('Event With Sum And Segment', {'Country': 'Turkey', 'Age': 28884}, 1, 0.99); // not legacy code - Map segmentation = { - "country": "Germany", - "app_version": "1.0", - "rating": 10, - "precision": 324.54678, - "timestamp": 1234567890, - "clicked": false, - "languages": ["en", "de", "fr"], - "sub_names": ["John", "Doe", "Jane"] - }; - final String? viewID = await Countly.instance.views.startView("Dashboard", segmentation); - - // IMMEDIATE CALLS - await Countly.instance.content.enterContentZone(); - await Countly.instance.remoteConfig.downloadAllKeys((rResult, error, fullValueUpdate, downloadedValues) { - if (rResult == RequestResult.success) { - // do sth - } else { - // do sth - } - }); - - await Countly.instance.remoteConfig.enrollIntoABTestsForKeys(['key1', 'key2']); - await Countly.instance.remoteConfig.exitABTestsForKeys(['key1', 'key2']); - - // END IMMEDIATE CALLS - await Countly.reportFeedbackWidgetManually(CountlyPresentableFeedback('test', 'nps', 'test'), {}, {}); - - await Future.delayed(const Duration(seconds: 2)); - await Countly.instance.sessions.updateSession(); - await Countly.instance.views.stopViewWithID(viewID!); - await Countly.instance.content.refreshContentZone(); - - await Future.delayed(const Duration(seconds: 2)); - await Countly.instance.sessions.endSession(); - - await Countly.instance.attemptToSendStoredRequests(); - // check queues are empty and all requests are sent - await Future.delayed(const Duration(seconds: 10)); -} - /// Creates truncable events (which covers all possible truncable situations) /// - Events with segmentation /// - Views with segmentation From d74eab8d6682ad10aae1bdbcbf4e7c4d8bfaad4e Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 1 Jul 2025 15:15:57 +0300 Subject: [PATCH 09/64] feat: get server config --- .../countly_flutter/CountlyFlutterPlugin.java | 9 +++++++++ example/integration_test/utils.dart | 6 ++++++ ios/Classes/CountlyFlutterPlugin.m | 15 ++++++++++++++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/ly/count/dart/countly_flutter/CountlyFlutterPlugin.java b/android/src/main/java/ly/count/dart/countly_flutter/CountlyFlutterPlugin.java index a524bf5e..5aa5c3fc 100644 --- a/android/src/main/java/ly/count/dart/countly_flutter/CountlyFlutterPlugin.java +++ b/android/src/main/java/ly/count/dart/countly_flutter/CountlyFlutterPlugin.java @@ -1413,6 +1413,15 @@ else if ("getRequestQueue".equals(call.method)) { JSONObject jsonObject = args.getJSONObject(0); countlyStore.setServerConfig(jsonObject.toString()); result.success("setServerConfig: success"); + } else if ("getServerConfig".equals(call.method)) { + CountlyStore countlyStore = new CountlyStore(context, new ModuleLog()); + String sc = countlyStore.getServerConfig(); + Map serverConfigMap = new HashMap<>(); + try { + serverConfigMap = toMap(new JSONObject(sc)); + } catch (JSONException ignored) { + } + result.success(serverConfigMap); } else if ("addDirectRequest".equals(call.method)) { JSONObject jsonObject = args.getJSONObject(0); Map requestMap = new HashMap<>(); diff --git a/example/integration_test/utils.dart b/example/integration_test/utils.dart index 66b86d15..1609600c 100644 --- a/example/integration_test/utils.dart +++ b/example/integration_test/utils.dart @@ -45,6 +45,12 @@ void setServerConfig(Map serverConfig) async { }); } +/// Retrieve the server configuration from the native side +Future> getServerConfig() async { + final Map sc = await _channelTest.invokeMethod('getServerConfig'); + return Map.from(sc); +} + /// Verify the common request queue parameters void testCommonRequestParams(Map> requestObject) { expect(requestObject['app_key']?[0], APP_KEY); diff --git a/ios/Classes/CountlyFlutterPlugin.m b/ios/Classes/CountlyFlutterPlugin.m index 757717e3..7db0d847 100644 --- a/ios/Classes/CountlyFlutterPlugin.m +++ b/ios/Classes/CountlyFlutterPlugin.m @@ -151,7 +151,20 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result [NSUserDefaults.standardUserDefaults synchronize]; result(@"setServerConfig: success"); }); - }else if ([@"recordEvent" isEqualToString:call.method]) { + }else if ([@"getServerConfig" isEqualToString:call.method]) { + dispatch_async(dispatch_get_main_queue(), ^{ + NSDictionary *storedConfig = [NSUserDefaults.standardUserDefaults objectForKey:@"kCountlyServerConfigPersistencyKey"]; + + NSMutableDictionary *serverConfig = nil; + if ([storedConfig isKindOfClass:[NSDictionary class]]) { + serverConfig = [storedConfig mutableCopy]; + } else { + serverConfig = [NSMutableDictionary new]; + } + + result(serverConfig); + }); + } else if ([@"recordEvent" isEqualToString:call.method]) { dispatch_async(dispatch_get_main_queue(), ^{ NSString *key = [command objectAtIndex:0]; NSString *countString = [command objectAtIndex:1]; From 43ad93e9a33d57515a98b1a5d5432a3d5aa73c75 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 1 Jul 2025 15:16:24 +0300 Subject: [PATCH 10/64] feat: use name rather than id --- .../integration_test/sbs_tests/sbs_utils.dart | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/example/integration_test/sbs_tests/sbs_utils.dart b/example/integration_test/sbs_tests/sbs_utils.dart index 6887ff1f..aa8d876e 100644 --- a/example/integration_test/sbs_tests/sbs_utils.dart +++ b/example/integration_test/sbs_tests/sbs_utils.dart @@ -77,16 +77,16 @@ Future callAllFeatures() async { await Countly.setUserLocation(countryCode: 'TR', city: 'Istanbul', gpsCoordinates: '41.0082,28.9784', ipAddress: '10.2.33.12'); await Countly.instance.events.recordEvent('Event With Sum And Segment', {'Country': 'Turkey', 'Age': 28884}, 1, 0.99); // not legacy code Map segmentation = { - "country": "Germany", - "app_version": "1.0", - "rating": 10, - "precision": 324.54678, - "timestamp": 1234567890, - "clicked": false, - "languages": ["en", "de", "fr"], - "sub_names": ["John", "Doe", "Jane"] + 'country': 'Germany', + 'app_version': '1.0', + 'rating': 10, + 'precision': 324.54678, + 'timestamp': 1234567890, + 'clicked': false, + 'languages': ['en', 'de', 'fr'], + 'sub_names': ['John', 'Doe', 'Jane'] }; - final String? viewID = await Countly.instance.views.startView("Dashboard", segmentation); + await Countly.instance.views.startView('Dashboard', segmentation); // IMMEDIATE CALLS await Countly.instance.content.enterContentZone(); @@ -106,7 +106,7 @@ Future callAllFeatures() async { await Future.delayed(const Duration(seconds: 2)); await Countly.instance.sessions.updateSession(); - await Countly.instance.views.stopViewWithID(viewID!); + await Countly.instance.views.stopViewWithName('Dashboard'); await Countly.instance.content.refreshContentZone(); await Future.delayed(const Duration(seconds: 2)); From b2f4262fb2a8111927d5a4157401ee11002a43a6 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 2 Jul 2025 10:06:20 +0300 Subject: [PATCH 11/64] feat: apply custom handler delay beforehand --- example/integration_test/utils.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/example/integration_test/utils.dart b/example/integration_test/utils.dart index 1609600c..2b3f4fe8 100644 --- a/example/integration_test/utils.dart +++ b/example/integration_test/utils.dart @@ -159,13 +159,13 @@ void createServer(List>> requestArray, {int delay = 0, // Store the request parameters for later verification requestArray.add(queryParams); + if (_serverDelay > 0) { + print('[Test Server][${DateTime.now().toIso8601String()}] Applying delay of ${_serverDelay} seconds'); + await Future.delayed(Duration(seconds: _serverDelay)); + } if (customHandler != null) { await customHandler(request, queryParams, request.response); } else { - if (_serverDelay > 0) { - print('[Test Server][${DateTime.now().toIso8601String()}] Applying delay of ${_serverDelay} seconds'); - await Future.delayed(Duration(seconds: _serverDelay)); - } // Default response request.response ..statusCode = HttpStatus.ok From 82dd61bba8fec15851accc4d689706baa2b9a270 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 2 Jul 2025 11:20:28 +0300 Subject: [PATCH 12/64] feat: backoff to the base test --- .../sbs_tests/SBS_000_base_test.dart | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/example/integration_test/sbs_tests/SBS_000_base_test.dart b/example/integration_test/sbs_tests/SBS_000_base_test.dart index 4173a286..40f21dcc 100644 --- a/example/integration_test/sbs_tests/SBS_000_base_test.dart +++ b/example/integration_test/sbs_tests/SBS_000_base_test.dart @@ -11,12 +11,20 @@ import 'sbs_utils.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('SBS_000_base', (WidgetTester tester) async { + int serverDelay = 0; List>> requestArray = >>[]; createServer(requestArray, customHandler: (request, queryParams, response) async { Map responseJson = {'result': 'Success'}; if (queryParams.containsKey('method') && queryParams['method']!.first == 'feedback') { responseJson = {'result': []}; } + + if (serverDelay > 0 && !queryParams.containsKey('events')) { + // this orientation check for avoiding delay on backoff check + // to validate end session sent after 60 seconds + await Future.delayed(Duration(seconds: serverDelay)); + } + response ..statusCode = HttpStatus.ok ..headers.contentType = ContentType.json @@ -30,17 +38,28 @@ void main() { await callAllFeatures(); print(requestArray); - expect(true, requestArray.any((item) => item.containsKey('events'))); - expect(true, requestArray.any((item) => item.containsKey('location'))); //x2 - expect(true, requestArray.any((item) => item.containsKey('crash'))); //x2 - expect(true, requestArray.any((item) => item.containsKey('begin_session'))); - expect(true, requestArray.any((item) => item.containsKey('end_session'))); - expect(true, requestArray.any((item) => item.containsKey('session_duration') && !item.containsKey('end_session'))); - expect(true, requestArray.any((item) => item.containsKey('session_duration') && item.containsKey('end_session'))); - expect(true, requestArray.any((item) => item.containsKey('apm'))); //x2 - expect(true, requestArray.any((item) => item.containsKey('user_details'))); //x2 - + List RQ = await getRequestQueue(); + List EQ = await getEventQueue(); + expect(RQ.length, 0); + expect(EQ.length, 0); + validateRequestCounts({'events': 2, 'location': 1, 'crash': 2, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1}, requestArray); validateInternalEventCounts({'orientation': 1, 'view': 6}, requestArray); validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); + + await Countly.instance.content.exitContentZone(); + requestArray.clear(); + + serverDelay = 11; + + await Countly.instance.sessions.beginSession(); + await Countly.instance.sessions.endSession(); // this will be backed off for 60 seconds + await Countly.instance.attemptToSendStoredRequests(); + + validateRequestCounts({'begin_session': 1}, requestArray); + await Countly.instance.attemptToSendStoredRequests(); // this will not take effect + expect(requestArray.length, 1); + + await Future.delayed(const Duration(seconds: 75)); + validateRequestCounts({'begin_session': 1, 'end_session': 1}, requestArray); }); } From 310f73d440df3b9a64066993a6942ed61c07f3f5 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 2 Jul 2025 11:20:56 +0300 Subject: [PATCH 13/64] fix: revert delay with custom handler --- example/integration_test/utils.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/example/integration_test/utils.dart b/example/integration_test/utils.dart index 2b3f4fe8..1609600c 100644 --- a/example/integration_test/utils.dart +++ b/example/integration_test/utils.dart @@ -159,13 +159,13 @@ void createServer(List>> requestArray, {int delay = 0, // Store the request parameters for later verification requestArray.add(queryParams); - if (_serverDelay > 0) { - print('[Test Server][${DateTime.now().toIso8601String()}] Applying delay of ${_serverDelay} seconds'); - await Future.delayed(Duration(seconds: _serverDelay)); - } if (customHandler != null) { await customHandler(request, queryParams, request.response); } else { + if (_serverDelay > 0) { + print('[Test Server][${DateTime.now().toIso8601String()}] Applying delay of ${_serverDelay} seconds'); + await Future.delayed(Duration(seconds: _serverDelay)); + } // Default response request.response ..statusCode = HttpStatus.ok From 996cba43c768af74363539658f7c0ca415ce041b Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 2 Jul 2025 11:21:57 +0300 Subject: [PATCH 14/64] feat: request counter to sbs utils --- .../integration_test/sbs_tests/sbs_utils.dart | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/example/integration_test/sbs_tests/sbs_utils.dart b/example/integration_test/sbs_tests/sbs_utils.dart index aa8d876e..8b88850d 100644 --- a/example/integration_test/sbs_tests/sbs_utils.dart +++ b/example/integration_test/sbs_tests/sbs_utils.dart @@ -116,3 +116,29 @@ Future callAllFeatures() async { // check queues are empty and all requests are sent await Future.delayed(const Duration(seconds: 10)); } + +/// Validates the request counts in the request array. +/// This function checks the number of requests for each method recorded in the request array +/// and compares them with the expected counts provided in the `requests` map. +/// It expects the keys in the `requests` map to be the method names (like 'events', 'location', 'crash', etc.) +/// and the values to be the expected counts of those methods. +/// @param requests A map where keys are the names of the request methods and values are the expected counts. like {'events': 2, 'location': 1, 'crash': 2, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1} +/// @param requestArray The array of requests to validate against. +void validateRequestCounts(Map requests, List>> requestArray) { + Map actualRequests = {}; + + // key is method values are the switch cases + for (var request in requestArray) { + for (var entry in requests.entries) { + if (request.containsKey(entry.key)) { + actualRequests[entry.key] = (actualRequests[entry.key] ?? 0) + 1; + } + } + } + + expect(actualRequests.length, requests.length, reason: 'Mismatch in number of request methods'); + // Validate the counts + for (var entry in requests.entries) { + expect(actualRequests[entry.key], entry.value, reason: 'Mismatch for method ${entry.key}'); + } +} From 17b85894df5664ca6f7338a69f29c719492d52af Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 2 Jul 2025 11:25:41 +0300 Subject: [PATCH 15/64] feat: sbs order test --- .../sbs_tests/SBS_200_sbs_order_test.dart | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 example/integration_test/sbs_tests/SBS_200_sbs_order_test.dart diff --git a/example/integration_test/sbs_tests/SBS_200_sbs_order_test.dart b/example/integration_test/sbs_tests/SBS_200_sbs_order_test.dart new file mode 100644 index 00000000..75dde6d8 --- /dev/null +++ b/example/integration_test/sbs_tests/SBS_200_sbs_order_test.dart @@ -0,0 +1,186 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:countly_flutter/countly_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../utils.dart'; +import 'sbs_utils.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + testWidgets('SBS_200_sbs_order', (WidgetTester tester) async { + List>> requestArray = >>[]; + createServer(requestArray, customHandler: (request, queryParams, response) async { + Map responseJson = {'result': 'Success'}; + if (queryParams.containsKey('method')) { + if (queryParams['method']!.first == 'feedback') { + responseJson = {'result': []}; + } else if (queryParams['method']!.first == 'sc') { + responseJson = { + 'v': 1, + 't': 1750748806695, + 'c': { + 'ecz': false, + 'crt': true, + 'vt': false, + 'st': true, + 'cet': true, + 'cr': false, + 'log': true, + 'tracking': true, + 'networking': true, + 'ebs': 10, + 'czi': 30, + 'dort': 0, + 'lkl': 128, + 'lvs': 256, + 'lsv': 100, + 'lbc': 100, + 'scui': 4, + 'ltlpt': 30, + 'ltl': 200, + 'lt': false, + 'rcz': true, + 'bom': true, + 'bom_d': 60, + 'bom_at': 10, + 'bom_rqp': 0.5, + 'bom_ra': 24 + } + }; + } + } + response + ..statusCode = HttpStatus.ok + ..headers.contentType = ContentType.json + ..headers.set('Access-Control-Allow-Origin', '*') + ..write(jsonEncode(responseJson)); + }); + + setServerConfig({ + 'v': 1, + 't': 1750748806687, + 'c': { + 'ecz': false, + 'crt': true, + 'vt': true, + 'st': true, + 'cet': true, + 'cr': false, + 'log': true, + 'tracking': true, + 'networking': true, + 'sui': 60, + 'ebs': 10, + 'czi': 30, + 'dort': 0, + 'lkl': 128, + 'lvs': 256, + 'lsv': 100, + 'lbc': 100, + 'scui': 4, + 'ltlpt': 30, + 'ltl': 200, + 'lt': false, + 'rcz': false, + 'bom': true, + 'bom_d': 60, + 'bom_at': 10, + 'bom_rqp': 0.5, + 'bom_ra': 24 + } + }); + + const String providedSBS = ''' +{ + "v": 1, + "t": 1750748806688, + "c": { + "ecz": false, + "crt": true, + "vt": true, + "st": true, + "cet": true, + "cr": false, + "log": true, + "tracking": true, + "networking": true, + "sui": 60, + "ebs": 10, + "czi": 30, + "dort": 0, + "lkl": 128, + "lvs": 256, + "lsv": 100, + "lbc": 100, + "scui": 4, + "ltlpt": 30, + "ltl": 200, + "lt": false, + "rcz": false, + "bom": true, + "bom_d": 60, + "bom_at": 10, + "bom_rqp": 0.5, + "bom_ra": 24 + } +} +'''; + + // Initialize the SDK + CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); + config.setSDKBehaviorSettings(providedSBS); + config.setMaxRequestQueueSize(5); + config.setEventQueueSizeToSend(5); + config.setUpdateSessionTimerDelay(30); + await Countly.initWithConfig(config); + + // EQ and RQ is overridden by dev provided config from defaults to 5 + // EQ RQ is 5 always but timer delay overridden to 60 by provided SBS + // rcz and vt disabled by server given config + // and at last timestamp is 1750748806695 + + await callAllFeatures(); + + print(requestArray); + expect(await getServerConfig(), { + 'v': 1, + 't': 1750748806695, + 'c': { + 'ecz': false, + 'crt': true, + 'vt': false, + 'st': true, + 'cet': true, + 'cr': false, + 'log': true, + 'tracking': true, + 'networking': true, + 'sui': 60, + 'ebs': 10, + 'czi': 30, + 'dort': 0, + 'lkl': 128, + 'lvs': 256, + 'lsv': 100, + 'lbc': 100, + 'scui': 4, + 'ltlpt': 30, + 'ltl': 200, + 'lt': false, + 'rcz': true, + 'bom': true, + 'bom_d': 60, + 'bom_at': 10, + 'bom_rqp': 0.5, + 'bom_ra': 24 + } + }); + + validateRequestCounts({'events': 8, 'location': 1, 'crash': 2, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1}, requestArray); + validateInternalEventCounts({'orientation': 1}, requestArray); + validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); + }); +} From e2006eebf8180442d72ef9e09b004a3dad010f9b Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 2 Jul 2025 14:48:04 +0300 Subject: [PATCH 16/64] feat: introduce order tests --- .../sbs_tests/SBS_200_sbs_order_test.dart | 186 ------------------ .../sbs_tests/SBS_201A_DP_FS_test.dart | 56 ++++++ 2 files changed, 56 insertions(+), 186 deletions(-) delete mode 100644 example/integration_test/sbs_tests/SBS_200_sbs_order_test.dart create mode 100644 example/integration_test/sbs_tests/SBS_201A_DP_FS_test.dart diff --git a/example/integration_test/sbs_tests/SBS_200_sbs_order_test.dart b/example/integration_test/sbs_tests/SBS_200_sbs_order_test.dart deleted file mode 100644 index 75dde6d8..00000000 --- a/example/integration_test/sbs_tests/SBS_200_sbs_order_test.dart +++ /dev/null @@ -1,186 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:countly_flutter/countly_flutter.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:integration_test/integration_test.dart'; - -import '../utils.dart'; -import 'sbs_utils.dart'; - -void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - testWidgets('SBS_200_sbs_order', (WidgetTester tester) async { - List>> requestArray = >>[]; - createServer(requestArray, customHandler: (request, queryParams, response) async { - Map responseJson = {'result': 'Success'}; - if (queryParams.containsKey('method')) { - if (queryParams['method']!.first == 'feedback') { - responseJson = {'result': []}; - } else if (queryParams['method']!.first == 'sc') { - responseJson = { - 'v': 1, - 't': 1750748806695, - 'c': { - 'ecz': false, - 'crt': true, - 'vt': false, - 'st': true, - 'cet': true, - 'cr': false, - 'log': true, - 'tracking': true, - 'networking': true, - 'ebs': 10, - 'czi': 30, - 'dort': 0, - 'lkl': 128, - 'lvs': 256, - 'lsv': 100, - 'lbc': 100, - 'scui': 4, - 'ltlpt': 30, - 'ltl': 200, - 'lt': false, - 'rcz': true, - 'bom': true, - 'bom_d': 60, - 'bom_at': 10, - 'bom_rqp': 0.5, - 'bom_ra': 24 - } - }; - } - } - response - ..statusCode = HttpStatus.ok - ..headers.contentType = ContentType.json - ..headers.set('Access-Control-Allow-Origin', '*') - ..write(jsonEncode(responseJson)); - }); - - setServerConfig({ - 'v': 1, - 't': 1750748806687, - 'c': { - 'ecz': false, - 'crt': true, - 'vt': true, - 'st': true, - 'cet': true, - 'cr': false, - 'log': true, - 'tracking': true, - 'networking': true, - 'sui': 60, - 'ebs': 10, - 'czi': 30, - 'dort': 0, - 'lkl': 128, - 'lvs': 256, - 'lsv': 100, - 'lbc': 100, - 'scui': 4, - 'ltlpt': 30, - 'ltl': 200, - 'lt': false, - 'rcz': false, - 'bom': true, - 'bom_d': 60, - 'bom_at': 10, - 'bom_rqp': 0.5, - 'bom_ra': 24 - } - }); - - const String providedSBS = ''' -{ - "v": 1, - "t": 1750748806688, - "c": { - "ecz": false, - "crt": true, - "vt": true, - "st": true, - "cet": true, - "cr": false, - "log": true, - "tracking": true, - "networking": true, - "sui": 60, - "ebs": 10, - "czi": 30, - "dort": 0, - "lkl": 128, - "lvs": 256, - "lsv": 100, - "lbc": 100, - "scui": 4, - "ltlpt": 30, - "ltl": 200, - "lt": false, - "rcz": false, - "bom": true, - "bom_d": 60, - "bom_at": 10, - "bom_rqp": 0.5, - "bom_ra": 24 - } -} -'''; - - // Initialize the SDK - CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); - config.setSDKBehaviorSettings(providedSBS); - config.setMaxRequestQueueSize(5); - config.setEventQueueSizeToSend(5); - config.setUpdateSessionTimerDelay(30); - await Countly.initWithConfig(config); - - // EQ and RQ is overridden by dev provided config from defaults to 5 - // EQ RQ is 5 always but timer delay overridden to 60 by provided SBS - // rcz and vt disabled by server given config - // and at last timestamp is 1750748806695 - - await callAllFeatures(); - - print(requestArray); - expect(await getServerConfig(), { - 'v': 1, - 't': 1750748806695, - 'c': { - 'ecz': false, - 'crt': true, - 'vt': false, - 'st': true, - 'cet': true, - 'cr': false, - 'log': true, - 'tracking': true, - 'networking': true, - 'sui': 60, - 'ebs': 10, - 'czi': 30, - 'dort': 0, - 'lkl': 128, - 'lvs': 256, - 'lsv': 100, - 'lbc': 100, - 'scui': 4, - 'ltlpt': 30, - 'ltl': 200, - 'lt': false, - 'rcz': true, - 'bom': true, - 'bom_d': 60, - 'bom_at': 10, - 'bom_rqp': 0.5, - 'bom_ra': 24 - } - }); - - validateRequestCounts({'events': 8, 'location': 1, 'crash': 2, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1}, requestArray); - validateInternalEventCounts({'orientation': 1}, requestArray); - validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); - }); -} diff --git a/example/integration_test/sbs_tests/SBS_201A_DP_FS_test.dart b/example/integration_test/sbs_tests/SBS_201A_DP_FS_test.dart new file mode 100644 index 00000000..ab4ec587 --- /dev/null +++ b/example/integration_test/sbs_tests/SBS_201A_DP_FS_test.dart @@ -0,0 +1,56 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:countly_flutter/countly_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../utils.dart'; +import 'sbs_utils.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + testWidgets('SBS_201A_DP_FS_test', (WidgetTester tester) async { + List>> requestArray = >>[]; + createServer(requestArray, customHandler: (request, queryParams, response) async { + Map responseJson = {'result': 'Success'}; + if (queryParams.containsKey('method')) { + if (queryParams['method']!.first == 'feedback') { + responseJson = {'result': []}; + } else if (queryParams['method']!.first == 'sc') { + responseJson = { + 'v': 1, + 't': 1750748806695, + 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': true, 'log': false, 'dort': 12, 'lkl': 120, 'lvs': 255, 'lsv': 99, 'lbc': 99, 'ltlpt': 29, 'ltl': 199, 'rcz': true, 'bom': true} + }; + } + } + response + ..statusCode = HttpStatus.ok + ..headers.contentType = ContentType.json + ..headers.set('Access-Control-Allow-Origin', '*') + ..write(jsonEncode(responseJson)); + }); + + // Initialize the SDK + CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); + config.setMaxRequestQueueSize(5).setEventQueueSizeToSend(5).disableBackoffMechanism().setRequiresConsent(true).disableLocation().setRequestDropAgeHours(5).setUpdateSessionTimerDelay(75); + config.content.setZoneTimerInterval(17); + config.sdkInternalLimits.setMaxBreadcrumbCount(1).setMaxKeyLength(3).setMaxSegmentationValues(3).setMaxValueSize(5).setMaxStackTraceLineLength(300).setMaxStackTraceLinesPerThread(2); + + await Countly.initWithConfig(config); + await Future.delayed(const Duration(seconds: 2)); + + await callAllFeatures(); + + expect(await getServerConfig(), { + 'v': 1, + 't': 1750748806695, + 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': true, 'log': false, 'dort': 12, 'lkl': 120, 'lvs': 255, 'lsv': 99, 'lbc': 99, 'ltlpt': 29, 'ltl': 199, 'rcz': true, 'bom': true} + }); + + validateRequestCounts({'events': 8, 'location': 2, 'crash': 0, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1, 'consent': 1}, requestArray); + validateInternalEventCounts({'orientation': 1}, requestArray); + validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); + }); +} From bd07ca538925a28eeca6873c068d3153ef194ff3 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 2 Jul 2025 15:09:01 +0300 Subject: [PATCH 17/64] feat: DP_P order test --- .../sbs_tests/SBS_201B_DP_P_test.dart | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 example/integration_test/sbs_tests/SBS_201B_DP_P_test.dart diff --git a/example/integration_test/sbs_tests/SBS_201B_DP_P_test.dart b/example/integration_test/sbs_tests/SBS_201B_DP_P_test.dart new file mode 100644 index 00000000..1f746ff6 --- /dev/null +++ b/example/integration_test/sbs_tests/SBS_201B_DP_P_test.dart @@ -0,0 +1,51 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:countly_flutter/countly_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../utils.dart'; +import 'sbs_utils.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + testWidgets('SBS_201A_DP_P_test', (WidgetTester tester) async { + List>> requestArray = >>[]; + createServer(requestArray, customHandler: (request, queryParams, response) async { + Map responseJson = {'result': 'Success'}; + if (queryParams.containsKey('method')) { + if (queryParams['method']!.first == 'feedback') { + responseJson = {'result': []}; + } + } + response + ..statusCode = HttpStatus.ok + ..headers.contentType = ContentType.json + ..headers.set('Access-Control-Allow-Origin', '*') + ..write(jsonEncode(responseJson)); + }); + + // Initialize the SDK + CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); + config.setSDKBehaviorSettings('{"v":1,"t":1750748806695,"c":{"crt":false,"vt":false,"st":true,"cr":false,"cet":true,"log":false,"dort":12,"lkl":120,"lvs":255,"lsv":99,"lbc":99,"ltlpt":29,"ltl":199,"rcz":true,"bom":true}}'); + config.setMaxRequestQueueSize(5).setEventQueueSizeToSend(5).disableBackoffMechanism().setRequiresConsent(true).disableLocation().setRequestDropAgeHours(5).setUpdateSessionTimerDelay(75); + config.content.setZoneTimerInterval(17); + config.sdkInternalLimits.setMaxBreadcrumbCount(1).setMaxKeyLength(3).setMaxSegmentationValues(3).setMaxValueSize(5).setMaxStackTraceLineLength(300).setMaxStackTraceLinesPerThread(2); + + await Countly.initWithConfig(config); + await Future.delayed(const Duration(seconds: 2)); + + await callAllFeatures(); + + expect(await getServerConfig(), { + 'v': 1, + 't': 1750748806695, + 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': true, 'log': false, 'dort': 12, 'lkl': 120, 'lvs': 255, 'lsv': 99, 'lbc': 99, 'ltlpt': 29, 'ltl': 199, 'rcz': true, 'bom': true} + }); + + validateRequestCounts({'events': 8, 'location': 3, 'crash': 0, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1, 'consent': 0}, requestArray); + validateInternalEventCounts({'orientation': 1}, requestArray); + validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); + }); +} From 978b04c414ad1cbd3cac4511c30c7464d999d591 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 2 Jul 2025 15:11:39 +0300 Subject: [PATCH 18/64] doc: add line comment to the internal event validation function --- example/integration_test/sbs_tests/sbs_utils.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/integration_test/sbs_tests/sbs_utils.dart b/example/integration_test/sbs_tests/sbs_utils.dart index 8b88850d..06255f30 100644 --- a/example/integration_test/sbs_tests/sbs_utils.dart +++ b/example/integration_test/sbs_tests/sbs_utils.dart @@ -36,6 +36,7 @@ void validateImmediateCounts(Map immediates, List requests, List Date: Wed, 2 Jul 2025 15:48:36 +0300 Subject: [PATCH 19/64] feat: dp p fs test --- .../sbs_tests/SBS_201B_DP_P_test.dart | 2 +- .../sbs_tests/SBS_201C_DP_P_FS_test.dart | 57 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 example/integration_test/sbs_tests/SBS_201C_DP_P_FS_test.dart diff --git a/example/integration_test/sbs_tests/SBS_201B_DP_P_test.dart b/example/integration_test/sbs_tests/SBS_201B_DP_P_test.dart index 1f746ff6..21d35b90 100644 --- a/example/integration_test/sbs_tests/SBS_201B_DP_P_test.dart +++ b/example/integration_test/sbs_tests/SBS_201B_DP_P_test.dart @@ -10,7 +10,7 @@ import 'sbs_utils.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - testWidgets('SBS_201A_DP_P_test', (WidgetTester tester) async { + testWidgets('SBS_201B_DP_P_test', (WidgetTester tester) async { List>> requestArray = >>[]; createServer(requestArray, customHandler: (request, queryParams, response) async { Map responseJson = {'result': 'Success'}; diff --git a/example/integration_test/sbs_tests/SBS_201C_DP_P_FS_test.dart b/example/integration_test/sbs_tests/SBS_201C_DP_P_FS_test.dart new file mode 100644 index 00000000..e46ca0d9 --- /dev/null +++ b/example/integration_test/sbs_tests/SBS_201C_DP_P_FS_test.dart @@ -0,0 +1,57 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:countly_flutter/countly_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../utils.dart'; +import 'sbs_utils.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + testWidgets('SBS_201C_DP_P_FS_test', (WidgetTester tester) async { + List>> requestArray = >>[]; + createServer(requestArray, customHandler: (request, queryParams, response) async { + Map responseJson = {'result': 'Success'}; + if (queryParams.containsKey('method')) { + if (queryParams['method']!.first == 'feedback') { + responseJson = {'result': []}; + } else if (queryParams['method']!.first == 'sc') { + responseJson = { + 'v': 1, + 't': 1750748806695, + 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': false, 'log': true, 'dort': 21, 'lkl': 67, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} + }; + } + } + response + ..statusCode = HttpStatus.ok + ..headers.contentType = ContentType.json + ..headers.set('Access-Control-Allow-Origin', '*') + ..write(jsonEncode(responseJson)); + }); + + // Initialize the SDK + CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); + config.setSDKBehaviorSettings('{"v":1,"t":1750748806695,"c":{"crt":false,"vt":false,"st":true,"cr":false,"cet":true,"log":false,"dort":12,"lkl":120,"lvs":255,"lsv":99,"lbc":99,"ltlpt":29,"ltl":199,"rcz":true,"bom":true}}'); + config.setMaxRequestQueueSize(5).setEventQueueSizeToSend(5).disableBackoffMechanism().setRequiresConsent(true).disableLocation().setRequestDropAgeHours(5).setUpdateSessionTimerDelay(75); + config.content.setZoneTimerInterval(17); + config.sdkInternalLimits.setMaxBreadcrumbCount(1).setMaxKeyLength(3).setMaxSegmentationValues(3).setMaxValueSize(5).setMaxStackTraceLineLength(300).setMaxStackTraceLinesPerThread(2); + + await Countly.initWithConfig(config); + await Future.delayed(const Duration(seconds: 2)); + + await callAllFeatures(); + + expect(await getServerConfig(), { + 'v': 1, + 't': 1750748806695, + 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': false, 'log': true, 'dort': 21, 'lkl': 67, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} + }); + + validateRequestCounts({'events': 1, 'location': 3, 'crash': 0, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1, 'consent': 0}, requestArray); + validateInternalEventCounts({'orientation': 1}, requestArray); + validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); + }); +} From 55e79f5dc4e493574facdc8fd4a4e6254aa7eec5 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 2 Jul 2025 15:50:40 +0300 Subject: [PATCH 20/64] feat: dp s fs test --- .../sbs_tests/SBS_201D_DP_S_FS_test.dart | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 example/integration_test/sbs_tests/SBS_201D_DP_S_FS_test.dart diff --git a/example/integration_test/sbs_tests/SBS_201D_DP_S_FS_test.dart b/example/integration_test/sbs_tests/SBS_201D_DP_S_FS_test.dart new file mode 100644 index 00000000..a130875d --- /dev/null +++ b/example/integration_test/sbs_tests/SBS_201D_DP_S_FS_test.dart @@ -0,0 +1,61 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:countly_flutter/countly_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../utils.dart'; +import 'sbs_utils.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + testWidgets('SBS_201D_DP_S_FS_test', (WidgetTester tester) async { + List>> requestArray = >>[]; + createServer(requestArray, customHandler: (request, queryParams, response) async { + Map responseJson = {'result': 'Success'}; + if (queryParams.containsKey('method')) { + if (queryParams['method']!.first == 'feedback') { + responseJson = {'result': []}; + } else if (queryParams['method']!.first == 'sc') { + responseJson = { + 'v': 1, + 't': 1750748806695, + 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': false, 'log': true, 'dort': 21, 'lkl': 67, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} + }; + } + } + response + ..statusCode = HttpStatus.ok + ..headers.contentType = ContentType.json + ..headers.set('Access-Control-Allow-Origin', '*') + ..write(jsonEncode(responseJson)); + }); + + setServerConfig({ + 'v': 1, + 't': 1750748806695, + 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': true, 'log': false, 'dort': 12, 'lkl': 120, 'lvs': 255, 'lsv': 99, 'lbc': 99, 'ltlpt': 29, 'ltl': 199, 'rcz': true, 'bom': true} + }); + // Initialize the SDK + CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); + config.setMaxRequestQueueSize(5).setEventQueueSizeToSend(5).disableBackoffMechanism().setRequiresConsent(true).disableLocation().setRequestDropAgeHours(5).setUpdateSessionTimerDelay(75); + config.content.setZoneTimerInterval(17); + config.sdkInternalLimits.setMaxBreadcrumbCount(1).setMaxKeyLength(3).setMaxSegmentationValues(3).setMaxValueSize(5).setMaxStackTraceLineLength(300).setMaxStackTraceLinesPerThread(2); + + await Countly.initWithConfig(config); + await Future.delayed(const Duration(seconds: 2)); + + await callAllFeatures(); + + expect(await getServerConfig(), { + 'v': 1, + 't': 1750748806695, + 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': false, 'log': true, 'dort': 21, 'lkl': 67, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} + }); + + validateRequestCounts({'events': 1, 'location': 3, 'crash': 0, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1, 'consent': 0}, requestArray); + validateInternalEventCounts({'orientation': 1}, requestArray); + validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); + }); +} From 2b000fe3f33a073dbd92730eb331ae165c924f0f Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 2 Jul 2025 16:23:38 +0300 Subject: [PATCH 21/64] feat: dp s p fs test --- .../sbs_tests/SBS_201D_DP_S_FS_test.dart | 8 +-- .../sbs_tests/SBS_201E_DP_S_P_FS_test.dart | 63 +++++++++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 example/integration_test/sbs_tests/SBS_201E_DP_S_P_FS_test.dart diff --git a/example/integration_test/sbs_tests/SBS_201D_DP_S_FS_test.dart b/example/integration_test/sbs_tests/SBS_201D_DP_S_FS_test.dart index a130875d..e478f266 100644 --- a/example/integration_test/sbs_tests/SBS_201D_DP_S_FS_test.dart +++ b/example/integration_test/sbs_tests/SBS_201D_DP_S_FS_test.dart @@ -21,7 +21,7 @@ void main() { responseJson = { 'v': 1, 't': 1750748806695, - 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': false, 'log': true, 'dort': 21, 'lkl': 67, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} + 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': true, 'log': true, 'dort': 21, 'lkl': 67, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} }; } } @@ -35,7 +35,7 @@ void main() { setServerConfig({ 'v': 1, 't': 1750748806695, - 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': true, 'log': false, 'dort': 12, 'lkl': 120, 'lvs': 255, 'lsv': 99, 'lbc': 99, 'ltlpt': 29, 'ltl': 199, 'rcz': true, 'bom': true} + 'c': {'crt': false, 'vt': false, 'st': true, 'rqs': 100, 'eqs': 20, 'cr': false, 'cet': true, 'log': false, 'dort': 12, 'lkl': 120, 'lvs': 255, 'lsv': 99, 'lbc': 99, 'ltlpt': 29, 'ltl': 199, 'rcz': true, 'bom': true} }); // Initialize the SDK CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); @@ -51,10 +51,10 @@ void main() { expect(await getServerConfig(), { 'v': 1, 't': 1750748806695, - 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': false, 'log': true, 'dort': 21, 'lkl': 67, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} + 'c': {'crt': false, 'vt': false, 'st': true, 'rqs': 100, 'eqs': 20, 'cr': false, 'cet': true, 'log': true, 'dort': 21, 'lkl': 67, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} }); - validateRequestCounts({'events': 1, 'location': 3, 'crash': 0, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1, 'consent': 0}, requestArray); + validateRequestCounts({'events': 3, 'location': 3, 'crash': 0, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1, 'consent': 0}, requestArray); validateInternalEventCounts({'orientation': 1}, requestArray); validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); }); diff --git a/example/integration_test/sbs_tests/SBS_201E_DP_S_P_FS_test.dart b/example/integration_test/sbs_tests/SBS_201E_DP_S_P_FS_test.dart new file mode 100644 index 00000000..d392e917 --- /dev/null +++ b/example/integration_test/sbs_tests/SBS_201E_DP_S_P_FS_test.dart @@ -0,0 +1,63 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:countly_flutter/countly_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../utils.dart'; +import 'sbs_utils.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + testWidgets('SBS_201E_DP_S_P_FS_test', (WidgetTester tester) async { + List>> requestArray = >>[]; + createServer(requestArray, customHandler: (request, queryParams, response) async { + Map responseJson = {'result': 'Success'}; + if (queryParams.containsKey('method')) { + if (queryParams['method']!.first == 'feedback') { + responseJson = {'result': []}; + } else if (queryParams['method']!.first == 'sc') { + responseJson = { + 'v': 1, + 't': 1750748806695, + 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': true, 'log': true, 'dort': 21, 'lkl': 67, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} + }; + } + } + response + ..statusCode = HttpStatus.ok + ..headers.contentType = ContentType.json + ..headers.set('Access-Control-Allow-Origin', '*') + ..write(jsonEncode(responseJson)); + }); + + setServerConfig({ + 'v': 1, + 't': 1750748806695, + 'c': {'crt': false, 'vt': false, 'st': true, 'rqs': 100, 'cr': false, 'cet': true, 'log': false, 'dort': 12, 'lkl': 120, 'lvs': 255, 'lsv': 99, 'lbc': 99, 'ltlpt': 29, 'ltl': 199, 'rcz': true, 'bom': true} + }); + // Initialize the SDK + CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); + config.setSDKBehaviorSettings('{"v":1,"t":1750748806695,"c":{"crt":true,"vt":false,"eqs": 30,"st":true,"cr":false,"cet":true,"log":false,"dort":12,"lkl":120,"lvs":255,"lsv":99,"lbc":99,"ltlpt":29,"ltl":199,"rcz":true,"bom":true}}'); + config.setMaxRequestQueueSize(5).setEventQueueSizeToSend(5).disableBackoffMechanism().setRequiresConsent(true).disableLocation().setRequestDropAgeHours(5).setUpdateSessionTimerDelay(75); + config.content.setZoneTimerInterval(17); + config.sdkInternalLimits.setMaxBreadcrumbCount(1).setMaxKeyLength(3).setMaxSegmentationValues(3).setMaxValueSize(5).setMaxStackTraceLineLength(300).setMaxStackTraceLinesPerThread(2); + + await Countly.initWithConfig(config); + await Future.delayed(const Duration(seconds: 2)); + + await callAllFeatures(); + +// EQS not exist because there are already stored SBS + expect(await getServerConfig(), { + 'v': 1, + 't': 1750748806695, + 'c': {'crt': false, 'vt': false, 'st': true, 'rqs': 100, 'cr': false, 'cet': true, 'log': true, 'dort': 21, 'lkl': 67, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} + }); + + validateRequestCounts({'events': 8, 'location': 3, 'crash': 0, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1, 'consent': 0}, requestArray); + validateInternalEventCounts({'orientation': 1}, requestArray); + validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); + }); +} From 1f36955e88f57451d0df95c91e32d110337c07f5 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 2 Jul 2025 18:13:36 +0300 Subject: [PATCH 22/64] feat: base SBS test ios --- example/integration_test/sbs_tests/SBS_000_base_test.dart | 4 ++-- example/integration_test/sbs_tests/notes.md | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/example/integration_test/sbs_tests/SBS_000_base_test.dart b/example/integration_test/sbs_tests/SBS_000_base_test.dart index 40f21dcc..b4e1f0d6 100644 --- a/example/integration_test/sbs_tests/SBS_000_base_test.dart +++ b/example/integration_test/sbs_tests/SBS_000_base_test.dart @@ -42,7 +42,7 @@ void main() { List EQ = await getEventQueue(); expect(RQ.length, 0); expect(EQ.length, 0); - validateRequestCounts({'events': 2, 'location': 1, 'crash': 2, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1}, requestArray); + validateRequestCounts({'events': 2, 'location': 1, 'crash': 2, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': Platform.isIOS ? 2 : 1}, requestArray); validateInternalEventCounts({'orientation': 1, 'view': 6}, requestArray); validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); @@ -59,7 +59,7 @@ void main() { await Countly.instance.attemptToSendStoredRequests(); // this will not take effect expect(requestArray.length, 1); - await Future.delayed(const Duration(seconds: 75)); + await Future.delayed(const Duration(seconds: 90)); // iOS required more time then Android validateRequestCounts({'begin_session': 1, 'end_session': 1}, requestArray); }); } diff --git a/example/integration_test/sbs_tests/notes.md b/example/integration_test/sbs_tests/notes.md index 9b61d589..842d0349 100644 --- a/example/integration_test/sbs_tests/notes.md +++ b/example/integration_test/sbs_tests/notes.md @@ -112,3 +112,8 @@ tests are: - 201C_DP_P_FS - 201D_DP_S_FS - 201E_DP_S_P_FS + +Notes iOS: +In the base test iOS required more time then Android at the end + +Sometimes iOS duplicates the requests but I could not find the main reason behind it. From 230401f9e0f8aec1d5de72b49c97b60ddf5e94e6 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 3 Jul 2025 11:54:36 +0300 Subject: [PATCH 23/64] feat: dp s p fs iOS --- .../sbs_tests/SBS_201E_DP_S_P_FS_test.dart | 3 ++- example/integration_test/sbs_tests/notes.md | 4 ++-- .../integration_test/sbs_tests/sbs_utils.dart | 20 ++++++++++++++----- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/example/integration_test/sbs_tests/SBS_201E_DP_S_P_FS_test.dart b/example/integration_test/sbs_tests/SBS_201E_DP_S_P_FS_test.dart index d392e917..1c8189dd 100644 --- a/example/integration_test/sbs_tests/SBS_201E_DP_S_P_FS_test.dart +++ b/example/integration_test/sbs_tests/SBS_201E_DP_S_P_FS_test.dart @@ -38,6 +38,7 @@ void main() { 'c': {'crt': false, 'vt': false, 'st': true, 'rqs': 100, 'cr': false, 'cet': true, 'log': false, 'dort': 12, 'lkl': 120, 'lvs': 255, 'lsv': 99, 'lbc': 99, 'ltlpt': 29, 'ltl': 199, 'rcz': true, 'bom': true} }); // Initialize the SDK + // in here eqs will not set because there is already stored SBS CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); config.setSDKBehaviorSettings('{"v":1,"t":1750748806695,"c":{"crt":true,"vt":false,"eqs": 30,"st":true,"cr":false,"cet":true,"log":false,"dort":12,"lkl":120,"lvs":255,"lsv":99,"lbc":99,"ltlpt":29,"ltl":199,"rcz":true,"bom":true}}'); config.setMaxRequestQueueSize(5).setEventQueueSizeToSend(5).disableBackoffMechanism().setRequiresConsent(true).disableLocation().setRequestDropAgeHours(5).setUpdateSessionTimerDelay(75); @@ -49,7 +50,7 @@ void main() { await callAllFeatures(); -// EQS not exist because there are already stored SBS + // EQS not exist because there are already stored SBS expect(await getServerConfig(), { 'v': 1, 't': 1750748806695, diff --git a/example/integration_test/sbs_tests/notes.md b/example/integration_test/sbs_tests/notes.md index 842d0349..95cd3ed3 100644 --- a/example/integration_test/sbs_tests/notes.md +++ b/example/integration_test/sbs_tests/notes.md @@ -115,5 +115,5 @@ tests are: Notes iOS: In the base test iOS required more time then Android at the end - -Sometimes iOS duplicates the requests but I could not find the main reason behind it. +Because there is a probability for iOS to duplicate requests, checking request counts were not good +getAvaliableFeedbackWidgets= if no consent it broken iOS diff --git a/example/integration_test/sbs_tests/sbs_utils.dart b/example/integration_test/sbs_tests/sbs_utils.dart index 06255f30..6452cbc8 100644 --- a/example/integration_test/sbs_tests/sbs_utils.dart +++ b/example/integration_test/sbs_tests/sbs_utils.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:io'; import 'package:countly_flutter/countly_flutter.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -69,9 +70,8 @@ void validateInternalEventCounts(Map internalEventsCounts, List callAllFeatures() async { - await Countly.getAvailableFeedbackWidgets(); - await Countly.giveAllConsent(); + await Countly.getAvailableFeedbackWidgets(); await Countly.instance.sessions.beginSession(); await createTruncableEvents(); await generateEvents(); @@ -137,8 +137,18 @@ void validateRequestCounts(Map requests, List Date: Thu, 3 Jul 2025 13:13:03 +0300 Subject: [PATCH 24/64] feat: test comments --- .../sbs_tests/SBS_000_base_test.dart | 4 +++- .../sbs_tests/SBS_201A_DP_FS_test.dart | 10 ++++++++++ .../sbs_tests/SBS_201B_DP_P_test.dart | 10 ++++++++++ .../sbs_tests/SBS_201C_DP_P_FS_test.dart | 15 +++++++++++++-- .../sbs_tests/SBS_201D_DP_S_FS_test.dart | 16 ++++++++++++++-- .../sbs_tests/SBS_201E_DP_S_P_FS_test.dart | 16 ++++++++++++++-- 6 files changed, 64 insertions(+), 7 deletions(-) diff --git a/example/integration_test/sbs_tests/SBS_000_base_test.dart b/example/integration_test/sbs_tests/SBS_000_base_test.dart index b4e1f0d6..7bdcde53 100644 --- a/example/integration_test/sbs_tests/SBS_000_base_test.dart +++ b/example/integration_test/sbs_tests/SBS_000_base_test.dart @@ -8,6 +8,8 @@ import 'package:integration_test/integration_test.dart'; import '../utils.dart'; import 'sbs_utils.dart'; +///This test calls all features possible +///It is base test, tries to show how features working without SBS and defaults void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('SBS_000_base', (WidgetTester tester) async { @@ -42,7 +44,7 @@ void main() { List EQ = await getEventQueue(); expect(RQ.length, 0); expect(EQ.length, 0); - validateRequestCounts({'events': 2, 'location': 1, 'crash': 2, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': Platform.isIOS ? 2 : 1}, requestArray); + validateRequestCounts({'events': 2, 'location': 1, 'crash': 2, 'begin_session': 1, 'consent': 0, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': Platform.isIOS ? 2 : 1}, requestArray); validateInternalEventCounts({'orientation': 1, 'view': 6}, requestArray); validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); diff --git a/example/integration_test/sbs_tests/SBS_201A_DP_FS_test.dart b/example/integration_test/sbs_tests/SBS_201A_DP_FS_test.dart index ab4ec587..81304e25 100644 --- a/example/integration_test/sbs_tests/SBS_201A_DP_FS_test.dart +++ b/example/integration_test/sbs_tests/SBS_201A_DP_FS_test.dart @@ -8,6 +8,16 @@ import 'package:integration_test/integration_test.dart'; import '../utils.dart'; import 'sbs_utils.dart'; +/// Test calls all possible configuration features and shows that: +/// - All internal limits are overridden by FS SBS and stored correctly +/// - FS overrides consent requirement to false, bom to true, request age drop hours to from 5 to 12, crash reporting to false, view reporting to false +/// - This test shows that FS is prior than DP +/// How it affects the SDK: +/// - event requests are increased if we compare it with base test +/// - location requests are increased because location is disabled in DP then later enabled while calling all features +/// - crash requests are not sent +/// - consent requests increased by one because it was enabled and disabled later +/// - no view events are sent void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('SBS_201A_DP_FS_test', (WidgetTester tester) async { diff --git a/example/integration_test/sbs_tests/SBS_201B_DP_P_test.dart b/example/integration_test/sbs_tests/SBS_201B_DP_P_test.dart index 21d35b90..32531614 100644 --- a/example/integration_test/sbs_tests/SBS_201B_DP_P_test.dart +++ b/example/integration_test/sbs_tests/SBS_201B_DP_P_test.dart @@ -8,6 +8,16 @@ import 'package:integration_test/integration_test.dart'; import '../utils.dart'; import 'sbs_utils.dart'; +/// Test calls all possible configuration features and shows that: +/// - All internal limits are overridden by P SBS and stored correctly +/// - P overrides consent requirement to false, bom to true, request age drop hours to from 5 to 12, crash reporting to false, view reporting to false +/// - This test shows that P is prior than DP +/// How it affects the SDK: +/// - event requests are increased if we compare it with base test +/// - location requests are increased because location is disabled in DP then later enabled while calling all features +/// - crash requests are not sent +/// - no consent request here because SDK did not wait for server response here, it got no time to form a consent request +/// - no view events are sent void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('SBS_201B_DP_P_test', (WidgetTester tester) async { diff --git a/example/integration_test/sbs_tests/SBS_201C_DP_P_FS_test.dart b/example/integration_test/sbs_tests/SBS_201C_DP_P_FS_test.dart index e46ca0d9..db90ea55 100644 --- a/example/integration_test/sbs_tests/SBS_201C_DP_P_FS_test.dart +++ b/example/integration_test/sbs_tests/SBS_201C_DP_P_FS_test.dart @@ -8,6 +8,17 @@ import 'package:integration_test/integration_test.dart'; import '../utils.dart'; import 'sbs_utils.dart'; +/// Test calls all possible configuration features and shows that: +/// - All internal limits are overridden by FS SBS and stored correctly +/// - P overrides consent requirement to false because FS does not have it +/// - FS overrides crt, vt to false, and it is more prior than P +/// - This test shows that FS is prior than DP and P, and P is prior than DP +/// How it affects the SDK: +/// - event requests reduces because cet is false FS +/// - location requests are increased because location is disabled in DP then later enabled while calling all features +/// - crash requests are not sent FS +/// - no consent request here because SDK did not wait for server response here, it got no time to form a consent request +/// - no view events are sent void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('SBS_201C_DP_P_FS_test', (WidgetTester tester) async { @@ -21,7 +32,7 @@ void main() { responseJson = { 'v': 1, 't': 1750748806695, - 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': false, 'log': true, 'dort': 21, 'lkl': 67, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} + 'c': {'crt': false, 'vt': false, 'st': true, 'cet': false, 'log': true, 'dort': 21, 'lkl': 67, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} }; } } @@ -34,7 +45,7 @@ void main() { // Initialize the SDK CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); - config.setSDKBehaviorSettings('{"v":1,"t":1750748806695,"c":{"crt":false,"vt":false,"st":true,"cr":false,"cet":true,"log":false,"dort":12,"lkl":120,"lvs":255,"lsv":99,"lbc":99,"ltlpt":29,"ltl":199,"rcz":true,"bom":true}}'); + config.setSDKBehaviorSettings('{"v":1,"t":1750748806695,"c":{"crt":true,"vt":true,"st":true,"cr":false,"cet":true,"log":false,"dort":12,"lkl":120,"lvs":255,"lsv":99,"lbc":99,"ltlpt":29,"ltl":199,"rcz":true,"bom":true}}'); config.setMaxRequestQueueSize(5).setEventQueueSizeToSend(5).disableBackoffMechanism().setRequiresConsent(true).disableLocation().setRequestDropAgeHours(5).setUpdateSessionTimerDelay(75); config.content.setZoneTimerInterval(17); config.sdkInternalLimits.setMaxBreadcrumbCount(1).setMaxKeyLength(3).setMaxSegmentationValues(3).setMaxValueSize(5).setMaxStackTraceLineLength(300).setMaxStackTraceLinesPerThread(2); diff --git a/example/integration_test/sbs_tests/SBS_201D_DP_S_FS_test.dart b/example/integration_test/sbs_tests/SBS_201D_DP_S_FS_test.dart index e478f266..7a57d219 100644 --- a/example/integration_test/sbs_tests/SBS_201D_DP_S_FS_test.dart +++ b/example/integration_test/sbs_tests/SBS_201D_DP_S_FS_test.dart @@ -8,6 +8,18 @@ import 'package:integration_test/integration_test.dart'; import '../utils.dart'; import 'sbs_utils.dart'; +/// Test calls all possible configuration features and shows that: +/// - Key length limit is overridden by S and stored correctly because FS does not include it +/// - FS overrides other limits and stored correctly +/// - S overrides RQ and EQ limits and stored correctly +/// - FS overrides consent requirement to false, bom to true, request age drop hours to from 5>12>21, crash reporting to false, view reporting to false +/// How it affects the SDK: +/// - event requests are reduced compared to EQ is 5 tests, because S is prior than DP +/// - location requests are increased because location is disabled in DP then later enabled while calling all features +/// - crash requests are not sent +/// - no consent request here because SDK did not wait for server response here, it already had it +/// - no view events are sent +/// This test shows than FS is prior than S, S is prior than DP void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('SBS_201D_DP_S_FS_test', (WidgetTester tester) async { @@ -21,7 +33,7 @@ void main() { responseJson = { 'v': 1, 't': 1750748806695, - 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': true, 'log': true, 'dort': 21, 'lkl': 67, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} + 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': true, 'log': true, 'dort': 21, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} }; } } @@ -51,7 +63,7 @@ void main() { expect(await getServerConfig(), { 'v': 1, 't': 1750748806695, - 'c': {'crt': false, 'vt': false, 'st': true, 'rqs': 100, 'eqs': 20, 'cr': false, 'cet': true, 'log': true, 'dort': 21, 'lkl': 67, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} + 'c': {'crt': false, 'vt': false, 'st': true, 'rqs': 100, 'eqs': 20, 'cr': false, 'cet': true, 'log': true, 'dort': 21, 'lkl': 120, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} }); validateRequestCounts({'events': 3, 'location': 3, 'crash': 0, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1, 'consent': 0}, requestArray); diff --git a/example/integration_test/sbs_tests/SBS_201E_DP_S_P_FS_test.dart b/example/integration_test/sbs_tests/SBS_201E_DP_S_P_FS_test.dart index 1c8189dd..2b490f9e 100644 --- a/example/integration_test/sbs_tests/SBS_201E_DP_S_P_FS_test.dart +++ b/example/integration_test/sbs_tests/SBS_201E_DP_S_P_FS_test.dart @@ -8,6 +8,18 @@ import 'package:integration_test/integration_test.dart'; import '../utils.dart'; import 'sbs_utils.dart'; +/// Test calls all possible configuration features and shows that: +/// - eq is only given in the P, but because P is ignored, it is not set at the end +/// - Key length limit is overridden by S and stored correctly because FS does not include it +/// - FS overrides other limits and stored correctly +/// - S overrides RQ limit and stored correctly +/// - FS overrides request age drop hours to from 5>12>21, crash reporting to false, view reporting to false +/// How it affects the SDK: +/// - event requests are increased because EQ size is 5 in DP and it is not affected by SBS because they do not have it +/// - location requests are increased because location is disabled in DP then later enabled while calling all features +/// - crash requests are not sent +/// - no consent request here because SDK did not wait for server response here, it already had it +/// This test shows than FS is prior than S, S is prior than DP, if there is S then P is ignored void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('SBS_201E_DP_S_P_FS_test', (WidgetTester tester) async { @@ -21,7 +33,7 @@ void main() { responseJson = { 'v': 1, 't': 1750748806695, - 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': true, 'log': true, 'dort': 21, 'lkl': 67, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} + 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': true, 'log': true, 'dort': 21, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} }; } } @@ -54,7 +66,7 @@ void main() { expect(await getServerConfig(), { 'v': 1, 't': 1750748806695, - 'c': {'crt': false, 'vt': false, 'st': true, 'rqs': 100, 'cr': false, 'cet': true, 'log': true, 'dort': 21, 'lkl': 67, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} + 'c': {'crt': false, 'vt': false, 'st': true, 'rqs': 100, 'cr': false, 'cet': true, 'log': true, 'dort': 21, 'lkl': 120, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} }); validateRequestCounts({'events': 8, 'location': 3, 'crash': 0, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1, 'consent': 0}, requestArray); From 94a3c75de65c24012ff7cec4cd5e6a017dba3181 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Fri, 4 Jul 2025 17:25:29 +0300 Subject: [PATCH 25/64] feat: feature test sets plan --- example/integration_test/sbs_tests/notes.md | 102 ++++++++------------ 1 file changed, 38 insertions(+), 64 deletions(-) diff --git a/example/integration_test/sbs_tests/notes.md b/example/integration_test/sbs_tests/notes.md index 95cd3ed3..3bf417d0 100644 --- a/example/integration_test/sbs_tests/notes.md +++ b/example/integration_test/sbs_tests/notes.md @@ -9,78 +9,52 @@ dev_provided = DP Order validation 200X tests are feature validation tests where -A = SDK internal limits -B = Tracking - will not affect SC request -C = Networking - will not affect SC request -D = Request Queue + Event Queue + Session Update Interval -E = Session Tracking + Crash Tracking + Location Tracking -F = Custom Event Tracking + View Tracking -G = Consent + Drop old request + Server config update interval -H = Content Zone + Content Zone Interval + Refresh Content Zone -I = Backoff +A = SDK internal limits + Content Zone + Content Zone Interval + Refresh Content Zone + Backoff Mechanism Enabled +B = Tracking + Server Config Update Interval - will not affect SC request +C = Networking + Consent + Request Queue + Session Update Interval + Drop old request - will not affect SC request +D = Session Tracking + Custom Event Tracking + Event Queue + View Tracking + Location Tracking + Crash Tracking + Backoff Configs Tests -- A variants: from_server, provided, dev_provided, storage -Change all SDK internal limits, respond them from the ${variant} and validate that all truncable things cool -at the end validate all queues are empty +- A +Call all features +Provide SBS from server {'lkl': 5, 'lvs': 5, 'lsv': 5, 'lbc': 5, 'ltlpt': 5, 'ltl': 5, 'rcz': false, 'cz': true, 'czi': 16, 'bom': false} +Change all SDK internal limits and validate that all are applied +Trigger two requests that their response duration is above 10 seconds +Validate that: +- content zone is called after init +- provided zone timer interval is not default one and 16 +- refresh content zone call is disabled +- backoff mechanism is disabled and two requests are passed +- validate the constraints for the backoff before sending requests - B -Provide SBS through config, that it disables tracking -call all functions, validate server request list only contains server config and queues are empty - -Stop the SDK, clear stored SBS, provide SDK config through config again with tracking disabled -but let server respond tracking enabled, call all functions, validate queues are empty, all requests are sent - -This also validates server response sbs > provided sbs +Call all features +Provide SBS from server {'tracking': false, 'scui': 1} +Validate that: +- No requests exist in the sent requestArray in mock server +- RQ is empty +- Only SBS requests are existing +- Validate that next SBS fetch called in 1 hours - C -Provide SBS through config, that it disables networking -call all functions, validate server request list only contains server config and queues are containing all features' requests - -Stop the SDK, clear stored SBS, provide SDK config through config again with networking disabled -but let server respond networking enabled, call all functions, validate queues are empty, all requests are sent - -This also validates server response sbs > provided sbs - -- D variants: from_server, provided, dev_provided, storage -Provide SBS through ${variant}, validate that: -Request queue is clipped with new limit -Event queue is clipped with new limit -Session update times are same as new limit - -- E variants: from_server, provided, storage -Call all features, respond from the ${variant} with disabling features -Validate that session and crash are not existing in the server request list -And location is existing as location disabled request -Validate RQ is not containing any crash and session requests +Call all features +Provide SBS from server {'networking': false, 'cr': true, 'rqs': 5, 'sui': 10, 'dort': 1} +Store couple of requests before starting the SDK and show that they are deleted by drop request age +Validate that: +- No requests exist in the sent requestArray in mock server +- RQ contains items +- Features that requires consent is not called and did not recorded things in RQ +- every 10 seconds session triggered -To validate location works with/without session -stop the SDK, re init it with enabling session and validate begin_session contains location which is empty strinh - -- F -Call all functions validate that all called views and custom events are no containig in the EQ, RQ and server request list - -Stop the SDK, re init with views enabled, now validate views are existing in all the queues because they must not be affected -with custom event tracking - -- G variants: from_server, provided, dev_provided, storage -Add some old requests to the storage -Validate RQ containing old requests -Call all functions, validate that all consent required features are disabled and do not generate any request. -And validate that RQ is not containing old requests -???Somehow give server config update time very low and validate SC is triggered again - -- H variants: from_server, provided, dev_provided, storage -Call all functions, validate that content zone is entered automatically in the init -And validate that refresh content zone is not called -And validate content zone called again after configured time - -- I variants: from_server, provided, dev_provided, storage -call all functions with a delay where backoff would be triggered with changed parameters. -First do not disable backoff, and try other parameters - -stop the sdk and re init it with disabling backoff. Now try that backoff mechanism is not triggered. +- D +Call all features +Provide SBS from server {'st': false, 'cet': false, 'vt': false, 'eqs': 5, 'lt': false, 'crt': false, 'bom_at': 5, 'bom_d': 30, 'bom_rqp': 0.01, 'bom_ra': 1} +Validate that: +- Session, custom events, location, crashes, views are not recorded +- Internal events are recorded and clipped by EQ limit +- Views are not affected by custom event tracking +- Backoff mechanism configs are applied --------------------------------------------------------------------------------------------------------------------------------- From 34dc0d3d6fcfff2ca72dccc0889c6662c5dded26 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Mon, 7 Jul 2025 11:51:02 +0300 Subject: [PATCH 26/64] feat: remove backoff because duplicate --- .../sbs_tests/SBS_000_base_test.dart | 16 ---------------- example/integration_test/sbs_tests/notes.md | 1 - 2 files changed, 17 deletions(-) diff --git a/example/integration_test/sbs_tests/SBS_000_base_test.dart b/example/integration_test/sbs_tests/SBS_000_base_test.dart index 7bdcde53..65041f4a 100644 --- a/example/integration_test/sbs_tests/SBS_000_base_test.dart +++ b/example/integration_test/sbs_tests/SBS_000_base_test.dart @@ -47,21 +47,5 @@ void main() { validateRequestCounts({'events': 2, 'location': 1, 'crash': 2, 'begin_session': 1, 'consent': 0, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': Platform.isIOS ? 2 : 1}, requestArray); validateInternalEventCounts({'orientation': 1, 'view': 6}, requestArray); validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); - - await Countly.instance.content.exitContentZone(); - requestArray.clear(); - - serverDelay = 11; - - await Countly.instance.sessions.beginSession(); - await Countly.instance.sessions.endSession(); // this will be backed off for 60 seconds - await Countly.instance.attemptToSendStoredRequests(); - - validateRequestCounts({'begin_session': 1}, requestArray); - await Countly.instance.attemptToSendStoredRequests(); // this will not take effect - expect(requestArray.length, 1); - - await Future.delayed(const Duration(seconds: 90)); // iOS required more time then Android - validateRequestCounts({'begin_session': 1, 'end_session': 1}, requestArray); }); } diff --git a/example/integration_test/sbs_tests/notes.md b/example/integration_test/sbs_tests/notes.md index 3bf417d0..51c37c1a 100644 --- a/example/integration_test/sbs_tests/notes.md +++ b/example/integration_test/sbs_tests/notes.md @@ -6,7 +6,6 @@ storage = S provided = P dev_provided = DP -Order validation 200X tests are feature validation tests where A = SDK internal limits + Content Zone + Content Zone Interval + Refresh Content Zone + Backoff Mechanism Enabled From 3a1b3ae912adf3700da06264fc090003e744875a Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Mon, 7 Jul 2025 11:52:17 +0300 Subject: [PATCH 27/64] feat: move order tests to folder --- .../sbs_tests/{ => order_tests}/SBS_201A_DP_FS_test.dart | 3 ++- .../sbs_tests/{ => order_tests}/SBS_201B_DP_P_test.dart | 1 + .../sbs_tests/{ => order_tests}/SBS_201C_DP_P_FS_test.dart | 0 .../sbs_tests/{ => order_tests}/SBS_201D_DP_S_FS_test.dart | 0 .../sbs_tests/{ => order_tests}/SBS_201E_DP_S_P_FS_test.dart | 0 5 files changed, 3 insertions(+), 1 deletion(-) rename example/integration_test/sbs_tests/{ => order_tests}/SBS_201A_DP_FS_test.dart (97%) rename example/integration_test/sbs_tests/{ => order_tests}/SBS_201B_DP_P_test.dart (99%) rename example/integration_test/sbs_tests/{ => order_tests}/SBS_201C_DP_P_FS_test.dart (100%) rename example/integration_test/sbs_tests/{ => order_tests}/SBS_201D_DP_S_FS_test.dart (100%) rename example/integration_test/sbs_tests/{ => order_tests}/SBS_201E_DP_S_P_FS_test.dart (100%) diff --git a/example/integration_test/sbs_tests/SBS_201A_DP_FS_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201A_DP_FS_test.dart similarity index 97% rename from example/integration_test/sbs_tests/SBS_201A_DP_FS_test.dart rename to example/integration_test/sbs_tests/order_tests/SBS_201A_DP_FS_test.dart index 81304e25..ee0da227 100644 --- a/example/integration_test/sbs_tests/SBS_201A_DP_FS_test.dart +++ b/example/integration_test/sbs_tests/order_tests/SBS_201A_DP_FS_test.dart @@ -14,7 +14,7 @@ import 'sbs_utils.dart'; /// - This test shows that FS is prior than DP /// How it affects the SDK: /// - event requests are increased if we compare it with base test -/// - location requests are increased because location is disabled in DP then later enabled while calling all features +/// - location requests are decreased because location is disabled in DP then later enabled while calling all features /// - crash requests are not sent /// - consent requests increased by one because it was enabled and disabled later /// - no view events are sent @@ -62,5 +62,6 @@ void main() { validateRequestCounts({'events': 8, 'location': 2, 'crash': 0, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1, 'consent': 1}, requestArray); validateInternalEventCounts({'orientation': 1}, requestArray); validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); + // validate key lengths }); } diff --git a/example/integration_test/sbs_tests/SBS_201B_DP_P_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201B_DP_P_test.dart similarity index 99% rename from example/integration_test/sbs_tests/SBS_201B_DP_P_test.dart rename to example/integration_test/sbs_tests/order_tests/SBS_201B_DP_P_test.dart index 32531614..bfd741ee 100644 --- a/example/integration_test/sbs_tests/SBS_201B_DP_P_test.dart +++ b/example/integration_test/sbs_tests/order_tests/SBS_201B_DP_P_test.dart @@ -57,5 +57,6 @@ void main() { validateRequestCounts({'events': 8, 'location': 3, 'crash': 0, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1, 'consent': 0}, requestArray); validateInternalEventCounts({'orientation': 1}, requestArray); validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); + // validate key lenght }); } diff --git a/example/integration_test/sbs_tests/SBS_201C_DP_P_FS_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201C_DP_P_FS_test.dart similarity index 100% rename from example/integration_test/sbs_tests/SBS_201C_DP_P_FS_test.dart rename to example/integration_test/sbs_tests/order_tests/SBS_201C_DP_P_FS_test.dart diff --git a/example/integration_test/sbs_tests/SBS_201D_DP_S_FS_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201D_DP_S_FS_test.dart similarity index 100% rename from example/integration_test/sbs_tests/SBS_201D_DP_S_FS_test.dart rename to example/integration_test/sbs_tests/order_tests/SBS_201D_DP_S_FS_test.dart diff --git a/example/integration_test/sbs_tests/SBS_201E_DP_S_P_FS_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201E_DP_S_P_FS_test.dart similarity index 100% rename from example/integration_test/sbs_tests/SBS_201E_DP_S_P_FS_test.dart rename to example/integration_test/sbs_tests/order_tests/SBS_201E_DP_S_P_FS_test.dart From 67df4a0c4b916680e8948281775637f3b9119688 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Mon, 7 Jul 2025 11:52:51 +0300 Subject: [PATCH 28/64] fix: update imports --- .../sbs_tests/order_tests/SBS_201A_DP_FS_test.dart | 4 ++-- .../sbs_tests/order_tests/SBS_201B_DP_P_test.dart | 4 ++-- .../sbs_tests/order_tests/SBS_201C_DP_P_FS_test.dart | 4 ++-- .../sbs_tests/order_tests/SBS_201D_DP_S_FS_test.dart | 4 ++-- .../sbs_tests/order_tests/SBS_201E_DP_S_P_FS_test.dart | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/example/integration_test/sbs_tests/order_tests/SBS_201A_DP_FS_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201A_DP_FS_test.dart index ee0da227..046115fb 100644 --- a/example/integration_test/sbs_tests/order_tests/SBS_201A_DP_FS_test.dart +++ b/example/integration_test/sbs_tests/order_tests/SBS_201A_DP_FS_test.dart @@ -5,8 +5,8 @@ import 'package:countly_flutter/countly_flutter.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; -import '../utils.dart'; -import 'sbs_utils.dart'; +import '../../utils.dart'; +import '../sbs_utils.dart'; /// Test calls all possible configuration features and shows that: /// - All internal limits are overridden by FS SBS and stored correctly diff --git a/example/integration_test/sbs_tests/order_tests/SBS_201B_DP_P_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201B_DP_P_test.dart index bfd741ee..4db76e98 100644 --- a/example/integration_test/sbs_tests/order_tests/SBS_201B_DP_P_test.dart +++ b/example/integration_test/sbs_tests/order_tests/SBS_201B_DP_P_test.dart @@ -5,8 +5,8 @@ import 'package:countly_flutter/countly_flutter.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; -import '../utils.dart'; -import 'sbs_utils.dart'; +import '../../utils.dart'; +import '../sbs_utils.dart'; /// Test calls all possible configuration features and shows that: /// - All internal limits are overridden by P SBS and stored correctly diff --git a/example/integration_test/sbs_tests/order_tests/SBS_201C_DP_P_FS_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201C_DP_P_FS_test.dart index db90ea55..8386d768 100644 --- a/example/integration_test/sbs_tests/order_tests/SBS_201C_DP_P_FS_test.dart +++ b/example/integration_test/sbs_tests/order_tests/SBS_201C_DP_P_FS_test.dart @@ -5,8 +5,8 @@ import 'package:countly_flutter/countly_flutter.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; -import '../utils.dart'; -import 'sbs_utils.dart'; +import '../../utils.dart'; +import '../sbs_utils.dart'; /// Test calls all possible configuration features and shows that: /// - All internal limits are overridden by FS SBS and stored correctly diff --git a/example/integration_test/sbs_tests/order_tests/SBS_201D_DP_S_FS_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201D_DP_S_FS_test.dart index 7a57d219..1ec648ef 100644 --- a/example/integration_test/sbs_tests/order_tests/SBS_201D_DP_S_FS_test.dart +++ b/example/integration_test/sbs_tests/order_tests/SBS_201D_DP_S_FS_test.dart @@ -5,8 +5,8 @@ import 'package:countly_flutter/countly_flutter.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; -import '../utils.dart'; -import 'sbs_utils.dart'; +import '../../utils.dart'; +import '../sbs_utils.dart'; /// Test calls all possible configuration features and shows that: /// - Key length limit is overridden by S and stored correctly because FS does not include it diff --git a/example/integration_test/sbs_tests/order_tests/SBS_201E_DP_S_P_FS_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201E_DP_S_P_FS_test.dart index 2b490f9e..5111992c 100644 --- a/example/integration_test/sbs_tests/order_tests/SBS_201E_DP_S_P_FS_test.dart +++ b/example/integration_test/sbs_tests/order_tests/SBS_201E_DP_S_P_FS_test.dart @@ -5,8 +5,8 @@ import 'package:countly_flutter/countly_flutter.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; -import '../utils.dart'; -import 'sbs_utils.dart'; +import '../../utils.dart'; +import '../sbs_utils.dart'; /// Test calls all possible configuration features and shows that: /// - eq is only given in the P, but because P is ignored, it is not set at the end From eca7608fbb0ee67f1a23fab2ebf8719b161de108 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Mon, 7 Jul 2025 11:55:45 +0300 Subject: [PATCH 29/64] feat: init time status table to order tests --- .../sbs_tests/order_tests/notes.md | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 example/integration_test/sbs_tests/order_tests/notes.md diff --git a/example/integration_test/sbs_tests/order_tests/notes.md b/example/integration_test/sbs_tests/order_tests/notes.md new file mode 100644 index 00000000..c2eff312 --- /dev/null +++ b/example/integration_test/sbs_tests/order_tests/notes.md @@ -0,0 +1,20 @@ +Init time SBS + +| Set Config | Provided SBS | Stored SBS | Temp ID | Initial Behavior | +|------------|--------------|------------|---------|------------------| +| ● | | | | Dev Set Config | +| ● | ● | | | Provided SBS | +| ● | | ● | | Stored SBS | +| ● | ● | ● | | Stored SBS | +| ● | ● | ● | ● | Stored SBS | +| ● | | ● | ● | Stored SBS | +| ● | | | ● | Dev Set Config | +| ● | ● | | ● | Provided SBS | +| | ● | | | Provided SBS | +| | ● | ● | | Stored SBS | +| | ● | | ● | Provided SBS | +| | ● | ● | ● | Stored SBS | +| | | ● | ● | Stored SBS | +| | | | ● | SDK Defaults | +| | | ● | | Stored SBS | +| | | | | SDK Defaults | From cf3f26f3a5d8439024ab7e8922a6841aaa7d7bb0 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Mon, 7 Jul 2025 12:18:15 +0300 Subject: [PATCH 30/64] feat: DP FS redo --- .../order_tests/SBS_201A_DP_FS_test.dart | 42 ++++++++----------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/example/integration_test/sbs_tests/order_tests/SBS_201A_DP_FS_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201A_DP_FS_test.dart index 046115fb..bbf087b7 100644 --- a/example/integration_test/sbs_tests/order_tests/SBS_201A_DP_FS_test.dart +++ b/example/integration_test/sbs_tests/order_tests/SBS_201A_DP_FS_test.dart @@ -5,19 +5,14 @@ import 'package:countly_flutter/countly_flutter.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import '../../event_tests/event_utils.dart'; import '../../utils.dart'; -import '../sbs_utils.dart'; -/// Test calls all possible configuration features and shows that: -/// - All internal limits are overridden by FS SBS and stored correctly -/// - FS overrides consent requirement to false, bom to true, request age drop hours to from 5 to 12, crash reporting to false, view reporting to false -/// - This test shows that FS is prior than DP -/// How it affects the SDK: -/// - event requests are increased if we compare it with base test -/// - location requests are decreased because location is disabled in DP then later enabled while calling all features -/// - crash requests are not sent -/// - consent requests increased by one because it was enabled and disabled later -/// - no view events are sent +/// Test records an event with a key and segmentation values that exceeds the maximum key length set by the SDK's internal limits server SBS limit. +/// - Key length limit is 3 and value size is 5 on DP +/// - Only key length is provided by FS is 8 +/// - The event is recorded with the key truncated to 8 #FS +/// - Values in segmentation are truncated to 5 characters #DP void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('SBS_201A_DP_FS_test', (WidgetTester tester) async { @@ -25,13 +20,11 @@ void main() { createServer(requestArray, customHandler: (request, queryParams, response) async { Map responseJson = {'result': 'Success'}; if (queryParams.containsKey('method')) { - if (queryParams['method']!.first == 'feedback') { - responseJson = {'result': []}; - } else if (queryParams['method']!.first == 'sc') { + if (queryParams['method']!.first == 'sc') { responseJson = { 'v': 1, 't': 1750748806695, - 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': true, 'log': false, 'dort': 12, 'lkl': 120, 'lvs': 255, 'lsv': 99, 'lbc': 99, 'ltlpt': 29, 'ltl': 199, 'rcz': true, 'bom': true} + 'c': {'lkl': 8} }; } } @@ -44,24 +37,23 @@ void main() { // Initialize the SDK CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); - config.setMaxRequestQueueSize(5).setEventQueueSizeToSend(5).disableBackoffMechanism().setRequiresConsent(true).disableLocation().setRequestDropAgeHours(5).setUpdateSessionTimerDelay(75); - config.content.setZoneTimerInterval(17); - config.sdkInternalLimits.setMaxBreadcrumbCount(1).setMaxKeyLength(3).setMaxSegmentationValues(3).setMaxValueSize(5).setMaxStackTraceLineLength(300).setMaxStackTraceLinesPerThread(2); + config.sdkInternalLimits.setMaxKeyLength(3).setMaxValueSize(5); await Countly.initWithConfig(config); await Future.delayed(const Duration(seconds: 2)); - await callAllFeatures(); + await Countly.instance.events.recordEvent('ThisWillCLIPPED_BY_FS', {'no1': 'valueCLIPPED_BY_DP', 'no2': 'valueCLIPPED_BY_DP', 'no3': 'valueCLIPPED_BY_DP'}); + List rq = await getRequestQueue(); + List eq = await getEventQueue(); + expect(rq.length, 0); + expect(eq.length, 1); + + validateEvent(event: jsonDecode(eq.first), key: 'ThisWill', segmentation: {'no1': 'value', 'no2': 'value', 'no3': 'value'}); expect(await getServerConfig(), { 'v': 1, 't': 1750748806695, - 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': true, 'log': false, 'dort': 12, 'lkl': 120, 'lvs': 255, 'lsv': 99, 'lbc': 99, 'ltlpt': 29, 'ltl': 199, 'rcz': true, 'bom': true} + 'c': {'lkl': 8} }); - - validateRequestCounts({'events': 8, 'location': 2, 'crash': 0, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1, 'consent': 1}, requestArray); - validateInternalEventCounts({'orientation': 1}, requestArray); - validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); - // validate key lengths }); } From 92ef0e80aa52de37262fcdb1855aded7ddc7baca Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Mon, 7 Jul 2025 12:18:24 +0300 Subject: [PATCH 31/64] feat: DP P redo --- .../order_tests/SBS_201B_DP_P_test.dart | 55 ++++++------------- 1 file changed, 18 insertions(+), 37 deletions(-) diff --git a/example/integration_test/sbs_tests/order_tests/SBS_201B_DP_P_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201B_DP_P_test.dart index 4db76e98..208c8558 100644 --- a/example/integration_test/sbs_tests/order_tests/SBS_201B_DP_P_test.dart +++ b/example/integration_test/sbs_tests/order_tests/SBS_201B_DP_P_test.dart @@ -1,62 +1,43 @@ import 'dart:convert'; -import 'dart:io'; import 'package:countly_flutter/countly_flutter.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import '../../event_tests/event_utils.dart'; import '../../utils.dart'; -import '../sbs_utils.dart'; - -/// Test calls all possible configuration features and shows that: -/// - All internal limits are overridden by P SBS and stored correctly -/// - P overrides consent requirement to false, bom to true, request age drop hours to from 5 to 12, crash reporting to false, view reporting to false -/// - This test shows that P is prior than DP -/// How it affects the SDK: -/// - event requests are increased if we compare it with base test -/// - location requests are increased because location is disabled in DP then later enabled while calling all features -/// - crash requests are not sent -/// - no consent request here because SDK did not wait for server response here, it got no time to form a consent request -/// - no view events are sent + +/// Test records an event with a key and segmentation values that exceeds the maximum key length set by the SDK's internal limits server SBS limit. +/// - Key length limit is 3 and value size is 5 on DP +/// - Only key length in P is 8 +/// - The event is recorded with the key truncated to 8 #P +/// - Values in segmentation are truncated to 5 characters #DP void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('SBS_201B_DP_P_test', (WidgetTester tester) async { List>> requestArray = >>[]; - createServer(requestArray, customHandler: (request, queryParams, response) async { - Map responseJson = {'result': 'Success'}; - if (queryParams.containsKey('method')) { - if (queryParams['method']!.first == 'feedback') { - responseJson = {'result': []}; - } - } - response - ..statusCode = HttpStatus.ok - ..headers.contentType = ContentType.json - ..headers.set('Access-Control-Allow-Origin', '*') - ..write(jsonEncode(responseJson)); - }); + createServer(requestArray); // Initialize the SDK CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); - config.setSDKBehaviorSettings('{"v":1,"t":1750748806695,"c":{"crt":false,"vt":false,"st":true,"cr":false,"cet":true,"log":false,"dort":12,"lkl":120,"lvs":255,"lsv":99,"lbc":99,"ltlpt":29,"ltl":199,"rcz":true,"bom":true}}'); - config.setMaxRequestQueueSize(5).setEventQueueSizeToSend(5).disableBackoffMechanism().setRequiresConsent(true).disableLocation().setRequestDropAgeHours(5).setUpdateSessionTimerDelay(75); - config.content.setZoneTimerInterval(17); - config.sdkInternalLimits.setMaxBreadcrumbCount(1).setMaxKeyLength(3).setMaxSegmentationValues(3).setMaxValueSize(5).setMaxStackTraceLineLength(300).setMaxStackTraceLinesPerThread(2); + config.setSDKBehaviorSettings('{"v":1,"t":1750748806695,"c":{"lkl":8}}'); + config.sdkInternalLimits.setMaxKeyLength(3).setMaxValueSize(5); await Countly.initWithConfig(config); await Future.delayed(const Duration(seconds: 2)); - await callAllFeatures(); + await Countly.instance.events.recordEvent('ThisWillCLIPPED_BY_FS', {'no1': 'valueCLIPPED_BY_DP', 'no2': 'valueCLIPPED_BY_DP', 'no3': 'valueCLIPPED_BY_DP'}); + List rq = await getRequestQueue(); + List eq = await getEventQueue(); + expect(rq.length, 0); + expect(eq.length, 1); + + validateEvent(event: jsonDecode(eq.first), key: 'ThisWill', segmentation: {'no1': 'value', 'no2': 'value', 'no3': 'value'}); expect(await getServerConfig(), { 'v': 1, 't': 1750748806695, - 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': true, 'log': false, 'dort': 12, 'lkl': 120, 'lvs': 255, 'lsv': 99, 'lbc': 99, 'ltlpt': 29, 'ltl': 199, 'rcz': true, 'bom': true} + 'c': {'lkl': 8} }); - - validateRequestCounts({'events': 8, 'location': 3, 'crash': 0, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1, 'consent': 0}, requestArray); - validateInternalEventCounts({'orientation': 1}, requestArray); - validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); - // validate key lenght }); } From 67639dba4d1afcfebc73a17d2472f349da02ff92 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Mon, 7 Jul 2025 12:50:30 +0300 Subject: [PATCH 32/64] feat: DP P FS redo --- .../order_tests/SBS_201C_DP_P_FS_test.dart | 46 ++++++++----------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/example/integration_test/sbs_tests/order_tests/SBS_201C_DP_P_FS_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201C_DP_P_FS_test.dart index 8386d768..e8b347c9 100644 --- a/example/integration_test/sbs_tests/order_tests/SBS_201C_DP_P_FS_test.dart +++ b/example/integration_test/sbs_tests/order_tests/SBS_201C_DP_P_FS_test.dart @@ -5,20 +5,16 @@ import 'package:countly_flutter/countly_flutter.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import '../../event_tests/event_utils.dart'; import '../../utils.dart'; -import '../sbs_utils.dart'; -/// Test calls all possible configuration features and shows that: -/// - All internal limits are overridden by FS SBS and stored correctly -/// - P overrides consent requirement to false because FS does not have it -/// - FS overrides crt, vt to false, and it is more prior than P -/// - This test shows that FS is prior than DP and P, and P is prior than DP -/// How it affects the SDK: -/// - event requests reduces because cet is false FS -/// - location requests are increased because location is disabled in DP then later enabled while calling all features -/// - crash requests are not sent FS -/// - no consent request here because SDK did not wait for server response here, it got no time to form a consent request -/// - no view events are sent +/// Test records an event with a key and segmentation values that exceeds the maximum key length set by the SDK's internal limits server SBS limit. +/// - Key length limit is 3 and value size is 5i segmentation values 2 on DP +/// - key length 8, segmentation values 4 in P +/// - Only key length in P is FS +/// - The event is recorded with the key truncated to 8 #FS +/// - Values in segmentation are truncated to 5 characters #DP +/// - Segmentation values are truncated to 4 characters #P void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('SBS_201C_DP_P_FS_test', (WidgetTester tester) async { @@ -26,13 +22,11 @@ void main() { createServer(requestArray, customHandler: (request, queryParams, response) async { Map responseJson = {'result': 'Success'}; if (queryParams.containsKey('method')) { - if (queryParams['method']!.first == 'feedback') { - responseJson = {'result': []}; - } else if (queryParams['method']!.first == 'sc') { + if (queryParams['method']!.first == 'sc') { responseJson = { 'v': 1, 't': 1750748806695, - 'c': {'crt': false, 'vt': false, 'st': true, 'cet': false, 'log': true, 'dort': 21, 'lkl': 67, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} + 'c': {'lkl': 8} }; } } @@ -45,24 +39,24 @@ void main() { // Initialize the SDK CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); - config.setSDKBehaviorSettings('{"v":1,"t":1750748806695,"c":{"crt":true,"vt":true,"st":true,"cr":false,"cet":true,"log":false,"dort":12,"lkl":120,"lvs":255,"lsv":99,"lbc":99,"ltlpt":29,"ltl":199,"rcz":true,"bom":true}}'); - config.setMaxRequestQueueSize(5).setEventQueueSizeToSend(5).disableBackoffMechanism().setRequiresConsent(true).disableLocation().setRequestDropAgeHours(5).setUpdateSessionTimerDelay(75); - config.content.setZoneTimerInterval(17); - config.sdkInternalLimits.setMaxBreadcrumbCount(1).setMaxKeyLength(3).setMaxSegmentationValues(3).setMaxValueSize(5).setMaxStackTraceLineLength(300).setMaxStackTraceLinesPerThread(2); + config.setSDKBehaviorSettings('{"v":1,"t":1750748806695,"c":{"lkl":6, "lsv": 4}}'); + config.sdkInternalLimits.setMaxKeyLength(3).setMaxValueSize(5).setMaxSegmentationValues(2); await Countly.initWithConfig(config); await Future.delayed(const Duration(seconds: 2)); - await callAllFeatures(); + await Countly.instance.events.recordEvent('ThisWillCLIPPED_BY_FS', {'no1': 'valueCLIPPED_BY_DP', 'no2': 'valueCLIPPED_BY_DP', 'no3': 'valueCLIPPED_BY_DP', 'no4': 'valueCLIPPED_BY_DP', 'no5': 'valueCLIPPED_BY_DP'}); + List rq = await getRequestQueue(); + List eq = await getEventQueue(); + expect(rq.length, 0); + expect(eq.length, 1); + + validateEvent(event: jsonDecode(eq.first), key: 'ThisWill', segmentation: {'no1': 'value', 'no4': 'value', 'no3': 'value', 'no5': 'value'}); expect(await getServerConfig(), { 'v': 1, 't': 1750748806695, - 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': false, 'log': true, 'dort': 21, 'lkl': 67, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} + 'c': {'lkl': 8, 'lsv': 4} }); - - validateRequestCounts({'events': 1, 'location': 3, 'crash': 0, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1, 'consent': 0}, requestArray); - validateInternalEventCounts({'orientation': 1}, requestArray); - validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); }); } From 1229dbc2e1f01e24d5af18a942351f5be568b736 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Mon, 7 Jul 2025 22:13:41 +0300 Subject: [PATCH 33/64] feat: merge couple of order tests --- ...S_test.dart => SBS_201A_DP_P_FS_test.dart} | 6 +- .../order_tests/SBS_201B_DP_P_test.dart | 43 ----------- ...S_test.dart => SBS_201B_DP_S_FS_test.dart} | 22 ++++-- .../order_tests/SBS_201C_DP_S_P_FS_test.dart | 70 +++++++++++++++++ .../order_tests/SBS_201D_DP_S_FS_test.dart | 73 ------------------ .../SBS_201D_DP_S_P_FS_temp_id_test.dart | 70 +++++++++++++++++ .../order_tests/SBS_201E_DP_S_P_FS_test.dart | 76 ------------------- 7 files changed, 158 insertions(+), 202 deletions(-) rename example/integration_test/sbs_tests/order_tests/{SBS_201C_DP_P_FS_test.dart => SBS_201A_DP_P_FS_test.dart} (93%) delete mode 100644 example/integration_test/sbs_tests/order_tests/SBS_201B_DP_P_test.dart rename example/integration_test/sbs_tests/order_tests/{SBS_201A_DP_FS_test.dart => SBS_201B_DP_S_FS_test.dart} (76%) create mode 100644 example/integration_test/sbs_tests/order_tests/SBS_201C_DP_S_P_FS_test.dart delete mode 100644 example/integration_test/sbs_tests/order_tests/SBS_201D_DP_S_FS_test.dart create mode 100644 example/integration_test/sbs_tests/order_tests/SBS_201D_DP_S_P_FS_temp_id_test.dart delete mode 100644 example/integration_test/sbs_tests/order_tests/SBS_201E_DP_S_P_FS_test.dart diff --git a/example/integration_test/sbs_tests/order_tests/SBS_201C_DP_P_FS_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201A_DP_P_FS_test.dart similarity index 93% rename from example/integration_test/sbs_tests/order_tests/SBS_201C_DP_P_FS_test.dart rename to example/integration_test/sbs_tests/order_tests/SBS_201A_DP_P_FS_test.dart index e8b347c9..3e9e03ed 100644 --- a/example/integration_test/sbs_tests/order_tests/SBS_201C_DP_P_FS_test.dart +++ b/example/integration_test/sbs_tests/order_tests/SBS_201A_DP_P_FS_test.dart @@ -9,15 +9,15 @@ import '../../event_tests/event_utils.dart'; import '../../utils.dart'; /// Test records an event with a key and segmentation values that exceeds the maximum key length set by the SDK's internal limits server SBS limit. -/// - Key length limit is 3 and value size is 5i segmentation values 2 on DP +/// - Key length limit is 3, value size is 5 and segmentation values 2 on DP /// - key length 8, segmentation values 4 in P -/// - Only key length in P is FS +/// - Only key length in FS is 8 /// - The event is recorded with the key truncated to 8 #FS /// - Values in segmentation are truncated to 5 characters #DP /// - Segmentation values are truncated to 4 characters #P void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - testWidgets('SBS_201C_DP_P_FS_test', (WidgetTester tester) async { + testWidgets('SBS_201A_DP_P_FS_test', (WidgetTester tester) async { List>> requestArray = >>[]; createServer(requestArray, customHandler: (request, queryParams, response) async { Map responseJson = {'result': 'Success'}; diff --git a/example/integration_test/sbs_tests/order_tests/SBS_201B_DP_P_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201B_DP_P_test.dart deleted file mode 100644 index 208c8558..00000000 --- a/example/integration_test/sbs_tests/order_tests/SBS_201B_DP_P_test.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'dart:convert'; - -import 'package:countly_flutter/countly_flutter.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:integration_test/integration_test.dart'; - -import '../../event_tests/event_utils.dart'; -import '../../utils.dart'; - -/// Test records an event with a key and segmentation values that exceeds the maximum key length set by the SDK's internal limits server SBS limit. -/// - Key length limit is 3 and value size is 5 on DP -/// - Only key length in P is 8 -/// - The event is recorded with the key truncated to 8 #P -/// - Values in segmentation are truncated to 5 characters #DP -void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - testWidgets('SBS_201B_DP_P_test', (WidgetTester tester) async { - List>> requestArray = >>[]; - createServer(requestArray); - - // Initialize the SDK - CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); - config.setSDKBehaviorSettings('{"v":1,"t":1750748806695,"c":{"lkl":8}}'); - config.sdkInternalLimits.setMaxKeyLength(3).setMaxValueSize(5); - - await Countly.initWithConfig(config); - await Future.delayed(const Duration(seconds: 2)); - - await Countly.instance.events.recordEvent('ThisWillCLIPPED_BY_FS', {'no1': 'valueCLIPPED_BY_DP', 'no2': 'valueCLIPPED_BY_DP', 'no3': 'valueCLIPPED_BY_DP'}); - List rq = await getRequestQueue(); - List eq = await getEventQueue(); - expect(rq.length, 0); - expect(eq.length, 1); - - validateEvent(event: jsonDecode(eq.first), key: 'ThisWill', segmentation: {'no1': 'value', 'no2': 'value', 'no3': 'value'}); - - expect(await getServerConfig(), { - 'v': 1, - 't': 1750748806695, - 'c': {'lkl': 8} - }); - }); -} diff --git a/example/integration_test/sbs_tests/order_tests/SBS_201A_DP_FS_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201B_DP_S_FS_test.dart similarity index 76% rename from example/integration_test/sbs_tests/order_tests/SBS_201A_DP_FS_test.dart rename to example/integration_test/sbs_tests/order_tests/SBS_201B_DP_S_FS_test.dart index bbf087b7..f816c888 100644 --- a/example/integration_test/sbs_tests/order_tests/SBS_201A_DP_FS_test.dart +++ b/example/integration_test/sbs_tests/order_tests/SBS_201B_DP_S_FS_test.dart @@ -9,13 +9,15 @@ import '../../event_tests/event_utils.dart'; import '../../utils.dart'; /// Test records an event with a key and segmentation values that exceeds the maximum key length set by the SDK's internal limits server SBS limit. -/// - Key length limit is 3 and value size is 5 on DP -/// - Only key length is provided by FS is 8 +/// - Key length limit is 3, value size is 5 and segmentation values 2 on DP +/// - key length 8, segmentation values 4 in S +/// - Only key length in FS is 8 /// - The event is recorded with the key truncated to 8 #FS /// - Values in segmentation are truncated to 5 characters #DP +/// - Segmentation values are truncated to 4 characters #S void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - testWidgets('SBS_201A_DP_FS_test', (WidgetTester tester) async { + testWidgets('SBS_201B_DP_S_FS_test', (WidgetTester tester) async { List>> requestArray = >>[]; createServer(requestArray, customHandler: (request, queryParams, response) async { Map responseJson = {'result': 'Success'}; @@ -35,25 +37,31 @@ void main() { ..write(jsonEncode(responseJson)); }); + setServerConfig({ + 'v': 1, + 't': 1750748806695, + 'c': {'lkl': 6, 'lsv': 4} + }); + // Initialize the SDK CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); - config.sdkInternalLimits.setMaxKeyLength(3).setMaxValueSize(5); + config.sdkInternalLimits.setMaxKeyLength(3).setMaxValueSize(5).setMaxSegmentationValues(2); await Countly.initWithConfig(config); await Future.delayed(const Duration(seconds: 2)); - await Countly.instance.events.recordEvent('ThisWillCLIPPED_BY_FS', {'no1': 'valueCLIPPED_BY_DP', 'no2': 'valueCLIPPED_BY_DP', 'no3': 'valueCLIPPED_BY_DP'}); + await Countly.instance.events.recordEvent('ThisWillCLIPPED_BY_FS', {'no1': 'valueCLIPPED_BY_DP', 'no2': 'valueCLIPPED_BY_DP', 'no3': 'valueCLIPPED_BY_DP', 'no4': 'valueCLIPPED_BY_DP', 'no5': 'valueCLIPPED_BY_DP'}); List rq = await getRequestQueue(); List eq = await getEventQueue(); expect(rq.length, 0); expect(eq.length, 1); - validateEvent(event: jsonDecode(eq.first), key: 'ThisWill', segmentation: {'no1': 'value', 'no2': 'value', 'no3': 'value'}); + validateEvent(event: jsonDecode(eq.first), key: 'ThisWill', segmentation: {'no1': 'value', 'no4': 'value', 'no3': 'value', 'no5': 'value'}); expect(await getServerConfig(), { 'v': 1, 't': 1750748806695, - 'c': {'lkl': 8} + 'c': {'lkl': 8, 'lsv': 4} }); }); } diff --git a/example/integration_test/sbs_tests/order_tests/SBS_201C_DP_S_P_FS_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201C_DP_S_P_FS_test.dart new file mode 100644 index 00000000..95c2e8ac --- /dev/null +++ b/example/integration_test/sbs_tests/order_tests/SBS_201C_DP_S_P_FS_test.dart @@ -0,0 +1,70 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:countly_flutter/countly_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../../event_tests/event_utils.dart'; +import '../../utils.dart'; + +/// Test records an event with a key and segmentation values that exceeds the maximum key length set by the SDK's internal limits server SBS limit. +/// - Key length limit is 3, value size is 5 and segmentation values 2 on DP +/// - key length 8, segmentation values 4 in S +/// - Only key length in FS is 8 +/// - Key length 10, value size 56 and breadcrumb count 99 in P +/// - The event is recorded with the key truncated to 8 #FS +/// - Values in segmentation are truncated to 5 characters #DP +/// - Segmentation values are truncated to 4 characters #S +/// - Provided SBS is omitted because already stored SBS +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + testWidgets('SBS_201C_DP_S_P_FS_test', (WidgetTester tester) async { + List>> requestArray = >>[]; + createServer(requestArray, customHandler: (request, queryParams, response) async { + Map responseJson = {'result': 'Success'}; + if (queryParams.containsKey('method')) { + if (queryParams['method']!.first == 'sc') { + responseJson = { + 'v': 1, + 't': 1750748806695, + 'c': {'lkl': 8} + }; + } + } + response + ..statusCode = HttpStatus.ok + ..headers.contentType = ContentType.json + ..headers.set('Access-Control-Allow-Origin', '*') + ..write(jsonEncode(responseJson)); + }); + + setServerConfig({ + 'v': 1, + 't': 1750748806695, + 'c': {'lkl': 6, 'lsv': 4} + }); + + // Initialize the SDK + CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); + config.setSDKBehaviorSettings('{"v":1,"t":1750748806695,"c":{"lkl":10, "lsv": 56, "lbc": 99}}'); + config.sdkInternalLimits.setMaxKeyLength(3).setMaxValueSize(5).setMaxSegmentationValues(2); + + await Countly.initWithConfig(config); + await Future.delayed(const Duration(seconds: 2)); + + await Countly.instance.events.recordEvent('ThisWillCLIPPED_BY_FS', {'no1': 'valueCLIPPED_BY_DP', 'no2': 'valueCLIPPED_BY_DP', 'no3': 'valueCLIPPED_BY_DP', 'no4': 'valueCLIPPED_BY_DP', 'no5': 'valueCLIPPED_BY_DP'}); + List rq = await getRequestQueue(); + List eq = await getEventQueue(); + expect(rq.length, 0); + expect(eq.length, 1); + + validateEvent(event: jsonDecode(eq.first), key: 'ThisWill', segmentation: {'no1': 'value', 'no4': 'value', 'no3': 'value', 'no5': 'value'}); + + expect(await getServerConfig(), { + 'v': 1, + 't': 1750748806695, + 'c': {'lkl': 8, 'lsv': 4} + }); + }); +} diff --git a/example/integration_test/sbs_tests/order_tests/SBS_201D_DP_S_FS_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201D_DP_S_FS_test.dart deleted file mode 100644 index 1ec648ef..00000000 --- a/example/integration_test/sbs_tests/order_tests/SBS_201D_DP_S_FS_test.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:countly_flutter/countly_flutter.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:integration_test/integration_test.dart'; - -import '../../utils.dart'; -import '../sbs_utils.dart'; - -/// Test calls all possible configuration features and shows that: -/// - Key length limit is overridden by S and stored correctly because FS does not include it -/// - FS overrides other limits and stored correctly -/// - S overrides RQ and EQ limits and stored correctly -/// - FS overrides consent requirement to false, bom to true, request age drop hours to from 5>12>21, crash reporting to false, view reporting to false -/// How it affects the SDK: -/// - event requests are reduced compared to EQ is 5 tests, because S is prior than DP -/// - location requests are increased because location is disabled in DP then later enabled while calling all features -/// - crash requests are not sent -/// - no consent request here because SDK did not wait for server response here, it already had it -/// - no view events are sent -/// This test shows than FS is prior than S, S is prior than DP -void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - testWidgets('SBS_201D_DP_S_FS_test', (WidgetTester tester) async { - List>> requestArray = >>[]; - createServer(requestArray, customHandler: (request, queryParams, response) async { - Map responseJson = {'result': 'Success'}; - if (queryParams.containsKey('method')) { - if (queryParams['method']!.first == 'feedback') { - responseJson = {'result': []}; - } else if (queryParams['method']!.first == 'sc') { - responseJson = { - 'v': 1, - 't': 1750748806695, - 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': true, 'log': true, 'dort': 21, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} - }; - } - } - response - ..statusCode = HttpStatus.ok - ..headers.contentType = ContentType.json - ..headers.set('Access-Control-Allow-Origin', '*') - ..write(jsonEncode(responseJson)); - }); - - setServerConfig({ - 'v': 1, - 't': 1750748806695, - 'c': {'crt': false, 'vt': false, 'st': true, 'rqs': 100, 'eqs': 20, 'cr': false, 'cet': true, 'log': false, 'dort': 12, 'lkl': 120, 'lvs': 255, 'lsv': 99, 'lbc': 99, 'ltlpt': 29, 'ltl': 199, 'rcz': true, 'bom': true} - }); - // Initialize the SDK - CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); - config.setMaxRequestQueueSize(5).setEventQueueSizeToSend(5).disableBackoffMechanism().setRequiresConsent(true).disableLocation().setRequestDropAgeHours(5).setUpdateSessionTimerDelay(75); - config.content.setZoneTimerInterval(17); - config.sdkInternalLimits.setMaxBreadcrumbCount(1).setMaxKeyLength(3).setMaxSegmentationValues(3).setMaxValueSize(5).setMaxStackTraceLineLength(300).setMaxStackTraceLinesPerThread(2); - - await Countly.initWithConfig(config); - await Future.delayed(const Duration(seconds: 2)); - - await callAllFeatures(); - - expect(await getServerConfig(), { - 'v': 1, - 't': 1750748806695, - 'c': {'crt': false, 'vt': false, 'st': true, 'rqs': 100, 'eqs': 20, 'cr': false, 'cet': true, 'log': true, 'dort': 21, 'lkl': 120, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} - }); - - validateRequestCounts({'events': 3, 'location': 3, 'crash': 0, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1, 'consent': 0}, requestArray); - validateInternalEventCounts({'orientation': 1}, requestArray); - validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); - }); -} diff --git a/example/integration_test/sbs_tests/order_tests/SBS_201D_DP_S_P_FS_temp_id_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201D_DP_S_P_FS_temp_id_test.dart new file mode 100644 index 00000000..9adcd290 --- /dev/null +++ b/example/integration_test/sbs_tests/order_tests/SBS_201D_DP_S_P_FS_temp_id_test.dart @@ -0,0 +1,70 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:countly_flutter/countly_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../../event_tests/event_utils.dart'; +import '../../utils.dart'; + +/// Test records an event with a key and segmentation values that exceeds the maximum key length set by the SDK's internal limits server SBS limit. +/// - Key length limit is 3, value size is 5 and segmentation values 2 on DP +/// - key length 8, segmentation values 4 in S +/// - Only key length in FS is 8 +/// - Key length 10, value size 56 and breadcrumb count 99 in P +/// - The event is recorded with the key truncated to 6 #S because of temporary id, sbs fetch did not sent +/// - Values in segmentation are truncated to 5 characters #DP +/// - Segmentation values are truncated to 4 characters #S +/// - Provided SBS is omitted because already stored SBS +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + testWidgets('SBS_201D_DP_S_P_FS_test', (WidgetTester tester) async { + List>> requestArray = >>[]; + createServer(requestArray, customHandler: (request, queryParams, response) async { + Map responseJson = {'result': 'Success'}; + if (queryParams.containsKey('method')) { + if (queryParams['method']!.first == 'sc') { + responseJson = { + 'v': 1, + 't': 1750748806695, + 'c': {'lkl': 8} + }; + } + } + response + ..statusCode = HttpStatus.ok + ..headers.contentType = ContentType.json + ..headers.set('Access-Control-Allow-Origin', '*') + ..write(jsonEncode(responseJson)); + }); + + setServerConfig({ + 'v': 1, + 't': 1750748806691, + 'c': {'lkl': 6, 'lsv': 4} + }); + + // Initialize the SDK + CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true).enableTemporaryDeviceIDMode(); + config.setSDKBehaviorSettings('{"v":1,"t":1750748806695,"c":{"lkl":10, "lsv": 56, "lbc": 99}}'); + config.sdkInternalLimits.setMaxKeyLength(3).setMaxValueSize(5).setMaxSegmentationValues(2); + + await Countly.initWithConfig(config); + await Future.delayed(const Duration(seconds: 2)); + + await Countly.instance.events.recordEvent('ThisWillCLIPPED_BY_FS', {'no1': 'valueCLIPPED_BY_DP', 'no2': 'valueCLIPPED_BY_DP', 'no3': 'valueCLIPPED_BY_DP', 'no4': 'valueCLIPPED_BY_DP', 'no5': 'valueCLIPPED_BY_DP'}); + List rq = await getRequestQueue(); + List eq = await getEventQueue(); + expect(rq.length, 0); + expect(eq.length, 1); + + validateEvent(event: jsonDecode(eq.first), key: 'ThisWi', segmentation: {'no1': 'value', 'no4': 'value', 'no3': 'value', 'no5': 'value'}); + + expect(await getServerConfig(), { + 'v': 1, + 't': 1750748806691, + 'c': {'lkl': 6, 'lsv': 4} + }); + }); +} diff --git a/example/integration_test/sbs_tests/order_tests/SBS_201E_DP_S_P_FS_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201E_DP_S_P_FS_test.dart deleted file mode 100644 index 5111992c..00000000 --- a/example/integration_test/sbs_tests/order_tests/SBS_201E_DP_S_P_FS_test.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:countly_flutter/countly_flutter.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:integration_test/integration_test.dart'; - -import '../../utils.dart'; -import '../sbs_utils.dart'; - -/// Test calls all possible configuration features and shows that: -/// - eq is only given in the P, but because P is ignored, it is not set at the end -/// - Key length limit is overridden by S and stored correctly because FS does not include it -/// - FS overrides other limits and stored correctly -/// - S overrides RQ limit and stored correctly -/// - FS overrides request age drop hours to from 5>12>21, crash reporting to false, view reporting to false -/// How it affects the SDK: -/// - event requests are increased because EQ size is 5 in DP and it is not affected by SBS because they do not have it -/// - location requests are increased because location is disabled in DP then later enabled while calling all features -/// - crash requests are not sent -/// - no consent request here because SDK did not wait for server response here, it already had it -/// This test shows than FS is prior than S, S is prior than DP, if there is S then P is ignored -void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - testWidgets('SBS_201E_DP_S_P_FS_test', (WidgetTester tester) async { - List>> requestArray = >>[]; - createServer(requestArray, customHandler: (request, queryParams, response) async { - Map responseJson = {'result': 'Success'}; - if (queryParams.containsKey('method')) { - if (queryParams['method']!.first == 'feedback') { - responseJson = {'result': []}; - } else if (queryParams['method']!.first == 'sc') { - responseJson = { - 'v': 1, - 't': 1750748806695, - 'c': {'crt': false, 'vt': false, 'st': true, 'cr': false, 'cet': true, 'log': true, 'dort': 21, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} - }; - } - } - response - ..statusCode = HttpStatus.ok - ..headers.contentType = ContentType.json - ..headers.set('Access-Control-Allow-Origin', '*') - ..write(jsonEncode(responseJson)); - }); - - setServerConfig({ - 'v': 1, - 't': 1750748806695, - 'c': {'crt': false, 'vt': false, 'st': true, 'rqs': 100, 'cr': false, 'cet': true, 'log': false, 'dort': 12, 'lkl': 120, 'lvs': 255, 'lsv': 99, 'lbc': 99, 'ltlpt': 29, 'ltl': 199, 'rcz': true, 'bom': true} - }); - // Initialize the SDK - // in here eqs will not set because there is already stored SBS - CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); - config.setSDKBehaviorSettings('{"v":1,"t":1750748806695,"c":{"crt":true,"vt":false,"eqs": 30,"st":true,"cr":false,"cet":true,"log":false,"dort":12,"lkl":120,"lvs":255,"lsv":99,"lbc":99,"ltlpt":29,"ltl":199,"rcz":true,"bom":true}}'); - config.setMaxRequestQueueSize(5).setEventQueueSizeToSend(5).disableBackoffMechanism().setRequiresConsent(true).disableLocation().setRequestDropAgeHours(5).setUpdateSessionTimerDelay(75); - config.content.setZoneTimerInterval(17); - config.sdkInternalLimits.setMaxBreadcrumbCount(1).setMaxKeyLength(3).setMaxSegmentationValues(3).setMaxValueSize(5).setMaxStackTraceLineLength(300).setMaxStackTraceLinesPerThread(2); - - await Countly.initWithConfig(config); - await Future.delayed(const Duration(seconds: 2)); - - await callAllFeatures(); - - // EQS not exist because there are already stored SBS - expect(await getServerConfig(), { - 'v': 1, - 't': 1750748806695, - 'c': {'crt': false, 'vt': false, 'st': true, 'rqs': 100, 'cr': false, 'cet': true, 'log': true, 'dort': 21, 'lkl': 120, 'lvs': 79, 'lsv': 90, 'lbc': 88, 'ltlpt': 34, 'ltl': 250, 'rcz': true, 'bom': true} - }); - - validateRequestCounts({'events': 8, 'location': 3, 'crash': 0, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1, 'consent': 0}, requestArray); - validateInternalEventCounts({'orientation': 1}, requestArray); - validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); - }); -} From 407c8089c538ac4d9e548d043d9813ececa2f6fb Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Mon, 7 Jul 2025 22:18:37 +0300 Subject: [PATCH 34/64] fix: iOS temp id wont apply S SBS --- ios/Classes/CountlyiOS/CHANGELOG.md | 3 ++ ios/Classes/CountlyiOS/Countly.m | 2 +- ios/Classes/CountlyiOS/CountlyServerConfig.h | 2 +- ios/Classes/CountlyiOS/CountlyServerConfig.m | 29 +++++++++++--------- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/ios/Classes/CountlyiOS/CHANGELOG.md b/ios/Classes/CountlyiOS/CHANGELOG.md index 792822e5..40951ae7 100644 --- a/ios/Classes/CountlyiOS/CHANGELOG.md +++ b/ios/Classes/CountlyiOS/CHANGELOG.md @@ -1,3 +1,6 @@ +## XX.XX.XX +* Mitigated an issue where the SDK didn't apply the stored SBS while in temporary ID mode. + ## 25.4.3 * Mitigated an issue where SDK behavior settings were set to default when fetching for new config. * Mitigated an issue where latest fetched behavior settings were replacing the current settings instead of merging. diff --git a/ios/Classes/CountlyiOS/Countly.m b/ios/Classes/CountlyiOS/Countly.m index dda8eda8..b8987bc1 100644 --- a/ios/Classes/CountlyiOS/Countly.m +++ b/ios/Classes/CountlyiOS/Countly.m @@ -95,7 +95,7 @@ - (void)startWithConfig:(CountlyConfig *)config if (config.disableSDKBehaviorSettingsUpdates) { [CountlyServerConfig.sharedInstance disableSDKBehaviourSettings]; } - [CountlyServerConfig.sharedInstance retrieveServerConfigFromStorage:config.sdkBehaviorSettings]; + [CountlyServerConfig.sharedInstance retrieveServerConfigFromStorage:config]; CountlyCommon.sharedInstance.maxKeyLength = config.sdkInternalLimits.getMaxKeyLength; CountlyCommon.sharedInstance.maxValueLength = config.sdkInternalLimits.getMaxValueSize; diff --git a/ios/Classes/CountlyiOS/CountlyServerConfig.h b/ios/Classes/CountlyiOS/CountlyServerConfig.h index d81fc83a..030a72a2 100644 --- a/ios/Classes/CountlyiOS/CountlyServerConfig.h +++ b/ios/Classes/CountlyiOS/CountlyServerConfig.h @@ -13,7 +13,7 @@ extern NSString* const kCountlySCKeySC; + (instancetype)sharedInstance; - (void)fetchServerConfig:(CountlyConfig *)config; -- (void)retrieveServerConfigFromStorage:(NSString*) sdkBehaviorSettings; +- (void)retrieveServerConfigFromStorage:(CountlyConfig *)config; - (void)fetchServerConfigIfTimeIsUp; - (void)disableSDKBehaviourSettings; diff --git a/ios/Classes/CountlyiOS/CountlyServerConfig.m b/ios/Classes/CountlyiOS/CountlyServerConfig.m index 8cc11a93..33f7cd97 100644 --- a/ios/Classes/CountlyiOS/CountlyServerConfig.m +++ b/ios/Classes/CountlyiOS/CountlyServerConfig.m @@ -114,13 +114,13 @@ - (instancetype)init return self; } -- (void)retrieveServerConfigFromStorage:(NSString *)sdkBehaviorSettings +- (void)retrieveServerConfigFromStorage:(CountlyConfig *)config { NSMutableDictionary *persistentBehaviorSettings = [CountlyPersistency.sharedInstance retrieveServerConfig]; - if (persistentBehaviorSettings.count == 0 && sdkBehaviorSettings) + if (persistentBehaviorSettings.count == 0 && config.sdkBehaviorSettings) { NSError *error = nil; - id parsed = [NSJSONSerialization JSONObjectWithData:[sdkBehaviorSettings cly_dataUTF8] options:0 error:&error]; + id parsed = [NSJSONSerialization JSONObjectWithData:[config.sdkBehaviorSettings cly_dataUTF8] options:0 error:&error]; if ([parsed isKindOfClass:[NSDictionary class]]) { persistentBehaviorSettings = [(NSDictionary *)parsed mutableCopy]; @@ -130,7 +130,7 @@ - (void)retrieveServerConfigFromStorage:(NSString *)sdkBehaviorSettings } } - [self populateServerConfig:persistentBehaviorSettings]; + [self populateServerConfig:persistentBehaviorSettings withConfig:config]; } - (void)mergeBehaviorSettings:(NSMutableDictionary *)baseConfig @@ -209,27 +209,27 @@ - (void)setDoubleProperty:(double *)property fromDictionary:(NSDictionary *)dict } } -- (void)populateServerConfig:(NSDictionary *)serverConfig +- (void)populateServerConfig:(NSDictionary *)serverConfig withConfig:(CountlyConfig *)config { if (!serverConfig[kRConfig]) { CLY_LOG_D(@"%s, config key is missing in the server configuration omitting", __FUNCTION__); return; } - + NSDictionary *dictionary = serverConfig[kRConfig]; - + if (!serverConfig[kRVersion] || !serverConfig[kRTimestamp]) { CLY_LOG_D(@"%s, version or timestamp is missing in the server configuration omitting", __FUNCTION__); return; } - + _version = [serverConfig[kRVersion] integerValue]; _timestamp = [serverConfig[kRTimestamp] longLongValue]; - + NSMutableString *logString = [NSMutableString stringWithString:@"Server Config: "]; - + [self setBoolProperty:&_trackingEnabled fromDictionary:dictionary key:kTracking logString:logString]; [self setBoolProperty:&_networkingEnabled fromDictionary:dictionary key:kNetworking logString:logString]; [self setIntegerProperty:&_sessionInterval fromDictionary:dictionary key:kRSessionUpdateInterval logString:logString]; @@ -258,6 +258,11 @@ - (void)populateServerConfig:(NSDictionary *)serverConfig [self setDoubleProperty:&_bomRQPercentage fromDictionary:dictionary key:kRBOMRQPercentage logString:logString]; [self setIntegerProperty:&_bomRequestAge fromDictionary:dictionary key:kRBOMRequestAge logString:logString]; [self setIntegerProperty:&_bomDuration fromDictionary:dictionary key:kRBOMDuration logString:logString]; + + if(![logString isEqualToString: @"Server Config: "]){ + // means new config gotten, if that is the case notify SDK + [self notifySdkConfigChange: config]; + } CLY_LOG_D(@"%s, version:[%li], timestamp:[%lli], %@", __FUNCTION__, _version, _timestamp, logString); } @@ -432,10 +437,8 @@ - (void)fetchServerConfig:(CountlyConfig *)config NSMutableDictionary *persistentBehaviorSettings = [CountlyPersistency.sharedInstance retrieveServerConfig]; [self mergeBehaviorSettings:persistentBehaviorSettings withConfig:serverConfigResponse]; [CountlyPersistency.sharedInstance storeServerConfig:persistentBehaviorSettings]; - [self populateServerConfig:persistentBehaviorSettings]; + [self populateServerConfig:persistentBehaviorSettings withConfig:config]; } - - [self notifySdkConfigChange:config]; // if no config let stored ones to be set }; // Set default values NSURLSessionTask *task = [CountlyCommon.sharedInstance.URLSession dataTaskWithRequest:[self serverConfigRequest] completionHandler:handler]; From b3f8f8ff154444ead36d9ff9e353d51b4b31922f Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Mon, 7 Jul 2025 22:34:56 +0300 Subject: [PATCH 35/64] feat: new test and possible notes test mapping --- .../SBS_201E_DP_P_FS_temp_id_test.dart | 63 +++++++++++++++++++ .../sbs_tests/order_tests/notes.md | 32 +++++----- 2 files changed, 79 insertions(+), 16 deletions(-) create mode 100644 example/integration_test/sbs_tests/order_tests/SBS_201E_DP_P_FS_temp_id_test.dart diff --git a/example/integration_test/sbs_tests/order_tests/SBS_201E_DP_P_FS_temp_id_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201E_DP_P_FS_temp_id_test.dart new file mode 100644 index 00000000..c3fa78ff --- /dev/null +++ b/example/integration_test/sbs_tests/order_tests/SBS_201E_DP_P_FS_temp_id_test.dart @@ -0,0 +1,63 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:countly_flutter/countly_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../../event_tests/event_utils.dart'; +import '../../utils.dart'; + +/// Test records an event with a key and segmentation values that exceeds the maximum key length set by the SDK's internal limits server SBS limit. +/// - Key length limit is 3, value size is 5 and segmentation values 2 on DP +/// - Only key length in FS is 8 +/// - Key length 6, value size 4 and breadcrumb count 99 in P +/// - The event is recorded with the key truncated to 6 #P because of temporary id, sbs fetch did not sent +/// - Values in segmentation are truncated to 5 characters #DP +/// - Segmentation values are truncated to 4 characters #P +/// - Provided SBS is omitted because already stored SBS +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + testWidgets('SBS_201E_DP_P_FS_test', (WidgetTester tester) async { + List>> requestArray = >>[]; + createServer(requestArray, customHandler: (request, queryParams, response) async { + Map responseJson = {'result': 'Success'}; + if (queryParams.containsKey('method')) { + if (queryParams['method']!.first == 'sc') { + responseJson = { + 'v': 1, + 't': 1750748806695, + 'c': {'lkl': 8} + }; + } + } + response + ..statusCode = HttpStatus.ok + ..headers.contentType = ContentType.json + ..headers.set('Access-Control-Allow-Origin', '*') + ..write(jsonEncode(responseJson)); + }); + + // Initialize the SDK + CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true).enableTemporaryDeviceIDMode(); + config.setSDKBehaviorSettings('{"v":1,"t":1750748806695,"c":{"lkl":6, "lsv": 4, "lbc": 99}}'); + config.sdkInternalLimits.setMaxKeyLength(3).setMaxValueSize(5).setMaxSegmentationValues(2); + + await Countly.initWithConfig(config); + await Future.delayed(const Duration(seconds: 2)); + + await Countly.instance.events.recordEvent('ThisWillCLIPPED_BY_FS', {'no1': 'valueCLIPPED_BY_DP', 'no2': 'valueCLIPPED_BY_DP', 'no3': 'valueCLIPPED_BY_DP', 'no4': 'valueCLIPPED_BY_DP', 'no5': 'valueCLIPPED_BY_DP'}); + List rq = await getRequestQueue(); + List eq = await getEventQueue(); + expect(rq.length, 0); + expect(eq.length, 1); + + validateEvent(event: jsonDecode(eq.first), key: 'ThisWi', segmentation: {'no1': 'value', 'no4': 'value', 'no3': 'value', 'no5': 'value'}); + + expect(await getServerConfig(), { + 'v': 1, + 't': 1750748806691, + 'c': {'lkl': 6, 'lsv': 4, 'lbc': 99} + }); + }); +} diff --git a/example/integration_test/sbs_tests/order_tests/notes.md b/example/integration_test/sbs_tests/order_tests/notes.md index c2eff312..3375b0f1 100644 --- a/example/integration_test/sbs_tests/order_tests/notes.md +++ b/example/integration_test/sbs_tests/order_tests/notes.md @@ -2,19 +2,19 @@ Init time SBS | Set Config | Provided SBS | Stored SBS | Temp ID | Initial Behavior | |------------|--------------|------------|---------|------------------| -| ● | | | | Dev Set Config | -| ● | ● | | | Provided SBS | -| ● | | ● | | Stored SBS | -| ● | ● | ● | | Stored SBS | -| ● | ● | ● | ● | Stored SBS | -| ● | | ● | ● | Stored SBS | -| ● | | | ● | Dev Set Config | -| ● | ● | | ● | Provided SBS | -| | ● | | | Provided SBS | -| | ● | ● | | Stored SBS | -| | ● | | ● | Provided SBS | -| | ● | ● | ● | Stored SBS | -| | | ● | ● | Stored SBS | -| | | | ● | SDK Defaults | -| | | ● | | Stored SBS | -| | | | | SDK Defaults | +| ● | | | | Dev Set Config | 201A +| ● | ● | | | Provided SBS | 201A +| ● | | ● | | Stored SBS | 201B +| ● | ● | ● | | Stored SBS | 201C +| ● | ● | ● | ● | Stored SBS | 201D +| ● | | ● | ● | Stored SBS | 201D +| ● | | | ● | Dev Set Config | 201D +| ● | ● | | ● | Provided SBS | 201E +| | ● | | | Provided SBS | 201A +| | ● | ● | | Stored SBS | 201C +| | ● | | ● | Provided SBS | 201E +| | ● | ● | ● | Stored SBS | 201D +| | | ● | ● | Stored SBS | 201D +| | | | ● | SDK Defaults | --- +| | | ● | | Stored SBS | 201B +| | | | | SDK Defaults | 000 From 9d3a696f89ac181af56f319ea97b2dafbc33602c Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 8 Jul 2025 07:26:01 +0300 Subject: [PATCH 36/64] fix: ts on 201E sbs --- .../sbs_tests/order_tests/SBS_201E_DP_P_FS_temp_id_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/integration_test/sbs_tests/order_tests/SBS_201E_DP_P_FS_temp_id_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201E_DP_P_FS_temp_id_test.dart index c3fa78ff..5db6b830 100644 --- a/example/integration_test/sbs_tests/order_tests/SBS_201E_DP_P_FS_temp_id_test.dart +++ b/example/integration_test/sbs_tests/order_tests/SBS_201E_DP_P_FS_temp_id_test.dart @@ -40,7 +40,7 @@ void main() { // Initialize the SDK CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true).enableTemporaryDeviceIDMode(); - config.setSDKBehaviorSettings('{"v":1,"t":1750748806695,"c":{"lkl":6, "lsv": 4, "lbc": 99}}'); + config.setSDKBehaviorSettings('{"v":1,"t":1750748806691,"c":{"lkl":6, "lsv": 4, "lbc": 99}}'); config.sdkInternalLimits.setMaxKeyLength(3).setMaxValueSize(5).setMaxSegmentationValues(2); await Countly.initWithConfig(config); From ef36574883e244a650a42cb3724664164bf1f10a Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 8 Jul 2025 07:46:42 +0300 Subject: [PATCH 37/64] fix: test setup migration version --- .../count/dart/countly_flutter/CountlyFlutterPlugin.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/android/src/main/java/ly/count/dart/countly_flutter/CountlyFlutterPlugin.java b/android/src/main/java/ly/count/dart/countly_flutter/CountlyFlutterPlugin.java index 5aa5c3fc..7266c748 100644 --- a/android/src/main/java/ly/count/dart/countly_flutter/CountlyFlutterPlugin.java +++ b/android/src/main/java/ly/count/dart/countly_flutter/CountlyFlutterPlugin.java @@ -72,6 +72,7 @@ public class CountlyFlutterPlugin implements MethodCallHandler, FlutterPlugin, A private final String COUNTLY_FLUTTER_SDK_NAME_NO_PUSH = "dart-flutterbnp-android"; private final boolean BUILDING_WITH_PUSH_DISABLED = false; + private static final int DATA_SCHEMA_VERSIONS = 4; public void notifyPublicChannelRCDL(RequestResult downloadResult, String error, boolean fullValueUpdate, Map downloadedValues, Integer requestID) { Map data = new HashMap<>(); @@ -1412,6 +1413,12 @@ else if ("getRequestQueue".equals(call.method)) { CountlyStore countlyStore = new CountlyStore(context, new ModuleLog()); JSONObject jsonObject = args.getJSONObject(0); countlyStore.setServerConfig(jsonObject.toString()); + // Why this added here, it is that because when it is set something in storage + // sdk assumes that it is an older version so it start migrations that needs to be done + // but in this case we only want to use setting server config and do not want migrations + // to mess up our process flow. So in here we are setting it to latest known to get away with it. + // Normally in a fresh install migrations are first to run and they run once. + countlyStore.setDataSchemaVersion(DATA_SCHEMA_VERSIONS); result.success("setServerConfig: success"); } else if ("getServerConfig".equals(call.method)) { CountlyStore countlyStore = new CountlyStore(context, new ModuleLog()); From 33352e97979132c59792ce0fe72b17ffedb0e191 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 8 Jul 2025 08:13:40 +0300 Subject: [PATCH 38/64] feat: update orde test notes --- example/integration_test/sbs_tests/notes.md | 61 ++++++++++++++++----- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/example/integration_test/sbs_tests/notes.md b/example/integration_test/sbs_tests/notes.md index 51c37c1a..c5e7bb27 100644 --- a/example/integration_test/sbs_tests/notes.md +++ b/example/integration_test/sbs_tests/notes.md @@ -60,31 +60,62 @@ Validate that: 201X tests order validation where - A -configure all configurables via CountlyConfig validate that all set -then trigger server fetch and validate that server values changed the dev_provided configs +configure couple of internal limits in the CountlyConfig +Provide couple of internal limits in the provided SBS and return one internal limit from FS +Validate order is working and values applied +DP P FS Final +a a +b. b1 b1 +c. c1 c2 c2 - B -configure all configurables and provide SBS via CountlyConfig. -Validate that all dev_provided is overridden by provided. +configure couple of internal limits in the CountlyConfig +Provide couple of internal limits in the stored SBS and return one internal limit from FS +Validate order is working and values applied +DP S FS Final +a a +b. b1 b1 +c. c1 c2 c2 - C -configure all configurables, and provide SBS via CountlyConfig -and respond from the server, validate that server values are precedence. +configure couple of internal limits in the CountlyConfig +Provide couple of internal limits in the provided SBS, stored SBS and return one internal limit from FS +Validate provided SBS is not applied because stored existing +Validate order is working and values applied +DP P S FS Final +a a +b. b1 b +c. c1 c2 c2 +d. d1 d2 d3. d3 - D -configure all configurables, store SBS before initializing, validate that stored values have the precedence -trigger server fetch and validate that all values from_server +configure couple of internal limits in the CountlyConfig and enable temporary id mode +Provide couple of internal limits in the provided SBS, stored SBS and return one internal limit from FS +Validate provided SBS is not applied because stored existing +Validate order is working and values applied +Also validate FS not fetched because temporary id mode +DP P S FS Final +a a +b. b1 b +c. c1 c2 c2 +d. d1 d2 d3. d2 - E -configure all configurables, store SBS before initializing, provide SBS via CountlyConfig, validate that provided values have the precedence -trigger server fetch and validate that all values from_server +configure couple of internal limits in the CountlyConfig and enable temporary id mode +Provide couple of internal limits in the provided SBS and return one internal limit from FS +Validate order is working and values applied +Validate FS not fetched because temporary id mode +DP P FS Final +a a +b. b1 b1 +c. c1 c2 c1 tests are: -- 201A_DP_FS -- 201B_DP_P -- 201C_DP_P_FS -- 201D_DP_S_FS -- 201E_DP_S_P_FS +- 201A_DP_P_FS +- 201B_DP_S_FS +- 201C_DP_P_S_FS +- 201D_DP_P_S_FS_temp_id +- 201E_DP_P_FS_temp_id Notes iOS: In the base test iOS required more time then Android at the end From 4e0c2dbf79b159fb678fb25fde85b6bde8666c74 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 8 Jul 2025 08:23:22 +0300 Subject: [PATCH 39/64] refactor: order tests shorten server part --- .../order_tests/SBS_201A_DP_P_FS_test.dart | 22 +++++-------------- .../order_tests/SBS_201B_DP_S_FS_test.dart | 22 +++++-------------- .../order_tests/SBS_201C_DP_S_P_FS_test.dart | 22 +++++-------------- .../SBS_201D_DP_S_P_FS_temp_id_test.dart | 22 +++++-------------- .../SBS_201E_DP_P_FS_temp_id_test.dart | 22 +++++-------------- .../integration_test/sbs_tests/sbs_utils.dart | 18 +++++++++++++++ 6 files changed, 43 insertions(+), 85 deletions(-) diff --git a/example/integration_test/sbs_tests/order_tests/SBS_201A_DP_P_FS_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201A_DP_P_FS_test.dart index 3e9e03ed..1af901a1 100644 --- a/example/integration_test/sbs_tests/order_tests/SBS_201A_DP_P_FS_test.dart +++ b/example/integration_test/sbs_tests/order_tests/SBS_201A_DP_P_FS_test.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:io'; import 'package:countly_flutter/countly_flutter.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -7,6 +6,7 @@ import 'package:integration_test/integration_test.dart'; import '../../event_tests/event_utils.dart'; import '../../utils.dart'; +import '../sbs_utils.dart'; /// Test records an event with a key and segmentation values that exceeds the maximum key length set by the SDK's internal limits server SBS limit. /// - Key length limit is 3, value size is 5 and segmentation values 2 on DP @@ -19,22 +19,10 @@ void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('SBS_201A_DP_P_FS_test', (WidgetTester tester) async { List>> requestArray = >>[]; - createServer(requestArray, customHandler: (request, queryParams, response) async { - Map responseJson = {'result': 'Success'}; - if (queryParams.containsKey('method')) { - if (queryParams['method']!.first == 'sc') { - responseJson = { - 'v': 1, - 't': 1750748806695, - 'c': {'lkl': 8} - }; - } - } - response - ..statusCode = HttpStatus.ok - ..headers.contentType = ContentType.json - ..headers.set('Access-Control-Allow-Origin', '*') - ..write(jsonEncode(responseJson)); + createServerWithConfig(requestArray, { + 'v': 1, + 't': 1750748806695, + 'c': {'lkl': 8} }); // Initialize the SDK diff --git a/example/integration_test/sbs_tests/order_tests/SBS_201B_DP_S_FS_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201B_DP_S_FS_test.dart index f816c888..1adb19ff 100644 --- a/example/integration_test/sbs_tests/order_tests/SBS_201B_DP_S_FS_test.dart +++ b/example/integration_test/sbs_tests/order_tests/SBS_201B_DP_S_FS_test.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:io'; import 'package:countly_flutter/countly_flutter.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -7,6 +6,7 @@ import 'package:integration_test/integration_test.dart'; import '../../event_tests/event_utils.dart'; import '../../utils.dart'; +import '../sbs_utils.dart'; /// Test records an event with a key and segmentation values that exceeds the maximum key length set by the SDK's internal limits server SBS limit. /// - Key length limit is 3, value size is 5 and segmentation values 2 on DP @@ -19,22 +19,10 @@ void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('SBS_201B_DP_S_FS_test', (WidgetTester tester) async { List>> requestArray = >>[]; - createServer(requestArray, customHandler: (request, queryParams, response) async { - Map responseJson = {'result': 'Success'}; - if (queryParams.containsKey('method')) { - if (queryParams['method']!.first == 'sc') { - responseJson = { - 'v': 1, - 't': 1750748806695, - 'c': {'lkl': 8} - }; - } - } - response - ..statusCode = HttpStatus.ok - ..headers.contentType = ContentType.json - ..headers.set('Access-Control-Allow-Origin', '*') - ..write(jsonEncode(responseJson)); + createServerWithConfig(requestArray, { + 'v': 1, + 't': 1750748806695, + 'c': {'lkl': 8} }); setServerConfig({ diff --git a/example/integration_test/sbs_tests/order_tests/SBS_201C_DP_S_P_FS_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201C_DP_S_P_FS_test.dart index 95c2e8ac..31d95225 100644 --- a/example/integration_test/sbs_tests/order_tests/SBS_201C_DP_S_P_FS_test.dart +++ b/example/integration_test/sbs_tests/order_tests/SBS_201C_DP_S_P_FS_test.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:io'; import 'package:countly_flutter/countly_flutter.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -7,6 +6,7 @@ import 'package:integration_test/integration_test.dart'; import '../../event_tests/event_utils.dart'; import '../../utils.dart'; +import '../sbs_utils.dart'; /// Test records an event with a key and segmentation values that exceeds the maximum key length set by the SDK's internal limits server SBS limit. /// - Key length limit is 3, value size is 5 and segmentation values 2 on DP @@ -21,22 +21,10 @@ void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('SBS_201C_DP_S_P_FS_test', (WidgetTester tester) async { List>> requestArray = >>[]; - createServer(requestArray, customHandler: (request, queryParams, response) async { - Map responseJson = {'result': 'Success'}; - if (queryParams.containsKey('method')) { - if (queryParams['method']!.first == 'sc') { - responseJson = { - 'v': 1, - 't': 1750748806695, - 'c': {'lkl': 8} - }; - } - } - response - ..statusCode = HttpStatus.ok - ..headers.contentType = ContentType.json - ..headers.set('Access-Control-Allow-Origin', '*') - ..write(jsonEncode(responseJson)); + createServerWithConfig(requestArray, { + 'v': 1, + 't': 1750748806695, + 'c': {'lkl': 8} }); setServerConfig({ diff --git a/example/integration_test/sbs_tests/order_tests/SBS_201D_DP_S_P_FS_temp_id_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201D_DP_S_P_FS_temp_id_test.dart index 9adcd290..3017c80b 100644 --- a/example/integration_test/sbs_tests/order_tests/SBS_201D_DP_S_P_FS_temp_id_test.dart +++ b/example/integration_test/sbs_tests/order_tests/SBS_201D_DP_S_P_FS_temp_id_test.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:io'; import 'package:countly_flutter/countly_flutter.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -7,6 +6,7 @@ import 'package:integration_test/integration_test.dart'; import '../../event_tests/event_utils.dart'; import '../../utils.dart'; +import '../sbs_utils.dart'; /// Test records an event with a key and segmentation values that exceeds the maximum key length set by the SDK's internal limits server SBS limit. /// - Key length limit is 3, value size is 5 and segmentation values 2 on DP @@ -21,22 +21,10 @@ void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('SBS_201D_DP_S_P_FS_test', (WidgetTester tester) async { List>> requestArray = >>[]; - createServer(requestArray, customHandler: (request, queryParams, response) async { - Map responseJson = {'result': 'Success'}; - if (queryParams.containsKey('method')) { - if (queryParams['method']!.first == 'sc') { - responseJson = { - 'v': 1, - 't': 1750748806695, - 'c': {'lkl': 8} - }; - } - } - response - ..statusCode = HttpStatus.ok - ..headers.contentType = ContentType.json - ..headers.set('Access-Control-Allow-Origin', '*') - ..write(jsonEncode(responseJson)); + createServerWithConfig(requestArray, { + 'v': 1, + 't': 1750748806695, + 'c': {'lkl': 8} }); setServerConfig({ diff --git a/example/integration_test/sbs_tests/order_tests/SBS_201E_DP_P_FS_temp_id_test.dart b/example/integration_test/sbs_tests/order_tests/SBS_201E_DP_P_FS_temp_id_test.dart index 5db6b830..c97443f9 100644 --- a/example/integration_test/sbs_tests/order_tests/SBS_201E_DP_P_FS_temp_id_test.dart +++ b/example/integration_test/sbs_tests/order_tests/SBS_201E_DP_P_FS_temp_id_test.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:io'; import 'package:countly_flutter/countly_flutter.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -7,6 +6,7 @@ import 'package:integration_test/integration_test.dart'; import '../../event_tests/event_utils.dart'; import '../../utils.dart'; +import '../sbs_utils.dart'; /// Test records an event with a key and segmentation values that exceeds the maximum key length set by the SDK's internal limits server SBS limit. /// - Key length limit is 3, value size is 5 and segmentation values 2 on DP @@ -20,22 +20,10 @@ void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('SBS_201E_DP_P_FS_test', (WidgetTester tester) async { List>> requestArray = >>[]; - createServer(requestArray, customHandler: (request, queryParams, response) async { - Map responseJson = {'result': 'Success'}; - if (queryParams.containsKey('method')) { - if (queryParams['method']!.first == 'sc') { - responseJson = { - 'v': 1, - 't': 1750748806695, - 'c': {'lkl': 8} - }; - } - } - response - ..statusCode = HttpStatus.ok - ..headers.contentType = ContentType.json - ..headers.set('Access-Control-Allow-Origin', '*') - ..write(jsonEncode(responseJson)); + createServerWithConfig(requestArray, { + 'v': 1, + 't': 1750748806695, + 'c': {'lkl': 8} }); // Initialize the SDK diff --git a/example/integration_test/sbs_tests/sbs_utils.dart b/example/integration_test/sbs_tests/sbs_utils.dart index 6452cbc8..d58650a4 100644 --- a/example/integration_test/sbs_tests/sbs_utils.dart +++ b/example/integration_test/sbs_tests/sbs_utils.dart @@ -152,3 +152,21 @@ void validateRequestCounts(Map requests, List>> requestArray, Map serverConfig) { + createServer(requestArray, customHandler: (request, queryParams, response) async { + Map responseJson = {'result': 'Success'}; + if (queryParams.containsKey('method')) { + if (queryParams['method']!.first == 'sc') { + responseJson = serverConfig; + } + } + response + ..statusCode = HttpStatus.ok + ..headers.contentType = ContentType.json + ..headers.set('Access-Control-Allow-Origin', '*') + ..write(jsonEncode(responseJson)); + }); +} From a701dfd0c7761130315d5b115ca8f463db267ba5 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 8 Jul 2025 08:33:22 +0300 Subject: [PATCH 40/64] refactor: base test --- .../sbs_tests/SBS_000_base_test.dart | 21 +------------------ .../integration_test/sbs_tests/sbs_utils.dart | 9 ++++++++ 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/example/integration_test/sbs_tests/SBS_000_base_test.dart b/example/integration_test/sbs_tests/SBS_000_base_test.dart index 65041f4a..574500e5 100644 --- a/example/integration_test/sbs_tests/SBS_000_base_test.dart +++ b/example/integration_test/sbs_tests/SBS_000_base_test.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'dart:io'; import 'package:countly_flutter/countly_flutter.dart'; @@ -13,26 +12,8 @@ import 'sbs_utils.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('SBS_000_base', (WidgetTester tester) async { - int serverDelay = 0; List>> requestArray = >>[]; - createServer(requestArray, customHandler: (request, queryParams, response) async { - Map responseJson = {'result': 'Success'}; - if (queryParams.containsKey('method') && queryParams['method']!.first == 'feedback') { - responseJson = {'result': []}; - } - - if (serverDelay > 0 && !queryParams.containsKey('events')) { - // this orientation check for avoiding delay on backoff check - // to validate end session sent after 60 seconds - await Future.delayed(Duration(seconds: serverDelay)); - } - - response - ..statusCode = HttpStatus.ok - ..headers.contentType = ContentType.json - ..headers.set('Access-Control-Allow-Origin', '*') - ..write(jsonEncode(responseJson)); - }); + createServerWithConfig(requestArray, {}); // Initialize the SDK CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); await Countly.initWithConfig(config); diff --git a/example/integration_test/sbs_tests/sbs_utils.dart b/example/integration_test/sbs_tests/sbs_utils.dart index d58650a4..bcda0917 100644 --- a/example/integration_test/sbs_tests/sbs_utils.dart +++ b/example/integration_test/sbs_tests/sbs_utils.dart @@ -153,6 +153,8 @@ void validateRequestCounts(Map requests, List>> requestArray, Map serverConfig) { @@ -161,8 +163,15 @@ void createServerWithConfig(List>> requestArray, Map 0) { + await Future.delayed(Duration(seconds: sbsServerDelay)); + } + response ..statusCode = HttpStatus.ok ..headers.contentType = ContentType.json From 7d5b492787181f19862daafb24e8b15b1c860ce6 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 8 Jul 2025 09:20:29 +0300 Subject: [PATCH 41/64] feat: 200A feature test --- .../sbs_tests/SBS_200A_test.dart | 55 +++++++++++++++++++ .../integration_test/sbs_tests/sbs_utils.dart | 8 ++- 2 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 example/integration_test/sbs_tests/SBS_200A_test.dart diff --git a/example/integration_test/sbs_tests/SBS_200A_test.dart b/example/integration_test/sbs_tests/SBS_200A_test.dart new file mode 100644 index 00000000..c4173e07 --- /dev/null +++ b/example/integration_test/sbs_tests/SBS_200A_test.dart @@ -0,0 +1,55 @@ +import 'package:countly_flutter/countly_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../utils.dart'; +import 'sbs_utils.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + testWidgets('SBS_200A_test', (WidgetTester tester) async { + List>> requestArray = >>[]; + createServerWithConfig(requestArray, { + 'v': 1, + 't': 1750748806695, + 'c': {'lkl': 5, 'lvs': 5, 'lsv': 5, 'lbc': 5, 'ltlpt': 5, 'ltl': 5, 'rcz': false, 'ecz': true, 'czi': 16, 'bom': false} + }); + + // Initialize the SDK + CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); + + await Countly.initWithConfig(config); + await Future.delayed(const Duration(seconds: 2)); + + await callAllFeatures(disableEnterContent: true); + + validateRequestCounts({'events': 2, 'location': 1, 'crash': 2, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1, 'consent': 0}, requestArray); + validateInternalEventCounts({'orientation': 1, 'view': 6}, requestArray); + // enter content zone is not called, but a content zone request is sent it is because server config is set cz to true + validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); + + // VALIDATE INTERNAL LIMITS + + await Countly.instance.content.refreshContentZone(); // this will not affect because refresh disabled + validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); + + await Countly.instance.content.exitContentZone(); + requestArray.clear(); + + sbsServerDelay = 11; + + await Countly.instance.sessions.beginSession(); + await Countly.instance.sessions.endSession(); // this will not be backed off because backoff disabled + await Future.delayed(const Duration(seconds: 10)); // wait for sdk to process and get the result from server + + await Countly.instance.attemptToSendStoredRequests(); // this will take affect and trigger sending the requests + await Future.delayed(const Duration(seconds: 2)); + + validateRequestCounts({'begin_session': 1, 'end_session': 1}, requestArray); + expect(await getServerConfig(), { + 'v': 1, + 't': 1750748806695, + 'c': {'lkl': 5, 'lvs': 5, 'lsv': 5, 'lbc': 5, 'ltlpt': 5, 'ltl': 5, 'rcz': false, 'ecz': true, 'czi': 16, 'bom': false} + }); + }); +} diff --git a/example/integration_test/sbs_tests/sbs_utils.dart b/example/integration_test/sbs_tests/sbs_utils.dart index bcda0917..b73cd9dc 100644 --- a/example/integration_test/sbs_tests/sbs_utils.dart +++ b/example/integration_test/sbs_tests/sbs_utils.dart @@ -69,7 +69,7 @@ void validateInternalEventCounts(Map internalEventsCounts, List callAllFeatures() async { +Future callAllFeatures({bool disableEnterContent = false}) async { await Countly.giveAllConsent(); await Countly.getAvailableFeedbackWidgets(); await Countly.instance.sessions.beginSession(); @@ -90,7 +90,9 @@ Future callAllFeatures() async { await Countly.instance.views.startView('Dashboard', segmentation); // IMMEDIATE CALLS - await Countly.instance.content.enterContentZone(); + if (!disableEnterContent) { + await Countly.instance.content.enterContentZone(); + } await Countly.instance.remoteConfig.downloadAllKeys((rResult, error, fullValueUpdate, downloadedValues) { if (rResult == RequestResult.success) { // do sth @@ -168,7 +170,7 @@ void createServerWithConfig(List>> requestArray, Map 0) { + if (sbsServerDelay > 0 && !queryParams.containsKey('events')) { await Future.delayed(Duration(seconds: sbsServerDelay)); } From 96a46fc1842cef3c3812c0b67328215c5a24039a Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 9 Jul 2025 08:13:26 +0300 Subject: [PATCH 42/64] feat: feature test A don --- .../sbs_tests/SBS_200A_test.dart | 61 ++++++++++++++++++- example/integration_test/sbs_tests/notes.md | 2 +- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/example/integration_test/sbs_tests/SBS_200A_test.dart b/example/integration_test/sbs_tests/SBS_200A_test.dart index c4173e07..98a8cc5f 100644 --- a/example/integration_test/sbs_tests/SBS_200A_test.dart +++ b/example/integration_test/sbs_tests/SBS_200A_test.dart @@ -1,3 +1,6 @@ +import 'dart:convert'; +import 'dart:io'; + import 'package:countly_flutter/countly_flutter.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -24,11 +27,65 @@ void main() { await callAllFeatures(disableEnterContent: true); validateRequestCounts({'events': 2, 'location': 1, 'crash': 2, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1, 'consent': 0}, requestArray); - validateInternalEventCounts({'orientation': 1, 'view': 6}, requestArray); + validateInternalEventCounts({'orientation': 1, 'view': 5}, requestArray); // 6 android // enter content zone is not called, but a content zone request is sent it is because server config is set cz to true validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); - // VALIDATE INTERNAL LIMITS + for (var queryParams in requestArray) { + if (queryParams.containsKey('method') || queryParams.containsKey('hc')) { + continue; // skip immediate requests + } + testCommonRequestParams(queryParams); // checks general params + if (queryParams.containsKey('apm')) { + Map apm = json.decode(queryParams['apm']![0]); + expect(apm['name'].toString().length <= 5, isTrue); + } else if (queryParams.containsKey('crash')) { + Map crash = json.decode(queryParams['crash']![0]); + Map crashDetails = crash['_custom']; + expect(crashDetails.length <= 5, isTrue); + List logs = crash['_logs'].split('\n'); + expect(logs.length <= 5, isTrue); + for (var log in logs) { + expect(log.length <= 5, isTrue); + } + // iOS crash limits are not applied to the stack trace + if (Platform.isAndroid) { + List stackTraces = crash['_error'].split('\n'); + expect(stackTraces.length <= 5, isTrue); + for (var stackTrace in stackTraces) { + expect(stackTrace.length <= 5, isTrue); + } + expect(crash['_error'].length <= 5 * 5, isTrue); + } + + for (var key in crashDetails.keys) { + expect(key.length <= 5, isTrue); + expect(crashDetails[key].toString().length <= 5, isTrue); + } + } else if (queryParams.containsKey('events')) { + var eventRaw = json.decode(queryParams['events']![0]); + for (var event in eventRaw) { + validateInternalLimitsForEvents(event, 5, 5, 5); + } + } else if (queryParams.containsKey('user_details')) { + Map userDetails = json.decode(queryParams['user_details']![0]); + if (userDetails['custom'] != null && userDetails['custom'].length <= 2) { + // operators are not truncated with segmentation values limit + expect((userDetails['custom'].values.where((v) => v is! Map).length ?? 0) <= 5, isTrue); + expect(userDetails['custom']['speci'], 'somet'); + expect(userDetails['custom']['not_s'], 'somet'); + } + + // in iOS user data requests are formed in a different request + if (userDetails['custom'].length > 2) { + checkUnchangingUserData(userDetails, 5, 5); + } + + if (Platform.isAndroid || (Platform.isIOS && userDetails['custom'] == null)) { + checkUnchangingUserPropeties(userDetails, 5); + } + } + } await Countly.instance.content.refreshContentZone(); // this will not affect because refresh disabled validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); diff --git a/example/integration_test/sbs_tests/notes.md b/example/integration_test/sbs_tests/notes.md index c5e7bb27..751586ac 100644 --- a/example/integration_test/sbs_tests/notes.md +++ b/example/integration_test/sbs_tests/notes.md @@ -17,7 +17,7 @@ Tests - A Call all features -Provide SBS from server {'lkl': 5, 'lvs': 5, 'lsv': 5, 'lbc': 5, 'ltlpt': 5, 'ltl': 5, 'rcz': false, 'cz': true, 'czi': 16, 'bom': false} +Provide SBS from server {'lkl': 5, 'lvs': 5, 'lsv': 5, 'lbc': 5, 'ltlpt': 5, 'ltl': 5, 'rcz': false, 'ecz': true, 'czi': 16, 'bom': false} Change all SDK internal limits and validate that all are applied Trigger two requests that their response duration is above 10 seconds Validate that: From 15c4152831f368a9189c0b9894d0ac2712c62870 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 9 Jul 2025 18:20:04 +0300 Subject: [PATCH 43/64] feat: sbs200b tracking test --- .../sbs_tests/SBS_200B_test.dart | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 example/integration_test/sbs_tests/SBS_200B_test.dart diff --git a/example/integration_test/sbs_tests/SBS_200B_test.dart b/example/integration_test/sbs_tests/SBS_200B_test.dart new file mode 100644 index 00000000..17d9ebcf --- /dev/null +++ b/example/integration_test/sbs_tests/SBS_200B_test.dart @@ -0,0 +1,45 @@ +import 'package:countly_flutter/countly_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../utils.dart'; +import 'sbs_utils.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + testWidgets('SBS_200B_test', (WidgetTester tester) async { + List>> requestArray = >>[]; + createServerWithConfig(requestArray, { + 'v': 1, + 't': 1750748806695, + 'c': {'tracking': false, 'scui': 1} + }); + + // Initialize the SDK + CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); + + await Countly.initWithConfig(config); + await Future.delayed(const Duration(seconds: 2)); + + await callAllFeatures(disableSend: true); + List RQ = await getRequestQueue(); + List EQ = await getEventQueue(); + expect(RQ.length, 0); + expect(EQ.length, 0); + + await Countly.instance.attemptToSendStoredRequests(); + // check queues are empty and all requests are sent + await Future.delayed(const Duration(seconds: 10)); + + validateRequestCounts({'events': 0, 'location': 0, 'crash': 0, 'begin_session': 0, 'end_session': 0, 'session_duration': 0, 'apm': 0, 'user_details': 0, 'consent': 0}, requestArray); + validateInternalEventCounts({}, requestArray); // 6 android + // enter content zone is not called, but a content zone request is sent it is because server config is set cz to true + validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'rc': 1}, requestArray); // ab and ab_opt_out are not called because they are not immediate methods + + expect(await getServerConfig(), { + 'v': 1, + 't': 1750748806695, + 'c': {'tracking': false, 'scui': 1} + }); + }); +} From 9919fa878b6f3cfb7a53947f8f46b186f023a2ef Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 10 Jul 2025 10:09:41 +0300 Subject: [PATCH 44/64] feat: 200B feature test --- .../sbs_tests/SBS_200B_test.dart | 5 ++ .../integration_test/sbs_tests/sbs_utils.dart | 48 ++++++++++++++++++- example/integration_test/utils.dart | 6 ++- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/example/integration_test/sbs_tests/SBS_200B_test.dart b/example/integration_test/sbs_tests/SBS_200B_test.dart index 17d9ebcf..7e1e499b 100644 --- a/example/integration_test/sbs_tests/SBS_200B_test.dart +++ b/example/integration_test/sbs_tests/SBS_200B_test.dart @@ -5,6 +5,7 @@ import 'package:integration_test/integration_test.dart'; import '../utils.dart'; import 'sbs_utils.dart'; +/// Currently it is not possible to test SCUI, we only test its value validations void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('SBS_200B_test', (WidgetTester tester) async { @@ -41,5 +42,9 @@ void main() { 't': 1750748806695, 'c': {'tracking': false, 'scui': 1} }); + + await Future.delayed(const Duration(seconds: 60)); + // wait one minute and ensure no sc requests sent + validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 4, 'rc': 1}, requestArray); }); } diff --git a/example/integration_test/sbs_tests/sbs_utils.dart b/example/integration_test/sbs_tests/sbs_utils.dart index b73cd9dc..d2aab6a3 100644 --- a/example/integration_test/sbs_tests/sbs_utils.dart +++ b/example/integration_test/sbs_tests/sbs_utils.dart @@ -6,6 +6,13 @@ import 'package:flutter_test/flutter_test.dart'; import '../event_tests/event_utils.dart'; import '../utils.dart'; +/// internal event key: [reserved segmentation keys : is it truncable] +/// For example mode in orientation is not truncable, but name in view is truncable +Map> reservedSegmentationKeys = { + '[CLY]_view': {'name': true, 'visit': false, 'start': false, 'segment': false}, + '[CLY]_orientation': {'mode': false} +}; + /// Validates the immediate counts in the request array. /// This function checks the number of immediate methods recorded in the request array /// and compares them with the expected counts provided in the `immediates` map. @@ -69,10 +76,15 @@ void validateInternalEventCounts(Map internalEventsCounts, List callAllFeatures({bool disableEnterContent = false}) async { +Future callAllFeatures({bool disableEnterContent = false, bool disableSend = false}) async { await Countly.giveAllConsent(); await Countly.getAvailableFeedbackWidgets(); await Countly.instance.sessions.beginSession(); + await Countly.addCrashLog('First Breadcrumb'); // breadcrumb + await Countly.addCrashLog('Launched app'); // breadcrumb + await Countly.addCrashLog('Came to end'); // breadcrumb + await Countly.addCrashLog('Not done yet'); // breadcrumb + await Countly.addCrashLog('Will enter soon'); // breadcrumb await createTruncableEvents(); await generateEvents(); await Countly.setUserLocation(countryCode: 'TR', city: 'Istanbul', gpsCoordinates: '41.0082,28.9784', ipAddress: '10.2.33.12'); @@ -114,7 +126,10 @@ Future callAllFeatures({bool disableEnterContent = false}) async { await Future.delayed(const Duration(seconds: 2)); await Countly.instance.sessions.endSession(); - + if (disableSend) { + // if send is disabled, we will not send the requests to the server + return; + } await Countly.instance.attemptToSendStoredRequests(); // check queues are empty and all requests are sent await Future.delayed(const Duration(seconds: 10)); @@ -181,3 +196,32 @@ void createServerWithConfig(List>> requestArray, Map event, int maxKeyLength, int maxValueSize, int maxSegmentationValues) { + // Validate key length + Map validationSetForKey = reservedSegmentationKeys[event['key']] ?? {}; + bool isReservedKey = validationSetForKey.isNotEmpty; + + if (!isReservedKey) { + // internal keys like '[CLY]_view' are not truncated + expect(event['key'].toString().length <= maxKeyLength, isTrue); + } + + if (event['segmentation'] != null) { + // Validate segmentation keys and values + Map segmentation = event['segmentation']; + expect(segmentation.length <= maxSegmentationValues + validationSetForKey.length, isTrue); + for (var key in segmentation.keys) { + bool checkValueSizeLimit = validationSetForKey[key] ?? true; + if (validationSetForKey[key] == null) { + expect(key.length <= maxKeyLength, isTrue); + } + + if (checkValueSizeLimit && segmentation[key] is String) { + expect(segmentation[key].toString().length <= maxValueSize, isTrue); + } + } + } +} diff --git a/example/integration_test/utils.dart b/example/integration_test/utils.dart index 1609600c..580603f5 100644 --- a/example/integration_test/utils.dart +++ b/example/integration_test/utils.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:io'; +import 'dart:math'; import 'package:countly_flutter/countly_flutter.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; @@ -397,9 +398,10 @@ void checkUnchangingUserData(userDetails, MAX_KEY_LENGTH, MAX_VALUE_SIZE) { } /// Truncate a string to a given limit -String truncate(string, limit) { - var length = string.length; +String truncate(String string, int limit) { + int length = string.length; limit = limit != null ? limit : length; + limit = min(length, limit); return string.substring(0, limit); } From 0b6d115848839f5a9214296070d15584bdb32054 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 10 Jul 2025 12:02:16 +0300 Subject: [PATCH 45/64] fix: ioS disable networking hc --- ios/Classes/CountlyiOS/CountlyHealthTracker.m | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ios/Classes/CountlyiOS/CountlyHealthTracker.m b/ios/Classes/CountlyiOS/CountlyHealthTracker.m index 1ee68a53..89fc18b8 100644 --- a/ios/Classes/CountlyiOS/CountlyHealthTracker.m +++ b/ios/Classes/CountlyiOS/CountlyHealthTracker.m @@ -146,6 +146,12 @@ - (void)sendHealthCheck { if (CountlyDeviceInfo.sharedInstance.isDeviceIDTemporary) { CLY_LOG_W(@"%s, currently in temporary id mode, omitting", __FUNCTION__); } + + if (!CountlyServerConfig.sharedInstance.networkingEnabled) + { + CLY_LOG_D(@"'sendHealthCheck' is aborted: SDK Networking is disabled from server config!"); + return; + } if (!_healthCheckEnabled || _healthCheckSent) { CLY_LOG_D(@"%s, health check status, sent: %d, not_enabled: %d", __FUNCTION__, _healthCheckSent, _healthCheckEnabled); From ee3702d00187e1095368e7ccc6cb296a3b2dae37 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 10 Jul 2025 12:55:15 +0300 Subject: [PATCH 46/64] feat: move dort to the SBS 200 A --- .../sbs_tests/SBS_200A_test.dart | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/example/integration_test/sbs_tests/SBS_200A_test.dart b/example/integration_test/sbs_tests/SBS_200A_test.dart index 98a8cc5f..7fe406cc 100644 --- a/example/integration_test/sbs_tests/SBS_200A_test.dart +++ b/example/integration_test/sbs_tests/SBS_200A_test.dart @@ -15,9 +15,14 @@ void main() { createServerWithConfig(requestArray, { 'v': 1, 't': 1750748806695, - 'c': {'lkl': 5, 'lvs': 5, 'lsv': 5, 'lbc': 5, 'ltlpt': 5, 'ltl': 5, 'rcz': false, 'ecz': true, 'czi': 16, 'bom': false} + 'c': {'lkl': 5, 'lvs': 5, 'lsv': 5, 'lbc': 5, 'ltlpt': 5, 'ltl': 5, 'rcz': false, 'ecz': true, 'czi': 16, 'bom': false, 'dort': 1} }); + storeRequest({'first': 'true', 'device_id': 'device_id_200C', 'app_key': APP_KEY, 'timestamp': DateTime.now().subtract(const Duration(minutes: 65)).millisecondsSinceEpoch.toString()}); + storeRequest({'second': 'true', 'device_id': 'device_id_200C', 'app_key': APP_KEY, 'timestamp': DateTime.now().subtract(const Duration(minutes: 45)).millisecondsSinceEpoch.toString()}); + List>> RQ = await getRequestQueueParsed(); + validateRequestCounts({'first': 1, 'second': 1}, RQ); // validate that requests are stored correctly + expect(RQ.length, 2); // two requests should be stored // Initialize the SDK CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); @@ -25,14 +30,17 @@ void main() { await Future.delayed(const Duration(seconds: 2)); await callAllFeatures(disableEnterContent: true); + RQ = await getRequestQueueParsed(); + expect(RQ.length, 0); - validateRequestCounts({'events': 2, 'location': 1, 'crash': 2, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1, 'consent': 0}, requestArray); - validateInternalEventCounts({'orientation': 1, 'view': 5}, requestArray); // 6 android + validateRequestCounts({'first': 0, 'second': 1, 'events': 2, 'location': 1, 'crash': 2, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1, 'consent': 0}, requestArray); + // validate that first request is deleted from the queue because of dort: 1 + validateInternalEventCounts({'orientation': 1, 'view': Platform.isAndroid ? 6 : 5}, requestArray); // 6 android // enter content zone is not called, but a content zone request is sent it is because server config is set cz to true validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); for (var queryParams in requestArray) { - if (queryParams.containsKey('method') || queryParams.containsKey('hc')) { + if (queryParams.containsKey('method') || queryParams.containsKey('hc') || queryParams.containsKey('second')) { continue; // skip immediate requests } testCommonRequestParams(queryParams); // checks general params @@ -43,7 +51,7 @@ void main() { Map crash = json.decode(queryParams['crash']![0]); Map crashDetails = crash['_custom']; expect(crashDetails.length <= 5, isTrue); - List logs = crash['_logs'].split('\n'); + List logs = (crash['_logs'] as String).split('\n').where((line) => line.trim().isNotEmpty).toList(); expect(logs.length <= 5, isTrue); for (var log in logs) { expect(log.length <= 5, isTrue); @@ -51,11 +59,9 @@ void main() { // iOS crash limits are not applied to the stack trace if (Platform.isAndroid) { List stackTraces = crash['_error'].split('\n'); - expect(stackTraces.length <= 5, isTrue); for (var stackTrace in stackTraces) { expect(stackTrace.length <= 5, isTrue); } - expect(crash['_error'].length <= 5 * 5, isTrue); } for (var key in crashDetails.keys) { @@ -106,7 +112,7 @@ void main() { expect(await getServerConfig(), { 'v': 1, 't': 1750748806695, - 'c': {'lkl': 5, 'lvs': 5, 'lsv': 5, 'lbc': 5, 'ltlpt': 5, 'ltl': 5, 'rcz': false, 'ecz': true, 'czi': 16, 'bom': false} + 'c': {'lkl': 5, 'lvs': 5, 'lsv': 5, 'lbc': 5, 'ltlpt': 5, 'ltl': 5, 'rcz': false, 'ecz': true, 'czi': 16, 'bom': false, 'dort': 1} }); }); } From 5a17440684af942df8d7b4653464442dc03dd3e8 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 10 Jul 2025 13:00:03 +0300 Subject: [PATCH 47/64] feat: new function and move rq check in A casue ios crash --- .../sbs_tests/SBS_000_base_test.dart | 1 - .../integration_test/sbs_tests/SBS_200A_test.dart | 6 +++--- example/integration_test/sbs_tests/sbs_utils.dart | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/example/integration_test/sbs_tests/SBS_000_base_test.dart b/example/integration_test/sbs_tests/SBS_000_base_test.dart index 574500e5..fcc61c9a 100644 --- a/example/integration_test/sbs_tests/SBS_000_base_test.dart +++ b/example/integration_test/sbs_tests/SBS_000_base_test.dart @@ -20,7 +20,6 @@ void main() { await callAllFeatures(); - print(requestArray); List RQ = await getRequestQueue(); List EQ = await getEventQueue(); expect(RQ.length, 0); diff --git a/example/integration_test/sbs_tests/SBS_200A_test.dart b/example/integration_test/sbs_tests/SBS_200A_test.dart index 7fe406cc..610a0925 100644 --- a/example/integration_test/sbs_tests/SBS_200A_test.dart +++ b/example/integration_test/sbs_tests/SBS_200A_test.dart @@ -20,15 +20,15 @@ void main() { storeRequest({'first': 'true', 'device_id': 'device_id_200C', 'app_key': APP_KEY, 'timestamp': DateTime.now().subtract(const Duration(minutes: 65)).millisecondsSinceEpoch.toString()}); storeRequest({'second': 'true', 'device_id': 'device_id_200C', 'app_key': APP_KEY, 'timestamp': DateTime.now().subtract(const Duration(minutes: 45)).millisecondsSinceEpoch.toString()}); - List>> RQ = await getRequestQueueParsed(); - validateRequestCounts({'first': 1, 'second': 1}, RQ); // validate that requests are stored correctly - expect(RQ.length, 2); // two requests should be stored // Initialize the SDK CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); await Countly.initWithConfig(config); await Future.delayed(const Duration(seconds: 2)); + List>> RQ = await getRequestQueueParsed(); + validateRequestCounts({'first': 1, 'second': 1}, RQ); // validate that requests are stored correctly + await callAllFeatures(disableEnterContent: true); RQ = await getRequestQueueParsed(); expect(RQ.length, 0); diff --git a/example/integration_test/sbs_tests/sbs_utils.dart b/example/integration_test/sbs_tests/sbs_utils.dart index d2aab6a3..627e2aee 100644 --- a/example/integration_test/sbs_tests/sbs_utils.dart +++ b/example/integration_test/sbs_tests/sbs_utils.dart @@ -225,3 +225,17 @@ void validateInternalLimitsForEvents(Map event, int maxKeyLengt } } } + +/// Retrieves the request queue from the server. +/// And parses it into a list of maps with query parameters. +Future>>> getRequestQueueParsed() async { + List>> requestArray = >>[]; + List rq = await getRequestQueue(); + if (rq.isNotEmpty) { + requestArray = rq.map((item) { + Uri parsed = Uri.parse('https://count.ly?' + item); + return parsed.queryParametersAll; + }).toList(); + } + return requestArray; +} From dbfcb6e31419b68c7b176751b1634640ab3a1583 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 10 Jul 2025 13:25:23 +0300 Subject: [PATCH 48/64] feat: fix consent order in SBS --- ios/Classes/CountlyiOS/CountlyServerConfig.m | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ios/Classes/CountlyiOS/CountlyServerConfig.m b/ios/Classes/CountlyiOS/CountlyServerConfig.m index 33f7cd97..b3115a68 100644 --- a/ios/Classes/CountlyiOS/CountlyServerConfig.m +++ b/ios/Classes/CountlyiOS/CountlyServerConfig.m @@ -304,13 +304,6 @@ - (void)notifySdkConfigChange:(CountlyConfig *)config CountlyCommon.sharedInstance.maxValueLength = config.sdkInternalLimits.getMaxValueSize; CountlyCommon.sharedInstance.maxSegmentationValues = config.sdkInternalLimits.getMaxSegmentationValues; - config.requiresConsent = _consentRequired ?: config.requiresConsent; - CountlyConsentManager.sharedInstance.requiresConsent = config.requiresConsent; - if (_consentRequired) - { - [CountlyConsentManager.sharedInstance cancelConsentForAllFeatures]; - } - config.eventSendThreshold = _eventQueueSize ?: config.eventSendThreshold; config.requestDropAgeHours = _dropOldRequestTime ?: config.requestDropAgeHours; config.storedRequestsLimit = _requestQueueSize ?: config.storedRequestsLimit; @@ -321,6 +314,13 @@ - (void)notifySdkConfigChange:(CountlyConfig *)config config.updateSessionPeriod = _sessionInterval ?: config.updateSessionPeriod; _sessionInterval = config.updateSessionPeriod; + config.requiresConsent = _consentRequired ?: config.requiresConsent; + CountlyConsentManager.sharedInstance.requiresConsent = config.requiresConsent; + if (_consentRequired) + { + [CountlyConsentManager.sharedInstance cancelConsentForAllFeatures]; + } + #if (TARGET_OS_IOS) [config.content setZoneTimerInterval:_contentZoneInterval ?: config.content.getZoneTimerInterval]; if (config.content.getZoneTimerInterval) From 0c8f75cd50fa3b838490352b222e28a9188e815f Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 10 Jul 2025 13:35:46 +0300 Subject: [PATCH 49/64] feat: if checks to completion handlers widgets --- .../CountlyiOS/CountlyFeedbacksInternal.m | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/ios/Classes/CountlyiOS/CountlyFeedbacksInternal.m b/ios/Classes/CountlyiOS/CountlyFeedbacksInternal.m index 6b320811..71a9e46a 100644 --- a/ios/Classes/CountlyiOS/CountlyFeedbacksInternal.m +++ b/ios/Classes/CountlyiOS/CountlyFeedbacksInternal.m @@ -416,14 +416,26 @@ - (void)getFeedbackWidgets:(void (^)(NSArray *feedback if (!CountlyServerConfig.sharedInstance.networkingEnabled) { CLY_LOG_D(@"'getFeedbackWidgets' is aborted: SDK Networking is disabled from server config!"); + if(completionHandler){ + completionHandler(nil, nil); + } return; } - if (!CountlyConsentManager.sharedInstance.consentForFeedback) + if (!CountlyConsentManager.sharedInstance.consentForFeedback) { + if(completionHandler){ + completionHandler(nil, nil); + } return; + } + - if (CountlyDeviceInfo.sharedInstance.isDeviceIDTemporary) + if (CountlyDeviceInfo.sharedInstance.isDeviceIDTemporary) { + if(completionHandler){ + completionHandler(nil, nil); + } return; + } NSURLSessionTask* task = [CountlyCommon.sharedInstance.URLSession dataTaskWithRequest:[self feedbacksRequest] completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) { From e046fd593989fd51a4fcbe9bc6174f44e6452c72 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 10 Jul 2025 15:31:10 +0300 Subject: [PATCH 50/64] feat: act consent like in android sbs --- ios/Classes/CountlyiOS/CountlyServerConfig.m | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ios/Classes/CountlyiOS/CountlyServerConfig.m b/ios/Classes/CountlyiOS/CountlyServerConfig.m index b3115a68..ce669d99 100644 --- a/ios/Classes/CountlyiOS/CountlyServerConfig.m +++ b/ios/Classes/CountlyiOS/CountlyServerConfig.m @@ -316,10 +316,6 @@ - (void)notifySdkConfigChange:(CountlyConfig *)config config.requiresConsent = _consentRequired ?: config.requiresConsent; CountlyConsentManager.sharedInstance.requiresConsent = config.requiresConsent; - if (_consentRequired) - { - [CountlyConsentManager.sharedInstance cancelConsentForAllFeatures]; - } #if (TARGET_OS_IOS) [config.content setZoneTimerInterval:_contentZoneInterval ?: config.content.getZoneTimerInterval]; From 0e26ed9683d317c9a931e51dc1f0190f2058f0c2 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 10 Jul 2025 16:33:44 +0300 Subject: [PATCH 51/64] feat: 200C sbs feature tests --- .../sbs_tests/SBS_200C_test.dart | 96 +++++++++++++++++++ .../integration_test/sbs_tests/sbs_utils.dart | 6 +- 2 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 example/integration_test/sbs_tests/SBS_200C_test.dart diff --git a/example/integration_test/sbs_tests/SBS_200C_test.dart b/example/integration_test/sbs_tests/SBS_200C_test.dart new file mode 100644 index 00000000..526bd7cf --- /dev/null +++ b/example/integration_test/sbs_tests/SBS_200C_test.dart @@ -0,0 +1,96 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:countly_flutter/countly_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../utils.dart'; +import 'sbs_utils.dart'; + +/// use auto sessions for showing session update +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + testWidgets('SBS_200C_test', (WidgetTester tester) async { + List>> requestArray = >>[]; + createServer(requestArray, customHandler: (request, queryParams, response) async { + Map responseJson = {'result': 'Success'}; + if (queryParams.containsKey('method')) { + if (queryParams['method']!.first == 'feedback') { + responseJson = {'result': []}; + } + } + + response + ..statusCode = HttpStatus.ok + ..headers.contentType = ContentType.json + ..headers.set('Access-Control-Allow-Origin', '*') + ..write(jsonEncode(responseJson)); + }); + + setServerConfig({ + 'v': 1, + 't': 1750748806695, + 'c': {'networking': false, 'cr': true, 'rqs': 5, 'sui': 10} + }); + + // Initialize the SDK + CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).setLoggingEnabled(true).setDeviceId('device_id_200C'); + + await Countly.initWithConfig(config); + await Future.delayed(const Duration(seconds: 2)); + + await callAllFeatures(disableConsentCall: true); + + // Validate that networking is disabled and no requests are sent + expect(requestArray.length, 1); // only SC request should be sent + validateImmediateCounts({'sc': 1}, requestArray); + requestArray.clear(); // clear requestArray to validate the next requests + + // Validate that consent is required and not given and all called features are not created a request + List>> rq = await getRequestQueueParsed(); + validateRequestCounts({'consent': 1, 'location': 1}, rq); + Map expectedConsent = {'push': false, 'views': false, 'attribution': false, 'content': false, 'users': false, 'feedback': false, 'apm': false, 'location': false, 'remote-config': false, 'sessions': false, 'crashes': false, 'events': false}; + + if (Platform.isAndroid) { + expectedConsent['scrolls'] = false; // Android has scrolls, content, star-rating, clicks consents extra + expectedConsent['content'] = false; + expectedConsent['star-rating'] = false; + expectedConsent['clicks'] = false; + } + + expect(jsonDecode(rq[0]['consent']![0]), expectedConsent); + expect(rq[1]['location']![0], ''); + expect(rq.length, 2); + + // Validate that session update occurs in every 10 seconds + await Countly.giveConsent(['sessions']); + // after giving this + // one consent, one begin session and two duration requests should be sent + // however this adds up to 6 request + // because our RQ limit is 5 the first consent request where all false is dropped + + await Future.delayed(const Duration(seconds: 25)); + rq = await getRequestQueueParsed(); + expect(rq.length, 5); // 5 request at max could be + expect(requestArray.length, 0); // none request sent after sc request + + validateRequestCounts({'begin_session': 1, 'session_duration': 2, 'consent': 1, 'location': 2}, rq); // one location is in begin_session + expect(rq[0]['location']![0], ''); // first request is location request from previous validations, it was consent request before but now location + + expect(rq[1]['begin_session']![0], '1'); // second request is begin session request from auto sessions + expect(rq[1]['location']![0], ''); // show location is disabled because no consent given with tied to session request + + expectedConsent['sessions'] = true; // now sessions consent is true + expect(jsonDecode(rq[2]['consent']![0]), expectedConsent); // second request is consent request + + expect(rq[3]['session_duration']![0], '5'); // fourth request is session duration request + expect(rq[4]['session_duration']![0], '10'); // fifth request is session duration request and it is 10 + + expect(await getServerConfig(), { + 'v': 1, + 't': 1750748806695, + 'c': {'networking': false, 'cr': true, 'rqs': 5, 'sui': 10} + }); + }); +} diff --git a/example/integration_test/sbs_tests/sbs_utils.dart b/example/integration_test/sbs_tests/sbs_utils.dart index 627e2aee..28133507 100644 --- a/example/integration_test/sbs_tests/sbs_utils.dart +++ b/example/integration_test/sbs_tests/sbs_utils.dart @@ -76,8 +76,10 @@ void validateInternalEventCounts(Map internalEventsCounts, List callAllFeatures({bool disableEnterContent = false, bool disableSend = false}) async { - await Countly.giveAllConsent(); +Future callAllFeatures({bool disableEnterContent = false, bool disableSend = false, bool disableConsentCall = false}) async { + if (!disableConsentCall) { + await Countly.giveAllConsent(); + } await Countly.getAvailableFeedbackWidgets(); await Countly.instance.sessions.beginSession(); await Countly.addCrashLog('First Breadcrumb'); // breadcrumb From fb776b03d144dca3da50076b399e1a6c4480f9eb Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 10 Jul 2025 16:35:28 +0300 Subject: [PATCH 52/64] refactor: 200C startup --- .../sbs_tests/SBS_200C_test.dart | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/example/integration_test/sbs_tests/SBS_200C_test.dart b/example/integration_test/sbs_tests/SBS_200C_test.dart index 526bd7cf..635d59a6 100644 --- a/example/integration_test/sbs_tests/SBS_200C_test.dart +++ b/example/integration_test/sbs_tests/SBS_200C_test.dart @@ -13,19 +13,10 @@ void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('SBS_200C_test', (WidgetTester tester) async { List>> requestArray = >>[]; - createServer(requestArray, customHandler: (request, queryParams, response) async { - Map responseJson = {'result': 'Success'}; - if (queryParams.containsKey('method')) { - if (queryParams['method']!.first == 'feedback') { - responseJson = {'result': []}; - } - } - - response - ..statusCode = HttpStatus.ok - ..headers.contentType = ContentType.json - ..headers.set('Access-Control-Allow-Origin', '*') - ..write(jsonEncode(responseJson)); + createServerWithConfig(requestArray, { + 'v': 1, + 't': 1750748806695, + 'c': {'networking': false, 'cr': true, 'rqs': 5, 'sui': 10} }); setServerConfig({ From 484057625b9beb1da6a35ea3ff998016ae3fc305 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Fri, 11 Jul 2025 10:46:41 +0300 Subject: [PATCH 53/64] feat: basic 200D --- .../sbs_tests/SBS_200A_test.dart | 7 ++-- .../sbs_tests/SBS_200D_test.dart | 42 +++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 example/integration_test/sbs_tests/SBS_200D_test.dart diff --git a/example/integration_test/sbs_tests/SBS_200A_test.dart b/example/integration_test/sbs_tests/SBS_200A_test.dart index 610a0925..9cbc89ea 100644 --- a/example/integration_test/sbs_tests/SBS_200A_test.dart +++ b/example/integration_test/sbs_tests/SBS_200A_test.dart @@ -17,8 +17,6 @@ void main() { 't': 1750748806695, 'c': {'lkl': 5, 'lvs': 5, 'lsv': 5, 'lbc': 5, 'ltlpt': 5, 'ltl': 5, 'rcz': false, 'ecz': true, 'czi': 16, 'bom': false, 'dort': 1} }); - storeRequest({'first': 'true', 'device_id': 'device_id_200C', 'app_key': APP_KEY, 'timestamp': DateTime.now().subtract(const Duration(minutes: 65)).millisecondsSinceEpoch.toString()}); - storeRequest({'second': 'true', 'device_id': 'device_id_200C', 'app_key': APP_KEY, 'timestamp': DateTime.now().subtract(const Duration(minutes: 45)).millisecondsSinceEpoch.toString()}); // Initialize the SDK CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); @@ -26,6 +24,9 @@ void main() { await Countly.initWithConfig(config); await Future.delayed(const Duration(seconds: 2)); + storeRequest({'first': 'true', 'device_id': 'device_id_200C', 'app_key': APP_KEY, 'timestamp': DateTime.now().subtract(const Duration(minutes: 65)).millisecondsSinceEpoch.toString()}); + storeRequest({'second': 'true', 'device_id': 'device_id_200C', 'app_key': APP_KEY, 'timestamp': DateTime.now().subtract(const Duration(minutes: 45)).millisecondsSinceEpoch.toString()}); + List>> RQ = await getRequestQueueParsed(); validateRequestCounts({'first': 1, 'second': 1}, RQ); // validate that requests are stored correctly @@ -33,7 +34,7 @@ void main() { RQ = await getRequestQueueParsed(); expect(RQ.length, 0); - validateRequestCounts({'first': 0, 'second': 1, 'events': 2, 'location': 1, 'crash': 2, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': 1, 'consent': 0}, requestArray); + validateRequestCounts({'first': 0, 'second': 1, 'events': 2, 'location': 1, 'crash': 2, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': Platform.isIOS ? 2 : 1, 'consent': 0}, requestArray); // validate that first request is deleted from the queue because of dort: 1 validateInternalEventCounts({'orientation': 1, 'view': Platform.isAndroid ? 6 : 5}, requestArray); // 6 android // enter content zone is not called, but a content zone request is sent it is because server config is set cz to true diff --git a/example/integration_test/sbs_tests/SBS_200D_test.dart b/example/integration_test/sbs_tests/SBS_200D_test.dart new file mode 100644 index 00000000..10e6b580 --- /dev/null +++ b/example/integration_test/sbs_tests/SBS_200D_test.dart @@ -0,0 +1,42 @@ +import 'dart:io'; + +import 'package:countly_flutter/countly_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../utils.dart'; +import 'sbs_utils.dart'; + +///This test calls all features possible +///It is base test, tries to show how features working without SBS and defaults +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + testWidgets('SBS_200D_test', (WidgetTester tester) async { + List>> requestArray = >>[]; + createServerWithConfig(requestArray, { + 'v': 1, + 't': 1750748806695, + 'c': {'st': false, 'cet': false, 'vt': false, 'eqs': 5, 'lt': false, 'crt': false, 'bom_at': 5, 'bom_d': 30, 'bom_rqp': 0.01, 'bom_ra': 1} + }); + // Initialize the SDK + CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); + await Countly.initWithConfig(config); + + await callAllFeatures(); + + List RQ = await getRequestQueue(); + List EQ = await getEventQueue(); + expect(RQ.length, 0); + expect(EQ.length, 0); + print(requestArray); + validateRequestCounts({'events': 0, 'location': 1, 'crash': 0, 'begin_session': 0, 'consent': 0, 'end_session': 0, 'session_duration': 0, 'apm': 2, 'user_details': Platform.isIOS ? 2 : 1}, requestArray); + validateInternalEventCounts({}, requestArray); + validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); + + expect(await getServerConfig(), { + 'v': 1, + 't': 1750748806695, + 'c': {'st': false, 'cet': false, 'vt': false, 'eqs': 5, 'lt': false, 'crt': false, 'bom_at': 5, 'bom_d': 30, 'bom_rqp': 0.01, 'bom_ra': 1} + }); + }); +} From dd82caba681efc418129de7ef1c6a26eda9894e9 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Fri, 11 Jul 2025 12:06:25 +0300 Subject: [PATCH 54/64] feat: nps widget report --- .../sbs_tests/SBS_000_base_test.dart | 4 +-- .../sbs_tests/SBS_200A_test.dart | 4 +-- .../sbs_tests/SBS_200D_test.dart | 5 ++- example/integration_test/sbs_tests/notes.md | 11 +++++-- .../integration_test/sbs_tests/sbs_utils.dart | 31 +++++++++---------- 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/example/integration_test/sbs_tests/SBS_000_base_test.dart b/example/integration_test/sbs_tests/SBS_000_base_test.dart index fcc61c9a..2c0033d5 100644 --- a/example/integration_test/sbs_tests/SBS_000_base_test.dart +++ b/example/integration_test/sbs_tests/SBS_000_base_test.dart @@ -24,8 +24,8 @@ void main() { List EQ = await getEventQueue(); expect(RQ.length, 0); expect(EQ.length, 0); - validateRequestCounts({'events': 2, 'location': 1, 'crash': 2, 'begin_session': 1, 'consent': 0, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': Platform.isIOS ? 2 : 1}, requestArray); - validateInternalEventCounts({'orientation': 1, 'view': 6}, requestArray); + validateRequestCounts({'events': 3, 'location': 1, 'crash': 2, 'begin_session': 1, 'consent': 0, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': Platform.isIOS ? 2 : 1}, requestArray); + validateInternalEventCounts({'orientation': 1, 'view': 6, 'nps': 1}, requestArray); validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); }); } diff --git a/example/integration_test/sbs_tests/SBS_200A_test.dart b/example/integration_test/sbs_tests/SBS_200A_test.dart index 9cbc89ea..eada9b3f 100644 --- a/example/integration_test/sbs_tests/SBS_200A_test.dart +++ b/example/integration_test/sbs_tests/SBS_200A_test.dart @@ -34,9 +34,9 @@ void main() { RQ = await getRequestQueueParsed(); expect(RQ.length, 0); - validateRequestCounts({'first': 0, 'second': 1, 'events': 2, 'location': 1, 'crash': 2, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': Platform.isIOS ? 2 : 1, 'consent': 0}, requestArray); + validateRequestCounts({'first': 0, 'second': 1, 'events': Platform.isAndroid ? 3 : 2, 'location': 1, 'crash': 2, 'begin_session': 1, 'end_session': 1, 'session_duration': 2, 'apm': 2, 'user_details': Platform.isIOS ? 2 : 1, 'consent': 0}, requestArray); // validate that first request is deleted from the queue because of dort: 1 - validateInternalEventCounts({'orientation': 1, 'view': Platform.isAndroid ? 6 : 5}, requestArray); // 6 android + validateInternalEventCounts({'orientation': 1, 'view': Platform.isAndroid ? 6 : 5, 'nps': 1}, requestArray); // 6 android // enter content zone is not called, but a content zone request is sent it is because server config is set cz to true validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); diff --git a/example/integration_test/sbs_tests/SBS_200D_test.dart b/example/integration_test/sbs_tests/SBS_200D_test.dart index 10e6b580..be8635f5 100644 --- a/example/integration_test/sbs_tests/SBS_200D_test.dart +++ b/example/integration_test/sbs_tests/SBS_200D_test.dart @@ -28,9 +28,8 @@ void main() { List EQ = await getEventQueue(); expect(RQ.length, 0); expect(EQ.length, 0); - print(requestArray); - validateRequestCounts({'events': 0, 'location': 1, 'crash': 0, 'begin_session': 0, 'consent': 0, 'end_session': 0, 'session_duration': 0, 'apm': 2, 'user_details': Platform.isIOS ? 2 : 1}, requestArray); - validateInternalEventCounts({}, requestArray); + validateRequestCounts({'events': 1, 'location': 1, 'crash': 0, 'begin_session': 0, 'consent': 0, 'end_session': 0, 'session_duration': 0, 'apm': 2, 'user_details': Platform.isIOS ? 2 : 1}, requestArray); + validateInternalEventCounts({'nps': 1}, requestArray); validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); expect(await getServerConfig(), { diff --git a/example/integration_test/sbs_tests/notes.md b/example/integration_test/sbs_tests/notes.md index 751586ac..b13310e7 100644 --- a/example/integration_test/sbs_tests/notes.md +++ b/example/integration_test/sbs_tests/notes.md @@ -17,8 +17,9 @@ Tests - A Call all features -Provide SBS from server {'lkl': 5, 'lvs': 5, 'lsv': 5, 'lbc': 5, 'ltlpt': 5, 'ltl': 5, 'rcz': false, 'ecz': true, 'czi': 16, 'bom': false} +Provide SBS from server {'lkl': 5, 'lvs': 5, 'lsv': 5, 'lbc': 5, 'ltlpt': 5, 'ltl': 5, 'rcz': false, 'ecz': true, 'czi': 16, 'bom': false, 'dort': 1} Change all SDK internal limits and validate that all are applied +Store couple of requests before starting the SDK and show that they are deleted by drop request age Trigger two requests that their response duration is above 10 seconds Validate that: - content zone is called after init @@ -38,8 +39,7 @@ Validate that: - C Call all features -Provide SBS from server {'networking': false, 'cr': true, 'rqs': 5, 'sui': 10, 'dort': 1} -Store couple of requests before starting the SDK and show that they are deleted by drop request age +Provide SBS from storage {'networking': false, 'cr': true, 'rqs': 5, 'sui': 10} Validate that: - No requests exist in the sent requestArray in mock server - RQ contains items @@ -121,3 +121,8 @@ Notes iOS: In the base test iOS required more time then Android at the end Because there is a probability for iOS to duplicate requests, checking request counts were not good getAvaliableFeedbackWidgets= if no consent it broken iOS +iOS crash limits not applied to the stack traces +because health checks one of the earlier ones, in 200C if it was FS heath checks was sent because it runs before we fetch SBS. +Android has scrolls, content, star-rating, clicks consents extra + +validation things with base test diff --git a/example/integration_test/sbs_tests/sbs_utils.dart b/example/integration_test/sbs_tests/sbs_utils.dart index 28133507..a03846df 100644 --- a/example/integration_test/sbs_tests/sbs_utils.dart +++ b/example/integration_test/sbs_tests/sbs_utils.dart @@ -10,7 +10,10 @@ import '../utils.dart'; /// For example mode in orientation is not truncable, but name in view is truncable Map> reservedSegmentationKeys = { '[CLY]_view': {'name': true, 'visit': false, 'start': false, 'segment': false}, - '[CLY]_orientation': {'mode': false} + '[CLY]_orientation': {'mode': false}, + '[CLY]_nps': {'platform': false, 'app_version': false, 'widget_id': false, 'closed': false, 'rating': false, 'comment': false}, + '[CLY]_survey': {'platform': false, 'app_version': false, 'widget_id': false, 'closed': false}, + '[CLY]_star_rating': {'platform': false, 'app_version': false, 'widget_id': false, 'closed': false, 'rating': false, 'comment': false}, }; /// Validates the immediate counts in the request array. @@ -119,7 +122,7 @@ Future callAllFeatures({bool disableEnterContent = false, bool disableSend await Countly.instance.remoteConfig.exitABTestsForKeys(['key1', 'key2']); // END IMMEDIATE CALLS - await Countly.reportFeedbackWidgetManually(CountlyPresentableFeedback('test', 'nps', 'test'), {}, {}); + await Countly.reportFeedbackWidgetManually(CountlyPresentableFeedback('npsID', 'nps', 'NPS Feedback'), {}, {'rating': 5, 'comment': 'Great app!'}); await Future.delayed(const Duration(seconds: 2)); await Countly.instance.sessions.updateSession(); @@ -155,20 +158,8 @@ void validateRequestCounts(Map requests, List>> requestArray, Map Date: Fri, 11 Jul 2025 12:07:02 +0300 Subject: [PATCH 55/64] feat: nps widget report --- example/integration_test/sbs_tests/SBS_200D_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/integration_test/sbs_tests/SBS_200D_test.dart b/example/integration_test/sbs_tests/SBS_200D_test.dart index be8635f5..8d903f65 100644 --- a/example/integration_test/sbs_tests/SBS_200D_test.dart +++ b/example/integration_test/sbs_tests/SBS_200D_test.dart @@ -28,7 +28,7 @@ void main() { List EQ = await getEventQueue(); expect(RQ.length, 0); expect(EQ.length, 0); - validateRequestCounts({'events': 1, 'location': 1, 'crash': 0, 'begin_session': 0, 'consent': 0, 'end_session': 0, 'session_duration': 0, 'apm': 2, 'user_details': Platform.isIOS ? 2 : 1}, requestArray); + validateRequestCounts({'events': Platform.isIOS ? 2 : 1, 'location': 1, 'crash': 0, 'begin_session': 0, 'consent': 0, 'end_session': 0, 'session_duration': 0, 'apm': 2, 'user_details': Platform.isIOS ? 2 : 1}, requestArray); validateInternalEventCounts({'nps': 1}, requestArray); validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); From 0b44c9feb60a27523eec28846d3dd1c597c7e553 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Fri, 11 Jul 2025 12:10:57 +0300 Subject: [PATCH 56/64] fix: stablize SBS200D --- example/integration_test/sbs_tests/SBS_200D_test.dart | 2 +- example/integration_test/sbs_tests/notes.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/example/integration_test/sbs_tests/SBS_200D_test.dart b/example/integration_test/sbs_tests/SBS_200D_test.dart index 8d903f65..be8635f5 100644 --- a/example/integration_test/sbs_tests/SBS_200D_test.dart +++ b/example/integration_test/sbs_tests/SBS_200D_test.dart @@ -28,7 +28,7 @@ void main() { List EQ = await getEventQueue(); expect(RQ.length, 0); expect(EQ.length, 0); - validateRequestCounts({'events': Platform.isIOS ? 2 : 1, 'location': 1, 'crash': 0, 'begin_session': 0, 'consent': 0, 'end_session': 0, 'session_duration': 0, 'apm': 2, 'user_details': Platform.isIOS ? 2 : 1}, requestArray); + validateRequestCounts({'events': 1, 'location': 1, 'crash': 0, 'begin_session': 0, 'consent': 0, 'end_session': 0, 'session_duration': 0, 'apm': 2, 'user_details': Platform.isIOS ? 2 : 1}, requestArray); validateInternalEventCounts({'nps': 1}, requestArray); validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); diff --git a/example/integration_test/sbs_tests/notes.md b/example/integration_test/sbs_tests/notes.md index b13310e7..ae3bcdef 100644 --- a/example/integration_test/sbs_tests/notes.md +++ b/example/integration_test/sbs_tests/notes.md @@ -124,5 +124,6 @@ getAvaliableFeedbackWidgets= if no consent it broken iOS iOS crash limits not applied to the stack traces because health checks one of the earlier ones, in 200C if it was FS heath checks was sent because it runs before we fetch SBS. Android has scrolls, content, star-rating, clicks consents extra +iOS reports all widget events directly but not android, android does not send rating report event immediately validation things with base test From 26c14ddf2af89c5b02dc4ad7eeb68411e2f96cf9 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Fri, 11 Jul 2025 13:15:17 +0300 Subject: [PATCH 57/64] feat: sbs200d test eqs --- .../sbs_tests/SBS_200D_test.dart | 14 ++++++++++++++ .../integration_test/sbs_tests/sbs_utils.dart | 2 ++ example/integration_test/utils.dart | 13 +++++++++++++ ios/Classes/CountlyFlutterPlugin.m | 16 ++++++++++++++++ 4 files changed, 45 insertions(+) diff --git a/example/integration_test/sbs_tests/SBS_200D_test.dart b/example/integration_test/sbs_tests/SBS_200D_test.dart index be8635f5..14c33048 100644 --- a/example/integration_test/sbs_tests/SBS_200D_test.dart +++ b/example/integration_test/sbs_tests/SBS_200D_test.dart @@ -32,6 +32,20 @@ void main() { validateInternalEventCounts({'nps': 1}, requestArray); validateImmediateCounts({'hc': 1, 'sc': 1, 'feedback': 1, 'queue': 2, 'ab': 1, 'ab_opt_out': 1, 'rc': 1}, requestArray); + recordReservedEvent('[CLY]_orientation', {'mode': 'portrait'}); + recordReservedEvent('[CLY]_orientation', {'mode': 'landscape'}); + recordReservedEvent('[CLY]_star_rating', {'platform': 'Web', 'app_version': '1.0', 'widget_id': 'starRatingID', 'closed': false, 'rating': 5, 'comment': 'Loved it!'}); + recordReservedEvent('[CLY]_star_rating', {'platform': 'Android', 'app_version': '1.0', 'widget_id': 'starRatingID', 'closed': false, 'rating': 3, 'comment': 'Meh'}); + EQ = await getEventQueue(); + expect(EQ.length, 4); + + recordReservedEvent('[CLY]_star_rating', {'platform': 'iOS', 'app_version': '1.0', 'widget_id': 'starRatingID', 'closed': false, 'rating': 1, 'comment': 'NO'}); + EQ = await getEventQueue(); + expect(EQ.length, 0); // validate that event queue is cleared when hit the limit and recording internal events are not affected by the custom event tracking disablement + await Future.delayed(const Duration(seconds: 2)); + + validateInternalEventCounts({'nps': 1, 'star_rating': 3, 'orientation': 2}, requestArray); + expect(await getServerConfig(), { 'v': 1, 't': 1750748806695, diff --git a/example/integration_test/sbs_tests/sbs_utils.dart b/example/integration_test/sbs_tests/sbs_utils.dart index a03846df..e6c21a46 100644 --- a/example/integration_test/sbs_tests/sbs_utils.dart +++ b/example/integration_test/sbs_tests/sbs_utils.dart @@ -14,6 +14,8 @@ Map> reservedSegmentationKeys = { '[CLY]_nps': {'platform': false, 'app_version': false, 'widget_id': false, 'closed': false, 'rating': false, 'comment': false}, '[CLY]_survey': {'platform': false, 'app_version': false, 'widget_id': false, 'closed': false}, '[CLY]_star_rating': {'platform': false, 'app_version': false, 'widget_id': false, 'closed': false, 'rating': false, 'comment': false}, + '[CLY]_push_action': {'p': false, 'i': false, 'b': false}, + // '[CLY]_action': {} this is in android but not used, iOS does not have this }; /// Validates the immediate counts in the request array. diff --git a/example/integration_test/utils.dart b/example/integration_test/utils.dart index 580603f5..cf187712 100644 --- a/example/integration_test/utils.dart +++ b/example/integration_test/utils.dart @@ -52,6 +52,19 @@ Future> getServerConfig() async { return Map.from(sc); } +Future recordReservedEvent(String key, Map? segmentation) async { + if (Platform.isIOS) { + // iOS uses a different method for reserved events + List args = []; + + args.add(key); + args.add(segmentation); + await _channelTest.invokeMethod('recordReservedEvent', {'data': json.encode(args.where((item) => item != null).toList())}); + } else { + await Countly.instance.events.recordEvent(key, segmentation); + } +} + /// Verify the common request queue parameters void testCommonRequestParams(Map> requestObject) { expect(requestObject['app_key']?[0], APP_KEY); diff --git a/ios/Classes/CountlyFlutterPlugin.m b/ios/Classes/CountlyFlutterPlugin.m index 7db0d847..7da2b371 100644 --- a/ios/Classes/CountlyFlutterPlugin.m +++ b/ios/Classes/CountlyFlutterPlugin.m @@ -164,6 +164,22 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result result(serverConfig); }); + } else if ([@"recordReservedEvent" isEqualToString:call.method]) { + dispatch_async(dispatch_get_main_queue(), ^{ + NSString *key = [command objectAtIndex:0]; + NSDictionary *segmentation; + if ((int)command.count > 1) { + segmentation = [command objectAtIndex:1]; + } else { + segmentation = nil; + } + + [[Countly sharedInstance] recordReservedEvent:key segmentation:segmentation]; + + NSString *resultString = @"recordReservedEvent for: "; + resultString = [resultString stringByAppendingString:key]; + result(resultString); + }); } else if ([@"recordEvent" isEqualToString:call.method]) { dispatch_async(dispatch_get_main_queue(), ^{ NSString *key = [command objectAtIndex:0]; From a0b2771de6f54883efedd8f0cd78d782cb65e066 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Fri, 11 Jul 2025 14:31:14 +0300 Subject: [PATCH 58/64] feat: sbs200d finalized --- .../sbs_tests/SBS_200D_test.dart | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/example/integration_test/sbs_tests/SBS_200D_test.dart b/example/integration_test/sbs_tests/SBS_200D_test.dart index 14c33048..dbdfdae4 100644 --- a/example/integration_test/sbs_tests/SBS_200D_test.dart +++ b/example/integration_test/sbs_tests/SBS_200D_test.dart @@ -16,7 +16,7 @@ void main() { createServerWithConfig(requestArray, { 'v': 1, 't': 1750748806695, - 'c': {'st': false, 'cet': false, 'vt': false, 'eqs': 5, 'lt': false, 'crt': false, 'bom_at': 5, 'bom_d': 30, 'bom_rqp': 0.01, 'bom_ra': 1} + 'c': {'st': false, 'cet': false, 'vt': false, 'eqs': 5, 'lt': false, 'crt': false, 'bom_at': 5, 'bom_d': 30, 'bom_rqp': 0.001, 'bom_ra': 1} }); // Initialize the SDK CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); @@ -49,7 +49,26 @@ void main() { expect(await getServerConfig(), { 'v': 1, 't': 1750748806695, - 'c': {'st': false, 'cet': false, 'vt': false, 'eqs': 5, 'lt': false, 'crt': false, 'bom_at': 5, 'bom_d': 30, 'bom_rqp': 0.01, 'bom_ra': 1} + 'c': {'st': false, 'cet': false, 'vt': false, 'eqs': 5, 'lt': false, 'crt': false, 'bom_at': 5, 'bom_d': 30, 'bom_rqp': 0.001, 'bom_ra': 1} }); + + await Countly.instance.content.exitContentZone(); + requestArray.clear(); + + sbsServerDelay = 5; + storeRequest({'first': 'true', 'device_id': 'device_id_200C', 'app_key': APP_KEY, 'timestamp': DateTime.now().subtract(const Duration(minutes: 65)).millisecondsSinceEpoch.toString()}); // this will be not backed off because ra 1 + await Countly.recordNetworkTrace('Network Trace', 203, 123, 421, 542, 564); // this will be not backed off because rqp 0.001 + await Countly.recordNetworkTrace('Network Trace', 200, 500, 600, 100, 150); // backoff will trigger here + await Countly.recordNetworkTrace('Network Trace', 201, 350, 222, 333, 111); // this will be backed off for 30 seconds + await Countly.instance.attemptToSendStoredRequests(); + await Future.delayed(const Duration(seconds: 15)); + + validateRequestCounts({'apm': 2, 'first': 1}, requestArray); + await Countly.instance.attemptToSendStoredRequests(); // this will not take effect + await Future.delayed(const Duration(seconds: 5)); + validateRequestCounts({'apm': 2, 'first': 1}, requestArray); + + await Future.delayed(const Duration(seconds: 40)); + validateRequestCounts({'apm': 3, 'first': 1}, requestArray); }); } From bb52c1afad99b8d97bee9f901ad3674f955edc9c Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 16 Jul 2025 11:04:34 +0300 Subject: [PATCH 59/64] feat: validation tests SBS --- .../sbs_tests/SBS_202A_test.dart | 35 +++++++++++++++ .../sbs_tests/SBS_202B_test.dart | 27 ++++++++++++ example/integration_test/sbs_tests/notes.md | 43 ++++++++++++++++++- 3 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 example/integration_test/sbs_tests/SBS_202A_test.dart create mode 100644 example/integration_test/sbs_tests/SBS_202B_test.dart diff --git a/example/integration_test/sbs_tests/SBS_202A_test.dart b/example/integration_test/sbs_tests/SBS_202A_test.dart new file mode 100644 index 00000000..7a2f73ea --- /dev/null +++ b/example/integration_test/sbs_tests/SBS_202A_test.dart @@ -0,0 +1,35 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:countly_flutter/countly_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../utils.dart'; +import 'sbs_utils.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + testWidgets('SBS_202A_test', (WidgetTester tester) async { + List>> requestArray = >>[]; + createServerWithConfig(requestArray, { + 'v': 1, + 't': 1750748806695, + 'c': {'lvs': 'hoho', 'lsv': 'hehe', 'lbc': -5, 'ltlpt': 0, 'ltl': 0, 'rcz': 'no', 'ecz': 'no', 'czi': -16, 'bom': 'test', 'dort': false, 'tracking': 'no', 'scui': 0.1, 'networking': 'yes', 'cr': '', 'rqs': -5, 'sui': -10} + }); + + setServerConfig({ + 'v': 1, + 't': 1750748806695, + 'c': {'st': 'yes', 'cet': 'no', 'vt': 0, 'eqs': 0, 'lt': 1, 'crt': 'value', 'bom_at': -1, 'bom_d': -1, 'bom_rqp': 50, 'bom_ra': -1, 'lkl': 'test'} + }); + + // Initialize the SDK + CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); + + await Countly.initWithConfig(config); + await Future.delayed(const Duration(seconds: 2)); + + expect(await getServerConfig(), {'v': 1, 't': 1750748806695, 'c': {}}); + }); +} diff --git a/example/integration_test/sbs_tests/SBS_202B_test.dart b/example/integration_test/sbs_tests/SBS_202B_test.dart new file mode 100644 index 00000000..0491a46c --- /dev/null +++ b/example/integration_test/sbs_tests/SBS_202B_test.dart @@ -0,0 +1,27 @@ +import 'package:countly_flutter/countly_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../utils.dart'; +import 'sbs_utils.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + testWidgets('SBS_202B_test', (WidgetTester tester) async { + List>> requestArray = >>[]; + createServerWithConfig(requestArray, { + 'v': -1, + 't': -1750748806695, + 'c': {'lvs': 'hoho', 'lsv': 'hehe', 'lbc': -5, 'ltlpt': 0, 'ltl': 0, 'rcz': 'no', 'ecz': 'no', 'czi': -16, 'bom': 'test', 'dort': false, 'tracking': 'no', 'scui': 0.1, 'networking': 'yes', 'cr': '', 'rqs': -5, 'sui': -10} + }); + + // Initialize the SDK + CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); + config.setSDKBehaviorSettings('{"c":{"st":"yes","cet":"no","vt":0,"eqs":0,"lt":1,"crt":"value","bom_at":-1,"bom_d":-1,"bom_rqp":50,"bom_ra":-1,"lkl":"test"}}'); + + await Countly.initWithConfig(config); + await Future.delayed(const Duration(seconds: 2)); + + expect(await getServerConfig(), {'v': -1, 't': -1750748806695, 'c': {}}); + }); +} diff --git a/example/integration_test/sbs_tests/notes.md b/example/integration_test/sbs_tests/notes.md index ae3bcdef..d9c92d1d 100644 --- a/example/integration_test/sbs_tests/notes.md +++ b/example/integration_test/sbs_tests/notes.md @@ -117,6 +117,47 @@ tests are: - 201D_DP_P_S_FS_temp_id - 201E_DP_P_FS_temp_id +--------------------------------------------------------------------------------------------------------------------------------- + +202X tests value validation where: +Provide SBS from server: +```json +{ + 'v': 1, + 't': 1750748806695, + 'c': {'lvs': 'hoho', 'lsv': 'hehe', 'lbc': -5, 'ltlpt': 0, 'ltl': 0, 'rcz': 'no', 'ecz': 'no', 'czi': -16, 'bom': 'test', 'dort': false, 'tracking': 'no', 'scui': 0.1, 'networking': 'yes', 'cr': '', 'rqs': -5, 'sui': -10} + } +``` + +- A +Store SBS: +```json +{ + 'v': 1, + 't': 1750748806695, + 'c': {'st': 'yes', 'cet': 'no', 'vt': 0, 'eqs': 0, 'lt': 1, 'crt': 'value', 'bom_at': -1, 'bom_d': -1, 'bom_rqp': 50, 'bom_ra': -1, 'lkl': 'test'} + } +``` +Validate that: +- Stored SBS at the end does not have any config values, only version and timestamp there. + +- B +Provide SBS through configuration: +```json +{ + 'v': 1, + 't': 1750748806695, + 'c': {'st': 'yes', 'cet': 'no', 'vt': 0, 'eqs': 0, 'lt': 1, 'crt': 'value', 'bom_at': -1, 'bom_d': -1, 'bom_rqp': 50, 'bom_ra': -1, 'lkl': 'test'} + } +``` +Validate that: +- Stored SBS at the end does not have any config values, only version and timestamp there. + +tests are: +- 202A_S_FS +- 202B_P_FS + +--------------------------------------------------------------------------------------------------------------------------------- Notes iOS: In the base test iOS required more time then Android at the end Because there is a probability for iOS to duplicate requests, checking request counts were not good @@ -126,4 +167,4 @@ because health checks one of the earlier ones, in 200C if it was FS heath checks Android has scrolls, content, star-rating, clicks consents extra iOS reports all widget events directly but not android, android does not send rating report event immediately -validation things with base test +validation things with base test \ No newline at end of file From 00b2260ee9cd0cb5239169666be112bfa4af9188 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 16 Jul 2025 11:05:52 +0300 Subject: [PATCH 60/64] fix: remove unused import --- example/integration_test/sbs_tests/SBS_202A_test.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/example/integration_test/sbs_tests/SBS_202A_test.dart b/example/integration_test/sbs_tests/SBS_202A_test.dart index 7a2f73ea..39db6e14 100644 --- a/example/integration_test/sbs_tests/SBS_202A_test.dart +++ b/example/integration_test/sbs_tests/SBS_202A_test.dart @@ -1,6 +1,3 @@ -import 'dart:convert'; -import 'dart:io'; - import 'package:countly_flutter/countly_flutter.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; From bbacfb41581f8e24dc336f42b9823a17900337d0 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 16 Jul 2025 11:07:50 +0300 Subject: [PATCH 61/64] fix: rename validation tests --- .../sbs_tests/{SBS_202A_test.dart => SBS_202A_S_FS_test.dart} | 2 +- .../sbs_tests/{SBS_202B_test.dart => SBS_202B_P_FS_test.dart} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename example/integration_test/sbs_tests/{SBS_202A_test.dart => SBS_202A_S_FS_test.dart} (94%) rename example/integration_test/sbs_tests/{SBS_202B_test.dart => SBS_202B_P_FS_test.dart} (94%) diff --git a/example/integration_test/sbs_tests/SBS_202A_test.dart b/example/integration_test/sbs_tests/SBS_202A_S_FS_test.dart similarity index 94% rename from example/integration_test/sbs_tests/SBS_202A_test.dart rename to example/integration_test/sbs_tests/SBS_202A_S_FS_test.dart index 39db6e14..36e1845f 100644 --- a/example/integration_test/sbs_tests/SBS_202A_test.dart +++ b/example/integration_test/sbs_tests/SBS_202A_S_FS_test.dart @@ -7,7 +7,7 @@ import 'sbs_utils.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - testWidgets('SBS_202A_test', (WidgetTester tester) async { + testWidgets('SBS_202A_S_FS_test', (WidgetTester tester) async { List>> requestArray = >>[]; createServerWithConfig(requestArray, { 'v': 1, diff --git a/example/integration_test/sbs_tests/SBS_202B_test.dart b/example/integration_test/sbs_tests/SBS_202B_P_FS_test.dart similarity index 94% rename from example/integration_test/sbs_tests/SBS_202B_test.dart rename to example/integration_test/sbs_tests/SBS_202B_P_FS_test.dart index 0491a46c..67c2ee2c 100644 --- a/example/integration_test/sbs_tests/SBS_202B_test.dart +++ b/example/integration_test/sbs_tests/SBS_202B_P_FS_test.dart @@ -7,7 +7,7 @@ import 'sbs_utils.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - testWidgets('SBS_202B_test', (WidgetTester tester) async { + testWidgets('SBS_202B_P_FS_test', (WidgetTester tester) async { List>> requestArray = >>[]; createServerWithConfig(requestArray, { 'v': -1, From e2b15ffa2401f1f917148864bb8210b2de48a305 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 16 Jul 2025 11:16:42 +0300 Subject: [PATCH 62/64] fix: add unkown key --- example/integration_test/sbs_tests/SBS_202A_S_FS_test.dart | 4 ++-- example/integration_test/sbs_tests/SBS_202B_P_FS_test.dart | 4 ++-- example/integration_test/sbs_tests/notes.md | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/example/integration_test/sbs_tests/SBS_202A_S_FS_test.dart b/example/integration_test/sbs_tests/SBS_202A_S_FS_test.dart index 36e1845f..4861d401 100644 --- a/example/integration_test/sbs_tests/SBS_202A_S_FS_test.dart +++ b/example/integration_test/sbs_tests/SBS_202A_S_FS_test.dart @@ -12,13 +12,13 @@ void main() { createServerWithConfig(requestArray, { 'v': 1, 't': 1750748806695, - 'c': {'lvs': 'hoho', 'lsv': 'hehe', 'lbc': -5, 'ltlpt': 0, 'ltl': 0, 'rcz': 'no', 'ecz': 'no', 'czi': -16, 'bom': 'test', 'dort': false, 'tracking': 'no', 'scui': 0.1, 'networking': 'yes', 'cr': '', 'rqs': -5, 'sui': -10} + 'c': {'lvs': 'hoho', 'lsv': 'hehe', 'lbc': -5, 'ltlpt': 0, 'unkown': 'very_unkown', 'ltl': 0, 'rcz': 'no', 'ecz': 'no', 'czi': -16, 'bom': 'test', 'dort': false, 'tracking': 'no', 'scui': 0.1, 'networking': 'yes', 'cr': '', 'rqs': -5, 'sui': -10} }); setServerConfig({ 'v': 1, 't': 1750748806695, - 'c': {'st': 'yes', 'cet': 'no', 'vt': 0, 'eqs': 0, 'lt': 1, 'crt': 'value', 'bom_at': -1, 'bom_d': -1, 'bom_rqp': 50, 'bom_ra': -1, 'lkl': 'test'} + 'c': {'st': 'yes', 'cet': 'no', 'vt': 0, 'eqs': 0, 'lt': 1, 'unkown1': 'very_unkown1', 'crt': 'value', 'bom_at': -1, 'bom_d': -1, 'bom_rqp': 50, 'bom_ra': -1, 'lkl': 'test'} }); // Initialize the SDK diff --git a/example/integration_test/sbs_tests/SBS_202B_P_FS_test.dart b/example/integration_test/sbs_tests/SBS_202B_P_FS_test.dart index 67c2ee2c..3e3043ff 100644 --- a/example/integration_test/sbs_tests/SBS_202B_P_FS_test.dart +++ b/example/integration_test/sbs_tests/SBS_202B_P_FS_test.dart @@ -12,12 +12,12 @@ void main() { createServerWithConfig(requestArray, { 'v': -1, 't': -1750748806695, - 'c': {'lvs': 'hoho', 'lsv': 'hehe', 'lbc': -5, 'ltlpt': 0, 'ltl': 0, 'rcz': 'no', 'ecz': 'no', 'czi': -16, 'bom': 'test', 'dort': false, 'tracking': 'no', 'scui': 0.1, 'networking': 'yes', 'cr': '', 'rqs': -5, 'sui': -10} + 'c': {'lvs': 'hoho', 'lsv': 'hehe', 'lbc': -5, 'ltlpt': 0, 'unkown': 'very_unkown', 'ltl': 0, 'rcz': 'no', 'ecz': 'no', 'czi': -16, 'bom': 'test', 'dort': false, 'tracking': 'no', 'scui': 0.1, 'networking': 'yes', 'cr': '', 'rqs': -5, 'sui': -10} }); // Initialize the SDK CountlyConfig config = CountlyConfig('http://0.0.0.0:8080', APP_KEY).enableManualSessionHandling().setLoggingEnabled(true); - config.setSDKBehaviorSettings('{"c":{"st":"yes","cet":"no","vt":0,"eqs":0,"lt":1,"crt":"value","bom_at":-1,"bom_d":-1,"bom_rqp":50,"bom_ra":-1,"lkl":"test"}}'); + config.setSDKBehaviorSettings('{"c":{"st":"yes","cet":"no","vt":0,"eqs":0,"lt":1,"unkown1": "very_unkown1","crt":"value","bom_at":-1,"bom_d":-1,"bom_rqp":50,"bom_ra":-1,"lkl":"test"}}'); await Countly.initWithConfig(config); await Future.delayed(const Duration(seconds: 2)); diff --git a/example/integration_test/sbs_tests/notes.md b/example/integration_test/sbs_tests/notes.md index d9c92d1d..d43d0908 100644 --- a/example/integration_test/sbs_tests/notes.md +++ b/example/integration_test/sbs_tests/notes.md @@ -125,7 +125,7 @@ Provide SBS from server: { 'v': 1, 't': 1750748806695, - 'c': {'lvs': 'hoho', 'lsv': 'hehe', 'lbc': -5, 'ltlpt': 0, 'ltl': 0, 'rcz': 'no', 'ecz': 'no', 'czi': -16, 'bom': 'test', 'dort': false, 'tracking': 'no', 'scui': 0.1, 'networking': 'yes', 'cr': '', 'rqs': -5, 'sui': -10} + 'c': {'lvs': 'hoho', 'lsv': 'hehe', 'lbc': -5, 'ltlpt': 0, 'unkown': 'very_unkown', 'ltl': 0, 'rcz': 'no', 'ecz': 'no', 'czi': -16, 'bom': 'test', 'dort': false, 'tracking': 'no', 'scui': 0.1, 'networking': 'yes', 'cr': '', 'rqs': -5, 'sui': -10} } ``` @@ -135,7 +135,7 @@ Store SBS: { 'v': 1, 't': 1750748806695, - 'c': {'st': 'yes', 'cet': 'no', 'vt': 0, 'eqs': 0, 'lt': 1, 'crt': 'value', 'bom_at': -1, 'bom_d': -1, 'bom_rqp': 50, 'bom_ra': -1, 'lkl': 'test'} + 'c': {'st': 'yes', 'cet': 'no', 'vt': 0, 'eqs': 0, 'unkown1': 'very_unkown1', 'lt': 1, 'crt': 'value', 'bom_at': -1, 'bom_d': -1, 'bom_rqp': 50, 'bom_ra': -1, 'lkl': 'test'} } ``` Validate that: @@ -147,7 +147,7 @@ Provide SBS through configuration: { 'v': 1, 't': 1750748806695, - 'c': {'st': 'yes', 'cet': 'no', 'vt': 0, 'eqs': 0, 'lt': 1, 'crt': 'value', 'bom_at': -1, 'bom_d': -1, 'bom_rqp': 50, 'bom_ra': -1, 'lkl': 'test'} + 'c': {'st': 'yes', 'cet': 'no', 'vt': 0, 'eqs': 0, 'unkown1': 'very_unkown1', 'lt': 1, 'crt': 'value', 'bom_at': -1, 'bom_d': -1, 'bom_rqp': 50, 'bom_ra': -1, 'lkl': 'test'} } ``` Validate that: From eeaa60081c404aa22ec4c8b2ab0bd2a67eb5e553 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray <57103426+arifBurakDemiray@users.noreply.github.com> Date: Wed, 6 Aug 2025 11:40:26 +0300 Subject: [PATCH 63/64] Update ios/Classes/CountlyiOS/CountlyServerConfig.m Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ios/Classes/CountlyiOS/CountlyServerConfig.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Classes/CountlyiOS/CountlyServerConfig.m b/ios/Classes/CountlyiOS/CountlyServerConfig.m index ce669d99..520e3c64 100644 --- a/ios/Classes/CountlyiOS/CountlyServerConfig.m +++ b/ios/Classes/CountlyiOS/CountlyServerConfig.m @@ -144,7 +144,7 @@ - (void)mergeBehaviorSettings:(NSMutableDictionary *)baseConfig if (!newConfig[kRVersion] || !newConfig[kRTimestamp]) { - CLY_LOG_D(@"%s, version or timestamp is missing in the behavioır settings omitting", __FUNCTION__); + CLY_LOG_D(@"%s, version or timestamp is missing in the behavior settings omitting", __FUNCTION__); return; } From 321c7f546e25472c98fe61366b84f7603bcaa9f8 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray <57103426+arifBurakDemiray@users.noreply.github.com> Date: Wed, 6 Aug 2025 11:42:10 +0300 Subject: [PATCH 64/64] Update android/src/main/java/ly/count/dart/countly_flutter/CountlyFlutterPlugin.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../ly/count/dart/countly_flutter/CountlyFlutterPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/ly/count/dart/countly_flutter/CountlyFlutterPlugin.java b/android/src/main/java/ly/count/dart/countly_flutter/CountlyFlutterPlugin.java index 7266c748..b9de3d41 100644 --- a/android/src/main/java/ly/count/dart/countly_flutter/CountlyFlutterPlugin.java +++ b/android/src/main/java/ly/count/dart/countly_flutter/CountlyFlutterPlugin.java @@ -1441,7 +1441,7 @@ else if ("getRequestQueue".equals(call.method)) { } else if ("halt".equals(call.method)) { Countly.sharedInstance().halt(); result.success("halt: success"); - } + } //------------------End------------------------------------ else if ("enterContentZone".equals(call.method)) {