Skip to content

Commit 97599fd

Browse files
committed
add tests
1 parent f1c025d commit 97599fd

File tree

4 files changed

+190
-12
lines changed

4 files changed

+190
-12
lines changed

Firestore/Swift/Source/Helper/PipelineHelper.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ enum Helper {
2424
return map(value as! [String: Sendable?])
2525
} else if value is [Sendable?] {
2626
return array(value as! [Sendable?])
27+
} else if value is TimeUnit {
28+
return Constant((value as! TimeUnit).rawValue)
2729
} else {
2830
return Constant(value)
2931
}
@@ -79,4 +81,30 @@ enum Helper {
7981
return Constant(value).bridge
8082
}
8183
}
84+
85+
static func convertObjCToSwift(_ objValue: Sendable) -> Sendable? {
86+
switch objValue {
87+
case is NSNumber, is NSString:
88+
return objValue
89+
90+
case is NSNull:
91+
return nil
92+
93+
case let data as NSData:
94+
return [UInt8](data)
95+
96+
case let data as NSArray:
97+
return data.map{ convertObjCToSwift($0) }
98+
99+
case let data as NSDictionary:
100+
var swiftDict = [String: Sendable?]()
101+
for (key, value) in data {
102+
swiftDict[key as! String] = convertObjCToSwift(value)
103+
}
104+
return swiftDict
105+
106+
default:
107+
return objValue
108+
}
109+
}
82110
}

Firestore/Swift/Source/SwiftAPI/Pipeline/PipelineResult.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public struct PipelineResult: @unchecked Sendable {
2727
self.bridge = bridge
2828
ref = self.bridge.reference
2929
id = self.bridge.documentID
30-
data = self.bridge.data()
30+
data = self.bridge.data().mapValues{ Helper.convertObjCToSwift($0)}
3131
createTime = self.bridge.create_time
3232
updateTime = self.bridge.update_time
3333
}
@@ -51,20 +51,20 @@ public struct PipelineResult: @unchecked Sendable {
5151
/// - Parameter fieldPath: The field path (e.g., "foo" or "foo.bar").
5252
/// - Returns: The data at the specified field location or `nil` if no such field exists.
5353
public func get(_ fieldName: String) -> Sendable? {
54-
return bridge.get(fieldName)
54+
return Helper.convertObjCToSwift(bridge.get(fieldName))
5555
}
5656

5757
/// Retrieves the field specified by `fieldPath`.
5858
/// - Parameter fieldPath: The field path (e.g., "foo" or "foo.bar").
5959
/// - Returns: The data at the specified field location or `nil` if no such field exists.
6060
public func get(_ fieldPath: FieldPath) -> Sendable? {
61-
return bridge.get(fieldPath)
61+
return Helper.convertObjCToSwift(bridge.get(fieldPath))
6262
}
6363

6464
/// Retrieves the field specified by `fieldPath`.
6565
/// - Parameter fieldPath: The field path (e.g., "foo" or "foo.bar").
6666
/// - Returns: The data at the specified field location or `nil` if no such field exists.
6767
public func get(_ field: Field) -> Sendable? {
68-
return bridge.get(field.fieldName)
68+
return Helper.convertObjCToSwift(bridge.get(field.fieldName))
6969
}
7070
}

Firestore/Swift/Tests/Integration/PipelineTests.swift

Lines changed: 152 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,45 @@ class PipelineIntegrationTests: FSTIntegrationTestCase {
586586

587587
TestHelper.compare(pipelineResult: snapshot.results.first!, expected: expectedResultsMap)
588588
}
589+
590+
591+
func testFailed() async throws {
592+
let db = firestore()
593+
let randomCol = collectionRef() // Ensure a unique collection for the test
594+
595+
// Add a dummy document to the collection.
596+
// A pipeline query with .select against an empty collection might not behave as expected.
597+
try await randomCol.document("dummyDoc").setData(["field": "value"])
598+
599+
let constantsFirst: [Selectable] = [
600+
//Constant([1, 2, 3, 4, 5, 6, 7, 0] as [UInt8]).as("bytes"),
601+
Constant([1, 2, 3]).as("arrayValue"), // Treated as an array of numbers
602+
]
603+
604+
// let constantsSecond: [Selectable] = [
605+
// ArrayExpression([
606+
// [11, 22, 33] as [UInt8]
607+
// ]).as("array")
608+
// ]
609+
610+
let expectedResultsMap: [String: Sendable?] = [
611+
// "bytes": [1, 2, 3, 4, 5, 6, 7, 0] as [UInt8],
612+
// "array": [
613+
// [11, 22, 33] as [UInt8]
614+
// ],
615+
"arrayValue": [1, 2, 3]
616+
]
617+
618+
let pipeline = db.pipeline()
619+
.collection(randomCol.path)
620+
.limit(1)
621+
.select(
622+
constantsFirst
623+
)
624+
let snapshot = try await pipeline.execute()
625+
626+
TestHelper.compare(pipelineResult: snapshot.results.first!, expected: expectedResultsMap)
627+
}
589628

