mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-11 22:55:59 +05:45
* Rewrite SelfDocumentID as a property wrapper Make wrapped types conform to DocumentReferenceConvertible and use that protocol to implement the conversion. * Change DocumentReferenceConvertible to DocumentIDWrappable * Make ServerTimestampWrappable use static functions Use the same form as DocumentIDWrappable. * Update extension String: ServerTimestampWrappable * Use #if compiler, not #if swift The FirebaseFirestoreSwift module is declared as conforming to Swift 4.0 so #if swift is still false, even when compiling with Swift 5.1. Using #if compiler will see the actual compiler version. Property wrappers don't depend on runtime features, so this works. Also, allow Wrappable protocols to throw during conversions.
300 lines
9.8 KiB
Swift
300 lines
9.8 KiB
Swift
/*
|
|
* Copyright 2019 Google
|
|
*
|
|
* 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 FirebaseFirestore
|
|
import FirebaseFirestoreSwift
|
|
|
|
class CodableIntegrationTests: FSTIntegrationTestCase {
|
|
private enum WriteFlavor {
|
|
case docRef
|
|
case writeBatch
|
|
case transaction
|
|
}
|
|
|
|
private let allFlavors: [WriteFlavor] = [.docRef, .writeBatch, .transaction]
|
|
|
|
private func setData<T: Encodable>(from value: T,
|
|
forDocument doc: DocumentReference,
|
|
withFlavor flavor: WriteFlavor = .docRef,
|
|
merge: Bool? = nil,
|
|
mergeFields: [Any]? = nil) throws {
|
|
let completion = completionForExpectation(withName: "setData")
|
|
|
|
switch flavor {
|
|
case .docRef:
|
|
if let merge = merge {
|
|
try doc.setData(from: value, merge: merge, completion: completion)
|
|
} else if let mergeFields = mergeFields {
|
|
try doc.setData(from: value, mergeFields: mergeFields, completion: completion)
|
|
} else {
|
|
try doc.setData(from: value, completion: completion)
|
|
}
|
|
case .writeBatch:
|
|
if let merge = merge {
|
|
try doc.firestore.batch().setData(from: value, forDocument: doc, merge: merge).commit(completion: completion)
|
|
} else if let mergeFields = mergeFields {
|
|
try doc.firestore.batch().setData(from: value, forDocument: doc, mergeFields: mergeFields).commit(completion: completion)
|
|
} else {
|
|
try doc.firestore.batch().setData(from: value, forDocument: doc).commit(completion: completion)
|
|
}
|
|
case .transaction:
|
|
doc.firestore.runTransaction({ (transaction, errorPointer) -> Any? in
|
|
do {
|
|
if let merge = merge {
|
|
try transaction.setData(from: value, forDocument: doc, merge: merge)
|
|
} else if let mergeFields = mergeFields {
|
|
try transaction.setData(from: value, forDocument: doc, mergeFields: mergeFields)
|
|
} else {
|
|
try transaction.setData(from: value, forDocument: doc)
|
|
}
|
|
} catch {
|
|
XCTFail("setData with transaction failed.")
|
|
}
|
|
return nil
|
|
}) { object, error in
|
|
completion?(error)
|
|
}
|
|
}
|
|
|
|
awaitExpectations()
|
|
}
|
|
|
|
func testCodableRoundTrip() throws {
|
|
struct Model: Codable, Equatable {
|
|
var name: String
|
|
var age: Int32
|
|
var ts: Timestamp
|
|
var geoPoint: GeoPoint
|
|
var docRef: DocumentReference
|
|
}
|
|
let docToWrite = documentRef()
|
|
let model = Model(name: "test",
|
|
age: 42,
|
|
ts: Timestamp(seconds: 987_654_321, nanoseconds: 0),
|
|
geoPoint: GeoPoint(latitude: 45, longitude: 54),
|
|
docRef: docToWrite)
|
|
|
|
for flavor in allFlavors {
|
|
try setData(from: model, forDocument: docToWrite, withFlavor: flavor)
|
|
|
|
let readAfterWrite = try readDocument(forRef: docToWrite).data(as: Model.self)
|
|
|
|
XCTAssertEqual(readAfterWrite!, model, "Failed with flavor \(flavor)")
|
|
}
|
|
}
|
|
|
|
#if swift(>=5.1)
|
|
func testServerTimestamp() throws {
|
|
struct Model: Codable, Equatable {
|
|
var name: String
|
|
@ServerTimestamp var ts: Timestamp? = nil
|
|
}
|
|
let model = Model(name: "name")
|
|
let docToWrite = documentRef()
|
|
|
|
for flavor in allFlavors {
|
|
try setData(from: model, forDocument: docToWrite, withFlavor: flavor)
|
|
|
|
let decoded = try readDocument(forRef: docToWrite).data(as: Model.self)
|
|
|
|
XCTAssertNotNil(decoded?.ts, "Failed with flavor \(flavor)")
|
|
if let ts = decoded?.ts {
|
|
XCTAssertGreaterThan(ts.seconds, 1_500_000_000, "Failed with flavor \(flavor)")
|
|
} else {
|
|
XCTFail("Expect server timestamp is set, but getting .pending")
|
|
}
|
|
}
|
|
}
|
|
#endif // swift(>=5.1)
|
|
|
|
@available(swift, deprecated: 5.1)
|
|
func testSwift4ServerTimestamp() throws {
|
|
struct Model: Codable, Equatable {
|
|
var name: String
|
|
var ts: Swift4ServerTimestamp
|
|
}
|
|
let model = Model(name: "name", ts: .pending)
|
|
let docToWrite = documentRef()
|
|
|
|
for flavor in allFlavors {
|
|
try setData(from: model, forDocument: docToWrite, withFlavor: flavor)
|
|
|
|
let decoded = try readDocument(forRef: docToWrite).data(as: Model.self)
|
|
|
|
XCTAssertNotNil(decoded?.ts, "Failed with flavor \(flavor)")
|
|
switch decoded!.ts {
|
|
case let .resolved(ts):
|
|
XCTAssertGreaterThan(ts.seconds, 1_500_000_000, "Failed with flavor \(flavor)")
|
|
case .pending:
|
|
XCTFail("Expect server timestamp is set, but getting .pending")
|
|
}
|
|
}
|
|
}
|
|
|
|
func testFieldValue() throws {
|
|
struct Model: Encodable {
|
|
var name: String
|
|
var array: FieldValue
|
|
var intValue: FieldValue
|
|
}
|
|
let model = Model(
|
|
name: "name",
|
|
array: FieldValue.arrayUnion([1, 2, 3]),
|
|
intValue: FieldValue.increment(3 as Int64)
|
|
)
|
|
|
|
let docToWrite = documentRef()
|
|
|
|
for flavor in allFlavors {
|
|
try setData(from: model, forDocument: docToWrite, withFlavor: flavor)
|
|
|
|
let data = readDocument(forRef: docToWrite)
|
|
|
|
XCTAssertEqual(data["array"] as! [Int], [1, 2, 3], "Failed with flavor \(flavor)")
|
|
XCTAssertEqual(data["intValue"] as! Int, 3, "Failed with flavor \(flavor)")
|
|
}
|
|
}
|
|
|
|
#if swift(>=5.1)
|
|
func testExplicitNull() throws {
|
|
struct Model: Encodable {
|
|
var name: String
|
|
@ExplicitNull var explicitNull: String?
|
|
var optional: String?
|
|
}
|
|
let model = Model(
|
|
name: "name",
|
|
explicitNull: nil,
|
|
optional: nil
|
|
)
|
|
|
|
let docToWrite = documentRef()
|
|
|
|
for flavor in allFlavors {
|
|
try setData(from: model, forDocument: docToWrite, withFlavor: flavor)
|
|
|
|
let data = readDocument(forRef: docToWrite).data()
|
|
|
|
XCTAssertTrue(data!.keys.contains("explicitNull"), "Failed with flavor \(flavor)")
|
|
XCTAssertEqual(data!["explicitNull"] as! NSNull, NSNull(), "Failed with flavor \(flavor)")
|
|
XCTAssertFalse(data!.keys.contains("optional"), "Failed with flavor \(flavor)")
|
|
}
|
|
}
|
|
#endif // swift(>=5.1)
|
|
|
|
@available(swift, deprecated: 5.1)
|
|
func testSwift4ExplicitNull() throws {
|
|
struct Model: Encodable {
|
|
var name: String
|
|
var explicitNull: Swift4ExplicitNull<String>
|
|
var optional: String?
|
|
}
|
|
let model = Model(
|
|
name: "name",
|
|
explicitNull: .none,
|
|
optional: nil
|
|
)
|
|
|
|
let docToWrite = documentRef()
|
|
|
|
for flavor in allFlavors {
|
|
try setData(from: model, forDocument: docToWrite, withFlavor: flavor)
|
|
|
|
let data = readDocument(forRef: docToWrite).data()
|
|
|
|
XCTAssertTrue(data!.keys.contains("explicitNull"), "Failed with flavor \(flavor)")
|
|
XCTAssertEqual(data!["explicitNull"] as! NSNull, NSNull(), "Failed with flavor \(flavor)")
|
|
XCTAssertFalse(data!.keys.contains("optional"), "Failed with flavor \(flavor)")
|
|
}
|
|
}
|
|
|
|
#if swift(>=5.1)
|
|
func testSelfDocumentID() throws {
|
|
struct Model: Codable, Equatable {
|
|
var name: String
|
|
@DocumentID var docId: DocumentReference?
|
|
}
|
|
|
|
let docToWrite = documentRef()
|
|
let model = Model(
|
|
name: "name",
|
|
docId: nil
|
|
)
|
|
|
|
try setData(from: model, forDocument: docToWrite, withFlavor: .docRef)
|
|
let data = readDocument(forRef: docToWrite).data()
|
|
|
|
// "docId" is ignored during encoding
|
|
XCTAssertEqual(data! as! [String: String], ["name": "name"])
|
|
|
|
// Decoded result has "docId" auto-populated.
|
|
let decoded = try readDocument(forRef: docToWrite).data(as: Model.self)
|
|
XCTAssertEqual(decoded!, Model(name: "name", docId: docToWrite))
|
|
}
|
|
#endif // swift(>=5.1)
|
|
|
|
func testSetThenMerge() throws {
|
|
struct Model: Codable, Equatable {
|
|
var name: String? = nil
|
|
var age: Int32? = nil
|
|
var hobby: String? = nil
|
|
}
|
|
let docToWrite = documentRef()
|
|
let model = Model(name: "test",
|
|
age: 42, hobby: nil)
|
|
// 'name' will be skipped in merge because it's Optional.
|
|
let update = Model(name: nil, age: 43, hobby: "No")
|
|
|
|
for flavor in allFlavors {
|
|
try setData(from: model, forDocument: docToWrite, withFlavor: flavor)
|
|
try setData(from: update, forDocument: docToWrite, withFlavor: flavor, merge: true)
|
|
|
|
var readAfterUpdate = try readDocument(forRef: docToWrite).data(as: Model.self)
|
|
|
|
XCTAssertEqual(readAfterUpdate!, Model(name: "test",
|
|
age: 43, hobby: "No"), "Failed with flavor \(flavor)")
|
|
|
|
let newUpdate = Model(name: "xxxx", age: 10, hobby: "Play")
|
|
// Note 'name' is not updated.
|
|
try setData(from: newUpdate, forDocument: docToWrite, withFlavor: flavor, mergeFields: ["age", FieldPath(["hobby"])])
|
|
|
|
readAfterUpdate = try readDocument(forRef: docToWrite).data(as: Model.self)
|
|
XCTAssertEqual(readAfterUpdate!, Model(name: "test",
|
|
age: 10, hobby: "Play"), "Failed with flavor \(flavor)")
|
|
}
|
|
}
|
|
|
|
func testAddDocument() throws {
|
|
struct Model: Codable, Equatable {
|
|
var name: String
|
|
}
|
|
|
|
let collection = collectionRef()
|
|
let model = Model(name: "test")
|
|
|
|
let added = expectation(description: "Add document")
|
|
let docRef = try collection.addDocument(from: model) { error in
|
|
XCTAssertNil(error)
|
|
added.fulfill()
|
|
}
|
|
awaitExpectations()
|
|
|
|
let result = try readDocument(forRef: docRef).data(as: Model.self)
|
|
XCTAssertEqual(model, result)
|
|
}
|
|
}
|