diff --git a/CodableFirebaseTests/TestCodableFirestore.swift b/CodableFirebaseTests/TestCodableFirestore.swift index b367c37..93017ed 100644 --- a/CodableFirebaseTests/TestCodableFirestore.swift +++ b/CodableFirebaseTests/TestCodableFirestore.swift @@ -29,278 +29,6 @@ fileprivate struct Document: Codable, Equatable { } } -fileprivate struct EmptyStruct : Codable, Equatable { - static func ==(_ lhs: EmptyStruct, _ rhs: EmptyStruct) -> Bool { - return true - } -} - -fileprivate class EmptyClass : Codable, Equatable { - static func ==(_ lhs: EmptyClass, _ rhs: EmptyClass) -> Bool { - return true - } -} - -// MARK: - Single-Value Types -/// A simple on-off switch type that encodes as a single Bool value. -fileprivate enum Switch : Codable { - case off - case on - - init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - switch try container.decode(Bool.self) { - case false: self = .off - case true: self = .on - } - } - - func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - switch self { - case .off: try container.encode(false) - case .on: try container.encode(true) - } - } -} - -/// A simple timestamp type that encodes as a single Double value. -fileprivate struct Timestamp : Codable, Equatable { - let value: Double - - init(_ value: Double) { - self.value = value - } - - init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - value = try container.decode(Double.self) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(self.value) - } - - static func ==(_ lhs: Timestamp, _ rhs: Timestamp) -> Bool { - return lhs.value == rhs.value - } -} - -/// A simple referential counter type that encodes as a single Int value. -fileprivate final class Counter : Codable, Equatable { - var count: Int = 0 - - init() {} - - init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - count = try container.decode(Int.self) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(self.count) - } - - static func ==(_ lhs: Counter, _ rhs: Counter) -> Bool { - return lhs === rhs || lhs.count == rhs.count - } -} - -// MARK: - Structured Types -/// A simple address type that encodes as a dictionary of values. -fileprivate struct Address : Codable, Equatable { - let street: String - let city: String - let state: String - let zipCode: Int - let country: String - - init(street: String, city: String, state: String, zipCode: Int, country: String) { - self.street = street - self.city = city - self.state = state - self.zipCode = zipCode - self.country = country - } - - static func ==(_ lhs: Address, _ rhs: Address) -> Bool { - return lhs.street == rhs.street && - lhs.city == rhs.city && - lhs.state == rhs.state && - lhs.zipCode == rhs.zipCode && - lhs.country == rhs.country - } - - static var testValue: Address { - return Address(street: "1 Infinite Loop", - city: "Cupertino", - state: "CA", - zipCode: 95014, - country: "United States") - } -} - -/// A simple person class that encodes as a dictionary of values. -fileprivate class Person : Codable, Equatable { - let name: String - let email: String - let website: URL? - - init(name: String, email: String, website: URL? = nil) { - self.name = name - self.email = email - self.website = website - } - - private enum CodingKeys : String, CodingKey { - case name - case email - case website - } - - // FIXME: Remove when subclasses (Employee) are able to override synthesized conformance. - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - name = try container.decode(String.self, forKey: .name) - email = try container.decode(String.self, forKey: .email) - website = try container.decodeIfPresent(URL.self, forKey: .website) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(name, forKey: .name) - try container.encode(email, forKey: .email) - try container.encodeIfPresent(website, forKey: .website) - } - - func isEqual(_ other: Person) -> Bool { - return self.name == other.name && - self.email == other.email && - self.website == other.website - } - - static func ==(_ lhs: Person, _ rhs: Person) -> Bool { - return lhs.isEqual(rhs) - } - - class var testValue: Person { - return Person(name: "Johnny Appleseed", email: "appleseed@apple.com") - } -} - -/// A class which shares its encoder and decoder with its superclass. -fileprivate class Employee : Person { - let id: Int - - init(name: String, email: String, website: URL? = nil, id: Int) { - self.id = id - super.init(name: name, email: email, website: website) - } - - enum CodingKeys : String, CodingKey { - case id - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - id = try container.decode(Int.self, forKey: .id) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(id, forKey: .id) - try super.encode(to: encoder) - } - - override func isEqual(_ other: Person) -> Bool { - if let employee = other as? Employee { - guard self.id == employee.id else { return false } - } - - return super.isEqual(other) - } - - override class var testValue: Employee { - return Employee(name: "Johnny Appleseed", email: "appleseed@apple.com", id: 42) - } -} - -/// A simple company struct which encodes as a dictionary of nested values. -fileprivate struct Company : Codable, Equatable { - let address: Address - var employees: [Employee] - - init(address: Address, employees: [Employee]) { - self.address = address - self.employees = employees - } - - static func ==(_ lhs: Company, _ rhs: Company) -> Bool { - return lhs.address == rhs.address && lhs.employees == rhs.employees - } - - static var testValue: Company { - return Company(address: Address.testValue, employees: [Employee.testValue]) - } -} - -/// An enum type which decodes from Bool?. -fileprivate enum EnhancedBool : Codable { - case `true` - case `false` - case fileNotFound - - init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - if container.decodeNil() { - self = .fileNotFound - } else { - let value = try container.decode(Bool.self) - self = value ? .true : .false - } - } - - func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - switch self { - case .true: try container.encode(true) - case .false: try container.encode(false) - case .fileNotFound: try container.encodeNil() - } - } -} - -/// A type which encodes as a dictionary directly through a single value container. -fileprivate final class Mapping : Codable, Equatable { - let values: [String : URL] - - init(values: [String : URL]) { - self.values = values - } - - init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - values = try container.decode([String : URL].self) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(values) - } - - static func ==(_ lhs: Mapping, _ rhs: Mapping) -> Bool { - return lhs === rhs || lhs.values == rhs.values - } - - static var testValue: Mapping { - return Mapping(values: ["Apple": URL(string: "http://apple.com")!, - "localhost": URL(string: "http://127.0.0.1")!]) - } -} - /// Wraps a type T so that it can be encoded at the top level of a payload. struct TopLevelWrapper : Codable, Equatable where T : Codable, T : Equatable { enum CodingKeys : String, CodingKey { @@ -328,118 +56,6 @@ struct TopLevelWrapper : Codable, Equatable where T : Codable, T : Equatable } } -struct NestedContainersTestType : Encodable { - let testSuperEncoder: Bool - - init(testSuperEncoder: Bool = false) { - self.testSuperEncoder = testSuperEncoder - } - - enum TopLevelCodingKeys : Int, CodingKey { - case a - case b - case c - } - - enum IntermediateCodingKeys : Int, CodingKey { - case one - case two - } - - func encode(to encoder: Encoder) throws { - if self.testSuperEncoder { - var topLevelContainer = encoder.container(keyedBy: TopLevelCodingKeys.self) - expectEqualPaths(encoder.codingPath, [], "Top-level Encoder's codingPath changed.") - expectEqualPaths(topLevelContainer.codingPath, [], "New first-level keyed container has non-empty codingPath.") - - let superEncoder = topLevelContainer.superEncoder(forKey: .a) - expectEqualPaths(encoder.codingPath, [], "Top-level Encoder's codingPath changed.") - expectEqualPaths(topLevelContainer.codingPath, [], "First-level keyed container's codingPath changed.") - expectEqualPaths(superEncoder.codingPath, [TopLevelCodingKeys.a], "New superEncoder had unexpected codingPath.") - _testNestedContainers(in: superEncoder, baseCodingPath: [TopLevelCodingKeys.a]) - } else { - _testNestedContainers(in: encoder, baseCodingPath: []) - } - } - - func _testNestedContainers(in encoder: Encoder, baseCodingPath: [CodingKey]) { - expectEqualPaths(encoder.codingPath, baseCodingPath, "New encoder has non-empty codingPath.") - - // codingPath should not change upon fetching a non-nested container. - var firstLevelContainer = encoder.container(keyedBy: TopLevelCodingKeys.self) - expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") - expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "New first-level keyed container has non-empty codingPath.") - - // Nested Keyed Container - do { - // Nested container for key should have a new key pushed on. - var secondLevelContainer = firstLevelContainer.nestedContainer(keyedBy: IntermediateCodingKeys.self, forKey: .a) - expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") - expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") - expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "New second-level keyed container had unexpected codingPath.") - - // Inserting a keyed container should not change existing coding paths. - let thirdLevelContainerKeyed = secondLevelContainer.nestedContainer(keyedBy: IntermediateCodingKeys.self, forKey: .one) - expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") - expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") - expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "Second-level keyed container's codingPath changed.") - expectEqualPaths(thirdLevelContainerKeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.a, IntermediateCodingKeys.one], "New third-level keyed container had unexpected codingPath.") - - // Inserting an unkeyed container should not change existing coding paths. - let thirdLevelContainerUnkeyed = secondLevelContainer.nestedUnkeyedContainer(forKey: .two) - expectEqualPaths(encoder.codingPath, baseCodingPath + [], "Top-level Encoder's codingPath changed.") - expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath + [], "First-level keyed container's codingPath changed.") - expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "Second-level keyed container's codingPath changed.") - expectEqualPaths(thirdLevelContainerUnkeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.a, IntermediateCodingKeys.two], "New third-level unkeyed container had unexpected codingPath.") - } - - // Nested Unkeyed Container - do { - // Nested container for key should have a new key pushed on. - var secondLevelContainer = firstLevelContainer.nestedUnkeyedContainer(forKey: .b) - expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") - expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") - expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.b], "New second-level keyed container had unexpected codingPath.") - - // Appending a keyed container should not change existing coding paths. - let thirdLevelContainerKeyed = secondLevelContainer.nestedContainer(keyedBy: IntermediateCodingKeys.self) - expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") - expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") - expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.b], "Second-level unkeyed container's codingPath changed.") - expectEqualPaths(thirdLevelContainerKeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.b, _TestKey(index: 0)], "New third-level keyed container had unexpected codingPath.") - - // Appending an unkeyed container should not change existing coding paths. - let thirdLevelContainerUnkeyed = secondLevelContainer.nestedUnkeyedContainer() - expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") - expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") - expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.b], "Second-level unkeyed container's codingPath changed.") - expectEqualPaths(thirdLevelContainerUnkeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.b, _TestKey(index: 1)], "New third-level unkeyed container had unexpected codingPath.") - } - } -} - -/// A key type which can take on any string or integer value. -/// This needs to mirror _PlistKey. -fileprivate struct _TestKey : CodingKey { - var stringValue: String - var intValue: Int? - - init?(stringValue: String) { - self.stringValue = stringValue - self.intValue = nil - } - - init?(intValue: Int) { - self.stringValue = "\(intValue)" - self.intValue = intValue - } - - init(index: Int) { - self.stringValue = "Index \(index)" - self.intValue = index - } -} - class TestCodableFirestore: XCTestCase { func testFirebaseEncoder() { @@ -466,116 +82,6 @@ class TestCodableFirestore: XCTestCase { XCTAssertEqual(try? FirestoreDecoder().decode(Document.self, from: dict) , model) } - func testEncodingTopLevelEmptyStruct() { - _testRoundTrip(of: EmptyStruct(), expected: [:]) - } - - func testEncodingTopLevelEmptyClass() { - _testRoundTrip(of: EmptyClass(), expected: [:]) - } - - // MARK: - Encoding Top-Level Single-Value Types - func testEncodingTopLevelSingleValueEnum() { - let s1 = Switch.off - _testEncodeFailure(of: s1) - _testRoundTrip(of: TopLevelWrapper(s1)) - - let s2 = Switch.on - _testEncodeFailure(of: s2) - _testRoundTrip(of: TopLevelWrapper(s2)) - } - - func testEncodingTopLevelSingleValueStruct() { - let t = Timestamp(3141592653) - _testEncodeFailure(of: t) - _testRoundTrip(of: TopLevelWrapper(t)) - } - - func testEncodingTopLevelSingleValueClass() { - let c = Counter() - _testEncodeFailure(of: c) - _testRoundTrip(of: TopLevelWrapper(c)) - } - - // MARK: - Encoding Top-Level Structured Types - func testEncodingTopLevelStructuredStruct() { - // Address is a struct type with multiple fields. - _testRoundTrip(of: Address.testValue) - } - - func testEncodingTopLevelStructuredClass() { - // Person is a class with multiple fields. - _testRoundTrip(of: Person.testValue) - } - - func testEncodingTopLevelStructuredSingleClass() { - // Mapping is a class which encodes as a dictionary through a single value container. - _testRoundTrip(of: Mapping.testValue) - } - - func testEncodingTopLevelDeepStructuredType() { - // Company is a type with fields which are Codable themselves. - _testRoundTrip(of: Company.testValue) - } - - func testEncodingClassWhichSharesEncoderWithSuper() { - // Employee is a type which shares its encoder & decoder with its superclass, Person. - _testRoundTrip(of: Employee.testValue) - } - - func testEncodingTopLevelNullableType() { - // EnhancedBool is a type which encodes either as a Bool or as nil. - _testEncodeFailure(of: EnhancedBool.true) - _testEncodeFailure(of: EnhancedBool.false) - _testEncodeFailure(of: EnhancedBool.fileNotFound) - - _testRoundTrip(of: TopLevelWrapper(EnhancedBool.true)) - _testRoundTrip(of: TopLevelWrapper(EnhancedBool.false)) - _testRoundTrip(of: TopLevelWrapper(EnhancedBool.fileNotFound)) - } - - func testEncodingTopLevelNumericTypes() { - _testRoundTrip(of: TopLevelWrapper(3 as Int)) - _testRoundTrip(of: TopLevelWrapper(3 as Int8)) - _testRoundTrip(of: TopLevelWrapper(3 as Int16)) - _testRoundTrip(of: TopLevelWrapper(3 as Int32)) - _testRoundTrip(of: TopLevelWrapper(3 as Int64)) - _testRoundTrip(of: TopLevelWrapper(3 as UInt)) - _testRoundTrip(of: TopLevelWrapper(3 as UInt8)) - _testRoundTrip(of: TopLevelWrapper(3 as UInt16)) - _testRoundTrip(of: TopLevelWrapper(3 as UInt32)) - _testRoundTrip(of: TopLevelWrapper(3 as UInt64)) - _testRoundTrip(of: TopLevelWrapper(3 as Float)) - _testRoundTrip(of: TopLevelWrapper(3 as Double)) - } - - func testTypeCoercion() { - _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int].self) - _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int8].self) - _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int16].self) - _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int32].self) - _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int64].self) - _testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt].self) - _testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt8].self) - _testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt16].self) - _testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt32].self) - _testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt64].self) - _testRoundTripTypeCoercionFailure(of: [false, true], as: [Float].self) - _testRoundTripTypeCoercionFailure(of: [false, true], as: [Double].self) - _testRoundTripTypeCoercionFailure(of: [0, 1] as [Int], as: [Bool].self) - _testRoundTripTypeCoercionFailure(of: [0, 1] as [Int8], as: [Bool].self) - _testRoundTripTypeCoercionFailure(of: [0, 1] as [Int16], as: [Bool].self) - _testRoundTripTypeCoercionFailure(of: [0, 1] as [Int32], as: [Bool].self) - _testRoundTripTypeCoercionFailure(of: [0, 1] as [Int64], as: [Bool].self) - _testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt], as: [Bool].self) - _testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt8], as: [Bool].self) - _testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt16], as: [Bool].self) - _testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt32], as: [Bool].self) - _testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt64], as: [Bool].self) - _testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Float], as: [Bool].self) - _testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Double], as: [Bool].self) - } - // MARK: - Encoder Features func testNestedContainerCodingPaths() { do { @@ -610,14 +116,6 @@ class TestCodableFirestore: XCTestCase { } catch {} } - private func _testRoundTripTypeCoercionFailure(of value: T, as type: U.Type) where T : Codable, U : Codable { - do { - let data = try FirestoreEncoder().encode(value) - let _ = try FirestoreDecoder().decode(U.self, from: data) - XCTFail("Coercion from \(T.self) to \(U.self) was expected to fail.") - } catch {} - } - private func _testRoundTrip(of value: T, expected dict: [String: Any]? = nil) where T : Codable, T : Equatable { var payload: [String: Any]! = nil do {