From 501fadda6513a921950d80fccd1ccf005fbe4748 Mon Sep 17 00:00:00 2001 From: ijunaid Date: Mon, 22 Jul 2024 15:25:38 +0500 Subject: [PATCH 1/4] Send an empty location if location is disabled or location consent is not given, without checking for location consent. --- Countly.m | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Countly.m b/Countly.m index 1c5c40a8..0b3620f1 100644 --- a/Countly.m +++ b/Countly.m @@ -172,6 +172,7 @@ - (void)startWithConfig:(CountlyConfig *)config CountlyFeedbacks.sharedInstance.disableAskingForEachAppVersion = config.starRatingDisableAskingForEachAppVersion; CountlyFeedbacks.sharedInstance.ratingCompletionForAutoAsk = config.starRatingCompletion; [CountlyFeedbacks.sharedInstance checkForStarRatingAutoAsk]; +#endif if(config.disableLocation) { @@ -181,7 +182,6 @@ - (void)startWithConfig:(CountlyConfig *)config { [CountlyLocationManager.sharedInstance updateLocation:config.location city:config.city ISOCountryCode:config.ISOCountryCode IP:config.IP]; } -#endif if (!CountlyCommon.sharedInstance.manualSessionHandling) [CountlyConnectionManager.sharedInstance beginSession]; @@ -189,7 +189,15 @@ - (void)startWithConfig:(CountlyConfig *)config //NOTE: If there is no consent for sessions, location info and attribution should be sent separately, as they cannot be sent with begin_session request. if (!CountlyConsentManager.sharedInstance.consentForSessions) { - [CountlyLocationManager.sharedInstance sendLocationInfo]; + //Send an empty location if location is disabled or location consent is not given, without checking for location consent. + if (!CountlyConsentManager.sharedInstance.consentForLocation || CountlyLocationManager.sharedInstance.isLocationInfoDisabled) + { + [CountlyConnectionManager.sharedInstance sendLocationInfo]; + } + else + { + [CountlyLocationManager.sharedInstance sendLocationInfo]; + } [CountlyConnectionManager.sharedInstance sendAttribution]; } From 8151e3867436f9f7b1ac6edc36dd4326799256ab Mon Sep 17 00:00:00 2001 From: ijunaid Date: Mon, 22 Jul 2024 18:26:56 +0500 Subject: [PATCH 2/4] Sending empty location when consent is removed for location --- CountlyConsentManager.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CountlyConsentManager.m b/CountlyConsentManager.m index 9e5bd459..3cd62f79 100644 --- a/CountlyConsentManager.m +++ b/CountlyConsentManager.m @@ -350,6 +350,8 @@ - (void)setConsentForLocation:(BOOL)consentForLocation else { CLY_LOG_D(@"Consent for Location is cancelled."); + + [CountlyConnectionManager.sharedInstance sendLocationInfo]; } } From 11a2e5a0f3628011f94eb0ee2b3852c2a899fd0b Mon Sep 17 00:00:00 2001 From: ijunaid Date: Mon, 29 Jul 2024 11:17:29 +0500 Subject: [PATCH 3/4] Fixed empty location due to setting consents later --- Countly.m | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/Countly.m b/Countly.m index 0b3620f1..99041e78 100644 --- a/Countly.m +++ b/Countly.m @@ -187,20 +187,7 @@ - (void)startWithConfig:(CountlyConfig *)config [CountlyConnectionManager.sharedInstance beginSession]; //NOTE: If there is no consent for sessions, location info and attribution should be sent separately, as they cannot be sent with begin_session request. - if (!CountlyConsentManager.sharedInstance.consentForSessions) - { - //Send an empty location if location is disabled or location consent is not given, without checking for location consent. - if (!CountlyConsentManager.sharedInstance.consentForLocation || CountlyLocationManager.sharedInstance.isLocationInfoDisabled) - { - [CountlyConnectionManager.sharedInstance sendLocationInfo]; - } - else - { - [CountlyLocationManager.sharedInstance sendLocationInfo]; - } - [CountlyConnectionManager.sharedInstance sendAttribution]; - } - + #if (TARGET_OS_IOS || TARGET_OS_OSX) #ifndef COUNTLY_EXCLUDE_PUSHNOTIFICATIONS if ([config.features containsObject:CLYPushNotifications]) @@ -281,6 +268,21 @@ - (void)startWithConfig:(CountlyConfig *)config else if (config.consents) [self giveConsentForFeatures:config.consents]; + if (!CountlyConsentManager.sharedInstance.consentForSessions) + { + //Send an empty location if location is disabled or location consent is not given, without checking for location consent. + if (!CountlyConsentManager.sharedInstance.consentForLocation || CountlyLocationManager.sharedInstance.isLocationInfoDisabled) + { + [CountlyConnectionManager.sharedInstance sendLocationInfo]; + } + else + { + [CountlyLocationManager.sharedInstance sendLocationInfo]; + } + [CountlyConnectionManager.sharedInstance sendAttribution]; + } + + if (config.campaignType && config.campaignData) [self recordDirectAttributionWithCampaignType:config.campaignType andCampaignData:config.campaignData]; From 21ac26fb26fc3d8c3d9bc468105c84119269532e Mon Sep 17 00:00:00 2001 From: ijunaid Date: Mon, 29 Jul 2024 11:22:57 +0500 Subject: [PATCH 4/4] Tests added for location --- Countly.xcodeproj/project.pbxproj | 4 + CountlyTests/CountlyLocationTests.swift | 259 ++++++++++++++++++++++++ 2 files changed, 263 insertions(+) create mode 100644 CountlyTests/CountlyLocationTests.swift diff --git a/Countly.xcodeproj/project.pbxproj b/Countly.xcodeproj/project.pbxproj index f1707279..1b9594ba 100644 --- a/Countly.xcodeproj/project.pbxproj +++ b/Countly.xcodeproj/project.pbxproj @@ -36,6 +36,7 @@ 39924ED02BEBD0D400139F91 /* CountlyCrashesConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 39924ECF2BEBD0D400139F91 /* CountlyCrashesConfig.m */; }; 39924ED62BEBD20F00139F91 /* CountlyCrashData.m in Sources */ = {isa = PBXBuildFile; fileRef = 39924ED52BEBD20F00139F91 /* CountlyCrashData.m */; }; 39924ED82BEBD22100139F91 /* CountlyCrashData.h in Headers */ = {isa = PBXBuildFile; fileRef = 39924ED72BEBD22100139F91 /* CountlyCrashData.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 399B46502C52813700AD384E /* CountlyLocationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399B464F2C52813700AD384E /* CountlyLocationTests.swift */; }; 3B20A9872245225A00E3D7AE /* Countly.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B20A9852245225A00E3D7AE /* Countly.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3B20A9B22245228700E3D7AE /* CountlyConnectionManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B20A98D2245228300E3D7AE /* CountlyConnectionManager.h */; }; 3B20A9B32245228700E3D7AE /* CountlyNotificationService.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B20A98E2245228300E3D7AE /* CountlyNotificationService.m */; }; @@ -117,6 +118,7 @@ 39924ECF2BEBD0D400139F91 /* CountlyCrashesConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CountlyCrashesConfig.m; sourceTree = ""; }; 39924ED52BEBD20F00139F91 /* CountlyCrashData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CountlyCrashData.m; sourceTree = ""; }; 39924ED72BEBD22100139F91 /* CountlyCrashData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CountlyCrashData.h; sourceTree = ""; }; + 399B464F2C52813700AD384E /* CountlyLocationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountlyLocationTests.swift; sourceTree = ""; }; 3B20A9822245225A00E3D7AE /* Countly.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Countly.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3B20A9852245225A00E3D7AE /* Countly.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Countly.h; sourceTree = ""; }; 3B20A9862245225A00E3D7AE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -190,6 +192,7 @@ 3972EDDA2C08A38D00EB9D3E /* CountlyEventStruct.swift */, 3966DBCE2C11EE270002ED97 /* CountlyDeviceIDTests.swift */, 3964A3E62C2AF8E90091E677 /* CountlySegmentationTests.swift */, + 399B464F2C52813700AD384E /* CountlyLocationTests.swift */, ); path = CountlyTests; sourceTree = ""; @@ -418,6 +421,7 @@ buildActionMask = 2147483647; files = ( 1A5C4C972B35B0850032EE1F /* CountlyTests.swift in Sources */, + 399B46502C52813700AD384E /* CountlyLocationTests.swift in Sources */, 1A50D7052B3C5AA3009C6938 /* CountlyBaseTestCase.swift in Sources */, 3979E47D2C0760E900FA1CA4 /* CountlyUserProfileTests.swift in Sources */, 968426812BF2302C007B303E /* CountlyConnectionManagerTests.swift in Sources */, diff --git a/CountlyTests/CountlyLocationTests.swift b/CountlyTests/CountlyLocationTests.swift new file mode 100644 index 00000000..55eb1178 --- /dev/null +++ b/CountlyTests/CountlyLocationTests.swift @@ -0,0 +1,259 @@ +// +// CountlyLocationTests.swift +// CountlyTests +// +// Created by Muhammad Junaid Akram on 25/07/2024. +// Copyright © 2024 Countly. All rights reserved. +// + +import XCTest +@testable import Countly + +// M:Manual Sessions enabled +// A:Automatic sessions enabled +// H:Hybrid Sessions enabled +// CR:Consent Required +// CNR:Consent not Required +// CG:Consent given (All) +// CNG:Consent not given (All) +// CGS:Consent given for session +// CGL:Consent givent for location +// LD:Location Disable +// L: Location Provided + +class CountlyLocationTests: CountlyBaseTestCase { + + func testDummy() { + } + + func testLocationInit_CNR_A() throws { + let config = createBaseConfig() + Countly.sharedInstance().start(with: config); + + // get request queue + guard let queuedRequests = CountlyPersistency.sharedInstance().value(forKey: "queuedRequests") as? [String] else { + fatalError("Failed to get queuedRequests from CountlyPersistency") + } + XCTAssertTrue(queuedRequests[0].contains("begin_session=1"), "Begin session failed.") + XCTAssertFalse(queuedRequests[0].contains("location="), "Location should not be send in this scenario") + } + + func testLocationInit_CR_CNG_A() throws { + let config = createBaseConfig() + config.requiresConsent = true + Countly.sharedInstance().start(with: config); + + // get request queue + guard let queuedRequests = CountlyPersistency.sharedInstance().value(forKey: "queuedRequests") as? [String] else { + fatalError("Failed to get queuedRequests from CountlyPersistency") + } + + XCTAssertFalse(queuedRequests[0].contains("begin_session=1"), "Begin session should not start session consent is not given.") + XCTAssertTrue(queuedRequests[1].contains("location="), "Individual location request should send in this scenario") + } + + func testLocationInit_CR_CG_A() throws { + let config = createBaseConfig() + config.requiresConsent = true + config.enableAllConsents = true + Countly.sharedInstance().start(with: config); + + // get request queue + guard let queuedRequests = CountlyPersistency.sharedInstance().value(forKey: "queuedRequests") as? [String] else { + fatalError("Failed to get queuedRequests from CountlyPersistency") + } + + XCTAssertTrue(queuedRequests[0].contains("begin_session=1"), "Begin session failed.") + XCTAssertFalse(queuedRequests[0].contains("location="), "Location should not be send in this scenario") + } + + func testLocationInit_CR_CGS_A() throws { + let config = createBaseConfig() + config.requiresConsent = true + config.consents = [CLYConsent.sessions]; + Countly.sharedInstance().start(with: config); + + // get request queue + guard let queuedRequests = CountlyPersistency.sharedInstance().value(forKey: "queuedRequests") as? [String] else { + fatalError("Failed to get queuedRequests from CountlyPersistency") + } + + XCTAssertTrue(queuedRequests[0].contains("begin_session=1"), "Begin session failed.") + XCTAssertTrue(queuedRequests[0].contains("location="), "Location should send in this scenario") + } + + func testLocationInit_CR_CGL_A() throws { + let config = createBaseConfig() + config.requiresConsent = true + config.consents = [CLYConsent.location]; + Countly.sharedInstance().start(with: config); + + // get request queue + guard let queuedRequests = CountlyPersistency.sharedInstance().value(forKey: "queuedRequests") as? [String] else { + fatalError("Failed to get queuedRequests from CountlyPersistency") + } + + XCTAssertTrue(queuedRequests[0].contains("consent="), "Only consent request should send in this scenario") + } + + func testLocationInit_CR_CGLS_A() throws { + let config = createBaseConfig() + config.requiresConsent = true + config.consents = [CLYConsent.location, CLYConsent.sessions]; + Countly.sharedInstance().start(with: config); + + // get request queue + guard let queuedRequests = CountlyPersistency.sharedInstance().value(forKey: "queuedRequests") as? [String] else { + fatalError("Failed to get queuedRequests from CountlyPersistency") + } + + XCTAssertTrue(queuedRequests[0].contains("begin_session=1"), "Begin session failed.") + XCTAssertFalse(queuedRequests[0].contains("location="), "Location should not be send in this scenario") + } + + func testLocationInit_CNR_A_L() throws { + let config = createBaseConfig() + config.location = CLLocationCoordinate2D(latitude:35.6895, longitude: 139.6917) + config.city = "Tokyo" + config.isoCountryCode = "JP" + config.ip = "255.255.255.255" + Countly.sharedInstance().start(with: config); + + // get request queue + guard let queuedRequests = CountlyPersistency.sharedInstance().value(forKey: "queuedRequests") as? [String] else { + fatalError("Failed to get queuedRequests from CountlyPersistency") + } + XCTAssertTrue(queuedRequests[0].contains("begin_session=1"), "Begin session failed.") + + let parsedRequest = parseQueryString(queuedRequests[0]) + + XCTAssertTrue((parsedRequest["location"] as! String) == "35.689500,139.691700", "Begin session should contains provided location") + XCTAssertTrue((parsedRequest["city"] as! String) == "Tokyo", "Begin session should contains provided city") + XCTAssertTrue((parsedRequest["country_code"] as! String) == "JP", "Begin session should contains provided country code") + XCTAssertTrue((parsedRequest["ip_address"] as! String) == "255.255.255.255", "Begin session should contains provided IP address") + } + + func testLocationInit_CNR_M() throws { + let config = createBaseConfig() + config.manualSessionHandling = true + Countly.sharedInstance().start(with: config); + + Countly.sharedInstance().beginSession() + + // get request queue + guard let queuedRequests = CountlyPersistency.sharedInstance().value(forKey: "queuedRequests") as? [String] else { + fatalError("Failed to get queuedRequests from CountlyPersistency") + } + XCTAssertTrue(queuedRequests[0].contains("begin_session=1"), "Begin session failed.") + XCTAssertFalse(queuedRequests[0].contains("location="), "Location should not be send in this scenario") + } + + func testLocationInit_CR_CNG_M() throws { + let config = createBaseConfig() + config.manualSessionHandling = true + config.requiresConsent = true + Countly.sharedInstance().start(with: config); + Countly.sharedInstance().beginSession() + + // get request queue + guard let queuedRequests = CountlyPersistency.sharedInstance().value(forKey: "queuedRequests") as? [String] else { + fatalError("Failed to get queuedRequests from CountlyPersistency") + } + + XCTAssertFalse(queuedRequests[0].contains("begin_session=1"), "Begin session should not start session consent is not given.") + XCTAssertTrue(queuedRequests[1].contains("location="), "Individual location request should send in this scenario") + } + + func testLocationInit_CR_CG_M() throws { + let config = createBaseConfig() + config.manualSessionHandling = true + config.requiresConsent = true + config.enableAllConsents = true + Countly.sharedInstance().start(with: config); + Countly.sharedInstance().beginSession() + + // get request queue + guard let queuedRequests = CountlyPersistency.sharedInstance().value(forKey: "queuedRequests") as? [String] else { + fatalError("Failed to get queuedRequests from CountlyPersistency") + } + + XCTAssertTrue(queuedRequests[1].contains("begin_session=1"), "Begin session failed.") + XCTAssertFalse(queuedRequests[1].contains("location="), "Location should not be send in this scenario") + } + + func testLocationInit_CR_CGS_M() throws { + let config = createBaseConfig() + config.manualSessionHandling = true + config.requiresConsent = true + config.consents = [CLYConsent.sessions]; + Countly.sharedInstance().start(with: config); + Countly.sharedInstance().beginSession() + + // get request queue + guard let queuedRequests = CountlyPersistency.sharedInstance().value(forKey: "queuedRequests") as? [String] else { + fatalError("Failed to get queuedRequests from CountlyPersistency") + } + + XCTAssertTrue(queuedRequests[1].contains("begin_session=1"), "Begin session failed.") + XCTAssertTrue(queuedRequests[1].contains("location="), "Location should send in this scenario") + } + + func testLocationInit_CR_CGL_M() throws { + let config = createBaseConfig() + config.manualSessionHandling = true + config.requiresConsent = true + config.consents = [CLYConsent.location]; + Countly.sharedInstance().start(with: config); + Countly.sharedInstance().beginSession() + + // get request queue + guard let queuedRequests = CountlyPersistency.sharedInstance().value(forKey: "queuedRequests") as? [String] else { + fatalError("Failed to get queuedRequests from CountlyPersistency") + } + + XCTAssertTrue(queuedRequests[0].contains("consent="), "Only consent request should send in this scenario") + } + + func testLocationInit_CR_CGLS_M() throws { + let config = createBaseConfig() + config.manualSessionHandling = true + config.requiresConsent = true + config.consents = [CLYConsent.location, CLYConsent.sessions]; + Countly.sharedInstance().start(with: config); + Countly.sharedInstance().beginSession() + + // get request queue + guard let queuedRequests = CountlyPersistency.sharedInstance().value(forKey: "queuedRequests") as? [String] else { + fatalError("Failed to get queuedRequests from CountlyPersistency") + } + + XCTAssertTrue(queuedRequests[1].contains("begin_session=1"), "Begin session failed.") + XCTAssertFalse(queuedRequests[1].contains("location="), "Location should not be send in this scenario") + } + + func testLocationInit_CNR_M_L() throws { + let config = createBaseConfig() + config.manualSessionHandling = true + config.location = CLLocationCoordinate2D(latitude:35.6895, longitude: 139.6917) + config.city = "Tokyo" + config.isoCountryCode = "JP" + config.ip = "255.255.255.255" + Countly.sharedInstance().start(with: config); + Countly.sharedInstance().beginSession() + + // get request queue + guard let queuedRequests = CountlyPersistency.sharedInstance().value(forKey: "queuedRequests") as? [String] else { + fatalError("Failed to get queuedRequests from CountlyPersistency") + } + XCTAssertTrue(queuedRequests[0].contains("begin_session=1"), "Begin session failed.") + + let parsedRequest = parseQueryString(queuedRequests[0]) + + XCTAssertTrue((parsedRequest["location"] as! String) == "35.689500,139.691700", "Begin session should contains provided location") + XCTAssertTrue((parsedRequest["city"] as! String) == "Tokyo", "Begin session should contains provided city") + XCTAssertTrue((parsedRequest["country_code"] as! String) == "JP", "Begin session should contains provided country code") + XCTAssertTrue((parsedRequest["ip_address"] as! String) == "255.255.255.255", "Begin session should contains provided IP address") + } + +} +