Skip to content

Commit

Permalink
[MT-1504] Refresh and Reload improvements (#331)
Browse files Browse the repository at this point in the history
* Make remote commands accept local paths with and without extension definition

* Use constants for dle and tiq urls

* Share a ResourceRetriever and ResourceRefresher for RemoteCommands and RemotePublishSettings

Fix RemoteCommands issues:
- load url remote commands successfully after others have been previously added
- refresh remote commands on interval
- fix cache usage for remote commands
- use etag instead of if-not-modified-since
- safely extract fileName from url
- always use commandId to access different remote commands instead of a mix of commandId and fileName

Fix ResourceRetriever to accept all 20x responses as correct

* Add retry mechanism for ResourceRetriever and improve shouldRefresh checks in ResourceRefresher

Move the should refresh entirely inside of the resource refresher
Move the logging in the delegate calls instead

* Fix refresh logic with wrong conditions

Add delegate call for cached/bundle settings
Fix old tests

* Remove resource retriever forced refresh when not cached and add backoff for cooldown Interval

* Make condition clearer for when to use local bundle RemoteCommand config

* Use commandId instead of filename for remote commands cache and asset bundle for url remote commands

Create an internal bundle option just for tests

* Reorganize files

* Improve VisitorServiceRetriever urlSession and completion calls

* Simplify Resource Refresher error handling

* Improve Collect urlSession and Completion calls

* Test ResourceRetriever with a new MockURLSession

* Fix ResourceRefresher Cooldown logic and add some tests

* Add ResourceRefresher tests and create ErrorCooldown utility to help with tests

* Improve and test ErrorCooldown

Make sure that when errorCooldown is nil the normal refreshInterval is used

* Remove settings from bundle option

* Fix old remote commands manager tests

* Fix collect completion

* Fix remaining old tests for remote commands

Remove MockRemoteCommandsDiskStorage

* Add RemoteCommands tests for bundle and refreshes

* Fix TagManagement flaky tests

* Reload webview after session updates

* Add some documentation and improve isFileCached

* Fix comment spacing

* Fix inAppPurchase tests

* Improve readability of dynamicTrack and fix mock webview in tests

* Try to use M1 mac on CICD

* Fix wrong macos specification

* Reset ErrorCooldown counter on non-errors

* Make publishsettings tests more reliable

* Ignore 304 errors from ResourceRetriever in ResourceRefresher's ErrorCooldown

* Try to make Autotracking UI tests more reliable on slow CICD
  • Loading branch information
Enricoza committed May 2, 2024
1 parent 39cc131 commit 24babd7
Show file tree
Hide file tree
Showing 57 changed files with 1,528 additions and 926 deletions.
14 changes: 7 additions & 7 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on: [pull_request]
jobs:
build_iOS:
name: Build iOS
runs-on: macos-13
runs-on: macos-14
timeout-minutes: 60
env:
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
Expand Down Expand Up @@ -42,7 +42,7 @@ jobs:
working-directory: ${{ env.working-directory }}
build_macOS:
name: Build macOS
runs-on: macos-13
runs-on: macos-14
timeout-minutes: 60
env:
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
Expand Down Expand Up @@ -74,7 +74,7 @@ jobs:
working-directory: ${{ env.working-directory }}
build_tvOS:
name: Build tvOS
runs-on: macos-13
runs-on: macos-14
timeout-minutes: 60
env:
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
Expand Down Expand Up @@ -102,7 +102,7 @@ jobs:
working-directory: ${{ env.working-directory }}
build_TealiumSwiftExample_iOS:
name: Build TealiumSwiftExample iOS
runs-on: macos-13
runs-on: macos-14
timeout-minutes: 60
env:
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
Expand All @@ -125,7 +125,7 @@ jobs:
working-directory: ${{ env.working-directory }}
build_TealiumSwiftExample_tvOS:
name: Build TealiumSwiftExample tvOS
runs-on: macos-13
runs-on: macos-14
timeout-minutes: 60
env:
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
Expand All @@ -148,7 +148,7 @@ jobs:
working-directory: ${{ env.working-directory }}
build_ConsentManagerDemo:
name: Build ConsentManagerDemo
runs-on: macos-13
runs-on: macos-14
timeout-minutes: 60
env:
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
Expand All @@ -171,7 +171,7 @@ jobs:
working-directory: ${{ env.working-directory }}
build_VisitorServiceDemo:
name: Build TealiumVisitorProfileDemo
runs-on: macos-13
runs-on: macos-14
timeout-minutes: 60
env:
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on: [pull_request]
jobs:
install:
name: Setup Fastlane
runs-on: macos-13
runs-on: macos-14
steps:
- uses: actions/checkout@v2
- uses: ruby/setup-ruby@v1
Expand All @@ -17,7 +17,7 @@ jobs:
test_iOS:
needs: install
name: iOS Tests
runs-on: macos-13
runs-on: macos-14
timeout-minutes: 60
env:
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
Expand Down Expand Up @@ -79,7 +79,7 @@ jobs:
test_macOS:
needs: install
name: macOS Tests
runs-on: macos-13
runs-on: macos-14
timeout-minutes: 60
env:
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
Expand Down Expand Up @@ -131,7 +131,7 @@ jobs:
test_tvOS:
needs: install
name: tvOS Tests
runs-on: macos-13
runs-on: macos-14
timeout-minutes: 60
env:
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
Expand Down Expand Up @@ -199,7 +199,7 @@ jobs:
# working-directory: ${{ env.working-directory }}
test_Autotracking_iOS_uiTests:
name: Test TealiumAutotracking iOS UI Tests
runs-on: macos-13
runs-on: macos-14
timeout-minutes: 60
env:
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
Expand All @@ -219,7 +219,7 @@ jobs:
test_Autotracking_macOS_uiTests:
needs: install
name: Test TealiumAutotracking macOS UI Tests
runs-on: macos-13
runs-on: macos-14
timeout-minutes: 60
env:
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
Expand All @@ -239,7 +239,7 @@ jobs:
test_Autotracking_tvOS_uiTests:
needs: install
name: Test TealiumAutotracking tvOS UI Tests
runs-on: macos-13
runs-on: macos-14
timeout-minutes: 60
env:
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
Expand Down
166 changes: 138 additions & 28 deletions builder/tealium-swift.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

75 changes: 75 additions & 0 deletions support/tests/MockURLSession.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// MockURLSession.swift
// tealium-swift
//
// Created by Enrico Zannini on 08/04/24.
// Copyright © 2024 Tealium, Inc. All rights reserved.
//

import Foundation
import XCTest
@testable import TealiumCore

extension DataTaskResult {
private static func urlResponse(statusCode: Int, headerFields: [String: String]?) -> HTTPURLResponse? {
HTTPURLResponse(url: URL(string: "someURL")!, statusCode: statusCode, httpVersion: "1.1", headerFields: headerFields)
}
static func success(withData data: Data?, statusCode: Int = 200, headers: [String: String]? = nil) -> DataTaskResult {
return .success((urlResponse(statusCode: statusCode, headerFields: headers), data))
}
static func success<Obj: Codable>(with object: Obj, statusCode: Int = 200, headers: [String: String]? = nil) -> DataTaskResult {
return .success(withData: try? JSONEncoder().encode(object), statusCode: statusCode, headers: headers)
}
}

class MockURLSession: URLSessionProtocol {
var isInvalidated = false
var result: DataTaskResult?
@ToAnyObservable<TealiumReplaySubject<URLRequest>>(TealiumReplaySubject<URLRequest>())
var onRequestSent: TealiumObservable<URLRequest>

class MockDataTask: URLSessionDataTaskProtocol {
let completion: () -> Void
init(completion: @escaping () -> Void) {
self.completion = completion
}
func resume() {
completion()
}
}

func tealiumDataTask(with url: URL, completionHandler: @escaping DataTaskCompletion) -> URLSessionDataTaskProtocol {
let request = URLRequest(url: url)
_onRequestSent.publish(request)
return tealiumDataTask(with: request, completionHandler: completionHandler)

}

func tealiumDataTask(with request: URLRequest, completionHandler: @escaping DataTaskCompletion) -> URLSessionDataTaskProtocol {
_onRequestSent.publish(request)
return tealiumDataTask(with: request.url!) { result in
do {
let tuple = try result.get()
completionHandler(tuple.1, tuple.0, nil)
} catch {
completionHandler(nil, nil, error)
}
}
}

func tealiumDataTask(with url: URL, completionHandler: @escaping (DataTaskResult) -> Void) -> URLSessionDataTaskProtocol {
MockDataTask {
guard let result = self.result else {
XCTFail("MockURLSession called with with no result")
return
}
completionHandler(result)
}
}

func finishTealiumTasksAndInvalidate() {
isInvalidated = true
}


}
2 changes: 1 addition & 1 deletion support/tests/TestTealiumHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ var testOptionalData = [TealiumTestKey.stringKey: TealiumTestValue.stringValue,
let testTealiumConfig: TealiumConfig = TealiumConfig(account: TealiumTestValue.account,
profile: TealiumTestValue.profile,
environment: TealiumTestValue.environment,
options: testOptionalData as [String: Any])
options: testOptionalData)

let testTrackRequest = TealiumTrackRequest(data: [:])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ extension XCUIApplication {
let predicate = NSPredicate(format: "value CONTAINS[c] %@ || label CONTAINS[c] %@", text, text) // don't know why value works for macOS and label for iOS and tvOS
XCTAssertTrue(staticTexts
.containing(predicate).firstMatch
.waitForExistence(timeout: 5),
"Can not find \(text)")
.waitForExistence(timeout: 25), "Can not find \(text)")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class DataLayerTests: XCTestCase {

func testInsertSingleExpiresAfterASecond() {
eventData = Set<DataLayerItem>()
eventData.insert(key: "itemOne", value: "test1", expiry: .after(Date().addSeconds(1)!))
eventData.insert(key: "itemOne", value: "test1", expiry: .after(Date().addingTimeInterval(1)))
var eventDataExpired = eventData.removeExpired()
XCTAssertEqual(eventDataExpired.count, 1)
let exp = expectation(description: "waiting")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,23 @@ class MockDataLayerManager: DataLayerManagerProtocol {
var deleteSingleCount = 0
var deleteMultiCount = 0
var deleteAllCount = 0
var onDataUpdated: TealiumObservable<[String : Any]> = TealiumPublisher<[String:Any]>().asObservable()
var _onDataUpdated = TealiumPublisher<[String:Any]>()
var onDataUpdated: TealiumObservable<[String : Any]> { _onDataUpdated.asObservable() }
var onDataRemoved: TealiumObservable<[String]> = TealiumPublisher<[String]>().asObservable()
var all: [String: Any] {
get {
["all": "eventdata"]
}
set {
self.add(data: newValue, expiration: .forever)
}
}
var all: [String: Any] = ["all": "eventdata"]

var allSessionData: [String: Any] {
["all": "sessiondata"]
}
var allSessionData: [String: Any] = ["all": "sessiondata"]

var minutesBetweenSessionIdentifier: TimeInterval = 1.0

var secondsBetweenTrackEvents: TimeInterval = 1.0

var sessionId: String? {
get {
"testsessionid"
all[TealiumDataKey.sessionId] as? String
}
set {
self.add(data: ["sessionId": newValue!], expiration: .session)
self.add(data: [TealiumDataKey.sessionId: newValue as Any], expiration: .session)
}
}

Expand All @@ -61,10 +53,16 @@ class MockDataLayerManager: DataLayerManagerProtocol {

func add(data: [String: Any], expiration: Expiry) {
addMultiCount += 1
for kv in data {
all[kv.key] = kv.value
}
_onDataUpdated.publish(data)
}

func add(key: String, value: Any, expiration: Expiry) {
addSingleCount += 1
all[key] = value
_onDataUpdated.publish([key: value])
}

func joinTrace(id: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class SessionStarterTests: XCTestCase {

func testSessionURL() {
let sessionURL = sessionStarter.sessionURL
XCTAssertEqual(true, sessionURL.hasPrefix("https://tags.tiqcdn.com/utag/tiqapp/utag.v.js?a=ssTestAccount/ssTestProfile/"))
XCTAssertTrue(sessionURL.hasPrefix("https://tags.tiqcdn.com/utag/tiqapp/utag.v.js?a=ssTestAccount/ssTestProfile/"))
}

func testRequestSessionSuccessful() {
Expand Down
72 changes: 72 additions & 0 deletions support/tests/test_tealium_core/network/ErrorCooldownTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//
// ErrorCooldownTests.swift
// tealium-swift
//
// Created by Enrico Zannini on 09/04/24.
// Copyright © 2024 Tealium, Inc. All rights reserved.
//

import XCTest
@testable import TealiumCore

final class ErrorCooldownTests: XCTestCase {
let errorCooldown = ErrorCooldown(baseInterval: 10, maxInterval: 50)!

func testStartsNotInCooldown() {
XCTAssertFalse(errorCooldown.isInCooldown(lastFetch: Date()))
}

func testGoesInCooldownAfterError() {
errorCooldown.newCooldownEvent(error: HTTPError.unknown)
XCTAssertTrue(errorCooldown.isInCooldown(lastFetch: Date()))
}

func testCooldownEndsAfterErrorBaseInterval() {
errorCooldown.newCooldownEvent(error: HTTPError.unknown)
XCTAssertTrue(errorCooldown.isInCooldown(lastFetch: Date()))
XCTAssertFalse(errorCooldown.isInCooldown(lastFetch: Date().addingTimeInterval(-11)))
}

func testCooldownIncreasesAfterNewErrors() {
errorCooldown.newCooldownEvent(error: HTTPError.unknown)
XCTAssertTrue(errorCooldown.isInCooldown(lastFetch: Date()))
errorCooldown.newCooldownEvent(error: HTTPError.unknown)
XCTAssertTrue(errorCooldown.isInCooldown(lastFetch: Date().addingTimeInterval(-11)))
XCTAssertFalse(errorCooldown.isInCooldown(lastFetch: Date().addingTimeInterval(-21)))
}

func testCooldownCantBeOverMaxInterval() {
for _ in 0..<7 {
errorCooldown.newCooldownEvent(error: HTTPError.unknown)
}
XCTAssertTrue(errorCooldown.isInCooldown(lastFetch: Date().addingTimeInterval(-49)))
XCTAssertFalse(errorCooldown.isInCooldown(lastFetch: Date().addingTimeInterval(-51)))
}

func testCooldownIsResetOnSuccessEvent() {
for _ in 0..<7 {
errorCooldown.newCooldownEvent(error: HTTPError.unknown)
}
XCTAssertTrue(errorCooldown.isInCooldown(lastFetch: Date().addingTimeInterval(-49)))
XCTAssertFalse(errorCooldown.isInCooldown(lastFetch: Date().addingTimeInterval(-51)))
errorCooldown.newCooldownEvent(error: nil)
XCTAssertFalse(errorCooldown.isInCooldown(lastFetch: Date()))
}

func testCooldownIsOnBaseValueAfterBeingReset() {
for _ in 0..<7 {
errorCooldown.newCooldownEvent(error: HTTPError.unknown)
}
XCTAssertTrue(errorCooldown.isInCooldown(lastFetch: Date().addingTimeInterval(-49)))
XCTAssertFalse(errorCooldown.isInCooldown(lastFetch: Date().addingTimeInterval(-51)))
errorCooldown.newCooldownEvent(error: nil)
errorCooldown.newCooldownEvent(error: HTTPError.unknown)
XCTAssertTrue(errorCooldown.isInCooldown(lastFetch: Date().addingTimeInterval(-9)))
XCTAssertFalse(errorCooldown.isInCooldown(lastFetch: Date().addingTimeInterval(-11)))
}

func testInitializationFailsWithoutBaseInterval() {
let errorCooldown = ErrorCooldown(baseInterval: nil, maxInterval: 50)
XCTAssertNil(errorCooldown)
}
}
Loading

0 comments on commit 24babd7

Please sign in to comment.