Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add UserAgentDefaults for Managing User Agent String Persistence #992

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions PrebidMobile.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,8 @@
9720387C2358771600F8025A /* NativeRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9720387B2358771600F8025A /* NativeRequestTests.swift */; };
9743CB84235F1CDB002E2CAA /* NativeAssetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9743CB83235F1CDB002E2CAA /* NativeAssetTests.swift */; };
9743CB86235F264B002E2CAA /* NativeEventTrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9743CB85235F264B002E2CAA /* NativeEventTrackerTests.swift */; };
A74C24C72C09CC3F007DF612 /* UserAgentPersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = A74C24C62C09CC3F007DF612 /* UserAgentPersistence.swift */; };
A74C24C92C09D673007DF612 /* UserAgentPersistenceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A74C24C82C09D672007DF612 /* UserAgentPersistenceTest.swift */; };
FA5AD5E42271FA4100C8F274 /* ConstantsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA5AD5E32271FA4100C8F274 /* ConstantsTest.swift */; };
FA9D7F2722E8A83D006FCBEF /* AdViewUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA9D7F2622E8A83D006FCBEF /* AdViewUtilsTests.swift */; };
FAA29904242D1C27002ACBF2 /* TargetingObjCTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FAA29903242D1C27002ACBF2 /* TargetingObjCTests.m */; };
Expand Down Expand Up @@ -1648,6 +1650,8 @@
97826AA621FB4F1B001E2C05 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
9791778F2201AF4700E624CE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
979177962201AF5F00E624CE /* DispatcherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DispatcherTests.swift; sourceTree = "<group>"; };
A74C24C62C09CC3F007DF612 /* UserAgentPersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAgentPersistence.swift; sourceTree = "<group>"; };
A74C24C82C09D672007DF612 /* UserAgentPersistenceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAgentPersistenceTest.swift; sourceTree = "<group>"; };
FA4A88432497A99D00FDCBB6 /* Swizzling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Swizzling.swift; sourceTree = "<group>"; };
FA5AD5E32271FA4100C8F274 /* ConstantsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsTest.swift; sourceTree = "<group>"; };
FA85F9B4264946FC00B8BE72 /* TestUtils.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = TestUtils.xcodeproj; path = ../tools/TestUtils/TestUtils.xcodeproj; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2100,6 +2104,7 @@
537B651D2833A3DA008AE9D1 /* Reachability.swift */,
537B653F2833C091008AE9D1 /* NetworkType.swift */,
5347473A2BFB4D3D00966658 /* UserAgentService.swift */,
A74C24C62C09CC3F007DF612 /* UserAgentPersistence.swift */,
);
path = Utilities;
sourceTree = "<group>";
Expand Down Expand Up @@ -3020,6 +3025,7 @@
925D5E562737F2D900A8A2B5 /* PBMMacrosTest.m */,
925D5E582737F35500A8A2B5 /* UIViewExtensionsTest.swift */,
536A39252A84C50F00B1CCEA /* StringExtensionsTest.swift */,
A74C24C82C09D672007DF612 /* UserAgentPersistenceTest.swift */,
);
path = UtilitiesExtensionsTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -3831,6 +3837,7 @@
922AFD612737306900732C53 /* PBMModalViewControllerTest.swift in Sources */,
925D5E552737F2A900A8A2B5 /* PBMFunctionsPrivateTest.m in Sources */,
47D9A4E2F32109B33E75FD2C /* MockBundle.swift in Sources */,
A74C24C92C09D673007DF612 /* UserAgentPersistenceTest.swift in Sources */,
928E5A7427F0F84C000ADA1A /* NativeMarkupRequestObjectTest.swift in Sources */,
925D5DB72737C60D00A8A2B5 /* PBMBidResponseTransformer+TestExtension.swift in Sources */,
922AFCF42736FDD500732C53 /* PBMFunctionsObjCTest.m in Sources */,
Expand Down Expand Up @@ -4175,6 +4182,7 @@
FAEE4D0B262DC2B200AD9966 /* Dispatcher.swift in Sources */,
5BC37A10271F1D0000444D5E /* AdUnitConfig.swift in Sources */,
5BC37926271F1CFF00444D5E /* PBMDeepLinkPlusHelper+Testing.m in Sources */,
A74C24C72C09CC3F007DF612 /* UserAgentPersistence.swift in Sources */,
5BC37A90271F1D0000444D5E /* InterstitialAdUnitDelegate.swift in Sources */,
5BC37A88271F1D0000444D5E /* RewardedAdUnitDelegate.swift in Sources */,
5BC378DB271F1CFF00444D5E /* PBMORTBDeal.m in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*   Copyright 2018-2024 Prebid.org, Inc.

 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 Foundation
import UIKit

/**
A protocol that defines the interface for user agent persistence.
*/
protocol UserAgentPersistence {
/// The user agent string.
var userAgent: String? { get set }

/// Initializes a new instance of a class conforming to `UserAgentPersistence`.
init(osVersion: String?)
}

