From 9ec80b694885d53b86930aa0d3cfb4cf527665c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81yp?= Date: Tue, 3 Oct 2023 18:44:40 +0200 Subject: [PATCH] Implement config changes to UA (#2037) --- Core/UserAgentManager.swift | 192 +++++++++++- .../xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGoTests/UserAgentTests.swift | 292 +++++++++++++++++- 3 files changed, 475 insertions(+), 11 deletions(-) diff --git a/Core/UserAgentManager.swift b/Core/UserAgentManager.swift index 811293ca45..577922f317 100644 --- a/Core/UserAgentManager.swift +++ b/Core/UserAgentManager.swift @@ -85,8 +85,16 @@ public class DefaultUserAgentManager: UserAgentManager { } struct UserAgent { + + private enum DefaultPolicy: String { + + case ddg + case ddgFixed + case closest + + } - private struct Constants { + private enum Constants { // swiftlint:disable line_length static let fallbackWekKitVersion = "605.1.15" static let fallbackSafariComponent = "Safari/\(fallbackWekKitVersion)" @@ -96,6 +104,16 @@ struct UserAgent { static let uaOmitSitesConfigKey = "omitApplicationSites" static let uaOmitDomainConfigKey = "domain" + + static let defaultPolicyConfigKey = "defaultPolicy" + static let ddgDefaultSitesConfigKey = "ddgDefaultSites" + static let ddgFixedSitesConfigKey = "ddgFixedSites" + + static let closestUserAgentConfigKey = "closestUserAgent" + static let ddgFixedUserAgentConfigKey = "ddgFixedUserAgent" + + static let uaVersionsKey = "versions" + static let uaStateKey = "state" // swiftlint:enable line_length } @@ -110,12 +128,15 @@ struct UserAgent { private let versionComponent: String private let safariComponent: String private let applicationComponent = "DuckDuckGo/\(AppVersion.shared.majorVersionNumber)" + private let statistics: StatisticsStore + private let isTesting: Bool = ProcessInfo().arguments.contains("testing") - init(defaultAgent: String = Constants.fallbackDefaultAgent) { + init(defaultAgent: String = Constants.fallbackDefaultAgent, statistics: StatisticsStore = StatisticsUserDefaults()) { versionComponent = UserAgent.createVersionComponent(fromAgent: defaultAgent) baseAgent = UserAgent.createBaseAgent(fromAgent: defaultAgent, versionComponent: versionComponent) baseDesktopAgent = UserAgent.createBaseDesktopAgent(fromAgent: defaultAgent, versionComponent: versionComponent) safariComponent = UserAgent.createSafariComponent(fromAgent: baseAgent) + self.statistics = statistics } private func omitApplicationSites(forConfig config: PrivacyConfiguration) -> [String] { @@ -124,24 +145,160 @@ struct UserAgent { return omitApplicationObjs.map { $0[Constants.uaOmitDomainConfigKey] ?? "" } } - - public func agent(forUrl url: URL?, isDesktop: Bool, + + private func defaultPolicy(forConfig config: PrivacyConfiguration) -> DefaultPolicy { + let uaSettings = config.settings(for: .customUserAgent) + guard let policy = uaSettings[Constants.defaultPolicyConfigKey] as? String else { return .ddg } + + return DefaultPolicy(rawValue: policy) ?? .ddg + } + + private func ddgDefaultSites(forConfig config: PrivacyConfiguration) -> [String] { + let uaSettings = config.settings(for: .customUserAgent) + let defaultSitesObjs = uaSettings[Constants.ddgDefaultSitesConfigKey] as? [[String: String]] ?? [] + + return defaultSitesObjs.map { $0[Constants.uaOmitDomainConfigKey] ?? "" } + } + + private func ddgFixedSites(forConfig config: PrivacyConfiguration) -> [String] { + let uaSettings = config.settings(for: .customUserAgent) + let fixedSitesObjs = uaSettings[Constants.ddgFixedSitesConfigKey] as? [[String: String]] ?? [] + + return fixedSitesObjs.map { $0[Constants.uaOmitDomainConfigKey] ?? "" } + } + + private func closestUserAgentVersions(forConfig config: PrivacyConfiguration) -> [String] { + let uaSettings = config.settings(for: .customUserAgent) + let closestUserAgent = uaSettings[Constants.closestUserAgentConfigKey] as? [String: Any] ?? [:] + let versions = closestUserAgent[Constants.uaVersionsKey] as? [String] ?? [] + return versions + } + + private func ddgFixedUserAgentVersions(forConfig config: PrivacyConfiguration) -> [String] { + let uaSettings = config.settings(for: .customUserAgent) + let fixedUserAgent = uaSettings[Constants.ddgFixedUserAgentConfigKey] as? [String: Any] ?? [:] + let versions = fixedUserAgent[Constants.uaVersionsKey] as? [String] ?? [] + return versions + } + + public func agent(forUrl url: URL?, + isDesktop: Bool, privacyConfig: PrivacyConfiguration = ContentBlocking.shared.privacyConfigurationManager.privacyConfig) -> String { + + guard privacyConfig.isEnabled(featureKey: .customUserAgent) else { return oldLogic(forUrl: url, + isDesktop: isDesktop, + privacyConfig: privacyConfig) } + + if ddgDefaultSites(forConfig: privacyConfig).contains(where: { domain in + url?.isPart(ofDomain: domain) ?? false + }) { return oldLogic(forUrl: url, isDesktop: isDesktop, privacyConfig: privacyConfig) } + + if ddgFixedSites(forConfig: privacyConfig).contains(where: { domain in + url?.isPart(ofDomain: domain) ?? false + }) { return ddgFixedLogic(forUrl: url, isDesktop: isDesktop, privacyConfig: privacyConfig) } + + if closestUserAgentVersions(forConfig: privacyConfig).contains(statistics.atbWeek ?? "") { + if canUseClosestLogic { + return closestLogic(forUrl: url, isDesktop: isDesktop, privacyConfig: privacyConfig) + } else { + return oldLogic(forUrl: url, isDesktop: isDesktop, privacyConfig: privacyConfig) + } + } + + if ddgFixedUserAgentVersions(forConfig: privacyConfig).contains(statistics.atbWeek ?? "") { + return ddgFixedLogic(forUrl: url, isDesktop: isDesktop, privacyConfig: privacyConfig) + } + + switch defaultPolicy(forConfig: privacyConfig) { + case .ddg: return oldLogic(forUrl: url, isDesktop: isDesktop, privacyConfig: privacyConfig) + case .ddgFixed: return ddgFixedLogic(forUrl: url, isDesktop: isDesktop, privacyConfig: privacyConfig) + case .closest: + if canUseClosestLogic { + return closestLogic(forUrl: url, isDesktop: isDesktop, privacyConfig: privacyConfig) + } else { + return oldLogic(forUrl: url, isDesktop: isDesktop, privacyConfig: privacyConfig) + } + } + } + + private func oldLogic(forUrl url: URL?, + isDesktop: Bool, + privacyConfig: PrivacyConfiguration) -> String { let omittedSites = omitApplicationSites(forConfig: privacyConfig) let customUAEnabled = privacyConfig.isEnabled(featureKey: .customUserAgent) let omitApplicationComponent = !customUAEnabled || omittedSites.contains { domain in url?.isPart(ofDomain: domain) ?? false } - + let resolvedApplicationComponent = !omitApplicationComponent ? applicationComponent : nil + if isDesktop { return concatWithSpaces(baseDesktopAgent, resolvedApplicationComponent, safariComponent) } else { return concatWithSpaces(baseAgent, resolvedApplicationComponent, safariComponent) } } - + + private func ddgFixedLogic(forUrl url: URL?, + isDesktop: Bool, + privacyConfig: PrivacyConfiguration) -> String { + let omittedSites = omitApplicationSites(forConfig: privacyConfig) + let omitApplicationComponent = omittedSites.contains { domain in + url?.isPart(ofDomain: domain) ?? false + } + let resolvedApplicationComponent = !omitApplicationComponent ? applicationComponent : nil + + if canUseClosestLogic { + var defaultSafari = closestLogic(forUrl: url, isDesktop: isDesktop, privacyConfig: privacyConfig) + // If the UA should have DuckDuckGo append it prior to Safari + if let resolvedApplicationComponent { + if let index = defaultSafari.range(of: "Safari")?.lowerBound { + defaultSafari.insert(contentsOf: resolvedApplicationComponent + " ", at: index) + } + } + return defaultSafari + } else { + return oldLogic(forUrl: url, isDesktop: isDesktop, privacyConfig: privacyConfig) + } + } + + private func closestLogic(forUrl url: URL?, + isDesktop: Bool, + privacyConfig: PrivacyConfiguration) -> String { + if isDesktop { + return "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Safari/605.1.15" + } + return "Mozilla/5.0 (" + deviceProfile + ") AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Mobile/15E148 Safari/604.1" + } + + private var canUseClosestLogic: Bool { + guard let webKitVersion else { return false } + return webKitVersion.versionCompare("605.1.15") != .orderedAscending + } + + private var webKitVersion: String? { + let components = baseAgent.components(separatedBy: "AppleWebKit/") + + if components.count > 1 { + let versionComponents = components[1].components(separatedBy: " ") + return versionComponents.first + } + + return nil + } + + var deviceProfile: String { + let regex = try? NSRegularExpression(pattern: "\\((.*?)\\)") + if let match = regex?.firstMatch(in: baseAgent, range: NSRange(baseAgent.startIndex..., in: baseAgent)) { + let range = Range(match.range(at: 1), in: baseAgent) + if let range = range { + return String(baseAgent[range]) + } + } + return "iPhone; CPU iPhone OS 16_6 like Mac OS X" + } + private func concatWithSpaces(_ elements: String?...) -> String { return elements .compactMap { $0 } @@ -205,3 +362,26 @@ struct UserAgent { } } + +private extension StatisticsStore { + + var atbWeek: String? { + guard let atb else { return nil } + let trimmed = String(atb.dropFirst()) + + if let hyphenIndex = trimmed.firstIndex(of: "-") { + return String(trimmed.prefix(upTo: hyphenIndex)) + } else { + return trimmed + } + } + +} + +private extension String { + + func versionCompare(_ otherVersion: String) -> ComparisonResult { + compare(otherVersion, options: .numeric) + } + +} diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f903a9e2ac..10ef7c2de8 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -156,7 +156,7 @@ }, { "package": "TrackerRadarKit", - "repositoryURL": "https://github.com/duckduckgo/TrackerRadarKit.git", + "repositoryURL": "https://github.com/duckduckgo/TrackerRadarKit", "state": { "branch": null, "revision": "4684440d03304e7638a2c8086895367e90987463", diff --git a/DuckDuckGoTests/UserAgentTests.swift b/DuckDuckGoTests/UserAgentTests.swift index 86da0621c2..f740a323ee 100644 --- a/DuckDuckGoTests/UserAgentTests.swift +++ b/DuckDuckGoTests/UserAgentTests.swift @@ -22,11 +22,16 @@ import XCTest import BrowserServicesKit @testable import Core -class UserAgentTests: XCTestCase { +// swiftlint:disable file_length type_body_length +final class UserAgentTests: XCTestCase { private struct DefaultAgent { static let mobile = "Mozilla/5.0 (iPhone; CPU iPhone OS 12_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148" static let tablet = "Mozilla/5.0 (iPad; CPU OS 12_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148" + static let oldWebkitVersionMobile = "Mozilla/5.0 (iPhone; CPU iPhone OS 12_4 like Mac OS X) AppleWebKit/605.1.14 (KHTML, like Gecko) Mobile/15E148" + static let newWebkitVersionMobile = "Mozilla/5.0 (iPhone; CPU iPhone OS 12_4 like Mac OS X) AppleWebKit/605.1.16 (KHTML, like Gecko) Mobile/15E148" + static let sameWebkitVersionMobile = "Mozilla/5.0 (iPhone; CPU iPhone OS 12_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148" + } private struct ExpectedAgent { @@ -36,13 +41,25 @@ class UserAgentTests: XCTestCase { static let mobile = "Mozilla/5.0 (iPhone; CPU iPhone OS 12_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.4 Mobile/15E148 DuckDuckGo/7 Safari/605.1.15" static let tablet = "Mozilla/5.0 (iPad; CPU OS 12_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.4 Mobile/15E148 DuckDuckGo/7 Safari/605.1.15" static let desktop = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.4 DuckDuckGo/7 Safari/605.1.15" - + static let oldWebkitVersionMobile = "Mozilla/5.0 (iPhone; CPU iPhone OS 12_4 like Mac OS X) AppleWebKit/605.1.14 (KHTML, like Gecko) Version/12.4 Mobile/15E148 DuckDuckGo/7 Safari/605.1.14" + static let newWebkitVersionMobile = "Mozilla/5.0 (iPhone; CPU iPhone OS 12_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Mobile/15E148 DuckDuckGo/7 Safari/604.1" + static let sameWebkitVersionMobile = "Mozilla/5.0 (iPhone; CPU iPhone OS 12_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Mobile/15E148 DuckDuckGo/7 Safari/604.1" + static let mobileNoApplication = "Mozilla/5.0 (iPhone; CPU iPhone OS 12_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.4 Mobile/15E148 Safari/605.1.15" // Based on fallback constants in UserAgent static let mobileFallback = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.5 Mobile/15E148 DuckDuckGo/7 Safari/605.1.15" static let desktopFallback = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.5 DuckDuckGo/7 Safari/605.1.15" - + + static let mobileFixed = "Mozilla/5.0 (iPhone; CPU iPhone OS 12_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Mobile/15E148 DuckDuckGo/7 Safari/604.1" + static let tabletFixed = "Mozilla/5.0 (iPad; CPU OS 12_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Mobile/15E148 DuckDuckGo/7 Safari/604.1" + static let desktopFixed = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 DuckDuckGo/7 Safari/605.1.15" + + static let mobileClosest = "Mozilla/5.0 (iPhone; CPU iPhone OS 12_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Mobile/15E148 Safari/604.1" + static let tabletClosest = "Mozilla/5.0 (iPad; CPU OS 12_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Mobile/15E148 Safari/604.1" + static let desktopClosest = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Safari/605.1.15" + + // swiftlint:enable line_length } @@ -50,6 +67,8 @@ class UserAgentTests: XCTestCase { static let url = URL(string: "http://example.com/index.html") static let noAppUrl = URL(string: "http://cvs.com/index.html") static let noAppSubdomainUrl = URL(string: "http://subdomain.cvs.com/index.html") + static let ddgFixedUrl = URL(string: "http://test2.com/index.html") + static let ddgDefaultUrl = URL(string: "http://test3.com/index.html") } let testConfig = """ @@ -73,7 +92,7 @@ class UserAgentTests: XCTestCase { """.data(using: .utf8)! private var privacyConfig: PrivacyConfiguration! - + override func setUp() { super.setUp() @@ -164,4 +183,269 @@ class UserAgentTests: XCTestCase { XCTAssertEqual(ExpectedAgent.mobileNoApplication, testee.agent(forUrl: Constants.url, isDesktop: false, privacyConfig: manager.privacyConfig)) } + + /// Experimental config + + func makePrivacyConfig(from rawConfig: Data) -> PrivacyConfiguration { + let mockEmbeddedData = MockEmbeddedDataProvider(data: rawConfig, etag: "test") + let mockProtectionStore = MockDomainsProtectionStore() + + let manager = PrivacyConfigurationManager(fetchedETag: nil, + fetchedData: nil, + embeddedDataProvider: mockEmbeddedData, + localProtection: mockProtectionStore, + internalUserDecider: DefaultInternalUserDecider()) + return manager.privacyConfig + } + + let ddgConfig = """ + { + "features": { + "customUserAgent": { + "defaultPolicy": "ddg", + "state": "enabled", + "settings": { + "omitApplicationSites": [ + { + "domain": "cvs.com", + "reason": "Site reports browser not supported" + } + ], + "ddgFixedSites": [ + { + "domain": "test2.com" + } + ] + }, + "exceptions": [] + } + }, + "unprotectedTemporary": [] + } + """.data(using: .utf8)! + + func testWhenMobileUaAndDesktopFalseAndDomainSupportsFixedUAThenFixedMobileAgentUsed() { + let testee = UserAgent(defaultAgent: DefaultAgent.mobile) + let config = makePrivacyConfig(from: ddgConfig) + XCTAssertEqual(ExpectedAgent.mobileFixed, testee.agent(forUrl: Constants.ddgFixedUrl, isDesktop: false, privacyConfig: config)) + } + + func testWhenMobileUaAndDesktopTrueAndDomainSupportsFixedUAThenFixedMobileAgentUsed() { + let testee = UserAgent(defaultAgent: DefaultAgent.mobile) + let config = makePrivacyConfig(from: ddgConfig) + XCTAssertEqual(ExpectedAgent.desktopFixed, testee.agent(forUrl: Constants.ddgFixedUrl, isDesktop: true, privacyConfig: config)) + } + + func testWhenTabletUaAndDesktopFalseAndDomainSupportsFixedUAThenFixedMobileAgentUsed() { + let testee = UserAgent(defaultAgent: DefaultAgent.tablet) + let config = makePrivacyConfig(from: ddgConfig) + XCTAssertEqual(ExpectedAgent.tabletFixed, testee.agent(forUrl: Constants.ddgFixedUrl, isDesktop: false, privacyConfig: config)) + } + + func testWhenTabletUaAndDesktopTrueAndDomainSupportsFixedUAThenFixedMobileAgentUsed() { + let testee = UserAgent(defaultAgent: DefaultAgent.tablet) + let config = makePrivacyConfig(from: ddgConfig) + XCTAssertEqual(ExpectedAgent.desktopFixed, testee.agent(forUrl: Constants.ddgFixedUrl, isDesktop: true, privacyConfig: config)) + } + + let ddgFixedConfig = """ + { + "features": { + "customUserAgent": { + "state": "enabled", + "settings": { + "defaultPolicy": "ddgFixed", + "omitApplicationSites": [ + { + "domain": "cvs.com", + "reason": "Site reports browser not supported" + } + ], + "ddgDefaultSites": [ + { + "domain": "test3.com" + } + ] + }, + "exceptions": [] + } + }, + "unprotectedTemporary": [] + } + """.data(using: .utf8)! + + func testWhenMobileUaAndDesktopFalseAndDefaultPolicyFixedThenFixedMobileAgentUsed() { + let testee = UserAgent(defaultAgent: DefaultAgent.mobile) + let config = makePrivacyConfig(from: ddgFixedConfig) + XCTAssertEqual(ExpectedAgent.mobileFixed, testee.agent(forUrl: Constants.url, isDesktop: false, privacyConfig: config)) + } + + func testWhenMobileUaAndDesktopTrueAndDefaultPolicyFixedThenFixedMobileAgentUsed() { + let testee = UserAgent(defaultAgent: DefaultAgent.mobile) + let config = makePrivacyConfig(from: ddgFixedConfig) + XCTAssertEqual(ExpectedAgent.desktopFixed, testee.agent(forUrl: Constants.url, isDesktop: true, privacyConfig: config)) + } + + func testWhenTabletUaAndDesktopFalseAndDefaultPolicyFixedThenFixedMobileAgentUsed() { + let testee = UserAgent(defaultAgent: DefaultAgent.tablet) + let config = makePrivacyConfig(from: ddgFixedConfig) + XCTAssertEqual(ExpectedAgent.tabletFixed, testee.agent(forUrl: Constants.url, isDesktop: false, privacyConfig: config)) + } + + func testWhenTabletUaAndDesktopTrueAndDefaultPolicyFixedThenFixedMobileAgentUsed() { + let testee = UserAgent(defaultAgent: DefaultAgent.tablet) + let config = makePrivacyConfig(from: ddgFixedConfig) + XCTAssertEqual(ExpectedAgent.desktopFixed, testee.agent(forUrl: Constants.url, isDesktop: true, privacyConfig: config)) + } + + func testWhenDefaultPolicyFixedAndDomainIsOnDefaultListThenDefaultAgentUsed() { + let testee = UserAgent(defaultAgent: DefaultAgent.mobile) + let config = makePrivacyConfig(from: ddgFixedConfig) + XCTAssertEqual(ExpectedAgent.mobile, testee.agent(forUrl: Constants.ddgDefaultUrl, isDesktop: false, privacyConfig: config)) + } + + let closestConfig = """ + { + "features": { + "customUserAgent": { + "state": "enabled", + "settings": { + "defaultPolicy": "closest", + "omitApplicationSites": [ + { + "domain": "cvs.com", + "reason": "Site reports browser not supported" + } + ], + "ddgFixedSites": [ + { + "domain": "test2.com" + } + ], + "ddgDefaultSites": [ + { + "domain": "test3.com" + } + ] + }, + "exceptions": [] + } + }, + "unprotectedTemporary": [] + } + """.data(using: .utf8)! + + func testWhenMobileUaAndDesktopFalseAndDefaultPolicyClosestThenClosestMobileAgentUsed() { + let testee = UserAgent(defaultAgent: DefaultAgent.mobile) + let config = makePrivacyConfig(from: closestConfig) + XCTAssertEqual(ExpectedAgent.mobileClosest, testee.agent(forUrl: Constants.url, isDesktop: false, privacyConfig: config)) + } + + func testWhenMobileUaAndDesktopTrueAndDefaultPolicyClosestThenFixedMobileAgentUsed() { + let testee = UserAgent(defaultAgent: DefaultAgent.mobile) + let config = makePrivacyConfig(from: closestConfig) + XCTAssertEqual(ExpectedAgent.desktopClosest, testee.agent(forUrl: Constants.url, isDesktop: true, privacyConfig: config)) + } + + func testWhenTabletUaAndDesktopFalseAndDefaultPolicyClosestThenFixedMobileAgentUsed() { + let testee = UserAgent(defaultAgent: DefaultAgent.tablet) + let config = makePrivacyConfig(from: closestConfig) + XCTAssertEqual(ExpectedAgent.tabletClosest, testee.agent(forUrl: Constants.url, isDesktop: false, privacyConfig: config)) + } + + func testWhenTabletUaAndDesktopTrueAndDefaultPolicyClosestThenFixedMobileAgentUsed() { + let testee = UserAgent(defaultAgent: DefaultAgent.tablet) + let config = makePrivacyConfig(from: closestConfig) + XCTAssertEqual(ExpectedAgent.desktopClosest, testee.agent(forUrl: Constants.url, isDesktop: true, privacyConfig: config)) + } + + func testWhenDefaultPolicyClosestAndDomainIsOnDefaultListThenDefaultAgentUsed() { + let testee = UserAgent(defaultAgent: DefaultAgent.mobile) + let config = makePrivacyConfig(from: closestConfig) + XCTAssertEqual(ExpectedAgent.mobile, testee.agent(forUrl: Constants.ddgDefaultUrl, isDesktop: false, privacyConfig: config)) + } + + func testWhenDefaultPolicyClosestAndDomainIsOnFixedListThenFixedAgentUsed() { + let testee = UserAgent(defaultAgent: DefaultAgent.mobile) + let config = makePrivacyConfig(from: closestConfig) + XCTAssertEqual(ExpectedAgent.mobileFixed, testee.agent(forUrl: Constants.ddgFixedUrl, isDesktop: false, privacyConfig: config)) + } + + let configWithVersions = """ + { + "features": { + "customUserAgent": { + "state": "enabled", + "settings": { + "defaultPolicy": "ddg", + "omitApplicationSites": [ + { + "domain": "cvs.com", + "reason": "Site reports browser not supported" + } + ], + "closestUserAgent": { + "versions": ["350", "360"] + }, + "ddgFixedUserAgent": { + "versions": ["351", "361"] + } + }, + "exceptions": [] + } + }, + "unprotectedTemporary": [] + } + """.data(using: .utf8)! + + func testWhenAtbDoesNotMatchVersionFromConfigThenDefaultUAIsUsed() { + let statisticsStore = MockStatisticsStore() + statisticsStore.atb = "v300-1" + let testee = UserAgent(defaultAgent: DefaultAgent.mobile, statistics: statisticsStore) + let config = makePrivacyConfig(from: configWithVersions) + XCTAssertEqual(ExpectedAgent.mobile, testee.agent(forUrl: Constants.url, isDesktop: false, privacyConfig: config)) + } + + func testWhenAtbMatchesVersionInClosestUserAgentThenClosestUAIsUsed() { + let statisticsStore = MockStatisticsStore() + statisticsStore.atb = "v360-1" + let testee = UserAgent(defaultAgent: DefaultAgent.mobile, statistics: statisticsStore) + let config = makePrivacyConfig(from: configWithVersions) + XCTAssertEqual(ExpectedAgent.mobileClosest, testee.agent(forUrl: Constants.url, isDesktop: false, privacyConfig: config)) + } + + func testWhenAtbMatchesVersionInDDGFixedUserAgentThenDDGFixedUAIsUsed() { + let statisticsStore = MockStatisticsStore() + statisticsStore.atb = "v361-1" + let testee = UserAgent(defaultAgent: DefaultAgent.mobile, statistics: statisticsStore) + let config = makePrivacyConfig(from: configWithVersions) + XCTAssertEqual(ExpectedAgent.mobileFixed, testee.agent(forUrl: Constants.url, isDesktop: false, privacyConfig: config)) + } + + func testWhenAtbWithoutDayComponentMatchesVersionInDDGFixedUserAgentThenDDGFixedUAIsUsed() { + let statisticsStore = MockStatisticsStore() + statisticsStore.atb = "v361" + let testee = UserAgent(defaultAgent: DefaultAgent.mobile, statistics: statisticsStore) + let config = makePrivacyConfig(from: configWithVersions) + XCTAssertEqual(ExpectedAgent.mobileFixed, testee.agent(forUrl: Constants.url, isDesktop: false, privacyConfig: config)) + } + + func testWhenOldWebKitVersionThenDefaultMobileAgentUsed() { + let testee = UserAgent(defaultAgent: DefaultAgent.oldWebkitVersionMobile) + let config = makePrivacyConfig(from: ddgFixedConfig) + XCTAssertEqual(ExpectedAgent.oldWebkitVersionMobile, testee.agent(forUrl: Constants.url, isDesktop: false, privacyConfig: config)) + } + + func testWhenNewerWebKitVersionThenFixedAgentUsed() { + let testee = UserAgent(defaultAgent: DefaultAgent.newWebkitVersionMobile) + let config = makePrivacyConfig(from: ddgFixedConfig) + XCTAssertEqual(ExpectedAgent.newWebkitVersionMobile, testee.agent(forUrl: Constants.url, isDesktop: false, privacyConfig: config)) + } + + func testWhenSameWebKitVersionThenFixedAgentUsed() { + let testee = UserAgent(defaultAgent: DefaultAgent.sameWebkitVersionMobile) + let config = makePrivacyConfig(from: ddgFixedConfig) + XCTAssertEqual(ExpectedAgent.sameWebkitVersionMobile, testee.agent(forUrl: Constants.url, isDesktop: false, privacyConfig: config)) + } + }