diff --git a/Countly.m b/Countly.m index 7f40a66f..ba0a5398 100644 --- a/Countly.m +++ b/Countly.m @@ -186,6 +186,7 @@ - (void)startWithConfig:(CountlyConfig *)config CountlyFeedbacks.sharedInstance.disableAskingForEachAppVersion = config.starRatingDisableAskingForEachAppVersion; CountlyFeedbacks.sharedInstance.ratingCompletionForAutoAsk = config.starRatingCompletion; [CountlyFeedbacks.sharedInstance checkForStarRatingAutoAsk]; +#endif if(config.disableLocation) { @@ -195,7 +196,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]; @@ -203,12 +203,7 @@ - (void)startWithConfig:(CountlyConfig *)config [CountlyCommon.sharedInstance recordOrientation]; //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]; - [CountlyConnectionManager.sharedInstance sendAttribution]; - } - + #if (TARGET_OS_IOS || TARGET_OS_OSX) #ifndef COUNTLY_EXCLUDE_PUSHNOTIFICATIONS if ([config.features containsObject:CLYPushNotifications]) @@ -291,6 +286,21 @@ - (void)startWithConfig:(CountlyConfig *)config else if (config.requiresConsent) [CountlyConsentManager.sharedInstance sendConsents]; + 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]; 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/CountlyConsentManager.m b/CountlyConsentManager.m index ebf015c1..4dee203d 100644 --- a/CountlyConsentManager.m +++ b/CountlyConsentManager.m @@ -351,6 +351,8 @@ - (void)setConsentForLocation:(BOOL)consentForLocation else { CLY_LOG_D(@"Consent for Location is cancelled."); + + [CountlyConnectionManager.sharedInstance sendLocationInfo]; } } 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") + } + +} +