590629
func testAcceptsAndReturnsNil() async throws {
591630
let db = firestore()
@@ -595,8 +634,6 @@ class PipelineIntegrationTests: FSTIntegrationTestCase {
595634
// A pipeline query with .select against an empty collection might not behave as expected.
596635
try await randomCol.document("dummyDoc").setData(["field": "value"])
597636

598-
let refDate = Date(timeIntervalSince1970: 1_678_886_400)
599-
600637
let constantsFirst: [Selectable] = [
601638
Constant.nil.as("nil"),
602639
]
@@ -2595,4 +2632,117 @@ class PipelineIntegrationTests: FSTIntegrationTestCase {
25952632
XCTFail("No document retrieved for testSupportsTimestampConversions")
25962633
}
25972634
}
2635+
2636+
func testSupportsTimestampMath() async throws {
2637+
let db = firestore()
2638+
let randomCol = collectionRef()
2639+
try await randomCol.document("dummyDoc").setData(["field": "value"])
2640+
2641+
let initialTimestamp = Timestamp(seconds: 1_741_380_235, nanoseconds: 0)
2642+
2643+
let pipeline = db.pipeline()
2644+
.collection(randomCol.path)
2645+
.limit(1)
2646+
.select(
2647+
Constant(initialTimestamp).as("timestamp")
2648+
)
2649+
.select(
2650+
Field("timestamp").timestampAdd(.day, 10).as("plus10days"),
2651+
Field("timestamp").timestampAdd(.hour, 10).as("plus10hours"),
2652+
Field("timestamp").timestampAdd(.minute, 10).as("plus10minutes"),
2653+
Field("timestamp").timestampAdd(.second, 10).as("plus10seconds"),
2654+
Field("timestamp").timestampAdd(.microsecond, 10).as("plus10micros"),
2655+
Field("timestamp").timestampAdd(.millisecond, 10).as("plus10millis"),
2656+
Field("timestamp").timestampSub(.day, 10).as("minus10days"),
2657+
Field("timestamp").timestampSub(.hour, 10).as("minus10hours"),
2658+
Field("timestamp").timestampSub(.minute, 10).as("minus10minutes"),
2659+
Field("timestamp").timestampSub(.second, 10).as("minus10seconds"),
2660+
Field("timestamp").timestampSub(.microsecond, 10).as("minus10micros"),
2661+
Field("timestamp").timestampSub(.millisecond, 10).as("minus10millis")
2662+
)
2663+
2664+
let snapshot = try await pipeline.execute()
2665+
2666+
let expectedResults: [String: Timestamp] = [
2667+
"plus10days": Timestamp(seconds: 1_742_244_235, nanoseconds: 0),
2668+
"plus10hours": Timestamp(seconds: 1_741_416_235, nanoseconds: 0),
2669+
"plus10minutes": Timestamp(seconds: 1_741_380_835, nanoseconds: 0),
2670+
"plus10seconds": Timestamp(seconds: 1_741_380_245, nanoseconds: 0),
2671+
"plus10micros": Timestamp(seconds: 1_741_380_235, nanoseconds: 10_000),
2672+
"plus10millis": Timestamp(seconds: 1_741_380_235, nanoseconds: 10_000_000),
2673+
"minus10days": Timestamp(seconds: 1_740_516_235, nanoseconds: 0),
2674+
"minus10hours": Timestamp(seconds: 1_741_344_235, nanoseconds: 0),
2675+
"minus10minutes": Timestamp(seconds: 1_741_379_635, nanoseconds: 0),
2676+
"minus10seconds": Timestamp(seconds: 1_741_380_225, nanoseconds: 0),
2677+
"minus10micros": Timestamp(seconds: 1_741_380_234, nanoseconds: 999_990_000),
2678+
"minus10millis": Timestamp(seconds: 1_741_380_234, nanoseconds: 990_000_000)
2679+
]
2680+
2681+
XCTAssertEqual(snapshot.results.count, 1, "Should retrieve one document")
2682+
if let resultDoc = snapshot.results.first {
2683+
TestHelper.compare(pipelineResult: resultDoc, expected: expectedResults)
2684+
} else {
2685+
XCTFail("No document retrieved for timestamp math test")
2686+
}
2687+
}
2688+
2689+
func testSupportsByteLength() async throws {
2690+
let db = firestore()
2691+
let randomCol = collectionRef()
2692+
try await randomCol.document("dummyDoc").setData(["field": "value"])
2693+
2694+
let bytes : [UInt8] = [1, 2, 3, 4, 5, 6, 7, 0]
2695+
2696+
let pipeline = db.pipeline()
2697+
.collection(randomCol.path)
2698+
.limit(1)
2699+
.select(
2700+
Constant(bytes).as("bytes")
2701+
)
2702+
.select(
2703+
Field("bytes").byteLength().as("byteLength")
2704+
)
2705+
2706+
let snapshot = try await pipeline.execute()
2707+
2708+
let expectedResults: [String: Sendable] = [
2709+
"byteLength": 8
2710+
]
2711+
2712+
XCTAssertEqual(snapshot.results.count, 1, "Should retrieve one document")
2713+
if let resultDoc = snapshot.results.first {
2714+
TestHelper.compare(pipelineResult: resultDoc, expected: expectedResults.mapValues { $0 as Sendable })
2715+
} else {
2716+
XCTFail("No document retrieved for byte length test")
2717+
}
2718+
}
2719+
2720+
func testSupportsNot() async throws {
2721+
let db = firestore()
2722+
let randomCol = collectionRef()
2723+
try await randomCol.document("dummyDoc").setData(["field": "value"])
2724+
2725+
let pipeline = db.pipeline()
2726+
.collection(randomCol.path)
2727+
.limit(1)
2728+
.select(Constant(true).as("trueField"))
2729+
.select(
2730+
Field("trueField"),
2731+
(!(Field("trueField").eq(true))).as("falseField")
2732+
)
2733+
2734+
let snapshot = try await pipeline.execute()
2735+
2736+
let expectedResults: [String: Bool] = [
2737+
"trueField": true,
2738+
"falseField": false
2739+
]
2740+
2741+
XCTAssertEqual(snapshot.results.count, 1, "Should retrieve one document")
2742+
if let resultDoc = snapshot.results.first {
2743+
TestHelper.compare(pipelineResult: resultDoc, expected: expectedResults)
2744+
} else {
2745+
XCTFail("No document retrieved for not operator test")
2746+
}
2747+
}
25982748
}

