From 837dfbfe7a1b2a5e0ec2fb24a47a53dec53444b0 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Fri, 29 Nov 2024 15:39:38 -0800 Subject: [PATCH] Add `locale` parameter to RMF survey builder (#1105) Required: Task/Issue URL: https://app.asana.com/0/1199333091098016/1208868981778301/f iOS PR: duckduckgo/iOS#3648 macOS PR: duckduckgo/macos-browser#3613 What kind of version bump will this require?: Patch --- ...faultRemoteMessagingSurveyURLBuilder.swift | 10 +- .../RemoteMessagingSurveyActionMapping.swift | 1 + ...RemoteMessagingSurveyURLBuilderTests.swift | 127 ++++++++++++++++++ 3 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 Tests/RemoteMessagingTests/Mappers/DefaultRemoteMessagingSurveyURLBuilderTests.swift diff --git a/Sources/RemoteMessaging/Mappers/DefaultRemoteMessagingSurveyURLBuilder.swift b/Sources/RemoteMessaging/Mappers/DefaultRemoteMessagingSurveyURLBuilder.swift index dc3f3e48b..37e53e352 100644 --- a/Sources/RemoteMessaging/Mappers/DefaultRemoteMessagingSurveyURLBuilder.swift +++ b/Sources/RemoteMessaging/Mappers/DefaultRemoteMessagingSurveyURLBuilder.swift @@ -31,11 +31,16 @@ public struct DefaultRemoteMessagingSurveyURLBuilder: RemoteMessagingSurveyActio private let statisticsStore: StatisticsStore private let vpnActivationDateStore: VPNActivationDateProviding private let subscription: Subscription? + private let localeIdentifier: String - public init(statisticsStore: StatisticsStore, vpnActivationDateStore: VPNActivationDateProviding, subscription: Subscription?) { + public init(statisticsStore: StatisticsStore, + vpnActivationDateStore: VPNActivationDateProviding, + subscription: Subscription?, + localeIdentifier: String = Locale.current.identifier) { self.statisticsStore = statisticsStore self.vpnActivationDateStore = vpnActivationDateStore self.subscription = subscription + self.localeIdentifier = localeIdentifier } // swiftlint:disable:next cyclomatic_complexity @@ -72,6 +77,9 @@ public struct DefaultRemoteMessagingSurveyURLBuilder: RemoteMessagingSurveyActio let daysSinceInstall = Calendar.current.numberOfDaysBetween(installDate, and: Date()) { queryItems.append(URLQueryItem(name: parameter.rawValue, value: String(describing: daysSinceInstall))) } + case .locale: + let formattedLocale = LocaleMatchingAttribute.localeIdentifierAsJsonFormat(localeIdentifier) + queryItems.append(URLQueryItem(name: parameter.rawValue, value: formattedLocale)) case .privacyProStatus: if let privacyProStatusSurveyParameter = subscription?.privacyProStatusSurveyParameter { queryItems.append(URLQueryItem(name: parameter.rawValue, value: privacyProStatusSurveyParameter)) diff --git a/Sources/RemoteMessaging/Mappers/RemoteMessagingSurveyActionMapping.swift b/Sources/RemoteMessaging/Mappers/RemoteMessagingSurveyActionMapping.swift index 668b03e1c..a927e1c47 100644 --- a/Sources/RemoteMessaging/Mappers/RemoteMessagingSurveyActionMapping.swift +++ b/Sources/RemoteMessaging/Mappers/RemoteMessagingSurveyActionMapping.swift @@ -24,6 +24,7 @@ public enum RemoteMessagingSurveyActionParameter: String, CaseIterable { case atbVariant = "var" case daysInstalled = "delta" case hardwareModel = "mo" + case locale = "locale" case osVersion = "osv" case privacyProStatus = "ppro_status" case privacyProPlatform = "ppro_platform" diff --git a/Tests/RemoteMessagingTests/Mappers/DefaultRemoteMessagingSurveyURLBuilderTests.swift b/Tests/RemoteMessagingTests/Mappers/DefaultRemoteMessagingSurveyURLBuilderTests.swift new file mode 100644 index 000000000..c6a490eae --- /dev/null +++ b/Tests/RemoteMessagingTests/Mappers/DefaultRemoteMessagingSurveyURLBuilderTests.swift @@ -0,0 +1,127 @@ +// +// DefaultRemoteMessagingSurveyURLBuilderTests.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +import BrowserServicesKitTestsUtils +import RemoteMessagingTestsUtils +@testable import Subscription +@testable import RemoteMessaging + +class DefaultRemoteMessagingSurveyURLBuilderTests: XCTestCase { + + func testAddingATBParameter() { + let builder = buildRemoteMessagingSurveyURLBuilder(atb: "v456-7") + let baseURL = URL(string: "https://duckduckgo.com")! + let finalURL = builder.add(parameters: [.atb], to: baseURL) + + XCTAssertEqual(finalURL.absoluteString, "https://duckduckgo.com?atb=v456-7") + } + + func testAddingATBVariantParameter() { + let builder = buildRemoteMessagingSurveyURLBuilder(variant: "test-variant") + let baseURL = URL(string: "https://duckduckgo.com")! + let finalURL = builder.add(parameters: [.atbVariant], to: baseURL) + + XCTAssertEqual(finalURL.absoluteString, "https://duckduckgo.com?var=test-variant") + } + + func testAddingLocaleParameter() { + let builder = buildRemoteMessagingSurveyURLBuilder(locale: Locale(identifier: "en_NZ")) + let baseURL = URL(string: "https://duckduckgo.com")! + let finalURL = builder.add(parameters: [.locale], to: baseURL) + + XCTAssertEqual(finalURL.absoluteString, "https://duckduckgo.com?locale=en-NZ") + } + + func testAddingPrivacyProParameters() { + let builder = buildRemoteMessagingSurveyURLBuilder() + let baseURL = URL(string: "https://duckduckgo.com")! + let finalURL = builder.add(parameters: [.privacyProStatus, .privacyProPlatform, .privacyProPlatform], to: baseURL) + + XCTAssertEqual(finalURL.absoluteString, "https://duckduckgo.com?ppro_status=auto_renewable&ppro_platform=apple&ppro_platform=apple") + } + + func testAddingVPNUsageParameters() { + let builder = buildRemoteMessagingSurveyURLBuilder(vpnDaysSinceActivation: 10, vpnDaysSinceLastActive: 5) + let baseURL = URL(string: "https://duckduckgo.com")! + let finalURL = builder.add(parameters: [.vpnFirstUsed, .vpnLastUsed], to: baseURL) + + XCTAssertEqual(finalURL.absoluteString, "https://duckduckgo.com?vpn_first_used=10&vpn_last_used=5") + } + + func testAddingParametersToURLThatAlreadyHasThem() { + let builder = buildRemoteMessagingSurveyURLBuilder(vpnDaysSinceActivation: 10, vpnDaysSinceLastActive: 5) + let baseURL = URL(string: "https://duckduckgo.com?param=test")! + let finalURL = builder.add(parameters: [.vpnFirstUsed, .vpnLastUsed], to: baseURL) + + XCTAssertEqual(finalURL.absoluteString, "https://duckduckgo.com?param=test&vpn_first_used=10&vpn_last_used=5") + } + + private func buildRemoteMessagingSurveyURLBuilder( + atb: String = "v123-4", + variant: String = "var", + vpnDaysSinceActivation: Int = 2, + vpnDaysSinceLastActive: Int = 1, + locale: Locale = Locale(identifier: "en_US") + ) -> DefaultRemoteMessagingSurveyURLBuilder { + + let mockStatisticsStore = MockStatisticsStore() + mockStatisticsStore.atb = atb + mockStatisticsStore.variant = variant + + let vpnActivationDateStore = MockVPNActivationDateStore( + daysSinceActivation: vpnDaysSinceActivation, + daysSinceLastActive: vpnDaysSinceLastActive + ) + + let subscription = DDGSubscription(productId: "product-id", + name: "product-name", + billingPeriod: .monthly, + startedAt: Date(timeIntervalSince1970: 1000), + expiresOrRenewsAt: Date(timeIntervalSince1970: 2000), + platform: .apple, + status: .autoRenewable) + + return DefaultRemoteMessagingSurveyURLBuilder( + statisticsStore: mockStatisticsStore, + vpnActivationDateStore: vpnActivationDateStore, + subscription: subscription, + localeIdentifier: locale.identifier) + } + +} + +private class MockVPNActivationDateStore: VPNActivationDateProviding { + + var _daysSinceActivation: Int + var _daysSinceLastActive: Int + + init(daysSinceActivation: Int, daysSinceLastActive: Int) { + self._daysSinceActivation = daysSinceActivation + self._daysSinceLastActive = daysSinceLastActive + } + + func daysSinceActivation() -> Int? { + return _daysSinceActivation + } + + func daysSinceLastActive() -> Int? { + return _daysSinceLastActive + } + +}