/**
A class that handles persistence of the user agent string in UserDefaults.
*/
class UserAgentDefaults: UserAgentPersistence {
/// The key used to store the user agent dictionary in UserDefaults.
private let key: String = "PBMUserAgentService_UserAgentStore"

/// Get the current OS version.
private let osVersion: String

/**
Get the contents of the user agent dictionary from UserDefaults.
*/
var contents: [String: String]? {
UserDefaults.standard.dictionary(forKey: key) as? [String: String]
}

/// Shadow variable to save processing by preventing the casting of UserDefaults to an array on all requests
private lazy var _userAgent: String? = contents?[osVersion]

/**
Get and set the user agent for the current OS version in UserDefaults.
*/
var userAgent: String? {
get {
return _userAgent
}
set {
var userAgentDictionary: [String: String] = [:]
userAgentDictionary[osVersion] = newValue
// Overwrites any previous value in UserDefaults.
UserDefaults.standard.set(userAgentDictionary, forKey: key)
_userAgent = newValue
}
}

/**
Initializes a new instance of `UserAgentDefaults` with a specified OS version.

- Parameter osVersion: The OS version to use for the user agent persistence.
*/
required init(osVersion: String? = nil) {
self.osVersion = osVersion ?? UIDevice.current.systemVersion
}

/**
Resets the user agent defaults by removing the stored user agent dictionary from UserDefaults.
*/
func reset() {
_userAgent = nil
UserDefaults.standard.removeObject(forKey: key)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ public class UserAgentService: NSObject {

public static let shared = UserAgentService()

public private(set) var userAgent: String = ""
public private(set) lazy var userAgent: String = store.userAgent ?? ""

private var store: UserAgentPersistence
private var webViews = [WKWebView]()

override init() {
required init(store: UserAgentPersistence? = nil) {
self.store = store ?? UserAgentDefaults()
super.init()
fetchUserAgent()
}
Expand All @@ -48,6 +50,7 @@ public class UserAgentService: NSObject {

if let result = result, self.userAgent.isEmpty {
self.userAgent = "\(result)"
store.userAgent = self.userAgent
}

self.webViews.removeAll(where: { $0 == webView })
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*   Copyright 2018-2024 Prebid.org, Inc.

 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
@testable import PrebidMobile

class UserAgentDefaultsTest: XCTestCase {

var userAgentDefaults: UserAgentDefaults!

override func setUp() {
super.setUp()
// Initialize UserAgentDefaults before each test
userAgentDefaults = UserAgentDefaults()
}

override func tearDown() {
// Reset UserDefaults and UserAgentDefaults after each test
userAgentDefaults.reset()
userAgentDefaults = nil
super.tearDown()
}

func testUserAgentPersistence() {
// Test setting and getting the user agent
let testUserAgent = "TestUserAgentString"
userAgentDefaults.userAgent = testUserAgent

XCTAssertEqual(userAgentDefaults.userAgent, testUserAgent, "User agent should be correctly set and retrieved.")
}

func testUserAgentInitializationWithCurrentOSVersion() {
// Test that the user agent is correctly initialized with the current OS version
let testUserAgent = "TestUserAgentString"
userAgentDefaults.userAgent = testUserAgent

let newUserAgentDefaults = UserAgentDefaults()
XCTAssertEqual(newUserAgentDefaults.userAgent, testUserAgent, "User agent should be correctly retrieved with current OS version.")
}

func testUserAgentReset() {
// Test resetting the user agent
let testUserAgent = "TestUserAgentString"
userAgentDefaults.userAgent = testUserAgent
userAgentDefaults.reset()

XCTAssertNil(userAgentDefaults.userAgent, "User agent should be nil after reset.")
}

func testUserAgentPersistenceWithDifferentOSVersion() {
// Test user agent persistence with a different OS version
let testUserAgent = "TestUserAgentString"
let customOSVersion = "CustomOSVersion"

let customUserAgentDefaults = UserAgentDefaults(osVersion: customOSVersion)
customUserAgentDefaults.userAgent = testUserAgent

XCTAssertEqual(customUserAgentDefaults.userAgent, testUserAgent, "User agent should be correctly set and retrieved for a custom OS version.")

let newCustomUserAgentDefaults = UserAgentDefaults(osVersion: customOSVersion)
XCTAssertEqual(newCustomUserAgentDefaults.userAgent, testUserAgent, "User agent should be correctly retrieved for a custom OS version.")
}

func testEmptyUserAgent() {
userAgentDefaults.userAgent = "TestUserAgentString"
// Test setting an empty user agent string
userAgentDefaults.userAgent = nil
XCTAssertNil(userAgentDefaults.userAgent, "User agent should be nil when set to an empty string.")
}

func testContents() {
// Test the contents property
let testUserAgent = "TestUserAgentString"
userAgentDefaults.userAgent = testUserAgent

XCTAssertEqual(userAgentDefaults.contents?[UIDevice.current.systemVersion], testUserAgent, "Contents should include the user agent for the current OS version.")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,23 @@
import XCTest
@testable import PrebidMobile

private class MockUserAgentPersistence: UserAgentPersistence {
var userAgent: String?
required init(osVersion: String? = nil) {}
}

class UserAgentServiceTest: XCTestCase {

var expectationUserAgentExecuted: XCTestExpectation?

var service: UserAgentService {
UserAgentService(store: MockUserAgentPersistence())
}

func testUserAgentService() {
expectationUserAgentExecuted = expectation(description: "expectationUserAgentExecuted")

let userAgentService = UserAgentService.shared
let userAgentService = service

userAgentService.fetchUserAgent { [weak self] userAgentString in
XCTAssert(userAgentService.userAgent == userAgentString)
Expand Down Expand Up @@ -54,16 +63,16 @@ class UserAgentServiceTest: XCTestCase {
let expectationCheckThread = self.expectation(description: "Check thread expectation")

DispatchQueue.global(qos: .background).async {
print(UserAgentService.shared.userAgent)
print(self.service.userAgent)
expectationCheckThread.fulfill()
}

waitForExpectations(timeout: 1)
}

func testMultipleCalls() {
let userAgentService = UserAgentService.shared
let userAgentService = service

expectationUserAgentExecuted = expectation(description: "expectationUserAgentExecuted")
expectationUserAgentExecuted?.expectedFulfillmentCount = 3

Expand All @@ -83,4 +92,23 @@ class UserAgentServiceTest: XCTestCase {

XCTAssert(userAgentService.userAgent.isEmpty == false)
}

func testPersistence() {
let userAgentService = UserAgentService()

expectationUserAgentExecuted = expectation(description: "expectationUserAgentExecuted")
expectationUserAgentExecuted?.expectedFulfillmentCount = 2

userAgentService.fetchUserAgent { [weak self] _ in
self?.expectationUserAgentExecuted?.fulfill()
}

userAgentService.fetchUserAgent { [weak self] _ in
self?.expectationUserAgentExecuted?.fulfill()
}

waitForExpectations(timeout: 10)

XCTAssert(userAgentService.userAgent.isEmpty == false)
}
}