// Copyright 2021 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 Foundation import XCTest import FirebaseCoreInternal @objc(FIRHeartbeatLoggingTestUtils) @objcMembers public class HeartbeatLoggingTestUtils: NSObject { /// This should mirror the `Constants` enum in the `HeartbeatLogging` module. /// See `HeartbeatLogging/Sources/StorageFactory.swift`. public enum Constants { /// The name of the file system directory where heartbeat data is stored. public static let heartbeatFileStorageDirectoryPath = "google-heartbeat-storage" /// The name of the user defaults suite where heartbeat data is stored. public static let heartbeatUserDefaultsSuiteName = "com.google.heartbeat.storage" } public static var dateFormatter: DateFormatter { HeartbeatsPayload.dateFormatter } public static var emptyHeartbeatsPayload: _ObjC_HeartbeatsPayload { let literalData = """ { "version": 2, "heartbeats": [] } """ .data(using: .utf8)! let decoder = JSONDecoder() decoder.dateDecodingStrategy = .formatted(HeartbeatsPayload.dateFormatter) let heartbeatsPayload = try! decoder.decode(HeartbeatsPayload.self, from: literalData) return _ObjC_HeartbeatsPayload(heartbeatsPayload) } public static var nonEmptyHeartbeatsPayload: _ObjC_HeartbeatsPayload { let literalData = """ { "version": 2, "heartbeats": [ { "agent": "dummy_agent_1", "dates": ["2021-11-01", "2021-11-02"] }, { "agent": "dummy_agent_2", "dates": ["2021-11-03"] } ] } """ .data(using: .utf8)! let decoder = JSONDecoder() decoder.dateDecodingStrategy = .formatted(HeartbeatsPayload.dateFormatter) let heartbeatsPayload = try! decoder.decode(HeartbeatsPayload.self, from: literalData) return _ObjC_HeartbeatsPayload(heartbeatsPayload) } @objc(assertEncodedPayloadString:isEqualToLiteralString:withError:) public static func assertEqualPayloadStrings(_ encoded: String, _ literal: String) throws { var encodedData = try XCTUnwrap(Data(base64URLEncoded: encoded)) if encodedData.count > 0 { encodedData = try! encodedData.unzipped() } let literalData = try XCTUnwrap(literal.data(using: .utf8)) let decoder = JSONDecoder() decoder.dateDecodingStrategy = .formatted(HeartbeatsPayload.dateFormatter) let payloadFromEncoded = try? decoder.decode(HeartbeatsPayload.self, from: encodedData) let payloadFromLiteral = try? decoder.decode(HeartbeatsPayload.self, from: literalData) let encoder = JSONEncoder() encoder.dateEncodingStrategy = .formatted(HeartbeatsPayload.dateFormatter) encoder.outputFormatting = .prettyPrinted let payloadDataFromEncoded = try XCTUnwrap(encoder.encode(payloadFromEncoded)) let payloadDataFromLiteral = try XCTUnwrap(encoder.encode(payloadFromLiteral)) XCTAssertEqual( payloadFromEncoded, payloadFromLiteral, """ Mismatched payloads! Payload 1: \(String(data: payloadDataFromEncoded, encoding: .utf8) ?? "") Payload 2: \(String(data: payloadDataFromLiteral, encoding: .utf8) ?? "") """ ) } /// Removes all underlying storage containers used by the module. /// - Throws: An error if the storage container could not be removed. public static func removeUnderlyingHeartbeatStorageContainers() throws { #if os(tvOS) UserDefaults().removePersistentDomain(forName: Constants.heartbeatUserDefaultsSuiteName) #else let applicationSupportDirectory = try XCTUnwrap( FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first ) let heartbeatsDirectoryURL = applicationSupportDirectory .appendingPathComponent( Constants.heartbeatFileStorageDirectoryPath, isDirectory: true ) do { try FileManager.default.removeItem(at: heartbeatsDirectoryURL) } catch CocoaError.fileNoSuchFile { // Do nothing. } catch { throw error } #endif // os(tvOS) } }