aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firestore/Example
diff options
context:
space:
mode:
Diffstat (limited to 'Firestore/Example')
-rw-r--r--Firestore/Example/Firestore.xcodeproj/project.pbxproj475
-rw-r--r--Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_FuzzTests_iOS.xcscheme57
-rw-r--r--Firestore/Example/FuzzTests/FSTFuzzTestsPrincipal.mm74
-rw-r--r--Firestore/Example/FuzzTests/Firestore_FuzzTests_iOS-Info.plist24
-rw-r--r--Firestore/Example/Podfile9
-rw-r--r--Firestore/Example/ProtobufCpp.podspec74
-rw-r--r--Firestore/Example/Tests/Core/FSTQueryListenerTests.mm14
-rw-r--r--Firestore/Example/Tests/Core/FSTViewTests.mm46
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm82
-rw-r--r--Firestore/Example/Tests/Integration/FSTDatastoreTests.mm5
-rw-r--r--Firestore/Example/Tests/Integration/FSTStreamTests.mm73
-rw-r--r--Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm8
-rw-r--r--Firestore/Example/Tests/Local/FSTLevelDBTransactionTests.mm2
-rw-r--r--Firestore/Example/Tests/Local/FSTLocalStoreTests.mm32
-rw-r--r--Firestore/Example/Tests/Remote/FSTDatastoreTests.mm6
-rw-r--r--Firestore/Example/Tests/Remote/FSTRemoteEventTests.mm906
-rw-r--r--Firestore/Example/Tests/SpecTests/FSTSpecTests.mm13
-rw-r--r--Firestore/Example/Tests/SpecTests/json/existence_filter_spec_test.json780
-rw-r--r--Firestore/Example/Tests/SpecTests/json/limbo_spec_test.json411
-rw-r--r--Firestore/Example/Tests/SpecTests/json/limit_spec_test.json20
-rw-r--r--Firestore/Example/Tests/SpecTests/json/listen_spec_test.json1075
-rw-r--r--Firestore/Example/Tests/Util/FSTHelpers.h54
-rw-r--r--Firestore/Example/Tests/Util/FSTHelpers.mm135
23 files changed, 3932 insertions, 443 deletions
diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj
index ca8b598..68b3a55 100644
--- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj
+++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj
@@ -24,7 +24,9 @@
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
+ 0535C1B65DADAE1CE47FA3CA /* string_format_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9CFD366B783AE27B9E79EE7A /* string_format_apple_test.mm */; };
132E3E53179DE287D875F3F2 /* FSTLevelDBTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 132E36BB104830BD806351AC /* FSTLevelDBTransactionTests.mm */; };
+ 1CAA9012B25F975D445D5978 /* strerror_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 358C3B5FE573B1D60A4F7592 /* strerror_test.cc */; };
3B843E4C1F3A182900548890 /* remote_store_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 3B843E4A1F3930A400548890 /* remote_store_spec_test.json */; };
54131E9720ADE679001DF3FF /* string_format_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54131E9620ADE678001DF3FF /* string_format_test.cc */; };
54511E8E209805F8005BD28F /* hashing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54511E8D209805F8005BD28F /* hashing_test.cc */; };
@@ -130,6 +132,7 @@
54DA12AE1F315EE100DD57A1 /* resume_token_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A41F315EE100DD57A1 /* resume_token_spec_test.json */; };
54DA12AF1F315EE100DD57A1 /* write_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A51F315EE100DD57A1 /* write_spec_test.json */; };
54EB764D202277B30088B8F3 /* array_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54EB764C202277B30088B8F3 /* array_sorted_map_test.cc */; };
+ 5A080105CCBFDB6BF3F3772D /* path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 403DBF6EFB541DFD01582AA3 /* path_test.cc */; };
5D405BE298CE4692CB00790A /* Pods_Firestore_Tests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2B50B3A0DF77100EEE887891 /* Pods_Firestore_Tests_iOS.framework */; };
6003F58E195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58F195388D20070C39A /* CoreGraphics.framework */; };
@@ -144,6 +147,23 @@
6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; };
6161B5032047140C00A99DBB /* FIRFirestoreSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6161B5012047140400A99DBB /* FIRFirestoreSourceTests.mm */; };
+ 618BBEA620B89AAC00B5BCE7 /* target.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7D20B89AAC00B5BCE7 /* target.pb.cc */; };
+ 618BBEA720B89AAC00B5BCE7 /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */; };
+ 618BBEA820B89AAC00B5BCE7 /* mutation.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE8220B89AAC00B5BCE7 /* mutation.pb.cc */; };
+ 618BBEA920B89AAC00B5BCE7 /* common.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE8820B89AAC00B5BCE7 /* common.pb.cc */; };
+ 618BBEAA20B89AAC00B5BCE7 /* firestore.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE8A20B89AAC00B5BCE7 /* firestore.pb.cc */; };
+ 618BBEAB20B89AAC00B5BCE7 /* query.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE8C20B89AAC00B5BCE7 /* query.pb.cc */; };
+ 618BBEAC20B89AAC00B5BCE7 /* document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE8E20B89AAC00B5BCE7 /* document.pb.cc */; };
+ 618BBEAD20B89AAC00B5BCE7 /* write.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE8F20B89AAC00B5BCE7 /* write.pb.cc */; };
+ 618BBEAE20B89AAC00B5BCE7 /* latlng.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9220B89AAC00B5BCE7 /* latlng.pb.cc */; };
+ 618BBEAF20B89AAC00B5BCE7 /* annotations.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9520B89AAC00B5BCE7 /* annotations.pb.cc */; };
+ 618BBEB020B89AAC00B5BCE7 /* http.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9720B89AAC00B5BCE7 /* http.pb.cc */; };
+ 618BBEB120B89AAC00B5BCE7 /* status.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9920B89AAC00B5BCE7 /* status.pb.cc */; };
+ 61F72C5620BC48FD001A68CB /* serializer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 61F72C5520BC48FD001A68CB /* serializer_test.cc */; };
+ 6EDD3B4620BF247500C33877 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
+ 6EDD3B4820BF247500C33877 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
+ 6EDD3B4920BF247500C33877 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; };
+ 6EDD3B6020BF25AE00C33877 /* FSTFuzzTestsPrincipal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6EDD3B5E20BF24D000C33877 /* FSTFuzzTestsPrincipal.mm */; };
71719F9F1E33DC2100824A3D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */; };
7346E61D20325C6900FD6CEF /* FSTDispatchQueueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7346E61C20325C6900FD6CEF /* FSTDispatchQueueTests.mm */; };
73866AA12082B0A5009BB4FF /* FIRArrayTransformTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 73866A9F2082B069009BB4FF /* FIRArrayTransformTests.mm */; };
@@ -180,6 +200,8 @@
B6FB4690208F9BB300554BA2 /* executor_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4688208F9B9100554BA2 /* executor_test.cc */; };
BF219E98F1C5A1DAEB5EEC86 /* Pods_Firestore_Example_iOS_SwiftBuildTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 379B34A1536045869826D82A /* Pods_Firestore_Example_iOS_SwiftBuildTest.framework */; };
C1AA536F90A0A576CA2816EB /* Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB92EB03E3F92485023F64ED /* Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework */; };
+ C482E724F4B10968417C3F78 /* Pods_Firestore_FuzzTests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B79CA87A1A01FC5329031C9B /* Pods_Firestore_FuzzTests_iOS.framework */; };
+ C80B10E79CDD7EF7843C321E /* type_traits_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2A0CF41BA5AED6049B0BEB2C /* type_traits_apple_test.mm */; };
C8D3CE2343E53223E6487F2C /* Pods_Firestore_Example_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5918805E993304321A05E82B /* Pods_Firestore_Example_iOS.framework */; };
DE03B2D41F2149D600A30B9C /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; };
DE03B2D51F2149D600A30B9C /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
@@ -215,6 +237,13 @@
remoteGlobalIDString = 6003F589195388D20070C39A;
remoteInfo = Firestore;
};
+ 6EDD3AD320BF247500C33877 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6003F582195388D10070C39A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 6003F589195388D20070C39A;
+ remoteInfo = Firestore;
+ };
DE03B2961F2149D600A30B9C /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 6003F582195388D10070C39A /* Project object */;
@@ -251,11 +280,14 @@
1277F98C20D2DF0867496976 /* Pods-Firestore_IntegrationTests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_IntegrationTests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_IntegrationTests_iOS/Pods-Firestore_IntegrationTests_iOS.debug.xcconfig"; sourceTree = "<group>"; };
12F4357299652983A615F886 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
132E36BB104830BD806351AC /* FSTLevelDBTransactionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTLevelDBTransactionTests.mm; sourceTree = "<group>"; };
+ 2A0CF41BA5AED6049B0BEB2C /* type_traits_apple_test.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = type_traits_apple_test.mm; sourceTree = "<group>"; };
2B50B3A0DF77100EEE887891 /* Pods_Firestore_Tests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Tests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 358C3B5FE573B1D60A4F7592 /* strerror_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = strerror_test.cc; sourceTree = "<group>"; };
379B34A1536045869826D82A /* Pods_Firestore_Example_iOS_SwiftBuildTest.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Example_iOS_SwiftBuildTest.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3B843E4A1F3930A400548890 /* remote_store_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = remote_store_spec_test.json; sourceTree = "<group>"; };
3C81DE3772628FE297055662 /* Pods-Firestore_Example_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS/Pods-Firestore_Example_iOS.debug.xcconfig"; sourceTree = "<group>"; };
3F0992A4B83C60841C52E960 /* Pods-Firestore_Example_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS/Pods-Firestore_Example_iOS.release.xcconfig"; sourceTree = "<group>"; };
+ 403DBF6EFB541DFD01582AA3 /* path_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = path_test.cc; sourceTree = "<group>"; };
444B7AB3F5A2929070CB1363 /* hard_assert_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = hard_assert_test.cc; sourceTree = "<group>"; };
54131E9620ADE678001DF3FF /* string_format_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = string_format_test.cc; sourceTree = "<group>"; };
54511E8D209805F8005BD28F /* hashing_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = hashing_test.cc; sourceTree = "<group>"; };
@@ -392,13 +424,44 @@
6003F5B7195388D20070C39A /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = "<group>"; };
6003F5B9195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
6161B5012047140400A99DBB /* FIRFirestoreSourceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRFirestoreSourceTests.mm; sourceTree = "<group>"; };
+ 618BBE7D20B89AAC00B5BCE7 /* target.pb.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = target.pb.cc; sourceTree = "<group>"; };
+ 618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = maybe_document.pb.cc; sourceTree = "<group>"; };
+ 618BBE7F20B89AAC00B5BCE7 /* target.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = target.pb.h; sourceTree = "<group>"; };
+ 618BBE8020B89AAC00B5BCE7 /* maybe_document.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = maybe_document.pb.h; sourceTree = "<group>"; };
+ 618BBE8120B89AAC00B5BCE7 /* mutation.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mutation.pb.h; sourceTree = "<group>"; };
+ 618BBE8220B89AAC00B5BCE7 /* mutation.pb.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mutation.pb.cc; sourceTree = "<group>"; };
+ 618BBE8620B89AAC00B5BCE7 /* query.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = query.pb.h; sourceTree = "<group>"; };
+ 618BBE8720B89AAC00B5BCE7 /* common.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = common.pb.h; sourceTree = "<group>"; };
+ 618BBE8820B89AAC00B5BCE7 /* common.pb.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = common.pb.cc; sourceTree = "<group>"; };
+ 618BBE8920B89AAC00B5BCE7 /* firestore.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = firestore.pb.h; sourceTree = "<group>"; };
+ 618BBE8A20B89AAC00B5BCE7 /* firestore.pb.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = firestore.pb.cc; sourceTree = "<group>"; };
+ 618BBE8B20B89AAC00B5BCE7 /* write.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = write.pb.h; sourceTree = "<group>"; };
+ 618BBE8C20B89AAC00B5BCE7 /* query.pb.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = query.pb.cc; sourceTree = "<group>"; };
+ 618BBE8D20B89AAC00B5BCE7 /* document.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = document.pb.h; sourceTree = "<group>"; };
+ 618BBE8E20B89AAC00B5BCE7 /* document.pb.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = document.pb.cc; sourceTree = "<group>"; };
+ 618BBE8F20B89AAC00B5BCE7 /* write.pb.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = write.pb.cc; sourceTree = "<group>"; };
+ 618BBE9120B89AAC00B5BCE7 /* latlng.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = latlng.pb.h; sourceTree = "<group>"; };
+ 618BBE9220B89AAC00B5BCE7 /* latlng.pb.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = latlng.pb.cc; sourceTree = "<group>"; };
+ 618BBE9420B89AAC00B5BCE7 /* http.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = http.pb.h; sourceTree = "<group>"; };
+ 618BBE9520B89AAC00B5BCE7 /* annotations.pb.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = annotations.pb.cc; sourceTree = "<group>"; };
+ 618BBE9620B89AAC00B5BCE7 /* annotations.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = annotations.pb.h; sourceTree = "<group>"; };
+ 618BBE9720B89AAC00B5BCE7 /* http.pb.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = http.pb.cc; sourceTree = "<group>"; };
+ 618BBE9920B89AAC00B5BCE7 /* status.pb.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = status.pb.cc; sourceTree = "<group>"; };
+ 618BBE9A20B89AAC00B5BCE7 /* status.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = status.pb.h; sourceTree = "<group>"; };
+ 61F72C5520BC48FD001A68CB /* serializer_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = serializer_test.cc; sourceTree = "<group>"; };
69E6C311558EC77729A16CF1 /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig"; sourceTree = "<group>"; };
+ 6EDD3B5B20BF247500C33877 /* Firestore_FuzzTests_iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Firestore_FuzzTests_iOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 6EDD3B5C20BF247500C33877 /* Firestore_FuzzTests_iOS-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Firestore_FuzzTests_iOS-Info.plist"; sourceTree = "<group>"; };
+ 6EDD3B5E20BF24D000C33877 /* FSTFuzzTestsPrincipal.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTFuzzTestsPrincipal.mm; sourceTree = "<group>"; };
71719F9E1E33DC2100824A3D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
7346E61C20325C6900FD6CEF /* FSTDispatchQueueTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTDispatchQueueTests.mm; sourceTree = "<group>"; };
73866A9F2082B069009BB4FF /* FIRArrayTransformTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRArrayTransformTests.mm; sourceTree = "<group>"; };
74ACEC3603BE58B57A7E8D4C /* Pods-Firestore_Example_iOS-SwiftBuildTest.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS-SwiftBuildTest.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS-SwiftBuildTest/Pods-Firestore_Example_iOS-SwiftBuildTest.release.xcconfig"; sourceTree = "<group>"; };
+ 84434E57CA72951015FC71BC /* Pods-Firestore_FuzzTests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_FuzzTests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_FuzzTests_iOS/Pods-Firestore_FuzzTests_iOS.debug.xcconfig"; sourceTree = "<group>"; };
873B8AEA1B1F5CCA007FD442 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
8E002F4AD5D9B6197C940847 /* Firestore.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = Firestore.podspec; path = ../Firestore.podspec; sourceTree = "<group>"; };
+ 97C492D2524E92927C11F425 /* Pods-Firestore_FuzzTests_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_FuzzTests_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_FuzzTests_iOS/Pods-Firestore_FuzzTests_iOS.release.xcconfig"; sourceTree = "<group>"; };
+ 9CFD366B783AE27B9E79EE7A /* string_format_apple_test.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = string_format_apple_test.mm; sourceTree = "<group>"; };
AB356EF6200EA5EB0089B766 /* field_value_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = field_value_test.cc; sourceTree = "<group>"; };
AB380CF82019382300D97691 /* target_id_generator_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = target_id_generator_test.cc; sourceTree = "<group>"; };
AB380CFC201A2EE200D97691 /* string_util_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = string_util_test.cc; sourceTree = "<group>"; };
@@ -431,6 +494,7 @@
B6FB4688208F9B9100554BA2 /* executor_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = executor_test.cc; sourceTree = "<group>"; };
B6FB4689208F9B9100554BA2 /* executor_libdispatch_test.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = executor_libdispatch_test.mm; sourceTree = "<group>"; };
B6FB468A208F9B9100554BA2 /* executor_test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = executor_test.h; sourceTree = "<group>"; };
+ B79CA87A1A01FC5329031C9B /* Pods_Firestore_FuzzTests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_FuzzTests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BB92EB03E3F92485023F64ED /* Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D3CC3DC5338DCAF43A211155 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
DE03B2E91F2149D600A30B9C /* Firestore_IntegrationTests_iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Firestore_IntegrationTests_iOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -485,6 +549,17 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 6EDD3B4520BF247500C33877 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 6EDD3B4620BF247500C33877 /* Foundation.framework in Frameworks */,
+ C482E724F4B10968417C3F78 /* Pods_Firestore_FuzzTests_iOS.framework in Frameworks */,
+ 6EDD3B4820BF247500C33877 /* UIKit.framework in Frameworks */,
+ 6EDD3B4920BF247500C33877 /* XCTest.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
DE03B2D31F2149D600A30B9C /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -530,6 +605,7 @@
isa = PBXGroup;
children = (
546854A820A36867004BDBD5 /* datastore_test.cc */,
+ 61F72C5520BC48FD001A68CB /* serializer_test.cc */,
);
path = remote;
sourceTree = "<group>";
@@ -554,12 +630,16 @@
54A0353420A3D8CB003E0143 /* iterator_adaptors_test.cc */,
54C2294E1FECABAE007D065B /* log_test.cc */,
AB380D03201BC6E400D97691 /* ordered_code_test.cc */,
+ 403DBF6EFB541DFD01582AA3 /* path_test.cc */,
54740A531FC913E500713A1A /* secure_random_test.cc */,
54A0352C20A3B3D7003E0143 /* status_test.cc */,
54A0352B20A3B3D7003E0143 /* status_test_util.h */,
54A0352D20A3B3D7003E0143 /* statusor_test.cc */,
+ 358C3B5FE573B1D60A4F7592 /* strerror_test.cc */,
+ 9CFD366B783AE27B9E79EE7A /* string_format_apple_test.mm */,
54131E9620ADE678001DF3FF /* string_format_test.cc */,
AB380CFC201A2EE200D97691 /* string_util_test.cc */,
+ 2A0CF41BA5AED6049B0BEB2C /* type_traits_apple_test.mm */,
);
path = util;
sourceTree = "<group>";
@@ -624,6 +704,8 @@
6003F581195388D10070C39A = {
isa = PBXGroup;
children = (
+ 618BBE7A20B89AAC00B5BCE7 /* CoreTestsProtos */,
+ 6EDD3B5D20BF24A700C33877 /* FuzzTests */,
543B4F0520A91E4B001F506D /* App */,
60FF7A9C1954A5C5007DD14C /* Podspec Metadata */,
6003F5B5195388D20070C39A /* Tests */,
@@ -640,6 +722,7 @@
isa = PBXGroup;
children = (
6003F58A195388D20070C39A /* Firestore_Example_iOS.app */,
+ 6EDD3B5B20BF247500C33877 /* Firestore_FuzzTests_iOS.xctest */,
DE03B2E91F2149D600A30B9C /* Firestore_IntegrationTests_iOS.xctest */,
54C9EDF12040E16300A969CD /* Firestore_SwiftTests_iOS.xctest */,
6003F5AE195388D20070C39A /* Firestore_Tests_iOS.xctest */,
@@ -656,6 +739,7 @@
5918805E993304321A05E82B /* Pods_Firestore_Example_iOS.framework */,
BB92EB03E3F92485023F64ED /* Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework */,
379B34A1536045869826D82A /* Pods_Firestore_Example_iOS_SwiftBuildTest.framework */,
+ B79CA87A1A01FC5329031C9B /* Pods_Firestore_FuzzTests_iOS.framework */,
ECEBABC7E7B693BE808A1052 /* Pods_Firestore_IntegrationTests_iOS.framework */,
2B50B3A0DF77100EEE887891 /* Pods_Firestore_Tests_iOS.framework */,
6003F591195388D20070C39A /* UIKit.framework */,
@@ -725,6 +809,111 @@
name = "Podspec Metadata";
sourceTree = "<group>";
};
+ 618BBE7A20B89AAC00B5BCE7 /* CoreTestsProtos */ = {
+ isa = PBXGroup;
+ children = (
+ 618BBE7B20B89AAC00B5BCE7 /* firestore */,
+ 618BBE8320B89AAC00B5BCE7 /* google */,
+ );
+ name = CoreTestsProtos;
+ path = ../Protos/cpp;
+ sourceTree = "<group>";
+ };
+ 618BBE7B20B89AAC00B5BCE7 /* firestore */ = {
+ isa = PBXGroup;
+ children = (
+ 618BBE7C20B89AAC00B5BCE7 /* local */,
+ );
+ path = firestore;
+ sourceTree = "<group>";
+ };
+ 618BBE7C20B89AAC00B5BCE7 /* local */ = {
+ isa = PBXGroup;
+ children = (
+ 618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */,
+ 618BBE8020B89AAC00B5BCE7 /* maybe_document.pb.h */,
+ 618BBE8220B89AAC00B5BCE7 /* mutation.pb.cc */,
+ 618BBE8120B89AAC00B5BCE7 /* mutation.pb.h */,
+ 618BBE7D20B89AAC00B5BCE7 /* target.pb.cc */,
+ 618BBE7F20B89AAC00B5BCE7 /* target.pb.h */,
+ );
+ path = local;
+ sourceTree = "<group>";
+ };
+ 618BBE8320B89AAC00B5BCE7 /* google */ = {
+ isa = PBXGroup;
+ children = (
+ 618BBE9320B89AAC00B5BCE7 /* api */,
+ 618BBE8420B89AAC00B5BCE7 /* firestore */,
+ 618BBE9820B89AAC00B5BCE7 /* rpc */,
+ 618BBE9020B89AAC00B5BCE7 /* type */,
+ );
+ path = google;
+ sourceTree = "<group>";
+ };
+ 618BBE8420B89AAC00B5BCE7 /* firestore */ = {
+ isa = PBXGroup;
+ children = (
+ 618BBE8520B89AAC00B5BCE7 /* v1beta1 */,
+ );
+ path = firestore;
+ sourceTree = "<group>";
+ };
+ 618BBE8520B89AAC00B5BCE7 /* v1beta1 */ = {
+ isa = PBXGroup;
+ children = (
+ 618BBE8820B89AAC00B5BCE7 /* common.pb.cc */,
+ 618BBE8720B89AAC00B5BCE7 /* common.pb.h */,
+ 618BBE8E20B89AAC00B5BCE7 /* document.pb.cc */,
+ 618BBE8D20B89AAC00B5BCE7 /* document.pb.h */,
+ 618BBE8A20B89AAC00B5BCE7 /* firestore.pb.cc */,
+ 618BBE8920B89AAC00B5BCE7 /* firestore.pb.h */,
+ 618BBE8C20B89AAC00B5BCE7 /* query.pb.cc */,
+ 618BBE8620B89AAC00B5BCE7 /* query.pb.h */,
+ 618BBE8F20B89AAC00B5BCE7 /* write.pb.cc */,
+ 618BBE8B20B89AAC00B5BCE7 /* write.pb.h */,
+ );
+ path = v1beta1;
+ sourceTree = "<group>";
+ };
+ 618BBE9020B89AAC00B5BCE7 /* type */ = {
+ isa = PBXGroup;
+ children = (
+ 618BBE9220B89AAC00B5BCE7 /* latlng.pb.cc */,
+ 618BBE9120B89AAC00B5BCE7 /* latlng.pb.h */,
+ );
+ path = type;
+ sourceTree = "<group>";
+ };
+ 618BBE9320B89AAC00B5BCE7 /* api */ = {
+ isa = PBXGroup;
+ children = (
+ 618BBE9520B89AAC00B5BCE7 /* annotations.pb.cc */,
+ 618BBE9620B89AAC00B5BCE7 /* annotations.pb.h */,
+ 618BBE9720B89AAC00B5BCE7 /* http.pb.cc */,
+ 618BBE9420B89AAC00B5BCE7 /* http.pb.h */,
+ );
+ path = api;
+ sourceTree = "<group>";
+ };
+ 618BBE9820B89AAC00B5BCE7 /* rpc */ = {
+ isa = PBXGroup;
+ children = (
+ 618BBE9920B89AAC00B5BCE7 /* status.pb.cc */,
+ 618BBE9A20B89AAC00B5BCE7 /* status.pb.h */,
+ );
+ path = rpc;
+ sourceTree = "<group>";
+ };
+ 6EDD3B5D20BF24A700C33877 /* FuzzTests */ = {
+ isa = PBXGroup;
+ children = (
+ 6EDD3B5E20BF24D000C33877 /* FSTFuzzTestsPrincipal.mm */,
+ 6EDD3B5C20BF247500C33877 /* Firestore_FuzzTests_iOS-Info.plist */,
+ );
+ path = FuzzTests;
+ sourceTree = "<group>";
+ };
AAEA2A72CFD1FA5AD34462F7 /* Pods */ = {
isa = PBXGroup;
children = (
@@ -734,6 +923,8 @@
74ACEC3603BE58B57A7E8D4C /* Pods-Firestore_Example_iOS-SwiftBuildTest.release.xcconfig */,
3C81DE3772628FE297055662 /* Pods-Firestore_Example_iOS.debug.xcconfig */,
3F0992A4B83C60841C52E960 /* Pods-Firestore_Example_iOS.release.xcconfig */,
+ 84434E57CA72951015FC71BC /* Pods-Firestore_FuzzTests_iOS.debug.xcconfig */,
+ 97C492D2524E92927C11F425 /* Pods-Firestore_FuzzTests_iOS.release.xcconfig */,
1277F98C20D2DF0867496976 /* Pods-Firestore_IntegrationTests_iOS.debug.xcconfig */,
F354C0FE92645B56A6C6FD44 /* Pods-Firestore_IntegrationTests_iOS.release.xcconfig */,
E592181BFD7C53C305123739 /* Pods-Firestore_Tests_iOS.debug.xcconfig */,
@@ -1039,6 +1230,26 @@
productReference = 6003F5AE195388D20070C39A /* Firestore_Tests_iOS.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
+ 6EDD3AD120BF247500C33877 /* Firestore_FuzzTests_iOS */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 6EDD3B5820BF247500C33877 /* Build configuration list for PBXNativeTarget "Firestore_FuzzTests_iOS" */;
+ buildPhases = (
+ 6EDD3AD420BF247500C33877 /* [CP] Check Pods Manifest.lock */,
+ 6EDD3AD520BF247500C33877 /* Sources */,
+ 6EDD3B4520BF247500C33877 /* Frameworks */,
+ 6EDD3B4A20BF247500C33877 /* Resources */,
+ 6EDD3B5720BF247500C33877 /* [CP] Embed Pods Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 6EDD3AD220BF247500C33877 /* PBXTargetDependency */,
+ );
+ name = Firestore_FuzzTests_iOS;
+ productName = FirestoreTests;
+ productReference = 6EDD3B5B20BF247500C33877 /* Firestore_FuzzTests_iOS.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
DE03B2941F2149D600A30B9C /* Firestore_IntegrationTests_iOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = DE03B2E61F2149D600A30B9C /* Build configuration list for PBXNativeTarget "Firestore_IntegrationTests_iOS" */;
@@ -1098,6 +1309,9 @@
DevelopmentTeam = EQHXZ8M8AV;
TestTargetID = 6003F589195388D20070C39A;
};
+ 6EDD3AD120BF247500C33877 = {
+ DevelopmentTeam = EQHXZ8M8AV;
+ };
DE03B2941F2149D600A30B9C = {
DevelopmentTeam = EQHXZ8M8AV;
};
@@ -1131,6 +1345,7 @@
DE03B2941F2149D600A30B9C /* Firestore_IntegrationTests_iOS */,
DE29E7F51F2174B000909613 /* AllTests_iOS */,
DE0761E31F2FE611003233AF /* SwiftBuildTest */,
+ 6EDD3AD120BF247500C33877 /* Firestore_FuzzTests_iOS */,
);
};
/* End PBXProject section */
@@ -1173,6 +1388,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 6EDD3B4A20BF247500C33877 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
DE03B2D81F2149D600A30B9C /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -1274,12 +1496,14 @@
"${BUILT_PRODUCTS_DIR}/leveldb-library/leveldb.framework",
"${BUILT_PRODUCTS_DIR}/GoogleTest/GoogleTest.framework",
"${BUILT_PRODUCTS_DIR}/OCMock/OCMock.framework",
+ "${BUILT_PRODUCTS_DIR}/ProtobufCpp/ProtobufCpp.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/leveldb.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleTest.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ProtobufCpp.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -1304,6 +1528,42 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
+ 6EDD3AD420BF247500C33877 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-Firestore_FuzzTests_iOS-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 6EDD3B5720BF247500C33877 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${SRCROOT}/Pods/Target Support Files/Pods-Firestore_FuzzTests_iOS/Pods-Firestore_FuzzTests_iOS-frameworks.sh",
+ "${BUILT_PRODUCTS_DIR}/LibFuzzer/LibFuzzer.framework",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputPaths = (
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LibFuzzer.framework",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Firestore_FuzzTests_iOS/Pods-Firestore_FuzzTests_iOS-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
83F2AB95D08093BB076EE521 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -1517,6 +1777,7 @@
5492E0CA2021557E00B64F25 /* FSTWatchChangeTests.mm in Sources */,
5492E0AB2021552D00B64F25 /* StringViewTests.mm in Sources */,
5492E03C2021401F00B64F25 /* XCTestCase+Await.mm in Sources */,
+ 618BBEAF20B89AAC00B5BCE7 /* annotations.pb.cc in Sources */,
5467FB08203E6A44009C9584 /* app_testing.mm in Sources */,
54EB764D202277B30088B8F3 /* array_sorted_map_test.cc in Sources */,
B6FB4684208EA0EC00554BA2 /* async_queue_libdispatch_test.mm in Sources */,
@@ -1524,11 +1785,13 @@
B6FB467D208E9D3C00554BA2 /* async_queue_test.cc in Sources */,
54740A581FC914F000713A1A /* autoid_test.cc in Sources */,
AB380D02201BC69F00D97691 /* bits_test.cc in Sources */,
+ 618BBEA920B89AAC00B5BCE7 /* common.pb.cc in Sources */,
548DB929200D59F600E00ABC /* comparison_test.cc in Sources */,
ABC1D7DC2023A04B00BA84F0 /* credentials_provider_test.cc in Sources */,
ABE6637A201FA81900ED349A /* database_id_test.cc in Sources */,
AB38D93020236E21000A432D /* database_info_test.cc in Sources */,
546854AA20A36867004BDBD5 /* datastore_test.cc in Sources */,
+ 618BBEAC20B89AAC00B5BCE7 /* document.pb.cc in Sources */,
B6152AD7202A53CB000E5744 /* document_key_test.cc in Sources */,
AB6B908420322E4D00CC290A /* document_test.cc in Sources */,
ABC1D7DD2023A04F00BA84F0 /* empty_credentials_provider_test.cc in Sources */,
@@ -1540,32 +1803,54 @@
54A0352620A3AED0003E0143 /* field_transform_test.mm in Sources */,
AB356EF7200EA5EB0089B766 /* field_value_test.cc in Sources */,
ABC1D7E42024AFDE00BA84F0 /* firebase_credentials_provider_test.mm in Sources */,
+ 618BBEAA20B89AAC00B5BCE7 /* firestore.pb.cc in Sources */,
AB7BAB342012B519001E0872 /* geo_point_test.cc in Sources */,
73FE5066020EF9B2892C86BF /* hard_assert_test.cc in Sources */,
54511E8E209805F8005BD28F /* hashing_test.cc in Sources */,
+ 618BBEB020B89AAC00B5BCE7 /* http.pb.cc in Sources */,
54A0353520A3D8CB003E0143 /* iterator_adaptors_test.cc in Sources */,
+ 618BBEAE20B89AAC00B5BCE7 /* latlng.pb.cc in Sources */,
54995F6F205B6E12004EFFA0 /* leveldb_key_test.cc in Sources */,
54C2294F1FECABAE007D065B /* log_test.cc in Sources */,
+ 618BBEA720B89AAC00B5BCE7 /* maybe_document.pb.cc in Sources */,
AB6B908620322E6D00CC290A /* maybe_document_test.cc in Sources */,
+ 618BBEA820B89AAC00B5BCE7 /* mutation.pb.cc in Sources */,
AB6B908820322E8800CC290A /* no_document_test.cc in Sources */,
AB380D04201BC6E400D97691 /* ordered_code_test.cc in Sources */,
+ 5A080105CCBFDB6BF3F3772D /* path_test.cc in Sources */,
549CCA5920A36E1F00BCEB75 /* precondition_test.cc in Sources */,
+ 618BBEAB20B89AAC00B5BCE7 /* query.pb.cc in Sources */,
B686F2B22025000D0028D6BE /* resource_path_test.cc in Sources */,
54740A571FC914BA00713A1A /* secure_random_test.cc in Sources */,
+ 61F72C5620BC48FD001A68CB /* serializer_test.cc in Sources */,
ABA495BB202B7E80008A7851 /* snapshot_version_test.cc in Sources */,
549CCA5220A36DBC00BCEB75 /* sorted_map_test.cc in Sources */,
549CCA5020A36DBC00BCEB75 /* sorted_set_test.cc in Sources */,
+ 618BBEB120B89AAC00B5BCE7 /* status.pb.cc in Sources */,
54A0352F20A3B3D8003E0143 /* status_test.cc in Sources */,
54A0353020A3B3D8003E0143 /* statusor_test.cc in Sources */,
+ 1CAA9012B25F975D445D5978 /* strerror_test.cc in Sources */,
+ 0535C1B65DADAE1CE47FA3CA /* string_format_apple_test.mm in Sources */,
54131E9720ADE679001DF3FF /* string_format_test.cc in Sources */,
AB380CFE201A2F4500D97691 /* string_util_test.cc in Sources */,
+ 618BBEA620B89AAC00B5BCE7 /* target.pb.cc in Sources */,
AB380CFB2019388600D97691 /* target_id_generator_test.cc in Sources */,
54A0352A20A3B3BD003E0143 /* testutil.cc in Sources */,
ABF6506C201131F8005F2C74 /* timestamp_test.cc in Sources */,
ABC1D7E12023A40C00BA84F0 /* token_test.cc in Sources */,
54A0352720A3AED0003E0143 /* transform_operations_test.mm in Sources */,
549CCA5120A36DBC00BCEB75 /* tree_sorted_map_test.cc in Sources */,
+ C80B10E79CDD7EF7843C321E /* type_traits_apple_test.mm in Sources */,
ABC1D7DE2023A05300BA84F0 /* user_test.cc in Sources */,
+ 618BBEAD20B89AAC00B5BCE7 /* write.pb.cc in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 6EDD3AD520BF247500C33877 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 6EDD3B6020BF25AE00C33877 /* FSTFuzzTestsPrincipal.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1621,6 +1906,11 @@
target = 6003F589195388D20070C39A /* Firestore_Example_iOS */;
targetProxy = 6003F5B3195388D20070C39A /* PBXContainerItemProxy */;
};
+ 6EDD3AD220BF247500C33877 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 6003F589195388D20070C39A /* Firestore_Example_iOS */;
+ targetProxy = 6EDD3AD320BF247500C33877 /* PBXContainerItemProxy */;
+ };
DE03B2951F2149D600A30B9C /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 6003F589195388D20070C39A /* Firestore_Example_iOS */;
@@ -1785,6 +2075,7 @@
HEADER_SEARCH_PATHS = "";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
ONLY_ACTIVE_ARCH = YES;
+ OTHER_CFLAGS = "";
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
@@ -1818,6 +2109,7 @@
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = "";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ OTHER_CFLAGS = "";
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
@@ -1897,10 +2189,61 @@
"\"${PODS_ROOT}/GoogleTest/googlemock/include\"",
"\"${PODS_ROOT}/GoogleTest/googletest/include\"",
"\"${PODS_ROOT}/leveldb-library/include\"",
+ "\"${PODS_ROOT}/../../../Firestore/Protos/cpp\"",
+ "\"${PODS_ROOT}/ProtobufCpp/src\"",
);
INFOPLIST_FILE = "Tests/Tests-Info.plist";
+ OTHER_CFLAGS = (
+ "$(inherited)",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/GoogleTest/GoogleTest.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/OCMock/OCMock.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/ProtobufCpp/ProtobufCpp.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/leveldb-library/leveldb.framework/Headers\"",
+ "$(inherited)",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/BoringSSL/openssl.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/FirebaseAuth/FirebaseAuth.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/FirebaseFirestore/FirebaseFirestore.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/GoogleToolboxForMac/GoogleToolboxForMac.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/Protobuf/Protobuf.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/gRPC-Core/grpc.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/gRPC-ProtoRPC/ProtoRPC.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/gRPC-RxLibrary/RxLibrary.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/gRPC/GRPCClient.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/leveldb-library/leveldb.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb.framework/Headers\"",
+ "-isystem",
+ "\"${PODS_ROOT}/Headers/Public\"",
+ "-isystem",
+ "\"${PODS_ROOT}/Headers/Public/Firebase\"",
+ "-isystem",
+ "\"${PODS_ROOT}/Headers/Public/FirebaseAnalytics\"",
+ "-isystem",
+ "\"${PODS_ROOT}/Headers/Public/FirebaseInstanceID\"",
+ "-DPB_FIELD_32BIT",
+ "-DPB_NO_PACKED_STRUCTS=1",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
+ SYSTEM_HEADER_SEARCH_PATHS = "\"${PODS_ROOT}/nanopb\"";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Firestore_Example_iOS.app/Firestore_Example_iOS";
WRAPPER_EXTENSION = xctest;
};
@@ -1931,8 +2274,131 @@
"\"${PODS_ROOT}/GoogleTest/googlemock/include\"",
"\"${PODS_ROOT}/GoogleTest/googletest/include\"",
"\"${PODS_ROOT}/leveldb-library/include\"",
+ "\"${PODS_ROOT}/../../../Firestore/Protos/cpp\"",
+ "\"${PODS_ROOT}/ProtobufCpp/src\"",
);
INFOPLIST_FILE = "Tests/Tests-Info.plist";
+ OTHER_CFLAGS = (
+ "$(inherited)",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/GoogleTest/GoogleTest.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/OCMock/OCMock.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/ProtobufCpp/ProtobufCpp.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/leveldb-library/leveldb.framework/Headers\"",
+ "$(inherited)",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/BoringSSL/openssl.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/FirebaseAuth/FirebaseAuth.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/FirebaseFirestore/FirebaseFirestore.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/GoogleToolboxForMac/GoogleToolboxForMac.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/Protobuf/Protobuf.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/gRPC-Core/grpc.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/gRPC-ProtoRPC/ProtoRPC.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/gRPC-RxLibrary/RxLibrary.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/gRPC/GRPCClient.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/leveldb-library/leveldb.framework/Headers\"",
+ "-iquote",
+ "\"${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb.framework/Headers\"",
+ "-isystem",
+ "\"${PODS_ROOT}/Headers/Public\"",
+ "-isystem",
+ "\"${PODS_ROOT}/Headers/Public/Firebase\"",
+ "-isystem",
+ "\"${PODS_ROOT}/Headers/Public/FirebaseAnalytics\"",
+ "-isystem",
+ "\"${PODS_ROOT}/Headers/Public/FirebaseInstanceID\"",
+ "-DPB_FIELD_32BIT",
+ "-DPB_NO_PACKED_STRUCTS=1",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SYSTEM_HEADER_SEARCH_PATHS = "\"${PODS_ROOT}/nanopb\"";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Firestore_Example_iOS.app/Firestore_Example_iOS";
+ WRAPPER_EXTENSION = xctest;
+ };
+ name = Release;
+ };
+ 6EDD3B5920BF247500C33877 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 84434E57CA72951015FC71BC /* Pods-Firestore_FuzzTests_iOS.debug.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(SDKROOT)/Developer/Library/Frameworks",
+ "$(inherited)",
+ "$(DEVELOPER_FRAMEWORKS_DIR)",
+ );
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = "";
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "COCOAPODS=1",
+ "GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1",
+ );
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"${PODS_ROOT}/../../..\"",
+ "\"${PODS_ROOT}/../../../Firestore/third_party/abseil-cpp\"",
+ "\"${PODS_ROOT}/nanopb\"",
+ );
+ INFOPLIST_FILE = "FuzzTests/Firestore_FuzzTests_iOS-Info.plist";
+ OTHER_CFLAGS = (
+ "$(inherited)",
+ "-fsanitize-coverage=trace-pc-guard",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Firestore_Example_iOS.app/Firestore_Example_iOS";
+ WRAPPER_EXTENSION = xctest;
+ };
+ name = Debug;
+ };
+ 6EDD3B5A20BF247500C33877 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 97C492D2524E92927C11F425 /* Pods-Firestore_FuzzTests_iOS.release.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(SDKROOT)/Developer/Library/Frameworks",
+ "$(inherited)",
+ "$(DEVELOPER_FRAMEWORKS_DIR)",
+ );
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = "";
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "COCOAPODS=1",
+ "GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1",
+ );
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"${PODS_ROOT}/../../..\"",
+ "\"${PODS_ROOT}/../../../Firestore/third_party/abseil-cpp\"",
+ "\"${PODS_ROOT}/nanopb\"",
+ );
+ INFOPLIST_FILE = "FuzzTests/Firestore_FuzzTests_iOS-Info.plist";
+ OTHER_CFLAGS = (
+ "$(inherited)",
+ "-fsanitize-coverage=trace-pc-guard",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Firestore_Example_iOS.app/Firestore_Example_iOS";
@@ -2123,6 +2589,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ 6EDD3B5820BF247500C33877 /* Build configuration list for PBXNativeTarget "Firestore_FuzzTests_iOS" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 6EDD3B5920BF247500C33877 /* Debug */,
+ 6EDD3B5A20BF247500C33877 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
DE03B2E61F2149D600A30B9C /* Build configuration list for PBXNativeTarget "Firestore_IntegrationTests_iOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_FuzzTests_iOS.xcscheme b/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_FuzzTests_iOS.xcscheme
new file mode 100644
index 0000000..039273b
--- /dev/null
+++ b/Firestore/Example/Firestore.xcodeproj/xcshareddata/xcschemes/Firestore_FuzzTests_iOS.xcscheme
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0930"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ enableAddressSanitizer = "YES"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6EDD3AD120BF247500C33877"
+ BuildableName = "Firestore_FuzzTests_iOS.xctest"
+ BlueprintName = "Firestore_FuzzTests_iOS"
+ ReferencedContainer = "container:Firestore.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/Firestore/Example/FuzzTests/FSTFuzzTestsPrincipal.mm b/Firestore/Example/FuzzTests/FSTFuzzTestsPrincipal.mm
new file mode 100644
index 0000000..038e687
--- /dev/null
+++ b/Firestore/Example/FuzzTests/FSTFuzzTestsPrincipal.mm
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2018 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/NSObject.h>
+
+#include "LibFuzzer/FuzzerDefs.h"
+
+#include "Firestore/core/src/firebase/firestore/remote/serializer.h"
+
+using firebase::firestore::remote::Serializer;
+
+namespace {
+
+// Fuzz-test the deserialization process in Firestore. The Serializer reads raw
+// bytes and converts them to a model object.
+void FuzzTestDeserialization(const uint8_t *data, size_t size) {
+ // TODO(minafarid): fuzz-test Serializer.
+}
+
+// Contains the code to be fuzzed. Called by the fuzzing library with
+// different argument values for `data` and `size`.
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ FuzzTestDeserialization(data, size);
+ return 0;
+}
+
+// Simulates calling the main() function of libFuzzer (FuzzerMain.cpp).
+int RunFuzzTestingMain() {
+ // Arguments to libFuzzer main() function should be added to this array,
+ // e.g., dictionaries, corpus, number of runs, jobs, etc.
+ char *program_args[] = {
+ const_cast<char *>("RunFuzzTestingMain") // First arg is program name.
+ };
+ char **argv = program_args;
+ int argc = sizeof(program_args) / sizeof(program_args[0]);
+
+ // Start fuzzing using libFuzzer's driver.
+ return fuzzer::FuzzerDriver(&argc, &argv, LLVMFuzzerTestOneInput);
+}
+
+} // namespace
+
+/**
+ * This class is registered as the NSPrincipalClass in the
+ * Firestore_FuzzTests_iOS bundle's Info.plist. XCTest instantiates this class
+ * to perform one-time setup for the test bundle, as documented here:
+ *
+ * https://developer.apple.com/documentation/xctest/xctestobservationcenter
+ */
+@interface FSTFuzzTestsPrincipal : NSObject
+@end
+
+@implementation FSTFuzzTestsPrincipal
+
+- (instancetype)init {
+ self = [super init];
+ RunFuzzTestingMain();
+ return self;
+}
+
+@end
diff --git a/Firestore/Example/FuzzTests/Firestore_FuzzTests_iOS-Info.plist b/Firestore/Example/FuzzTests/Firestore_FuzzTests_iOS-Info.plist
new file mode 100644
index 0000000..0d53e5f
--- /dev/null
+++ b/Firestore/Example/FuzzTests/Firestore_FuzzTests_iOS-Info.plist
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>BNDL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>NSPrincipalClass</key>
+ <string>FSTFuzzTestsPrincipal</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+</dict>
+</plist>
diff --git a/Firestore/Example/Podfile b/Firestore/Example/Podfile
index 88cf93a..110c676 100644
--- a/Firestore/Example/Podfile
+++ b/Firestore/Example/Podfile
@@ -10,7 +10,7 @@ target 'Firestore_Example_iOS' do
# The next line is the forcing function for the Firebase pod. The Firebase
# version's subspecs should depend on the component versions in their
# corresponding podspec's.
- pod 'Firebase/Core', '5.1.0'
+ pod 'Firebase/Core', '5.2.0'
pod 'FirebaseAuth', :path => '../../'
pod 'FirebaseCore', :path => '../../'
@@ -22,6 +22,7 @@ target 'Firestore_Example_iOS' do
pod 'leveldb-library'
pod 'OCMock'
pod 'GoogleTest', :podspec => 'GoogleTest.podspec'
+ pod 'ProtobufCpp', :podspec => 'ProtobufCpp.podspec'
end
target 'Firestore_IntegrationTests_iOS' do
@@ -37,4 +38,10 @@ target 'Firestore_Example_iOS' do
target 'SwiftBuildTest' do
platform :ios, '8.0'
end
+
+ target 'Firestore_FuzzTests_iOS' do
+ inherit! :search_paths
+
+ pod 'LibFuzzer', :podspec => 'LibFuzzer.podspec'
+ end
end
diff --git a/Firestore/Example/ProtobufCpp.podspec b/Firestore/Example/ProtobufCpp.podspec
new file mode 100644
index 0000000..c809c06
--- /dev/null
+++ b/Firestore/Example/ProtobufCpp.podspec
@@ -0,0 +1,74 @@
+# Copyright 2018 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.
+
+# A Private podspec for Protobuf which exposes the C++ headers (rather than
+# only the Obj-C headers). Suitable only for use inside this source tree.
+
+Pod::Spec.new do |s|
+ s.name = 'ProtobufCpp'
+ s.version = '3.5.2'
+ s.summary = 'Protocol Buffers v.3 runtime library for C++.'
+ s.homepage = 'https://github.com/google/protobuf'
+ s.license = '3-Clause BSD License'
+ s.authors = { 'The Protocol Buffers contributors' => 'protobuf@googlegroups.com' }
+ s.cocoapods_version = '>= 1.0'
+
+ s.source = {
+ :git => 'https://github.com/google/protobuf.git',
+ :tag => "v#{s.version}"
+ }
+
+ s.source_files = 'src/**/*.{h,cc}'
+ s.exclude_files = # skip test files. (Yes, the test files are intermixed with
+ # the source. No there doesn't seem to be a common/simple
+ # pattern we could use to exclude them; 'test' appears in
+ # various places throughout the file names and also in a
+ # non-test file. So, we'll exclude all files that either
+ # start with 'test' or include test and have a previous
+ # character that isn't "y" (so that bytestream isn't
+ # matched.))
+ 'src/**/test*.*',
+ 'src/**/*[^y]test*.*',
+ 'src/**/testing/**',
+ 'src/**/mock*',
+ # skip the javascript handling code.
+ 'src/**/js/**',
+ # skip the protoc compiler
+ 'src/google/protobuf/compiler/**/*'
+
+ s.header_mappings_dir = 'src/'
+
+ # Set a CPP symbol so the code knows to use framework imports.
+ s.pod_target_xcconfig = {
+ 'GCC_PREPROCESSOR_DEFINITIONS' =>
+ '$(inherited) ' +
+ 'GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1 ' +
+ 'HAVE_PTHREAD=1',
+ 'HEADER_SEARCH_PATHS' => '"${PODS_ROOT}/ProtobufCpp/src"',
+
+ # Cocoapods flattens header imports, leading to much anguish. The
+ # following two statements work around this.
+ # - https://github.com/CocoaPods/CocoaPods/issues/1437
+ 'USE_HEADERMAP' => 'NO',
+ 'ALWAYS_SEARCH_USER_PATHS' => 'NO',
+ }
+
+ # Disable warnings that upstream does not concern itself with
+ s.compiler_flags = '$(inherited) ' +
+ '-Wno-comma ' +
+ '-Wno-shorten-64-to-32'
+
+ s.requires_arc = false
+ s.library = 'c++'
+end
diff --git a/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm b/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm
index 5629075..ddd831a 100644
--- a/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm
+++ b/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm
@@ -118,11 +118,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTViewSnapshot *snap1 = FSTTestApplyChanges(view, @[], nil);
-
- FSTTargetChange *ackTarget =
- [FSTTargetChange changeWithDocuments:@[]
- currentStatusUpdate:FSTCurrentStatusUpdateMarkCurrent];
- FSTViewSnapshot *snap2 = FSTTestApplyChanges(view, @[], ackTarget);
+ FSTViewSnapshot *snap2 = FSTTestApplyChanges(view, @[], FSTTestTargetChangeMarkCurrent());
[listener queryDidChangeViewSnapshot:snap1];
XCTAssertEqualObjects(accum, @[]);
@@ -188,9 +184,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTViewSnapshot *snap1 = FSTTestApplyChanges(view, @[ doc1 ], nil);
- FSTTargetChange *ackTarget =
- [FSTTargetChange changeWithDocuments:@[ doc1 ]
- currentStatusUpdate:FSTCurrentStatusUpdateMarkCurrent];
+ FSTTargetChange *ackTarget = FSTTestTargetChangeAckDocuments({doc1.key});
FSTViewSnapshot *snap2 = FSTTestApplyChanges(view, @[], ackTarget);
FSTViewSnapshot *snap3 = FSTTestApplyChanges(view, @[ doc2 ], nil);
@@ -342,9 +336,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTViewSnapshot *snap1 = FSTTestApplyChanges(view, @[ doc1 ], nil);
FSTViewSnapshot *snap2 = FSTTestApplyChanges(view, @[ doc2 ], nil);
FSTViewSnapshot *snap3 =
- FSTTestApplyChanges(view, @[],
- [FSTTargetChange changeWithDocuments:@[ doc1, doc2 ]
- currentStatusUpdate:FSTCurrentStatusUpdateMarkCurrent]);
+ FSTTestApplyChanges(view, @[], FSTTestTargetChangeAckDocuments({doc1.key, doc2.key}));
[listener applyChangedOnlineState:FSTOnlineStateOnline]; // no event
[listener queryDidChangeViewSnapshot:snap1];
diff --git a/Firestore/Example/Tests/Core/FSTViewTests.mm b/Firestore/Example/Tests/Core/FSTViewTests.mm
index ec62d82..8c8d8a0 100644
--- a/Firestore/Example/Tests/Core/FSTViewTests.mm
+++ b/Firestore/Example/Tests/Core/FSTViewTests.mm
@@ -56,10 +56,8 @@ NS_ASSUME_NONNULL_BEGIN
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
FSTDocument *doc3 = FSTTestDoc("rooms/other/messages/1", 0, @{@"text" : @"msg3"}, NO);
- FSTViewSnapshot *_Nullable snapshot =
- FSTTestApplyChanges(view, @[ doc1, doc2, doc3 ],
- [FSTTargetChange changeWithDocuments:@[ doc1, doc2, doc3 ]
- currentStatusUpdate:FSTCurrentStatusUpdateMarkCurrent]);
+ FSTViewSnapshot *_Nullable snapshot = FSTTestApplyChanges(
+ view, @[ doc1, doc2, doc3 ], FSTTestTargetChangeAckDocuments({doc1.key, doc2.key, doc3.key}));
XCTAssertEqual(snapshot.query, query);
@@ -90,8 +88,7 @@ NS_ASSUME_NONNULL_BEGIN
// delete doc2, add doc3
FSTViewSnapshot *snapshot =
FSTTestApplyChanges(view, @[ FSTTestDeletedDoc("rooms/eros/messages/2", 0), doc3 ],
- [FSTTargetChange changeWithDocuments:@[ doc1, doc3 ]
- currentStatusUpdate:FSTCurrentStatusUpdateMarkCurrent]);
+ FSTTestTargetChangeAckDocuments({doc1.key, doc3.key}));
XCTAssertEqual(snapshot.query, query);
@@ -216,10 +213,8 @@ NS_ASSUME_NONNULL_BEGIN
FSTTestApplyChanges(view, @[ doc1, doc3 ], nil);
// add doc2, which should push out doc3
- FSTViewSnapshot *snapshot =
- FSTTestApplyChanges(view, @[ doc2 ],
- [FSTTargetChange changeWithDocuments:@[ doc1, doc2, doc3 ]
- currentStatusUpdate:FSTCurrentStatusUpdateMarkCurrent]);
+ FSTViewSnapshot *snapshot = FSTTestApplyChanges(
+ view, @[ doc2 ], FSTTestTargetChangeAckDocuments({doc1.key, doc2.key, doc3.key}));
XCTAssertEqual(snapshot.query, query);
@@ -263,9 +258,8 @@ NS_ASSUME_NONNULL_BEGIN
previousChanges:viewDocChanges];
FSTViewSnapshot *snapshot =
[view applyChangesToDocuments:viewDocChanges
- targetChange:[FSTTargetChange
- changeWithDocuments:@[ doc1, doc2, doc3, doc4 ]
- currentStatusUpdate:FSTCurrentStatusUpdateMarkCurrent]]
+ targetChange:FSTTestTargetChangeAckDocuments(
+ {doc1.key, doc2.key, doc3.key, doc4.key})]
.snapshot;
XCTAssertEqual(snapshot.query, query);
@@ -294,27 +288,21 @@ NS_ASSUME_NONNULL_BEGIN
applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1 ])]];
XCTAssertEqualObjects(change.limboChanges, @[]);
- change =
- [view applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[])]
- targetChange:[FSTTargetChange
- changeWithDocuments:@[]
- currentStatusUpdate:FSTCurrentStatusUpdateMarkCurrent]];
+ change = [view applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[])]
+ targetChange:FSTTestTargetChangeMarkCurrent()];
XCTAssertEqualObjects(
change.limboChanges,
@[ [FSTLimboDocumentChange changeWithType:FSTLimboDocumentChangeTypeAdded key:doc1.key] ]);
- change = [view
- applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[])]
- targetChange:[FSTTargetChange changeWithDocuments:@[ doc1 ]
- currentStatusUpdate:FSTCurrentStatusUpdateNone]];
+ change = [view applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[])]
+ targetChange:FSTTestTargetChangeAckDocuments({doc1.key})];
XCTAssertEqualObjects(
change.limboChanges,
@[ [FSTLimboDocumentChange changeWithType:FSTLimboDocumentChangeTypeRemoved key:doc1.key] ]);
- change = [view
- applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc2 ])]
- targetChange:[FSTTargetChange changeWithDocuments:@[ doc2 ]
- currentStatusUpdate:FSTCurrentStatusUpdateNone]];
+ change =
+ [view applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc2 ])]
+ targetChange:FSTTestTargetChangeAckDocuments({doc2.key})];
XCTAssertEqualObjects(change.limboChanges, @[]);
change = [view
@@ -343,11 +331,9 @@ NS_ASSUME_NONNULL_BEGIN
FSTView *view =
[[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{doc1.key, doc2.key}];
- FSTTargetChange *markCurrent =
- [FSTTargetChange changeWithDocuments:@[]
- currentStatusUpdate:FSTCurrentStatusUpdateMarkCurrent];
FSTViewDocumentChanges *changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[])];
- FSTViewChange *change = [view applyChangesToDocuments:changes targetChange:markCurrent];
+ FSTViewChange *change =
+ [view applyChangesToDocuments:changes targetChange:FSTTestTargetChangeMarkCurrent()];
XCTAssertEqualObjects(change.limboChanges, @[]);
}
diff --git a/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm b/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm
index f8091c0..e048a40 100644
--- a/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm
+++ b/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm
@@ -162,8 +162,10 @@
NSDictionary<NSString *, id> *initialData = @{
@"updated" : @NO,
};
- NSDictionary<NSString *, id> *mergeData =
- @{@"time" : [FIRFieldValue fieldValueForServerTimestamp]};
+ NSDictionary<NSString *, id> *mergeData = @{
+ @"time" : [FIRFieldValue fieldValueForServerTimestamp],
+ @"nested" : @{@"time" : [FIRFieldValue fieldValueForServerTimestamp]}
+ };
[self writeDocumentRef:doc data:initialData];
@@ -182,6 +184,7 @@
FIRDocumentSnapshot *document = [self readDocumentForRef:doc];
XCTAssertEqual(document[@"updated"], @NO);
XCTAssertTrue([document[@"time"] isKindOfClass:[FIRTimestamp class]]);
+ XCTAssertTrue([document[@"nested.time"] isKindOfClass:[FIRTimestamp class]]);
}
- (void)testCanDeleteFieldUsingMerge {
@@ -218,6 +221,81 @@
XCTAssertNil(document[@"nested.foo"]);
}
+- (void)testCanDeleteFieldUsingMergeFields {
+ FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
+
+ NSDictionary<NSString *, id> *initialData = @{
+ @"untouched" : @YES,
+ @"foo" : @"bar",
+ @"inner" : @{@"removed" : @YES, @"foo" : @"bar"},
+ @"nested" : @{@"untouched" : @YES, @"foo" : @"bar"}
+ };
+ NSDictionary<NSString *, id> *mergeData = @{
+ @"foo" : [FIRFieldValue fieldValueForDelete],
+ @"inner" : @{@"foo" : [FIRFieldValue fieldValueForDelete]},
+ @"nested" : @{
+ @"untouched" : [FIRFieldValue fieldValueForDelete],
+ @"foo" : [FIRFieldValue fieldValueForDelete]
+ }
+ };
+ NSDictionary<NSString *, id> *finalData =
+ @{ @"untouched" : @YES,
+ @"inner" : @{},
+ @"nested" : @{@"untouched" : @YES} };
+
+ [self writeDocumentRef:doc data:initialData];
+
+ XCTestExpectation *completed =
+ [self expectationWithDescription:@"testCanMergeDataWithAnExistingDocumentUsingSet"];
+
+ [doc setData:mergeData
+ mergeFields:@[ @"foo", @"inner", @"nested.foo" ]
+ completion:^(NSError *error) {
+ XCTAssertNil(error);
+ [completed fulfill];
+ }];
+
+ [self awaitExpectations];
+
+ FIRDocumentSnapshot *document = [self readDocumentForRef:doc];
+ XCTAssertEqualObjects([document data], finalData);
+}
+
+- (void)testCanSetServerTimestampsUsingMergeFields {
+ FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
+
+ NSDictionary<NSString *, id> *initialData = @{
+ @"untouched" : @YES,
+ @"foo" : @"bar",
+ @"nested" : @{@"untouched" : @YES, @"foo" : @"bar"}
+ };
+ NSDictionary<NSString *, id> *mergeData = @{
+ @"foo" : [FIRFieldValue fieldValueForServerTimestamp],
+ @"inner" : @{@"foo" : [FIRFieldValue fieldValueForServerTimestamp]},
+ @"nested" : @{@"foo" : [FIRFieldValue fieldValueForServerTimestamp]}
+ };
+
+ [self writeDocumentRef:doc data:initialData];
+
+ XCTestExpectation *completed =
+ [self expectationWithDescription:@"testCanMergeDataWithAnExistingDocumentUsingSet"];
+
+ [doc setData:mergeData
+ mergeFields:@[ @"foo", @"inner", @"nested.foo" ]
+ completion:^(NSError *error) {
+ XCTAssertNil(error);
+ [completed fulfill];
+ }];
+
+ [self awaitExpectations];
+
+ FIRDocumentSnapshot *document = [self readDocumentForRef:doc];
+ XCTAssertTrue([document exists]);
+ XCTAssertTrue([document[@"foo"] isKindOfClass:[FIRTimestamp class]]);
+ XCTAssertTrue([document[@"inner.foo"] isKindOfClass:[FIRTimestamp class]]);
+ XCTAssertTrue([document[@"nested.foo"] isKindOfClass:[FIRTimestamp class]]);
+}
+
- (void)testMergeReplacesArrays {
FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
diff --git a/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm b/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm
index e6e1a19..cba9017 100644
--- a/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm
+++ b/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm
@@ -48,6 +48,7 @@ namespace util = firebase::firestore::util;
using firebase::firestore::auth::EmptyCredentialsProvider;
using firebase::firestore::core::DatabaseInfo;
using firebase::firestore::model::DatabaseId;
+using firebase::firestore::model::DocumentKeySet;
using firebase::firestore::model::Precondition;
using firebase::firestore::model::TargetId;
@@ -121,6 +122,10 @@ NS_ASSUME_NONNULL_BEGIN
HARD_FAIL("Not implemented");
}
+- (DocumentKeySet)remoteKeysForTarget:(FSTBoxedTargetID *)targetId {
+ return DocumentKeySet{};
+}
+
- (void)applyRemoteEvent:(FSTRemoteEvent *)remoteEvent {
[self.listenEvents addObject:remoteEvent];
XCTestExpectation *expectation = [self.listenEventExpectations objectAtIndex:0];
diff --git a/Firestore/Example/Tests/Integration/FSTStreamTests.mm b/Firestore/Example/Tests/Integration/FSTStreamTests.mm
index b944a93..3ad6868 100644
--- a/Firestore/Example/Tests/Integration/FSTStreamTests.mm
+++ b/Firestore/Example/Tests/Integration/FSTStreamTests.mm
@@ -18,8 +18,11 @@
#import <GRPCClient/GRPCCall.h>
+#import <FirebaseFirestore/FIRFirestoreErrors.h>
#import <FirebaseFirestore/FIRFirestoreSettings.h>
+#include <utility>
+
#import "Firestore/Example/Tests/Util/FSTHelpers.h"
#import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
#import "Firestore/Source/Remote/FSTDatastore.h"
@@ -327,4 +330,74 @@ using firebase::firestore::model::SnapshotVersion;
]];
}
+class MockCredentialsProvider : public firebase::firestore::auth::EmptyCredentialsProvider {
+ public:
+ MockCredentialsProvider() {
+ observed_states_ = [NSMutableArray new];
+ }
+
+ void GetToken(firebase::firestore::auth::TokenListener completion) override {
+ [observed_states_ addObject:@"GetToken"];
+ EmptyCredentialsProvider::GetToken(std::move(completion));
+ }
+
+ void InvalidateToken() override {
+ [observed_states_ addObject:@"InvalidateToken"];
+ EmptyCredentialsProvider::InvalidateToken();
+ }
+
+ NSMutableArray<NSString *> *observed_states() const {
+ return observed_states_;
+ }
+
+ private:
+ NSMutableArray<NSString *> *observed_states_;
+};
+
+- (void)testStreamRefreshesTokenUponExpiration {
+ MockCredentialsProvider credentials;
+ FSTDatastore *datastore = [[FSTDatastore alloc] initWithDatabaseInfo:&_databaseInfo
+ workerDispatchQueue:_workerDispatchQueue
+ credentials:&credentials];
+ FSTWatchStream *watchStream = [datastore createWatchStream];
+
+ [_delegate awaitNotificationFromBlock:^{
+ [watchStream startWithDelegate:_delegate];
+ }];
+
+ // Simulate callback from GRPC with an unauthenticated error -- this should invalidate the token.
+ NSError *unauthenticatedError = [NSError errorWithDomain:FIRFirestoreErrorDomain
+ code:FIRFirestoreErrorCodeUnauthenticated
+ userInfo:nil];
+ dispatch_async(_testQueue, ^{
+ [watchStream.callbackFilter writesFinishedWithError:unauthenticatedError];
+ });
+ // Drain the queue.
+ dispatch_sync(_testQueue, ^{
+ });
+
+ // Try reconnecting.
+ [_delegate awaitNotificationFromBlock:^{
+ [watchStream startWithDelegate:_delegate];
+ }];
+ // Simulate a different error -- token should not be invalidated this time.
+ NSError *unavailableError = [NSError errorWithDomain:FIRFirestoreErrorDomain
+ code:FIRFirestoreErrorCodeUnavailable
+ userInfo:nil];
+ dispatch_async(_testQueue, ^{
+ [watchStream.callbackFilter writesFinishedWithError:unavailableError];
+ });
+ dispatch_sync(_testQueue, ^{
+ });
+
+ [_delegate awaitNotificationFromBlock:^{
+ [watchStream startWithDelegate:_delegate];
+ }];
+ dispatch_sync(_testQueue, ^{
+ });
+
+ NSArray<NSString *> *expected = @[ @"GetToken", @"InvalidateToken", @"GetToken", @"GetToken" ];
+ XCTAssertEqualObjects(credentials.observed_states(), expected);
+}
+
@end
diff --git a/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm b/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm
index 3da8083..e6ab720 100644
--- a/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm
+++ b/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm
@@ -42,7 +42,7 @@ using leveldb::Status;
@end
@implementation FSTLevelDBMigrationsTests {
- std::shared_ptr<DB> _db;
+ std::unique_ptr<DB> _db;
}
- (void)setUp {
@@ -62,12 +62,12 @@ using leveldb::Status;
}
- (void)testAddsTargetGlobal {
- FSTPBTargetGlobal *metadata = [FSTLevelDBQueryCache readTargetMetadataFromDB:_db];
+ FSTPBTargetGlobal *metadata = [FSTLevelDBQueryCache readTargetMetadataFromDB:_db.get()];
XCTAssertNil(metadata, @"Not expecting metadata yet, we should have an empty db");
LevelDbTransaction transaction(_db.get(), "testAddsTargetGlobal");
[FSTLevelDBMigrations runMigrationsWithTransaction:&transaction];
transaction.Commit();
- metadata = [FSTLevelDBQueryCache readTargetMetadataFromDB:_db];
+ metadata = [FSTLevelDBQueryCache readTargetMetadataFromDB:_db.get()];
XCTAssertNotNil(metadata, @"Migrations should have added the metadata");
}
@@ -107,7 +107,7 @@ using leveldb::Status;
LevelDbTransaction transaction(_db.get(), "testCountsQueries");
[FSTLevelDBMigrations runMigrationsWithTransaction:&transaction];
transaction.Commit();
- FSTPBTargetGlobal *metadata = [FSTLevelDBQueryCache readTargetMetadataFromDB:_db];
+ FSTPBTargetGlobal *metadata = [FSTLevelDBQueryCache readTargetMetadataFromDB:_db.get()];
XCTAssertEqual(expected, metadata.targetCount, @"Failed to count all of the targets we added");
}
}
diff --git a/Firestore/Example/Tests/Local/FSTLevelDBTransactionTests.mm b/Firestore/Example/Tests/Local/FSTLevelDBTransactionTests.mm
index 29f5d6c..5c2718b 100644
--- a/Firestore/Example/Tests/Local/FSTLevelDBTransactionTests.mm
+++ b/Firestore/Example/Tests/Local/FSTLevelDBTransactionTests.mm
@@ -46,7 +46,7 @@ using firebase::firestore::local::LevelDbTransaction;
@end
@implementation FSTLevelDBTransactionTests {
- std::shared_ptr<DB> _db;
+ std::unique_ptr<DB> _db;
}
- (void)setUp {
diff --git a/Firestore/Example/Tests/Local/FSTLocalStoreTests.mm b/Firestore/Example/Tests/Local/FSTLocalStoreTests.mm
index e10fb12..8760571 100644
--- a/Firestore/Example/Tests/Local/FSTLocalStoreTests.mm
+++ b/Firestore/Example/Tests/Local/FSTLocalStoreTests.mm
@@ -609,8 +609,8 @@ NS_ASSUME_NONNULL_BEGIN
FSTQuery *query = FSTTestQuery("foo");
FSTTargetID targetID = [self allocateQuery:query];
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, NO),
- @[ @(targetID) ], @[])];
+ [self applyRemoteEvent:FSTTestAddedRemoteEvent(FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, NO),
+ @[ @(targetID) ])];
[self collectGarbage];
FSTAssertContains(FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, NO));
@@ -703,8 +703,8 @@ NS_ASSUME_NONNULL_BEGIN
FSTQuery *query = FSTTestQuery("foo");
FSTTargetID targetID = [self allocateQuery:query];
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, NO),
- @[ @(targetID) ], @[])];
+ [self applyRemoteEvent:FSTTestAddedRemoteEvent(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, NO),
+ @[ @(targetID) ])];
[self writeMutation:FSTTestSetMutation(@"foo/baz", @{@"foo" : @"baz"})];
[self collectGarbage];
FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, NO));
@@ -805,21 +805,19 @@ NS_ASSUME_NONNULL_BEGIN
FSTBoxedTargetID *targetID = @(queryData.targetID);
NSData *resumeToken = FSTTestResumeTokenFromSnapshotVersion(1000);
- FSTWatchChange *watchChange =
+ FSTWatchTargetChange *watchChange =
[FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent
targetIDs:@[ targetID ]
resumeToken:resumeToken];
NSMutableDictionary<FSTBoxedTargetID *, FSTQueryData *> *listens =
[NSMutableDictionary dictionary];
listens[targetID] = queryData;
- NSMutableDictionary<FSTBoxedTargetID *, NSNumber *> *pendingResponses =
- [NSMutableDictionary dictionary];
- FSTWatchChangeAggregator *aggregator =
- [[FSTWatchChangeAggregator alloc] initWithSnapshotVersion:testutil::Version(1000)
- listenTargets:listens
- pendingTargetResponses:pendingResponses];
- [aggregator addWatchChanges:@[ watchChange ]];
- FSTRemoteEvent *remoteEvent = [aggregator remoteEvent];
+ FSTWatchChangeAggregator *aggregator = [[FSTWatchChangeAggregator alloc]
+ initWithTargetMetadataProvider:[FSTTestTargetMetadataProvider
+ providerWithSingleResultForKey:testutil::Key("foo/bar")
+ targets:@[ targetID ]]];
+ [aggregator handleTargetChange:watchChange];
+ FSTRemoteEvent *remoteEvent = [aggregator remoteEventAtSnapshotVersion:testutil::Version(1000)];
[self applyRemoteEvent:remoteEvent];
// Stop listening so that the query should become inactive (but persistent)
@@ -842,10 +840,10 @@ NS_ASSUME_NONNULL_BEGIN
[self allocateQuery:query];
FSTAssertTargetID(2);
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/baz", 10, @{@"a" : @"b"}, NO),
- @[ @2 ], @[])];
- [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 20, @{@"a" : @"b"}, NO),
- @[ @2 ], @[])];
+ [self applyRemoteEvent:FSTTestAddedRemoteEvent(FSTTestDoc("foo/baz", 10, @{@"a" : @"b"}, NO),
+ @[ @2 ])];
+ [self applyRemoteEvent:FSTTestAddedRemoteEvent(FSTTestDoc("foo/bar", 20, @{@"a" : @"b"}, NO),
+ @[ @2 ])];
[self.localStore locallyWriteMutations:@[ FSTTestSetMutation(@"foo/bonk", @{@"a" : @"b"}) ]];
diff --git a/Firestore/Example/Tests/Remote/FSTDatastoreTests.mm b/Firestore/Example/Tests/Remote/FSTDatastoreTests.mm
index 6d6e912..9783e37 100644
--- a/Firestore/Example/Tests/Remote/FSTDatastoreTests.mm
+++ b/Firestore/Example/Tests/Remote/FSTDatastoreTests.mm
@@ -53,6 +53,12 @@
code:FIRFirestoreErrorCodeUnavailable
userInfo:nil];
XCTAssertFalse([FSTDatastore isPermanentWriteError:error]);
+
+ // "unauthenticated" is considered a recoverable error due to expired token.
+ error = [NSError errorWithDomain:FIRFirestoreErrorDomain
+ code:FIRFirestoreErrorCodeUnauthenticated
+ userInfo:nil];
+ XCTAssertFalse([FSTDatastore isPermanentWriteError:error]);
}
@end
diff --git a/Firestore/Example/Tests/Remote/FSTRemoteEventTests.mm b/Firestore/Example/Tests/Remote/FSTRemoteEventTests.mm
index 84d0fa1..c6936f7 100644
--- a/Firestore/Example/Tests/Remote/FSTRemoteEventTests.mm
+++ b/Firestore/Example/Tests/Remote/FSTRemoteEventTests.mm
@@ -34,6 +34,7 @@
namespace testutil = firebase::firestore::testutil;
using firebase::firestore::model::DocumentKey;
using firebase::firestore::model::DocumentKeySet;
+using firebase::firestore::model::SnapshotVersion;
NS_ASSUME_NONNULL_BEGIN
@@ -42,86 +43,204 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FSTRemoteEventTests {
NSData *_resumeToken1;
- NSMutableDictionary<NSNumber *, NSNumber *> *_noPendingResponses;
+ NSMutableDictionary<NSNumber *, NSNumber *> *_noOutstandingResponses;
+ FSTTestTargetMetadataProvider *_targetMetadataProvider;
}
- (void)setUp {
_resumeToken1 = [@"resume1" dataUsingEncoding:NSUTF8StringEncoding];
- _noPendingResponses = [NSMutableDictionary dictionary];
+ _noOutstandingResponses = [NSMutableDictionary dictionary];
+ _targetMetadataProvider = [FSTTestTargetMetadataProvider new];
}
-- (FSTWatchChangeAggregator *)aggregatorWithTargets:(NSArray<NSNumber *> *)targets
- outstanding:
- (NSDictionary<NSNumber *, NSNumber *> *)outstanding
- changes:(NSArray<FSTWatchChange *> *)watchChanges {
- NSMutableDictionary<NSNumber *, FSTQueryData *> *listens = [NSMutableDictionary dictionary];
- FSTQueryData *dummyQueryData = [FSTQueryData alloc];
- for (NSNumber *targetID in targets) {
- listens[targetID] = dummyQueryData;
+/**
+ * Creates a map with query data for the provided target IDs. All targets are considered active
+ * and query a collection named "coll".
+ */
+- (NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *)queryDataForTargets:
+ (NSArray<FSTBoxedTargetID *> *)targetIDs {
+ NSMutableDictionary<FSTBoxedTargetID *, FSTQueryData *> *targets =
+ [NSMutableDictionary dictionary];
+ for (FSTBoxedTargetID *targetID in targetIDs) {
+ FSTQuery *query = FSTTestQuery("coll");
+ targets[targetID] = [[FSTQueryData alloc] initWithQuery:query
+ targetID:targetID.intValue
+ listenSequenceNumber:0
+ purpose:FSTQueryPurposeListen];
+ }
+ return targets;
+}
+
+/**
+ * Creates a map with query data for the provided target IDs. All targets are marked as limbo
+ * queries for the document at "coll/limbo".
+ */
+- (NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *)queryDataForLimboTargets:
+ (NSArray<FSTBoxedTargetID *> *)targetIDs {
+ NSMutableDictionary<FSTBoxedTargetID *, FSTQueryData *> *targets =
+ [NSMutableDictionary dictionary];
+ for (FSTBoxedTargetID *targetID in targetIDs) {
+ FSTQuery *query = FSTTestQuery("coll/limbo");
+ targets[targetID] = [[FSTQueryData alloc] initWithQuery:query
+ targetID:targetID.intValue
+ listenSequenceNumber:0
+ purpose:FSTQueryPurposeLimboResolution];
}
+ return targets;
+}
+
+/**
+ * Creates an aggregator initialized with the set of provided FSTWatchChanges. Tests can add further
+ * changes via `handleDocumentChange`, `handleTargetChange` and `handleExistenceFilterChange`.
+ *
+ * @param snapshotVersion The version at which to create the remote event. This corresponds to the
+ * snapshot version provided by a NO_CHANGE event.
+ * @param targetMap A map of query data for all active targets. The map must include an entry for
+ * every target referenced by any of the watch changes.
+ * @param outstandingResponses The number of outstanding ACKs a target has to receive before it is
+ * considered active, or `_noOutstandingResponses` if all targets are already active.
+ * @param existingKeys The set of documents that are considered synced with the test targets as
+ * part of a previous listen. To modify this set during test execution, invoke
+ * `[_targetMetadataProvider setSyncedKeys:forQueryData:]`.
+ * @param watchChanges The watch changes to apply before returning the aggregator. Supported
+ * changes are FSTDocumentWatchChange and FSTWatchTargetChange.
+ */
+- (FSTWatchChangeAggregator *)
+aggregatorWithTargetMap:(NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *)targetMap
+ outstandingResponses:
+ (nullable NSDictionary<FSTBoxedTargetID *, NSNumber *> *)outstandingResponses
+ existingKeys:(DocumentKeySet)existingKeys
+ changes:(NSArray<FSTWatchChange *> *)watchChanges {
FSTWatchChangeAggregator *aggregator =
- [[FSTWatchChangeAggregator alloc] initWithSnapshotVersion:testutil::Version(3)
- listenTargets:listens
- pendingTargetResponses:outstanding];
- [aggregator addWatchChanges:watchChanges];
+ [[FSTWatchChangeAggregator alloc] initWithTargetMetadataProvider:_targetMetadataProvider];
+
+ NSMutableArray<FSTBoxedTargetID *> *targetIDs = [NSMutableArray array];
+ [targetMap enumerateKeysAndObjectsUsingBlock:^(FSTBoxedTargetID *targetID,
+ FSTQueryData *queryData, BOOL *stop) {
+ [targetIDs addObject:targetID];
+ [_targetMetadataProvider setSyncedKeys:existingKeys forQueryData:queryData];
+ }];
+
+ [outstandingResponses
+ enumerateKeysAndObjectsUsingBlock:^(FSTBoxedTargetID *targetID, NSNumber *count, BOOL *stop) {
+ for (int i = 0; i < count.intValue; ++i) {
+ [aggregator recordTargetRequest:targetID];
+ }
+ }];
+
+ for (FSTWatchChange *change in watchChanges) {
+ if ([change isKindOfClass:[FSTDocumentWatchChange class]]) {
+ [aggregator handleDocumentChange:(FSTDocumentWatchChange *)change];
+ } else if ([change isKindOfClass:[FSTWatchTargetChange class]]) {
+ [aggregator handleTargetChange:(FSTWatchTargetChange *)change];
+ } else {
+ HARD_ASSERT("Encountered unexpected type of FSTWatchChange");
+ }
+ }
+
+ [aggregator handleTargetChange:[[FSTWatchTargetChange alloc]
+ initWithState:FSTWatchTargetChangeStateNoChange
+ targetIDs:targetIDs
+ resumeToken:_resumeToken1
+ cause:nil]];
+
return aggregator;
}
+/**
+ * Creates a single remote event that includes target changes for all provided FSTWatchChanges.
+ *
+ * @param snapshotVersion The version at which to create the remote event. This corresponds to the
+ * snapshot version provided by the NO_CHANGE event.
+ * @param targetMap A map of query data for all active targets. The map must include an entry for
+ * every target referenced by any of the watch changes.
+ * @param outstandingResponses The number of outstanding ACKs a target has to receive before it is
+ * considered active, or `_noOutstandingResponses` if all targets are already active.
+ * @param existingKeys The set of documents that are considered synced with the test targets as
+ * part of a previous listen.
+ * @param watchChanges The watch changes to apply before creating the remote event. Supported
+ * changes are FSTDocumentWatchChange and FSTWatchTargetChange.
+ */
+- (FSTRemoteEvent *)
+remoteEventAtSnapshotVersion:(FSTTestSnapshotVersion)snapshotVersion
+ targetMap:(NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *)targetMap
+ outstandingResponses:
+ (nullable NSDictionary<FSTBoxedTargetID *, NSNumber *> *)outstandingResponses
+ existingKeys:(DocumentKeySet)existingKeys
+ changes:(NSArray<FSTWatchChange *> *)watchChanges {
+ FSTWatchChangeAggregator *aggregator = [self aggregatorWithTargetMap:targetMap
+ outstandingResponses:outstandingResponses
+ existingKeys:existingKeys
+ changes:watchChanges];
+ return [aggregator remoteEventAtSnapshotVersion:testutil::Version(snapshotVersion)];
+}
+
- (void)testWillAccumulateDocumentAddedAndRemovedEvents {
- FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
- FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{ @"value" : @2 }, NO);
+ // The target map that contains an entry for every target in this test. If a target ID is omitted,
+ // the target is considered inactive and FSTTestTargetMetadataProvider will fail on access.
+ NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap =
+ [self queryDataForTargets:@[ @1, @2, @3, @4, @5, @6 ]];
+ FSTDocument *existingDoc = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1, @2, @3 ]
removedTargetIDs:@[ @4, @5, @6 ]
- documentKey:doc1.key
- document:doc1];
+ documentKey:existingDoc.key
+ document:existingDoc];
+ FSTDocument *newDoc = FSTTestDoc("docs/2", 2, @{ @"value" : @2 }, NO);
FSTWatchChange *change2 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1, @4 ]
removedTargetIDs:@[ @2, @6 ]
- documentKey:doc2.key
- document:doc2];
-
- FSTWatchChangeAggregator *aggregator = [self aggregatorWithTargets:@[ @1, @2, @3, @4, @5, @6 ]
- outstanding:_noPendingResponses
- changes:@[ change1, change2 ]];
-
- FSTRemoteEvent *event = [aggregator remoteEvent];
+ documentKey:newDoc.key
+ document:newDoc];
+
+ // Create a remote event that includes both `change1` and `change2` as well as a NO_CHANGE event
+ // with the default resume token (`_resumeToken1`).
+ // As `existingDoc` is provided as an existing key, any updates to this document will be treated
+ // as modifications rather than adds.
+ FSTRemoteEvent *event = [self remoteEventAtSnapshotVersion:3
+ targetMap:targetMap
+ outstandingResponses:_noOutstandingResponses
+ existingKeys:DocumentKeySet{existingDoc.key}
+ changes:@[ change1, change2 ]];
XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.documentUpdates.size(), 2);
- XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), doc1);
- XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
+ XCTAssertEqualObjects(event.documentUpdates.at(existingDoc.key), existingDoc);
+ XCTAssertEqualObjects(event.documentUpdates.at(newDoc.key), newDoc);
- XCTAssertEqual(event.targetChanges.count, 6);
+ // 'change1' and 'change2' affect six different targets
+ XCTAssertEqual(event.targetChanges.size(), 6);
- FSTUpdateMapping *mapping1 =
- [FSTUpdateMapping mappingWithAddedDocuments:@[ doc1, doc2 ] removedDocuments:@[]];
- XCTAssertEqualObjects(event.targetChanges[@1].mapping, mapping1);
+ FSTTargetChange *targetChange1 =
+ FSTTestTargetChange(DocumentKeySet{newDoc.key}, DocumentKeySet{existingDoc.key},
+ DocumentKeySet{}, _resumeToken1, NO);
+ XCTAssertEqualObjects(event.targetChanges.at(1), targetChange1);
- FSTUpdateMapping *mapping2 =
- [FSTUpdateMapping mappingWithAddedDocuments:@[ doc1 ] removedDocuments:@[ doc2 ]];
- XCTAssertEqualObjects(event.targetChanges[@2].mapping, mapping2);
+ FSTTargetChange *targetChange2 = FSTTestTargetChange(
+ DocumentKeySet{}, DocumentKeySet{existingDoc.key}, DocumentKeySet{}, _resumeToken1, NO);
+ XCTAssertEqualObjects(event.targetChanges.at(2), targetChange2);
- FSTUpdateMapping *mapping3 =
- [FSTUpdateMapping mappingWithAddedDocuments:@[ doc1 ] removedDocuments:@[]];
- XCTAssertEqualObjects(event.targetChanges[@3].mapping, mapping3);
+ FSTTargetChange *targetChange3 = FSTTestTargetChange(
+ DocumentKeySet{}, DocumentKeySet{existingDoc.key}, DocumentKeySet{}, _resumeToken1, NO);
+ XCTAssertEqualObjects(event.targetChanges.at(3), targetChange3);
- FSTUpdateMapping *mapping4 =
- [FSTUpdateMapping mappingWithAddedDocuments:@[ doc2 ] removedDocuments:@[ doc1 ]];
- XCTAssertEqualObjects(event.targetChanges[@4].mapping, mapping4);
+ FSTTargetChange *targetChange4 =
+ FSTTestTargetChange(DocumentKeySet{newDoc.key}, DocumentKeySet{},
+ DocumentKeySet{existingDoc.key}, _resumeToken1, NO);
+ XCTAssertEqualObjects(event.targetChanges.at(4), targetChange4);
- FSTUpdateMapping *mapping5 =
- [FSTUpdateMapping mappingWithAddedDocuments:@[] removedDocuments:@[ doc1 ]];
- XCTAssertEqualObjects(event.targetChanges[@5].mapping, mapping5);
+ FSTTargetChange *targetChange5 = FSTTestTargetChange(
+ DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{existingDoc.key}, _resumeToken1, NO);
+ XCTAssertEqualObjects(event.targetChanges.at(5), targetChange5);
- FSTUpdateMapping *mapping6 =
- [FSTUpdateMapping mappingWithAddedDocuments:@[] removedDocuments:@[ doc1, doc2 ]];
- XCTAssertEqualObjects(event.targetChanges[@6].mapping, mapping6);
+ FSTTargetChange *targetChange6 = FSTTestTargetChange(
+ DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{existingDoc.key}, _resumeToken1, NO);
+ XCTAssertEqualObjects(event.targetChanges.at(6), targetChange6);
}
- (void)testWillIgnoreEventsForPendingTargets {
- FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
- FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{ @"value" : @2 }, NO);
+ NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap = [self queryDataForTargets:@[ @1 ]];
+ FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
removedTargetIDs:@[]
documentKey:doc1.key
@@ -135,31 +254,34 @@ NS_ASSUME_NONNULL_BEGIN
targetIDs:@[ @1 ]
cause:nil];
+ FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{ @"value" : @2 }, NO);
FSTWatchChange *change4 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
removedTargetIDs:@[]
documentKey:doc2.key
document:doc2];
// We're waiting for the unwatch and watch ack
- NSDictionary<NSNumber *, NSNumber *> *pendingResponses = @{ @1 : @2 };
-
- FSTWatchChangeAggregator *aggregator =
- [self aggregatorWithTargets:@[ @1 ]
- outstanding:pendingResponses
- changes:@[ change1, change2, change3, change4 ]];
- FSTRemoteEvent *event = [aggregator remoteEvent];
+ NSDictionary<NSNumber *, NSNumber *> *outstandingResponses = @{ @1 : @2 };
+
+ FSTRemoteEvent *event =
+ [self remoteEventAtSnapshotVersion:3
+ targetMap:targetMap
+ outstandingResponses:outstandingResponses
+ existingKeys:DocumentKeySet {}
+ changes:@[ change1, change2, change3, change4 ]];
XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
// doc1 is ignored because it was part of an inactive target, but doc2 is in the changes
// because it become active.
XCTAssertEqual(event.documentUpdates.size(), 1);
XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
- XCTAssertEqual(event.targetChanges.count, 1);
+ XCTAssertEqual(event.targetChanges.size(), 1);
}
- (void)testWillIgnoreEventsForRemovedTargets {
- FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
+ NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap = [self queryDataForTargets:@[]];
+ FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
removedTargetIDs:@[]
documentKey:doc1.key
@@ -170,25 +292,25 @@ NS_ASSUME_NONNULL_BEGIN
cause:nil];
// We're waiting for the unwatch ack
- NSDictionary<NSNumber *, NSNumber *> *pendingResponses = @{ @1 : @1 };
+ NSDictionary<NSNumber *, NSNumber *> *outstandingResponses = @{ @1 : @1 };
- FSTWatchChangeAggregator *aggregator =
- [self aggregatorWithTargets:@[] outstanding:pendingResponses changes:@[ change1, change2 ]];
-
- FSTRemoteEvent *event = [aggregator remoteEvent];
+ FSTRemoteEvent *event = [self remoteEventAtSnapshotVersion:3
+ targetMap:targetMap
+ outstandingResponses:outstandingResponses
+ existingKeys:DocumentKeySet {}
+ changes:@[ change1, change2 ]];
XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
// doc1 is ignored because it was part of an inactive target
XCTAssertEqual(event.documentUpdates.size(), 0);
// Target 1 is ignored because it was removed
- XCTAssertEqual(event.targetChanges.count, 0);
+ XCTAssertEqual(event.targetChanges.size(), 0);
}
- (void)testWillKeepResetMappingEvenWithUpdates {
- FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
- FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{ @"value" : @2 }, NO);
- FSTDocument *doc3 = FSTTestDoc("docs/3", 3, @{ @"value" : @3 }, NO);
+ NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap = [self queryDataForTargets:@[ @1 ]];
+ FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
removedTargetIDs:@[]
documentKey:doc1.key
@@ -199,10 +321,13 @@ NS_ASSUME_NONNULL_BEGIN
cause:nil];
// Add doc2, doc3
+ FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{ @"value" : @2 }, NO);
FSTWatchChange *change3 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
removedTargetIDs:@[]
documentKey:doc2.key
document:doc2];
+
+ FSTDocument *doc3 = FSTTestDoc("docs/3", 3, @{ @"value" : @3 }, NO);
FSTWatchChange *change4 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
removedTargetIDs:@[]
documentKey:doc3.key
@@ -213,203 +338,208 @@ NS_ASSUME_NONNULL_BEGIN
removedTargetIDs:@[ @1 ]
documentKey:doc2.key
document:doc2];
-
- FSTWatchChangeAggregator *aggregator =
- [self aggregatorWithTargets:@[ @1 ]
- outstanding:_noPendingResponses
- changes:@[ change1, change2, change3, change4, change5 ]];
-
- FSTRemoteEvent *event = [aggregator remoteEvent];
+ FSTRemoteEvent *event =
+ [self remoteEventAtSnapshotVersion:3
+ targetMap:targetMap
+ outstandingResponses:_noOutstandingResponses
+ existingKeys:DocumentKeySet{doc1.key}
+ changes:@[ change1, change2, change3, change4, change5 ]];
XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.documentUpdates.size(), 3);
XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), doc1);
XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
XCTAssertEqualObjects(event.documentUpdates.at(doc3.key), doc3);
- XCTAssertEqual(event.targetChanges.count, 1);
+ XCTAssertEqual(event.targetChanges.size(), 1);
// Only doc3 is part of the new mapping
- FSTResetMapping *expectedMapping = [FSTResetMapping mappingWithDocuments:@[ doc3 ]];
-
- XCTAssertEqualObjects(event.targetChanges[@1].mapping, expectedMapping);
+ FSTTargetChange *expectedChange = FSTTestTargetChange(
+ DocumentKeySet{doc3.key}, DocumentKeySet{}, DocumentKeySet{doc1.key}, _resumeToken1, NO);
+ XCTAssertEqualObjects(event.targetChanges.at(1), expectedChange);
}
- (void)testWillHandleSingleReset {
+ NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap = [self queryDataForTargets:@[ @1 ]];
+
// Reset target
- FSTWatchChange *change = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateReset
- targetIDs:@[ @1 ]
- cause:nil];
+ FSTWatchTargetChange *change =
+ [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateReset
+ targetIDs:@[ @1 ]
+ cause:nil];
- FSTWatchChangeAggregator *aggregator =
- [self aggregatorWithTargets:@[ @1 ] outstanding:_noPendingResponses changes:@[ change ]];
+ FSTWatchChangeAggregator *aggregator = [self aggregatorWithTargetMap:targetMap
+ outstandingResponses:_noOutstandingResponses
+ existingKeys:DocumentKeySet {}
+ changes:@[]];
+ [aggregator handleTargetChange:change];
+
+ FSTRemoteEvent *event = [aggregator remoteEventAtSnapshotVersion:testutil::Version(3)];
- FSTRemoteEvent *event = [aggregator remoteEvent];
XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.documentUpdates.size(), 0);
-
- XCTAssertEqual(event.targetChanges.count, 1);
+ XCTAssertEqual(event.targetChanges.size(), 1);
// Reset mapping is empty
- FSTResetMapping *expectedMapping = [FSTResetMapping mappingWithDocuments:@[]];
- XCTAssertEqualObjects(event.targetChanges[@1].mapping, expectedMapping);
+ FSTTargetChange *expectedChange =
+ FSTTestTargetChange(DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{}, [NSData data], NO);
+ XCTAssertEqualObjects(event.targetChanges.at(1), expectedChange);
}
- (void)testWillHandleTargetAddAndRemovalInSameBatch {
- FSTDocument *doc1a = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
- FSTDocument *doc1b = FSTTestDoc("docs/1", 1, @{ @"value" : @2 }, NO);
+ NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap =
+ [self queryDataForTargets:@[ @1, @2 ]];
+ FSTDocument *doc1a = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
removedTargetIDs:@[ @2 ]
documentKey:doc1a.key
document:doc1a];
+ FSTDocument *doc1b = FSTTestDoc("docs/1", 1, @{ @"value" : @2 }, NO);
FSTWatchChange *change2 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @2 ]
removedTargetIDs:@[ @1 ]
documentKey:doc1b.key
document:doc1b];
- FSTWatchChangeAggregator *aggregator = [self aggregatorWithTargets:@[ @1, @2 ]
- outstanding:_noPendingResponses
- changes:@[ change1, change2 ]];
- FSTRemoteEvent *event = [aggregator remoteEvent];
+ FSTRemoteEvent *event = [self remoteEventAtSnapshotVersion:3
+ targetMap:targetMap
+ outstandingResponses:_noOutstandingResponses
+ existingKeys:DocumentKeySet{doc1a.key}
+ changes:@[ change1, change2 ]];
XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.documentUpdates.size(), 1);
XCTAssertEqualObjects(event.documentUpdates.at(doc1b.key), doc1b);
- XCTAssertEqual(event.targetChanges.count, 2);
+ XCTAssertEqual(event.targetChanges.size(), 2);
- FSTUpdateMapping *mapping1 =
- [FSTUpdateMapping mappingWithAddedDocuments:@[] removedDocuments:@[ doc1b ]];
- XCTAssertEqualObjects(event.targetChanges[@1].mapping, mapping1);
+ FSTTargetChange *targetChange1 = FSTTestTargetChange(
+ DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{doc1b.key}, _resumeToken1, NO);
+ XCTAssertEqualObjects(event.targetChanges.at(1), targetChange1);
- FSTUpdateMapping *mapping2 =
- [FSTUpdateMapping mappingWithAddedDocuments:@[ doc1b ] removedDocuments:@[]];
- XCTAssertEqualObjects(event.targetChanges[@2].mapping, mapping2);
+ FSTTargetChange *targetChange2 = FSTTestTargetChange(DocumentKeySet{}, DocumentKeySet{doc1b.key},
+ DocumentKeySet{}, _resumeToken1, NO);
+ XCTAssertEqualObjects(event.targetChanges.at(2), targetChange2);
}
- (void)testTargetCurrentChangeWillMarkTheTargetCurrent {
+ NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap = [self queryDataForTargets:@[ @1 ]];
+
FSTWatchChange *change = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent
targetIDs:@[ @1 ]
resumeToken:_resumeToken1];
+ FSTRemoteEvent *event = [self remoteEventAtSnapshotVersion:3
+ targetMap:targetMap
+ outstandingResponses:_noOutstandingResponses
+ existingKeys:DocumentKeySet {}
+ changes:@[ change ]];
- FSTWatchChangeAggregator *aggregator =
- [self aggregatorWithTargets:@[ @1 ] outstanding:_noPendingResponses changes:@[ change ]];
-
- FSTRemoteEvent *event = [aggregator remoteEvent];
XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.documentUpdates.size(), 0);
- XCTAssertEqual(event.targetChanges.count, 1);
- FSTTargetChange *targetChange = event.targetChanges[@1];
- XCTAssertEqualObjects(targetChange.mapping, [[FSTUpdateMapping alloc] init]);
- XCTAssertEqual(targetChange.currentStatusUpdate, FSTCurrentStatusUpdateMarkCurrent);
- XCTAssertEqualObjects(targetChange.resumeToken, _resumeToken1);
+ XCTAssertEqual(event.targetChanges.size(), 1);
+
+ FSTTargetChange *targetChange =
+ FSTTestTargetChange(DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{}, _resumeToken1, YES);
+ XCTAssertEqualObjects(event.targetChanges.at(1), targetChange);
}
- (void)testTargetAddedChangeWillResetPreviousState {
- FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
- FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{ @"value" : @2 }, NO);
+ NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap =
+ [self queryDataForTargets:@[ @1, @3 ]];
+ FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1, @3 ]
removedTargetIDs:@[ @2 ]
documentKey:doc1.key
document:doc1];
+
FSTWatchChange *change2 = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent
targetIDs:@[ @1, @2, @3 ]
resumeToken:_resumeToken1];
+
FSTWatchChange *change3 = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateRemoved
targetIDs:@[ @1 ]
cause:nil];
+
FSTWatchChange *change4 = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateRemoved
targetIDs:@[ @2 ]
cause:nil];
+
FSTWatchChange *change5 = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateAdded
targetIDs:@[ @1 ]
cause:nil];
+
+ FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{ @"value" : @2 }, NO);
FSTWatchChange *change6 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
removedTargetIDs:@[ @3 ]
documentKey:doc2.key
document:doc2];
- NSDictionary<NSNumber *, NSNumber *> *pendingResponses = @{ @1 : @2, @2 : @1 };
+ NSDictionary<NSNumber *, NSNumber *> *outstandingResponses = @{ @1 : @2, @2 : @1 };
- FSTWatchChangeAggregator *aggregator =
- [self aggregatorWithTargets:@[ @1, @3 ]
- outstanding:pendingResponses
- changes:@[ change1, change2, change3, change4, change5, change6 ]];
+ FSTRemoteEvent *event =
+ [self remoteEventAtSnapshotVersion:3
+ targetMap:targetMap
+ outstandingResponses:outstandingResponses
+ existingKeys:DocumentKeySet{doc2.key}
+ changes:@[ change1, change2, change3, change4, change5, change6 ]];
- FSTRemoteEvent *event = [aggregator remoteEvent];
XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.documentUpdates.size(), 2);
XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), doc1);
XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
// target 1 and 3 are affected (1 because of re-add), target 2 is not because of remove
- XCTAssertEqual(event.targetChanges.count, 2);
+ XCTAssertEqual(event.targetChanges.size(), 2);
- // doc1 was before the remove, so it does not show up in the mapping
- FSTUpdateMapping *mapping1 =
- [FSTUpdateMapping mappingWithAddedDocuments:@[ doc2 ] removedDocuments:@[]];
- XCTAssertEqualObjects(event.targetChanges[@1].mapping, mapping1);
- // Current was before the remove
- XCTAssertEqual(event.targetChanges[@1].currentStatusUpdate, FSTCurrentStatusUpdateNone);
+ // doc1 was before the remove, so it does not show up in the mapping.
+ // Current was before the remove.
+ FSTTargetChange *targetChange1 = FSTTestTargetChange(DocumentKeySet{}, DocumentKeySet{doc2.key},
+ DocumentKeySet{}, _resumeToken1, NO);
+ XCTAssertEqualObjects(event.targetChanges.at(1), targetChange1);
// Doc1 was before the remove
- FSTUpdateMapping *mapping3 =
- [FSTUpdateMapping mappingWithAddedDocuments:@[ doc1 ] removedDocuments:@[ doc2 ]];
- XCTAssertEqualObjects(event.targetChanges[@3].mapping, mapping3);
// Current was before the remove
- XCTAssertEqual(event.targetChanges[@3].currentStatusUpdate, FSTCurrentStatusUpdateMarkCurrent);
- XCTAssertEqualObjects(event.targetChanges[@3].resumeToken, _resumeToken1);
+ FSTTargetChange *targetChange3 = FSTTestTargetChange(
+ DocumentKeySet{doc1.key}, DocumentKeySet{}, DocumentKeySet{doc2.key}, _resumeToken1, YES);
+ XCTAssertEqualObjects(event.targetChanges.at(3), targetChange3);
}
- (void)testNoChangeWillStillMarkTheAffectedTargets {
- FSTWatchChange *change = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateNoChange
- targetIDs:@[ @1 ]
- resumeToken:_resumeToken1];
+ NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap = [self queryDataForTargets:@[ @1 ]];
- FSTWatchChangeAggregator *aggregator =
- [self aggregatorWithTargets:@[ @1 ] outstanding:_noPendingResponses changes:@[ change ]];
-
- FSTRemoteEvent *event = [aggregator remoteEvent];
- XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
- XCTAssertEqual(event.documentUpdates.size(), 0);
- XCTAssertEqual(event.targetChanges.count, 1);
- XCTAssertEqualObjects(event.targetChanges[@1].mapping, [[FSTUpdateMapping alloc] init]);
- XCTAssertEqual(event.targetChanges[@1].currentStatusUpdate, FSTCurrentStatusUpdateNone);
- XCTAssertEqualObjects(event.targetChanges[@1].resumeToken, _resumeToken1);
-}
+ FSTWatchChangeAggregator *aggregator = [self aggregatorWithTargetMap:targetMap
+ outstandingResponses:_noOutstandingResponses
+ existingKeys:DocumentKeySet {}
+ changes:@[]];
-- (void)testExistenceFiltersWillReplacePreviousExistenceFilters {
- FSTExistenceFilter *filter1 = [FSTExistenceFilter filterWithCount:1];
- FSTExistenceFilter *filter2 = [FSTExistenceFilter filterWithCount:2];
- FSTWatchChange *change1 = [FSTExistenceFilterWatchChange changeWithFilter:filter1 targetID:1];
- FSTWatchChange *change2 = [FSTExistenceFilterWatchChange changeWithFilter:filter1 targetID:2];
- // replace filter1 for target 2
- FSTWatchChange *change3 = [FSTExistenceFilterWatchChange changeWithFilter:filter2 targetID:2];
+ FSTWatchTargetChange *change =
+ [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateNoChange
+ targetIDs:@[ @1 ]
+ resumeToken:_resumeToken1];
+ [aggregator handleTargetChange:change];
- FSTWatchChangeAggregator *aggregator =
- [self aggregatorWithTargets:@[ @1, @2 ]
- outstanding:_noPendingResponses
- changes:@[ change1, change2, change3 ]];
+ FSTRemoteEvent *event = [aggregator remoteEventAtSnapshotVersion:testutil::Version(3)];
- FSTRemoteEvent *event = [aggregator remoteEvent];
XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.documentUpdates.size(), 0);
- XCTAssertEqual(event.targetChanges.count, 0);
- XCTAssertEqual(aggregator.existenceFilters.count, 2);
- XCTAssertEqual(aggregator.existenceFilters[@1], filter1);
- XCTAssertEqual(aggregator.existenceFilters[@2], filter2);
+ XCTAssertEqual(event.targetChanges.size(), 1);
+
+ FSTTargetChange *targetChange =
+ FSTTestTargetChange(DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{}, _resumeToken1, NO);
+ XCTAssertEqualObjects(event.targetChanges.at(1), targetChange);
}
-- (void)testExistenceFilterMismatchResetsTarget {
- FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
- FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{ @"value" : @2 }, NO);
+- (void)testExistenceFilterMismatchClearsTarget {
+ NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap =
+ [self queryDataForTargets:@[ @1, @2 ]];
+ FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
removedTargetIDs:@[]
documentKey:doc1.key
document:doc1];
+ FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{ @"value" : @2 }, NO);
FSTWatchChange *change2 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
removedTargetIDs:@[]
documentKey:doc2.key
@@ -420,270 +550,353 @@ NS_ASSUME_NONNULL_BEGIN
resumeToken:_resumeToken1];
FSTWatchChangeAggregator *aggregator =
- [self aggregatorWithTargets:@[ @1 ]
- outstanding:_noPendingResponses
- changes:@[ change1, change2, change3 ]];
+ [self aggregatorWithTargetMap:targetMap
+ outstandingResponses:_noOutstandingResponses
+ existingKeys:DocumentKeySet{doc1.key, doc2.key}
+ changes:@[ change1, change2, change3 ]];
+
+ FSTRemoteEvent *event = [aggregator remoteEventAtSnapshotVersion:testutil::Version(3)];
- FSTRemoteEvent *event = [aggregator remoteEvent];
XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.documentUpdates.size(), 2);
XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), doc1);
XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
- XCTAssertEqual(event.targetChanges.count, 1);
+ XCTAssertEqual(event.targetChanges.size(), 2);
+
+ FSTTargetChange *targetChange1 = FSTTestTargetChange(
+ DocumentKeySet{}, DocumentKeySet{doc1.key, doc2.key}, DocumentKeySet{}, _resumeToken1, YES);
+ XCTAssertEqualObjects(event.targetChanges.at(1), targetChange1);
+
+ FSTTargetChange *targetChange2 =
+ FSTTestTargetChange(DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{}, _resumeToken1, NO);
+ XCTAssertEqualObjects(event.targetChanges.at(2), targetChange2);
+
+ // The existence filter mismatch will remove the document from target 1,
+ // but not synthesize a document delete.
+ FSTExistenceFilterWatchChange *change4 =
+ [FSTExistenceFilterWatchChange changeWithFilter:[FSTExistenceFilter filterWithCount:1]
+ targetID:1];
+ [aggregator handleExistenceFilter:change4];
- FSTUpdateMapping *mapping1 =
- [FSTUpdateMapping mappingWithAddedDocuments:@[ doc1, doc2 ] removedDocuments:@[]];
- XCTAssertEqualObjects(event.targetChanges[@1].mapping, mapping1);
- XCTAssertEqual(event.targetChanges[@1].snapshotVersion, testutil::Version(3));
- XCTAssertEqual(event.targetChanges[@1].currentStatusUpdate, FSTCurrentStatusUpdateMarkCurrent);
- XCTAssertEqualObjects(event.targetChanges[@1].resumeToken, _resumeToken1);
+ event = [aggregator remoteEventAtSnapshotVersion:testutil::Version(4)];
- [event handleExistenceFilterMismatchForTargetID:@1];
+ FSTTargetChange *targetChange3 = FSTTestTargetChange(
+ DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{doc1.key, doc2.key}, [NSData data], NO);
+ XCTAssertEqualObjects(event.targetChanges.at(1), targetChange3);
- // Mapping is reset
- XCTAssertEqualObjects(event.targetChanges[@1].mapping, [[FSTResetMapping alloc] init]);
- // Reset the resume snapshot
- XCTAssertEqual(event.targetChanges[@1].snapshotVersion, testutil::Version(0));
- // Target needs to be set to not current
- XCTAssertEqual(event.targetChanges[@1].currentStatusUpdate, FSTCurrentStatusUpdateMarkNotCurrent);
- XCTAssertEqual(event.targetChanges[@1].resumeToken.length, 0);
+ XCTAssertEqual(event.targetChanges.size(), 1);
+ XCTAssertEqual(event.targetMismatches.size(), 1);
+ XCTAssertEqual(event.documentUpdates.size(), 0);
}
-- (void)testDocumentUpdate {
+- (void)testExistenceFilterMismatchRemovesCurrentChanges {
+ NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap = [self queryDataForTargets:@[ @1 ]];
+
+ FSTWatchChangeAggregator *aggregator = [self aggregatorWithTargetMap:targetMap
+ outstandingResponses:_noOutstandingResponses
+ existingKeys:DocumentKeySet {}
+ changes:@[]];
+
+ FSTWatchTargetChange *markCurrent =
+ [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent
+ targetIDs:@[ @1 ]
+ resumeToken:_resumeToken1];
+ [aggregator handleTargetChange:markCurrent];
+
FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
- FSTDeletedDocument *deletedDoc1 =
- [FSTDeletedDocument documentWithKey:doc1.key version:testutil::Version(3)];
- FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{ @"value" : @2 }, NO);
- FSTDocument *doc3 = FSTTestDoc("docs/3", 3, @{ @"value" : @3 }, NO);
+ FSTDocumentWatchChange *addDoc = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
+ removedTargetIDs:@[]
+ documentKey:doc1.key
+ document:doc1];
+ [aggregator handleDocumentChange:addDoc];
+
+ // The existence filter mismatch will remove the document from target 1, but not synthesize a
+ // document delete.
+ FSTExistenceFilterWatchChange *existenceFilter =
+ [FSTExistenceFilterWatchChange changeWithFilter:[FSTExistenceFilter filterWithCount:0]
+ targetID:1];
+ [aggregator handleExistenceFilter:existenceFilter];
+
+ FSTRemoteEvent *event = [aggregator remoteEventAtSnapshotVersion:testutil::Version(3)];
+ XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
+ XCTAssertEqual(event.documentUpdates.size(), 1);
+ XCTAssertEqual(event.targetMismatches.size(), 1);
+ XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), doc1);
+
+ XCTAssertEqual(event.targetChanges.size(), 1);
+
+ FSTTargetChange *targetChange1 =
+ FSTTestTargetChange(DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{}, [NSData data], NO);
+ XCTAssertEqualObjects(event.targetChanges.at(1), targetChange1);
+}
+
+- (void)testDocumentUpdate {
+ NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap = [self queryDataForTargets:@[ @1 ]];
+
+ FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
removedTargetIDs:@[]
documentKey:doc1.key
document:doc1];
+ FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{ @"value" : @2 }, NO);
FSTWatchChange *change2 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
removedTargetIDs:@[]
documentKey:doc2.key
document:doc2];
- FSTWatchChangeAggregator *aggregator = [self aggregatorWithTargets:@[ @1 ]
- outstanding:_noPendingResponses
- changes:@[ change1, change2 ]];
+ FSTWatchChangeAggregator *aggregator = [self aggregatorWithTargetMap:targetMap
+ outstandingResponses:_noOutstandingResponses
+ existingKeys:DocumentKeySet {}
+ changes:@[ change1, change2 ]];
+
+ FSTRemoteEvent *event = [aggregator remoteEventAtSnapshotVersion:testutil::Version(3)];
- FSTRemoteEvent *event = [aggregator remoteEvent];
XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.documentUpdates.size(), 2);
XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), doc1);
XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
- // Update doc1
- [event addDocumentUpdate:deletedDoc1];
- [event addDocumentUpdate:doc3];
+ [_targetMetadataProvider setSyncedKeys:DocumentKeySet{doc1.key, doc2.key}
+ forQueryData:targetMap[@1]];
+
+ FSTDeletedDocument *deletedDoc1 =
+ [FSTDeletedDocument documentWithKey:doc1.key version:testutil::Version(3)];
+ FSTDocumentWatchChange *change3 =
+ [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[]
+ removedTargetIDs:@[ @1 ]
+ documentKey:deletedDoc1.key
+ document:deletedDoc1];
+ [aggregator handleDocumentChange:change3];
+
+ FSTDocument *updatedDoc2 = FSTTestDoc("docs/2", 3, @{ @"value" : @2 }, NO);
+ FSTDocumentWatchChange *change4 =
+ [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
+ removedTargetIDs:@[]
+ documentKey:updatedDoc2.key
+ document:updatedDoc2];
+ [aggregator handleDocumentChange:change4];
+
+ FSTDocument *doc3 = FSTTestDoc("docs/3", 3, @{ @"value" : @3 }, NO);
+ FSTDocumentWatchChange *change5 =
+ [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
+ removedTargetIDs:@[]
+ documentKey:doc3.key
+ document:doc3];
+ [aggregator handleDocumentChange:change5];
+
+ event = [aggregator remoteEventAtSnapshotVersion:testutil::Version(3)];
XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.documentUpdates.size(), 3);
// doc1 is replaced
XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), deletedDoc1);
- // doc2 is untouched
- XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
+ // doc2 is updated
+ XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), updatedDoc2);
// doc3 is new
XCTAssertEqualObjects(event.documentUpdates.at(doc3.key), doc3);
// Target is unchanged
- XCTAssertEqual(event.targetChanges.count, 1);
+ XCTAssertEqual(event.targetChanges.size(), 1);
- FSTUpdateMapping *mapping1 =
- [FSTUpdateMapping mappingWithAddedDocuments:@[ doc1, doc2 ] removedDocuments:@[]];
- XCTAssertEqualObjects(event.targetChanges[@1].mapping, mapping1);
+ FSTTargetChange *targetChange =
+ FSTTestTargetChange(DocumentKeySet{doc3.key}, DocumentKeySet{updatedDoc2.key},
+ DocumentKeySet{deletedDoc1.key}, _resumeToken1, NO);
+ XCTAssertEqualObjects(event.targetChanges.at(1), targetChange);
}
- (void)testResumeTokensHandledPerTarget {
+ NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap =
+ [self queryDataForTargets:@[ @1, @2 ]];
+
+ FSTWatchChangeAggregator *aggregator = [self aggregatorWithTargetMap:targetMap
+ outstandingResponses:_noOutstandingResponses
+ existingKeys:DocumentKeySet {}
+ changes:@[]];
+
+ FSTWatchTargetChange *change1 =
+ [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent
+ targetIDs:@[ @1 ]
+ resumeToken:_resumeToken1];
+ [aggregator handleTargetChange:change1];
+
NSData *resumeToken2 = [@"resume2" dataUsingEncoding:NSUTF8StringEncoding];
- FSTWatchChange *change1 = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent
- targetIDs:@[ @1 ]
- resumeToken:_resumeToken1];
- FSTWatchChange *change2 = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent
- targetIDs:@[ @2 ]
- resumeToken:resumeToken2];
- FSTWatchChangeAggregator *aggregator = [self aggregatorWithTargets:@[ @1, @2 ]
- outstanding:_noPendingResponses
- changes:@[ change1, change2 ]];
-
- FSTRemoteEvent *event = [aggregator remoteEvent];
- XCTAssertEqual(event.targetChanges.count, 2);
-
- FSTUpdateMapping *mapping1 =
- [FSTUpdateMapping mappingWithAddedDocuments:@[] removedDocuments:@[]];
- XCTAssertEqualObjects(event.targetChanges[@1].mapping, mapping1);
- XCTAssertEqual(event.targetChanges[@1].snapshotVersion, testutil::Version(3));
- XCTAssertEqual(event.targetChanges[@1].currentStatusUpdate, FSTCurrentStatusUpdateMarkCurrent);
- XCTAssertEqualObjects(event.targetChanges[@1].resumeToken, _resumeToken1);
-
- XCTAssertEqualObjects(event.targetChanges[@2].mapping, mapping1);
- XCTAssertEqual(event.targetChanges[@2].snapshotVersion, testutil::Version(3));
- XCTAssertEqual(event.targetChanges[@2].currentStatusUpdate, FSTCurrentStatusUpdateMarkCurrent);
- XCTAssertEqualObjects(event.targetChanges[@2].resumeToken, resumeToken2);
+ FSTWatchTargetChange *change2 =
+ [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent
+ targetIDs:@[ @2 ]
+ resumeToken:resumeToken2];
+ [aggregator handleTargetChange:change2];
+
+ FSTRemoteEvent *event = [aggregator remoteEventAtSnapshotVersion:testutil::Version(3)];
+ XCTAssertEqual(event.targetChanges.size(), 2);
+
+ FSTTargetChange *targetChange1 =
+ FSTTestTargetChange(DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{}, _resumeToken1, YES);
+ XCTAssertEqualObjects(event.targetChanges.at(1), targetChange1);
+
+ FSTTargetChange *targetChange2 =
+ FSTTestTargetChange(DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{}, resumeToken2, YES);
+ XCTAssertEqualObjects(event.targetChanges.at(2), targetChange2);
}
- (void)testLastResumeTokenWins {
+ NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap =
+ [self queryDataForTargets:@[ @1, @2 ]];
+
+ FSTWatchChangeAggregator *aggregator = [self aggregatorWithTargetMap:targetMap
+ outstandingResponses:_noOutstandingResponses
+ existingKeys:DocumentKeySet {}
+ changes:@[]];
+
+ FSTWatchTargetChange *change1 =
+ [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent
+ targetIDs:@[ @1 ]
+ resumeToken:_resumeToken1];
+ [aggregator handleTargetChange:change1];
+
NSData *resumeToken2 = [@"resume2" dataUsingEncoding:NSUTF8StringEncoding];
+ FSTWatchTargetChange *change2 =
+ [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateNoChange
+ targetIDs:@[ @1 ]
+ resumeToken:resumeToken2];
+ [aggregator handleTargetChange:change2];
+
NSData *resumeToken3 = [@"resume3" dataUsingEncoding:NSUTF8StringEncoding];
+ FSTWatchTargetChange *change3 =
+ [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateNoChange
+ targetIDs:@[ @2 ]
+ resumeToken:resumeToken3];
+ [aggregator handleTargetChange:change3];
- FSTWatchChange *change1 = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent
- targetIDs:@[ @1 ]
- resumeToken:_resumeToken1];
- FSTWatchChange *change2 = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateReset
- targetIDs:@[ @1 ]
- resumeToken:resumeToken2];
- FSTWatchChange *change3 = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateReset
- targetIDs:@[ @2 ]
- resumeToken:resumeToken3];
- FSTWatchChangeAggregator *aggregator =
- [self aggregatorWithTargets:@[ @1, @2 ]
- outstanding:_noPendingResponses
- changes:@[ change1, change2, change3 ]];
-
- FSTRemoteEvent *event = [aggregator remoteEvent];
- XCTAssertEqual(event.targetChanges.count, 2);
-
- FSTResetMapping *mapping1 = [FSTResetMapping mappingWithDocuments:@[]];
- XCTAssertEqualObjects(event.targetChanges[@1].mapping, mapping1);
- XCTAssertEqual(event.targetChanges[@1].snapshotVersion, testutil::Version(3));
- XCTAssertEqual(event.targetChanges[@1].currentStatusUpdate, FSTCurrentStatusUpdateMarkCurrent);
- XCTAssertEqualObjects(event.targetChanges[@1].resumeToken, resumeToken2);
-
- XCTAssertEqualObjects(event.targetChanges[@2].mapping, mapping1);
- XCTAssertEqual(event.targetChanges[@2].snapshotVersion, testutil::Version(3));
- XCTAssertEqual(event.targetChanges[@2].currentStatusUpdate, FSTCurrentStatusUpdateNone);
- XCTAssertEqualObjects(event.targetChanges[@2].resumeToken, resumeToken3);
+ FSTRemoteEvent *event = [aggregator remoteEventAtSnapshotVersion:testutil::Version(3)];
+ XCTAssertEqual(event.targetChanges.size(), 2);
+
+ FSTTargetChange *targetChange1 =
+ FSTTestTargetChange(DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{}, resumeToken2, YES);
+ XCTAssertEqualObjects(event.targetChanges.at(1), targetChange1);
+
+ FSTTargetChange *targetChange2 =
+ FSTTestTargetChange(DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{}, resumeToken3, NO);
+ XCTAssertEqualObjects(event.targetChanges.at(2), targetChange2);
}
- (void)testSynthesizeDeletes {
- FSTWatchChange *shouldSynthesize =
- [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent targetIDs:@[ @1 ]];
+ NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap =
+ [self queryDataForLimboTargets:@[ @1 ]];
- FSTWatchChangeAggregator *aggregator = [self aggregatorWithTargets:@[ @1 ]
- outstanding:_noPendingResponses
- changes:@[ shouldSynthesize ]];
+ DocumentKey limboKey = testutil::Key("coll/limbo");
- FSTRemoteEvent *event = [aggregator remoteEvent];
- DocumentKey synthesized = DocumentKey::FromPathString("docs/2");
- XCTAssertEqual(event.documentUpdates.find(synthesized), event.documentUpdates.end());
+ FSTWatchChange *resolveLimboTarget =
+ [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent targetIDs:@[ @1 ]];
+
+ FSTRemoteEvent *event = [self remoteEventAtSnapshotVersion:3
+ targetMap:targetMap
+ outstandingResponses:_noOutstandingResponses
+ existingKeys:DocumentKeySet {}
+ changes:@[ resolveLimboTarget ]];
- FSTTargetChange *limboTargetChange = event.targetChanges[@1];
- [event synthesizeDeleteForLimboTargetChange:limboTargetChange key:synthesized];
FSTDeletedDocument *expected =
- [FSTDeletedDocument documentWithKey:synthesized version:event.snapshotVersion];
- XCTAssertEqualObjects(expected, event.documentUpdates.at(synthesized));
- XCTAssertTrue(event.limboDocumentChanges.contains(synthesized));
+ [FSTDeletedDocument documentWithKey:limboKey version:event.snapshotVersion];
+ XCTAssertEqualObjects(event.documentUpdates.at(limboKey), expected);
+ XCTAssertTrue(event.limboDocumentChanges.contains(limboKey));
}
- (void)testDoesntSynthesizeDeletesForWrongState {
- FSTWatchChange *wrongState =
- [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateNoChange targetIDs:@[ @2 ]];
+ NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap =
+ [self queryDataForLimboTargets:@[ @1 ]];
- FSTWatchChangeAggregator *aggregator =
- [self aggregatorWithTargets:@[ @2 ] outstanding:_noPendingResponses changes:@[ wrongState ]];
+ FSTWatchChange *wrongState =
+ [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateNoChange targetIDs:@[ @1 ]];
- FSTRemoteEvent *event = [aggregator remoteEvent];
+ FSTRemoteEvent *event = [self remoteEventAtSnapshotVersion:3
+ targetMap:targetMap
+ outstandingResponses:_noOutstandingResponses
+ existingKeys:DocumentKeySet {}
+ changes:@[ wrongState ]];
- DocumentKey notSynthesized = DocumentKey::FromPathString("docs/no1");
- [event synthesizeDeleteForLimboTargetChange:event.targetChanges[@2] key:notSynthesized];
- XCTAssertEqual(event.documentUpdates.find(notSynthesized), event.documentUpdates.end());
- XCTAssertFalse(event.limboDocumentChanges.contains(notSynthesized));
+ XCTAssertEqual(event.documentUpdates.size(), 0);
+ XCTAssertEqual(event.limboDocumentChanges.size(), 0);
}
- (void)testDoesntSynthesizeDeletesForExistingDoc {
+ NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap =
+ [self queryDataForLimboTargets:@[ @3 ]];
+
FSTWatchChange *hasDocument =
[FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent targetIDs:@[ @3 ]];
- FSTDocument *doc = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
- FSTWatchChange *docChange = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @3 ]
- removedTargetIDs:@[]
- documentKey:doc.key
- document:doc];
- FSTWatchChangeAggregator *aggregator = [self aggregatorWithTargets:@[ @3 ]
- outstanding:_noPendingResponses
- changes:@[ hasDocument, docChange ]];
-
- FSTRemoteEvent *event = [aggregator remoteEvent];
- [event synthesizeDeleteForLimboTargetChange:event.targetChanges[@3] key:doc.key];
- FSTMaybeDocument *docData = event.documentUpdates.at(doc.key);
- XCTAssertFalse([docData isKindOfClass:[FSTDeletedDocument class]]);
- XCTAssertFalse(event.limboDocumentChanges.contains(doc.key));
+
+ FSTRemoteEvent *event =
+ [self remoteEventAtSnapshotVersion:3
+ targetMap:targetMap
+ outstandingResponses:_noOutstandingResponses
+ existingKeys:DocumentKeySet{FSTTestDocKey(@"coll/limbo")}
+ changes:@[ hasDocument ]];
+
+ XCTAssertEqual(event.documentUpdates.size(), 0);
+ XCTAssertEqual(event.limboDocumentChanges.size(), 0);
}
-- (void)testFilterUpdates {
+- (void)testSeparatesDocumentUpdates {
+ NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap =
+ [self queryDataForLimboTargets:@[ @1 ]];
+
FSTDocument *newDoc = FSTTestDoc("docs/new", 1, @{@"key" : @"value"}, NO);
- FSTDocument *existingDoc = FSTTestDoc("docs/existing", 1, @{@"some" : @"data"}, NO);
FSTWatchChange *newDocChange = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
removedTargetIDs:@[]
documentKey:newDoc.key
document:newDoc];
+ FSTDocument *existingDoc = FSTTestDoc("docs/existing", 1, @{@"some" : @"data"}, NO);
FSTWatchChange *existingDocChange =
[[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
removedTargetIDs:@[]
documentKey:existingDoc.key
document:existingDoc];
- FSTWatchChangeAggregator *aggregator =
- [self aggregatorWithTargets:@[ @1 ]
- outstanding:_noPendingResponses
- changes:@[ newDocChange, existingDocChange ]];
- FSTRemoteEvent *event = [aggregator remoteEvent];
- DocumentKeySet existingKeys = DocumentKeySet{existingDoc.key};
-
- FSTTargetChange *updateChange = event.targetChanges[@1];
- XCTAssertTrue([updateChange.mapping isKindOfClass:[FSTUpdateMapping class]]);
- FSTUpdateMapping *update = (FSTUpdateMapping *)updateChange.mapping;
- FSTDocumentKey *existingDocKey = existingDoc.key;
- FSTDocumentKey *newDocKey = newDoc.key;
- XCTAssertTrue(update.addedDocuments.contains(existingDocKey));
-
- [update filterUpdatesUsingExistingKeys:existingKeys];
- // Now it's been filtered, since it already existed.
- XCTAssertFalse(update.addedDocuments.contains(existingDocKey));
- XCTAssertTrue(update.addedDocuments.contains(newDocKey));
-}
-
-- (void)testDoesntFilterResets {
- FSTDocument *existingDoc = FSTTestDoc("docs/existing", 1, @{@"some" : @"data"}, NO);
- const DocumentKey &existingDocKey = existingDoc.key;
- FSTWatchTargetChange *resetTargetChange =
- [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateReset
- targetIDs:@[ @2 ]
- resumeToken:_resumeToken1];
- FSTWatchChange *existingDocChange =
- [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @2 ]
- removedTargetIDs:@[]
- documentKey:existingDocKey
- document:existingDoc];
- FSTWatchChangeAggregator *aggregator =
- [self aggregatorWithTargets:@[ @2 ]
- outstanding:_noPendingResponses
- changes:@[ resetTargetChange, existingDocChange ]];
- FSTRemoteEvent *event = [aggregator remoteEvent];
- DocumentKeySet existingKeys = DocumentKeySet{existingDocKey};
-
- FSTTargetChange *resetChange = event.targetChanges[@2];
- XCTAssertTrue([resetChange.mapping isKindOfClass:[FSTResetMapping class]]);
- FSTResetMapping *resetMapping = (FSTResetMapping *)resetChange.mapping;
- XCTAssertTrue(resetMapping.documents.contains(existingDocKey));
-
- [resetMapping filterUpdatesUsingExistingKeys:existingKeys];
- // Document is still there, even though it already exists. Reset mappings don't get filtered.
- XCTAssertTrue(resetMapping.documents.contains(existingDocKey));
+ FSTDeletedDocument *deletedDoc = FSTTestDeletedDoc("docs/deleted", 1);
+ FSTWatchChange *deletedDocChange =
+ [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[]
+ removedTargetIDs:@[ @1 ]
+ documentKey:deletedDoc.key
+ document:deletedDoc];
+
+ FSTDeletedDocument *missingDoc = FSTTestDeletedDoc("docs/missing", 1);
+ FSTWatchChange *missingDocChange =
+ [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[]
+ removedTargetIDs:@[ @1 ]
+ documentKey:missingDoc.key
+ document:missingDoc];
+
+ FSTRemoteEvent *event = [self
+ remoteEventAtSnapshotVersion:3
+ targetMap:targetMap
+ outstandingResponses:_noOutstandingResponses
+ existingKeys:DocumentKeySet{existingDoc.key, deletedDoc.key}
+ changes:@[
+ newDocChange, existingDocChange, deletedDocChange, missingDocChange
+ ]];
+
+ FSTTargetChange *targetChange =
+ FSTTestTargetChange(DocumentKeySet{newDoc.key}, DocumentKeySet{existingDoc.key},
+ DocumentKeySet{deletedDoc.key}, _resumeToken1, NO);
+
+ XCTAssertEqualObjects(event.targetChanges.at(1), targetChange);
}
- (void)testTracksLimboDocuments {
+ NSMutableDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap =
+ [NSMutableDictionary dictionary];
+ [targetMap addEntriesFromDictionary:[self queryDataForTargets:@[ @1 ]]];
+ [targetMap addEntriesFromDictionary:[self queryDataForLimboTargets:@[ @2 ]]];
+
// Add 3 docs: 1 is limbo and non-limbo, 2 is limbo-only, 3 is non-limbo
FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"key" : @"value"}, NO);
FSTDocument *doc2 = FSTTestDoc("docs/2", 1, @{@"key" : @"value"}, NO);
FSTDocument *doc3 = FSTTestDoc("docs/3", 1, @{@"key" : @"value"}, NO);
// Target 2 is a limbo target
-
FSTWatchChange *docChange1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1, @2 ]
removedTargetIDs:@[]
documentKey:doc1.key
@@ -702,20 +915,13 @@ NS_ASSUME_NONNULL_BEGIN
FSTWatchChange *targetsChange =
[FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent targetIDs:@[ @1, @2 ]];
- NSMutableDictionary<NSNumber *, FSTQueryData *> *listens = [NSMutableDictionary dictionary];
- listens[@1] = [FSTQueryData alloc];
- listens[@2] = [[FSTQueryData alloc] initWithQuery:[FSTQuery alloc]
- targetID:2
- listenSequenceNumber:1000
- purpose:FSTQueryPurposeLimboResolution];
- FSTWatchChangeAggregator *aggregator =
- [[FSTWatchChangeAggregator alloc] initWithSnapshotVersion:testutil::Version(3)
- listenTargets:listens
- pendingTargetResponses:@{}];
-
- [aggregator addWatchChanges:@[ docChange1, docChange2, docChange3, targetsChange ]];
+ FSTRemoteEvent *event =
+ [self remoteEventAtSnapshotVersion:3
+ targetMap:targetMap
+ outstandingResponses:_noOutstandingResponses
+ existingKeys:DocumentKeySet {}
+ changes:@[ docChange1, docChange2, docChange3, targetsChange ]];
- FSTRemoteEvent *event = [aggregator remoteEvent];
DocumentKeySet limboDocChanges = event.limboDocumentChanges;
// Doc1 is in both limbo and non-limbo targets, therefore not tracked as limbo
XCTAssertFalse(limboDocChanges.contains(doc1.key));
diff --git a/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm b/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm
index 77010e5..c131f7e 100644
--- a/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm
+++ b/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm
@@ -256,12 +256,15 @@ static NSString *const kNoIOSTag = @"no-ios";
} else if (watchEntity[@"doc"]) {
NSArray *docSpec = watchEntity[@"doc"];
FSTDocumentKey *key = FSTTestDocKey(docSpec[0]);
- FSTObjectValue *value = FSTTestObjectValue(docSpec[2]);
+ FSTObjectValue *_Nullable value =
+ [docSpec[2] isKindOfClass:[NSNull class]] ? nil : FSTTestObjectValue(docSpec[2]);
SnapshotVersion version = [self parseVersion:docSpec[1]];
- FSTMaybeDocument *doc = [FSTDocument documentWithData:value
- key:key
- version:std::move(version)
- hasLocalMutations:NO];
+ FSTMaybeDocument *doc =
+ value ? [FSTDocument documentWithData:value
+ key:key
+ version:std::move(version)
+ hasLocalMutations:NO]
+ : [FSTDeletedDocument documentWithKey:key version:std::move(version)];
FSTWatchChange *change =
[[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:watchEntity[@"targets"]
removedTargetIDs:watchEntity[@"removedTargets"]
diff --git a/Firestore/Example/Tests/SpecTests/json/existence_filter_spec_test.json b/Firestore/Example/Tests/SpecTests/json/existence_filter_spec_test.json
index abd2cf4..3e5d4fb 100644
--- a/Firestore/Example/Tests/SpecTests/json/existence_filter_spec_test.json
+++ b/Firestore/Example/Tests/SpecTests/json/existence_filter_spec_test.json
@@ -1,4 +1,458 @@
{
+ "Existence filter match": {
+ "describeName": "Existence Filters:",
+ "itName": "Existence filter match",
+ "tags": [],
+ "config": {
+ "useGarbageCollection": true
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/1",
+ 1000,
+ {
+ "v": 1
+ }
+ ]
+ ],
+ "targets": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-1000"
+ ],
+ "watchSnapshot": 1000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/1",
+ 1000,
+ {
+ "v": 1
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "watchFilter": [
+ [
+ 2
+ ],
+ "collection/1"
+ ],
+ "watchSnapshot": 2000
+ }
+ ]
+ },
+ "Existence filter match after pending update": {
+ "describeName": "Existence Filters:",
+ "itName": "Existence filter match after pending update",
+ "tags": [],
+ "config": {
+ "useGarbageCollection": true
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-1000"
+ ],
+ "watchSnapshot": 2000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/1",
+ 2000,
+ {
+ "v": 2
+ }
+ ]
+ ],
+ "targets": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchFilter": [
+ [
+ 2
+ ],
+ "collection/1"
+ ],
+ "watchSnapshot": 2000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/1",
+ 2000,
+ {
+ "v": 2
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ }
+ ]
+ },
+ "Existence filter with empty target": {
+ "describeName": "Existence Filters:",
+ "itName": "Existence filter with empty target",
+ "tags": [],
+ "config": {
+ "useGarbageCollection": true
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-1000"
+ ],
+ "watchSnapshot": 2000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "watchFilter": [
+ [
+ 2
+ ],
+ "collection/1"
+ ],
+ "watchSnapshot": 2000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
+ }
+ ]
+ },
+ "Existence filter ignored with pending target": {
+ "describeName": "Existence Filters:",
+ "itName": "Existence filter ignored with pending target",
+ "tags": [],
+ "config": {
+ "useGarbageCollection": false
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/1",
+ 2000,
+ {
+ "v": 2
+ }
+ ]
+ ],
+ "targets": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-1000"
+ ],
+ "watchSnapshot": 1000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/1",
+ 2000,
+ {
+ "v": 2
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "userUnlisten": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {}
+ }
+ },
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": "resume-token-1000"
+ }
+ }
+ },
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/1",
+ 2000,
+ {
+ "v": 2
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "watchFilter": [
+ [
+ 2
+ ]
+ ]
+ },
+ {
+ "watchRemove": {
+ "targetIds": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-2000"
+ ],
+ "watchSnapshot": 2000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ }
+ ]
+ },
"Existence filter mismatch triggers re-run of query": {
"describeName": "Existence Filters:",
"itName": "Existence filter mismatch triggers re-run of query",
@@ -501,6 +955,332 @@
}
]
},
+ "Existence filter handled at global snapshot": {
+ "describeName": "Existence Filters:",
+ "itName": "Existence filter handled at global snapshot",
+ "tags": [],
+ "config": {
+ "useGarbageCollection": true
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/1",
+ 1000,
+ {
+ "v": 1
+ }
+ ]
+ ],
+ "targets": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-1000"
+ ],
+ "watchSnapshot": 1000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/1",
+ 1000,
+ {
+ "v": 1
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "watchFilter": [
+ [
+ 2
+ ],
+ "collection/1",
+ "collection/2"
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/3",
+ 3000,
+ {
+ "v": 3
+ }
+ ]
+ ],
+ "targets": [
+ 2
+ ]
+ },
+ "watchSnapshot": 2000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/3",
+ 3000,
+ {
+ "v": 3
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "watchRemove": {
+ "targetIds": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/1",
+ 1000,
+ {
+ "v": 1
+ }
+ ],
+ [
+ "collection/2",
+ 2000,
+ {
+ "v": 2
+ }
+ ],
+ [
+ "collection/3",
+ 3000,
+ {
+ "v": 3
+ }
+ ]
+ ],
+ "targets": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-3000"
+ ],
+ "watchSnapshot": 3000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/2",
+ 2000,
+ {
+ "v": 2
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ }
+ ]
+ },
+ "Existence filter synthesizes deletes": {
+ "describeName": "Existence Filters:",
+ "itName": "Existence filter synthesizes deletes",
+ "tags": [],
+ "config": {
+ "useGarbageCollection": true
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection/a",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection/a",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "v": 1
+ }
+ ]
+ ],
+ "targets": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-1000"
+ ],
+ "watchSnapshot": 1000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection/a",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "v": 1
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "watchFilter": [
+ [
+ 2
+ ]
+ ],
+ "watchSnapshot": 2000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection/a",
+ "filters": [],
+ "orderBys": []
+ },
+ "removed": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "v": 1
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ }
+ ]
+ },
"Existence filter limbo resolution is denied": {
"describeName": "Existence Filters:",
"itName": "Existence filter limbo resolution is denied",
diff --git a/Firestore/Example/Tests/SpecTests/json/limbo_spec_test.json b/Firestore/Example/Tests/SpecTests/json/limbo_spec_test.json
index ee2d883..a186496 100644
--- a/Firestore/Example/Tests/SpecTests/json/limbo_spec_test.json
+++ b/Firestore/Example/Tests/SpecTests/json/limbo_spec_test.json
@@ -1146,5 +1146,416 @@
]
}
]
+ },
+ "Limbo documents handle receiving ack and then current": {
+ "describeName": "Limbo Documents:",
+ "itName": "Limbo documents handle receiving ack and then current",
+ "tags": [],
+ "config": {
+ "useGarbageCollection": false
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "include": true,
+ "key": "a"
+ }
+ ],
+ [
+ "collection/b",
+ 1000,
+ {
+ "include": true,
+ "key": "b"
+ }
+ ]
+ ],
+ "targets": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-1000"
+ ],
+ "watchSnapshot": 1000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "include": true,
+ "key": "a"
+ }
+ ],
+ [
+ "collection/b",
+ 1000,
+ {
+ "include": true,
+ "key": "b"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "userUnlisten": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {}
+ }
+ },
+ {
+ "userListen": [
+ 4,
+ {
+ "path": "collection",
+ "limit": 1,
+ "filters": [
+ [
+ "include",
+ "==",
+ true
+ ]
+ ],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "4": {
+ "query": {
+ "path": "collection",
+ "limit": 1,
+ "filters": [
+ [
+ "include",
+ "==",
+ true
+ ]
+ ],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ },
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "limit": 1,
+ "filters": [
+ [
+ "include",
+ "==",
+ true
+ ]
+ ],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "include": true,
+ "key": "a"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "watchAck": [
+ 4
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "include": true,
+ "key": "a"
+ }
+ ]
+ ],
+ "targets": [
+ 4
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 4
+ ],
+ "resume-token-2000"
+ ],
+ "watchSnapshot": 2000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "limit": 1,
+ "filters": [
+ [
+ "include",
+ "==",
+ true
+ ]
+ ],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "userPatch": [
+ "collection/a",
+ {
+ "include": false
+ }
+ ],
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "limit": 1,
+ "filters": [
+ [
+ "include",
+ "==",
+ true
+ ]
+ ],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/b",
+ 1000,
+ {
+ "include": true,
+ "key": "b"
+ }
+ ]
+ ],
+ "removed": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "include": true,
+ "key": "a"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ],
+ "stateExpect": {
+ "limboDocs": [
+ "collection/b"
+ ],
+ "activeTargets": {
+ "1": {
+ "query": {
+ "path": "collection/b",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ },
+ "4": {
+ "query": {
+ "path": "collection",
+ "limit": 1,
+ "filters": [
+ [
+ "include",
+ "==",
+ true
+ ]
+ ],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "watchAck": [
+ 1
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/b",
+ 1000,
+ {
+ "include": true,
+ "key": "b"
+ }
+ ]
+ ],
+ "targets": [
+ 1
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 1
+ ],
+ "resume-token-3000"
+ ],
+ "watchSnapshot": 3000
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "include": true,
+ "key": "a"
+ }
+ ]
+ ],
+ "removedTargets": [
+ 4
+ ]
+ }
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/b",
+ 1000,
+ {
+ "include": true,
+ "key": "b"
+ }
+ ]
+ ],
+ "targets": [
+ 4
+ ]
+ },
+ "watchSnapshot": 4000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "limit": 1,
+ "filters": [
+ [
+ "include",
+ "==",
+ true
+ ]
+ ],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ],
+ "stateExpect": {
+ "limboDocs": [],
+ "activeTargets": {
+ "4": {
+ "query": {
+ "path": "collection",
+ "limit": 1,
+ "filters": [
+ [
+ "include",
+ "==",
+ true
+ ]
+ ],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ }
+ ]
}
}
diff --git a/Firestore/Example/Tests/SpecTests/json/limit_spec_test.json b/Firestore/Example/Tests/SpecTests/json/limit_spec_test.json
index 5a02463..6aa1daa 100644
--- a/Firestore/Example/Tests/SpecTests/json/limit_spec_test.json
+++ b/Firestore/Example/Tests/SpecTests/json/limit_spec_test.json
@@ -287,7 +287,15 @@
"targets": [
2
]
- },
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-2000"
+ ],
"watchSnapshot": 2000,
"stateExpect": {
"limboDocs": [
@@ -1144,7 +1152,15 @@
"targets": [
2
]
- },
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-2000"
+ ],
"watchSnapshot": 2000,
"stateExpect": {
"limboDocs": [
diff --git a/Firestore/Example/Tests/SpecTests/json/listen_spec_test.json b/Firestore/Example/Tests/SpecTests/json/listen_spec_test.json
index 7bfe557..e838d2f 100644
--- a/Firestore/Example/Tests/SpecTests/json/listen_spec_test.json
+++ b/Firestore/Example/Tests/SpecTests/json/listen_spec_test.json
@@ -238,6 +238,177 @@
}
]
},
+ "Doesn't raise events for empty target": {
+ "describeName": "Listens:",
+ "itName": "Doesn't raise events for empty target",
+ "tags": [],
+ "config": {
+ "useGarbageCollection": true
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection1",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection1",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "userListen": [
+ 4,
+ {
+ "path": "collection2",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection1",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ },
+ "4": {
+ "query": {
+ "path": "collection2",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "userListen": [
+ 6,
+ {
+ "path": "collection3",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection1",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ },
+ "4": {
+ "query": {
+ "path": "collection2",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ },
+ "6": {
+ "query": {
+ "path": "collection3",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-1000"
+ ]
+ },
+ {
+ "watchAck": [
+ 4
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection2/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "targets": [
+ 4
+ ]
+ }
+ },
+ {
+ "watchAck": [
+ 6
+ ],
+ "watchSnapshot": 1000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection1",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ },
+ {
+ "query": {
+ "path": "collection2",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection2/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
+ }
+ ]
+ },
"Ensure correct query results with latency-compensated deletes": {
"describeName": "Listens:",
"itName": "Ensure correct query results with latency-compensated deletes",
@@ -385,6 +556,79 @@
}
]
},
+ "Does not raise event for initial document delete": {
+ "describeName": "Listens:",
+ "itName": "Does not raise event for initial document delete",
+ "tags": [""],
+ "config": {
+ "useGarbageCollection": true
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/a",
+ 1000,
+ null
+ ]
+ ],
+ "removedTargets": [
+ 2
+ ]
+ },
+ "watchSnapshot": 1000
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-2000"
+ ],
+ "watchSnapshot": 2000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ }
+ ]
+ },
"Will process removals without waiting for a consistent snapshot": {
"describeName": "Listens:",
"itName": "Will process removals without waiting for a consistent snapshot",
@@ -1690,5 +1934,836 @@
]
}
]
+ },
+ "Synthesizes deletes for missing document": {
+ "describeName": "Listens:",
+ "itName": "Synthesizes deletes for missing document",
+ "tags": [],
+ "config": {
+ "useGarbageCollection": false
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ],
+ [
+ "collection/b",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "targets": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-1000"
+ ],
+ "watchSnapshot": 1000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ],
+ [
+ "collection/b",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "userUnlisten": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {}
+ }
+ },
+ {
+ "watchRemove": {
+ "targetIds": [
+ 2
+ ]
+ }
+ },
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": "resume-token-1000"
+ }
+ }
+ },
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ],
+ [
+ "collection/b",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "userUnlisten": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {}
+ }
+ },
+ {
+ "userListen": [
+ 4,
+ {
+ "path": "collection/a",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "4": {
+ "query": {
+ "path": "collection/a",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ },
+ "expect": [
+ {
+ "query": {
+ "path": "collection/a",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "watchAck": [
+ 4
+ ]
+ },
+ {
+ "watchCurrent": [
+ [
+ 4
+ ],
+ "resume-token-2000"
+ ],
+ "watchSnapshot": 2000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection/a",
+ "filters": [],
+ "orderBys": []
+ },
+ "removed": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "userUnlisten": [
+ 4,
+ {
+ "path": "collection/a",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {}
+ }
+ },
+ {
+ "watchRemove": {
+ "targetIds": [
+ 4
+ ]
+ }
+ },
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": "resume-token-1000"
+ }
+ }
+ },
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/b",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
+ }
+ ]
+ },
+ "Re-opens target without existence filter": {
+ "describeName": "Listens:",
+ "itName": "Re-opens target without existence filter",
+ "tags": [],
+ "config": {
+ "useGarbageCollection": false
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "targets": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-1000"
+ ],
+ "watchSnapshot": 1000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "userUnlisten": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {}
+ }
+ },
+ {
+ "watchRemove": {
+ "targetIds": [
+ 2
+ ]
+ }
+ },
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": "resume-token-1000"
+ }
+ }
+ },
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/a",
+ 2000,
+ null
+ ]
+ ],
+ "removedTargets": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-2000"
+ ],
+ "watchSnapshot": 2000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "removed": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ }
+ ]
+ },
+ "Ignores update from inactive target": {
+ "describeName": "Listens:",
+ "itName": "Ignores update from inactive target",
+ "tags": [],
+ "config": {
+ "useGarbageCollection": false
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "targets": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-1000"
+ ],
+ "watchSnapshot": 1000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "userUnlisten": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {}
+ }
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/b",
+ 2000,
+ {
+ "key": "b"
+ }
+ ]
+ ],
+ "targets": [
+ 2
+ ]
+ },
+ "watchSnapshot": 2000
+ },
+ {
+ "watchRemove": {
+ "targetIds": [
+ 2
+ ]
+ }
+ },
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": "resume-token-1000"
+ }
+ }
+ },
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
+ }
+ ]
+ },
+ "Does not synthesize deletes for previously acked documents": {
+ "describeName": "Listens:",
+ "itName": "Does not synthesize deletes for previously acked documents",
+ "tags": [],
+ "config": {
+ "useGarbageCollection": false
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection/a",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection/a",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "targets": [
+ 2
+ ]
+ },
+ "watchSnapshot": 1000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection/a",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-2000"
+ ],
+ "watchSnapshot": 2000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection/a",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "userUnlisten": [
+ 2,
+ {
+ "path": "collection/a",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {}
+ }
+ },
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection/a",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection/a",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": "resume-token-2000"
+ }
+ }
+ },
+ "expect": [
+ {
+ "query": {
+ "path": "collection/a",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
+ }
+ ]
}
}
diff --git a/Firestore/Example/Tests/Util/FSTHelpers.h b/Firestore/Example/Tests/Util/FSTHelpers.h
index ccc01ca..7946c06 100644
--- a/Firestore/Example/Tests/Util/FSTHelpers.h
+++ b/Firestore/Example/Tests/Util/FSTHelpers.h
@@ -16,11 +16,11 @@
#import <Foundation/Foundation.h>
-#include <map>
#include <vector>
#import "Firestore/Source/Core/FSTTypes.h"
#import "Firestore/Source/Model/FSTDocumentDictionary.h"
+#import "Firestore/Source/Remote/FSTRemoteEvent.h"
#include "Firestore/core/src/firebase/firestore/model/field_path.h"
#include "Firestore/core/src/firebase/firestore/model/field_value.h"
@@ -148,6 +148,42 @@ inline NSString *FSTRemoveExceptionPrefix(NSString *exception) {
XCTAssertTrue(didThrow, ##__VA_ARGS__); \
} while (0)
+/**
+ * An implementation of FSTTargetMetadataProvider that provides controlled access to the
+ * `FSTTargetMetadataProvider` callbacks. Any target accessed via these callbacks must be
+ * registered beforehand via the factory methods or via `setSyncedKeys:forQueryData:`.
+ */
+@interface FSTTestTargetMetadataProvider : NSObject <FSTTargetMetadataProvider>
+
+/**
+ * Creates an FSTTestTargetMetadataProvider that behaves as if there's an established listen for
+ * each of the given targets, where each target has previously seen query results containing just
+ * the given documentKey.
+ *
+ * Internally this means that the `remoteKeysForTarget` callback for these targets will return just
+ * the documentKey and that the provided targets will be returned as active from the
+ * `queryDataForTarget` target.
+ */
++ (instancetype)providerWithSingleResultForKey:(firebase::firestore::model::DocumentKey)documentKey
+ targets:(NSArray<FSTBoxedTargetID *> *)targets;
+
+/**
+ * Creates an FSTTestTargetMetadataProvider that behaves as if there's an established listen for
+ * each of the given targets, where each target has not seen any previous document.
+ *
+ * Internally this means that the `remoteKeysForTarget` callback for these targets will return an
+ * empty set of document keys and that the provided targets will be returned as active from the
+ * `queryDataForTarget` target.
+ */
++ (instancetype)providerWithEmptyResultForKey:(firebase::firestore::model::DocumentKey)documentKey
+ targets:(NSArray<FSTBoxedTargetID *> *)targets;
+
+/** Sets or replaces the local state for the provided query data. */
+- (void)setSyncedKeys:(firebase::firestore::model::DocumentKeySet)keys
+ forQueryData:(FSTQueryData *)queryData;
+
+@end
+
/** Creates a new FIRTimestamp from components. Note that year, month, and day are all one-based. */
FIRTimestamp *FSTTestTimestamp(int year, int month, int day, int hour, int minute, int second);
@@ -250,6 +286,9 @@ FSTDeleteMutation *FSTTestDeleteMutation(NSString *path);
/** Converts a list of documents to a sorted map. */
FSTMaybeDocumentDictionary *FSTTestDocUpdates(NSArray<FSTMaybeDocument *> *docs);
+/** Creates a remote event that inserts a new document. */
+FSTRemoteEvent *FSTTestAddedRemoteEvent(FSTMaybeDocument *doc, NSArray<NSNumber *> *addedToTargets);
+
/** Creates a remote event with changes to a document. */
FSTRemoteEvent *FSTTestUpdateRemoteEvent(FSTMaybeDocument *doc,
NSArray<NSNumber *> *updatedInTargets,
@@ -260,6 +299,19 @@ FSTLocalViewChanges *FSTTestViewChanges(FSTQuery *query,
NSArray<NSString *> *addedKeys,
NSArray<NSString *> *removedKeys);
+/** Creates a test target change that acks all 'docs' and marks the target as CURRENT */
+FSTTargetChange *FSTTestTargetChangeAckDocuments(firebase::firestore::model::DocumentKeySet docs);
+
+/** Creates a test target change that marks the target as CURRENT */
+FSTTargetChange *FSTTestTargetChangeMarkCurrent();
+
+/** Creates a test target change. */
+FSTTargetChange *FSTTestTargetChange(firebase::firestore::model::DocumentKeySet added,
+ firebase::firestore::model::DocumentKeySet modified,
+ firebase::firestore::model::DocumentKeySet removed,
+ NSData *resumeToken,
+ BOOL current);
+
/** Creates a resume token to match the given snapshot version. */
NSData *_Nullable FSTTestResumeTokenFromSnapshotVersion(FSTTestSnapshotVersion watchSnapshot);
diff --git a/Firestore/Example/Tests/Util/FSTHelpers.mm b/Firestore/Example/Tests/Util/FSTHelpers.mm
index 5ed4fd5..8ece82f 100644
--- a/Firestore/Example/Tests/Util/FSTHelpers.mm
+++ b/Firestore/Example/Tests/Util/FSTHelpers.mm
@@ -22,7 +22,7 @@
#include <cinttypes>
#include <list>
-#include <map>
+#include <unordered_map>
#include <utility>
#include <vector>
@@ -58,6 +58,7 @@ namespace util = firebase::firestore::util;
namespace testutil = firebase::firestore::testutil;
using firebase::firestore::model::DatabaseId;
using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::DocumentKeySet;
using firebase::firestore::model::FieldMask;
using firebase::firestore::model::FieldPath;
using firebase::firestore::model::FieldTransform;
@@ -65,8 +66,8 @@ using firebase::firestore::model::FieldValue;
using firebase::firestore::model::Precondition;
using firebase::firestore::model::ResourcePath;
using firebase::firestore::model::ServerTimestampTransform;
+using firebase::firestore::model::SnapshotVersion;
using firebase::firestore::model::TransformOperation;
-using firebase::firestore::model::DocumentKeySet;
NS_ASSUME_NONNULL_BEGIN
@@ -298,29 +299,125 @@ FSTViewSnapshot *_Nullable FSTTestApplyChanges(FSTView *view,
.snapshot;
}
+@implementation FSTTestTargetMetadataProvider {
+ std::unordered_map<FSTTargetID, DocumentKeySet> _syncedKeys;
+ std::unordered_map<FSTTargetID, FSTQueryData *> _queryData;
+}
+
++ (instancetype)providerWithSingleResultForKey:(DocumentKey)documentKey
+ targets:(NSArray<FSTBoxedTargetID *> *)targets {
+ FSTTestTargetMetadataProvider *metadataProvider = [FSTTestTargetMetadataProvider new];
+ FSTQuery *query = [FSTQuery queryWithPath:documentKey.path()];
+
+ for (FSTBoxedTargetID *targetID in targets) {
+ FSTQueryData *queryData = [[FSTQueryData alloc] initWithQuery:query
+ targetID:targetID.intValue
+ listenSequenceNumber:0
+ purpose:FSTQueryPurposeListen];
+ [metadataProvider setSyncedKeys:DocumentKeySet{documentKey} forQueryData:queryData];
+ }
+
+ return metadataProvider;
+}
+
++ (instancetype)providerWithEmptyResultForKey:(DocumentKey)documentKey
+ targets:(NSArray<FSTBoxedTargetID *> *)targets {
+ FSTTestTargetMetadataProvider *metadataProvider = [FSTTestTargetMetadataProvider new];
+ FSTQuery *query = [FSTQuery queryWithPath:documentKey.path()];
+
+ for (FSTBoxedTargetID *targetID in targets) {
+ FSTQueryData *queryData = [[FSTQueryData alloc] initWithQuery:query
+ targetID:targetID.intValue
+ listenSequenceNumber:0
+ purpose:FSTQueryPurposeListen];
+ [metadataProvider setSyncedKeys:DocumentKeySet {} forQueryData:queryData];
+ }
+
+ return metadataProvider;
+}
+
+- (void)setSyncedKeys:(DocumentKeySet)keys forQueryData:(FSTQueryData *)queryData {
+ _syncedKeys[queryData.targetID] = keys;
+ _queryData[queryData.targetID] = queryData;
+}
+
+- (DocumentKeySet)remoteKeysForTarget:(FSTBoxedTargetID *)targetID {
+ auto it = _syncedKeys.find(targetID.intValue);
+ HARD_ASSERT(it != _syncedKeys.end(), "Cannot process unknown target %s", targetID.intValue);
+ return it->second;
+}
+
+- (nullable FSTQueryData *)queryDataForTarget:(FSTBoxedTargetID *)targetID {
+ auto it = _queryData.find(targetID.intValue);
+ HARD_ASSERT(it != _queryData.end(), "Cannot process unknown target %s", targetID.intValue);
+ return it->second;
+}
+
+@end
+
+FSTRemoteEvent *FSTTestAddedRemoteEvent(FSTMaybeDocument *doc,
+ NSArray<FSTBoxedTargetID *> *addedToTargets) {
+ HARD_ASSERT(![doc isKindOfClass:[FSTDocument class]] || ![(FSTDocument *)doc hasLocalMutations],
+ "Docs from remote updates shouldn't have local changes.");
+ FSTDocumentWatchChange *change =
+ [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:addedToTargets
+ removedTargetIDs:{}
+ documentKey:doc.key
+ document:doc];
+ FSTWatchChangeAggregator *aggregator = [[FSTWatchChangeAggregator alloc]
+ initWithTargetMetadataProvider:[FSTTestTargetMetadataProvider
+ providerWithEmptyResultForKey:doc.key
+ targets:addedToTargets]];
+ [aggregator handleDocumentChange:change];
+ return [aggregator remoteEventAtSnapshotVersion:doc.version];
+}
+
FSTRemoteEvent *FSTTestUpdateRemoteEvent(FSTMaybeDocument *doc,
- NSArray<NSNumber *> *updatedInTargets,
- NSArray<NSNumber *> *removedFromTargets) {
+ NSArray<FSTBoxedTargetID *> *updatedInTargets,
+ NSArray<FSTBoxedTargetID *> *removedFromTargets) {
+ HARD_ASSERT(![doc isKindOfClass:[FSTDocument class]] || ![(FSTDocument *)doc hasLocalMutations],
+ "Docs from remote updates shouldn't have local changes.");
FSTDocumentWatchChange *change =
[[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:updatedInTargets
removedTargetIDs:removedFromTargets
documentKey:doc.key
document:doc];
- NSMutableDictionary<NSNumber *, FSTQueryData *> *listens = [NSMutableDictionary dictionary];
- FSTQueryData *dummyQueryData = [FSTQueryData alloc];
- for (NSNumber *targetID in updatedInTargets) {
- listens[targetID] = dummyQueryData;
- }
- for (NSNumber *targetID in removedFromTargets) {
- listens[targetID] = dummyQueryData;
- }
- NSMutableDictionary<NSNumber *, NSNumber *> *pending = [NSMutableDictionary dictionary];
- FSTWatchChangeAggregator *aggregator =
- [[FSTWatchChangeAggregator alloc] initWithSnapshotVersion:doc.version
- listenTargets:listens
- pendingTargetResponses:pending];
- [aggregator addWatchChange:change];
- return [aggregator remoteEvent];
+ NSArray<FSTBoxedTargetID *> *targets =
+ [updatedInTargets arrayByAddingObjectsFromArray:removedFromTargets];
+ FSTWatchChangeAggregator *aggregator = [[FSTWatchChangeAggregator alloc]
+ initWithTargetMetadataProvider:[FSTTestTargetMetadataProvider
+ providerWithSingleResultForKey:doc.key
+ targets:targets]];
+ [aggregator handleDocumentChange:change];
+ return [aggregator remoteEventAtSnapshotVersion:doc.version];
+}
+
+FSTTargetChange *FSTTestTargetChangeMarkCurrent() {
+ return [[FSTTargetChange alloc] initWithResumeToken:[NSData data]
+ current:YES
+ addedDocuments:DocumentKeySet {}
+ modifiedDocuments:DocumentKeySet {}
+ removedDocuments:DocumentKeySet{}];
+}
+
+FSTTargetChange *FSTTestTargetChangeAckDocuments(DocumentKeySet docs) {
+ return [[FSTTargetChange alloc] initWithResumeToken:[NSData data]
+ current:YES
+ addedDocuments:docs
+ modifiedDocuments:DocumentKeySet {}
+ removedDocuments:DocumentKeySet{}];
+}
+
+FSTTargetChange *FSTTestTargetChange(DocumentKeySet added,
+ DocumentKeySet modified,
+ DocumentKeySet removed,
+ NSData *resumeToken,
+ BOOL current) {
+ return [[FSTTargetChange alloc] initWithResumeToken:resumeToken
+ current:current
+ addedDocuments:added
+ modifiedDocuments:modified
+ removedDocuments:removed];
}
/** Creates a resume token to match the given snapshot version. */