glean-core/ios/GleanTests/TestUtils.swift (121 lines of code) (raw):

/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ @testable import Glean import Gzip import OHHTTPStubs import OHHTTPStubsSwift import XCTest /// Stub out receiving a request on Glean's default Telemetry endpoint. /// /// When receiving a request, it extracts the ping type from the URL /// according to the endpoint URL format. /// /// It assumes the request body is a JSON document and tries to decode it. /// If necessary, it decompresses the request body first. /// /// - parameters /// * callback: A callback to validate the incoming request. /// It receives a `pingType` and the ping's JSON-decoded `payload`. func stubServerReceive(callback: @escaping (String, [String: Any]?) -> Void) { let host = URL(string: Configuration.Constants.defaultTelemetryEndpoint)!.host! stub(condition: isHost(host)) { data in let request = data as NSURLRequest let url = request.url! let parts = url.absoluteString.split(separator: "/") let pingType = String(parts[4]) var body = request.ohhttpStubs_HTTPBody()! if request.value(forHTTPHeaderField: "Content-Encoding") == "gzip" { body = try! body.gunzipped() } let payload = try! JSONSerialization.jsonObject(with: body, options: []) as? [String: Any] callback(pingType, payload) return HTTPStubsResponse( data: Data("OK".utf8), statusCode: 200, headers: nil ) } } /// Resets Glean, and discards any pings sent during `initialize()` /// that might interfere with what is being tested in the specific unit test /// /// This also prevents outgoing network requests during unit tests while /// still allowing us to use the default telemetry endpoint. func resetGleanDiscardingInitialPings(testCase: XCTestCase, tag: String, clearStores: Bool = true, configuration: Configuration = Configuration()) { let expectation = testCase.expectation(description: "\(tag): Ping Received") // We are using OHHTTPStubs combined with an XCTestExpectation in order to capture // outgoing network requests and prevent actual requests being made from tests. // We wait for 2 pings, in this order: // // 1. baseline ping with reason "dirty-startup" // 2. baseline ping with reason "active" // // Once we received the "active" baseline ping we're good to go. // All subsequent pings should be from the test itself. stubServerReceive { pingType, json in if pingType != "baseline" { return } let pingInfo = json?["ping_info"] as? [String: Any] let reason = pingInfo?["reason"] as? String if reason != "active" { return } // Fulfill test's expectation once we parsed the incoming data. DispatchQueue.main.async { // Let the response get processed before we mark the expectation fulfilled expectation.fulfill() } } // We may recieve more than one ping, using this function means we don't care about any of them expectation.assertForOverFulfill = false // Force NO overdue metrics ping. We only expect baseline pings. let mps = MetricsPingScheduler(true) mps.updateSentDate(Date()) _ = Ping<NoReasonCodes>( name: "store1", includeClientId: true, sendIfEmpty: false, preciseTimestamps: true, includeInfoSections: true, enabled: true, schedulesPings: [], reasonCodes: [], followsCollectionEnabled: true, uploaderCapabilities: [] ) _ = Ping<NoReasonCodes>( name: "store2", includeClientId: true, sendIfEmpty: false, preciseTimestamps: true, includeInfoSections: true, enabled: true, schedulesPings: [], reasonCodes: [], followsCollectionEnabled: true, uploaderCapabilities: [] ) _ = Ping<NoReasonCodes>( name: "store3", includeClientId: true, sendIfEmpty: false, preciseTimestamps: true, includeInfoSections: true, enabled: true, schedulesPings: [], reasonCodes: [], followsCollectionEnabled: true, uploaderCapabilities: [] ) Glean.shared.resetGlean(configuration: configuration, clearStores: clearStores) testCase.waitForExpectations(timeout: 5.0) { error in XCTAssertNil(error, "Test timed out waiting for upload: \(error!)") } // In case this isn't the first test, there might be pending operations. // We wait for them to finish, so they don't clutter the next tests. Dispatchers.shared.serialOperationQueue.waitUntilAllOperationsAreFinished() } func tearDownStubs() { HTTPStubs.removeAllStubs() } /// Stringify a JSON object and if unable to, just return an empty string. func JSONStringify(_ json: Any) -> String { do { let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted) if let string = String(data: data, encoding: String.Encoding.utf8) { return string } } catch { print(error) } return "" } func stubBuildInfo(_ dateString: String? = nil) -> BuildInfo { let buildDate: DateComponents if let dateString = dateString { let date = Date.fromISO8601String(dateString: dateString, precision: .second)! buildDate = Calendar.current.dateComponents(in: TimeZone(abbreviation: "UTC")!, from: date) } else { buildDate = DateComponents( calendar: Calendar.current, timeZone: TimeZone(abbreviation: "UTC"), year: 2019, month: 10, day: 23, hour: 12, minute: 52, second: 8 ) } return BuildInfo(buildDate: buildDate) }