// // Copyright 2022 Google LLC // // 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 FirebaseSessions class SessionGeneratorTests: XCTestCase { var generator: SessionGenerator! let validSettings: [String: Any] = [:] var cache: SettingsCacheClient! var appInfo: MockApplicationInfo! var downloader: MockSettingsDownloader! var remoteSettings: RemoteSettings! var localOverrideSettings: LocalOverrideSettings! var sdkDefaultSettings: SDKDefaultSettings! var sessionSettings: SessionsSettings! override func setUp() { // Clear all UserDefaults if let appDomain = Bundle.main.bundleIdentifier { UserDefaults.standard.removePersistentDomain(forName: appDomain) } appInfo = MockApplicationInfo() cache = SettingsCache() cache.removeCache() // just reinstantiating cache isn't enough because of persistence downloader = MockSettingsDownloader(successResponse: validSettings) remoteSettings = RemoteSettings(appInfo: appInfo, downloader: downloader, cache: cache) remoteSettings.updateSettings(currentTime: Date()) localOverrideSettings = LocalOverrideSettings() sdkDefaultSettings = SDKDefaultSettings() sessionSettings = SessionsSettings( appInfo: appInfo, installations: MockInstallationsProtocol(), sdkDefaults: sdkDefaultSettings, localOverrides: localOverrideSettings, remoteSettings: remoteSettings ) generator = SessionGenerator(collectEvents: Sessions .shouldCollectEvents(settings: sessionSettings)) } func isValidSessionID(_ sessionID: String) -> Bool { if sessionID.count != 32 { assertionFailure("Session ID isn't 32 characters long") return false } if sessionID.contains("-") { assertionFailure("Session ID contains a dash") return false } if sessionID.lowercased().compare(sessionID) != ComparisonResult.orderedSame { assertionFailure("Session ID is not lowercase") return false } return true } // This test case isn't important behavior. When Crash and Perf integrate // with the Sessions SDK, we may want to move to a lazy solution where // sessionID can never be empty func test_sessionID_beforeGenerateReturnsNothing() throws { XCTAssertNil(generator.currentSession) } func test_generateNewSessionID_generatesValidID() throws { let sessionInfo = generator.generateNewSession() XCTAssert(isValidSessionID(sessionInfo.sessionId)) XCTAssert(isValidSessionID(sessionInfo.firstSessionId)) XCTAssertEqual(sessionInfo.firstSessionId, sessionInfo.sessionId) } /// Ensures that generating a Session ID multiple times results in the fist Session ID being set /// in the firstSessionId field func test_generateNewSessionID_rotatesPreviousID() throws { let firstSessionInfo = generator.generateNewSession() XCTAssert(isValidSessionID(firstSessionInfo.sessionId)) XCTAssert(isValidSessionID(firstSessionInfo.firstSessionId)) XCTAssertEqual(firstSessionInfo.firstSessionId, firstSessionInfo.sessionId) XCTAssertEqual(firstSessionInfo.sessionIndex, 0) let secondSessionInfo = generator.generateNewSession() XCTAssert(isValidSessionID(secondSessionInfo.sessionId)) XCTAssert(isValidSessionID(secondSessionInfo.firstSessionId)) // Ensure the new firstSessionId is equal to the first Session ID from earlier XCTAssertEqual(secondSessionInfo.firstSessionId, firstSessionInfo.sessionId) // Session Index should increase XCTAssertEqual(secondSessionInfo.sessionIndex, 1) // Do a third round just in case let thirdSessionInfo = generator.generateNewSession() XCTAssert(isValidSessionID(thirdSessionInfo.sessionId)) XCTAssert(isValidSessionID(thirdSessionInfo.firstSessionId)) // Ensure the new firstSessionId is equal to the first Session ID from earlier XCTAssertEqual(thirdSessionInfo.firstSessionId, firstSessionInfo.sessionId) // Session Index should increase XCTAssertEqual(thirdSessionInfo.sessionIndex, 2) } func test_sessionsNotSampled_AllEventsAllowed() throws { let someSettings: [String: Any] = [ "cache_duration": 10, "app_quality": [ "sampling_rate": 1.0, "session_timeout_seconds": 50, ], ] cache.removeCache() downloader = MockSettingsDownloader(successResponse: someSettings) remoteSettings = RemoteSettings(appInfo: appInfo, downloader: downloader, cache: cache) remoteSettings.updateSettings(currentTime: Date()) sessionSettings = SessionsSettings( appInfo: appInfo, installations: MockInstallationsProtocol(), sdkDefaults: sdkDefaultSettings, localOverrides: localOverrideSettings, remoteSettings: remoteSettings ) // Rebuild the SessionGenerator with the new settings generator = SessionGenerator(collectEvents: Sessions .shouldCollectEvents(settings: sessionSettings)) let sessionInfo = generator.generateNewSession() XCTAssertTrue(sessionInfo.shouldDispatchEvents) } func test_sessionsSampled_NoEventsAllowed() throws { let someSettings: [String: Any] = [ "cache_duration": 10, "app_quality": [ "sampling_rate": 0.0, "session_timeout_seconds": 50, ], ] cache.removeCache() downloader = MockSettingsDownloader(successResponse: someSettings) remoteSettings = RemoteSettings(appInfo: appInfo, downloader: downloader, cache: cache) remoteSettings.updateSettings(currentTime: Date()) sessionSettings = SessionsSettings( appInfo: appInfo, installations: MockInstallationsProtocol(), sdkDefaults: sdkDefaultSettings, localOverrides: localOverrideSettings, remoteSettings: remoteSettings ) // Rebuild the SessionGenerator with the new settings generator = SessionGenerator(collectEvents: Sessions .shouldCollectEvents(settings: sessionSettings)) let sessionInfo = generator.generateNewSession() XCTAssertFalse(sessionInfo.shouldDispatchEvents) } func test_sessionsSampling_persistsPerRun() throws { let mockSettings = MockSettingsProtocol() mockSettings.samplingRate = 0 // Rebuild the SessionGenerator with the new settings generator = SessionGenerator(collectEvents: Sessions .shouldCollectEvents(settings: mockSettings)) let sessionInfo = generator.generateNewSession() XCTAssertFalse(sessionInfo.shouldDispatchEvents) // Try again let sessionInfo2 = generator.generateNewSession() XCTAssertFalse(sessionInfo2.shouldDispatchEvents) // Start returning true from the calculator mockSettings.samplingRate = 1 // Try again after the calculator starts returning true. // It should still be false in the sessionInfo because the // sampling rate is calculated per-run let sessionInfo3 = generator.generateNewSession() XCTAssertFalse(sessionInfo3.shouldDispatchEvents) } }