Firestore/Swift/Tests/TestHelper/TestHelper.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,10 +163,10 @@ public enum TestHelper {
163163
guard let value2 = dict2[key], areEqual(value1, value2) else {
164164
XCTFail("""
165165
Dictionary value mismatch for key: '\(key)'
166-
Expected value: '\(String(describing: value1))' (from dict1)
167-
Actual value: '\(String(describing: dict2[key]))' (from dict2)
168-
Full dict1: \(String(describing: dict1))
169-
Full dict2: \(String(describing: dict2))
166+
Actual value: '\(String(describing: value1))' (from dict1)
167+
Expected value: '\(String(describing: dict2[key]))' (from dict2)
168+
Full actual value: \(String(describing: dict1))
169+
Full expected value: \(String(describing: dict2))
170170
""")
171171
return false
172172
}
@@ -182,8 +182,8 @@ public enum TestHelper {
182182
if !areEqual(value1, value2) {
183183
XCTFail("""
184184
Array value mismatch.
185-
Expected array value: '\(String(describing: value1))'
186-
Actual array value: '\(String(describing: value2))'
185+
Actual array value: '\(String(describing: value1))'
186+
Expected array value: '\(String(describing: value2))'
187187
""")
188188
return false
189189
}

0 commit comments

Comments
 (0)