diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 78cc39aa7e..1e124c0183 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -336,12 +336,12 @@ jobs: additional_flags+=(--cmake_flag=-DFIREBASE_USE_BORINGSSL=ON) fi fi - python scripts/gha/build_testapps.py --t ${{ needs.prepare_matrix.outputs.apis }} --p ${{ matrix.target_platform }} --output_directory "${{ github.workspace }}" --noadd_timestamp ${additional_flags[*]} + python scripts/gha/build_testapps.py --t ${{ needs.prepare_matrix.outputs.apis }} --p ${{ matrix.target_platform }} --output_directory "${{ github.workspace }}" --noadd_timestamp ${additional_flags[*]} --short_output_paths - name: Run desktop integration tests if: matrix.target_platform == 'Desktop' && !cancelled() run: | - python scripts/gha/desktop_tester.py --testapp_dir testapps + python scripts/gha/desktop_tester.py --testapp_dir ta # Workaround for https://github.com/GoogleCloudPlatform/github-actions/issues/100 # Must be run after the Python setup action - name: Set CLOUDSDK_PYTHON (Windows) @@ -354,11 +354,11 @@ jobs: - name: Upload Desktop Artifacts to GCS if: matrix.target_platform == 'Desktop' && !cancelled() run: | - python scripts/gha/gcs_uploader.py --testapp_dir testapps --key_file scripts/gha-encrypted/gcs_key_file.json + python scripts/gha/gcs_uploader.py --testapp_dir ta --key_file scripts/gha-encrypted/gcs_key_file.json - name: Run mobile integration tests if: matrix.target_platform != 'Desktop' && !cancelled() run: | - python scripts/gha/test_lab.py --android_model ${{ needs.prepare_matrix.outputs.android_device }} --android_api ${{ needs.prepare_matrix.outputs.android_api }} --ios_model ${{ needs.prepare_matrix.outputs.ios_device }} --ios_version ${{ needs.prepare_matrix.outputs.ios_version }} --testapp_dir testapps --code_platform cpp --key_file scripts/gha-encrypted/gcs_key_file.json + python scripts/gha/test_lab.py --android_model ${{ needs.prepare_matrix.outputs.android_device }} --android_api ${{ needs.prepare_matrix.outputs.android_api }} --ios_model ${{ needs.prepare_matrix.outputs.ios_device }} --ios_version ${{ needs.prepare_matrix.outputs.ios_version }} --testapp_dir ta --code_platform cpp --key_file scripts/gha-encrypted/gcs_key_file.json ### The below allow us to set the failure label and comment early, when the first failure ### in the matrix occurs. It'll be cleaned up in a subsequent job. @@ -393,7 +393,7 @@ jobs: if: ${{ !cancelled() }} shell: bash run: | - cat testapps/summary.log + cat ta/summary.log if [[ "${{ job.status }}" != "success" ]]; then exit 1 fi diff --git a/firestore/integration_test/integration_test.xcodeproj/project.pbxproj b/firestore/integration_test/integration_test.xcodeproj/project.pbxproj index baae0f5067..daabcc5412 100644 --- a/firestore/integration_test/integration_test.xcodeproj/project.pbxproj +++ b/firestore/integration_test/integration_test.xcodeproj/project.pbxproj @@ -311,6 +311,7 @@ "\"$(SRCROOT)/external/googletest/src/googlemock\"", ); INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.cpp.firestore.testapp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; WRAPPER_EXTENSION = app; @@ -339,6 +340,7 @@ "\"$(SRCROOT)/external/googletest/src/googlemock\"", ); INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.cpp.firestore.testapp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; WRAPPER_EXTENSION = app; diff --git a/firestore/integration_test/src/integration_test.cc b/firestore/integration_test/src/integration_test.cc index 7c39811d9f..ec9d1c3927 100644 --- a/firestore/integration_test/src/integration_test.cc +++ b/firestore/integration_test/src/integration_test.cc @@ -12,6 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. +/* + IMPORTANT: This file is used by both the regular and the internal Firestore + integration tests, and needs to be present and identical in both. + + Please ensure that any changes to this file are reflected in both of its + locations: + + - firestore/integration_test/src/integration_test.cc + - firestore/integration_test_internal/src/integration_test.cc + + If you make any modifications to this file in one of the two locations, please + copy the modified file into the other location before committing the change. +*/ + #include #include diff --git a/firestore/integration_test_internal/CMakeLists.txt b/firestore/integration_test_internal/CMakeLists.txt index 50543bd147..aac89f42ba 100644 --- a/firestore/integration_test_internal/CMakeLists.txt +++ b/firestore/integration_test_internal/CMakeLists.txt @@ -50,6 +50,12 @@ set(MSVC_RUNTIME_MODE MD) project(firebase_testapp) +# Ensure min/max macros don't get declared on Windows +# (so we can use std::min/max), before including the Firebase subdirectories. +if(MSVC) + add_definitions(-DNOMINMAX) +endif() + # Integration test source files. set(FIREBASE_APP_FRAMEWORK_SRCS src/app_framework.cc @@ -61,36 +67,127 @@ set(FIREBASE_TEST_FRAMEWORK_SRCS src/firebase_test_framework.cc ) -set(FIREBASE_INTEGRATION_TEST_SRCS - src/array_transform_test.cc - src/cleanup_test.cc - src/collection_reference_test.cc - src/cursor_test.cc - src/document_change_test.cc - src/document_reference_test.cc - src/document_snapshot_test.cc - src/field_value_test.cc - src/fields_test.cc - src/includes_test.cc - src/listener_registration_test.cc - src/numeric_transforms_test.cc - src/query_network_test.cc - src/query_snapshot_test.cc - src/query_test.cc - src/sanity_test.cc - src/server_timestamp_test.cc - src/smoke_test.cc - src/transaction_extra_test.cc - src/transaction_test.cc - src/type_test.cc - src/validation_test.cc - src/write_batch_test.cc - src/firestore_test.cc - src/util/future_test_util.cc - src/util/integration_test_util.cc - src/firestore_integration_test.cc +# These sources contain the actual tests that run on all platforms, both Android +# and non-Android. +set(FIREBASE_INTEGRATION_TEST_PORTABLE_TEST_SRCS + # Copy of the standard integration test source file. This ensures that even + # if only the internal integration test is run, all of the tests on the + # public API are performed. + src/integration_test.cc + # Internal tests below. + src/collection_reference_test.cc + src/cursor_test.cc + src/document_change_test.cc + src/document_reference_test.cc + src/document_snapshot_test.cc + src/field_value_test.cc + src/fields_test.cc + src/firestore_test.cc + src/includes_test.cc + src/listener_registration_test.cc + src/numeric_transforms_test.cc + src/query_network_test.cc + src/query_snapshot_test.cc + src/query_test.cc + src/sanity_test.cc + src/server_timestamp_test.cc + src/smoke_test.cc + src/transaction_test.cc + src/type_test.cc + src/validation_test.cc + src/write_batch_test.cc +) + +# These sources contain the actual tests that run on Android only. +set(FIREBASE_INTEGRATION_TEST_ANDROID_TEST_SRCS + src/android/field_path_portable_test.cc + src/android/firestore_integration_test_android_test.cc + src/android/geo_point_android_test.cc + src/android/jni_runnable_android_test.cc + src/android/promise_android_test.cc + src/android/settings_android_test.cc + src/android/snapshot_metadata_android_test.cc + src/android/timestamp_android_test.cc + src/jni/declaration_test.cc + src/jni/env_test.cc + src/jni/object_test.cc + src/jni/ownership_test.cc + src/jni/task_test.cc + src/jni/traits_test.cc +) + +# These sources contain the actual tests that run on non-Android only. +set(FIREBASE_INTEGRATION_TEST_DESKTOP_AND_IOS_TEST_SRCS + src/array_transform_test.cc + src/cleanup_test.cc + src/collection_reference_test.cc + src/transaction_extra_test.cc +) + +# These sources contain test plumbing and support, for all platforms. +set(FIREBASE_INTEGRATION_TEST_PORTABLE_SUPPORT_SRCS + src/firestore_integration_test.cc + src/util/future_test_util.cc + src/util/integration_test_util.cc +) + +# These sources contain Android-specific test plumbing and utilities. +set(FIREBASE_INTEGRATION_TEST_ANDROID_SUPPORT_SRCS + src/android/cancellation_token_source.cc + src/android/task_completion_source.cc + src/android/firestore_integration_test_android.cc + src/android/util_autoid.cc + # wrapper_assertions.cc from the C++ SDK is included here because even + # though it's not included in the Android version of the SDK, it's still + # used for some tests. + ${FIREBASE_CPP_SDK_DIR}/firestore/src/common/wrapper_assertions.cc ) +if(NOT ANDROID) + # Include portable source files and portable tests, plus tests that shouldn't + # be included on Android. + set(FIREBASE_INTEGRATION_TEST_SRCS + ${FIREBASE_INTEGRATION_TEST_PORTABLE_SUPPORT_SRCS} + ${FIREBASE_INTEGRATION_TEST_PORTABLE_TEST_SRCS} + ${FIREBASE_INTEGRATION_TEST_DESKTOP_AND_IOS_TEST_SRCS} + ) +else() + # Include portable source files and tests, plus Android-specific source files + # and tests. + set(FIREBASE_INTEGRATION_TEST_SRCS + ${FIREBASE_INTEGRATION_TEST_PORTABLE_SUPPORT_SRCS} + ${FIREBASE_INTEGRATION_TEST_ANDROID_SUPPORT_SRCS} + ${FIREBASE_INTEGRATION_TEST_PORTABLE_TEST_SRCS} + ${FIREBASE_INTEGRATION_TEST_ANDROID_TEST_SRCS} + ) +endif() + +if(ANDROID) + # Firestore's internal integration test requires absl on Android, + # so download it now. + set(ABSEIL_CPP_ROOT ${CMAKE_CURRENT_LIST_DIR}/external/abseil-cpp/src/abseil-cpp) + if (NOT EXISTS ${ABSEIL_CPP_ROOT}/absl) + configure_file(abseil-cpp.cmake + ${CMAKE_CURRENT_LIST_DIR}/external/abseil-cpp/CMakeLists.txt COPYONLY) + execute_process(COMMAND ${CMAKE_COMMAND} . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/abseil-cpp ) + if(result) + message(FATAL_ERROR "CMake step for abseil-cpp failed: ${result}") + endif() + execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/abseil-cpp ) + if(result) + message(FATAL_ERROR "Build step for abseil-cpp failed: ${result}") + endif() + endif() + add_subdirectory(external/abseil-cpp) +else() + set(ABSEIL_CPP_ROOT + ${PROJECT_BINARY_DIR}/bin/external/src/firestore-build/external/src/abseil-cpp) +endif() + # The include directory for the testapp. include_directories(src) # The include directory for the C++ SDK root. @@ -98,7 +195,7 @@ include_directories(${FIREBASE_CPP_SDK_DIR}) # Additional public headers from Firestore core SDK. include_directories(${PROJECT_BINARY_DIR}/bin/external/src/firestore) # Additional public headers for absl. -include_directories(${PROJECT_BINARY_DIR}/bin/external/src/firestore-build/external/src/abseil-cpp) +include_directories(${ABSEIL_CPP_ROOT}) # Allow testing internal Firebase APIs. add_definitions(-DINTERNAL_EXPERIMENTAL) @@ -177,6 +274,8 @@ if(ANDROID) set(ADDITIONAL_LIBS log android atomic native_app_glue) else() + set(ABSEIL_CPP_ROOT ${CMAKE_CURRENT_LIST_DIR}/external/abseil-cpp/src/abseil-cpp) + # Build a desktop application. add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) @@ -250,11 +349,22 @@ else() endif() endif() +if(NOT ANDROID) + set(${ADDITIONAL_LIBS} ${ADDITIONAL_LIBS} firestore_core absl_variant) +else() + set(${ADDITIONAL_LIBS} ${ADDITIONAL_LIBS} + absl_base + absl_memory + absl_meta + absl_optional + absl_strings) +endif() + # Add the Firebase libraries to the target using the function from the SDK. add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL) # Note that firebase_app needs to be last in the list. set(firebase_libs firebase_firestore firebase_auth firebase_app) set(gtest_libs gtest gmock) -target_link_libraries(${integration_test_target_name} ${firebase_libs} firestore_core absl_variant +target_link_libraries(${integration_test_target_name} ${firebase_libs} ${gtest_libs} ${ADDITIONAL_LIBS}) diff --git a/firestore/integration_test_internal/abseil-cpp.cmake b/firestore/integration_test_internal/abseil-cpp.cmake new file mode 100644 index 0000000000..5b3ab3fed3 --- /dev/null +++ b/firestore/integration_test_internal/abseil-cpp.cmake @@ -0,0 +1,33 @@ +# Copyright 2019 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include(ExternalProject) + +set(version 20200225) + +ExternalProject_Add( + abseil-cpp + + DOWNLOAD_DIR ${FIREBASE_DOWNLOAD_DIR} + DOWNLOAD_NAME abseil-cpp-${version}.tar.gz + URL https://github.com/abseil/abseil-cpp/archive/${version}.tar.gz + URL_HASH SHA256=728a813291bdec2aa46eab8356ace9f75ac2ed9dfe2df5ab603c4e6c09f1c353 + + PREFIX ${CMAKE_CURRENT_BINARY_DIR} + + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/firestore/integration_test_internal/build.gradle b/firestore/integration_test_internal/build.gradle index 8069b1e03f..277ecb2d5c 100644 --- a/firestore/integration_test_internal/build.gradle +++ b/firestore/integration_test_internal/build.gradle @@ -69,6 +69,9 @@ android { proguardFile file('proguard.pro') } } + lintOptions { + abortOnError false + } } apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" diff --git a/firestore/integration_test_internal/integration_test.xcodeproj/project.pbxproj b/firestore/integration_test_internal/integration_test.xcodeproj/project.pbxproj index baae0f5067..70a863b429 100644 --- a/firestore/integration_test_internal/integration_test.xcodeproj/project.pbxproj +++ b/firestore/integration_test_internal/integration_test.xcodeproj/project.pbxproj @@ -11,15 +11,40 @@ 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D51C85F68000C89379 /* Foundation.framework */; }; 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D71C85F68000C89379 /* CoreGraphics.framework */; }; 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D91C85F68000C89379 /* UIKit.framework */; }; + D61A465D2606EA0B007EBE9B /* transaction_extra_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D61A465C2606EA0B007EBE9B /* transaction_extra_test.cc */; }; D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D61C5F8C22BABA9B00A79141 /* Images.xcassets */; }; - D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D61C5F9222BABAD100A79141 /* integration_test.cc */; }; + D61CFBC126091C3B0035CB2A /* integration_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D61CFBC026091C3A0035CB2A /* integration_test.cc */; }; D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D62CCBBF22F367140099BE9F /* gmock-all.cc */; }; D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */; }; D67D355822BABD2200292C1D /* gtest-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D67D355622BABD2100292C1D /* gtest-all.cc */; }; + D6AAAD432606C22D0025C53B /* smoke_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6AAAD292606C22C0025C53B /* smoke_test.cc */; }; + D6AAAD442606C22D0025C53B /* field_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6AAAD2A2606C22C0025C53B /* field_value_test.cc */; }; + D6AAAD452606C22D0025C53B /* document_change_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6AAAD2B2606C22D0025C53B /* document_change_test.cc */; }; + D6AAAD462606C22D0025C53B /* query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6AAAD2D2606C22D0025C53B /* query_test.cc */; }; + D6AAAD472606C22D0025C53B /* document_snapshot_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6AAAD2E2606C22D0025C53B /* document_snapshot_test.cc */; }; + D6AAAD482606C22D0025C53B /* cursor_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6AAAD2F2606C22D0025C53B /* cursor_test.cc */; }; + D6AAAD492606C22D0025C53B /* collection_reference_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6AAAD302606C22D0025C53B /* collection_reference_test.cc */; }; + D6AAAD4A2606C22D0025C53B /* fields_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6AAAD312606C22D0025C53B /* fields_test.cc */; }; + D6AAAD4B2606C22D0025C53B /* array_transform_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6AAAD322606C22D0025C53B /* array_transform_test.cc */; }; + D6AAAD4C2606C22D0025C53B /* server_timestamp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6AAAD332606C22D0025C53B /* server_timestamp_test.cc */; }; + D6AAAD4D2606C22D0025C53B /* query_snapshot_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6AAAD342606C22D0025C53B /* query_snapshot_test.cc */; }; + D6AAAD4E2606C22D0025C53B /* firestore_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6AAAD352606C22D0025C53B /* firestore_test.cc */; }; + D6AAAD4F2606C22D0025C53B /* transaction_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6AAAD362606C22D0025C53B /* transaction_test.cc */; }; + D6AAAD502606C22D0025C53B /* numeric_transforms_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6AAAD372606C22D0025C53B /* numeric_transforms_test.cc */; }; + D6AAAD512606C22D0025C53B /* sanity_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6AAAD382606C22D0025C53B /* sanity_test.cc */; }; + D6AAAD522606C22D0025C53B /* type_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6AAAD392606C22D0025C53B /* type_test.cc */; }; + D6AAAD532606C22D0025C53B /* includes_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6AAAD3A2606C22D0025C53B /* includes_test.cc */; }; + D6AAAD552606C22D0025C53B /* listener_registration_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6AAAD3D2606C22D0025C53B /* listener_registration_test.cc */; }; + D6AAAD562606C22D0025C53B /* query_network_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6AAAD3E2606C22D0025C53B /* query_network_test.cc */; }; + D6AAAD572606C22D0025C53B /* document_reference_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6AAAD3F2606C22D0025C53B /* document_reference_test.cc */; }; + D6AAAD592606C22D0025C53B /* firestore_integration_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6AAAD412606C22D0025C53B /* firestore_integration_test.cc */; }; + D6AAAD5A2606C22D0025C53B /* cleanup_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6AAAD422606C22D0025C53B /* cleanup_test.cc */; }; D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E722CB322900C2651A /* ios_app_framework.mm */; }; D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */; }; D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EC22CB323300C2651A /* firebase_test_framework.cc */; }; D6C179F022CB32A000C2651A /* app_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EF22CB32A000C2651A /* app_framework.cc */; }; + D6ED33BD2606CD890058CBF9 /* future_test_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6ED33B92606CD890058CBF9 /* future_test_util.cc */; }; + D6ED33BE2606CD890058CBF9 /* integration_test_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6ED33BB2606CD890058CBF9 /* integration_test_util.cc */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -29,20 +54,49 @@ 529226D71C85F68000C89379 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 529226D91C85F68000C89379 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 529226EE1C85F68000C89379 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + D61A465C2606EA0B007EBE9B /* transaction_extra_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = transaction_extra_test.cc; path = src/transaction_extra_test.cc; sourceTree = ""; }; D61C5F8C22BABA9B00A79141 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; D61C5F8D22BABA9C00A79141 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - D61C5F9222BABAD100A79141 /* integration_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = integration_test.cc; path = src/integration_test.cc; sourceTree = ""; }; + D61CFBC026091C3A0035CB2A /* integration_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = integration_test.cc; path = src/integration_test.cc; sourceTree = ""; }; D62CCBBF22F367140099BE9F /* gmock-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gmock-all.cc"; path = "external/googletest/src/googlemock/src/gmock-all.cc"; sourceTree = ""; }; D62CCBC122F367320099BE9F /* gmock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gmock.h; path = external/googletest/src/googlemock/include/gmock/gmock.h; sourceTree = ""; }; D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; D67D355622BABD2100292C1D /* gtest-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gtest-all.cc"; path = "external/googletest/src/googletest/src/gtest-all.cc"; sourceTree = ""; }; D67D355722BABD2100292C1D /* gtest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gtest.h; path = external/googletest/src/googletest/include/gtest/gtest.h; sourceTree = ""; }; + D6AAAD292606C22C0025C53B /* smoke_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = smoke_test.cc; path = src/smoke_test.cc; sourceTree = ""; }; + D6AAAD2A2606C22C0025C53B /* field_value_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = field_value_test.cc; path = src/field_value_test.cc; sourceTree = ""; }; + D6AAAD2B2606C22D0025C53B /* document_change_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = document_change_test.cc; path = src/document_change_test.cc; sourceTree = ""; }; + D6AAAD2C2606C22D0025C53B /* firebase_test_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = firebase_test_framework.h; path = src/firebase_test_framework.h; sourceTree = ""; }; + D6AAAD2D2606C22D0025C53B /* query_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = query_test.cc; path = src/query_test.cc; sourceTree = ""; }; + D6AAAD2E2606C22D0025C53B /* document_snapshot_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = document_snapshot_test.cc; path = src/document_snapshot_test.cc; sourceTree = ""; }; + D6AAAD2F2606C22D0025C53B /* cursor_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = cursor_test.cc; path = src/cursor_test.cc; sourceTree = ""; }; + D6AAAD302606C22D0025C53B /* collection_reference_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = collection_reference_test.cc; path = src/collection_reference_test.cc; sourceTree = ""; }; + D6AAAD312606C22D0025C53B /* fields_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = fields_test.cc; path = src/fields_test.cc; sourceTree = ""; }; + D6AAAD322606C22D0025C53B /* array_transform_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = array_transform_test.cc; path = src/array_transform_test.cc; sourceTree = ""; }; + D6AAAD332606C22D0025C53B /* server_timestamp_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = server_timestamp_test.cc; path = src/server_timestamp_test.cc; sourceTree = ""; }; + D6AAAD342606C22D0025C53B /* query_snapshot_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = query_snapshot_test.cc; path = src/query_snapshot_test.cc; sourceTree = ""; }; + D6AAAD352606C22D0025C53B /* firestore_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = firestore_test.cc; path = src/firestore_test.cc; sourceTree = ""; }; + D6AAAD362606C22D0025C53B /* transaction_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = transaction_test.cc; path = src/transaction_test.cc; sourceTree = ""; }; + D6AAAD372606C22D0025C53B /* numeric_transforms_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = numeric_transforms_test.cc; path = src/numeric_transforms_test.cc; sourceTree = ""; }; + D6AAAD382606C22D0025C53B /* sanity_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sanity_test.cc; path = src/sanity_test.cc; sourceTree = ""; }; + D6AAAD392606C22D0025C53B /* type_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = type_test.cc; path = src/type_test.cc; sourceTree = ""; }; + D6AAAD3A2606C22D0025C53B /* includes_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = includes_test.cc; path = src/includes_test.cc; sourceTree = ""; }; + D6AAAD3C2606C22D0025C53B /* firestore_integration_test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = firestore_integration_test.h; path = src/firestore_integration_test.h; sourceTree = ""; }; + D6AAAD3D2606C22D0025C53B /* listener_registration_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = listener_registration_test.cc; path = src/listener_registration_test.cc; sourceTree = ""; }; + D6AAAD3E2606C22D0025C53B /* query_network_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = query_network_test.cc; path = src/query_network_test.cc; sourceTree = ""; }; + D6AAAD3F2606C22D0025C53B /* document_reference_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = document_reference_test.cc; path = src/document_reference_test.cc; sourceTree = ""; }; + D6AAAD412606C22D0025C53B /* firestore_integration_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = firestore_integration_test.cc; path = src/firestore_integration_test.cc; sourceTree = ""; }; + D6AAAD422606C22D0025C53B /* cleanup_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = cleanup_test.cc; path = src/cleanup_test.cc; sourceTree = ""; }; D6C179E722CB322900C2651A /* ios_app_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_app_framework.mm; path = src/ios/ios_app_framework.mm; sourceTree = ""; }; D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_firebase_test_framework.mm; path = src/ios/ios_firebase_test_framework.mm; sourceTree = ""; }; D6C179EB22CB323300C2651A /* firebase_test_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = firebase_test_framework.h; path = src/firebase_test_framework.h; sourceTree = ""; }; D6C179EC22CB323300C2651A /* firebase_test_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = firebase_test_framework.cc; path = src/firebase_test_framework.cc; sourceTree = ""; }; D6C179ED22CB323300C2651A /* app_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = app_framework.h; path = src/app_framework.h; sourceTree = ""; }; D6C179EF22CB32A000C2651A /* app_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = app_framework.cc; path = src/app_framework.cc; sourceTree = ""; }; + D6ED33B92606CD890058CBF9 /* future_test_util.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = future_test_util.cc; path = src/util/future_test_util.cc; sourceTree = ""; }; + D6ED33BA2606CD890058CBF9 /* event_accumulator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = event_accumulator.h; path = src/util/event_accumulator.h; sourceTree = ""; }; + D6ED33BB2606CD890058CBF9 /* integration_test_util.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = integration_test_util.cc; path = src/util/integration_test_util.cc; sourceTree = ""; }; + D6ED33BC2606CD890058CBF9 /* future_test_util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = future_test_util.h; path = src/util/future_test_util.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -102,6 +156,11 @@ 5292271D1C85FB5500C89379 /* src */ = { isa = PBXGroup; children = ( + D61CFBC026091C3A0035CB2A /* integration_test.cc */, + D6ED33BA2606CD890058CBF9 /* event_accumulator.h */, + D6ED33B92606CD890058CBF9 /* future_test_util.cc */, + D6ED33BC2606CD890058CBF9 /* future_test_util.h */, + D6ED33BB2606CD890058CBF9 /* integration_test_util.cc */, D62CCBC122F367320099BE9F /* gmock.h */, D62CCBBF22F367140099BE9F /* gmock-all.cc */, D67D355622BABD2100292C1D /* gtest-all.cc */, @@ -110,7 +169,31 @@ D6C179ED22CB323300C2651A /* app_framework.h */, D6C179EC22CB323300C2651A /* firebase_test_framework.cc */, D6C179EB22CB323300C2651A /* firebase_test_framework.h */, - D61C5F9222BABAD100A79141 /* integration_test.cc */, + D6AAAD312606C22D0025C53B /* fields_test.cc */, + D6AAAD322606C22D0025C53B /* array_transform_test.cc */, + D6AAAD422606C22D0025C53B /* cleanup_test.cc */, + D6AAAD302606C22D0025C53B /* collection_reference_test.cc */, + D6AAAD2F2606C22D0025C53B /* cursor_test.cc */, + D6AAAD2B2606C22D0025C53B /* document_change_test.cc */, + D6AAAD3F2606C22D0025C53B /* document_reference_test.cc */, + D6AAAD2E2606C22D0025C53B /* document_snapshot_test.cc */, + D6AAAD2A2606C22C0025C53B /* field_value_test.cc */, + D6AAAD2C2606C22D0025C53B /* firebase_test_framework.h */, + D6AAAD412606C22D0025C53B /* firestore_integration_test.cc */, + D6AAAD3C2606C22D0025C53B /* firestore_integration_test.h */, + D6AAAD352606C22D0025C53B /* firestore_test.cc */, + D6AAAD3A2606C22D0025C53B /* includes_test.cc */, + D6AAAD3D2606C22D0025C53B /* listener_registration_test.cc */, + D6AAAD372606C22D0025C53B /* numeric_transforms_test.cc */, + D6AAAD3E2606C22D0025C53B /* query_network_test.cc */, + D6AAAD342606C22D0025C53B /* query_snapshot_test.cc */, + D6AAAD2D2606C22D0025C53B /* query_test.cc */, + D6AAAD382606C22D0025C53B /* sanity_test.cc */, + D6AAAD332606C22D0025C53B /* server_timestamp_test.cc */, + D6AAAD292606C22C0025C53B /* smoke_test.cc */, + D6AAAD362606C22D0025C53B /* transaction_test.cc */, + D61A465C2606EA0B007EBE9B /* transaction_extra_test.cc */, + D6AAAD392606C22D0025C53B /* type_test.cc */, 5292271E1C85FB5B00C89379 /* ios */, ); name = src; @@ -166,6 +249,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = 529226C91C85F68000C89379; @@ -196,13 +280,38 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D6AAAD432606C22D0025C53B /* smoke_test.cc in Sources */, + D6AAAD522606C22D0025C53B /* type_test.cc in Sources */, + D6AAAD492606C22D0025C53B /* collection_reference_test.cc in Sources */, + D6AAAD572606C22D0025C53B /* document_reference_test.cc in Sources */, D67D355822BABD2200292C1D /* gtest-all.cc in Sources */, + D6AAAD4D2606C22D0025C53B /* query_snapshot_test.cc in Sources */, + D6AAAD4F2606C22D0025C53B /* transaction_test.cc in Sources */, + D61A465D2606EA0B007EBE9B /* transaction_extra_test.cc in Sources */, + D6AAAD5A2606C22D0025C53B /* cleanup_test.cc in Sources */, D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */, + D61CFBC126091C3B0035CB2A /* integration_test.cc in Sources */, + D6AAAD532606C22D0025C53B /* includes_test.cc in Sources */, + D6AAAD502606C22D0025C53B /* numeric_transforms_test.cc in Sources */, + D6ED33BE2606CD890058CBF9 /* integration_test_util.cc in Sources */, D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */, - D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */, + D6AAAD4C2606C22D0025C53B /* server_timestamp_test.cc in Sources */, + D6AAAD4E2606C22D0025C53B /* firestore_test.cc in Sources */, + D6AAAD452606C22D0025C53B /* document_change_test.cc in Sources */, + D6AAAD472606C22D0025C53B /* document_snapshot_test.cc in Sources */, D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */, D6C179F022CB32A000C2651A /* app_framework.cc in Sources */, + D6AAAD442606C22D0025C53B /* field_value_test.cc in Sources */, + D6AAAD562606C22D0025C53B /* query_network_test.cc in Sources */, + D6AAAD552606C22D0025C53B /* listener_registration_test.cc in Sources */, + D6AAAD4A2606C22D0025C53B /* fields_test.cc in Sources */, + D6AAAD462606C22D0025C53B /* query_test.cc in Sources */, + D6ED33BD2606CD890058CBF9 /* future_test_util.cc in Sources */, + D6AAAD592606C22D0025C53B /* firestore_integration_test.cc in Sources */, D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */, + D6AAAD4B2606C22D0025C53B /* array_transform_test.cc in Sources */, + D6AAAD512606C22D0025C53B /* sanity_test.cc in Sources */, + D6AAAD482606C22D0025C53B /* cursor_test.cc in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -301,6 +410,15 @@ "$(inherited)", "$(PROJECT_DIR)", ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "COCOAPODS=1", + "$(inherited)", + "PB_FIELD_32BIT=1", + "PB_NO_PACKED_STRUCTS=1", + "PB_ENABLE_MALLOC=1", + "INTERNAL_EXPERIMENTAL=1", + ); HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, @@ -309,8 +427,11 @@ "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", "\"$(SRCROOT)/external/googletest/src/googletest\"", "\"$(SRCROOT)/external/googletest/src/googlemock\"", + "\"$(SRCROOT)/Pods/FirebaseFirestore\"", + "\"$(SRCROOT)/../..\"", ); INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.cpp.firestore.testapp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; WRAPPER_EXTENSION = app; @@ -329,6 +450,15 @@ "$(inherited)", "$(PROJECT_DIR)", ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "COCOAPODS=1", + "$(inherited)", + "PB_FIELD_32BIT=1", + "PB_NO_PACKED_STRUCTS=1", + "PB_ENABLE_MALLOC=1", + "INTERNAL_EXPERIMENTAL=1", + ); HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, @@ -337,8 +467,11 @@ "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", "\"$(SRCROOT)/external/googletest/src/googletest\"", "\"$(SRCROOT)/external/googletest/src/googlemock\"", + "\"$(SRCROOT)/Pods/FirebaseFirestore\"", + "\"$(SRCROOT)/../..\"", ); INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.cpp.firestore.testapp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; WRAPPER_EXTENSION = app; diff --git a/firestore/integration_test_internal/proguard.pro b/firestore/integration_test_internal/proguard.pro index 2d04b8a9a5..72f4e1d5ab 100644 --- a/firestore/integration_test_internal/proguard.pro +++ b/firestore/integration_test_internal/proguard.pro @@ -1,2 +1,4 @@ -ignorewarnings -keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { * ; } +-keep,includedescriptorclasses public class com.google.android.gms.tasks.CancellationTokenSource { * ; } +-keep,includedescriptorclasses public class com.google.android.gms.tasks.TaskCompletionSource { * ; } diff --git a/firestore/integration_test_internal/src/android/cancellation_token_source.cc b/firestore/integration_test_internal/src/android/cancellation_token_source.cc index 876b7cab3f..48cd95177f 100644 --- a/firestore/integration_test_internal/src/android/cancellation_token_source.cc +++ b/firestore/integration_test_internal/src/android/cancellation_token_source.cc @@ -1,4 +1,4 @@ -#include "firestore/src/tests/android/cancellation_token_source.h" +#include "android/cancellation_token_source.h" #include "firestore/src/jni/env.h" #include "firestore/src/jni/loader.h" @@ -14,7 +14,7 @@ using jni::Method; using jni::Object; constexpr char kClassName[] = - PROGUARD_KEEP_CLASS "com/google/android/gms/tasks/CancellationTokenSource"; + "com/google/android/gms/tasks/CancellationTokenSource"; Constructor kConstructor("()V"); Method kGetToken("getToken", "()Lcom/google/android/gms/tasks/CancellationToken;"); diff --git a/firestore/integration_test_internal/src/android/firebase_firestore_settings_android_test.cc b/firestore/integration_test_internal/src/android/firebase_firestore_settings_android_test.cc deleted file mode 100644 index 22f43f159c..0000000000 --- a/firestore/integration_test_internal/src/android/firebase_firestore_settings_android_test.cc +++ /dev/null @@ -1,52 +0,0 @@ -#include "firestore/src/android/firebase_firestore_settings_android.h" - -#include - -#include "firestore/src/include/firebase/firestore/settings.h" -#include "firestore/src/tests/firestore_integration_test.h" -#include "testing/base/public/gmock.h" -#include "gtest/gtest.h" - -namespace firebase { -namespace firestore { - -TEST_F(FirestoreIntegrationTest, ConverterBoolsAllTrue) { - JNIEnv* env = app()->GetJNIEnv(); - - Settings settings; - settings.set_host("foo"); - settings.set_ssl_enabled(true); - settings.set_persistence_enabled(true); - jobject java_settings = - FirebaseFirestoreSettingsInternal::SettingToJavaSetting(env, settings); - const Settings result = - FirebaseFirestoreSettingsInternal::JavaSettingToSetting(env, - java_settings); - EXPECT_EQ("foo", result.host()); - EXPECT_TRUE(result.is_ssl_enabled()); - EXPECT_TRUE(result.is_persistence_enabled()); - - env->DeleteLocalRef(java_settings); -} - -TEST_F(FirestoreIntegrationTest, ConverterBoolsAllFalse) { - JNIEnv* env = app()->GetJNIEnv(); - - Settings settings; - settings.set_host("bar"); - settings.set_ssl_enabled(false); - settings.set_persistence_enabled(false); - jobject java_settings = - FirebaseFirestoreSettingsInternal::SettingToJavaSetting(env, settings); - const Settings result = - FirebaseFirestoreSettingsInternal::JavaSettingToSetting(env, - java_settings); - EXPECT_EQ("bar", result.host()); - EXPECT_FALSE(result.is_ssl_enabled()); - EXPECT_FALSE(result.is_persistence_enabled()); - - env->DeleteLocalRef(java_settings); -} - -} // namespace firestore -} // namespace firebase diff --git a/firestore/integration_test_internal/src/android/firestore_android_test_main.cc b/firestore/integration_test_internal/src/android/firestore_android_test_main.cc deleted file mode 100644 index 6073ef3052..0000000000 --- a/firestore/integration_test_internal/src/android/firestore_android_test_main.cc +++ /dev/null @@ -1,234 +0,0 @@ -// Copyright 2016 Google Inc. All rights reserved. -// -// 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. - -// This file defines a stand-alone no-op native Android app. The app deals with -// app events and do nothing interesting. This app is the one under Android -// instrumented test. -// -// In addition, this file also contains an Android native function to be called -// in the Android instrumented test directly. The native function will run all -// gtest tests and update information on passed tests. - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -// non-google3 code should include "firebase/app.h" instead. -#include "app/src/include/firebase/app.h" -#include "app/src/assert.h" -#include "firestore/src/include/firebase/firestore.h" -#include "firestore/src/android/firestore_android.h" -// non-google3 code should include "gtest/gtest.h" instead. -#include "gtest/gtest.h" -// For GTEST_FLAG(filter). -#include "third_party/googletest/googletest/src/gtest-internal-inl.h" - -#ifndef NATIVE_FUNCTION_NAME -#define NATIVE_FUNCTION_NAME Java_com_google_firebase_test_MyTest_RunAllTest -#endif // NATIVE_FUNCTION_NAME - -#define STRINGIFY_(X) #X -#define STRINGIFY(X) STRINGIFY_(X) - -// Preserve the JNIEnv and activity that are required by the Firestore client. -static JNIEnv* g_env = nullptr; -static jobject g_activity = nullptr; -static int g_stdout_pipe_file[2]; -static int g_stderr_pipe_file[2]; -static pthread_t g_pipe_stdout_thread; -static pthread_t g_pipe_stderr_thread; - -// This implementation is derived from http://github.com/google/fplutil -static struct android_app* g_app_state = nullptr; -static bool g_destroy_requested = false; -static bool g_started = false; -static bool g_restarted = false; -static pthread_mutex_t g_started_mutex; - -static void* pipe_thread(void* pipe) { - int* pipe_file = static_cast(pipe); - char buffer[256]; - const char* tag = pipe_file == g_stdout_pipe_file ? "stdout" : "stderr"; - size_t read_size = read(pipe_file[0], buffer, sizeof(buffer) - 1); - while (read_size > 0) { - // Remove trailing \n. - if (buffer[read_size - 1] == '\n') buffer[read_size - 1] = '\0'; - __android_log_print(ANDROID_LOG_INFO, tag, "%s", buffer); - read_size = read(pipe_file[0], buffer, sizeof(buffer) - 1); - } - return nullptr; -} - -namespace firebase { -namespace firestore { - -App* GetApp(const char* name) { - if (name == nullptr || std::string{name} == kDefaultAppName) { - return App::Create(g_env, g_activity); - } else { - App* default_app = App::GetInstance(); - FIREBASE_ASSERT_MESSAGE(default_app, - "Cannot create a named app before the default app"); - return App::Create(default_app->options(), name, g_env, g_activity); - } -} - -App* GetApp() { return GetApp(nullptr); } - -// Process events pending on the main thread. -// Returns true when the app receives an event requesting exit. -bool ProcessEvents(int msec) { - android_poll_source* source = nullptr; - int events; - int looperId = ALooper_pollAll(msec, /*outFd=*/nullptr, &events, - reinterpret_cast(&source)); - if (looperId >= 0 && source) { - source->process(g_app_state, source); - } - return g_destroy_requested | g_restarted; -} - -FirestoreInternal* CreateTestFirestoreInternal(App* app) { - return new FirestoreInternal(app); -} - -void InitializeFirestore(Firestore*) { - // No extra initialization necessary -} - -} // namespace firestore -} // namespace firebase - -extern "C" JNIEXPORT jboolean JNICALL NATIVE_FUNCTION_NAME(JNIEnv* env, - jobject thiz, - jobject activity, - jstring filter) { - if (env == nullptr || thiz == nullptr || activity == nullptr || - filter == nullptr) { - __android_log_print(ANDROID_LOG_INFO, __func__, "Invalid parameters."); - return static_cast(false); - } - - // Preparation work before run all tests. - if (g_env == nullptr) { - g_env = env; - } - if (g_activity == nullptr) { - g_activity = activity; - } - // Anything that goes into std output will be gone. So we re-direct it into - // android logcat. - pipe(g_stdout_pipe_file); - dup2(g_stdout_pipe_file[1], STDOUT_FILENO); - if (pthread_create(&g_pipe_stdout_thread, nullptr, pipe_thread, - g_stdout_pipe_file) == -1) { - __android_log_print(ANDROID_LOG_INFO, __func__, - "Failed to re-direct stdout to logcat"); - } else { - __android_log_print(ANDROID_LOG_INFO, __func__, "Dump stdout to logcat"); - pthread_detach(g_pipe_stdout_thread); - } - pipe(g_stderr_pipe_file); - dup2(g_stderr_pipe_file[1], STDERR_FILENO); - if (pthread_create(&g_pipe_stderr_thread, nullptr, pipe_thread, - g_stderr_pipe_file) == -1) { - __android_log_print(ANDROID_LOG_INFO, __func__, - "Failed to re-direct stderr to logcat"); - } else { - __android_log_print(ANDROID_LOG_INFO, __func__, "Dump stderr to logcat"); - pthread_detach(g_pipe_stderr_thread); - } - // Required by gtest. Although this is a no-op. TODO(zxu): potentially pass - // through the Java test flag and translate the JUnit test flag into an - // equivalent gtest flag. - int argc = 1; - const char* argv[] = {STRINGIFY(NATIVE_FUNCTION_NAME)}; - testing::InitGoogleTest(&argc, const_cast(argv)); - testing::internal::GTestFlagSaver flag_saver; - const char* filter_c_str = env->GetStringUTFChars(filter, /*isCopy=*/nullptr); - testing::GTEST_FLAG(filter) = filter_c_str; - env->ReleaseStringUTFChars(filter, filter_c_str); - - // Now run all tests. - __android_log_print(ANDROID_LOG_INFO, __func__, "Start to run test %s", - testing::GTEST_FLAG(filter).c_str()); - auto unit_test = testing::UnitTest::GetInstance(); - bool result = unit_test->Run() == 0; - __android_log_print( - ANDROID_LOG_INFO, __func__, - "Tests finished.\n" - " passed tests: %d\n" - " skipped tests: %d\n" - " failed tests: %d\n" - " disabled tests: %d\n" - " total tests: %d\n", - unit_test->successful_test_count(), unit_test->skipped_test_count(), - unit_test->failed_test_count(), unit_test->disabled_test_count(), - unit_test->total_test_count()); - return static_cast(result); -} - -// Handle state changes from via native app glue. -static void OnAppCmd(struct android_app* app, int32_t cmd) { - g_destroy_requested |= cmd == APP_CMD_DESTROY; -} - -// A dummy android_main() that flushes pending events and finishes the activity. -// Modified from the Firebase Game's testapp. -extern "C" void android_main(struct android_app* state) { - // native_app_glue spawns a new thread, calling android_main() when the - // activity onStart() or onRestart() methods are called. This code handles - // the case where we're re-entering this method on a different thread by - // signalling the existing thread to exit, waiting for it to complete before - // reinitializing the application. - if (g_started) { - g_restarted = true; - // Wait for the existing thread to exit. - pthread_mutex_lock(&g_started_mutex); - pthread_mutex_unlock(&g_started_mutex); - } else { - g_started_mutex = PTHREAD_MUTEX_INITIALIZER; - } - pthread_mutex_lock(&g_started_mutex); - g_started = true; - - // Save native app glue state and setup a callback to track the state. - g_destroy_requested = false; - g_app_state = state; - g_app_state->onAppCmd = OnAppCmd; - - // Wait until the user wants to quit the app. - __android_log_print(ANDROID_LOG_INFO, __func__, - "started. Waiting for events."); - while (!firebase::firestore::ProcessEvents(1000)) { - } - - // Finish the activity. - __android_log_print(ANDROID_LOG_INFO, __func__, "quitting."); - if (!g_restarted) ANativeActivity_finish(state->activity); - - g_app_state->activity->vm->DetachCurrentThread(); - g_started = false; - g_restarted = false; - pthread_mutex_unlock(&g_started_mutex); -} diff --git a/firestore/integration_test_internal/src/android/firestore_integration_test_android.cc b/firestore/integration_test_internal/src/android/firestore_integration_test_android.cc index 941ab1df52..dbe5fc39d2 100644 --- a/firestore/integration_test_internal/src/android/firestore_integration_test_android.cc +++ b/firestore/integration_test_internal/src/android/firestore_integration_test_android.cc @@ -1,9 +1,9 @@ -#include "firestore/src/tests/android/firestore_integration_test_android.h" +#include "android/firestore_integration_test_android.h" #include "firestore/src/jni/env.h" #include "firestore/src/jni/string.h" -#include "firestore/src/tests/android/cancellation_token_source.h" -#include "firestore/src/tests/android/task_completion_source.h" +#include "android/cancellation_token_source.h" +#include "android/task_completion_source.h" namespace firebase { namespace firestore { diff --git a/firestore/integration_test_internal/src/android/firestore_integration_test_android.h b/firestore/integration_test_internal/src/android/firestore_integration_test_android.h index 9510d869d1..10ee990031 100644 --- a/firestore/integration_test_internal/src/android/firestore_integration_test_android.h +++ b/firestore/integration_test_internal/src/android/firestore_integration_test_android.h @@ -10,8 +10,8 @@ #include "firestore/src/jni/ownership.h" #include "firestore/src/jni/task.h" #include "firestore/src/jni/throwable.h" -#include "firestore/src/tests/firestore_integration_test.h" -#include "testing/base/public/gmock.h" +#include "firestore_integration_test.h" +#include "gmock/gmock.h" namespace firebase { namespace firestore { diff --git a/firestore/integration_test_internal/src/android/firestore_integration_test_android_test.cc b/firestore/integration_test_internal/src/android/firestore_integration_test_android_test.cc index 61ebb9b5c2..a7b51ac7c3 100644 --- a/firestore/integration_test_internal/src/android/firestore_integration_test_android_test.cc +++ b/firestore/integration_test_internal/src/android/firestore_integration_test_android_test.cc @@ -1,4 +1,4 @@ -#include "firestore/src/tests/android/firestore_integration_test_android.h" +#include "android/firestore_integration_test_android.h" #include diff --git a/firestore/integration_test_internal/src/android/geo_point_android_test.cc b/firestore/integration_test_internal/src/android/geo_point_android_test.cc index 883add3d32..b1934133f2 100644 --- a/firestore/integration_test_internal/src/android/geo_point_android_test.cc +++ b/firestore/integration_test_internal/src/android/geo_point_android_test.cc @@ -1,7 +1,7 @@ #include "firestore/src/android/geo_point_android.h" #include "firestore/src/jni/env.h" -#include "firestore/src/tests/firestore_integration_test.h" +#include "firestore_integration_test.h" #include "gtest/gtest.h" #include "firebase/firestore/geo_point.h" diff --git a/firestore/integration_test_internal/src/android/jni_runnable_android_test.cc b/firestore/integration_test_internal/src/android/jni_runnable_android_test.cc index 6d47f8fc1e..02093980b4 100644 --- a/firestore/integration_test_internal/src/android/jni_runnable_android_test.cc +++ b/firestore/integration_test_internal/src/android/jni_runnable_android_test.cc @@ -5,7 +5,7 @@ #include "firestore/src/jni/ownership.h" #include "firestore/src/jni/task.h" #include "firestore/src/jni/throwable.h" -#include "firestore/src/tests/android/firestore_integration_test_android.h" +#include "android/firestore_integration_test_android.h" #include "gtest/gtest.h" namespace firebase { diff --git a/firestore/integration_test_internal/src/android/promise_android_test.cc b/firestore/integration_test_internal/src/android/promise_android_test.cc index d359e5e2e8..af08245e70 100644 --- a/firestore/integration_test_internal/src/android/promise_android_test.cc +++ b/firestore/integration_test_internal/src/android/promise_android_test.cc @@ -15,12 +15,14 @@ #include "firestore/src/jni/object.h" #include "firestore/src/jni/ownership.h" #include "firestore/src/jni/task.h" -#include "firestore/src/tests/android/cancellation_token_source.h" -#include "firestore/src/tests/android/firestore_integration_test_android.h" -#include "firestore/src/tests/android/task_completion_source.h" -#include "testing/base/public/gmock.h" +#include "android/cancellation_token_source.h" +#include "android/firestore_integration_test_android.h" +#include "android/task_completion_source.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" #include "firebase/firestore/firestore_errors.h" +#include "app_framework.h" +#include "firebase_test_framework.h" namespace firebase { namespace firestore { @@ -41,9 +43,15 @@ class PromiseTest : public FirestoreAndroidIntegrationTest { public: PromiseTest() : promises_(GetFirestoreInternal(TestFirestore())) { jni::Env env = GetEnv(); - cancellation_token_source_ = CancellationTokenSource::Create(env); - task_completion_source_ = TaskCompletionSource::Create( - env, cancellation_token_source_.GetToken(env)); + // TODO(b/183294303): Uncomment this initialization code when + // these tests are fixed on Android. + // + // Note: This usage of jni::Loader may be broken because this is + // running in a background thread. Requires further investigation. + // + // cancellation_token_source_ = CancellationTokenSource::Create(env); + // task_completion_source_ = TaskCompletionSource::Create( + // env, cancellation_token_source_.GetToken(env)); } // An enum of asynchronous functions to use in tests, as required by @@ -77,7 +85,9 @@ class PromiseTest : public FirestoreAndroidIntegrationTest { cancellation_token_source_.Cancel(env); } - static jni::Env GetEnv() { return FirestoreInternal::GetEnv(); } + static jni::Env GetEnv() { + return jni::Env(app_framework::GetJniEnv()); + } private: PromiseFactory promises_; @@ -213,6 +223,8 @@ class TestVoidCompletion : public TestCompletionBase { }; TEST_F(PromiseTest, FutureVoidShouldSucceedWhenTaskSucceeds) { + SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android. + jni::Env env = GetEnv(); auto future = promises().NewFuture(env, AsyncFn::kFn, GetTask()); EXPECT_EQ(future.status(), FutureStatus::kFutureStatusPending); @@ -226,6 +238,8 @@ TEST_F(PromiseTest, FutureVoidShouldSucceedWhenTaskSucceeds) { } TEST_F(PromiseTest, FutureNonVoidShouldSucceedWhenTaskSucceeds) { + SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android. + jni::Env env = GetEnv(); auto future = promises().NewFuture(env, AsyncFn::kFn, GetTask()); @@ -240,6 +254,8 @@ TEST_F(PromiseTest, FutureNonVoidShouldSucceedWhenTaskSucceeds) { } TEST_F(PromiseTest, FutureVoidShouldFailWhenTaskFails) { + SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android. + jni::Env env = GetEnv(); auto future = promises().NewFuture(env, AsyncFn::kFn, GetTask()); EXPECT_EQ(future.status(), FutureStatus::kFutureStatusPending); @@ -254,6 +270,8 @@ TEST_F(PromiseTest, FutureVoidShouldFailWhenTaskFails) { } TEST_F(PromiseTest, FutureNonVoidShouldFailWhenTaskFails) { + SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android. + jni::Env env = GetEnv(); auto future = promises().NewFuture(env, AsyncFn::kFn, GetTask()); @@ -269,6 +287,8 @@ TEST_F(PromiseTest, FutureNonVoidShouldFailWhenTaskFails) { } TEST_F(PromiseTest, FutureVoidShouldCancelWhenTaskCancels) { + SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android. + jni::Env env = GetEnv(); auto future = promises().NewFuture(env, AsyncFn::kFn, GetTask()); EXPECT_EQ(future.status(), FutureStatus::kFutureStatusPending); @@ -283,6 +303,8 @@ TEST_F(PromiseTest, FutureVoidShouldCancelWhenTaskCancels) { } TEST_F(PromiseTest, FutureNonVoidShouldCancelWhenTaskCancels) { + SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android. + jni::Env env = GetEnv(); auto future = promises().NewFuture(env, AsyncFn::kFn, GetTask()); @@ -298,6 +320,8 @@ TEST_F(PromiseTest, FutureNonVoidShouldCancelWhenTaskCancels) { } TEST_F(PromiseTest, FutureVoidShouldCallCompletionWhenTaskSucceeds) { + SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android. + jni::Env env = GetEnv(); TestVoidCompletion completion; auto future = promises().NewFuture(env, AsyncFn::kFn, GetTask(), @@ -314,6 +338,8 @@ TEST_F(PromiseTest, FutureVoidShouldCallCompletionWhenTaskSucceeds) { } TEST_F(PromiseTest, FutureNonVoidShouldCallCompletionWhenTaskSucceeds) { + SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android. + jni::Env env = GetEnv(); TestCompletion completion; auto future = promises().NewFuture(env, AsyncFn::kFn, @@ -330,6 +356,8 @@ TEST_F(PromiseTest, FutureNonVoidShouldCallCompletionWhenTaskSucceeds) { } TEST_F(PromiseTest, FutureVoidShouldCallCompletionWhenTaskFails) { + SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android. + jni::Env env = GetEnv(); TestVoidCompletion completion; auto future = promises().NewFuture(env, AsyncFn::kFn, GetTask(), @@ -346,6 +374,8 @@ TEST_F(PromiseTest, FutureVoidShouldCallCompletionWhenTaskFails) { } TEST_F(PromiseTest, FutureNonVoidShouldCallCompletionWhenTaskFails) { + SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android. + jni::Env env = GetEnv(); TestCompletion completion; auto future = promises().NewFuture(env, AsyncFn::kFn, @@ -362,6 +392,8 @@ TEST_F(PromiseTest, FutureNonVoidShouldCallCompletionWhenTaskFails) { } TEST_F(PromiseTest, FutureVoidShouldCallCompletionWhenTaskCancels) { + SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android. + jni::Env env = GetEnv(); TestVoidCompletion completion; auto future = promises().NewFuture(env, AsyncFn::kFn, GetTask(), @@ -378,6 +410,8 @@ TEST_F(PromiseTest, FutureVoidShouldCallCompletionWhenTaskCancels) { } TEST_F(PromiseTest, FutureNonVoidShouldCallCompletionWhenTaskCancels) { + SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android. + jni::Env env = GetEnv(); TestCompletion completion; auto future = promises().NewFuture(env, AsyncFn::kFn, diff --git a/firestore/integration_test_internal/src/android/settings_android_test.cc b/firestore/integration_test_internal/src/android/settings_android_test.cc index d97f007e6f..957bb82265 100644 --- a/firestore/integration_test_internal/src/android/settings_android_test.cc +++ b/firestore/integration_test_internal/src/android/settings_android_test.cc @@ -2,8 +2,8 @@ #include "firestore/src/include/firebase/firestore/settings.h" #include "firestore/src/jni/env.h" -#include "firestore/src/tests/firestore_integration_test.h" -#include "testing/base/public/gmock.h" +#include "firestore_integration_test.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" namespace firebase { diff --git a/firestore/integration_test_internal/src/android/snapshot_metadata_android_test.cc b/firestore/integration_test_internal/src/android/snapshot_metadata_android_test.cc index 3e4a6b2f43..3716c1adcd 100644 --- a/firestore/integration_test_internal/src/android/snapshot_metadata_android_test.cc +++ b/firestore/integration_test_internal/src/android/snapshot_metadata_android_test.cc @@ -2,18 +2,24 @@ #include "firestore/src/include/firebase/firestore/snapshot_metadata.h" #include "firestore/src/jni/env.h" -#include "firestore/src/tests/firestore_integration_test.h" -#include "testing/base/public/gmock.h" +#include "firestore_integration_test.h" +#include "android/firestore_integration_test_android.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" +#include "firebase_test_framework.h" namespace firebase { namespace firestore { +using SnapshotMetadataAndroidTest = FirestoreAndroidIntegrationTest; + using jni::Class; using jni::Env; using jni::Local; -TEST_F(FirestoreIntegrationTest, Converts) { +TEST_F(SnapshotMetadataAndroidTest, Converts) { + SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android. + Env env; Local clazz = diff --git a/firestore/integration_test_internal/src/android/task_completion_source.cc b/firestore/integration_test_internal/src/android/task_completion_source.cc index 7158eb7c50..ad741dd0df 100644 --- a/firestore/integration_test_internal/src/android/task_completion_source.cc +++ b/firestore/integration_test_internal/src/android/task_completion_source.cc @@ -1,4 +1,4 @@ -#include "firestore/src/tests/android/task_completion_source.h" +#include "android/task_completion_source.h" #include "firestore/src/jni/env.h" #include "firestore/src/jni/loader.h" @@ -15,7 +15,7 @@ using jni::Object; using jni::Task; constexpr char kClassName[] = - PROGUARD_KEEP_CLASS "com/google/android/gms/tasks/TaskCompletionSource"; + "com/google/android/gms/tasks/TaskCompletionSource"; Constructor kConstructor("()V"); Constructor kConstructorWithCancellationToken( "(Lcom/google/android/gms/tasks/CancellationToken;)V"); diff --git a/firestore/integration_test_internal/src/android/timestamp_android_test.cc b/firestore/integration_test_internal/src/android/timestamp_android_test.cc index 3256043af5..a81cbad7a4 100644 --- a/firestore/integration_test_internal/src/android/timestamp_android_test.cc +++ b/firestore/integration_test_internal/src/android/timestamp_android_test.cc @@ -1,7 +1,7 @@ #include "firestore/src/android/timestamp_android.h" #include "firestore/src/jni/env.h" -#include "firestore/src/tests/firestore_integration_test.h" +#include "firestore_integration_test.h" #include "gtest/gtest.h" #include "firebase/firestore/timestamp.h" diff --git a/firestore/integration_test_internal/src/android/util_autoid.cc b/firestore/integration_test_internal/src/android/util_autoid.cc new file mode 100644 index 0000000000..afb215eab0 --- /dev/null +++ b/firestore/integration_test_internal/src/android/util_autoid.cc @@ -0,0 +1,54 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Android-specific implementation of AutoId, since core library is +// not used on Android. +#include "android/util_autoid.h" + +#include +#include + +namespace firebase { +namespace firestore { +namespace util { +namespace { +const int kAutoIdLength = 20; +const char kAutoIdAlphabet[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; +} // namespace + +std::string CreateAutoId() { + std::string auto_id; + auto_id.reserve(kAutoIdLength); + + // -2 here because: + // * sizeof(kAutoIdAlphabet) includes the trailing null terminator + // * uniform_int_distribution is inclusive on both sizes + std::uniform_int_distribution letters(0, sizeof(kAutoIdAlphabet) - 2); + + std::random_device randomizer; + std::mt19937 generator(randomizer()); + + for (int i = 0; i < kAutoIdLength; i++) { + int rand_index = letters(generator); + auto_id.append(1, kAutoIdAlphabet[rand_index]); + } + return auto_id; +} + +} // namespace util +} // namespace firestore +} // namespace firebase diff --git a/firestore/integration_test_internal/src/android/util_autoid.h b/firestore/integration_test_internal/src/android/util_autoid.h new file mode 100644 index 0000000000..7eb8016fea --- /dev/null +++ b/firestore/integration_test_internal/src/android/util_autoid.h @@ -0,0 +1,33 @@ +/* + * Copyright 2021 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. + */ + +#ifndef FIRESTORE_TEST_ANDROID_UTIL_AUTOID_H_ +#define FIRESTORE_TEST_ANDROID_UTIL_AUTOID_H_ + +#include + +namespace firebase { +namespace firestore { +namespace util { + +// Generates a random ID suitable for use as a document ID. +std::string CreateAutoId(); + +} // namespace util +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_TEST_ANDROID_UTIL_AUTOID_H_ diff --git a/firestore/integration_test_internal/src/cleanup_test.cc b/firestore/integration_test_internal/src/cleanup_test.cc index a127fdbe9f..b1b775b35c 100644 --- a/firestore/integration_test_internal/src/cleanup_test.cc +++ b/firestore/integration_test_internal/src/cleanup_test.cc @@ -13,9 +13,9 @@ void ExpectAllMethodsAreNoOps(Query* ptr); // and return null. template void ExpectNullFirestore(T* ptr) { - EXPECT_EQ(ptr->firestore(), nullptr); + EXPECT_TRUE(ptr->firestore() == nullptr); // Make sure to check both const and non-const overloads. - EXPECT_EQ(static_cast(ptr)->firestore(), nullptr); + EXPECT_TRUE(static_cast(ptr)->firestore() == nullptr); } // Checks that the given object can be copied from, and the resulting copy can @@ -46,20 +46,20 @@ void ExpectEqualityToWork(T* ptr) { // value-initialized values. void ExpectAllMethodsAreNoOps(CollectionReference* ptr) { - EXPECT_EQ(*ptr, CollectionReference()); + EXPECT_TRUE(*ptr == CollectionReference()); ExpectCopyableAndMoveable(ptr); ExpectEqualityToWork(ptr); ExpectAllMethodsAreNoOps(static_cast(ptr)); - EXPECT_EQ(ptr->id(), ""); - EXPECT_EQ(ptr->path(), ""); + EXPECT_TRUE(ptr->id() == ""); + EXPECT_TRUE(ptr->path() == ""); - EXPECT_EQ(ptr->Document(), DocumentReference()); - EXPECT_EQ(ptr->Document("foo"), DocumentReference()); - EXPECT_EQ(ptr->Document(std::string("foo")), DocumentReference()); + EXPECT_TRUE(ptr->Document() == DocumentReference()); + EXPECT_TRUE(ptr->Document("foo") == DocumentReference()); + EXPECT_TRUE(ptr->Document(std::string("foo")) == DocumentReference()); - EXPECT_EQ(ptr->Add(MapFieldValue()), FailedFuture()); + EXPECT_TRUE(ptr->Add(MapFieldValue()) == FailedFuture()); } void ExpectAllMethodsAreNoOps(DocumentChange* ptr) { @@ -67,11 +67,11 @@ void ExpectAllMethodsAreNoOps(DocumentChange* ptr) { // ExpectEqualityToWork(ptr); ExpectCopyableAndMoveable(ptr); - EXPECT_EQ(ptr->type(), DocumentChange::Type()); + EXPECT_TRUE(ptr->type() == DocumentChange::Type()); // TODO(b/137966104): implement == on `DocumentSnapshot` ptr->document(); - EXPECT_EQ(ptr->old_index(), 0); - EXPECT_EQ(ptr->new_index(), 0); + EXPECT_TRUE(ptr->old_index() == 0); + EXPECT_TRUE(ptr->new_index() == 0); } void ExpectAllMethodsAreNoOps(DocumentReference* ptr) { @@ -81,23 +81,23 @@ void ExpectAllMethodsAreNoOps(DocumentReference* ptr) { ExpectCopyableAndMoveable(ptr); ExpectNullFirestore(ptr); - EXPECT_EQ(ptr->ToString(), "DocumentReference(invalid)"); + EXPECT_TRUE(ptr->ToString() == "DocumentReference(invalid)"); - EXPECT_EQ(ptr->id(), ""); - EXPECT_EQ(ptr->path(), ""); + EXPECT_TRUE(ptr->id() == ""); + EXPECT_TRUE(ptr->path() == ""); - EXPECT_EQ(ptr->Parent(), CollectionReference()); - EXPECT_EQ(ptr->Collection("foo"), CollectionReference()); - EXPECT_EQ(ptr->Collection(std::string("foo")), CollectionReference()); + EXPECT_TRUE(ptr->Parent() == CollectionReference()); + EXPECT_TRUE(ptr->Collection("foo") == CollectionReference()); + EXPECT_TRUE(ptr->Collection(std::string("foo")) == CollectionReference()); - EXPECT_EQ(ptr->Get(), FailedFuture()); + EXPECT_TRUE(ptr->Get() == FailedFuture()); - EXPECT_EQ(ptr->Set(MapFieldValue()), FailedFuture()); + EXPECT_TRUE(ptr->Set(MapFieldValue()) == FailedFuture()); - EXPECT_EQ(ptr->Update(MapFieldValue()), FailedFuture()); - EXPECT_EQ(ptr->Update(MapFieldPathValue()), FailedFuture()); + EXPECT_TRUE(ptr->Update(MapFieldValue()) == FailedFuture()); + EXPECT_TRUE(ptr->Update(MapFieldPathValue()) == FailedFuture()); - EXPECT_EQ(ptr->Delete(), FailedFuture()); + EXPECT_TRUE(ptr->Delete() == FailedFuture()); #if defined(FIREBASE_USE_STD_FUNCTION) ptr->AddSnapshotListener( @@ -112,20 +112,20 @@ void ExpectAllMethodsAreNoOps(DocumentSnapshot* ptr) { // ExpectEqualityToWork(ptr); ExpectCopyableAndMoveable(ptr); - EXPECT_EQ(ptr->ToString(), "DocumentSnapshot(invalid)"); + EXPECT_TRUE(ptr->ToString() == "DocumentSnapshot(invalid)"); - EXPECT_EQ(ptr->id(), ""); + EXPECT_TRUE(ptr->id() == ""); EXPECT_FALSE(ptr->exists()); - EXPECT_EQ(ptr->reference(), DocumentReference()); + EXPECT_TRUE(ptr->reference() == DocumentReference()); // TODO(b/137966104): implement == on `SnapshotMetadata` ptr->metadata(); - EXPECT_EQ(ptr->GetData(), MapFieldValue()); + EXPECT_TRUE(ptr->GetData() == MapFieldValue()); - EXPECT_EQ(ptr->Get("foo"), FieldValue()); - EXPECT_EQ(ptr->Get(std::string("foo")), FieldValue()); - EXPECT_EQ(ptr->Get(FieldPath{"foo"}), FieldValue()); + EXPECT_TRUE(ptr->Get("foo") == FieldValue()); + EXPECT_TRUE(ptr->Get(std::string("foo")) == FieldValue()); + EXPECT_TRUE(ptr->Get(FieldPath{"foo"}) == FieldValue()); } void ExpectAllMethodsAreNoOps(FieldValue* ptr) { @@ -136,7 +136,7 @@ void ExpectAllMethodsAreNoOps(FieldValue* ptr) { // FieldValue doesn't have a separate "invalid" type in its enum. EXPECT_TRUE(ptr->is_null()); - EXPECT_EQ(ptr->type(), FieldValue::Type()); + EXPECT_TRUE(ptr->type() == FieldValue::Type()); EXPECT_FALSE(ptr->is_boolean()); EXPECT_FALSE(ptr->is_integer()); @@ -149,14 +149,14 @@ void ExpectAllMethodsAreNoOps(FieldValue* ptr) { EXPECT_FALSE(ptr->is_array()); EXPECT_FALSE(ptr->is_map()); - EXPECT_EQ(ptr->boolean_value(), false); - EXPECT_EQ(ptr->integer_value(), 0); - EXPECT_EQ(ptr->double_value(), 0); - EXPECT_EQ(ptr->timestamp_value(), Timestamp()); - EXPECT_EQ(ptr->string_value(), ""); - EXPECT_EQ(ptr->blob_value(), nullptr); - EXPECT_EQ(ptr->reference_value(), DocumentReference()); - EXPECT_EQ(ptr->geo_point_value(), GeoPoint()); + EXPECT_TRUE(ptr->boolean_value() == false); + EXPECT_TRUE(ptr->integer_value() == 0); + EXPECT_TRUE(ptr->double_value() == 0); + EXPECT_TRUE(ptr->timestamp_value() == Timestamp()); + EXPECT_TRUE(ptr->string_value() == ""); + EXPECT_TRUE(ptr->blob_value() == nullptr); + EXPECT_TRUE(ptr->reference_value() == DocumentReference()); + EXPECT_TRUE(ptr->geo_point_value() == GeoPoint()); EXPECT_TRUE(ptr->array_value().empty()); EXPECT_TRUE(ptr->map_value().empty()); } @@ -173,46 +173,46 @@ void ExpectAllMethodsAreNoOps(Query* ptr) { ExpectCopyableAndMoveable(ptr); ExpectNullFirestore(ptr); - EXPECT_EQ(ptr->WhereEqualTo("foo", FieldValue()), Query()); - EXPECT_EQ(ptr->WhereEqualTo(FieldPath{"foo"}, FieldValue()), Query()); + EXPECT_TRUE(ptr->WhereEqualTo("foo", FieldValue()) == Query()); + EXPECT_TRUE(ptr->WhereEqualTo(FieldPath{"foo"}, FieldValue()) == Query()); - EXPECT_EQ(ptr->WhereLessThan("foo", FieldValue()), Query()); - EXPECT_EQ(ptr->WhereLessThan(FieldPath{"foo"}, FieldValue()), Query()); + EXPECT_TRUE(ptr->WhereLessThan("foo", FieldValue()) == Query()); + EXPECT_TRUE(ptr->WhereLessThan(FieldPath{"foo"}, FieldValue()) == Query()); - EXPECT_EQ(ptr->WhereLessThanOrEqualTo("foo", FieldValue()), Query()); - EXPECT_EQ(ptr->WhereLessThanOrEqualTo(FieldPath{"foo"}, FieldValue()), + EXPECT_TRUE(ptr->WhereLessThanOrEqualTo("foo", FieldValue()) == Query()); + EXPECT_TRUE(ptr->WhereLessThanOrEqualTo(FieldPath{"foo"}, FieldValue()) == Query()); - EXPECT_EQ(ptr->WhereGreaterThan("foo", FieldValue()), Query()); - EXPECT_EQ(ptr->WhereGreaterThan(FieldPath{"foo"}, FieldValue()), Query()); + EXPECT_TRUE(ptr->WhereGreaterThan("foo", FieldValue()) == Query()); + EXPECT_TRUE(ptr->WhereGreaterThan(FieldPath{"foo"}, FieldValue()) == Query()); - EXPECT_EQ(ptr->WhereGreaterThanOrEqualTo("foo", FieldValue()), Query()); - EXPECT_EQ(ptr->WhereGreaterThanOrEqualTo(FieldPath{"foo"}, FieldValue()), + EXPECT_TRUE(ptr->WhereGreaterThanOrEqualTo("foo", FieldValue()) == Query()); + EXPECT_TRUE(ptr->WhereGreaterThanOrEqualTo(FieldPath{"foo"}, FieldValue()) == Query()); - EXPECT_EQ(ptr->WhereArrayContains("foo", FieldValue()), Query()); - EXPECT_EQ(ptr->WhereArrayContains(FieldPath{"foo"}, FieldValue()), Query()); + EXPECT_TRUE(ptr->WhereArrayContains("foo", FieldValue()) == Query()); + EXPECT_TRUE(ptr->WhereArrayContains(FieldPath{"foo"}, FieldValue()) == Query()); - EXPECT_EQ(ptr->OrderBy("foo"), Query()); - EXPECT_EQ(ptr->OrderBy(FieldPath{"foo"}), Query()); + EXPECT_TRUE(ptr->OrderBy("foo") == Query()); + EXPECT_TRUE(ptr->OrderBy(FieldPath{"foo"}) == Query()); - EXPECT_EQ(ptr->Limit(123), Query()); + EXPECT_TRUE(ptr->Limit(123) == Query()); - EXPECT_EQ(ptr->StartAt(DocumentSnapshot()), Query()); - EXPECT_EQ(ptr->StartAt(std::vector()), Query()); + EXPECT_TRUE(ptr->StartAt(DocumentSnapshot()) == Query()); + EXPECT_TRUE(ptr->StartAt(std::vector()) == Query()); - EXPECT_EQ(ptr->StartAfter(DocumentSnapshot()), Query()); - EXPECT_EQ(ptr->StartAfter(std::vector()), Query()); + EXPECT_TRUE(ptr->StartAfter(DocumentSnapshot()) == Query()); + EXPECT_TRUE(ptr->StartAfter(std::vector()) == Query()); - EXPECT_EQ(ptr->EndBefore(DocumentSnapshot()), Query()); - EXPECT_EQ(ptr->EndBefore(std::vector()), Query()); + EXPECT_TRUE(ptr->EndBefore(DocumentSnapshot()) == Query()); + EXPECT_TRUE(ptr->EndBefore(std::vector()) == Query()); - EXPECT_EQ(ptr->EndAt(DocumentSnapshot()), Query()); - EXPECT_EQ(ptr->EndAt(std::vector()), Query()); + EXPECT_TRUE(ptr->EndAt(DocumentSnapshot()) == Query()); + EXPECT_TRUE(ptr->EndAt(std::vector()) == Query()); - EXPECT_EQ(ptr->Get(), FailedFuture()); + EXPECT_TRUE(ptr->Get() == FailedFuture()); - EXPECT_EQ(ptr->Get(), FailedFuture()); + EXPECT_TRUE(ptr->Get() == FailedFuture()); #if defined(FIREBASE_USE_STD_FUNCTION) ptr->AddSnapshotListener( @@ -227,7 +227,7 @@ void ExpectAllMethodsAreNoOps(QuerySnapshot* ptr) { // ExpectEqualityToWork(ptr); ExpectCopyableAndMoveable(ptr); - EXPECT_EQ(ptr->query(), Query()); + EXPECT_TRUE(ptr->query() == Query()); // TODO(b/137966104): implement == on `SnapshotMetadata` ptr->metadata(); @@ -235,7 +235,7 @@ void ExpectAllMethodsAreNoOps(QuerySnapshot* ptr) { EXPECT_TRUE(ptr->DocumentChanges().empty()); EXPECT_TRUE(ptr->documents().empty()); EXPECT_TRUE(ptr->empty()); - EXPECT_EQ(ptr->size(), 0); + EXPECT_TRUE(ptr->size() == 0); } void ExpectAllMethodsAreNoOps(WriteBatch* ptr) { @@ -249,7 +249,7 @@ void ExpectAllMethodsAreNoOps(WriteBatch* ptr) { ptr->Delete(DocumentReference()); - EXPECT_EQ(ptr->Commit(), FailedFuture()); + EXPECT_TRUE(ptr->Commit() == FailedFuture()); } using CleanupTest = FirestoreIntegrationTest; @@ -342,7 +342,7 @@ TEST_F(CleanupTest, FieldValueIsBlankAfterCleanup) { // stay valid after Firestore has shut down. EXPECT_TRUE(str_value.is_valid()); EXPECT_TRUE(str_value.is_string()); - EXPECT_EQ(str_value.string_value(), "bar"); + EXPECT_TRUE(str_value.string_value() == "bar"); // However, need to make sure that in a reference value, the reference was // cleaned up. @@ -389,7 +389,7 @@ TEST_F(CleanupTest, QuerySnapshotIsBlankAfterCleanup) { WriteDocument(doc, MapFieldValue{{"foo", FieldValue::String("bar")}}); QuerySnapshot snap = ReadDocuments(col); - EXPECT_EQ(snap.size(), 1); + EXPECT_TRUE(snap.size() == 1); DeleteFirestore(col.firestore()); SCOPED_TRACE("QuerySnapshot.AfterCleanup"); diff --git a/firestore/integration_test_internal/src/field_value_test.cc b/firestore/integration_test_internal/src/field_value_test.cc index ab93c176bf..125040f058 100644 --- a/firestore/integration_test_internal/src/field_value_test.cc +++ b/firestore/integration_test_internal/src/field_value_test.cc @@ -55,36 +55,36 @@ TEST_F(FieldValueTest, Assignment) { TEST_F(FieldValueTest, TestNullType) { FieldValue value = FieldValue::Null(); - EXPECT_EQ(Type::kNull, value.type()); + EXPECT_TRUE(Type::kNull == value.type()); } TEST_F(FieldValueTest, TestBooleanType) { FieldValue value = FieldValue::Boolean(true); - EXPECT_EQ(Type::kBoolean, value.type()); - EXPECT_EQ(true, value.boolean_value()); + EXPECT_TRUE(Type::kBoolean == value.type()); + EXPECT_TRUE(true == value.boolean_value()); } TEST_F(FieldValueTest, TestIntegerType) { FieldValue value = FieldValue::Integer(123); - EXPECT_EQ(Type::kInteger, value.type()); - EXPECT_EQ(123, value.integer_value()); + EXPECT_TRUE(Type::kInteger == value.type()); + EXPECT_TRUE(123 == value.integer_value()); } TEST_F(FieldValueTest, TestDoubleType) { FieldValue value = FieldValue::Double(3.1415926); - EXPECT_EQ(Type::kDouble, value.type()); - EXPECT_EQ(3.1415926, value.double_value()); + EXPECT_TRUE(Type::kDouble == value.type()); + EXPECT_TRUE(3.1415926 == value.double_value()); } TEST_F(FieldValueTest, TestTimestampType) { FieldValue value = FieldValue::Timestamp({12345, 54321}); - EXPECT_EQ(Type::kTimestamp, value.type()); - EXPECT_EQ(Timestamp(12345, 54321), value.timestamp_value()); + EXPECT_TRUE(Type::kTimestamp == value.type()); + EXPECT_TRUE(Timestamp(12345, 54321) == value.timestamp_value()); } TEST_F(FieldValueTest, TestStringType) { FieldValue value = FieldValue::String("hello"); - EXPECT_EQ(Type::kString, value.type()); + EXPECT_TRUE(Type::kString == value.type()); EXPECT_STREQ("hello", value.string_value().c_str()); } @@ -92,131 +92,131 @@ TEST_F(FieldValueTest, TestStringTypeSpecialCases) { // Latin small letter e with acute accent. Codepoints above 7F are encoded // in multiple bytes. std::string str = u8"\u00E9clair"; - EXPECT_EQ(FieldValue::String(str).string_value(), str); + EXPECT_TRUE(FieldValue::String(str).string_value() == str); // Latin small letter e + combining acute accent. Similar to above but using // a combining character, which is not normalized. str = u8"e\u0301clair"; - EXPECT_EQ(FieldValue::String(str).string_value(), str); + EXPECT_TRUE(FieldValue::String(str).string_value() == str); // Face with tears of joy. This is an emoji outside the BMP and encodes as // four bytes in UTF-8 and as a surrogate pair in UTF-16. JNI's modified UTF-8 // encodes each surrogate as a separate three byte value for a total of six // bytes. str = u8"\U0001F602!!"; - EXPECT_EQ(FieldValue::String(str).string_value(), str); + EXPECT_TRUE(FieldValue::String(str).string_value() == str); // Embedded null character. JNI's modified UTF-8 encoding encodes this in a // two byte sequence that doesn't contain a zero byte. str = u8"aaa"; str[1] = '\0'; FieldValue value = FieldValue::String(str); - EXPECT_EQ(value.string_value(), str); + EXPECT_TRUE(value.string_value() == str); EXPECT_STREQ(value.string_value().c_str(), "a"); } TEST_F(FieldValueTest, TestBlobType) { uint8_t blob[] = "( ͡° ͜ʖ ͡°)"; FieldValue value = FieldValue::Blob(blob, sizeof(blob)); - EXPECT_EQ(Type::kBlob, value.type()); - EXPECT_EQ(sizeof(blob), value.blob_size()); + EXPECT_TRUE(Type::kBlob == value.type()); + EXPECT_TRUE(sizeof(blob) == value.blob_size()); const uint8_t* value_blob = value.blob_value(); FieldValue copied(value); - EXPECT_EQ(Type::kBlob, copied.type()); - EXPECT_EQ(sizeof(blob), copied.blob_size()); + EXPECT_TRUE(Type::kBlob == copied.type()); + EXPECT_TRUE(sizeof(blob) == copied.blob_size()); const uint8_t* copied_blob = copied.blob_value(); for (int i = 0; i < sizeof(blob); ++i) { - EXPECT_EQ(blob[i], value_blob[i]); - EXPECT_EQ(blob[i], copied_blob[i]); + EXPECT_TRUE(blob[i] == value_blob[i]); + EXPECT_TRUE(blob[i] == copied_blob[i]); } } TEST_F(FieldValueTest, TestReferenceType) { FieldValue value = FieldValue::Reference(TestFirestore()->Document("foo/bar")); - EXPECT_EQ(Type::kReference, value.type()); - EXPECT_EQ(value.reference_value().path(), "foo/bar"); + EXPECT_TRUE(Type::kReference == value.type()); + EXPECT_TRUE(value.reference_value().path() == "foo/bar"); } TEST_F(FieldValueTest, TestGeoPointType) { FieldValue value = FieldValue::GeoPoint({43, 80}); - EXPECT_EQ(Type::kGeoPoint, value.type()); - EXPECT_EQ(GeoPoint(43, 80), value.geo_point_value()); + EXPECT_TRUE(Type::kGeoPoint == value.type()); + EXPECT_TRUE(GeoPoint(43, 80) == value.geo_point_value()); } TEST_F(FieldValueTest, TestArrayType) { FieldValue value = FieldValue::Array( {FieldValue::Boolean(true), FieldValue::Integer(123)}); - EXPECT_EQ(Type::kArray, value.type()); + EXPECT_TRUE(Type::kArray == value.type()); const std::vector& array = value.array_value(); - EXPECT_EQ(2, array.size()); - EXPECT_EQ(true, array[0].boolean_value()); - EXPECT_EQ(123, array[1].integer_value()); + EXPECT_TRUE(2 == array.size()); + EXPECT_TRUE(true == array[0].boolean_value()); + EXPECT_TRUE(123 == array[1].integer_value()); } TEST_F(FieldValueTest, TestMapType) { FieldValue value = FieldValue::Map(MapFieldValue{{"Bool", FieldValue::Boolean(true)}, {"Int", FieldValue::Integer(123)}}); - EXPECT_EQ(Type::kMap, value.type()); + EXPECT_TRUE(Type::kMap == value.type()); MapFieldValue map = value.map_value(); - EXPECT_EQ(2, map.size()); - EXPECT_EQ(true, map["Bool"].boolean_value()); - EXPECT_EQ(123, map["Int"].integer_value()); + EXPECT_TRUE(2 == map.size()); + EXPECT_TRUE(true == map["Bool"].boolean_value()); + EXPECT_TRUE(123 == map["Int"].integer_value()); } TEST_F(FieldValueTest, TestSentinelType) { FieldValue delete_value = FieldValue::Delete(); - EXPECT_EQ(Type::kDelete, delete_value.type()); + EXPECT_TRUE(Type::kDelete == delete_value.type()); FieldValue server_timestamp_value = FieldValue::ServerTimestamp(); - EXPECT_EQ(Type::kServerTimestamp, server_timestamp_value.type()); + EXPECT_TRUE(Type::kServerTimestamp == server_timestamp_value.type()); std::vector array = {FieldValue::Boolean(true), FieldValue::Integer(123)}; FieldValue array_union = FieldValue::ArrayUnion(array); - EXPECT_EQ(Type::kArrayUnion, array_union.type()); + EXPECT_TRUE(Type::kArrayUnion == array_union.type()); FieldValue array_remove = FieldValue::ArrayRemove(array); - EXPECT_EQ(Type::kArrayRemove, array_remove.type()); + EXPECT_TRUE(Type::kArrayRemove == array_remove.type()); FieldValue increment_integer = FieldValue::Increment(1); - EXPECT_EQ(Type::kIncrementInteger, increment_integer.type()); + EXPECT_TRUE(Type::kIncrementInteger == increment_integer.type()); FieldValue increment_double = FieldValue::Increment(1.0); - EXPECT_EQ(Type::kIncrementDouble, increment_double.type()); + EXPECT_TRUE(Type::kIncrementDouble == increment_double.type()); } TEST_F(FieldValueTest, TestEquality) { - EXPECT_EQ(FieldValue::Null(), FieldValue::Null()); - EXPECT_EQ(FieldValue::Boolean(true), FieldValue::Boolean(true)); - EXPECT_EQ(FieldValue::Integer(123), FieldValue::Integer(123)); - EXPECT_EQ(FieldValue::Double(456.0), FieldValue::Double(456.0)); - EXPECT_EQ(FieldValue::String("foo"), FieldValue::String("foo")); + EXPECT_TRUE(FieldValue::Null() == FieldValue::Null()); + EXPECT_TRUE(FieldValue::Boolean(true) == FieldValue::Boolean(true)); + EXPECT_TRUE(FieldValue::Integer(123) == FieldValue::Integer(123)); + EXPECT_TRUE(FieldValue::Double(456.0) == FieldValue::Double(456.0)); + EXPECT_TRUE(FieldValue::String("foo") == FieldValue::String("foo")); - EXPECT_EQ(FieldValue::Timestamp({123, 456}), + EXPECT_TRUE(FieldValue::Timestamp({123, 456}) == FieldValue::Timestamp({123, 456})); uint8_t blob[] = "( ͡° ͜ʖ ͡°)"; - EXPECT_EQ(FieldValue::Blob(blob, sizeof(blob)), + EXPECT_TRUE(FieldValue::Blob(blob, sizeof(blob)) == FieldValue::Blob(blob, sizeof(blob))); - EXPECT_EQ(FieldValue::GeoPoint({43, 80}), FieldValue::GeoPoint({43, 80})); + EXPECT_TRUE(FieldValue::GeoPoint({43, 80}) == FieldValue::GeoPoint({43, 80})); - EXPECT_EQ( - FieldValue::Array({FieldValue::Integer(3), FieldValue::Double(4.0)}), + EXPECT_TRUE( + FieldValue::Array({FieldValue::Integer(3), FieldValue::Double(4.0)}) == FieldValue::Array({FieldValue::Integer(3), FieldValue::Double(4.0)})); - EXPECT_EQ(FieldValue::Map(MapFieldValue{{"foo", FieldValue::Integer(3)}}), + EXPECT_TRUE(FieldValue::Map(MapFieldValue{{"foo", FieldValue::Integer(3)}}) == FieldValue::Map(MapFieldValue{{"foo", FieldValue::Integer(3)}})); - EXPECT_EQ(FieldValue::Delete(), FieldValue::Delete()); - EXPECT_EQ(FieldValue::ServerTimestamp(), FieldValue::ServerTimestamp()); + EXPECT_TRUE(FieldValue::Delete() == FieldValue::Delete()); + EXPECT_TRUE(FieldValue::ServerTimestamp() == FieldValue::ServerTimestamp()); // TODO(varconst): make this work on Android, or remove the tests below. - // EXPECT_EQ(FieldValue::ArrayUnion({FieldValue::Null()}), + // EXPECT_TRUE(FieldValue::ArrayUnion({FieldValue::Null()}) == // FieldValue::ArrayUnion({FieldValue::Null()})); - // EXPECT_EQ(FieldValue::ArrayRemove({FieldValue::Null()}), + // EXPECT_TRUE(FieldValue::ArrayRemove({FieldValue::Null()}) == // FieldValue::ArrayRemove({FieldValue::Null()})); } @@ -263,92 +263,92 @@ TEST_F(FieldValueTest, TestInequalityDueToDifferentTypes) { } TEST_F(FieldValueTest, TestToString) { - EXPECT_EQ("", FieldValue().ToString()); + EXPECT_TRUE("" == FieldValue().ToString()); - EXPECT_EQ("null", FieldValue::Null().ToString()); - EXPECT_EQ("true", FieldValue::Boolean(true).ToString()); - EXPECT_EQ("123", FieldValue::Integer(123L).ToString()); - EXPECT_EQ("3.14", FieldValue::Double(3.14).ToString()); - EXPECT_EQ("Timestamp(seconds=12345, nanoseconds=54321)", + EXPECT_TRUE("null" == FieldValue::Null().ToString()); + EXPECT_TRUE("true" == FieldValue::Boolean(true).ToString()); + EXPECT_TRUE("123" == FieldValue::Integer(123L).ToString()); + EXPECT_TRUE("3.14" == FieldValue::Double(3.14).ToString()); + EXPECT_TRUE("Timestamp(seconds=12345, nanoseconds=54321)" == FieldValue::Timestamp({12345, 54321}).ToString()); - EXPECT_EQ("'hello'", FieldValue::String("hello").ToString()); + EXPECT_TRUE("'hello'" == FieldValue::String("hello").ToString()); uint8_t blob[] = "( ͡° ͜ʖ ͡°)"; - EXPECT_EQ("Blob(28 20 cd a1 c2 b0 20 cd 9c ca 96 20 cd a1 c2 b0 29 00)", + EXPECT_TRUE("Blob(28 20 cd a1 c2 b0 20 cd 9c ca 96 20 cd a1 c2 b0 29 00)" == FieldValue::Blob(blob, sizeof(blob)).ToString()); - EXPECT_EQ("GeoPoint(latitude=43, longitude=80)", + EXPECT_TRUE("GeoPoint(latitude=43, longitude=80)" == FieldValue::GeoPoint({43, 80}).ToString()); - EXPECT_EQ("DocumentReference(invalid)", FieldValue::Reference({}).ToString()); + EXPECT_TRUE("DocumentReference(invalid)" == FieldValue::Reference({}).ToString()); - EXPECT_EQ("[]", FieldValue::Array({}).ToString()); - EXPECT_EQ("[null]", FieldValue::Array({FieldValue::Null()}).ToString()); - EXPECT_EQ("[null, true, 1]", + EXPECT_TRUE("[]" == FieldValue::Array({}).ToString()); + EXPECT_TRUE("[null]" == FieldValue::Array({FieldValue::Null()}).ToString()); + EXPECT_TRUE("[null, true, 1]" == FieldValue::Array({FieldValue::Null(), FieldValue::Boolean(true), FieldValue::Integer(1)}) .ToString()); // TODO(b/150016438): uncomment this case (fails on Android). - // EXPECT_EQ("[]", FieldValue::Array({FieldValue()}).ToString()); + // EXPECT_TRUE("[]" == FieldValue::Array({FieldValue()}).ToString()); - EXPECT_EQ("{}", FieldValue::Map({}).ToString()); + EXPECT_TRUE("{}" == FieldValue::Map({}).ToString()); // TODO(b/150016438): uncomment this case (fails on Android). - // EXPECT_EQ("{bad: }", FieldValue::Map({ + // EXPECT_TRUE("{bad: }" == FieldValue::Map({ // {"bad", // FieldValue()}, // }) // .ToString()); - EXPECT_EQ("{Null: null}", FieldValue::Map({ + EXPECT_TRUE("{Null: null}" == FieldValue::Map({ {"Null", FieldValue::Null()}, }) .ToString()); // Note: because the map is unordered, it's hard to check the case where a map // has more than one element. - EXPECT_EQ("FieldValue::Delete()", FieldValue::Delete().ToString()); - EXPECT_EQ("FieldValue::ServerTimestamp()", + EXPECT_TRUE("FieldValue::Delete()" == FieldValue::Delete().ToString()); + EXPECT_TRUE("FieldValue::ServerTimestamp()" == FieldValue::ServerTimestamp().ToString()); - EXPECT_EQ("FieldValue::ArrayUnion()", + EXPECT_TRUE("FieldValue::ArrayUnion()" == FieldValue::ArrayUnion({FieldValue::Null()}).ToString()); - EXPECT_EQ("FieldValue::ArrayRemove()", + EXPECT_TRUE("FieldValue::ArrayRemove()" == FieldValue::ArrayRemove({FieldValue::Null()}).ToString()); - EXPECT_EQ("FieldValue::Increment()", FieldValue::Increment(1).ToString()); - EXPECT_EQ("FieldValue::Increment()", FieldValue::Increment(1.0).ToString()); + EXPECT_TRUE("FieldValue::Increment()" == FieldValue::Increment(1).ToString()); + EXPECT_TRUE("FieldValue::Increment()" == FieldValue::Increment(1.0).ToString()); } TEST_F(FieldValueTest, TestIncrementChoosesTheCorrectType) { // Signed integers // NOLINTNEXTLINE -- exact integer width doesn't matter. short foo = 1; - EXPECT_EQ(FieldValue::Increment(foo).type(), Type::kIncrementInteger); - EXPECT_EQ(FieldValue::Increment(1).type(), Type::kIncrementInteger); - EXPECT_EQ(FieldValue::Increment(1L).type(), Type::kIncrementInteger); + EXPECT_TRUE(FieldValue::Increment(foo).type() == Type::kIncrementInteger); + EXPECT_TRUE(FieldValue::Increment(1).type() == Type::kIncrementInteger); + EXPECT_TRUE(FieldValue::Increment(1L).type() == Type::kIncrementInteger); // Note: using `long long` syntax to avoid go/lsc-long-long-literal. // NOLINTNEXTLINE -- exact integer width doesn't matter. long long llfoo = 1; - EXPECT_EQ(FieldValue::Increment(llfoo).type(), Type::kIncrementInteger); + EXPECT_TRUE(FieldValue::Increment(llfoo).type() == Type::kIncrementInteger); // Unsigned integers // NOLINTNEXTLINE -- exact integer width doesn't matter. unsigned short ufoo = 1; - EXPECT_EQ(FieldValue::Increment(ufoo).type(), Type::kIncrementInteger); - EXPECT_EQ(FieldValue::Increment(1U).type(), Type::kIncrementInteger); + EXPECT_TRUE(FieldValue::Increment(ufoo).type() == Type::kIncrementInteger); + EXPECT_TRUE(FieldValue::Increment(1U).type() == Type::kIncrementInteger); // Floating point - EXPECT_EQ(FieldValue::Increment(1.0f).type(), Type::kIncrementDouble); - EXPECT_EQ(FieldValue::Increment(1.0).type(), Type::kIncrementDouble); + EXPECT_TRUE(FieldValue::Increment(1.0f).type() == Type::kIncrementDouble); + EXPECT_TRUE(FieldValue::Increment(1.0).type() == Type::kIncrementDouble); // The statements below shouldn't compile (uncomment to check). // Types that would lead to truncation: - // EXPECT_EQ(FieldValue::Increment(1UL).type(), Type::kIncrementInteger); + // EXPECT_TRUE(FieldValue::Increment(1UL).type() == Type::kIncrementInteger); // unsigned long long ullfoo = 1; - // EXPECT_EQ(FieldValue::Increment(ullfoo).type(), Type::kIncrementInteger); - // EXPECT_EQ(FieldValue::Increment(1.0L).type(), Type::kIncrementDouble); + // EXPECT_TRUE(FieldValue::Increment(ullfoo).type() == Type::kIncrementInteger); + // EXPECT_TRUE(FieldValue::Increment(1.0L).type() == Type::kIncrementDouble); // Inapplicable types: - // EXPECT_EQ(FieldValue::Increment(true).type(), Type::kIncrementInteger); - // EXPECT_EQ(FieldValue::Increment('a').type(), Type::kIncrementInteger); - // EXPECT_EQ(FieldValue::Increment("abc").type(), Type::kIncrementInteger); + // EXPECT_TRUE(FieldValue::Increment(true).type() == Type::kIncrementInteger); + // EXPECT_TRUE(FieldValue::Increment('a').type() == Type::kIncrementInteger); + // EXPECT_TRUE(FieldValue::Increment("abc").type() == Type::kIncrementInteger); } #endif // !defined(FIRESTORE_STUB_BUILD) diff --git a/firestore/integration_test_internal/src/fields_test.cc b/firestore/integration_test_internal/src/fields_test.cc index a9430c4e69..49d178e18e 100644 --- a/firestore/integration_test_internal/src/fields_test.cc +++ b/firestore/integration_test_internal/src/fields_test.cc @@ -161,10 +161,10 @@ TEST_F(FieldsTest, TestFieldsWithSpecialCharsCanBeReadDirectly) { MapFieldValue expected = DottedData(1); EXPECT_EQ(expected["a"].string_value(), snapshot.Get("a").string_value()); - EXPECT_EQ(expected["b.dot"].integer_value(), - snapshot.GetData()["b.dot"].integer_value()); - EXPECT_EQ(expected["c\\slash"].integer_value(), - snapshot.GetData()["c\\slash"].integer_value()); + EXPECT_TRUE(expected["b.dot"].integer_value() == + snapshot.GetData()["b.dot"].integer_value()); + EXPECT_TRUE(expected["c\\slash"].integer_value() == + snapshot.GetData()["c\\slash"].integer_value()); } TEST_F(FieldsTest, TestFieldsWithSpecialCharsCanBeUpdated) { diff --git a/firestore/integration_test_internal/src/firestore_integration_test.cc b/firestore/integration_test_internal/src/firestore_integration_test.cc index 1a4f015fad..d2c8040fd8 100644 --- a/firestore/integration_test_internal/src/firestore_integration_test.cc +++ b/firestore/integration_test_internal/src/firestore_integration_test.cc @@ -4,8 +4,12 @@ #include #include +#if !defined(__ANDROID__) #include "absl/strings/ascii.h" #include "Firestore/core/src/util/autoid.h" +#else +#include "android/util_autoid.h" +#endif // !defined(__ANDROID__) #include "app_framework.h" namespace firebase { @@ -32,7 +36,9 @@ void LocateEmulator(Firestore* db) { address = std::getenv("FIRESTORE_EMULATOR_HOST"); } +#if !defined(__ANDROID__) absl::StripAsciiWhitespace(&address); +#endif // !defined(__ANDROID__) if (!address.empty()) { auto settings = db->settings(); settings.set_host(address); @@ -125,7 +131,6 @@ Firestore* FirestoreIntegrationTest::TestFirestore( LocateEmulator(db); InitializeFirestore(db); - return db; } diff --git a/firestore/integration_test_internal/src/firestore_test.cc b/firestore/integration_test_internal/src/firestore_test.cc index 154e8a8cb2..b5ec3b9c09 100644 --- a/firestore/integration_test_internal/src/firestore_test.cc +++ b/firestore/integration_test_internal/src/firestore_test.cc @@ -22,9 +22,15 @@ #include "firestore/src/common/macros.h" #include "gmock/gmock.h" #include "gtest/gtest.h" +#if !defined(__ANDROID__) #include "Firestore/core/src/util/autoid.h" +#else +#include "android/util_autoid.h" +#endif // !defined(__ANDROID__) #include "Firestore/core/src/util/firestore_exceptions.h" +#include "firebase_test_framework.h" + // These test cases are in sync with native iOS client SDK test // Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm // and native Android client SDK test @@ -1475,6 +1481,8 @@ TEST_F(FirestoreIntegrationTest, DomainObjectsReferToSameFirestoreInstance) { } TEST_F(FirestoreIntegrationTest, AuthWorks) { + SKIP_TEST_ON_MACOS; // TODO(b/183294303) Fix this test on Mac. + // This test only works locally or on guitar because it depends on a live // Auth backend. if (getenv("UNITTEST_ON_FORGE") != nullptr) { @@ -1523,6 +1531,8 @@ TEST_F(FirestoreIntegrationTest, AuthWorks) { // This test is to ensure b/172986326 doesn't regress. // Note: this test only exists in C++. TEST_F(FirestoreIntegrationTest, FirestoreCanBeDeletedFromTransaction) { + SKIP_TEST_ON_MACOS; // TODO(b/183294303) Fix this test on Mac. + auto* app = App::Create(this->app()->options(), "foo"); auto* db = Firestore::GetInstance(app); diff --git a/firestore/integration_test_internal/src/includes_test.cc b/firestore/integration_test_internal/src/includes_test.cc index 19a2993337..7e8bcb5532 100644 --- a/firestore/integration_test_internal/src/includes_test.cc +++ b/firestore/integration_test_internal/src/includes_test.cc @@ -2,6 +2,7 @@ #include "firebase/firestore.h" #include "firestore_integration_test.h" +#include "app_framework.h" #include "gtest/gtest.h" namespace firebase { @@ -25,62 +26,65 @@ struct TestTransactionFunction : TransactionFunction { Error Apply(Transaction&, std::string&) override { return Error::kErrorOk; } }; +} // namespace + // This test makes sure that all the objects in Firestore public API are // available from just including "firestore.h". // If this test compiles, that is sufficient. // Not using `FirestoreIntegrationTest` to avoid any headers it includes. TEST_F(IncludesTest, TestIncludingFirestoreHeaderIsSufficient) { + // We don't actually need to run any of the below, just compile it. + if (0) { #if defined(__ANDROID__) - App* app = App::Create(nullptr, nullptr); + App* app = App::Create(app_framework::GetJniEnv(), app_framework::GetActivity()); #elif defined(FIRESTORE_STUB_BUILD) - // Stubs don't load values from `GoogleService-Info.plist`/etc., so the app - // has to be configured explicitly. - AppOptions options; - options.set_project_id("foo"); - options.set_app_id("foo"); - options.set_api_key("foo"); - App* app = App::Create(options); + // Stubs don't load values from `GoogleService-Info.plist`/etc., so the app + // has to be configured explicitly. + AppOptions options; + options.set_project_id("foo"); + options.set_app_id("foo"); + options.set_api_key("foo"); + App* app = App::Create(options); #else - App* app = App::Create(); - + App* app = App::Create(); #endif // defined(__ANDROID__) - Firestore* firestore = CreateFirestore(app); + Firestore* firestore = CreateFirestore(app); - { - // Check that Firestore isn't just forward-declared. - DocumentReference doc = firestore->Document("foo/bar"); - Future future = doc.Get(); - DocumentChange doc_change; - DocumentReference doc_ref; - DocumentSnapshot doc_snap; - FieldPath field_path; - FieldValue field_value; - ListenerRegistration listener_registration; - MapFieldValue map_field_value; - MetadataChanges metadata_changes = MetadataChanges::kExclude; - Query query; - QuerySnapshot query_snapshot; - SetOptions set_options; - Settings settings; - SnapshotMetadata snapshot_metadata; - Source source = Source::kDefault; - // Cannot default-construct a `Transaction`. - WriteBatch write_batch; + { + // Check that Firestore isn't just forward-declared. + DocumentReference doc = firestore->Document("foo/bar"); + Future future = doc.Get(); + DocumentChange doc_change; + DocumentReference doc_ref; + DocumentSnapshot doc_snap; + FieldPath field_path; + FieldValue field_value; + ListenerRegistration listener_registration; + MapFieldValue map_field_value; + MetadataChanges metadata_changes = MetadataChanges::kExclude; + Query query; + QuerySnapshot query_snapshot; + SetOptions set_options; + Settings settings; + SnapshotMetadata snapshot_metadata; + Source source = Source::kDefault; + // Cannot default-construct a `Transaction`. + WriteBatch write_batch; - TestListener test_listener; - TestTransactionFunction test_transaction_function; + TestListener test_listener; + TestTransactionFunction test_transaction_function; - Timestamp timestamp; - GeoPoint geo_point; - Error error = Error::kErrorOk; + Timestamp timestamp; + GeoPoint geo_point; + Error error = Error::kErrorOk; + } + delete firestore; + delete app; } - delete firestore; - delete app; } -} // namespace } // namespace firestore } // namespace firebase diff --git a/firestore/integration_test_internal/src/integration_test.cc b/firestore/integration_test_internal/src/integration_test.cc new file mode 100644 index 0000000000..ec9d1c3927 --- /dev/null +++ b/firestore/integration_test_internal/src/integration_test.cc @@ -0,0 +1,680 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// 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. + +/* + IMPORTANT: This file is used by both the regular and the internal Firestore + integration tests, and needs to be present and identical in both. + + Please ensure that any changes to this file are reflected in both of its + locations: + + - firestore/integration_test/src/integration_test.cc + - firestore/integration_test_internal/src/integration_test.cc + + If you make any modifications to this file in one of the two locations, please + copy the modified file into the other location before committing the change. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "app_framework.h" // NOLINT +#include "firebase/auth.h" +#include "firebase/firestore.h" +#include "firebase_test_framework.h" // NOLINT + +// The TO_STRING macro is useful for command line defined strings as the quotes +// get stripped. +#define TO_STRING_EXPAND(X) #X +#define TO_STRING(X) TO_STRING_EXPAND(X) + +// Path to the Firebase config file to load. +#ifdef FIREBASE_CONFIG +#define FIREBASE_CONFIG_STRING TO_STRING(FIREBASE_CONFIG) +#else +#define FIREBASE_CONFIG_STRING "" +#endif // FIREBASE_CONFIG + +namespace firebase_testapp_automated { + +using app_framework::GetCurrentTimeInMicroseconds; +using app_framework::LogDebug; +using app_framework::ProcessEvents; +using firebase_test_framework::FirebaseTest; +using testing::ElementsAre; +using testing::Pair; +using testing::ResultOf; +using testing::UnorderedElementsAre; + +// Very basic first-level tests for Firestore. More comprehensive integration +// tests are contained in other source files. +class FirebaseFirestoreBasicTest : public FirebaseTest { + public: + FirebaseFirestoreBasicTest(); + ~FirebaseFirestoreBasicTest() override; + + // Called once before all tests. + static void SetUpTestSuite(); + // Called once after all tests. + static void TearDownTestSuite(); + + // Called at the start of each test. + void SetUp() override; + // Called after each test. + void TearDown() override; + + protected: + // Initialize Firebase App and Firebase Auth. + static void InitializeAppAndAuth(); + // Shut down Firebase App and Firebase Auth. + static void TerminateAppAndAuth(); + + // Sign in an anonymous user. + static void SignIn(); + // Sign out the current user, if applicable. + // If this is an anonymous user, deletes the user instead, + // to avoid polluting the user list. + static void SignOut(); + + // Initialize Firestore. + void InitializeFirestore(); + // Shut down Firestore. + void TerminateFirestore(); + + // Create a custom-named collection to work with for this test. + firebase::firestore::CollectionReference GetTestCollection(); + + // Add the DocumentReference to the cleanup list. At TearDown, all these + // documents will be deleted. + firebase::firestore::DocumentReference Cleanup( + const firebase::firestore::DocumentReference& doc) { + if (find(cleanup_documents_.begin(), cleanup_documents_.end(), doc) == + cleanup_documents_.end()) { + cleanup_documents_.push_back(doc); + } + // Pass through the DocumentReference to simplify test code. + return doc; + } + + firebase::firestore::DocumentReference Doc(const char* suffix = ""); + + static firebase::App* shared_app_; + static firebase::auth::Auth* shared_auth_; + + bool initialized_ = false; + firebase::firestore::Firestore* firestore_ = nullptr; + + std::string collection_name_; + std::vector cleanup_documents_; +}; + +// Initialization flow looks like this: +// - Once, before any tests run: +// - SetUpTestSuite: Initialize App and Auth. Sign in. +// - For each test: +// - SetUp: Initialize Firestore. +// - Run the test. +// - TearDown: Shut down Firestore. +// - Once, after all tests are finished: +// - TearDownTestSuite: Sign out. Shut down Auth and App. + +firebase::App* FirebaseFirestoreBasicTest::shared_app_; +firebase::auth::Auth* FirebaseFirestoreBasicTest::shared_auth_; + +void FirebaseFirestoreBasicTest::SetUpTestSuite() { + InitializeAppAndAuth(); +} + +void FirebaseFirestoreBasicTest::InitializeAppAndAuth() { + LogDebug("Initialize Firebase App."); + + FindFirebaseConfig(FIREBASE_CONFIG_STRING); + +#if defined(__ANDROID__) + shared_app_ = ::firebase::App::Create(app_framework::GetJniEnv(), + app_framework::GetActivity()); +#else + shared_app_ = ::firebase::App::Create(); +#endif // defined(__ANDROID__) + + ASSERT_NE(shared_app_, nullptr); + + LogDebug("Initializing Auth."); + + // Initialize Firebase Auth. + ::firebase::ModuleInitializer initializer; + initializer.Initialize( + shared_app_, &shared_auth_, [](::firebase::App* app, void* target) { + LogDebug("Attempting to initialize Firebase Auth."); + ::firebase::InitResult result; + *reinterpret_cast(target) = + ::firebase::auth::Auth::GetAuth(app, &result); + return result; + }); + + WaitForCompletion(initializer.InitializeLastResult(), "InitializeAuth"); + + ASSERT_EQ(initializer.InitializeLastResult().error(), 0) + << initializer.InitializeLastResult().error_message(); + + LogDebug("Successfully initialized Auth."); + + ASSERT_NE(shared_auth_, nullptr); + + // Sign in anonymously. + SignIn(); +} + +void FirebaseFirestoreBasicTest::TearDownTestSuite() { + TerminateAppAndAuth(); +} + +void FirebaseFirestoreBasicTest::TerminateAppAndAuth() { + if (shared_auth_) { + LogDebug("Signing out."); + SignOut(); + LogDebug("Shutdown Auth."); + delete shared_auth_; + shared_auth_ = nullptr; + } + if (shared_app_) { + LogDebug("Shutdown App."); + delete shared_app_; + shared_app_ = nullptr; + } +} + +FirebaseFirestoreBasicTest::FirebaseFirestoreBasicTest() { + FindFirebaseConfig(FIREBASE_CONFIG_STRING); +} + +FirebaseFirestoreBasicTest::~FirebaseFirestoreBasicTest() { + // Must be cleaned up on exit. + assert(firestore_ == nullptr); +} + +void FirebaseFirestoreBasicTest::SetUp() { + FirebaseTest::SetUp(); + InitializeFirestore(); +} + +void FirebaseFirestoreBasicTest::TearDown() { + // Delete the shared path, if there is one. + if (initialized_) { + if (!cleanup_documents_.empty() && firestore_ && shared_app_) { + LogDebug("Cleaning up documents."); + std::vector> cleanups; + cleanups.reserve(cleanup_documents_.size()); + for (int i = 0; i < cleanup_documents_.size(); ++i) { + cleanups.push_back(cleanup_documents_[i].Delete()); + } + for (int i = 0; i < cleanups.size(); ++i) { + WaitForCompletion(cleanups[i], "FirebaseFirestoreBasicTest::TearDown"); + } + cleanup_documents_.clear(); + } + } + TerminateFirestore(); + FirebaseTest::TearDown(); +} + +void FirebaseFirestoreBasicTest::InitializeFirestore() { + LogDebug("Initializing Firebase Firestore."); + + ::firebase::ModuleInitializer initializer; + initializer.Initialize( + shared_app_, &firestore_, [](::firebase::App* app, void* target) { + LogDebug("Attempting to initialize Firebase Firestore."); + ::firebase::InitResult result; + *reinterpret_cast(target) = + firebase::firestore::Firestore::GetInstance(app, &result); + return result; + }); + + WaitForCompletion(initializer.InitializeLastResult(), "InitializeFirestore"); + + ASSERT_EQ(initializer.InitializeLastResult().error(), 0) + << initializer.InitializeLastResult().error_message(); + + LogDebug("Successfully initialized Firebase Firestore."); + + initialized_ = true; +} + +void FirebaseFirestoreBasicTest::TerminateFirestore() { + if (!initialized_) return; + + if (firestore_) { + LogDebug("Shutdown the Firestore library."); + delete firestore_; + firestore_ = nullptr; + } + initialized_ = false; + + ProcessEvents(100); +} + +void FirebaseFirestoreBasicTest::SignIn() { + if (shared_auth_->current_user() != nullptr) { + // Already signed in. + return; + } + LogDebug("Signing in."); + firebase::Future sign_in_future = + shared_auth_->SignInAnonymously(); + WaitForCompletion(sign_in_future, "SignInAnonymously"); + if (sign_in_future.error() != 0) { + FAIL() << "Ensure your application has the Anonymous sign-in provider " + "enabled in Firebase Console."; + } + ProcessEvents(100); +} + +void FirebaseFirestoreBasicTest::SignOut() { + if (shared_auth_ == nullptr) { + // Auth is not set up. + return; + } + if (shared_auth_->current_user() == nullptr) { + // Already signed out. + return; + } + + if (shared_auth_->current_user()->is_anonymous()) { + // If signed in anonymously, delete the anonymous user. + WaitForCompletion(shared_auth_->current_user()->Delete(), "DeleteAnonymousUser"); + } + else { + // If not signed in anonymously (e.g. if the tests were modified to sign in + // as an actual user), just sign out normally. + shared_auth_->SignOut(); + + // Wait for the sign-out to finish. + while (shared_auth_->current_user() != nullptr) { + if (ProcessEvents(100)) break; + } + } + EXPECT_EQ(shared_auth_->current_user(), nullptr); +} + + +firebase::firestore::CollectionReference +FirebaseFirestoreBasicTest::GetTestCollection() { + if (collection_name_.empty()) { + // Generate a collection for the test data based on the time in + // milliseconds. + int64_t time_in_microseconds = GetCurrentTimeInMicroseconds(); + + char buffer[21] = {0}; + snprintf(buffer, sizeof(buffer), "test%lld", + static_cast(time_in_microseconds)); // NOLINT + collection_name_ = buffer; + } + return firestore_->Collection(collection_name_.c_str()); +} + +firebase::firestore::DocumentReference FirebaseFirestoreBasicTest::Doc( + const char* suffix) { + std::string path = + std::string( + ::testing::UnitTest::GetInstance()->current_test_info()->name()) + + suffix; + return Cleanup(GetTestCollection().Document(path)); +} + +// Test cases below. + +TEST_F(FirebaseFirestoreBasicTest, TestInitializeAndTerminate) { + // Already tested via SetUp() and TearDown(). +} + +TEST_F(FirebaseFirestoreBasicTest, TestSignIn) { + EXPECT_NE(shared_auth_->current_user(), nullptr); +} + +TEST_F(FirebaseFirestoreBasicTest, TestAppAndSettings) { + EXPECT_EQ(firestore_->app(), shared_app_); + firebase::firestore::Settings settings = firestore_->settings(); + firestore_->set_settings(settings); + // No comparison operator in settings, so just assume it worked if we didn't + // crash. +} + +TEST_F(FirebaseFirestoreBasicTest, TestNonWrappedTypes) { + const firebase::Timestamp timestamp{1, 2}; + EXPECT_EQ(timestamp.seconds(), 1); + EXPECT_EQ(timestamp.nanoseconds(), 2); + const firebase::firestore::SnapshotMetadata metadata{ + /*has_pending_writes*/ false, /*is_from_cache*/ true}; + EXPECT_FALSE(metadata.has_pending_writes()); + EXPECT_TRUE(metadata.is_from_cache()); + const firebase::firestore::GeoPoint point{1.23, 4.56}; + EXPECT_EQ(point.latitude(), 1.23); + EXPECT_EQ(point.longitude(), 4.56); +} + +TEST_F(FirebaseFirestoreBasicTest, TestCollection) { + firebase::firestore::CollectionReference collection = + firestore_->Collection("foo"); + EXPECT_EQ(collection.firestore(), firestore_); + EXPECT_EQ(collection.id(), "foo"); + EXPECT_EQ(collection.Document("bar").path(), "foo/bar"); +} + +TEST_F(FirebaseFirestoreBasicTest, TestDocument) { + firebase::firestore::DocumentReference document = + firestore_->Document("foo/bar"); + EXPECT_EQ(document.firestore(), firestore_); + EXPECT_EQ(document.path(), "foo/bar"); +} + +TEST_F(FirebaseFirestoreBasicTest, TestSetGet) { + SignIn(); + + firebase::firestore::DocumentReference document = Doc(); + + WaitForCompletion( + document.Set(firebase::firestore::MapFieldValue{ + {"str", firebase::firestore::FieldValue::String("foo")}, + {"int", firebase::firestore::FieldValue::Integer(123)}}), + "document.Set"); + firebase::Future future = + document.Get(); + WaitForCompletion(future, "document.Get"); + EXPECT_NE(future.result(), nullptr); + EXPECT_THAT(future.result()->GetData(), + UnorderedElementsAre( + Pair("str", firebase::firestore::FieldValue::String("foo")), + Pair("int", firebase::firestore::FieldValue::Integer(123)))); +} + +TEST_F(FirebaseFirestoreBasicTest, TestSetUpdateGet) { + SignIn(); + + firebase::firestore::DocumentReference document = Doc(); + + WaitForCompletion( + document.Set(firebase::firestore::MapFieldValue{ + {"str", firebase::firestore::FieldValue::String("foo")}, + {"int", firebase::firestore::FieldValue::Integer(123)}}), + "document.Set"); + WaitForCompletion( + document.Update(firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(321)}}), + "document.Update"); + firebase::Future future = + document.Get(); + WaitForCompletion(future, "document.Get"); + EXPECT_NE(future.result(), nullptr); + EXPECT_THAT(future.result()->GetData(), + UnorderedElementsAre( + Pair("str", firebase::firestore::FieldValue::String("foo")), + Pair("int", firebase::firestore::FieldValue::Integer(321)))); +} + +TEST_F(FirebaseFirestoreBasicTest, TestSetDelete) { + SignIn(); + + firebase::firestore::DocumentReference document = Doc(); + + WaitForCompletion( + document.Set(firebase::firestore::MapFieldValue{ + {"str", firebase::firestore::FieldValue::String("bar")}, + {"int", firebase::firestore::FieldValue::Integer(456)}}), + "document.Set"); + + WaitForCompletion(document.Delete(), "document.Delete"); + firebase::Future future = + document.Get(); + WaitForCompletion(future, "document.Get"); + EXPECT_NE(future.result(), nullptr); + EXPECT_FALSE(future.result()->exists()); + + // TODO: Test error cases (deleting invalid path, etc.) +} + +TEST_F(FirebaseFirestoreBasicTest, TestDocumentListener) { + SKIP_TEST_IF_USING_STLPORT; // STLPort uses EventListener rather than + // std::function. +#if !defined(STLPORT) + SignIn(); + + firebase::firestore::DocumentReference document = Doc(); + + WaitForCompletion( + document.Set(firebase::firestore::MapFieldValue{ + {"val", firebase::firestore::FieldValue::String("start")}}), + "document.Set 0"); + + std::vector document_snapshots; + firebase::firestore::ListenerRegistration registration = + document.AddSnapshotListener( + [&](const firebase::firestore::DocumentSnapshot& result, + firebase::firestore::Error error_code, + const std::string& error_message) { + EXPECT_EQ(error_code, firebase::firestore::kErrorOk); + EXPECT_EQ(error_message, ""); + document_snapshots.push_back(result.GetData()); + }); + + WaitForCompletion( + document.Set(firebase::firestore::MapFieldValue{ + {"val", firebase::firestore::FieldValue::String("update")}}), + "document.Set 1"); + registration.Remove(); + WaitForCompletion( + document.Set(firebase::firestore::MapFieldValue{ + {"val", firebase::firestore::FieldValue::String("final")}}), + "document.Set 2"); + EXPECT_THAT( + document_snapshots, + ElementsAre( + firebase::firestore::MapFieldValue{ + {"val", firebase::firestore::FieldValue::String("start")}}, + firebase::firestore::MapFieldValue{ + {"val", firebase::firestore::FieldValue::String("update")}})); +#endif // !defined(STLPORT) +} + +TEST_F(FirebaseFirestoreBasicTest, TestBatchWrite) { + SignIn(); + + firebase::firestore::DocumentReference document1 = Doc("1"); + firebase::firestore::DocumentReference document2 = Doc("2"); + + firebase::firestore::WriteBatch batch = firestore_->batch(); + batch.Set(document1, + firebase::firestore::MapFieldValue{ + {"str", firebase::firestore::FieldValue::String("first")}}); + batch.Set(document2, + firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(2222)}}); + WaitForCompletion(batch.Commit(), "batch.Commit"); + + // Confirm the updated docs are correct. + auto future1 = Doc("1").Get(); + WaitForCompletion(future1, "document.Get 1"); + EXPECT_THAT(future1.result()->GetData(), + ElementsAre(Pair( + "str", firebase::firestore::FieldValue::String("first")))); + + auto future2 = Doc("2").Get(); + WaitForCompletion(future2, "document.Get 2"); + EXPECT_THAT( + future2.result()->GetData(), + ElementsAre(Pair("int", firebase::firestore::FieldValue::Integer(2222)))); +} + +TEST_F(FirebaseFirestoreBasicTest, TestRunTransaction) { + SignIn(); + + WaitForCompletion( + Doc("1").Set(firebase::firestore::MapFieldValue{ + {"str", firebase::firestore::FieldValue::String("foo")}}), + "document.Set 1"); + WaitForCompletion( + Doc("2").Set(firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(123)}}), + "document.Set 2"); + WaitForCompletion( + Doc("3").Set(firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(678)}}), + "document.Set 3"); + // Make sure there's no doc 4. + WaitForCompletion(Doc("4").Delete(), "document.Delete 4"); + + auto collection = GetTestCollection(); + + auto transaction_future = firestore_->RunTransaction( + [collection, this](firebase::firestore::Transaction& transaction, + std::string&) -> firebase::firestore::Error { + // Set a default error to ensure that the error is filled in by Get(). + firebase::firestore::Error geterr = + static_cast(-1); + std::string getmsg = "[[uninitialized message]]"; + int64_t prev_int = transaction.Get(Doc("2"), &geterr, &getmsg) + .Get("int") + .integer_value(); + EXPECT_EQ(geterr, firebase::firestore::kErrorOk) << getmsg; + + // Update 1, increment 2, delete 3, add 4. + transaction.Update( + Doc("1"), + firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(456)}}); + LogDebug("Previous value: %lld", prev_int); + transaction.Update(Doc("2"), + firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer( + prev_int + 100)}}); + transaction.Delete(Doc("3")); + transaction.Set( + Doc("4"), + firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(789)}}); + return firebase::firestore::kErrorOk; + }); + + WaitForCompletion(transaction_future, "firestore.RunTransaction"); + + (void)Doc("4"); // Add new doc to cleanup list + + // Confirm the updated docs are correct. + // First doc had an additional field added. + auto future1 = Doc("1").Get(); + WaitForCompletion(future1, "document.Get 1"); + EXPECT_THAT(future1.result()->GetData(), + UnorderedElementsAre( + Pair("str", firebase::firestore::FieldValue::String("foo")), + Pair("int", firebase::firestore::FieldValue::Integer(456)))); + + // Second doc was incremented by 100. + auto future2 = Doc("2").Get(); + WaitForCompletion(future2, "document.Get 2"); + EXPECT_THAT( + future2.result()->GetData(), + ElementsAre(Pair("int", firebase::firestore::FieldValue::Integer(223)))); + + // Third doc was deleted. + auto future3 = Doc("3").Get(); + WaitForCompletion(future3, "document.Get 3"); + EXPECT_FALSE(future3.result()->exists()); + + // Fourth doc was newly added. + auto future4 = Doc("4").Get(); + WaitForCompletion(future4, "document.Get 4"); + EXPECT_THAT( + future4.result()->GetData(), + ElementsAre(Pair("int", firebase::firestore::FieldValue::Integer(789)))); +} + +// TODO: Add test for failing transaction. + +TEST_F(FirebaseFirestoreBasicTest, TestQuery) { + SignIn(); + + firebase::firestore::CollectionReference collection = GetTestCollection(); + // { "int" : 99, "int" : 100, "int" : 101, "int": 102, "str": "hello" } + // Query for int > 100 should return only the 101 and 102 entries. + WaitForCompletion(Doc("1").Set(firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(99)}}), + "document.Set 1"); + WaitForCompletion( + Doc("2").Set(firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(100)}}), + "document.Set 2"); + WaitForCompletion( + Doc("3").Set(firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(101)}}), + "document.Set 3"); + WaitForCompletion( + Doc("4").Set(firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(102)}}), + "document.Set 4"); + WaitForCompletion( + Doc("5").Set(firebase::firestore::MapFieldValue{ + {"str", firebase::firestore::FieldValue::String("hello")}}), + "document.Set 5"); + + firebase::firestore::Query query = collection.WhereGreaterThan( + "int", firebase::firestore::FieldValue::Integer(100)); + auto query_future = query.Get(); + WaitForCompletion(query_future, "query.Get"); + EXPECT_NE(query_future.result(), nullptr); + auto DocumentSnapshot_GetData = + [](const firebase::firestore::DocumentSnapshot& ds) { + return ds.GetData(); + }; + EXPECT_THAT( + query_future.result()->documents(), + UnorderedElementsAre( + ResultOf(DocumentSnapshot_GetData, + ElementsAre(Pair( + "int", firebase::firestore::FieldValue::Integer(102)))), + ResultOf( + DocumentSnapshot_GetData, + ElementsAre(Pair( + "int", firebase::firestore::FieldValue::Integer(101)))))); +} + +TEST_F(FirebaseFirestoreBasicTest, + TestInvalidatingReferencesWhenDeletingFirestore) { + delete firestore_; + firestore_ = nullptr; + // TODO: Ensure existing Firestore objects are invalidated. +} + +TEST_F(FirebaseFirestoreBasicTest, TestInvalidatingReferencesWhenDeletingApp) { + delete shared_app_; + shared_app_ = nullptr; + // TODO: Ensure existing Firestore objects are invalidated. + + // Fully shut down App and Auth so they can be reinitialized. + TerminateAppAndAuth(); + // Reinitialize App and Auth. + InitializeAppAndAuth(); +} + +// TODO: Add test for Auth signout while connected. + +// TODO: Add additional comprehensive tests as needed. + +} // namespace firebase_testapp_automated diff --git a/firestore/src/tests/jni/declaration_test.cc b/firestore/integration_test_internal/src/jni/declaration_test.cc similarity index 97% rename from firestore/src/tests/jni/declaration_test.cc rename to firestore/integration_test_internal/src/jni/declaration_test.cc index 79f2af1dfd..d3f538b7b4 100644 --- a/firestore/src/tests/jni/declaration_test.cc +++ b/firestore/integration_test_internal/src/jni/declaration_test.cc @@ -7,8 +7,8 @@ #include "firestore/src/jni/iterator.h" #include "firestore/src/jni/loader.h" #include "firestore/src/jni/set.h" -#include "firestore/src/tests/firestore_integration_test.h" -#include "testing/base/public/gmock.h" +#include "firestore_integration_test.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" namespace firebase { diff --git a/firestore/src/tests/jni/env_test.cc b/firestore/integration_test_internal/src/jni/env_test.cc similarity index 99% rename from firestore/src/tests/jni/env_test.cc rename to firestore/integration_test_internal/src/jni/env_test.cc index 8a9da0626d..382c95fa2b 100644 --- a/firestore/src/tests/jni/env_test.cc +++ b/firestore/integration_test_internal/src/jni/env_test.cc @@ -5,7 +5,7 @@ #include "firestore/src/android/exception_android.h" #include "firestore/src/common/macros.h" #include "firestore/src/jni/array.h" -#include "firestore/src/tests/firestore_integration_test.h" +#include "firestore_integration_test.h" #include "gtest/gtest.h" #include "Firestore/core/src/util/firestore_exceptions.h" diff --git a/firestore/src/tests/jni/object_test.cc b/firestore/integration_test_internal/src/jni/object_test.cc similarity index 92% rename from firestore/src/tests/jni/object_test.cc rename to firestore/integration_test_internal/src/jni/object_test.cc index bb54371d47..5b9716dfa9 100644 --- a/firestore/src/tests/jni/object_test.cc +++ b/firestore/integration_test_internal/src/jni/object_test.cc @@ -3,7 +3,7 @@ #include #include "firestore/src/jni/env.h" -#include "firestore/src/tests/firestore_integration_test.h" +#include "firestore_integration_test.h" #include "gtest/gtest.h" namespace firebase { diff --git a/firestore/src/tests/jni/ownership_test.cc b/firestore/integration_test_internal/src/jni/ownership_test.cc similarity index 98% rename from firestore/src/tests/jni/ownership_test.cc rename to firestore/integration_test_internal/src/jni/ownership_test.cc index 14797ba91a..170832bf67 100644 --- a/firestore/src/tests/jni/ownership_test.cc +++ b/firestore/integration_test_internal/src/jni/ownership_test.cc @@ -6,8 +6,8 @@ #include "firestore/src/jni/jni.h" #include "firestore/src/jni/object.h" #include "firestore/src/jni/traits.h" -#include "firestore/src/tests/firestore_integration_test.h" -#include "testing/base/public/gmock.h" +#include "firestore_integration_test.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" namespace firebase { diff --git a/firestore/src/tests/jni/task_test.cc b/firestore/integration_test_internal/src/jni/task_test.cc similarity index 94% rename from firestore/src/tests/jni/task_test.cc rename to firestore/integration_test_internal/src/jni/task_test.cc index ee1b4ac190..c9f3937ed9 100644 --- a/firestore/src/tests/jni/task_test.cc +++ b/firestore/integration_test_internal/src/jni/task_test.cc @@ -5,10 +5,10 @@ #include "firestore/src/jni/ownership.h" #include "firestore/src/jni/string.h" #include "firestore/src/jni/throwable.h" -#include "firestore/src/tests/android/cancellation_token_source.h" -#include "firestore/src/tests/android/firestore_integration_test_android.h" -#include "firestore/src/tests/android/task_completion_source.h" -#include "testing/base/public/gmock.h" +#include "android/cancellation_token_source.h" +#include "android/firestore_integration_test_android.h" +#include "android/task_completion_source.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" namespace firebase { diff --git a/firestore/src/tests/jni/traits_test.cc b/firestore/integration_test_internal/src/jni/traits_test.cc similarity index 98% rename from firestore/src/tests/jni/traits_test.cc rename to firestore/integration_test_internal/src/jni/traits_test.cc index 6c3dc0108d..ecfac82468 100644 --- a/firestore/src/tests/jni/traits_test.cc +++ b/firestore/integration_test_internal/src/jni/traits_test.cc @@ -8,7 +8,7 @@ #include "firestore/src/jni/object.h" #include "firestore/src/jni/ownership.h" #include "firestore/src/jni/string.h" -#include "firestore/src/tests/firestore_integration_test.h" +#include "firestore_integration_test.h" #include "gtest/gtest.h" namespace firebase { diff --git a/firestore/integration_test_internal/src/query_test.cc b/firestore/integration_test_internal/src/query_test.cc index 23d0cad112..5f95ceef25 100644 --- a/firestore/integration_test_internal/src/query_test.cc +++ b/firestore/integration_test_internal/src/query_test.cc @@ -24,6 +24,7 @@ #include "gtest/gtest.h" #include "firebase/firestore/firestore_errors.h" #include "Firestore/core/src/util/firestore_exceptions.h" +#include "firebase_test_framework.h" // These test cases are in sync with native iOS client SDK test // Firestore/Example/Tests/Integration/API/FIRQueryTests.mm @@ -37,6 +38,8 @@ namespace firebase { namespace firestore { namespace { +using QueryTest = FirestoreIntegrationTest; + using ::testing::ElementsAreArray; using ::testing::IsEmpty; @@ -57,7 +60,7 @@ std::vector AllDocsExcept( #if !defined(FIRESTORE_STUB_BUILD) -TEST_F(FirestoreIntegrationTest, TestLimitQueries) { +TEST_F(QueryTest, TestLimitQueries) { CollectionReference collection = Collection({{"a", {{"k", FieldValue::String("a")}}}, {"b", {{"k", FieldValue::String("b")}}}, @@ -68,7 +71,7 @@ TEST_F(FirestoreIntegrationTest, TestLimitQueries) { QuerySnapshotToValues(snapshot)); } -TEST_F(FirestoreIntegrationTest, TestLimitQueriesUsingDescendingSortOrder) { +TEST_F(QueryTest, TestLimitQueriesUsingDescendingSortOrder) { CollectionReference collection = Collection( {{"a", {{"k", FieldValue::String("a")}, {"sort", FieldValue::Integer(0)}}}, @@ -88,7 +91,7 @@ TEST_F(FirestoreIntegrationTest, TestLimitQueriesUsingDescendingSortOrder) { } #if defined(__ANDROID__) && FIRESTORE_HAVE_EXCEPTIONS -TEST_F(FirestoreIntegrationTest, TestLimitToLastMustAlsoHaveExplicitOrderBy) { +TEST_F(QueryTest, TestLimitToLastMustAlsoHaveExplicitOrderBy) { CollectionReference collection = Collection(); EXPECT_THROW(Await(collection.LimitToLast(2).Get()), std::logic_error); @@ -101,7 +104,7 @@ TEST_F(FirestoreIntegrationTest, TestLimitToLastMustAlsoHaveExplicitOrderBy) { // queries are sent to the backend with a modified OrderBy() clause, they can // map to the same target representation as Limit() query, even if both queries // appear separate to the user. -TEST_F(FirestoreIntegrationTest, +TEST_F(QueryTest, TestListenUnlistenRelistenSequenceOfMirrorQueries) { CollectionReference collection = Collection( {{"a", @@ -200,7 +203,7 @@ TEST_F(FirestoreIntegrationTest, {"sort", FieldValue::Integer(-2)}})); } -TEST_F(FirestoreIntegrationTest, +TEST_F(QueryTest, TestKeyOrderIsDescendingForDescendingInequality) { CollectionReference collection = Collection({{"a", {{"foo", FieldValue::Integer(42)}}}, @@ -217,7 +220,7 @@ TEST_F(FirestoreIntegrationTest, QuerySnapshotToIds(snapshot)); } -TEST_F(FirestoreIntegrationTest, TestUnaryFilterQueries) { +TEST_F(QueryTest, TestUnaryFilterQueries) { CollectionReference collection = Collection( {{"a", {{"null", FieldValue::Null()}, {"nan", FieldValue::Double(NAN)}}}, {"b", {{"null", FieldValue::Null()}, {"nan", FieldValue::Integer(0)}}}, @@ -232,7 +235,7 @@ TEST_F(FirestoreIntegrationTest, TestUnaryFilterQueries) { QuerySnapshotToValues(snapshot)); } -TEST_F(FirestoreIntegrationTest, TestQueryWithFieldPaths) { +TEST_F(QueryTest, TestQueryWithFieldPaths) { CollectionReference collection = Collection({{"a", {{"a", FieldValue::Integer(1)}}}, {"b", {{"a", FieldValue::Integer(2)}}}, @@ -243,7 +246,7 @@ TEST_F(FirestoreIntegrationTest, TestQueryWithFieldPaths) { EXPECT_EQ(std::vector({"b", "a"}), QuerySnapshotToIds(snapshot)); } -TEST_F(FirestoreIntegrationTest, TestFilterOnInfinity) { +TEST_F(QueryTest, TestFilterOnInfinity) { CollectionReference collection = Collection({{"a", {{"inf", FieldValue::Double(INFINITY)}}}, {"b", {{"inf", FieldValue::Double(-INFINITY)}}}}); @@ -254,7 +257,7 @@ TEST_F(FirestoreIntegrationTest, TestFilterOnInfinity) { QuerySnapshotToValues(snapshot)); } -TEST_F(FirestoreIntegrationTest, TestWillNotGetMetadataOnlyUpdates) { +TEST_F(QueryTest, TestWillNotGetMetadataOnlyUpdates) { CollectionReference collection = Collection({{"a", {{"v", FieldValue::String("a")}}}, {"b", {{"v", FieldValue::String("b")}}}}); @@ -276,7 +279,7 @@ TEST_F(FirestoreIntegrationTest, TestWillNotGetMetadataOnlyUpdates) { registration.Remove(); } -TEST_F(FirestoreIntegrationTest, +TEST_F(QueryTest, TestCanListenForTheSameQueryWithDifferentOptions) { CollectionReference collection = Collection(); WriteDocuments(collection, {{"a", {{"v", FieldValue::String("a")}}}, @@ -352,7 +355,7 @@ TEST_F(FirestoreIntegrationTest, registration_full.Remove(); } -TEST_F(FirestoreIntegrationTest, TestCanListenForQueryMetadataChanges) { +TEST_F(QueryTest, TestCanListenForQueryMetadataChanges) { CollectionReference collection = Collection({{"1", {{"sort", FieldValue::Double(1.0)}, @@ -402,7 +405,7 @@ TEST_F(FirestoreIntegrationTest, TestCanListenForQueryMetadataChanges) { registration2.Remove(); } -TEST_F(FirestoreIntegrationTest, TestCanExplicitlySortByDocumentId) { +TEST_F(QueryTest, TestCanExplicitlySortByDocumentId) { CollectionReference collection = Collection({{"a", {{"key", FieldValue::String("a")}}}, {"b", {{"key", FieldValue::String("b")}}}, @@ -415,7 +418,7 @@ TEST_F(FirestoreIntegrationTest, TestCanExplicitlySortByDocumentId) { QuerySnapshotToIds(snapshot)); } -TEST_F(FirestoreIntegrationTest, TestCanQueryByDocumentId) { +TEST_F(QueryTest, TestCanQueryByDocumentId) { CollectionReference collection = Collection({{"aa", {{"key", FieldValue::String("aa")}}}, {"ab", {{"key", FieldValue::String("ab")}}}, @@ -437,7 +440,7 @@ TEST_F(FirestoreIntegrationTest, TestCanQueryByDocumentId) { QuerySnapshotToIds(snapshot2)); } -TEST_F(FirestoreIntegrationTest, TestCanQueryByDocumentIdUsingRefs) { +TEST_F(QueryTest, TestCanQueryByDocumentIdUsingRefs) { CollectionReference collection = Collection({{"aa", {{"key", FieldValue::String("aa")}}}, {"ab", {{"key", FieldValue::String("ab")}}}, @@ -462,7 +465,7 @@ TEST_F(FirestoreIntegrationTest, TestCanQueryByDocumentIdUsingRefs) { QuerySnapshotToIds(snapshot2)); } -TEST_F(FirestoreIntegrationTest, TestCanQueryWithAndWithoutDocumentKey) { +TEST_F(QueryTest, TestCanQueryWithAndWithoutDocumentKey) { CollectionReference collection = Collection(); Await(collection.Add({})); QuerySnapshot snapshot1 = ReadDocuments(collection.OrderBy( @@ -472,7 +475,7 @@ TEST_F(FirestoreIntegrationTest, TestCanQueryWithAndWithoutDocumentKey) { EXPECT_EQ(QuerySnapshotToValues(snapshot1), QuerySnapshotToValues(snapshot2)); } -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotEqualFilters) { +TEST_F(QueryTest, TestQueriesCanUseNotEqualFilters) { // These documents are ordered by value in "zip" since the NotEqual filter is // an inequality, which results in documents being sorted by value. std::map docs = { @@ -501,7 +504,7 @@ TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotEqualFilters) { ElementsAreArray(AllDocsExcept(docs, {"c", "i", "j"}))); } -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotEqualFiltersWithObject) { +TEST_F(QueryTest, TestQueriesCanUseNotEqualFiltersWithObject) { // These documents are ordered by value in "zip" since the NotEqual filter is // an inequality, which results in documents being sorted by value. std::map docs = { @@ -529,7 +532,7 @@ TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotEqualFiltersWithObject) { ElementsAreArray(AllDocsExcept(docs, {"h", "i", "j"}))); } -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotEqualFiltersWithNull) { +TEST_F(QueryTest, TestQueriesCanUseNotEqualFiltersWithNull) { // These documents are ordered by value in "zip" since the NotEqual filter is // an inequality, which results in documents being sorted by value. std::map docs = { @@ -558,7 +561,7 @@ TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotEqualFiltersWithNull) { ElementsAreArray(AllDocsExcept(docs, {"i", "j"}))); } -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotEqualFiltersWithNan) { +TEST_F(QueryTest, TestQueriesCanUseNotEqualFiltersWithNan) { // These documents are ordered by value in "zip" since the NotEqual filter is // an inequality, which results in documents being sorted by value. std::map docs = { @@ -586,7 +589,7 @@ TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotEqualFiltersWithNan) { ElementsAreArray(AllDocsExcept(docs, {"a", "i", "j"}))); } -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotEqualFiltersWithDocIds) { +TEST_F(QueryTest, TestQueriesCanUseNotEqualFiltersWithDocIds) { MapFieldValue doc_a = {{"key", FieldValue::String("aa")}}; MapFieldValue doc_b = {{"key", FieldValue::String("ab")}}; MapFieldValue doc_c = {{"key", FieldValue::String("ba")}}; @@ -601,7 +604,7 @@ TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotEqualFiltersWithDocIds) { QuerySnapshotToValues(snapshot)); } -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseArrayContainsFilters) { +TEST_F(QueryTest, TestQueriesCanUseArrayContainsFilters) { CollectionReference collection = Collection( {{"a", {{"array", FieldValue::Array({FieldValue::Integer(42)})}}}, {"b", @@ -634,7 +637,7 @@ TEST_F(FirestoreIntegrationTest, TestQueriesCanUseArrayContainsFilters) { // so there isn't much of anything else interesting to test. } -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseInFilters) { +TEST_F(QueryTest, TestQueriesCanUseInFilters) { CollectionReference collection = Collection( {{"a", {{"zip", FieldValue::Integer(98101)}}}, {"b", {{"zip", FieldValue::Integer(98102)}}}, @@ -670,7 +673,7 @@ TEST_F(FirestoreIntegrationTest, TestQueriesCanUseInFilters) { QuerySnapshotToValues(snapshot)); } -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseInFiltersWithDocIds) { +TEST_F(QueryTest, TestQueriesCanUseInFiltersWithDocIds) { CollectionReference collection = Collection({{"aa", {{"key", FieldValue::String("aa")}}}, {"ab", {{"key", FieldValue::String("ab")}}}, @@ -685,7 +688,7 @@ TEST_F(FirestoreIntegrationTest, TestQueriesCanUseInFiltersWithDocIds) { QuerySnapshotToValues(snapshot)); } -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotInFilters) { +TEST_F(QueryTest, TestQueriesCanUseNotInFilters) { // These documents are ordered by value in "zip" since the NotEqual filter is // an inequality, which results in documents being sorted by value. std::map docs = { @@ -716,7 +719,7 @@ TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotInFilters) { ElementsAreArray(AllDocsExcept(docs, {"c", "d", "f", "i", "j"}))); } -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotInFiltersWithObject) { +TEST_F(QueryTest, TestQueriesCanUseNotInFiltersWithObject) { // These documents are ordered by value in "zip" since the NotEqual filter is // an inequality, which results in documents being sorted by value. std::map docs = { @@ -744,7 +747,7 @@ TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotInFiltersWithObject) { ElementsAreArray(AllDocsExcept(docs, {"h", "i", "j"}))); } -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotInFiltersWithNull) { +TEST_F(QueryTest, TestQueriesCanUseNotInFiltersWithNull) { // These documents are ordered by value in "zip" since the NotEqual filter is // an inequality, which results in documents being sorted by value. std::map docs = { @@ -772,7 +775,7 @@ TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotInFiltersWithNull) { EXPECT_THAT(QuerySnapshotToValues(snapshot), IsEmpty()); } -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotInFiltersWithNan) { +TEST_F(QueryTest, TestQueriesCanUseNotInFiltersWithNan) { // These documents are ordered by value in "zip" since the NotEqual filter is // an inequality, which results in documents being sorted by value. std::map docs = { @@ -801,7 +804,7 @@ TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotInFiltersWithNan) { ElementsAreArray(AllDocsExcept(docs, {"a", "i", "j"}))); } -TEST_F(FirestoreIntegrationTest, +TEST_F(QueryTest, TestQueriesCanUseNotInFiltersWithNanAndNumber) { // These documents are ordered by value in "zip" since the NotEqual filter is // an inequality, which results in documents being sorted by value. @@ -830,7 +833,7 @@ TEST_F(FirestoreIntegrationTest, ElementsAreArray(AllDocsExcept(docs, {"a", "c", "i", "j"}))); } -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotInFiltersWithDocIds) { +TEST_F(QueryTest, TestQueriesCanUseNotInFiltersWithDocIds) { MapFieldValue doc_a = {{"key", FieldValue::String("aa")}}; MapFieldValue doc_b = {{"key", FieldValue::String("ab")}}; MapFieldValue doc_c = {{"key", FieldValue::String("ba")}}; @@ -846,7 +849,7 @@ TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotInFiltersWithDocIds) { QuerySnapshotToValues(snapshot)); } -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseArrayContainsAnyFilters) { +TEST_F(QueryTest, TestQueriesCanUseArrayContainsAnyFilters) { CollectionReference collection = Collection( {{"a", {{"array", FieldValue::Array({FieldValue::Integer(42)})}}}, {"b", @@ -890,7 +893,7 @@ TEST_F(FirestoreIntegrationTest, TestQueriesCanUseArrayContainsAnyFilters) { QuerySnapshotToValues(snapshot)); } -TEST_F(FirestoreIntegrationTest, TestCollectionGroupQueries) { +TEST_F(QueryTest, TestCollectionGroupQueries) { Firestore* db = TestFirestore(); // Use .Document() to get a random collection group name to use but ensure it // starts with 'b' for predictable ordering. @@ -923,7 +926,7 @@ TEST_F(FirestoreIntegrationTest, TestCollectionGroupQueries) { QuerySnapshotToIds(query_snapshot)); } -TEST_F(FirestoreIntegrationTest, +TEST_F(QueryTest, TestCollectionGroupQueriesWithStartAtEndAtWithArbitraryDocumentIds) { Firestore* db = TestFirestore(); // Use .Document() to get a random collection group name to use but ensure it @@ -955,7 +958,7 @@ TEST_F(FirestoreIntegrationTest, QuerySnapshotToIds(query_snapshot)); } -TEST_F(FirestoreIntegrationTest, +TEST_F(QueryTest, TestCollectionGroupQueriesWithWhereFiltersOnArbitraryDocumentIds) { Firestore* db = TestFirestore(); // Use .Document() to get a random collection group name to use but ensure it @@ -1000,11 +1003,11 @@ TEST_F(FirestoreIntegrationTest, #endif // !defined(FIRESTORE_STUB_BUILD) #if defined(__ANDROID__) || defined(FIRESTORE_STUB_BUILD) -TEST(QueryTest, Construction) { +TEST(QueryTestAndroidStub, Construction) { testutil::AssertWrapperConstructionContract(); } -TEST(QueryTest, Assignment) { +TEST(QueryTestAndroidStub, Assignment) { testutil::AssertWrapperAssignmentContract(); } #endif // defined(__ANDROID__) || defined(FIRESTORE_STUB_BUILD) diff --git a/firestore/integration_test_internal/src/sanity_test.cc b/firestore/integration_test_internal/src/sanity_test.cc index 0fdc4be14b..f17a987cc9 100644 --- a/firestore/integration_test_internal/src/sanity_test.cc +++ b/firestore/integration_test_internal/src/sanity_test.cc @@ -9,6 +9,10 @@ #include #include "gtest/gtest.h" +#if defined(_WIN32) +#define __PRETTY_FUNCTION__ __FUNCSIG__ +#endif // defined(_WIN32) + class SanityTest : public testing::Test { protected: void SetUp() override { printf("==== SetUp ====\n"); } diff --git a/firestore/integration_test_internal/src/transaction_extra_test.cc b/firestore/integration_test_internal/src/transaction_extra_test.cc index de8719baf1..14512b2605 100644 --- a/firestore/integration_test_internal/src/transaction_extra_test.cc +++ b/firestore/integration_test_internal/src/transaction_extra_test.cc @@ -9,7 +9,7 @@ #elif defined(FIRESTORE_STUB_BUILD) #include "firestore/src/stub/transaction_stub.h" #endif // defined(__ANDROID__) - +#include "firebase_test_framework.h" // These test cases are in sync with native iOS client SDK test // Firestore/Example/Tests/Integration/API/FSTTransactionTests.mm // and native Android client SDK test @@ -30,6 +30,8 @@ using TransactionExtraTest = FirestoreIntegrationTest; TEST_F(TransactionExtraTest, TestRetriesWhenDocumentThatWasReadWithoutBeingWrittenChanges) { + SKIP_TEST_ON_IOS; // TODO(b/183294303): Fix this test on iOS. + DocumentReference doc1 = TestFirestore()->Collection("counter").Document(); DocumentReference doc2 = TestFirestore()->Collection("counter").Document(); WriteDocument(doc1, MapFieldValue{{"count", FieldValue::Integer(15)}}); @@ -69,6 +71,8 @@ TEST_F(TransactionExtraTest, } TEST_F(TransactionExtraTest, TestReadingADocTwiceWithDifferentVersions) { + SKIP_TEST_ON_IOS; // TODO(b/183294303): Fix this test on iOS. + int counter = 0; DocumentReference doc = TestFirestore()->Collection("counters").Document(); WriteDocument(doc, MapFieldValue{{"count", FieldValue::Double(15.0)}}); diff --git a/firestore/integration_test_internal/src/util/event_accumulator.h b/firestore/integration_test_internal/src/util/event_accumulator.h index 89ddbff856..aa56753e29 100644 --- a/firestore/integration_test_internal/src/util/event_accumulator.h +++ b/firestore/integration_test_internal/src/util/event_accumulator.h @@ -1,7 +1,7 @@ #ifndef FIREBASE_FIRESTORE_CLIENT_CPP_SRC_TESTS_UTIL_EVENT_ACCUMULATOR_H_ #define FIREBASE_FIRESTORE_CLIENT_CPP_SRC_TESTS_UTIL_EVENT_ACCUMULATOR_H_ -#include "firestore/src/tests/firestore_integration_test.h" +#include "firestore_integration_test.h" namespace firebase { namespace firestore { diff --git a/firestore/integration_test_internal/src/util/future_test_util.h b/firestore/integration_test_internal/src/util/future_test_util.h index d250a1930f..14108349c4 100644 --- a/firestore/integration_test_internal/src/util/future_test_util.h +++ b/firestore/integration_test_internal/src/util/future_test_util.h @@ -4,9 +4,8 @@ #include #include -#include "app/src/include/firebase/future.h" +#include "firebase/future.h" #include "gtest/gtest.h" -#include "gmock/gmock.h" namespace firebase { diff --git a/firestore/integration_test_internal/src/util/integration_test_util.cc b/firestore/integration_test_internal/src/util/integration_test_util.cc index 3fd4d1d227..1426e44570 100644 --- a/firestore/integration_test_internal/src/util/integration_test_util.cc +++ b/firestore/integration_test_internal/src/util/integration_test_util.cc @@ -7,6 +7,7 @@ #include "firestore/src/ios/firestore_ios.h" #include "absl/memory/memory.h" #include "Firestore/core/src/auth/empty_credentials_provider.h" +#include "app_framework.h" namespace firebase { namespace firestore { @@ -15,8 +16,12 @@ using auth::EmptyCredentialsProvider; struct TestFriend { static FirestoreInternal* CreateTestFirestoreInternal(App* app) { +#if !defined(__ANDROID__) return new FirestoreInternal(app, absl::make_unique()); +#else + return new FirestoreInternal(app); +#endif // !defined(__ANDROID__) } }; @@ -26,12 +31,21 @@ App* GetApp(const char* name) { // https://github.com/firebase/firebase-ios-sdk/blob/9a5afbffc17bb63b7bb7f51b9ea9a6a9e1c88a94/Firestore/core/test/firebase/firestore/testutil/app_testing.mm#L29 if (name == nullptr || std::string{name} == kDefaultAppName) { +#if defined(__ANDROID__) + return App::Create(app_framework::GetJniEnv(), app_framework::GetActivity()); +#else return App::Create(); +#endif // defined(__ANDROID__) } else { App* default_app = App::GetInstance(); SIMPLE_HARD_ASSERT(default_app, "Cannot create a named app before the default app"); +#if defined(__ANDROID__) + return App::Create(default_app->options(), name, + app_framework::GetJniEnv(), app_framework::GetActivity()); +#else return App::Create(default_app->options(), name); +#endif // defined(__ANDROID__) } } @@ -42,7 +56,7 @@ FirestoreInternal* CreateTestFirestoreInternal(App* app) { } void InitializeFirestore(Firestore* instance) { - // Firestore::set_log_level(LogLevel::kLogLevelDebug); + Firestore::set_log_level(LogLevel::kLogLevelDebug); } } // namespace firestore diff --git a/firestore/integration_test_internal/src/util/integration_test_util_apple.mm b/firestore/integration_test_internal/src/util/integration_test_util_apple.mm deleted file mode 100644 index 0dd161d1e6..0000000000 --- a/firestore/integration_test_internal/src/util/integration_test_util_apple.mm +++ /dev/null @@ -1,18 +0,0 @@ -#import - -#include - -#include -#include - -#include "firestore/src/include/firebase/firestore.h" - -namespace firebase { -namespace firestore { - -void InitializeFirestore(Firestore* instance) { - Firestore::set_log_level(LogLevel::kLogLevelDebug); -} - -} // namespace firestore -} // namespace firebase diff --git a/firestore/integration_test_internal/src/write_batch_test.cc b/firestore/integration_test_internal/src/write_batch_test.cc index 1b4c2b5ae9..9d5e5988a3 100644 --- a/firestore/integration_test_internal/src/write_batch_test.cc +++ b/firestore/integration_test_internal/src/write_batch_test.cc @@ -129,7 +129,7 @@ TEST_F(WriteBatchTest, TestBatchesCommitAtomicallyRaisingCorrectEvents) { EventAccumulator accumulator; accumulator.listener()->AttachTo(&collection, MetadataChanges::kInclude); QuerySnapshot initial_snapshot = accumulator.Await(); - EXPECT_EQ(0, initial_snapshot.size()); + EXPECT_TRUE(0 == initial_snapshot.size()); // Atomically write two documents. Await(TestFirestore() @@ -160,7 +160,7 @@ TEST_F(WriteBatchTest, TestBatchesFailAtomicallyRaisingCorrectEvents) { EventAccumulator accumulator; accumulator.listener()->AttachTo(&collection, MetadataChanges::kInclude); QuerySnapshot initial_snapshot = accumulator.Await(); - EXPECT_EQ(0, initial_snapshot.size()); + EXPECT_TRUE(0 == initial_snapshot.size()); // Atomically write 1 document and update a nonexistent document. Future future = @@ -170,8 +170,8 @@ TEST_F(WriteBatchTest, TestBatchesFailAtomicallyRaisingCorrectEvents) { .Update(doc_b, MapFieldValue{{"b", FieldValue::Integer(2)}}) .Commit(); Await(future); - EXPECT_EQ(FutureStatus::kFutureStatusComplete, future.status()); - EXPECT_EQ(Error::kErrorNotFound, future.error()); + EXPECT_TRUE(FutureStatus::kFutureStatusComplete == future.status()); + EXPECT_TRUE(Error::kErrorNotFound == future.error()); // Local event with the set document. QuerySnapshot local_snapshot = accumulator.Await(); @@ -183,7 +183,7 @@ TEST_F(WriteBatchTest, TestBatchesFailAtomicallyRaisingCorrectEvents) { // Server event with the set reverted QuerySnapshot server_snapshot = accumulator.Await(); EXPECT_FALSE(server_snapshot.metadata().has_pending_writes()); - EXPECT_EQ(0, server_snapshot.size()); + EXPECT_TRUE(0 == server_snapshot.size()); } TEST_F(WriteBatchTest, TestWriteTheSameServerTimestampAcrossWrites) { @@ -193,7 +193,7 @@ TEST_F(WriteBatchTest, TestWriteTheSameServerTimestampAcrossWrites) { EventAccumulator accumulator; accumulator.listener()->AttachTo(&collection, MetadataChanges::kInclude); QuerySnapshot initial_snapshot = accumulator.Await(); - EXPECT_EQ(0, initial_snapshot.size()); + EXPECT_TRUE(0 == initial_snapshot.size()); // Atomically write two documents with server timestamps. Await(TestFirestore() @@ -211,9 +211,9 @@ TEST_F(WriteBatchTest, TestWriteTheSameServerTimestampAcrossWrites) { QuerySnapshot server_snapshot = accumulator.AwaitRemoteEvent(); EXPECT_FALSE(server_snapshot.metadata().has_pending_writes()); - EXPECT_EQ(2, server_snapshot.size()); + EXPECT_TRUE(2 == server_snapshot.size()); const FieldValue when = server_snapshot.documents()[0].Get("when"); - EXPECT_EQ(FieldValue::Type::kTimestamp, when.type()); + EXPECT_TRUE(FieldValue::Type::kTimestamp == when.type()); EXPECT_THAT(QuerySnapshotToValues(server_snapshot), testing::ElementsAre(MapFieldValue{{"when", when}}, MapFieldValue{{"when", when}})); @@ -245,7 +245,7 @@ TEST_F(WriteBatchTest, TestCanWriteTheSameDocumentMultipleTimes) { DocumentSnapshot server_snapshot = accumulator.Await(); EXPECT_FALSE(server_snapshot.metadata().has_pending_writes()); const FieldValue when = server_snapshot.Get("when"); - EXPECT_EQ(FieldValue::Type::kTimestamp, when.type()); + EXPECT_TRUE(FieldValue::Type::kTimestamp == when.type()); EXPECT_THAT(server_snapshot.GetData(), testing::ContainerEq(MapFieldValue{{"a", FieldValue::Integer(1)}, {"b", FieldValue::Integer(2)}, diff --git a/firestore/src/common/document_change.cc b/firestore/src/common/document_change.cc index 74607b4c9d..759f26e13e 100644 --- a/firestore/src/common/document_change.cc +++ b/firestore/src/common/document_change.cc @@ -20,6 +20,12 @@ namespace firestore { using CleanupFnDocumentChange = CleanupFn; using Type = DocumentChange::Type; +#if defined(ANDROID) +// Older NDK (r16b) fails to define this properly. Fix this when support for +// the older NDK is removed. +const std::size_t DocumentChange::npos = static_cast(-1); +#endif // defined(ANDROID) + DocumentChange::DocumentChange() {} DocumentChange::DocumentChange(const DocumentChange& value) { diff --git a/firestore/src/include/firebase/firestore/document_change.h b/firestore/src/include/firebase/firestore/document_change.h index 31f6e08451..0c37e0619f 100644 --- a/firestore/src/include/firebase/firestore/document_change.h +++ b/firestore/src/include/firebase/firestore/document_change.h @@ -63,7 +63,13 @@ class DocumentChange { /** * The sentinel index used as a return value to indicate no matches. */ +#if defined(ANDROID) + // Older NDK (r16b) fails to define this properly. Fix this when support for + // the older NDK is removed. + static const std::size_t npos; +#else static constexpr std::size_t npos = static_cast(-1); +#endif // defined(ANDROID) /** * @brief Creates an invalid DocumentChange that has to be reassigned before diff --git a/firestore/src/tests/array_transform_test.cc b/firestore/src/tests/array_transform_test.cc deleted file mode 100644 index 468edb112d..0000000000 --- a/firestore/src/tests/array_transform_test.cc +++ /dev/null @@ -1,230 +0,0 @@ -#include -#include - -#include "firestore/src/include/firebase/firestore.h" -#include "firestore/src/tests/firestore_integration_test.h" -#include "firestore/src/tests/util/event_accumulator.h" -#include "testing/base/public/gmock.h" -#include "gtest/gtest.h" - -// These test cases are in sync with native iOS client SDK test -// Firestore/Example/Tests/Integration/API/FIRArrayTransformTests.mm -// and native Android client SDK test -// firebase_firestore/tests/integration_tests/src/com/google/firebase/firestore/ArrayTransformsTest.java -// firebase_firestore/tests/integration_tests/src/com/google/firebase/firestore/ArrayTransformServerApplicationTest.java - -namespace firebase { -namespace firestore { - -class ArrayTransformTest : public FirestoreIntegrationTest { - protected: - void SetUp() override { - document_ = Document(); - registration_ = accumulator_.listener()->AttachTo( - &document_, MetadataChanges::kInclude); - - // Wait for initial null snapshot to avoid potential races. - DocumentSnapshot snapshot = accumulator_.AwaitServerEvent(); - EXPECT_FALSE(snapshot.exists()); - } - - void TearDown() override { registration_.Remove(); } - - void WriteInitialData(const MapFieldValue& data) { - Await(document_.Set(data)); - ExpectLocalAndRemoteEvent(data); - } - - void ExpectLocalAndRemoteEvent(const MapFieldValue& data) { - EXPECT_THAT(accumulator_.AwaitLocalEvent().GetData(), - testing::ContainerEq(data)); - EXPECT_THAT(accumulator_.AwaitRemoteEvent().GetData(), - testing::ContainerEq(data)); - } - - DocumentReference document_; - EventAccumulator accumulator_; - ListenerRegistration registration_; -}; - -class ArrayTransformServerApplicationTest : public FirestoreIntegrationTest { - protected: - void SetUp() override { document_ = Document(); } - - DocumentReference document_; -}; - -TEST_F(ArrayTransformTest, CreateDocumentWithArrayUnion) { - Await(document_.Set(MapFieldValue{ - {"array", FieldValue::ArrayUnion( - {FieldValue::Integer(1), FieldValue::Integer(2)})}})); - ExpectLocalAndRemoteEvent(MapFieldValue{ - {"array", - FieldValue::Array({FieldValue::Integer(1), FieldValue::Integer(2)})}}); -} - -TEST_F(ArrayTransformTest, AppendToArrayViaUpdate) { - WriteInitialData(MapFieldValue{ - {"array", - FieldValue::Array({FieldValue::Integer(1), FieldValue::Integer(3)})}}); - Await(document_.Update(MapFieldValue{ - {"array", - FieldValue::ArrayUnion({FieldValue::Integer(2), FieldValue::Integer(1), - FieldValue::Integer(4)})}})); - ExpectLocalAndRemoteEvent(MapFieldValue{ - {"array", - FieldValue::Array({FieldValue::Integer(1), FieldValue::Integer(3), - FieldValue::Integer(2), FieldValue::Integer(4)})}}); -} - -TEST_F(ArrayTransformTest, AppendToArrayViaMergeSet) { - WriteInitialData(MapFieldValue{ - {"array", - FieldValue::Array({FieldValue::Integer(1), FieldValue::Integer(3)})}}); - Await(document_.Set(MapFieldValue{{"array", FieldValue::ArrayUnion( - {FieldValue::Integer(2), - FieldValue::Integer(1), - FieldValue::Integer(4)})}}, - SetOptions::Merge())); - ExpectLocalAndRemoteEvent(MapFieldValue{ - {"array", - FieldValue::Array({FieldValue::Integer(1), FieldValue::Integer(3), - FieldValue::Integer(2), FieldValue::Integer(4)})}}); -} - -TEST_F(ArrayTransformTest, AppendObjectToArrayViaUpdate) { - WriteInitialData(MapFieldValue{ - {"array", FieldValue::Array( - {FieldValue::Map({{"a", FieldValue::String("hi")}})})}}); - Await(document_.Update(MapFieldValue{ - {"array", - FieldValue::ArrayUnion( - {{FieldValue::Map({{"a", FieldValue::String("hi")}})}, - {FieldValue::Map({{"a", FieldValue::String("bye")}})}})}})); - ExpectLocalAndRemoteEvent(MapFieldValue{ - {"array", FieldValue::Array( - {{FieldValue::Map({{"a", FieldValue::String("hi")}})}, - {FieldValue::Map({{"a", FieldValue::String("bye")}})}})}}); -} - -TEST_F(ArrayTransformTest, RemoveFromArrayViaUpdate) { - WriteInitialData(MapFieldValue{ - {"array", - FieldValue::Array({FieldValue::Integer(1), FieldValue::Integer(3), - FieldValue::Integer(1), FieldValue::Integer(3)})}}); - Await(document_.Update(MapFieldValue{ - {"array", FieldValue::ArrayRemove( - {FieldValue::Integer(1), FieldValue::Integer(4)})}})); - ExpectLocalAndRemoteEvent(MapFieldValue{ - {"array", - FieldValue::Array({FieldValue::Integer(3), FieldValue::Integer(3)})}}); -} - -TEST_F(ArrayTransformTest, RemoveFromArrayViaMergeSet) { - WriteInitialData(MapFieldValue{ - {"array", - FieldValue::Array({FieldValue::Integer(1), FieldValue::Integer(3), - FieldValue::Integer(1), FieldValue::Integer(3)})}}); - Await(document_.Set(MapFieldValue{{"array", FieldValue::ArrayRemove( - {FieldValue::Integer(1), - FieldValue::Integer(4)})}}, - SetOptions::Merge())); - ExpectLocalAndRemoteEvent(MapFieldValue{ - {"array", - FieldValue::Array({FieldValue::Integer(3), FieldValue::Integer(3)})}}); -} - -TEST_F(ArrayTransformTest, RemoveObjectFromArrayViaUpdate) { - WriteInitialData(MapFieldValue{ - {"array", FieldValue::Array( - {FieldValue::Map({{"a", FieldValue::String("hi")}}), - FieldValue::Map({{"a", FieldValue::String("bye")}})})}}); - Await(document_.Update(MapFieldValue{ - {"array", FieldValue::ArrayRemove( - {{FieldValue::Map({{"a", FieldValue::String("hi")}})}})}})); - ExpectLocalAndRemoteEvent(MapFieldValue{ - {"array", FieldValue::Array( - {{FieldValue::Map({{"a", FieldValue::String("bye")}})}})}}); -} - -TEST_F(ArrayTransformServerApplicationTest, SetWithNoCachedBaseDoc) { - Await(document_.Set(MapFieldValue{ - {"array", FieldValue::ArrayUnion( - {FieldValue::Integer(1), FieldValue::Integer(2)})}})); - DocumentSnapshot snapshot = *Await(document_.Get(Source::kCache)); - EXPECT_THAT(snapshot.GetData(), - testing::ContainerEq(MapFieldValue{ - {"array", FieldValue::Array({FieldValue::Integer(1), - FieldValue::Integer(2)})}})); -} - -TEST_F(ArrayTransformServerApplicationTest, UpdateWithNoCachedBaseDoc) { - // Write an initial document in an isolated Firestore instance so it's not - // stored in our cache. - Await(TestFirestore("isolated") - ->Document(document_.path()) - .Set(MapFieldValue{ - {"array", FieldValue::Array({FieldValue::Integer(42)})}})); - - Await(document_.Update(MapFieldValue{ - {"array", FieldValue::ArrayUnion( - {FieldValue::Integer(1), FieldValue::Integer(2)})}})); - - // Nothing should be cached since it was an update and we had no base doc. - Future future = document_.Get(Source::kCache); - Await(future); - EXPECT_EQ(Error::kErrorUnavailable, future.error()); -} - -TEST_F(ArrayTransformServerApplicationTest, MergeSetWithNoCachedBaseDoc) { - // Write an initial document in an isolated Firestore instance so it's not - // stored in our cache. - Await(TestFirestore("isolated") - ->Document(document_.path()) - .Set(MapFieldValue{ - {"array", FieldValue::Array({FieldValue::Integer(42)})}})); - - Await(document_.Set(MapFieldValue{{"array", FieldValue::ArrayUnion( - {FieldValue::Integer(1), - FieldValue::Integer(2)})}}, - SetOptions::Merge())); - // Document will be cached but we'll be missing 42. - DocumentSnapshot snapshot = *Await(document_.Get(Source::kCache)); - EXPECT_THAT(snapshot.GetData(), - testing::ContainerEq(MapFieldValue{ - {"array", FieldValue::Array({FieldValue::Integer(1), - FieldValue::Integer(2)})}})); -} - -TEST_F(ArrayTransformServerApplicationTest, - UpdateWithCachedBaseDocUsingArrayUnion) { - Await(document_.Set( - MapFieldValue{{"array", FieldValue::Array({FieldValue::Integer(42)})}})); - Await(document_.Update(MapFieldValue{ - {"array", FieldValue::ArrayUnion( - {FieldValue::Integer(1), FieldValue::Integer(2)})}})); - DocumentSnapshot snapshot = *Await(document_.Get(Source::kCache)); - EXPECT_THAT(snapshot.GetData(), - testing::ContainerEq(MapFieldValue{ - {"array", FieldValue::Array({FieldValue::Integer(42), - FieldValue::Integer(1), - FieldValue::Integer(2)})}})); -} - -TEST_F(ArrayTransformServerApplicationTest, - UpdateWithCachedBaseDocUsingArrayRemove) { - Await(document_.Set(MapFieldValue{ - {"array", - FieldValue::Array({FieldValue::Integer(42), FieldValue::Integer(1L), - FieldValue::Integer(2L)})}})); - Await(document_.Update(MapFieldValue{ - {"array", FieldValue::ArrayRemove( - {FieldValue::Integer(1), FieldValue::Integer(2)})}})); - DocumentSnapshot snapshot = *Await(document_.Get(Source::kCache)); - EXPECT_THAT(snapshot.GetData(), - testing::ContainerEq(MapFieldValue{ - {"array", FieldValue::Array({FieldValue::Integer(42)})}})); -} - -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/cleanup_test.cc b/firestore/src/tests/cleanup_test.cc deleted file mode 100644 index 30f75eb97e..0000000000 --- a/firestore/src/tests/cleanup_test.cc +++ /dev/null @@ -1,422 +0,0 @@ -#include "app/src/include/firebase/internal/common.h" -#include "firestore/src/common/futures.h" -#include "firestore/src/include/firebase/firestore.h" -#include "firestore/src/tests/firestore_integration_test.h" - -namespace firebase { -namespace firestore { -namespace { - -void ExpectAllMethodsAreNoOps(Query* ptr); - -// Checks that methods accessing the associated Firestore instance don't crash -// and return null. -template -void ExpectNullFirestore(T* ptr) { - EXPECT_EQ(ptr->firestore(), nullptr); - // Make sure to check both const and non-const overloads. - EXPECT_EQ(static_cast(ptr)->firestore(), nullptr); -} - -// Checks that the given object can be copied from, and the resulting copy can -// be moved. -template -void ExpectCopyableAndMoveable(T* ptr) { - EXPECT_NO_THROW({ - // Copy constructor - T copy = *ptr; - // Move constructor - T moved = std::move(copy); - - // Copy assignment operator - copy = *ptr; - // Move assignment operator - moved = std::move(copy); - }); -} - -// Checks that `operator==` and `operator!=` work correctly by comparing to -// a default-constructed instance. -template -void ExpectEqualityToWork(T* ptr) { - EXPECT_TRUE(*ptr == T()); - EXPECT_FALSE(*ptr != T()); -} - -// `ExpectAllMethodsAreNoOps` calls all the public API methods on the given -// `ptr` and checks that the calls don't crash and, where applicable, return -// value-initialized values. - -void ExpectAllMethodsAreNoOps(CollectionReference* ptr) { - EXPECT_EQ(*ptr, CollectionReference()); - ExpectCopyableAndMoveable(ptr); - ExpectEqualityToWork(ptr); - - ExpectAllMethodsAreNoOps(static_cast(ptr)); - - EXPECT_EQ(ptr->id(), ""); - EXPECT_EQ(ptr->path(), ""); - - EXPECT_EQ(ptr->Document(), DocumentReference()); - EXPECT_EQ(ptr->Document("foo"), DocumentReference()); - EXPECT_EQ(ptr->Document(std::string("foo")), DocumentReference()); - - EXPECT_EQ(ptr->Add(MapFieldValue()), FailedFuture()); -} - -void ExpectAllMethodsAreNoOps(DocumentChange* ptr) { - // TODO(b/137966104): implement == on `DocumentChange` - // ExpectEqualityToWork(ptr); - ExpectCopyableAndMoveable(ptr); - - EXPECT_EQ(ptr->type(), DocumentChange::Type()); - // TODO(b/137966104): implement == on `DocumentSnapshot` - EXPECT_NO_THROW(ptr->document()); - EXPECT_EQ(ptr->old_index(), 0); - EXPECT_EQ(ptr->new_index(), 0); -} - -void ExpectAllMethodsAreNoOps(DocumentReference* ptr) { - EXPECT_FALSE(ptr->is_valid()); - - ExpectEqualityToWork(ptr); - ExpectCopyableAndMoveable(ptr); - ExpectNullFirestore(ptr); - - EXPECT_EQ(ptr->ToString(), "DocumentReference(invalid)"); - - EXPECT_EQ(ptr->id(), ""); - EXPECT_EQ(ptr->path(), ""); - - EXPECT_EQ(ptr->Parent(), CollectionReference()); - EXPECT_EQ(ptr->Collection("foo"), CollectionReference()); - EXPECT_EQ(ptr->Collection(std::string("foo")), CollectionReference()); - - EXPECT_EQ(ptr->Get(), FailedFuture()); - - EXPECT_EQ(ptr->Set(MapFieldValue()), FailedFuture()); - - EXPECT_EQ(ptr->Update(MapFieldValue()), FailedFuture()); - EXPECT_EQ(ptr->Update(MapFieldPathValue()), FailedFuture()); - - EXPECT_EQ(ptr->Delete(), FailedFuture()); - -#if defined(FIREBASE_USE_STD_FUNCTION) - EXPECT_NO_THROW(ptr->AddSnapshotListener( - [](const DocumentSnapshot&, Error, const std::string&) {})); -#else - EXPECT_NO_THROW(ptr->AddSnapshotListener(nullptr)); -#endif -} - -void ExpectAllMethodsAreNoOps(DocumentSnapshot* ptr) { - // TODO(b/137966104): implement == on `DocumentSnapshot` - // ExpectEqualityToWork(ptr); - ExpectCopyableAndMoveable(ptr); - - EXPECT_EQ(ptr->ToString(), "DocumentSnapshot(invalid)"); - - EXPECT_EQ(ptr->id(), ""); - EXPECT_FALSE(ptr->exists()); - - EXPECT_EQ(ptr->reference(), DocumentReference()); - // TODO(b/137966104): implement == on `SnapshotMetadata` - EXPECT_NO_THROW(ptr->metadata()); - - EXPECT_EQ(ptr->GetData(), MapFieldValue()); - - EXPECT_EQ(ptr->Get("foo"), FieldValue()); - EXPECT_EQ(ptr->Get(std::string("foo")), FieldValue()); - EXPECT_EQ(ptr->Get(FieldPath{"foo"}), FieldValue()); -} - -void ExpectAllMethodsAreNoOps(FieldValue* ptr) { - ExpectEqualityToWork(ptr); - ExpectCopyableAndMoveable(ptr); - - EXPECT_FALSE(ptr->is_valid()); - // FieldValue doesn't have a separate "invalid" type in its enum. - EXPECT_TRUE(ptr->is_null()); - - EXPECT_EQ(ptr->type(), FieldValue::Type()); - - EXPECT_FALSE(ptr->is_boolean()); - EXPECT_FALSE(ptr->is_integer()); - EXPECT_FALSE(ptr->is_double()); - EXPECT_FALSE(ptr->is_timestamp()); - EXPECT_FALSE(ptr->is_string()); - EXPECT_FALSE(ptr->is_blob()); - EXPECT_FALSE(ptr->is_reference()); - EXPECT_FALSE(ptr->is_geo_point()); - EXPECT_FALSE(ptr->is_array()); - EXPECT_FALSE(ptr->is_map()); - - EXPECT_EQ(ptr->boolean_value(), false); - EXPECT_EQ(ptr->integer_value(), 0); - EXPECT_EQ(ptr->double_value(), 0); - EXPECT_EQ(ptr->timestamp_value(), Timestamp()); - EXPECT_EQ(ptr->string_value(), ""); - EXPECT_EQ(ptr->blob_value(), nullptr); - EXPECT_EQ(ptr->reference_value(), DocumentReference()); - EXPECT_EQ(ptr->geo_point_value(), GeoPoint()); - EXPECT_TRUE(ptr->array_value().empty()); - EXPECT_TRUE(ptr->map_value().empty()); -} - -void ExpectAllMethodsAreNoOps(ListenerRegistration* ptr) { - // `ListenerRegistration` isn't equality comparable. - ExpectCopyableAndMoveable(ptr); - - EXPECT_NO_THROW(ptr->Remove()); -} - -void ExpectAllMethodsAreNoOps(Query* ptr) { - ExpectEqualityToWork(ptr); - ExpectCopyableAndMoveable(ptr); - ExpectNullFirestore(ptr); - - EXPECT_EQ(ptr->WhereEqualTo("foo", FieldValue()), Query()); - EXPECT_EQ(ptr->WhereEqualTo(FieldPath{"foo"}, FieldValue()), Query()); - - EXPECT_EQ(ptr->WhereLessThan("foo", FieldValue()), Query()); - EXPECT_EQ(ptr->WhereLessThan(FieldPath{"foo"}, FieldValue()), Query()); - - EXPECT_EQ(ptr->WhereLessThanOrEqualTo("foo", FieldValue()), Query()); - EXPECT_EQ(ptr->WhereLessThanOrEqualTo(FieldPath{"foo"}, FieldValue()), - Query()); - - EXPECT_EQ(ptr->WhereGreaterThan("foo", FieldValue()), Query()); - EXPECT_EQ(ptr->WhereGreaterThan(FieldPath{"foo"}, FieldValue()), Query()); - - EXPECT_EQ(ptr->WhereGreaterThanOrEqualTo("foo", FieldValue()), Query()); - EXPECT_EQ(ptr->WhereGreaterThanOrEqualTo(FieldPath{"foo"}, FieldValue()), - Query()); - - EXPECT_EQ(ptr->WhereArrayContains("foo", FieldValue()), Query()); - EXPECT_EQ(ptr->WhereArrayContains(FieldPath{"foo"}, FieldValue()), Query()); - - EXPECT_EQ(ptr->OrderBy("foo"), Query()); - EXPECT_EQ(ptr->OrderBy(FieldPath{"foo"}), Query()); - - EXPECT_EQ(ptr->Limit(123), Query()); - - EXPECT_EQ(ptr->StartAt(DocumentSnapshot()), Query()); - EXPECT_EQ(ptr->StartAt(std::vector()), Query()); - - EXPECT_EQ(ptr->StartAfter(DocumentSnapshot()), Query()); - EXPECT_EQ(ptr->StartAfter(std::vector()), Query()); - - EXPECT_EQ(ptr->EndBefore(DocumentSnapshot()), Query()); - EXPECT_EQ(ptr->EndBefore(std::vector()), Query()); - - EXPECT_EQ(ptr->EndAt(DocumentSnapshot()), Query()); - EXPECT_EQ(ptr->EndAt(std::vector()), Query()); - - EXPECT_EQ(ptr->Get(), FailedFuture()); - - EXPECT_EQ(ptr->Get(), FailedFuture()); - -#if defined(FIREBASE_USE_STD_FUNCTION) - EXPECT_NO_THROW(ptr->AddSnapshotListener( - [](const QuerySnapshot&, Error, const std::string&) {})); -#else - EXPECT_NO_THROW(ptr->AddSnapshotListener(nullptr)); -#endif -} - -void ExpectAllMethodsAreNoOps(QuerySnapshot* ptr) { - // TODO(b/137966104): implement == on `QuerySnapshot` - // ExpectEqualityToWork(ptr); - ExpectCopyableAndMoveable(ptr); - - EXPECT_EQ(ptr->query(), Query()); - - // TODO(b/137966104): implement == on `SnapshotMetadata` - EXPECT_NO_THROW(ptr->metadata()); - - EXPECT_TRUE(ptr->DocumentChanges().empty()); - EXPECT_TRUE(ptr->documents().empty()); - EXPECT_TRUE(ptr->empty()); - EXPECT_EQ(ptr->size(), 0); -} - -void ExpectAllMethodsAreNoOps(WriteBatch* ptr) { - // `WriteBatch` isn't equality comparable. - ExpectCopyableAndMoveable(ptr); - - EXPECT_NO_THROW(ptr->Set(DocumentReference(), MapFieldValue())); - - EXPECT_NO_THROW(ptr->Update(DocumentReference(), MapFieldValue())); - EXPECT_NO_THROW(ptr->Update(DocumentReference(), MapFieldPathValue())); - - EXPECT_NO_THROW(ptr->Delete(DocumentReference())); - - EXPECT_EQ(ptr->Commit(), FailedFuture()); -} - -using CleanupTest = FirestoreIntegrationTest; - -TEST_F(CleanupTest, CollectionReferenceIsBlankAfterCleanup) { - { - CollectionReference default_constructed; - SCOPED_TRACE("CollectionReference.DefaultConstructed"); - ExpectAllMethodsAreNoOps(&default_constructed); - } - - CollectionReference col = Collection(); - DeleteFirestore(col.firestore()); - SCOPED_TRACE("CollectionReference.AfterCleanup"); - ExpectAllMethodsAreNoOps(&col); -} - -TEST_F(CleanupTest, DocumentChangeIsBlankAfterCleanup) { - { - DocumentChange default_constructed; - SCOPED_TRACE("DocumentChange.DefaultConstructed"); - ExpectAllMethodsAreNoOps(&default_constructed); - } - - CollectionReference col = Collection("col"); - DocumentReference doc = col.Document(); - WriteDocument(doc, MapFieldValue{{"foo", FieldValue::String("bar")}}); - - QuerySnapshot snap = ReadDocuments(col); - auto changes = snap.DocumentChanges(); - ASSERT_EQ(changes.size(), 1); - DocumentChange& change = changes.front(); - - DeleteFirestore(col.firestore()); - SCOPED_TRACE("DocumentChange.AfterCleanup"); - ExpectAllMethodsAreNoOps(&change); -} - -TEST_F(CleanupTest, DocumentReferenceIsBlankAfterCleanup) { - { - DocumentReference default_constructed; - SCOPED_TRACE("DocumentReference.DefaultConstructed"); - ExpectAllMethodsAreNoOps(&default_constructed); - } - - DocumentReference doc = Document(); - DeleteFirestore(doc.firestore()); - SCOPED_TRACE("DocumentReference.AfterCleanup"); - ExpectAllMethodsAreNoOps(&doc); -} - -TEST_F(CleanupTest, DocumentSnapshotIsBlankAfterCleanup) { - { - DocumentSnapshot default_constructed; - SCOPED_TRACE("DocumentSnapshot.DefaultConstructed"); - ExpectAllMethodsAreNoOps(&default_constructed); - } - - DocumentReference doc = Document(); - WriteDocument(doc, MapFieldValue{{"foo", FieldValue::String("bar")}}); - DocumentSnapshot snap = ReadDocument(doc); - - DeleteFirestore(doc.firestore()); - SCOPED_TRACE("DocumentSnapshot.AfterCleanup"); - ExpectAllMethodsAreNoOps(&snap); -} - -TEST_F(CleanupTest, FieldValueIsBlankAfterCleanup) { - { - FieldValue default_constructed; - SCOPED_TRACE("FieldValue.DefaultConstructed"); - ExpectAllMethodsAreNoOps(&default_constructed); - } - - DocumentReference doc = Document(); - WriteDocument(doc, MapFieldValue{{"foo", FieldValue::String("bar")}, - {"ref", FieldValue::Reference(doc)}}); - DocumentSnapshot snap = ReadDocument(doc); - - FieldValue str_value = snap.Get("foo"); - EXPECT_TRUE(str_value.is_valid()); - EXPECT_TRUE(str_value.is_string()); - - FieldValue ref_value = snap.Get("ref"); - EXPECT_TRUE(ref_value.is_valid()); - EXPECT_TRUE(ref_value.is_reference()); - - DeleteFirestore(doc.firestore()); - // `FieldValue`s are not cleaned up, because they are owned by the user and - // stay valid after Firestore has shut down. - EXPECT_TRUE(str_value.is_valid()); - EXPECT_TRUE(str_value.is_string()); - EXPECT_EQ(str_value.string_value(), "bar"); - - // However, need to make sure that in a reference value, the reference was - // cleaned up. - EXPECT_TRUE(ref_value.is_valid()); - EXPECT_TRUE(ref_value.is_reference()); - DocumentReference ref_after_cleanup = ref_value.reference_value(); - SCOPED_TRACE("FieldValue.AfterCleanup"); - ExpectAllMethodsAreNoOps(&ref_after_cleanup); -} - -// Note: `Firestore` is not default-constructible, and it is deleted immediately -// after cleanup. Thus, there is no case where a user could be accessing -// a "blank" Firestore instance. - -#if defined(FIREBASE_USE_STD_FUNCTION) -TEST_F(CleanupTest, ListenerRegistrationIsBlankAfterCleanup) { - { - ListenerRegistration default_constructed; - SCOPED_TRACE("ListenerRegistration.DefaultConstructed"); - ExpectAllMethodsAreNoOps(&default_constructed); - } - - DocumentReference doc = Document(); - ListenerRegistration reg = doc.AddSnapshotListener( - [](const DocumentSnapshot&, Error, const std::string&) {}); - DeleteFirestore(doc.firestore()); - SCOPED_TRACE("ListenerRegistration.AfterCleanup"); - ExpectAllMethodsAreNoOps(®); -} -#endif - -// Note: `Query` cleanup is tested as part of `CollectionReference` cleanup -// (`CollectionReference` is derived from `Query`). - -TEST_F(CleanupTest, QuerySnapshotIsBlankAfterCleanup) { - { - QuerySnapshot default_constructed; - SCOPED_TRACE("QuerySnapshot.DefaultConstructed"); - ExpectAllMethodsAreNoOps(&default_constructed); - } - - CollectionReference col = Collection("col"); - DocumentReference doc = col.Document(); - WriteDocument(doc, MapFieldValue{{"foo", FieldValue::String("bar")}}); - - QuerySnapshot snap = ReadDocuments(col); - EXPECT_EQ(snap.size(), 1); - - DeleteFirestore(col.firestore()); - SCOPED_TRACE("QuerySnapshot.AfterCleanup"); - ExpectAllMethodsAreNoOps(&snap); -} - -// Note: `Transaction` is uncopyable and not default constructible, and storing -// a pointer to a `Transaction` is not valid in general, because the object will -// be destroyed as soon as the transaction is finished. Thus, there is no valid -// case where a user could be accessing a "blank" transaction. - -TEST_F(CleanupTest, WriteBatchIsBlankAfterCleanup) { - { - WriteBatch default_constructed; - SCOPED_TRACE("WriteBatch.DefaultConstructed"); - ExpectAllMethodsAreNoOps(&default_constructed); - } - - Firestore* db = TestFirestore(); - WriteBatch batch = db->batch(); - DeleteFirestore(db); - SCOPED_TRACE("WriteBatch.AfterCleanup"); - ExpectAllMethodsAreNoOps(&batch); -} - -} // namespace -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/collection_reference_test.cc b/firestore/src/tests/collection_reference_test.cc deleted file mode 100644 index 1c700497b9..0000000000 --- a/firestore/src/tests/collection_reference_test.cc +++ /dev/null @@ -1,34 +0,0 @@ -#include - -#include "firestore/src/common/wrapper_assertions.h" -#include "firestore/src/include/firebase/firestore.h" -#if defined(__ANDROID__) -#include "firestore/src/android/collection_reference_android.h" -#elif defined(FIRESTORE_STUB_BUILD) -#include "firestore/src/stub/collection_reference_stub.h" -#endif // defined(__ANDROID__) - -#include "testing/base/public/gmock.h" -#include "gtest/gtest.h" - -namespace firebase { -namespace firestore { - -#if defined(__ANDROID__) || defined(FIRESTORE_STUB_BUILD) - -using CollectionReferenceTest = testing::Test; - -TEST_F(CollectionReferenceTest, Construction) { - testutil::AssertWrapperConstructionContract(); -} - -TEST_F(CollectionReferenceTest, Assignment) { - testutil::AssertWrapperAssignmentContract(); -} - -#endif // defined(__ANDROID__) || defined(FIRESTORE_STUB_BUILD) - -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/cursor_test.cc b/firestore/src/tests/cursor_test.cc deleted file mode 100644 index ba416931f9..0000000000 --- a/firestore/src/tests/cursor_test.cc +++ /dev/null @@ -1,278 +0,0 @@ -#include -#include - -#include "firestore/src/include/firebase/firestore.h" -#include "firestore/src/tests/firestore_integration_test.h" -#include "testing/base/public/gmock.h" -#include "gtest/gtest.h" - -// These test cases are in sync with native iOS client SDK test -// Firestore/Example/Tests/Integration/API/FIRCursorTests.mm -// and native Android client SDK test -// firebase_firestore/tests/integration_tests/src/com/google/firebase/firestore/CursorTest.java -// The iOS test names start with the mandatory test prefix while Android test -// names do not. Here we use the Android test names. - -namespace firebase { -namespace firestore { - -TEST_F(FirestoreIntegrationTest, CanPageThroughItems) { - CollectionReference collection = - Collection({{"a", {{"v", FieldValue::String("a")}}}, - {"b", {{"v", FieldValue::String("b")}}}, - {"c", {{"v", FieldValue::String("c")}}}, - {"d", {{"v", FieldValue::String("d")}}}, - {"e", {{"v", FieldValue::String("e")}}}, - {"f", {{"v", FieldValue::String("f")}}}}); - QuerySnapshot snapshot = ReadDocuments(collection.Limit(2)); - EXPECT_EQ(std::vector({{{"v", FieldValue::String("a")}}, - {{"v", FieldValue::String("b")}}}), - QuerySnapshotToValues(snapshot)); - - DocumentSnapshot last_doc = snapshot.documents()[1]; - snapshot = ReadDocuments(collection.Limit(3).StartAfter(last_doc)); - EXPECT_EQ(std::vector({{{"v", FieldValue::String("c")}}, - {{"v", FieldValue::String("d")}}, - {{"v", FieldValue::String("e")}}}), - QuerySnapshotToValues(snapshot)); - - last_doc = snapshot.documents()[2]; - snapshot = ReadDocuments(collection.Limit(1).StartAfter(last_doc)); - EXPECT_EQ(std::vector({{{"v", FieldValue::String("f")}}}), - QuerySnapshotToValues(snapshot)); - - last_doc = snapshot.documents()[0]; - snapshot = ReadDocuments(collection.Limit(3).StartAfter(last_doc)); - EXPECT_EQ(std::vector{}, QuerySnapshotToValues(snapshot)); -} - -TEST_F(FirestoreIntegrationTest, CanBeCreatedFromDocuments) { - CollectionReference collection = Collection( - {{"a", - {{"k", FieldValue::String("a")}, {"sort", FieldValue::Double(1.0)}}}, - {"b", - {{"k", FieldValue::String("b")}, {"sort", FieldValue::Double(2.0)}}}, - {"c", - {{"k", FieldValue::String("c")}, {"sort", FieldValue::Double(2.0)}}}, - {"d", - {{"k", FieldValue::String("d")}, {"sort", FieldValue::Double(2.0)}}}, - {"e", - {{"k", FieldValue::String("e")}, {"sort", FieldValue::Double(0.0)}}}, - // should not show up - {"f", - {{"k", FieldValue::String("f")}, - {"nosort", FieldValue::Double(1.0)}}}}); - Query query = collection.OrderBy("sort"); - DocumentSnapshot snapshot = ReadDocument(collection.Document("c")); - - EXPECT_TRUE(snapshot.exists()); - EXPECT_EQ(std::vector({{{"k", FieldValue::String("c")}, - {"sort", FieldValue::Double(2.0)}}, - {{"k", FieldValue::String("d")}, - {"sort", FieldValue::Double(2.0)}}}), - QuerySnapshotToValues(ReadDocuments(query.StartAt(snapshot)))); - - EXPECT_EQ( - std::vector( - {{{"k", FieldValue::String("e")}, {"sort", FieldValue::Double(0.0)}}, - {{"k", FieldValue::String("a")}, {"sort", FieldValue::Double(1.0)}}, - {{"k", FieldValue::String("b")}, - {"sort", FieldValue::Double(2.0)}}}), - QuerySnapshotToValues(ReadDocuments(query.EndBefore(snapshot)))); -} - -TEST_F(FirestoreIntegrationTest, CanBeCreatedFromValues) { - CollectionReference collection = Collection( - {{"a", - {{"k", FieldValue::String("a")}, {"sort", FieldValue::Double(1.0)}}}, - {"b", - {{"k", FieldValue::String("b")}, {"sort", FieldValue::Double(2.0)}}}, - {"c", - {{"k", FieldValue::String("c")}, {"sort", FieldValue::Double(2.0)}}}, - {"d", - {{"k", FieldValue::String("d")}, {"sort", FieldValue::Double(2.0)}}}, - {"e", - {{"k", FieldValue::String("e")}, {"sort", FieldValue::Double(0.0)}}}, - // should not show up - {"f", - {{"k", FieldValue::String("f")}, - {"nosort", FieldValue::Double(1.0)}}}}); - Query query = collection.OrderBy("sort"); - - QuerySnapshot snapshot = ReadDocuments( - query.StartAt(std::vector({FieldValue::Double(2.0)}))); - EXPECT_EQ( - std::vector( - {{{"k", FieldValue::String("b")}, {"sort", FieldValue::Double(2.0)}}, - {{"k", FieldValue::String("c")}, {"sort", FieldValue::Double(2.0)}}, - {{"k", FieldValue::String("d")}, - {"sort", FieldValue::Double(2.0)}}}), - QuerySnapshotToValues(snapshot)); - - snapshot = ReadDocuments( - query.EndBefore(std::vector({FieldValue::Double(2.0)}))); - EXPECT_EQ(std::vector({{{"k", FieldValue::String("e")}, - {"sort", FieldValue::Double(0.0)}}, - {{"k", FieldValue::String("a")}, - {"sort", FieldValue::Double(1.0)}}}), - QuerySnapshotToValues(snapshot)); -} - -TEST_F(FirestoreIntegrationTest, CanBeCreatedUsingDocumentId) { - std::map docs = { - {"a", {{"v", FieldValue::String("a")}}}, - {"b", {{"v", FieldValue::String("b")}}}, - {"c", {{"v", FieldValue::String("c")}}}, - {"d", {{"v", FieldValue::String("d")}}}, - {"e", {{"v", FieldValue::String("e")}}}}; - - CollectionReference writer = TestFirestore("writer") - ->Collection("parent-collection") - .Document() - .Collection("sub-collection"); - WriteDocuments(writer, docs); - - CollectionReference reader = - TestFirestore("reader")->Collection(writer.path()); - QuerySnapshot snapshot = ReadDocuments( - reader.OrderBy(FieldPath::DocumentId()) - .StartAt(std::vector({FieldValue::String("b")})) - .EndBefore(std::vector({FieldValue::String("d")}))); - EXPECT_EQ(std::vector({{{"v", FieldValue::String("b")}}, - {{"v", FieldValue::String("c")}}}), - QuerySnapshotToValues(snapshot)); -} - -TEST_F(FirestoreIntegrationTest, CanBeUsedWithReferenceValues) { - Firestore* db = TestFirestore(); - std::map docs = { - {"a", - {{"k", FieldValue::String("1a")}, - {"ref", FieldValue::Reference(db->Collection("1").Document("a"))}}}, - {"b", - {{"k", FieldValue::String("1b")}, - {"ref", FieldValue::Reference(db->Collection("1").Document("b"))}}}, - {"c", - {{"k", FieldValue::String("2a")}, - {"ref", FieldValue::Reference(db->Collection("2").Document("a"))}}}, - {"d", - {{"k", FieldValue::String("2b")}, - {"ref", FieldValue::Reference(db->Collection("2").Document("b"))}}}, - {"e", - {{"k", FieldValue::String("3a")}, - {"ref", FieldValue::Reference(db->Collection("3").Document("a"))}}}}; - - CollectionReference collection = Collection(docs); - - QuerySnapshot snapshot = ReadDocuments( - collection.OrderBy("ref") - .StartAfter(std::vector( - {FieldValue::Reference(db->Collection("1").Document("a"))})) - .EndAt(std::vector( - {FieldValue::Reference(db->Collection("2").Document("b"))}))); - - std::vector results; - for (const DocumentSnapshot& doc : snapshot.documents()) { - results.push_back(doc.Get("k").string_value()); - } - EXPECT_EQ(std::vector({"1b", "2a", "2b"}), results); -} - -TEST_F(FirestoreIntegrationTest, CanBeUsedInDescendingQueries) { - CollectionReference collection = Collection( - {{"a", - {{"k", FieldValue::String("a")}, {"sort", FieldValue::Double(1.0)}}}, - {"b", - {{"k", FieldValue::String("b")}, {"sort", FieldValue::Double(2.0)}}}, - {"c", - {{"k", FieldValue::String("c")}, {"sort", FieldValue::Double(2.0)}}}, - {"d", - {{"k", FieldValue::String("d")}, {"sort", FieldValue::Double(3.0)}}}, - {"e", - {{"k", FieldValue::String("e")}, {"sort", FieldValue::Double(0.0)}}}, - // should not show up - {"f", - {{"k", FieldValue::String("f")}, - {"nosort", FieldValue::Double(1.0)}}}}); - Query query = - collection.OrderBy("sort", Query::Direction::kDescending) - .OrderBy(FieldPath::DocumentId(), Query::Direction::kDescending); - - QuerySnapshot snapshot = ReadDocuments( - query.StartAt(std::vector({FieldValue::Double(2.0)}))); - EXPECT_EQ( - std::vector( - {{{"k", FieldValue::String("c")}, {"sort", FieldValue::Double(2.0)}}, - {{"k", FieldValue::String("b")}, {"sort", FieldValue::Double(2.0)}}, - {{"k", FieldValue::String("a")}, {"sort", FieldValue::Double(1.0)}}, - {{"k", FieldValue::String("e")}, - {"sort", FieldValue::Double(0.0)}}}), - QuerySnapshotToValues(snapshot)); - - snapshot = ReadDocuments( - query.EndBefore(std::vector({FieldValue::Double(2.0)}))); - EXPECT_EQ(std::vector({{{"k", FieldValue::String("d")}, - {"sort", FieldValue::Double(3.0)}}}), - QuerySnapshotToValues(snapshot)); -} - -TEST_F(FirestoreIntegrationTest, TimestampsCanBePassedToQueriesAsLimits) { - CollectionReference collection = - Collection({{"a", {{"timestamp", FieldValue::Timestamp({100, 2000})}}}, - {"b", {{"timestamp", FieldValue::Timestamp({100, 5000})}}}, - {"c", {{"timestamp", FieldValue::Timestamp({100, 3000})}}}, - {"d", {{"timestamp", FieldValue::Timestamp({100, 1000})}}}, - // Number of nanoseconds deliberately repeated. - {"e", {{"timestamp", FieldValue::Timestamp({100, 5000})}}}, - {"f", {{"timestamp", FieldValue::Timestamp({100, 4000})}}}}); - QuerySnapshot snapshot = ReadDocuments( - collection.OrderBy("timestamp") - .StartAfter( - std::vector({FieldValue::Timestamp({100, 2000})})) - .EndAt( - std::vector({FieldValue::Timestamp({100, 5000})}))); - EXPECT_EQ(std::vector({"c", "f", "b", "e"}), - QuerySnapshotToIds(snapshot)); -} - -TEST_F(FirestoreIntegrationTest, TimestampsCanBePassedToQueriesInWhereClause) { - CollectionReference collection = - Collection({{"a", {{"timestamp", FieldValue::Timestamp({100, 7000})}}}, - {"b", {{"timestamp", FieldValue::Timestamp({100, 4000})}}}, - {"c", {{"timestamp", FieldValue::Timestamp({100, 8000})}}}, - {"d", {{"timestamp", FieldValue::Timestamp({100, 5000})}}}, - {"e", {{"timestamp", FieldValue::Timestamp({100, 6000})}}}}); - - QuerySnapshot snapshot = ReadDocuments( - collection - .WhereGreaterThanOrEqualTo("timestamp", - FieldValue::Timestamp({100, 5000})) - .WhereLessThan("timestamp", FieldValue::Timestamp({100, 8000}))); - EXPECT_EQ(std::vector({"d", "e", "a"}), - QuerySnapshotToIds(snapshot)); -} - -TEST_F(FirestoreIntegrationTest, TimestampsAreTruncatedToMicroseconds) { - const FieldValue nanos = FieldValue::Timestamp({0, 123456789}); - const FieldValue micros = FieldValue::Timestamp({0, 123456000}); - const FieldValue millis = FieldValue::Timestamp({0, 123000000}); - CollectionReference collection = Collection({{"a", {{"timestamp", nanos}}}}); - - QuerySnapshot snapshot = - ReadDocuments(collection.WhereEqualTo("timestamp", nanos)); - EXPECT_EQ(1, QuerySnapshotToValues(snapshot).size()); - - // Because Timestamp should have been truncated to microseconds, the - // microsecond timestamp should be considered equal to the nanosecond one. - snapshot = ReadDocuments(collection.WhereEqualTo("timestamp", micros)); - EXPECT_EQ(1, QuerySnapshotToValues(snapshot).size()); - - // The truncation is just to the microseconds, however, so the millisecond - // timestamp should be treated as different and thus the query should return - // no results. - snapshot = ReadDocuments(collection.WhereEqualTo("timestamp", millis)); - EXPECT_TRUE(QuerySnapshotToValues(snapshot).empty()); -} - -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/document_change_test.cc b/firestore/src/tests/document_change_test.cc deleted file mode 100644 index 864f81353c..0000000000 --- a/firestore/src/tests/document_change_test.cc +++ /dev/null @@ -1,82 +0,0 @@ -#if defined(__ANDROID__) -#include "firestore/src/android/document_change_android.h" -#elif defined(FIRESTORE_STUB_BUILD) -#include "firestore/src/stub/document_change_stub.h" -#endif // defined(__ANDROID__) - -#include "firestore/src/common/wrapper_assertions.h" -#include "firestore/src/include/firebase/firestore.h" -#include "firestore/src/tests/firestore_integration_test.h" -#include "testing/base/public/gmock.h" -#include "gtest/gtest.h" - -namespace firebase { -namespace firestore { - -#if defined(__ANDROID__) || defined(FIRESTORE_STUB_BUILD) - -using DocumentChangeTest = FirestoreIntegrationTest; - -TEST_F(DocumentChangeTest, Construction) { - testutil::AssertWrapperConstructionContract(); -} - -TEST_F(DocumentChangeTest, Assignment) { - testutil::AssertWrapperAssignmentContract(); -} - -TEST_F(DocumentChangeTest, TestDocumentChanges) { - CollectionReference collection = Collection(); - Query query = collection.OrderBy("a"); - - DocumentReference doc1 = collection.Document("1"); - DocumentReference doc2 = collection.Document("2"); - - TestEventListener listener("TestDocumentChanges"); - ListenerRegistration registration = listener.AttachTo(&query); - Await(listener); - QuerySnapshot snapshot = listener.last_result(); - EXPECT_EQ(snapshot.size(), 0); - - WriteDocument(doc1, MapFieldValue{{"a", FieldValue::Integer(1)}}); - Await(listener); - snapshot = listener.last_result(); - - std::vector changes = snapshot.DocumentChanges(); - EXPECT_EQ(changes.size(), 1); - - EXPECT_EQ(changes[0].type(), DocumentChange::Type::kAdded); - EXPECT_EQ(changes[0].document().id(), doc1.id()); - EXPECT_EQ(changes[0].old_index(), DocumentChange::npos); - EXPECT_EQ(changes[0].new_index(), 0); - - WriteDocument(doc2, MapFieldValue{{"a", FieldValue::Integer(2)}}); - Await(listener, 2); - snapshot = listener.last_result(); - - changes = snapshot.DocumentChanges(); - EXPECT_EQ(changes.size(), 1); - EXPECT_EQ(changes[0].type(), DocumentChange::Type::kAdded); - EXPECT_EQ(changes[0].document().id(), doc2.id()); - EXPECT_EQ(changes[0].old_index(), DocumentChange::npos); - EXPECT_EQ(changes[0].new_index(), 1); - - // Make doc2 ordered before doc1. - WriteDocument(doc2, MapFieldValue{{"a", FieldValue::Integer(0)}}); - Await(listener, 3); - snapshot = listener.last_result(); - - changes = snapshot.DocumentChanges(); - EXPECT_EQ(changes.size(), 1); - EXPECT_EQ(changes[0].type(), DocumentChange::Type::kModified); - EXPECT_EQ(changes[0].document().id(), doc2.id()); - EXPECT_EQ(changes[0].old_index(), 1); - EXPECT_EQ(changes[0].new_index(), 0); -} - -#endif // defined(__ANDROID__) || defined(FIRESTORE_STUB_BUILD) - -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/document_reference_test.cc b/firestore/src/tests/document_reference_test.cc deleted file mode 100644 index dcc0a8d5ff..0000000000 --- a/firestore/src/tests/document_reference_test.cc +++ /dev/null @@ -1,56 +0,0 @@ -#include - -#include "firestore/src/common/wrapper_assertions.h" -#include "firestore/src/include/firebase/firestore.h" -#include "firestore/src/tests/firestore_integration_test.h" -#include "testing/base/public/gmock.h" -#include "gtest/gtest.h" - -#if defined(__ANDROID__) -#include "firestore/src/android/converter_android.h" -#include "firestore/src/android/document_reference_android.h" -#elif defined(FIRESTORE_STUB_BUILD) -#include "firestore/src/stub/document_reference_stub.h" -#endif // defined(__ANDROID__) - -namespace firebase { -namespace firestore { - -#if defined(__ANDROID__) || defined(FIRESTORE_STUB_BUILD) - -using DocumentReferenceTest = FirestoreIntegrationTest; - -TEST_F(DocumentReferenceTest, Construction) { - testutil::AssertWrapperConstructionContract(); -} - -TEST_F(DocumentReferenceTest, Assignment) { - testutil::AssertWrapperAssignmentContract(); -} - -#endif // defined(__ANDROID__) || defined(FIRESTORE_STUB_BUILD) - -#if defined(__ANDROID__) - -TEST_F(DocumentReferenceTest, RecoverFirestore) { - jni::Env env = FirestoreInternal::GetEnv(); - - DocumentReference result = - DocumentReferenceInternal::Create(env, jni::Object()); - EXPECT_FALSE(result.is_valid()); - - Firestore* db = TestFirestore(); - DocumentReference doc = Document(); - ASSERT_EQ(db, doc.firestore()); // Sanity check - - jni::Object doc_java = GetInternal(doc)->ToJava(); - result = DocumentReferenceInternal::Create(env, doc_java); - ASSERT_EQ(db, result.firestore()); -} - -#endif // defined(__ANDROID__) - -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/document_snapshot_test.cc b/firestore/src/tests/document_snapshot_test.cc deleted file mode 100644 index 5c995b734b..0000000000 --- a/firestore/src/tests/document_snapshot_test.cc +++ /dev/null @@ -1,35 +0,0 @@ -#include - -#include "firestore/src/include/firebase/firestore.h" -#if defined(__ANDROID__) -#include "firestore/src/android/document_snapshot_android.h" -#include "firestore/src/common/wrapper_assertions.h" -#elif defined(FIRESTORE_STUB_BUILD) -#include "firestore/src/common/wrapper_assertions.h" -#include "firestore/src/stub/document_snapshot_stub.h" -#endif // defined(__ANDROID__) - -#include "testing/base/public/gmock.h" -#include "gtest/gtest.h" - -namespace firebase { -namespace firestore { - -#if defined(__ANDROID__) || defined(FIRESTORE_STUB_BUILD) - -using DocumentSnapshotTest = testing::Test; - -TEST_F(DocumentSnapshotTest, Construction) { - testutil::AssertWrapperConstructionContract(); -} - -TEST_F(DocumentSnapshotTest, Assignment) { - testutil::AssertWrapperAssignmentContract(); -} - -#endif - -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/field_value_test.cc b/firestore/src/tests/field_value_test.cc deleted file mode 100644 index f0ffccaa35..0000000000 --- a/firestore/src/tests/field_value_test.cc +++ /dev/null @@ -1,359 +0,0 @@ -#include - -#include "firestore/src/include/firebase/firestore.h" -#include "firestore/src/tests/firestore_integration_test.h" -#if defined(__ANDROID__) -#include "firestore/src/android/field_value_android.h" -#include "firestore/src/common/wrapper_assertions.h" -#elif defined(FIRESTORE_STUB_BUILD) -#include "firestore/src/stub/field_value_stub.h" -#else -#include "firestore/src/ios/field_value_ios.h" -#endif // defined(__ANDROID__) - -#include "testing/base/public/gmock.h" -#include "gtest/gtest.h" - -namespace firebase { -namespace firestore { - -using Type = FieldValue::Type; -using FieldValueTest = FirestoreIntegrationTest; - -// Sanity test for stubs -TEST_F(FieldValueTest, TestFieldValueTypes) { - ASSERT_NO_THROW({ - FieldValue::Null(); - FieldValue::Boolean(true); - FieldValue::Integer(123L); - FieldValue::Double(3.1415926); - FieldValue::Timestamp({12345, 54321}); - FieldValue::String("hello"); - uint8_t blob[] = "( ͡° ͜ʖ ͡°)"; - FieldValue::Blob(blob, sizeof(blob)); - FieldValue::GeoPoint({43, 80}); - FieldValue::Array(std::vector{FieldValue::Null()}); - FieldValue::Map(MapFieldValue{{"Null", FieldValue::Null()}}); - FieldValue::Delete(); - FieldValue::ServerTimestamp(); - FieldValue::ArrayUnion(std::vector{FieldValue::Null()}); - FieldValue::ArrayRemove(std::vector{FieldValue::Null()}); - }); -} - -#if defined(__ANDROID__) || defined(FIRESTORE_STUB_BUILD) - -TEST_F(FieldValueTest, Construction) { - testutil::AssertWrapperConstructionContract(); -} - -TEST_F(FieldValueTest, Assignment) { - testutil::AssertWrapperAssignmentContract(); -} - -#endif // defined(__ANDROID__) || defined(FIRESTORE_STUB_BUILD) - -#if !defined(FIRESTORE_STUB_BUILD) - -TEST_F(FieldValueTest, TestNullType) { - FieldValue value = FieldValue::Null(); - EXPECT_EQ(Type::kNull, value.type()); -} - -TEST_F(FieldValueTest, TestBooleanType) { - FieldValue value = FieldValue::Boolean(true); - EXPECT_EQ(Type::kBoolean, value.type()); - EXPECT_EQ(true, value.boolean_value()); -} - -TEST_F(FieldValueTest, TestIntegerType) { - FieldValue value = FieldValue::Integer(123); - EXPECT_EQ(Type::kInteger, value.type()); - EXPECT_EQ(123, value.integer_value()); -} - -TEST_F(FieldValueTest, TestDoubleType) { - FieldValue value = FieldValue::Double(3.1415926); - EXPECT_EQ(Type::kDouble, value.type()); - EXPECT_EQ(3.1415926, value.double_value()); -} - -TEST_F(FieldValueTest, TestTimestampType) { - FieldValue value = FieldValue::Timestamp({12345, 54321}); - EXPECT_EQ(Type::kTimestamp, value.type()); - EXPECT_EQ(Timestamp(12345, 54321), value.timestamp_value()); -} - -TEST_F(FieldValueTest, TestStringType) { - FieldValue value = FieldValue::String("hello"); - EXPECT_EQ(Type::kString, value.type()); - EXPECT_STREQ("hello", value.string_value().c_str()); -} - -TEST_F(FieldValueTest, TestStringTypeSpecialCases) { - // Latin small letter e with acute accent. Codepoints above 7F are encoded - // in multiple bytes. - std::string str = u8"\u00E9clair"; - EXPECT_EQ(FieldValue::String(str).string_value(), str); - - // Latin small letter e + combining acute accent. Similar to above but using - // a combining character, which is not normalized. - str = u8"e\u0301clair"; - EXPECT_EQ(FieldValue::String(str).string_value(), str); - - // Face with tears of joy. This is an emoji outside the BMP and encodes as - // four bytes in UTF-8 and as a surrogate pair in UTF-16. JNI's modified UTF-8 - // encodes each surrogate as a separate three byte value for a total of six - // bytes. - str = u8"\U0001F602!!"; - EXPECT_EQ(FieldValue::String(str).string_value(), str); - - // Embedded null character. JNI's modified UTF-8 encoding encodes this in a - // two byte sequence that doesn't contain a zero byte. - str = u8"aaa"; - str[1] = '\0'; - FieldValue value = FieldValue::String(str); - EXPECT_EQ(value.string_value(), str); - EXPECT_STREQ(value.string_value().c_str(), "a"); -} - -TEST_F(FieldValueTest, TestBlobType) { - uint8_t blob[] = "( ͡° ͜ʖ ͡°)"; - FieldValue value = FieldValue::Blob(blob, sizeof(blob)); - EXPECT_EQ(Type::kBlob, value.type()); - EXPECT_EQ(sizeof(blob), value.blob_size()); - const uint8_t* value_blob = value.blob_value(); - - FieldValue copied(value); - EXPECT_EQ(Type::kBlob, copied.type()); - EXPECT_EQ(sizeof(blob), copied.blob_size()); - const uint8_t* copied_blob = copied.blob_value(); - - for (int i = 0; i < sizeof(blob); ++i) { - EXPECT_EQ(blob[i], value_blob[i]); - EXPECT_EQ(blob[i], copied_blob[i]); - } -} - -TEST_F(FieldValueTest, TestReferenceType) { - FieldValue value = - FieldValue::Reference(TestFirestore()->Document("foo/bar")); - EXPECT_EQ(Type::kReference, value.type()); - EXPECT_EQ(value.reference_value().path(), "foo/bar"); -} - -TEST_F(FieldValueTest, TestGeoPointType) { - FieldValue value = FieldValue::GeoPoint({43, 80}); - EXPECT_EQ(Type::kGeoPoint, value.type()); - EXPECT_EQ(GeoPoint(43, 80), value.geo_point_value()); -} - -TEST_F(FieldValueTest, TestArrayType) { - FieldValue value = FieldValue::Array( - {FieldValue::Boolean(true), FieldValue::Integer(123)}); - EXPECT_EQ(Type::kArray, value.type()); - const std::vector& array = value.array_value(); - EXPECT_EQ(2, array.size()); - EXPECT_EQ(true, array[0].boolean_value()); - EXPECT_EQ(123, array[1].integer_value()); -} - -TEST_F(FieldValueTest, TestMapType) { - FieldValue value = - FieldValue::Map(MapFieldValue{{"Bool", FieldValue::Boolean(true)}, - {"Int", FieldValue::Integer(123)}}); - EXPECT_EQ(Type::kMap, value.type()); - MapFieldValue map = value.map_value(); - EXPECT_EQ(2, map.size()); - EXPECT_EQ(true, map["Bool"].boolean_value()); - EXPECT_EQ(123, map["Int"].integer_value()); -} - -TEST_F(FieldValueTest, TestSentinelType) { - FieldValue delete_value = FieldValue::Delete(); - EXPECT_EQ(Type::kDelete, delete_value.type()); - - FieldValue server_timestamp_value = FieldValue::ServerTimestamp(); - EXPECT_EQ(Type::kServerTimestamp, server_timestamp_value.type()); - - std::vector array = {FieldValue::Boolean(true), - FieldValue::Integer(123)}; - FieldValue array_union = FieldValue::ArrayUnion(array); - EXPECT_EQ(Type::kArrayUnion, array_union.type()); - FieldValue array_remove = FieldValue::ArrayRemove(array); - EXPECT_EQ(Type::kArrayRemove, array_remove.type()); - - FieldValue increment_integer = FieldValue::Increment(1); - EXPECT_EQ(Type::kIncrementInteger, increment_integer.type()); - - FieldValue increment_double = FieldValue::Increment(1.0); - EXPECT_EQ(Type::kIncrementDouble, increment_double.type()); -} - -TEST_F(FieldValueTest, TestEquality) { - EXPECT_EQ(FieldValue::Null(), FieldValue::Null()); - EXPECT_EQ(FieldValue::Boolean(true), FieldValue::Boolean(true)); - EXPECT_EQ(FieldValue::Integer(123), FieldValue::Integer(123)); - EXPECT_EQ(FieldValue::Double(456.0), FieldValue::Double(456.0)); - EXPECT_EQ(FieldValue::String("foo"), FieldValue::String("foo")); - - EXPECT_EQ(FieldValue::Timestamp({123, 456}), - FieldValue::Timestamp({123, 456})); - - uint8_t blob[] = "( ͡° ͜ʖ ͡°)"; - EXPECT_EQ(FieldValue::Blob(blob, sizeof(blob)), - FieldValue::Blob(blob, sizeof(blob))); - - EXPECT_EQ(FieldValue::GeoPoint({43, 80}), FieldValue::GeoPoint({43, 80})); - - EXPECT_EQ( - FieldValue::Array({FieldValue::Integer(3), FieldValue::Double(4.0)}), - FieldValue::Array({FieldValue::Integer(3), FieldValue::Double(4.0)})); - - EXPECT_EQ(FieldValue::Map(MapFieldValue{{"foo", FieldValue::Integer(3)}}), - FieldValue::Map(MapFieldValue{{"foo", FieldValue::Integer(3)}})); - - EXPECT_EQ(FieldValue::Delete(), FieldValue::Delete()); - EXPECT_EQ(FieldValue::ServerTimestamp(), FieldValue::ServerTimestamp()); - // TODO(varconst): make this work on Android, or remove the tests below. - // EXPECT_EQ(FieldValue::ArrayUnion({FieldValue::Null()}), - // FieldValue::ArrayUnion({FieldValue::Null()})); - // EXPECT_EQ(FieldValue::ArrayRemove({FieldValue::Null()}), - // FieldValue::ArrayRemove({FieldValue::Null()})); -} - -TEST_F(FieldValueTest, TestInequality) { - EXPECT_NE(FieldValue::Boolean(false), FieldValue::Boolean(true)); - EXPECT_NE(FieldValue::Integer(123), FieldValue::Integer(456)); - EXPECT_NE(FieldValue::Double(123.0), FieldValue::Double(456.0)); - EXPECT_NE(FieldValue::String("foo"), FieldValue::String("bar")); - - EXPECT_NE(FieldValue::Timestamp({123, 456}), - FieldValue::Timestamp({789, 123})); - - uint8_t blob1[] = "( ͡° ͜ʖ ͡°)"; - uint8_t blob2[] = "___"; - EXPECT_NE(FieldValue::Blob(blob1, sizeof(blob2)), - FieldValue::Blob(blob2, sizeof(blob2))); - - EXPECT_NE(FieldValue::GeoPoint({43, 80}), FieldValue::GeoPoint({12, 34})); - - EXPECT_NE( - FieldValue::Array({FieldValue::Integer(3), FieldValue::Double(4.0)}), - FieldValue::Array({FieldValue::Integer(5), FieldValue::Double(4.0)})); - - EXPECT_NE(FieldValue::Map(MapFieldValue{{"foo", FieldValue::Integer(3)}}), - FieldValue::Map(MapFieldValue{{"foo", FieldValue::Integer(4)}})); - - EXPECT_NE(FieldValue::Delete(), FieldValue::ServerTimestamp()); - EXPECT_NE(FieldValue::ArrayUnion({FieldValue::Null()}), - FieldValue::ArrayUnion({FieldValue::Boolean(false)})); - EXPECT_NE(FieldValue::ArrayRemove({FieldValue::Null()}), - FieldValue::ArrayRemove({FieldValue::Boolean(false)})); -} - -TEST_F(FieldValueTest, TestInequalityDueToDifferentTypes) { - EXPECT_NE(FieldValue::Null(), FieldValue::Delete()); - EXPECT_NE(FieldValue::Integer(1), FieldValue::Boolean(true)); - EXPECT_NE(FieldValue::Integer(123), FieldValue::Double(123)); - EXPECT_NE(FieldValue::ArrayUnion({FieldValue::Null()}), - FieldValue::ArrayRemove({FieldValue::Null()})); - EXPECT_NE(FieldValue::Array({FieldValue::Null()}), - FieldValue::ArrayRemove({FieldValue::Null()})); - // Fully exhaustive check seems overkill, just check the types that are known - // to have the same (or very similar) representation. -} - -TEST_F(FieldValueTest, TestToString) { - EXPECT_EQ("", FieldValue().ToString()); - - EXPECT_EQ("null", FieldValue::Null().ToString()); - EXPECT_EQ("true", FieldValue::Boolean(true).ToString()); - EXPECT_EQ("123", FieldValue::Integer(123L).ToString()); - EXPECT_EQ("3.14", FieldValue::Double(3.14).ToString()); - EXPECT_EQ("Timestamp(seconds=12345, nanoseconds=54321)", - FieldValue::Timestamp({12345, 54321}).ToString()); - EXPECT_EQ("'hello'", FieldValue::String("hello").ToString()); - uint8_t blob[] = "( ͡° ͜ʖ ͡°)"; - EXPECT_EQ("Blob(28 20 cd a1 c2 b0 20 cd 9c ca 96 20 cd a1 c2 b0 29 00)", - FieldValue::Blob(blob, sizeof(blob)).ToString()); - EXPECT_EQ("GeoPoint(latitude=43, longitude=80)", - FieldValue::GeoPoint({43, 80}).ToString()); - - EXPECT_EQ("DocumentReference(invalid)", FieldValue::Reference({}).ToString()); - - EXPECT_EQ("[]", FieldValue::Array({}).ToString()); - EXPECT_EQ("[null]", FieldValue::Array({FieldValue::Null()}).ToString()); - EXPECT_EQ("[null, true, 1]", - FieldValue::Array({FieldValue::Null(), FieldValue::Boolean(true), - FieldValue::Integer(1)}) - .ToString()); - // TODO(b/150016438): uncomment this case (fails on Android). - // EXPECT_EQ("[]", FieldValue::Array({FieldValue()}).ToString()); - - EXPECT_EQ("{}", FieldValue::Map({}).ToString()); - // TODO(b/150016438): uncomment this case (fails on Android). - // EXPECT_EQ("{bad: }", FieldValue::Map({ - // {"bad", - // FieldValue()}, - // }) - // .ToString()); - EXPECT_EQ("{Null: null}", FieldValue::Map({ - {"Null", FieldValue::Null()}, - }) - .ToString()); - // Note: because the map is unordered, it's hard to check the case where a map - // has more than one element. - - EXPECT_EQ("FieldValue::Delete()", FieldValue::Delete().ToString()); - EXPECT_EQ("FieldValue::ServerTimestamp()", - FieldValue::ServerTimestamp().ToString()); - EXPECT_EQ("FieldValue::ArrayUnion()", - FieldValue::ArrayUnion({FieldValue::Null()}).ToString()); - EXPECT_EQ("FieldValue::ArrayRemove()", - FieldValue::ArrayRemove({FieldValue::Null()}).ToString()); - - EXPECT_EQ("FieldValue::Increment()", FieldValue::Increment(1).ToString()); - EXPECT_EQ("FieldValue::Increment()", FieldValue::Increment(1.0).ToString()); -} - -TEST_F(FieldValueTest, TestIncrementChoosesTheCorrectType) { - // Signed integers - // NOLINTNEXTLINE -- exact integer width doesn't matter. - short foo = 1; - EXPECT_EQ(FieldValue::Increment(foo).type(), Type::kIncrementInteger); - EXPECT_EQ(FieldValue::Increment(1).type(), Type::kIncrementInteger); - EXPECT_EQ(FieldValue::Increment(1L).type(), Type::kIncrementInteger); - // Note: using `long long` syntax to avoid go/lsc-long-long-literal. - // NOLINTNEXTLINE -- exact integer width doesn't matter. - long long llfoo = 1; - EXPECT_EQ(FieldValue::Increment(llfoo).type(), Type::kIncrementInteger); - - // Unsigned integers - // NOLINTNEXTLINE -- exact integer width doesn't matter. - unsigned short ufoo = 1; - EXPECT_EQ(FieldValue::Increment(ufoo).type(), Type::kIncrementInteger); - EXPECT_EQ(FieldValue::Increment(1U).type(), Type::kIncrementInteger); - - // Floating point - EXPECT_EQ(FieldValue::Increment(1.0f).type(), Type::kIncrementDouble); - EXPECT_EQ(FieldValue::Increment(1.0).type(), Type::kIncrementDouble); - - // The statements below shouldn't compile (uncomment to check). - - // Types that would lead to truncation: - // EXPECT_EQ(FieldValue::Increment(1UL).type(), Type::kIncrementInteger); - // unsigned long long ullfoo = 1; - // EXPECT_EQ(FieldValue::Increment(ullfoo).type(), Type::kIncrementInteger); - // EXPECT_EQ(FieldValue::Increment(1.0L).type(), Type::kIncrementDouble); - - // Inapplicable types: - // EXPECT_EQ(FieldValue::Increment(true).type(), Type::kIncrementInteger); - // EXPECT_EQ(FieldValue::Increment('a').type(), Type::kIncrementInteger); - // EXPECT_EQ(FieldValue::Increment("abc").type(), Type::kIncrementInteger); -} - -#endif // !defined(FIRESTORE_STUB_BUILD) - -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/fields_test.cc b/firestore/src/tests/fields_test.cc deleted file mode 100644 index 76e6c3bcce..0000000000 --- a/firestore/src/tests/fields_test.cc +++ /dev/null @@ -1,229 +0,0 @@ -#include -#include -#include -#include - -#include "firestore/src/include/firebase/firestore.h" -#include "firestore/src/tests/firestore_integration_test.h" -#include "testing/base/public/gmock.h" -#include "gtest/gtest.h" - -// These test cases are in sync with native iOS client SDK test -// Firestore/Example/Tests/Integration/API/FIRFieldsTests.mm -// and native Android client SDK test -// firebase_firestore/tests/integration_tests/src/com/google/firebase/firestore/FieldsTest.java -// except we do not port the tests for legacy timestamp behavior. C++ SDK does -// not support the legacy timestamp behavior. - -namespace firebase { -namespace firestore { - -class FieldsTest : public FirestoreIntegrationTest { - protected: - /** - * Creates test data with nested fields. - */ - MapFieldValue NestedData(int number) { - char buffer[32]; - MapFieldValue result; - - snprintf(buffer, sizeof(buffer), "room %d", number); - result["name"] = FieldValue::String(buffer); - - MapFieldValue nested; - nested["createdAt"] = FieldValue::Integer(number); - MapFieldValue deep_nested; - snprintf(buffer, sizeof(buffer), "deep-field-%d", number); - deep_nested["field"] = FieldValue::String(buffer); - nested["deep"] = FieldValue::Map(deep_nested); - result["metadata"] = FieldValue::Map(nested); - - return result; - } - - /** - * Creates test data with special characters in field names. Datastore - * currently prohibits mixing nested data with special characters so tests - * that use this data must be separate. - */ - MapFieldValue DottedData(int number) { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "field %d", number); - - return {{"a", FieldValue::String(buffer)}, - {"b.dot", FieldValue::Integer(number)}, - {"c\\slash", FieldValue::Integer(number)}}; - } - - /** - * Creates test data with Timestamp. - */ - MapFieldValue DataWithTimestamp(Timestamp timestamp) { - return { - {"timestamp", FieldValue::Timestamp(timestamp)}, - {"nested", - FieldValue::Map({{"timestamp2", FieldValue::Timestamp(timestamp)}})}}; - } -}; - -TEST_F(FieldsTest, TestNestedFieldsCanBeWrittenWithSet) { - DocumentReference doc = Document(); - WriteDocument(doc, NestedData(1)); - EXPECT_THAT(ReadDocument(doc).GetData(), testing::ContainerEq(NestedData(1))); -} - -TEST_F(FieldsTest, TestNestedFieldsCanBeReadDirectly) { - DocumentReference doc = Document(); - WriteDocument(doc, NestedData(1)); - DocumentSnapshot snapshot = ReadDocument(doc); - - MapFieldValue expected = NestedData(1); - EXPECT_EQ(expected["name"].string_value(), - snapshot.Get("name").string_value()); - EXPECT_EQ(expected["metadata"].map_value(), - snapshot.Get("metadata").map_value()); - EXPECT_EQ(expected["metadata"] - .map_value()["deep"] - .map_value()["field"] - .string_value(), - snapshot.Get("metadata.deep.field").string_value()); - EXPECT_FALSE(snapshot.Get("metadata.nofield").is_valid()); - EXPECT_FALSE(snapshot.Get("nometadata.nofield").is_valid()); -} - -TEST_F(FieldsTest, TestNestedFieldsCanBeReadDirectlyViaFieldPath) { - DocumentReference doc = Document(); - WriteDocument(doc, NestedData(1)); - DocumentSnapshot snapshot = ReadDocument(doc); - - MapFieldValue expected = NestedData(1); - EXPECT_EQ(expected["name"].string_value(), - snapshot.Get(FieldPath{"name"}).string_value()); - EXPECT_EQ(expected["metadata"].map_value(), - snapshot.Get(FieldPath{"metadata"}).map_value()); - EXPECT_EQ( - expected["metadata"] - .map_value()["deep"] - .map_value()["field"] - .string_value(), - snapshot.Get(FieldPath{"metadata", "deep", "field"}).string_value()); - EXPECT_FALSE(snapshot.Get(FieldPath{"metadata", "nofield"}).is_valid()); - EXPECT_FALSE(snapshot.Get(FieldPath{"nometadata", "nofield"}).is_valid()); -} - -TEST_F(FieldsTest, TestNestedFieldsCanBeUpdated) { - DocumentReference doc = Document(); - WriteDocument(doc, NestedData(1)); - UpdateDocument( - doc, MapFieldValue{{"metadata.deep.field", FieldValue::Integer(100)}, - {"metadata.added", FieldValue::Integer(200)}}); - EXPECT_THAT(ReadDocument(doc).GetData(), - testing::ContainerEq(MapFieldValue( - {{"name", FieldValue::String("room 1")}, - {"metadata", - FieldValue::Map( - {{"createdAt", FieldValue::Integer(1)}, - {"deep", FieldValue::Map( - {{"field", FieldValue::Integer(100)}})}, - {"added", FieldValue::Integer(200)}})}}))); -} - -TEST_F(FieldsTest, TestNestedFieldsCanBeUsedInQueryFilters) { - CollectionReference collection = Collection( - {{"1", NestedData(300)}, {"2", NestedData(100)}, {"3", NestedData(200)}}); - QuerySnapshot snapshot = ReadDocuments(collection.WhereGreaterThanOrEqualTo( - "metadata.createdAt", FieldValue::Integer(200))); - // inequality adds implicit sort on field - EXPECT_THAT(QuerySnapshotToValues(snapshot), - testing::ElementsAre(NestedData(200), NestedData(300))); -} - -TEST_F(FieldsTest, TestNestedFieldsCanBeUsedInOrderBy) { - CollectionReference collection = Collection( - {{"1", NestedData(300)}, {"2", NestedData(100)}, {"3", NestedData(200)}}); - QuerySnapshot snapshot = - ReadDocuments(collection.OrderBy("metadata.createdAt")); - EXPECT_THAT( - QuerySnapshotToValues(snapshot), - testing::ElementsAre(NestedData(100), NestedData(200), NestedData(300))); -} - -TEST_F(FieldsTest, TestFieldsWithSpecialCharsCanBeWrittenWithSet) { - DocumentReference doc = Document(); - WriteDocument(doc, DottedData(1)); - EXPECT_EQ(DottedData(1), ReadDocument(doc).GetData()); -} - -TEST_F(FieldsTest, TestFieldsWithSpecialCharsCanBeReadDirectly) { - DocumentReference doc = Document(); - WriteDocument(doc, DottedData(1)); - DocumentSnapshot snapshot = ReadDocument(doc); - - MapFieldValue expected = DottedData(1); - EXPECT_EQ(expected["a"].string_value(), snapshot.Get("a").string_value()); - EXPECT_EQ(expected["b.dot"].integer_value(), - snapshot.GetData()["b.dot"].integer_value()); - EXPECT_EQ(expected["c\\slash"].integer_value(), - snapshot.GetData()["c\\slash"].integer_value()); -} - -TEST_F(FieldsTest, TestFieldsWithSpecialCharsCanBeUpdated) { - DocumentReference doc = Document(); - WriteDocument(doc, DottedData(1)); - UpdateDocument(doc, MapFieldPathValue{ - {FieldPath{"b.dot"}, FieldValue::Integer(100)}, - {FieldPath{"c\\slash"}, FieldValue::Integer(200)}}); - DocumentSnapshot snapshot = ReadDocument(doc); - EXPECT_THAT(ReadDocument(doc).GetData(), - testing::ContainerEq( - MapFieldValue({{"a", FieldValue::String("field 1")}, - {"b.dot", FieldValue::Integer(100)}, - {"c\\slash", FieldValue::Integer(200)}}))); -} - -TEST_F(FieldsTest, TestFieldsWithSpecialCharsCanBeUsedInQueryFilters) { - CollectionReference collection = Collection( - {{"1", DottedData(300)}, {"2", DottedData(100)}, {"3", DottedData(200)}}); - QuerySnapshot snapshot = ReadDocuments(collection.WhereGreaterThanOrEqualTo( - FieldPath{"b.dot"}, FieldValue::Integer(200))); - // inequality adds implicit sort on field - EXPECT_THAT(QuerySnapshotToValues(snapshot), - testing::ElementsAre(DottedData(200), DottedData(300))); -} - -TEST_F(FieldsTest, TestFieldsWithSpecialCharsCanBeUsedInOrderBy) { - CollectionReference collection = Collection( - {{"1", DottedData(300)}, {"2", DottedData(100)}, {"3", DottedData(200)}}); - QuerySnapshot snapshot = - ReadDocuments(collection.OrderBy(FieldPath{"b.dot"})); - EXPECT_THAT( - QuerySnapshotToValues(snapshot), - testing::ElementsAre(DottedData(100), DottedData(200), DottedData(300))); -} - -TEST_F(FieldsTest, TestTimestampsInSnapshots) { - Timestamp original_timestamp{100, 123456789}; - // Timestamps are currently truncated to microseconds after being written to - // the database. - Timestamp truncated_timestamp{100, 123456000}; - - DocumentReference doc = Document(); - WriteDocument(doc, DataWithTimestamp(original_timestamp)); - DocumentSnapshot snapshot = ReadDocument(doc); - MapFieldValue data = snapshot.GetData(); - - Timestamp timestamp_from_snapshot = - snapshot.Get("timestamp").timestamp_value(); - Timestamp timestamp_from_data = data["timestamp"].timestamp_value(); - EXPECT_EQ(truncated_timestamp, timestamp_from_data); - EXPECT_EQ(timestamp_from_snapshot, timestamp_from_data); - - timestamp_from_snapshot = snapshot.Get("nested.timestamp2").timestamp_value(); - timestamp_from_data = - data["nested"].map_value()["timestamp2"].timestamp_value(); - EXPECT_EQ(truncated_timestamp, timestamp_from_data); - EXPECT_EQ(timestamp_from_snapshot, timestamp_from_data); -} - -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/firestore_integration_test.cc b/firestore/src/tests/firestore_integration_test.cc deleted file mode 100644 index 8f3b2c1be2..0000000000 --- a/firestore/src/tests/firestore_integration_test.cc +++ /dev/null @@ -1,289 +0,0 @@ -#include "firestore/src/tests/firestore_integration_test.h" - -#include -#include -#include - -#include "absl/strings/ascii.h" -#include "Firestore/core/src/util/autoid.h" - -namespace firebase { -namespace firestore { - -namespace { -// name of FirebaseApp to use for bootstrapping data into Firestore. We use a -// non-default app to avoid data ending up in the cache before tests run. -static const char* kBootstrapAppName = "bootstrap"; - -// Set Firestore up to use Firestore Emulator if it can be found. -void LocateEmulator(Firestore* db) { - // iOS and Android pass emulator address differently, iOS writes it to a - // temp file, but there is no equivalent to `/tmp/` for Android, so it - // uses an environment variable instead. - // TODO(wuandy): See if we can use environment variable for iOS as well? - std::ifstream ifs("/tmp/emulator_address"); - std::stringstream buffer; - buffer << ifs.rdbuf(); - std::string address; - if (ifs.good()) { - address = buffer.str(); - } else if (std::getenv("FIRESTORE_EMULATOR_HOST")) { - address = std::getenv("FIRESTORE_EMULATOR_HOST"); - } - - absl::StripAsciiWhitespace(&address); - if (!address.empty()) { - auto settings = db->settings(); - settings.set_host(address); - // Emulator does not support ssl yet. - settings.set_ssl_enabled(false); - db->set_settings(settings); - } -} - -} // namespace - -std::string ToFirestoreErrorCodeName(int error_code) { - switch (error_code) { - case kErrorOk: - return "kErrorOk"; - case kErrorCancelled: - return "kErrorCancelled"; - case kErrorUnknown: - return "kErrorUnknown"; - case kErrorInvalidArgument: - return "kErrorInvalidArgument"; - case kErrorDeadlineExceeded: - return "kErrorDeadlineExceeded"; - case kErrorNotFound: - return "kErrorNotFound"; - case kErrorAlreadyExists: - return "kErrorAlreadyExists"; - case kErrorPermissionDenied: - return "kErrorPermissionDenied"; - case kErrorResourceExhausted: - return "kErrorResourceExhausted"; - case kErrorFailedPrecondition: - return "kErrorFailedPrecondition"; - case kErrorAborted: - return "kErrorAborted"; - case kErrorOutOfRange: - return "kErrorOutOfRange"; - case kErrorUnimplemented: - return "kErrorUnimplemented"; - case kErrorInternal: - return "kErrorInternal"; - case kErrorUnavailable: - return "kErrorUnavailable"; - case kErrorDataLoss: - return "kErrorDataLoss"; - case kErrorUnauthenticated: - return "kErrorUnauthenticated"; - default: - return "[invalid error code]"; - } -} - -int WaitFor(const FutureBase& future) { - // Instead of getting a clock, we count the cycles instead. - int cycles = kTimeOutMillis / kCheckIntervalMillis; - while (future.status() == FutureStatus::kFutureStatusPending && cycles > 0) { - if (ProcessEvents(kCheckIntervalMillis)) { - std::cout << "WARNING: app receives an event requesting exit." - << std::endl; - break; - } - --cycles; - } - return cycles; -} - -FirestoreIntegrationTest::FirestoreIntegrationTest() { - // Allocate the default Firestore eagerly. - TestFirestore(); -} - -Firestore* FirestoreIntegrationTest::TestFirestore( - const std::string& name) const { - for (const auto& entry : firestores_) { - const FirestoreInfo& firestore_info = entry.second; - if (firestore_info.name() == name) { - return firestore_info.firestore(); - } - } - - App* app = GetApp(name.c_str()); - if (apps_.find(app) == apps_.end()) { - apps_[app] = UniquePtr(app); - } - - Firestore::set_log_level(LogLevel::kLogLevelDebug); - - Firestore* db = new Firestore(CreateTestFirestoreInternal(app)); - firestores_[db] = FirestoreInfo(name, UniquePtr(db)); - - LocateEmulator(db); - InitializeFirestore(db); - - return db; -} - -void FirestoreIntegrationTest::DeleteFirestore(Firestore* firestore) { - auto found = firestores_.find(firestore); - FIREBASE_ASSERT_MESSAGE(found != firestores_.end(), - "The given Firestore was not found."); - firestores_.erase(found); -} - -void FirestoreIntegrationTest::DisownFirestore(Firestore* firestore) { - auto found = firestores_.find(firestore); - FIREBASE_ASSERT_MESSAGE(found != firestores_.end(), - "The given Firestore was not found."); - found->second.ReleaseFirestore(); - firestores_.erase(found); -} - -void FirestoreIntegrationTest::DeleteApp(App* app) { - auto found = apps_.find(app); - FIREBASE_ASSERT_MESSAGE(found != apps_.end(), "The given App was not found."); - - // Remove the Firestore instances from our internal list that are owned by the - // given App. Deleting the App also deletes the Firestore instances created - // via that App; therefore, removing our references to those Firestore - // instances avoids double-deletion and also avoids returning deleted - // Firestore instances from TestFirestore(). - auto firestores_iterator = firestores_.begin(); - while (firestores_iterator != firestores_.end()) { - if (firestores_iterator->first->app() == app) { - firestores_iterator = firestores_.erase(firestores_iterator); - } else { - ++firestores_iterator; - } - } - - apps_.erase(found); -} - -CollectionReference FirestoreIntegrationTest::Collection() const { - return TestFirestore()->Collection(util::CreateAutoId()); -} - -CollectionReference FirestoreIntegrationTest::Collection( - const std::string& name_prefix) const { - return TestFirestore()->Collection(name_prefix + "_" + util::CreateAutoId()); -} - -CollectionReference FirestoreIntegrationTest::Collection( - const std::map& docs) const { - CollectionReference result = Collection(); - WriteDocuments(TestFirestore(kBootstrapAppName)->Collection(result.path()), - docs); - return result; -} - -std::string FirestoreIntegrationTest::DocumentPath() const { - return "test-collection/" + util::CreateAutoId(); -} - -DocumentReference FirestoreIntegrationTest::Document() const { - return TestFirestore()->Document(DocumentPath()); -} - -void FirestoreIntegrationTest::WriteDocument(DocumentReference reference, - const MapFieldValue& data) const { - Future future = reference.Set(data); - Await(future); - FailIfUnsuccessful("WriteDocument", future); -} - -void FirestoreIntegrationTest::WriteDocuments( - CollectionReference reference, - const std::map& data) const { - for (const auto& kv : data) { - WriteDocument(reference.Document(kv.first), kv.second); - } -} - -DocumentSnapshot FirestoreIntegrationTest::ReadDocument( - const DocumentReference& reference) const { - Future future = reference.Get(); - const DocumentSnapshot* result = Await(future); - if (FailIfUnsuccessful("ReadDocument", future)) { - return {}; - } else { - return *result; - } -} - -QuerySnapshot FirestoreIntegrationTest::ReadDocuments( - const Query& reference) const { - Future future = reference.Get(); - const QuerySnapshot* result = Await(future); - if (FailIfUnsuccessful("ReadDocuments", future)) { - return {}; - } else { - return *result; - } -} - -void FirestoreIntegrationTest::DeleteDocument( - DocumentReference reference) const { - Future future = reference.Delete(); - Await(future); - FailIfUnsuccessful("DeleteDocument", future); -} - -std::vector FirestoreIntegrationTest::QuerySnapshotToIds( - const QuerySnapshot& snapshot) const { - std::vector result; - for (const DocumentSnapshot& doc : snapshot.documents()) { - result.push_back(doc.id()); - } - return result; -} - -std::vector FirestoreIntegrationTest::QuerySnapshotToValues( - const QuerySnapshot& snapshot) const { - std::vector result; - for (const DocumentSnapshot& doc : snapshot.documents()) { - result.push_back(doc.GetData()); - } - return result; -} - -/* static */ -void FirestoreIntegrationTest::Await(const Future& future) { - while (future.status() == FutureStatus::kFutureStatusPending) { - if (ProcessEvents(kCheckIntervalMillis)) { - std::cout << "WARNING: app received an event requesting exit." - << std::endl; - break; - } - } -} - -/* static */ -bool FirestoreIntegrationTest::FailIfUnsuccessful(const char* operation, - const FutureBase& future) { - if (future.status() != FutureStatus::kFutureStatusComplete) { - ADD_FAILURE() << operation << " timed out: " << DescribeFailedFuture(future) - << std::endl; - return true; - } else if (future.error() != Error::kErrorOk) { - ADD_FAILURE() << operation << " failed: " << DescribeFailedFuture(future) - << std::endl; - return true; - } else { - return false; - } -} - -/* static */ -std::string FirestoreIntegrationTest::DescribeFailedFuture( - const FutureBase& future) { - return "Future failed: " + ToFirestoreErrorCodeName(future.error()) + " (" + - std::to_string(future.error()) + "): " + future.error_message(); -} - -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/firestore_integration_test.h b/firestore/src/tests/firestore_integration_test.h deleted file mode 100644 index 6129a0b420..0000000000 --- a/firestore/src/tests/firestore_integration_test.h +++ /dev/null @@ -1,339 +0,0 @@ -#ifndef FIREBASE_FIRESTORE_CLIENT_CPP_SRC_TESTS_FIRESTORE_INTEGRATION_TEST_H_ -#define FIREBASE_FIRESTORE_CLIENT_CPP_SRC_TESTS_FIRESTORE_INTEGRATION_TEST_H_ - -#include -#include -#include -#include -#include -#include - -#include "app/memory/unique_ptr.h" -#include "app/meta/move.h" -#include "app/src/assert.h" -#include "app/src/include/firebase/internal/common.h" -#include "app/src/mutex.h" -#include "firestore/src/include/firebase/firestore.h" -#include "gtest/gtest.h" - -namespace firebase { -namespace firestore { - -// The interval between checks for future completion. -const int kCheckIntervalMillis = 100; - -// The timeout of waiting for a Future or a listener. -const int kTimeOutMillis = 15000; - -FirestoreInternal* CreateTestFirestoreInternal(App* app); -void InitializeFirestore(Firestore* instance); - -App* GetApp(); -App* GetApp(const char* name); -bool ProcessEvents(int msec); - -// Converts a Firestore error code to a human-friendly name. The `error_code` -// argument is expected to be an element from the firebase::firestore::Error -// enum, but this function will gracefully handle the case where it is not. -std::string ToFirestoreErrorCodeName(int error_code); - -// Waits for a Future to complete. If a timeout is reached then this method -// returns as if successful; therefore, the caller should verify the status of -// the given Future after this function returns. Returns the number of cycles -// that were left before a timeout would have occurred. -int WaitFor(const FutureBase& future); - -template -class EventAccumulator; - -// An EventListener class for writing tests. This listener counts the number of -// events as well as keeps track of the last result. -template -class TestEventListener : public EventListener { - public: - explicit TestEventListener(std::string name) : name_(std::move(name)) {} - - ~TestEventListener() override {} - - void OnEvent(const T& value, Error error_code, - const std::string& error_message) override { - if (print_debug_info_) { - std::cout << "TestEventListener got: "; - if (error_code == Error::kErrorOk) { - std::cout << &value - << " from_cache=" << value.metadata().is_from_cache() - << " has_pending_write=" - << value.metadata().has_pending_writes() - << " event_count=" << event_count() << std::endl; - } else { - std::cout << "error_code=" << error_code << " error_message=\"" - << error_message << "\" event_count=" << event_count() - << std::endl; - } - } - - MutexLock lock(mutex_); - if (error_code != Error::kErrorOk) { - std::cerr << "ERROR: EventListener " << name_ << " got " << error_code - << std::endl; - if (first_error_code_ == Error::kErrorOk) { - first_error_code_ = error_code; - first_error_message_ = error_message; - } - } - last_results_.push_back(value); - } - - int event_count() const { - MutexLock lock(mutex_); - return static_cast(last_results_.size()); - } - - const T& last_result(int i = 0) { - FIREBASE_ASSERT(i >= 0 && i < last_results_.size()); - MutexLock lock(mutex_); - return last_results_[last_results_.size() - 1 - i]; - } - - // Hides the STLPort-related quirk that `AddSnapshotListener` has different - // signatures depending on whether `std::function` is available. - template - ListenerRegistration AttachTo( - U* ref, MetadataChanges metadata_changes = MetadataChanges::kExclude) { -#if defined(FIREBASE_USE_STD_FUNCTION) - return ref->AddSnapshotListener( - metadata_changes, [this](const T& result, Error error_code, - const std::string& error_message) { - OnEvent(result, error_code, error_message); - }); -#else - return ref->AddSnapshotListener(metadata_changes, this); -#endif - } - - std::string first_error_message() { - MutexLock lock(mutex_); - return first_error_message_; - } - - Error first_error_code() { - MutexLock lock(mutex_); - return first_error_code_; - } - - // Set this to true to print more details for each arrived event for debug. - void set_print_debug_info(bool value) { print_debug_info_ = value; } - - // Copies events from the internal buffer, starting from `start`, up to but - // not including `end`. - std::vector GetEventsInRange(int start, int end) const { - MutexLock lock(mutex_); - FIREBASE_ASSERT(start <= end); - FIREBASE_ASSERT(end <= last_results_.size()); - return std::vector(last_results_.begin() + start, - last_results_.begin() + end); - } - - private: - mutable Mutex mutex_; - - std::string name_; - - // We may want the last N result. So we store all in a vector in the order - // they arrived. - std::vector last_results_; - - // We generally only check to see if there is any error. So we only store the - // first non-OK error, if any. - Error first_error_code_ = Error::kErrorOk; - std::string first_error_message_ = ""; - - bool print_debug_info_ = false; -}; - -// Base class for Firestore integration tests. -// Note it keeps a cache of created Firestore instances, and is thread-unsafe. -class FirestoreIntegrationTest : public testing::Test { - friend class TransactionTester; - - public: - FirestoreIntegrationTest(); - FirestoreIntegrationTest(const FirestoreIntegrationTest&) = delete; - FirestoreIntegrationTest(FirestoreIntegrationTest&&) = delete; - - FirestoreIntegrationTest& operator=(const FirestoreIntegrationTest&) = delete; - FirestoreIntegrationTest& operator=(FirestoreIntegrationTest&&) = delete; - - protected: - App* app() { return TestFirestore()->app(); } - - // Returns a Firestore instance for an app with the given name. - // If this method is invoked again with the same `name`, then the same pointer - // will be returned. The only exception is if the `Firestore` was removed - // from the cache by a call to `DeleteFirestore()` or `DisownFirestore()`, or - // if `DeleteApp()` is called with the `App` of the returned `Firestore`. - Firestore* TestFirestore(const std::string& name = kDefaultAppName) const; - - // Deletes the given `Firestore` instance, which must have been returned by a - // previous invocation of `TestFirestore()`, and removes it from the cache of - // instances returned from `TestFirestore()`. Note that all `Firestore` - // instances returned from `TestFirestore()` will be automatically deleted at - // the end of the test case; therefore, this method is only needed if the test - // requires that the instance be deleted earlier than that. If the given - // `Firestore` instance has already been removed from the cache, such as by a - // previous invocation of this method, then the behavior of this method is - // undefined. - void DeleteFirestore(Firestore* firestore); - - // Relinquishes ownership of the given `Firestore` instance, which must have - // been returned by a previous invocation of `TestFirestore()`, and removes it - // from the cache of instances returned from `TestFirestore()`. Note that all - // `Firestore` instances returned from `TestFirestore()` will be automatically - // deleted at the end of the test case; therefore, this method is only needed - // if the test requires that the instance be excluded from this automatic - // deletion. If the given `Firestore` instance has already been removed from - // the cache, such as by a previous invocation of this method, then the - // behavior of this method is undefined. - void DisownFirestore(Firestore* firestore); - - // Deletes the given `App` instance. The given `App` must have been the `App` - // associated with a `Firestore` instance returned by a previous invocation of - // `TestFirestore()`. Normally the `App` is deleted at the end of the test, so - // this method is only needed if the test requires the App to be deleted - // earlier than that. Any `Firestore` instances that were returned from - // `TestFirestore()` and were associated with the given `App` will be deleted - // as if with `DeleteFirestore()`. - void DeleteApp(App* app); - - // Return a reference to the collection with auto-generated id. - CollectionReference Collection() const; - - // Return a reference to a collection with the path constructed by appending a - // unique id to the given name. - CollectionReference Collection(const std::string& name_prefix) const; - - // Return a reference to the collection with given content. - CollectionReference Collection( - const std::map& docs) const; - - // Return an auto-generated document path under collection "test-collection". - std::string DocumentPath() const; - - // Return a reference to the document with auto-generated id. - DocumentReference Document() const; - - // Write to the specified document and wait for the write to complete. - void WriteDocument(DocumentReference reference, - const MapFieldValue& data) const; - - // Write to the specified documents to a collection and wait for completion. - void WriteDocuments(CollectionReference reference, - const std::map& data) const; - - // Update the specified document and wait for the update to complete. - template - void UpdateDocument(DocumentReference reference, const MapType& data) const { - Future future = reference.Update(data); - Await(future); - EXPECT_EQ(FutureStatus::kFutureStatusComplete, future.status()); - EXPECT_EQ(0, future.error()) << DescribeFailedFuture(future) << std::endl; - } - - // Read the specified document. - DocumentSnapshot ReadDocument(const DocumentReference& reference) const; - - // Read documents in the specified collection / query. - QuerySnapshot ReadDocuments(const Query& reference) const; - - // Delete the specified document. - void DeleteDocument(DocumentReference reference) const; - - // Convert a QuerySnapshot to the id of each document. - std::vector QuerySnapshotToIds( - const QuerySnapshot& snapshot) const; - - // Convert a QuerySnapshot to the contents of each document. - std::vector QuerySnapshotToValues( - const QuerySnapshot& snapshot) const; - - // TODO(zxu): add a helper function to block on signal. - - // A helper function to block until the future completes. - template - static const T* Await(const Future& future) { - int cycles = WaitFor(future); - EXPECT_GT(cycles, 0) << "Waiting future timed out."; - if (future.status() == FutureStatus::kFutureStatusComplete) { - if (future.result() == nullptr) { - std::cout << "WARNING: " << DescribeFailedFuture(future) << std::endl; - } - } else { - std::cout << "WARNING: Future is not completed." << std::endl; - } - return future.result(); - } - - static void Await(const Future& future); - - // A helper function to block until there is at least n event. - template - static void Await(const TestEventListener& listener, int n = 1) { - // Instead of getting a clock, we count the cycles instead. - int cycles = kTimeOutMillis / kCheckIntervalMillis; - while (listener.event_count() < n && cycles > 0) { - if (ProcessEvents(kCheckIntervalMillis)) { - std::cout << "WARNING: app receives an event requesting exit." - << std::endl; - return; - } - --cycles; - } - EXPECT_GT(cycles, 0) << "Waiting listener timed out."; - } - - // Fails the current test if the given future did not complete or contained an - // error. Returns true if the future has failed. - static bool FailIfUnsuccessful(const char* operation, - const FutureBase& future); - - static std::string DescribeFailedFuture(const FutureBase& future); - - void DisableNetwork() { Await(TestFirestore()->DisableNetwork()); } - - void EnableNetwork() { Await(TestFirestore()->EnableNetwork()); } - - static FirestoreInternal* GetFirestoreInternal(Firestore* firestore) { - return firestore->internal_; - } - - private: - template - friend class EventAccumulator; - - class FirestoreInfo { - public: - FirestoreInfo() = default; - FirestoreInfo(const std::string& name, UniquePtr&& firestore) - : name_(name), firestore_(Move(firestore)) {} - - const std::string& name() const { return name_; } - Firestore* firestore() const { return firestore_.get(); } - void ReleaseFirestore() { firestore_.release(); } - - private: - std::string name_; - UniquePtr firestore_; - }; - - // The Firestore and App instance caches. - // Note that `firestores_` is intentionally ordered *after* `apps_` so that - // the Firestore pointers will be deleted before the App pointers when this - // object is destructed. - mutable std::unordered_map> apps_; - mutable std::unordered_map firestores_; -}; - -} // namespace firestore -} // namespace firebase - -#endif // FIREBASE_FIRESTORE_CLIENT_CPP_SRC_TESTS_FIRESTORE_INTEGRATION_TEST_H_ diff --git a/firestore/src/tests/firestore_test.cc b/firestore/src/tests/firestore_test.cc deleted file mode 100644 index 0a8e396399..0000000000 --- a/firestore/src/tests/firestore_test.cc +++ /dev/null @@ -1,1562 +0,0 @@ -#include "firestore/src/include/firebase/firestore.h" - -#if !defined(__ANDROID__) -#include // NOLINT(build/c++11) -#endif -#include - -#if !defined(FIRESTORE_STUB_BUILD) -#include "app/src/semaphore.h" -#endif - -#if defined(__ANDROID__) -#include "firestore/src/android/exception_android.h" -#include "firestore/src/android/jni_runnable_android.h" -#include "firestore/src/jni/env.h" -#include "firestore/src/jni/ownership.h" -#include "firestore/src/jni/task.h" -#include "firestore/src/tests/android/firestore_integration_test_android.h" -#endif // defined(__ANDROID__) - -#include "app/memory/unique_ptr.h" -#include "app/src/log.h" -#include "auth/src/include/firebase/auth.h" -#include "firestore/src/common/macros.h" -#include "firestore/src/tests/firestore_integration_test.h" -#include "firestore/src/tests/util/event_accumulator.h" -#include "firestore/src/tests/util/future_test_util.h" -#include "testing/base/public/gmock.h" -#include "gtest/gtest.h" -#include "Firestore/core/src/util/autoid.h" -#include "Firestore/core/src/util/firestore_exceptions.h" - -// These test cases are in sync with native iOS client SDK test -// Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm -// and native Android client SDK test -// firebase_firestore/tests/integration_tests/src/com/google/firebase/firestore/FirestoreTest.java -// Some test cases are named differently between iOS and Android. Here we choose -// the most descriptive names. - -namespace firebase { -namespace firestore { - -using ::firebase::auth::Auth; -using ::testing::ContainerEq; -using ::testing::HasSubstr; - -TEST_F(FirestoreIntegrationTest, GetInstance) { - // Create App. - App* app = this->app(); - EXPECT_NE(nullptr, app); - - // Get an instance. - InitResult result; - Firestore* instance = Firestore::GetInstance(app, &result); - EXPECT_EQ(kInitResultSuccess, result); - EXPECT_NE(nullptr, instance); - EXPECT_EQ(app, instance->app()); - - Auth* auth = Auth::GetAuth(app); - - // Tests normally create instances outside of those managed by - // Firestore::GetInstance. This means that in this case `instance` is a new - // one unmanaged by the test framework. If both the implicit instance and this - // instance were started they would try to use the same underlying database - // and would fail. - delete instance; - - // Firestore calls Auth::GetAuth, which implicitly creates an auth instance. - // Even though app is cleaned up automatically, the Auth instance is not. - // TODO(mcg): Figure out why App's CleanupNotifier doesn't handle Auth. - delete auth; -} - -// Sanity test for stubs. -TEST_F(FirestoreIntegrationTest, TestCanCreateCollectionAndDocumentReferences) { - ASSERT_NO_THROW({ - Firestore* db = TestFirestore(); - CollectionReference c = db->Collection("a/b/c").Document("d").Parent(); - DocumentReference d = db->Document("a/b").Collection("c/d/e").Parent(); - - CollectionReference(c).Document(); - DocumentReference(d).Parent(); - - CollectionReference(std::move(c)).Document(); - DocumentReference(std::move(d)).Parent(); - }); -} - -#if defined(FIRESTORE_STUB_BUILD) - -TEST_F(FirestoreIntegrationTest, TestStubsReturnFailedFutures) { - Firestore* db = TestFirestore(); - Future future = db->EnableNetwork(); - Await(future); - EXPECT_EQ(FutureStatus::kFutureStatusComplete, future.status()); - EXPECT_EQ(Error::kErrorFailedPrecondition, future.error()); - - future = db->Document("foo/bar").Set( - MapFieldValue{{"foo", FieldValue::String("bar")}}); - Await(future); - EXPECT_EQ(FutureStatus::kFutureStatusComplete, future.status()); - EXPECT_EQ(Error::kErrorFailedPrecondition, future.error()); -} - -#else // defined(FIRESTORE_STUB_BUILD) - -TEST_F(FirestoreIntegrationTest, TestCanReadNonExistentDocuments) { - DocumentReference doc = Collection("rooms").Document(); - - DocumentSnapshot snap = ReadDocument(doc); - ASSERT_FALSE(snap.exists()); - EXPECT_THAT(snap.GetData(), ContainerEq(MapFieldValue())); -} - -TEST_F(FirestoreIntegrationTest, TestCanUpdateAnExistingDocument) { - DocumentReference document = Collection("rooms").Document("eros"); - Await(document.Set(MapFieldValue{ - {"desc", FieldValue::String("Description")}, - {"owner", - FieldValue::Map({{"name", FieldValue::String("Jonny")}, - {"email", FieldValue::String("abc@xyz.com")}})}})); - Await(document.Update( - MapFieldValue{{"desc", FieldValue::String("NewDescription")}, - {"owner.email", FieldValue::String("new@xyz.com")}})); - DocumentSnapshot doc = ReadDocument(document); - EXPECT_THAT( - doc.GetData(), - ContainerEq(MapFieldValue{ - {"desc", FieldValue::String("NewDescription")}, - {"owner", - FieldValue::Map({{"name", FieldValue::String("Jonny")}, - {"email", FieldValue::String("new@xyz.com")}})}})); -} - -TEST_F(FirestoreIntegrationTest, TestCanUpdateAnUnknownDocument) { - DocumentReference writer_reference = - TestFirestore("writer")->Collection("collection").Document(); - DocumentReference reader_reference = TestFirestore("reader") - ->Collection("collection") - .Document(writer_reference.id()); - Await(writer_reference.Set(MapFieldValue{{"a", FieldValue::String("a")}})); - Await(reader_reference.Update(MapFieldValue{{"b", FieldValue::String("b")}})); - - DocumentSnapshot writer_snapshot = - *Await(writer_reference.Get(Source::kCache)); - EXPECT_TRUE(writer_snapshot.exists()); - EXPECT_THAT(writer_snapshot.GetData(), - ContainerEq(MapFieldValue{{"a", FieldValue::String("a")}})); - EXPECT_TRUE(writer_snapshot.metadata().is_from_cache()); - - Future future = reader_reference.Get(Source::kCache); - Await(future); - EXPECT_EQ(Error::kErrorUnavailable, future.error()); - - writer_snapshot = ReadDocument(writer_reference); - EXPECT_THAT(writer_snapshot.GetData(), - ContainerEq(MapFieldValue{{"a", FieldValue::String("a")}, - {"b", FieldValue::String("b")}})); - EXPECT_FALSE(writer_snapshot.metadata().is_from_cache()); - DocumentSnapshot reader_snapshot = ReadDocument(reader_reference); - EXPECT_THAT(reader_snapshot.GetData(), - ContainerEq(MapFieldValue{{"a", FieldValue::String("a")}, - {"b", FieldValue::String("b")}})); - EXPECT_FALSE(reader_snapshot.metadata().is_from_cache()); -} - -TEST_F(FirestoreIntegrationTest, TestCanOverwriteAnExistingDocumentUsingSet) { - DocumentReference document = Collection("rooms").Document(); - Await(document.Set(MapFieldValue{ - {"desc", FieldValue::String("Description")}, - {"owner.data", - FieldValue::Map({{"name", FieldValue::String("Jonny")}, - {"email", FieldValue::String("abc@xyz.com")}})}})); - Await(document.Set(MapFieldValue{ - {"updated", FieldValue::Boolean(true)}, - {"owner.data", - FieldValue::Map({{"name", FieldValue::String("Sebastian")}})}})); - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_THAT( - snapshot.GetData(), - ContainerEq(MapFieldValue{ - {"updated", FieldValue::Boolean(true)}, - {"owner.data", - FieldValue::Map({{"name", FieldValue::String("Sebastian")}})}})); -} - -TEST_F(FirestoreIntegrationTest, - TestCanMergeDataWithAnExistingDocumentUsingSet) { - DocumentReference document = Collection("rooms").Document("eros"); - Await(document.Set(MapFieldValue{ - {"desc", FieldValue::String("Description")}, - {"owner.data", - FieldValue::Map({{"name", FieldValue::String("Jonny")}, - {"email", FieldValue::String("abc@xyz.com")}})}})); - Await(document.Set( - MapFieldValue{ - {"updated", FieldValue::Boolean(true)}, - {"owner.data", - FieldValue::Map({{"name", FieldValue::String("Sebastian")}})}}, - SetOptions::Merge())); - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_THAT( - snapshot.GetData(), - ContainerEq(MapFieldValue{ - {"desc", FieldValue::String("Description")}, - {"updated", FieldValue::Boolean(true)}, - {"owner.data", - FieldValue::Map({{"name", FieldValue::String("Sebastian")}, - {"email", FieldValue::String("abc@xyz.com")}})}})); -} - -TEST_F(FirestoreIntegrationTest, TestCanMergeServerTimestamps) { - DocumentReference document = Collection("rooms").Document("eros"); - Await(document.Set(MapFieldValue{{"untouched", FieldValue::Boolean(true)}})); - Await(document.Set( - MapFieldValue{{"time", FieldValue::ServerTimestamp()}, - {"nested", FieldValue::Map( - {{"time", FieldValue::ServerTimestamp()}})}}, - SetOptions::Merge())); - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_TRUE(snapshot.Get("untouched").boolean_value()); - EXPECT_TRUE(snapshot.Get("time").is_timestamp()); - EXPECT_TRUE(snapshot.Get("nested.time").is_timestamp()); -} - -TEST_F(FirestoreIntegrationTest, TestCanMergeEmptyObject) { - DocumentReference document = Document(); - EventAccumulator accumulator; - ListenerRegistration registration = - accumulator.listener()->AttachTo(&document); - accumulator.Await(); - - document.Set(MapFieldValue{}); - DocumentSnapshot snapshot = accumulator.Await(); - EXPECT_THAT(snapshot.GetData(), ContainerEq(MapFieldValue{})); - - Await(document.Set(MapFieldValue{{"a", FieldValue::Map({})}}, - SetOptions::MergeFields({"a"}))); - snapshot = accumulator.Await(); - EXPECT_THAT(snapshot.GetData(), - ContainerEq(MapFieldValue{{"a", FieldValue::Map({})}})); - - Await(document.Set(MapFieldValue{{"b", FieldValue::Map({})}}, - SetOptions::Merge())); - snapshot = accumulator.Await(); - EXPECT_THAT(snapshot.GetData(), - ContainerEq(MapFieldValue{{"a", FieldValue::Map({})}, - {"b", FieldValue::Map({})}})); - - snapshot = *Await(document.Get(Source::kServer)); - EXPECT_THAT(snapshot.GetData(), - ContainerEq(MapFieldValue{{"a", FieldValue::Map({})}, - {"b", FieldValue::Map({})}})); - registration.Remove(); -} - -TEST_F(FirestoreIntegrationTest, TestCanDeleteFieldUsingMerge) { - DocumentReference document = Collection("rooms").Document("eros"); - Await(document.Set(MapFieldValue{ - {"untouched", FieldValue::Boolean(true)}, - {"foo", FieldValue::String("bar")}, - {"nested", FieldValue::Map({{"untouched", FieldValue::Boolean(true)}, - {"foo", FieldValue::String("bar")}})}})); - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_TRUE(snapshot.Get("untouched").boolean_value()); - EXPECT_TRUE(snapshot.Get("nested.untouched").boolean_value()); - EXPECT_TRUE(snapshot.Get("foo").is_valid()); - EXPECT_TRUE(snapshot.Get("nested.foo").is_valid()); - - Await(document.Set( - MapFieldValue{{"foo", FieldValue::Delete()}, - {"nested", FieldValue::Map(MapFieldValue{ - {"foo", FieldValue::Delete()}})}}, - SetOptions::Merge())); - snapshot = ReadDocument(document); - EXPECT_TRUE(snapshot.Get("untouched").boolean_value()); - EXPECT_TRUE(snapshot.Get("nested.untouched").boolean_value()); - EXPECT_FALSE(snapshot.Get("foo").is_valid()); - EXPECT_FALSE(snapshot.Get("nested.foo").is_valid()); -} - -TEST_F(FirestoreIntegrationTest, TestCanDeleteFieldUsingMergeFields) { - DocumentReference document = Collection("rooms").Document("eros"); - Await(document.Set(MapFieldValue{ - {"untouched", FieldValue::Boolean(true)}, - {"foo", FieldValue::String("bar")}, - {"inner", FieldValue::Map({{"removed", FieldValue::Boolean(true)}, - {"foo", FieldValue::String("bar")}})}, - {"nested", FieldValue::Map({{"untouched", FieldValue::Boolean(true)}, - {"foo", FieldValue::String("bar")}})}})); - Await(document.Set( - MapFieldValue{ - {"foo", FieldValue::Delete()}, - {"inner", FieldValue::Map({{"foo", FieldValue::Delete()}})}, - {"nested", FieldValue::Map({{"untouched", FieldValue::Delete()}, - {"foo", FieldValue::Delete()}})}}, - SetOptions::MergeFields({"foo", "inner", "nested.foo"}))); - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_THAT( - snapshot.GetData(), - ContainerEq(MapFieldValue{ - {"untouched", FieldValue::Boolean(true)}, - {"inner", FieldValue::Map({})}, - {"nested", - FieldValue::Map({{"untouched", FieldValue::Boolean(true)}})}})); -} - -TEST_F(FirestoreIntegrationTest, TestCanSetServerTimestampsUsingMergeFields) { - DocumentReference document = Collection("rooms").Document("eros"); - Await(document.Set(MapFieldValue{ - {"untouched", FieldValue::Boolean(true)}, - {"foo", FieldValue::String("bar")}, - {"nested", FieldValue::Map({{"untouched", FieldValue::Boolean(true)}, - {"foo", FieldValue::String("bar")}})}})); - Await(document.Set( - MapFieldValue{ - {"foo", FieldValue::ServerTimestamp()}, - {"inner", FieldValue::Map({{"foo", FieldValue::ServerTimestamp()}})}, - {"nested", - FieldValue::Map({{"foo", FieldValue::ServerTimestamp()}})}}, - SetOptions::MergeFields({"foo", "inner", "nested.foo"}))); - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_TRUE(snapshot.exists()); - EXPECT_TRUE(snapshot.Get("foo").is_timestamp()); - EXPECT_TRUE(snapshot.Get("inner.foo").is_timestamp()); - EXPECT_TRUE(snapshot.Get("nested.foo").is_timestamp()); -} - -TEST_F(FirestoreIntegrationTest, TestMergeReplacesArrays) { - DocumentReference document = Collection("rooms").Document("eros"); - Await(document.Set(MapFieldValue{ - {"untouched", FieldValue::Boolean(true)}, - {"data", FieldValue::String("old")}, - {"topLevel", FieldValue::Array( - {FieldValue::String("old"), FieldValue::String("old")})}, - {"mapInArray", FieldValue::Array({FieldValue::Map( - {{"data", FieldValue::String("old")}})})}})); - Await(document.Set( - MapFieldValue{ - {"data", FieldValue::String("new")}, - {"topLevel", FieldValue::Array({FieldValue::String("new")})}, - {"mapInArray", FieldValue::Array({FieldValue::Map( - {{"data", FieldValue::String("new")}})})}}, - SetOptions::Merge())); - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_THAT( - snapshot.GetData(), - ContainerEq(MapFieldValue{ - {"untouched", FieldValue::Boolean(true)}, - {"data", FieldValue::String("new")}, - {"topLevel", FieldValue::Array({FieldValue::String("new")})}, - {"mapInArray", FieldValue::Array({FieldValue::Map( - {{"data", FieldValue::String("new")}})})}})); -} - -TEST_F(FirestoreIntegrationTest, - TestCanDeepMergeDataWithAnExistingDocumentUsingSet) { - DocumentReference document = Collection("rooms").Document("eros"); - Await(document.Set(MapFieldValue{ - {"owner.data", - FieldValue::Map({{"name", FieldValue::String("Jonny")}, - {"email", FieldValue::String("old@xyz.com")}})}})); - Await(document.Set( - MapFieldValue{ - {"desc", FieldValue::String("NewDescription")}, - {"owner.data", - FieldValue::Map({{"name", FieldValue::String("Sebastian")}, - {"email", FieldValue::String("new@xyz.com")}})}}, - SetOptions::MergeFieldPaths({{"desc"}, {"owner.data", "name"}}))); - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_THAT( - snapshot.GetData(), - ContainerEq(MapFieldValue{ - {"desc", FieldValue::String("NewDescription")}, - {"owner.data", - FieldValue::Map({{"name", FieldValue::String("Sebastian")}, - {"email", FieldValue::String("old@xyz.com")}})}})); -} - -#if defined(__ANDROID__) && FIRESTORE_HAVE_EXCEPTIONS -// TODO(b/136012313): iOS currently doesn't rethrow native exceptions as C++ -// exceptions. -TEST_F(FirestoreIntegrationTest, TestFieldMaskCannotContainMissingFields) { - DocumentReference document = Collection("rooms").Document(); - try { - document.Set(MapFieldValue{{"desc", FieldValue::String("NewDescription")}}, - SetOptions::MergeFields({"desc", "owner"})); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_STREQ( - "Field 'owner' is specified in your field mask but not in your input " - "data.", - exception.what()); - } -} -#endif // defined(__ANDROID__) && FIRESTORE_HAVE_EXCEPTIONS - -TEST_F(FirestoreIntegrationTest, TestFieldsNotInFieldMaskAreIgnored) { - DocumentReference document = Collection("rooms").Document("eros"); - Await(document.Set(MapFieldValue{ - {"desc", FieldValue::String("Description")}, - {"owner", - FieldValue::Map({{"name", FieldValue::String("Jonny")}, - {"email", FieldValue::String("abc@xyz.com")}})}})); - Await( - document.Set(MapFieldValue{{"desc", FieldValue::String("NewDescription")}, - {"owner", FieldValue::String("Sebastian")}}, - SetOptions::MergeFields({"desc"}))); - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_THAT( - snapshot.GetData(), - ContainerEq(MapFieldValue{ - {"desc", FieldValue::String("NewDescription")}, - {"owner", - FieldValue::Map({{"name", FieldValue::String("Jonny")}, - {"email", FieldValue::String("abc@xyz.com")}})}})); -} - -TEST_F(FirestoreIntegrationTest, TestFieldDeletesNotInFieldMaskAreIgnored) { - DocumentReference document = Collection("rooms").Document("eros"); - Await(document.Set(MapFieldValue{ - {"desc", FieldValue::String("Description")}, - {"owner", - FieldValue::Map({{"name", FieldValue::String("Jonny")}, - {"email", FieldValue::String("abc@xyz.com")}})}})); - Await( - document.Set(MapFieldValue{{"desc", FieldValue::String("NewDescription")}, - {"owner", FieldValue::Delete()}}, - SetOptions::MergeFields({"desc"}))); - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_THAT( - snapshot.GetData(), - ContainerEq(MapFieldValue{ - {"desc", FieldValue::String("NewDescription")}, - {"owner", - FieldValue::Map({{"name", FieldValue::String("Jonny")}, - {"email", FieldValue::String("abc@xyz.com")}})}})); -} - -TEST_F(FirestoreIntegrationTest, TestFieldTransformsNotInFieldMaskAreIgnored) { - DocumentReference document = Collection("rooms").Document("eros"); - Await(document.Set(MapFieldValue{ - {"desc", FieldValue::String("Description")}, - {"owner", - FieldValue::Map({{"name", FieldValue::String("Jonny")}, - {"email", FieldValue::String("abc@xyz.com")}})}})); - Await( - document.Set(MapFieldValue{{"desc", FieldValue::String("NewDescription")}, - {"owner", FieldValue::ServerTimestamp()}}, - SetOptions::MergeFields({"desc"}))); - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_THAT( - snapshot.GetData(), - ContainerEq(MapFieldValue{ - {"desc", FieldValue::String("NewDescription")}, - {"owner", - FieldValue::Map({{"name", FieldValue::String("Jonny")}, - {"email", FieldValue::String("abc@xyz.com")}})}})); -} - -TEST_F(FirestoreIntegrationTest, TestCanSetEmptyFieldMask) { - DocumentReference document = Collection("rooms").Document("eros"); - Await(document.Set(MapFieldValue{ - {"desc", FieldValue::String("Description")}, - {"owner", - FieldValue::Map({{"name", FieldValue::String("Jonny")}, - {"email", FieldValue::String("abc@xyz.com")}})}})); - Await(document.Set( - MapFieldValue{{"desc", FieldValue::String("NewDescription")}}, - SetOptions::MergeFields({}))); - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_THAT( - snapshot.GetData(), - ContainerEq(MapFieldValue{ - {"desc", FieldValue::String("Description")}, - {"owner", - FieldValue::Map({{"name", FieldValue::String("Jonny")}, - {"email", FieldValue::String("abc@xyz.com")}})}})); -} - -TEST_F(FirestoreIntegrationTest, TestCanSpecifyFieldsMultipleTimesInFieldMask) { - DocumentReference document = Collection("rooms").Document("eros"); - Await(document.Set(MapFieldValue{ - {"desc", FieldValue::String("Description")}, - {"owner", - FieldValue::Map({{"name", FieldValue::String("Jonny")}, - {"email", FieldValue::String("abc@xyz.com")}})}})); - Await(document.Set( - MapFieldValue{ - {"desc", FieldValue::String("NewDescription")}, - {"owner", - FieldValue::Map({{"name", FieldValue::String("Sebastian")}, - {"email", FieldValue::String("new@new.com")}})}}, - SetOptions::MergeFields({"owner.name", "owner", "owner"}))); - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_THAT( - snapshot.GetData(), - ContainerEq(MapFieldValue{ - {"desc", FieldValue::String("Description")}, - {"owner", - FieldValue::Map({{"name", FieldValue::String("Sebastian")}, - {"email", FieldValue::String("new@new.com")}})}})); -} - -TEST_F(FirestoreIntegrationTest, TestCanDeleteAFieldWithAnUpdate) { - DocumentReference document = Collection("rooms").Document("eros"); - Await(document.Set(MapFieldValue{ - {"desc", FieldValue::String("Description")}, - {"owner", - FieldValue::Map({{"name", FieldValue::String("Jonny")}, - {"email", FieldValue::String("abc@xyz.com")}})}})); - Await(document.Update(MapFieldValue{{"owner.email", FieldValue::Delete()}})); - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_THAT(snapshot.GetData(), - ContainerEq(MapFieldValue{ - {"desc", FieldValue::String("Description")}, - {"owner", - FieldValue::Map({{"name", FieldValue::String("Jonny")}})}})); -} - -TEST_F(FirestoreIntegrationTest, TestCanUpdateFieldsWithDots) { - DocumentReference document = Collection("rooms").Document("eros"); - Await(document.Set(MapFieldValue{{"a.b", FieldValue::String("old")}, - {"c.d", FieldValue::String("old")}, - {"e.f", FieldValue::String("old")}})); - Await(document.Update({{FieldPath{"a.b"}, FieldValue::String("new")}})); - Await(document.Update({{FieldPath{"c.d"}, FieldValue::String("new")}})); - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_THAT(snapshot.GetData(), - ContainerEq(MapFieldValue{{"a.b", FieldValue::String("new")}, - {"c.d", FieldValue::String("new")}, - {"e.f", FieldValue::String("old")}})); -} - -TEST_F(FirestoreIntegrationTest, TestCanUpdateNestedFields) { - DocumentReference document = Collection("rooms").Document("eros"); - Await(document.Set(MapFieldValue{ - {"a", FieldValue::Map({{"b", FieldValue::String("old")}})}, - {"c", FieldValue::Map({{"d", FieldValue::String("old")}})}, - {"e", FieldValue::Map({{"f", FieldValue::String("old")}})}})); - Await(document.Update({{"a.b", FieldValue::String("new")}})); - Await(document.Update({{"c.d", FieldValue::String("new")}})); - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_THAT(snapshot.GetData(), - ContainerEq(MapFieldValue{ - {"a", FieldValue::Map({{"b", FieldValue::String("new")}})}, - {"c", FieldValue::Map({{"d", FieldValue::String("new")}})}, - {"e", FieldValue::Map({{"f", FieldValue::String("old")}})}})); -} - -// Verify that multiple deletes in a single update call work. -// https://github.com/firebase/quickstart-unity/issues/882 -TEST_F(FirestoreIntegrationTest, TestCanUpdateFieldsWithMultipleDeletes) { - DocumentReference document = Collection("rooms").Document(); - Await(document.Set(MapFieldValue{{"key1", FieldValue::String("value1")}, - {"key2", FieldValue::String("value2")}, - {"key3", FieldValue::String("value3")}, - {"key4", FieldValue::String("value4")}, - {"key5", FieldValue::String("value5")}})); - Await(document.Update({{FieldPath{"key1"}, FieldValue::Delete()}, - {FieldPath{"key3"}, FieldValue::Delete()}, - {FieldPath{"key5"}, FieldValue::Delete()}})); - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_THAT(snapshot.GetData(), ContainerEq(MapFieldValue{ - {"key2", FieldValue::String("value2")}, - {"key4", FieldValue::String("value4")}})); -} - -TEST_F(FirestoreIntegrationTest, TestDeleteDocument) { - DocumentReference document = Collection("rooms").Document("eros"); - WriteDocument(document, MapFieldValue{{"value", FieldValue::String("bar")}}); - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_THAT(snapshot.GetData(), - ContainerEq(MapFieldValue{{"value", FieldValue::String("bar")}})); - - Await(document.Delete()); - snapshot = ReadDocument(document); - EXPECT_FALSE(snapshot.exists()); -} - -TEST_F(FirestoreIntegrationTest, TestCannotUpdateNonexistentDocument) { - DocumentReference document = Collection("rooms").Document(); - Future future = - document.Update(MapFieldValue{{"owner", FieldValue::String("abc")}}); - Await(future); - EXPECT_EQ(FutureStatus::kFutureStatusComplete, future.status()); - EXPECT_EQ(Error::kErrorNotFound, future.error()); - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_FALSE(snapshot.exists()); -} - -TEST_F(FirestoreIntegrationTest, TestCanRetrieveNonexistentDocument) { - DocumentReference document = Collection("rooms").Document(); - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_FALSE(snapshot.exists()); - - TestEventListener listener{"for document"}; - ListenerRegistration registration = listener.AttachTo(&document); - Await(listener); - EXPECT_EQ(Error::kErrorOk, listener.first_error_code()); - EXPECT_FALSE(listener.last_result().exists()); - registration.Remove(); -} - -TEST_F(FirestoreIntegrationTest, - TestAddingToACollectionYieldsTheCorrectDocumentReference) { - DocumentReference document = Collection("rooms").Document(); - Await(document.Set(MapFieldValue{{"foo", FieldValue::Double(1.0)}})); - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_THAT(snapshot.GetData(), - ContainerEq(MapFieldValue{{"foo", FieldValue::Double(1.0)}})); -} - -TEST_F(FirestoreIntegrationTest, - TestSnapshotsInSyncListenerFiresAfterListenersInSync) { - DocumentReference document = Collection("rooms").Document(); - Await(document.Set(MapFieldValue{{"foo", FieldValue::Double(1.0)}})); - std::vector events; - - class SnapshotTestEventListener : public TestEventListener { - public: - SnapshotTestEventListener(std::string name, - std::vector* events) - : TestEventListener(std::move(name)), events_(events) {} - - void OnEvent(const DocumentSnapshot& value, Error error_code, - const std::string& error_message) override { - TestEventListener::OnEvent(value, error_code, error_message); - events_->push_back("doc"); - } - - private: - std::vector* events_; - }; - SnapshotTestEventListener listener{"doc", &events}; - ListenerRegistration doc_registration = listener.AttachTo(&document); - // Wait for the initial event from the backend so that we know we'll get - // exactly one snapshot event for our local write below. - Await(listener); - EXPECT_EQ(1, events.size()); - events.clear(); - -#if defined(__APPLE__) - // TODO(varconst): the implementation of `Semaphore::Post()` on Apple - // platforms has a data race which may result in semaphore data being accessed - // on the listener thread after it was destroyed on the main thread. To work - // around this, use `std::promise`. - std::promise promise; -#else - Semaphore completed{0}; -#endif - -#if defined(FIREBASE_USE_STD_FUNCTION) - ListenerRegistration sync_registration = - TestFirestore()->AddSnapshotsInSyncListener([&] { - events.push_back("snapshots-in-sync"); - if (events.size() == 3) { -#if defined(__APPLE__) - promise.set_value(); -#else - completed.Post(); -#endif - } - }); - -#else - class SyncEventListener : public EventListener { - public: - explicit SyncEventListener(std::vector* events, - Semaphore* completed) - : events_(events), completed_(completed) {} - - void OnEvent(Error) override { - events_->push_back("snapshots-in-sync"); - if (events.size() == 3) { - completed_->Post(); - } - } - - private: - std::vector* events_ = nullptr; - Semaphore* completed_ = nullptr; - }; - SyncEventListener sync_listener{&events, &completed}; - ListenerRegistration sync_registration = - TestFirestore()->AddSnapshotsInSyncListener(sync_listener); -#endif // defined(FIREBASE_USE_STD_FUNCTION) - - Await(document.Set(MapFieldValue{{"foo", FieldValue::Double(3.0)}})); - // Wait for the snapshots-in-sync listener to fire afterwards. -#if defined(__APPLE__) - promise.get_future().wait(); -#else - completed.Wait(); -#endif - - // We should have an initial snapshots-in-sync event, then a snapshot event - // for set(), then another event to indicate we're in sync again. - EXPECT_EQ(events, std::vector( - {"snapshots-in-sync", "doc", "snapshots-in-sync"})); - doc_registration.Remove(); - sync_registration.Remove(); -} - -TEST_F(FirestoreIntegrationTest, TestQueriesAreValidatedOnClient) { - // NOTE: Failure cases are validated in ValidationTest. - CollectionReference collection = Collection(); - Query query = - collection.WhereGreaterThanOrEqualTo("x", FieldValue::Integer(32)); - // Same inequality field works; - query.WhereLessThanOrEqualTo("x", FieldValue::String("cat")); - // Equality on different field works; - query.WhereEqualTo("y", FieldValue::String("cat")); - // Array contains on different field works; - query.WhereArrayContains("y", FieldValue::String("cat")); - - // Ordering by inequality field succeeds. - query.OrderBy("x"); - collection.OrderBy("x").WhereGreaterThanOrEqualTo("x", - FieldValue::Integer(32)); - - // inequality same as first order by works - query.OrderBy("x").OrderBy("y"); - collection.OrderBy("x").OrderBy("y").WhereGreaterThanOrEqualTo( - "x", FieldValue::Integer(32)); - collection.OrderBy("x", Query::Direction::kDescending) - .WhereEqualTo("y", FieldValue::String("true")); - - // Equality different than orderBy works - collection.OrderBy("x").WhereEqualTo("y", FieldValue::String("cat")); - // Array contains different than orderBy works - collection.OrderBy("x").WhereArrayContains("y", FieldValue::String("cat")); -} - -// The test harness will generate Java JUnit test regardless whether this is -// inside a #if or not. So we move #if inside instead of enclose the whole case. -TEST_F(FirestoreIntegrationTest, TestListenCanBeCalledMultipleTimes) { - // Note: this test is flaky -- the test case may finish, triggering the - // destruction of Firestore, before the async callback finishes. -#if defined(FIREBASE_USE_STD_FUNCTION) - DocumentReference document = Collection("collection").Document(); - WriteDocument(document, MapFieldValue{{"foo", FieldValue::String("bar")}}); -#if defined(__APPLE__) - // TODO(varconst): the implementation of `Semaphore::Post()` on Apple - // platforms has a data race which may result in semaphore data being accessed - // on the listener thread after it was destroyed on the main thread. To work - // around this, use `std::promise`. - std::promise promise; -#else - Semaphore completed{0}; -#endif - DocumentSnapshot resulting_data; - document.AddSnapshotListener([&](const DocumentSnapshot& snapshot, - Error error_code, - const std::string& error_message) { - EXPECT_EQ(Error::kErrorOk, error_code); - EXPECT_EQ(std::string(), error_message); - document.AddSnapshotListener([&](const DocumentSnapshot& snapshot, - Error error_code, - const std::string& error_message) { - EXPECT_EQ(Error::kErrorOk, error_code); - EXPECT_EQ(std::string(), error_message); - resulting_data = snapshot; -#if defined(__APPLE__) - promise.set_value(); -#else - completed.Post(); -#endif - }); - }); -#if defined(__APPLE__) - promise.get_future().wait(); -#else - completed.Wait(); -#endif - EXPECT_THAT(resulting_data.GetData(), - ContainerEq(MapFieldValue{{"foo", FieldValue::String("bar")}})); -#endif // defined(FIREBASE_USE_STD_FUNCTION) -} - -TEST_F(FirestoreIntegrationTest, TestDocumentSnapshotEventsNonExistent) { - DocumentReference document = Collection("rooms").Document(); - TestEventListener listener("TestNonExistent"); - ListenerRegistration registration = - listener.AttachTo(&document, MetadataChanges::kInclude); - Await(listener); - EXPECT_EQ(1, listener.event_count()); - EXPECT_EQ(Error::kErrorOk, listener.first_error_code()); - EXPECT_FALSE(listener.last_result().exists()); - registration.Remove(); -} - -TEST_F(FirestoreIntegrationTest, TestDocumentSnapshotEventsForAdd) { - DocumentReference document = Collection("rooms").Document(); - TestEventListener listener("TestForAdd"); - ListenerRegistration registration = - listener.AttachTo(&document, MetadataChanges::kInclude); - Await(listener); - EXPECT_FALSE(listener.last_result().exists()); - - WriteDocument(document, MapFieldValue{{"a", FieldValue::Double(1.0)}}); - Await(listener, 3); - DocumentSnapshot snapshot = listener.last_result(1); - EXPECT_THAT(snapshot.GetData(), - ContainerEq(MapFieldValue{{"a", FieldValue::Double(1.0)}})); - EXPECT_TRUE(snapshot.metadata().has_pending_writes()); - snapshot = listener.last_result(); - EXPECT_THAT(snapshot.GetData(), - ContainerEq(MapFieldValue{{"a", FieldValue::Double(1.0)}})); - EXPECT_FALSE(snapshot.metadata().has_pending_writes()); - - registration.Remove(); -} - -TEST_F(FirestoreIntegrationTest, TestDocumentSnapshotEventsForChange) { - CollectionReference collection = - Collection(std::map{ - {"doc", MapFieldValue{{"a", FieldValue::Double(1.0)}}}}); - DocumentReference document = collection.Document("doc"); - TestEventListener listener("TestForChange"); - ListenerRegistration registration = - listener.AttachTo(&document, MetadataChanges::kInclude); - Await(listener); - DocumentSnapshot snapshot = listener.last_result(); - EXPECT_THAT(snapshot.GetData(), - ContainerEq(MapFieldValue{{"a", FieldValue::Double(1.0)}})); - EXPECT_FALSE(snapshot.metadata().has_pending_writes()); - EXPECT_FALSE(snapshot.metadata().is_from_cache()); - - UpdateDocument(document, MapFieldValue{{"a", FieldValue::Double(2.0)}}); - Await(listener, 3); - snapshot = listener.last_result(1); - EXPECT_THAT(snapshot.GetData(), - ContainerEq(MapFieldValue{{"a", FieldValue::Double(2.0)}})); - EXPECT_TRUE(snapshot.metadata().has_pending_writes()); - EXPECT_FALSE(snapshot.metadata().is_from_cache()); - snapshot = listener.last_result(); - EXPECT_THAT(snapshot.GetData(), - ContainerEq(MapFieldValue{{"a", FieldValue::Double(2.0)}})); - EXPECT_FALSE(snapshot.metadata().has_pending_writes()); - EXPECT_FALSE(snapshot.metadata().is_from_cache()); - - registration.Remove(); -} - -TEST_F(FirestoreIntegrationTest, TestDocumentSnapshotEventsForDelete) { - CollectionReference collection = - Collection(std::map{ - {"doc", MapFieldValue{{"a", FieldValue::Double(1.0)}}}}); - DocumentReference document = collection.Document("doc"); - TestEventListener listener("TestForDelete"); - ListenerRegistration registration = - listener.AttachTo(&document, MetadataChanges::kInclude); - Await(listener, 1); - DocumentSnapshot snapshot = listener.last_result(); - EXPECT_TRUE(snapshot.exists()); - EXPECT_THAT(snapshot.GetData(), - ContainerEq(MapFieldValue{{"a", FieldValue::Double(1.0)}})); - EXPECT_FALSE(snapshot.metadata().has_pending_writes()); - EXPECT_FALSE(snapshot.metadata().is_from_cache()); - - DeleteDocument(document); - Await(listener, 2); - snapshot = listener.last_result(); - EXPECT_FALSE(snapshot.exists()); - - registration.Remove(); -} - -TEST_F(FirestoreIntegrationTest, TestDocumentSnapshotErrorReporting) { - DocumentReference document = Collection("col").Document("__badpath__"); - TestEventListener listener("TestBadPath"); - ListenerRegistration registration = - listener.AttachTo(&document, MetadataChanges::kInclude); - Await(listener); - EXPECT_EQ(1, listener.event_count()); - EXPECT_EQ(Error::kErrorInvalidArgument, listener.first_error_code()); - EXPECT_THAT(listener.first_error_message(), HasSubstr("__badpath__")); - EXPECT_FALSE(listener.last_result().exists()); - registration.Remove(); -} - -TEST_F(FirestoreIntegrationTest, TestQuerySnapshotEventsForAdd) { - CollectionReference collection = Collection(); - DocumentReference document = collection.Document(); - TestEventListener listener("TestForCollectionAdd"); - ListenerRegistration registration = - listener.AttachTo(&collection, MetadataChanges::kInclude); - Await(listener); - EXPECT_EQ(0, listener.last_result().size()); - - WriteDocument(document, MapFieldValue{{"a", FieldValue::Double(1.0)}}); - Await(listener, 3); - QuerySnapshot snapshot = listener.last_result(1); - EXPECT_EQ(1, snapshot.size()); - EXPECT_THAT(snapshot.documents()[0].GetData(), - ContainerEq(MapFieldValue{{"a", FieldValue::Double(1.0)}})); - EXPECT_TRUE(snapshot.metadata().has_pending_writes()); - snapshot = listener.last_result(); - EXPECT_EQ(1, snapshot.size()); - EXPECT_THAT(snapshot.documents()[0].GetData(), - ContainerEq(MapFieldValue{{"a", FieldValue::Double(1.0)}})); - EXPECT_FALSE(snapshot.metadata().has_pending_writes()); - - registration.Remove(); -} - -TEST_F(FirestoreIntegrationTest, TestQuerySnapshotEventsForChange) { - CollectionReference collection = - Collection(std::map{ - {"doc", MapFieldValue{{"a", FieldValue::Double(1.0)}}}}); - DocumentReference document = collection.Document("doc"); - TestEventListener listener("TestForCollectionChange"); - ListenerRegistration registration = - listener.AttachTo(&collection, MetadataChanges::kInclude); - Await(listener); - QuerySnapshot snapshot = listener.last_result(); - EXPECT_EQ(1, snapshot.size()); - EXPECT_THAT(snapshot.documents()[0].GetData(), - ContainerEq(MapFieldValue{{"a", FieldValue::Double(1.0)}})); - EXPECT_FALSE(snapshot.metadata().has_pending_writes()); - - WriteDocument(document, MapFieldValue{{"a", FieldValue::Double(2.0)}}); - Await(listener, 3); - snapshot = listener.last_result(1); - EXPECT_EQ(1, snapshot.size()); - EXPECT_THAT(snapshot.documents()[0].GetData(), - ContainerEq(MapFieldValue{{"a", FieldValue::Double(2.0)}})); - EXPECT_TRUE(snapshot.metadata().has_pending_writes()); - snapshot = listener.last_result(); - EXPECT_EQ(1, snapshot.size()); - EXPECT_THAT(snapshot.documents()[0].GetData(), - ContainerEq(MapFieldValue{{"a", FieldValue::Double(2.0)}})); - EXPECT_FALSE(snapshot.metadata().has_pending_writes()); - - registration.Remove(); -} - -TEST_F(FirestoreIntegrationTest, TestQuerySnapshotEventsForDelete) { - CollectionReference collection = - Collection(std::map{ - {"doc", MapFieldValue{{"a", FieldValue::Double(1.0)}}}}); - DocumentReference document = collection.Document("doc"); - TestEventListener listener("TestForQueryDelete"); - ListenerRegistration registration = - listener.AttachTo(&collection, MetadataChanges::kInclude); - Await(listener); - QuerySnapshot snapshot = listener.last_result(); - EXPECT_EQ(1, snapshot.size()); - EXPECT_THAT(snapshot.documents()[0].GetData(), - ContainerEq(MapFieldValue{{"a", FieldValue::Double(1.0)}})); - EXPECT_FALSE(snapshot.metadata().has_pending_writes()); - - DeleteDocument(document); - Await(listener, 2); - snapshot = listener.last_result(); - EXPECT_EQ(0, snapshot.size()); - - registration.Remove(); -} - -TEST_F(FirestoreIntegrationTest, TestQuerySnapshotErrorReporting) { - CollectionReference collection = - Collection("a").Document("__badpath__").Collection("b"); - TestEventListener listener("TestBadPath"); - ListenerRegistration registration = - listener.AttachTo(&collection, MetadataChanges::kInclude); - Await(listener); - EXPECT_EQ(1, listener.event_count()); - EXPECT_EQ(Error::kErrorInvalidArgument, listener.first_error_code()); - EXPECT_THAT(listener.first_error_message(), HasSubstr("__badpath__")); - EXPECT_TRUE(listener.last_result().empty()); - registration.Remove(); -} - -TEST_F(FirestoreIntegrationTest, - TestMetadataOnlyChangesAreNotFiredWhenNoOptionsProvided) { - DocumentReference document = Collection().Document(); - TestEventListener listener("TestForNoMetadataOnlyChanges"); - ListenerRegistration registration = listener.AttachTo(&document); - WriteDocument(document, MapFieldValue{{"a", FieldValue::Double(1.0)}}); - Await(listener); - EXPECT_THAT(listener.last_result().GetData(), - ContainerEq(MapFieldValue{{"a", FieldValue::Double(1.0)}})); - WriteDocument(document, MapFieldValue{{"b", FieldValue::Double(1.0)}}); - Await(listener); - EXPECT_THAT(listener.last_result().GetData(), - ContainerEq(MapFieldValue{{"b", FieldValue::Double(1.0)}})); - registration.Remove(); -} - -TEST_F(FirestoreIntegrationTest, TestDocumentReferenceExposesFirestore) { - Firestore* db = TestFirestore(); - // EXPECT_EQ(db, db->Document("foo/bar").firestore()); - // TODO(varconst): use the commented out check above. - // Currently, integration tests create their own Firestore instances that - // aren't registered in the main cache. Because of that, Firestore objects - // will lazily create a new Firestore instance upon the first access. This - // doesn't affect production code, only tests. - // Also, the logic in `util_ios.h` can be modified to make sure that - // `TestFirestore()` doesn't create a new Firestore instance if there isn't - // one already. - EXPECT_NE(nullptr, db->Document("foo/bar").firestore()); -} - -TEST_F(FirestoreIntegrationTest, TestCollectionReferenceExposesFirestore) { - Firestore* db = TestFirestore(); - // EXPECT_EQ(db, db->Collection("foo").firestore()); - EXPECT_NE(nullptr, db->Collection("foo").firestore()); -} - -TEST_F(FirestoreIntegrationTest, TestQueryExposesFirestore) { - Firestore* db = TestFirestore(); - // EXPECT_EQ(db, db->Collection("foo").Limit(5).firestore()); - EXPECT_NE(nullptr, db->Collection("foo").Limit(5).firestore()); -} - -TEST_F(FirestoreIntegrationTest, TestDocumentReferenceEquality) { - Firestore* db = TestFirestore(); - DocumentReference document = db->Document("foo/bar"); - EXPECT_EQ(document, db->Document("foo/bar")); - EXPECT_EQ(document, document.Collection("blah").Parent()); - - EXPECT_NE(document, db->Document("foo/BAR")); - - Firestore* another_db = TestFirestore("another"); - EXPECT_NE(document, another_db->Document("foo/bar")); -} - -TEST_F(FirestoreIntegrationTest, TestQueryReferenceEquality) { - Firestore* db = TestFirestore(); - Query query = db->Collection("foo").OrderBy("bar").WhereEqualTo( - "baz", FieldValue::Integer(42)); - Query query2 = db->Collection("foo").OrderBy("bar").WhereEqualTo( - "baz", FieldValue::Integer(42)); - EXPECT_EQ(query, query2); - - Query query3 = db->Collection("foo").OrderBy("BAR").WhereEqualTo( - "baz", FieldValue::Integer(42)); - EXPECT_NE(query, query3); - - // PORT_NOTE: Right now there is no way to create another Firestore in test. - // So we skip the testing of two queries with different Firestore instance. -} - -TEST_F(FirestoreIntegrationTest, TestCanTraverseCollectionsAndDocuments) { - Firestore* db = TestFirestore(); - - // doc path from root Firestore. - EXPECT_EQ("a/b/c/d", db->Document("a/b/c/d").path()); - - // collection path from root Firestore. - EXPECT_EQ("a/b/c/d", db->Collection("a/b/c").Document("d").path()); - - // doc path from CollectionReference. - EXPECT_EQ("a/b/c/d", db->Collection("a").Document("b/c/d").path()); - - // collection path from DocumentReference. - EXPECT_EQ("a/b/c/d/e", db->Document("a/b").Collection("c/d/e").path()); -} - -TEST_F(FirestoreIntegrationTest, TestCanTraverseCollectionAndDocumentParents) { - Firestore* db = TestFirestore(); - CollectionReference collection = db->Collection("a/b/c"); - EXPECT_EQ("a/b/c", collection.path()); - - DocumentReference doc = collection.Parent(); - EXPECT_EQ("a/b", doc.path()); - - collection = doc.Parent(); - EXPECT_EQ("a", collection.path()); - - DocumentReference invalidDoc = collection.Parent(); - EXPECT_FALSE(invalidDoc.is_valid()); -} - -TEST_F(FirestoreIntegrationTest, TestCollectionId) { - EXPECT_EQ("foo", TestFirestore()->Collection("foo").id()); - EXPECT_EQ("baz", TestFirestore()->Collection("foo/bar/baz").id()); -} - -TEST_F(FirestoreIntegrationTest, TestDocumentId) { - EXPECT_EQ(TestFirestore()->Document("foo/bar").id(), "bar"); - EXPECT_EQ(TestFirestore()->Document("foo/bar/baz/qux").id(), "qux"); -} - -TEST_F(FirestoreIntegrationTest, TestCanQueueWritesWhileOffline) { - // Arrange - DocumentReference document = Collection("rooms").Document("eros"); - - // Act - Await(TestFirestore()->DisableNetwork()); - Future future = document.Set(MapFieldValue{ - {"desc", FieldValue::String("Description")}, - {"owner", - FieldValue::Map({{"name", FieldValue::String("Sebastian")}, - {"email", FieldValue::String("abc@xyz.com")}})}}); - EXPECT_EQ(FutureStatus::kFutureStatusPending, future.status()); - Await(TestFirestore()->EnableNetwork()); - Await(future); - - // Assert - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_THAT( - snapshot.GetData(), - ContainerEq(MapFieldValue{ - {"desc", FieldValue::String("Description")}, - {"owner", - FieldValue::Map({{"name", FieldValue::String("Sebastian")}, - {"email", FieldValue::String("abc@xyz.com")}})}})); - EXPECT_FALSE(snapshot.metadata().is_from_cache()); -} - -TEST_F(FirestoreIntegrationTest, TestCanGetDocumentsWhileOffline) { - DocumentReference document = Collection("rooms").Document(); - Await(TestFirestore()->DisableNetwork()); - Future future = document.Get(); - Await(future); - EXPECT_EQ(Error::kErrorUnavailable, future.error()); - - // Write the document to the local cache. - Future pending_write = document.Set(MapFieldValue{ - {"desc", FieldValue::String("Description")}, - {"owner", - FieldValue::Map({{"name", FieldValue::String("Sebastian")}, - {"email", FieldValue::String("abc@xyz.com")}})}}); - - // The network is offline and we return a cached result. - DocumentSnapshot snapshot = ReadDocument(document); - EXPECT_THAT( - snapshot.GetData(), - ContainerEq(MapFieldValue{ - {"desc", FieldValue::String("Description")}, - {"owner", - FieldValue::Map({{"name", FieldValue::String("Sebastian")}, - {"email", FieldValue::String("abc@xyz.com")}})}})); - EXPECT_TRUE(snapshot.metadata().is_from_cache()); - - // Enable the network and fetch the document again. - Await(TestFirestore()->EnableNetwork()); - Await(pending_write); - snapshot = ReadDocument(document); - EXPECT_THAT( - snapshot.GetData(), - ContainerEq(MapFieldValue{ - {"desc", FieldValue::String("Description")}, - {"owner", - FieldValue::Map({{"name", FieldValue::String("Sebastian")}, - {"email", FieldValue::String("abc@xyz.com")}})}})); - EXPECT_FALSE(snapshot.metadata().is_from_cache()); -} - -// Will not port the following two cases: -// TestWriteStreamReconnectsAfterIdle and -// TestWatchStreamReconnectsAfterIdle, -// both of which requires manipulating with DispatchQueue which is not exposed -// as a public API. -// Also, these tests exercise a particular part of SDK (streams), they are -// really unit tests that have to be run in integration tests setup. The -// existing Objective-C and Android tests cover these cases fairly well. - -TEST_F(FirestoreIntegrationTest, TestCanDisableAndEnableNetworking) { - // There's not currently a way to check if networking is in fact disabled, - // so for now just test that the method is well-behaved and doesn't throw. - Firestore* db = TestFirestore(); - Await(db->EnableNetwork()); - Await(db->EnableNetwork()); - Await(db->DisableNetwork()); - Await(db->DisableNetwork()); - Await(db->EnableNetwork()); -} - -// TODO(varconst): split this test. -TEST_F(FirestoreIntegrationTest, TestToString) { - Settings settings; - settings.set_host("foo.bar"); - settings.set_ssl_enabled(false); - EXPECT_EQ( - "Settings(host='foo.bar', is_ssl_enabled=false, " - "is_persistence_enabled=true, cache_size_bytes=104857600)", - settings.ToString()); - - CollectionReference collection = Collection("rooms"); - DocumentReference reference = collection.Document("eros"); - // Note: because the map is unordered, it's hard to check the case where a map - // has more than one element. - Await(reference.Set({ - {"owner", FieldValue::String("Jonny")}, - })); - EXPECT_EQ(std::string("DocumentReference(") + collection.id() + "/eros)", - reference.ToString()); - - DocumentSnapshot doc = ReadDocument(reference); - EXPECT_EQ( - "DocumentSnapshot(id=eros, " - "metadata=SnapshotMetadata{has_pending_writes=false, " - "is_from_cache=false}, doc={owner: 'Jonny'})", - doc.ToString()); -} - -// TODO(wuandy): Enable this for other platforms when they can handle -// exceptions. -#if defined(__ANDROID__) && FIRESTORE_HAVE_EXCEPTIONS -TEST_F(FirestoreIntegrationTest, ClientCallsAfterTerminateFails) { - EXPECT_THAT(TestFirestore()->Terminate(), FutureSucceeds()); - EXPECT_THROW(Await(TestFirestore()->DisableNetwork()), std::logic_error); -} - -TEST_F(FirestoreIntegrationTest, NewOperationThrowsAfterFirestoreTerminate) { - auto instance = TestFirestore(); - DocumentReference reference = TestFirestore()->Document("abc/123"); - Await(reference.Set({{"Field", FieldValue::Integer(100)}})); - - EXPECT_THAT(instance->Terminate(), FutureSucceeds()); - - EXPECT_THROW(Await(reference.Get()), std::logic_error); - EXPECT_THROW(Await(reference.Update({{"Field", FieldValue::Integer(1)}})), - std::logic_error); - EXPECT_THROW(Await(reference.Set({{"Field", FieldValue::Integer(1)}})), - std::logic_error); - EXPECT_THROW(Await(instance->batch() - .Set(reference, {{"Field", FieldValue::Integer(1)}}) - .Commit()), - std::logic_error); - EXPECT_THROW(Await(instance->RunTransaction( - [reference](Transaction& transaction, - std::string& error_message) -> Error { - Error error = Error::kErrorOk; - transaction.Get(reference, &error, &error_message); - return error; - })), - std::logic_error); -} - -TEST_F(FirestoreIntegrationTest, TerminateCanBeCalledMultipleTimes) { - auto instance = TestFirestore(); - DocumentReference reference = instance->Document("abc/123"); - Await(reference.Set({{"Field", FieldValue::Integer(100)}})); - - EXPECT_THAT(instance->Terminate(), FutureSucceeds()); - - EXPECT_THROW(Await(reference.Get()), std::logic_error); - - // Calling a second time should go through and change nothing. - EXPECT_THAT(instance->Terminate(), FutureSucceeds()); - - EXPECT_THROW(Await(reference.Update({{"Field", FieldValue::Integer(1)}})), - std::logic_error); -} -#endif // defined(__ANDROID__) && FIRESTORE_HAVE_EXCEPTIONS - -TEST_F(FirestoreIntegrationTest, MaintainsPersistenceAfterRestarting) { - Firestore* db = TestFirestore(); - App* app = db->app(); - DocumentReference doc = db->Collection("col1").Document("doc1"); - auto path = doc.path(); - Await(doc.Set({{"foo", FieldValue::String("bar")}})); - DeleteFirestore(db); - DeleteApp(app); - - DocumentReference doc_2 = TestFirestore()->Document(path); - auto snap = Await(doc_2.Get()); - EXPECT_TRUE(snap->exists()); -} - -// TODO(b/173730469) Enable this test on Android once the Auth issue is fixed. -#if !defined(__ANDROID__) -TEST_F(FirestoreIntegrationTest, RestartFirestoreLeadsToNewInstance) { - // Get App and Settings objects to use in the test. - Firestore* db_template = TestFirestore("restart_firestore_new_instance_test"); - App* app = db_template->app(); - Settings settings = db_template->settings(); - DeleteFirestore(db_template); - - // Get the Auth object so that it can be explicitly deleted to avoid a leak. - // This memory leak avoidance hack can be removed once Auth becomes a soft - // dependency (b/147772264). - InitResult init_result; - Auth* auth = Auth::GetAuth(app, &init_result); - ASSERT_EQ(kInitResultSuccess, init_result); - - // Verify that GetInstance() returns the same instance when specified the same - // App. - Firestore* db1 = Firestore::GetInstance(app, &init_result); - ASSERT_EQ(kInitResultSuccess, init_result); - Firestore* db1_copy = Firestore::GetInstance(app, &init_result); - ASSERT_EQ(kInitResultSuccess, init_result); - EXPECT_EQ(db1, db1_copy); - - // Create a document that we can use for verification later. - db1->set_settings(settings); - DocumentReference doc1 = db1->Collection("abc").Document(); - const std::string doc_path = doc1.path(); - EXPECT_THAT(doc1.Set({{"foo", FieldValue::String("bar")}}), FutureSucceeds()); - - // Terminate `db1` so that it will be removed from the instance cache. - EXPECT_THAT(db1->Terminate(), FutureSucceeds()); - - // Verify that GetInstance() returns a new instance since the old instance has - // been terminated. - Firestore* db2 = Firestore::GetInstance(app, &init_result); - ASSERT_EQ(kInitResultSuccess, init_result); - EXPECT_NE(db1, db2); - - // Verify that the new instance points to the same database by verifying that - // the document created with the old instance exists in the new instance. - DocumentReference doc2 = db2->Document(doc_path); - const DocumentSnapshot* snapshot2 = Await(doc2.Get(Source::kCache)); - ASSERT_NE(snapshot2, nullptr); - EXPECT_THAT(snapshot2->GetData(), - ContainerEq(MapFieldValue{{"foo", FieldValue::String("bar")}})); - - delete db2; - delete db1; - delete auth; -} -#endif // if !defined(__ANDROID__) - -TEST_F(FirestoreIntegrationTest, CanStopListeningAfterTerminate) { - auto instance = TestFirestore(); - DocumentReference reference = instance->Document("abc/123"); - EventAccumulator accumulator; - ListenerRegistration registration = - accumulator.listener()->AttachTo(&reference); - - accumulator.Await(); - EXPECT_THAT(instance->Terminate(), FutureSucceeds()); - - // This should proceed without error. - registration.Remove(); - // Multiple calls should proceed as effectively a no-op. - registration.Remove(); -} - -TEST_F(FirestoreIntegrationTest, WaitForPendingWritesResolves) { - DocumentReference document = Collection("abc").Document("123"); - - Await(TestFirestore()->DisableNetwork()); - Future await_pending_writes_1 = TestFirestore()->WaitForPendingWrites(); - Future pending_writes = - document.Set(MapFieldValue{{"desc", FieldValue::String("Description")}}); - Future await_pending_writes_2 = TestFirestore()->WaitForPendingWrites(); - - // `await_pending_writes_1` resolves immediately because there are no pending - // writes at the time it is created. - Await(await_pending_writes_1); - EXPECT_EQ(await_pending_writes_1.status(), - FutureStatus::kFutureStatusComplete); - EXPECT_EQ(pending_writes.status(), FutureStatus::kFutureStatusPending); - EXPECT_EQ(await_pending_writes_2.status(), - FutureStatus::kFutureStatusPending); - - TestFirestore()->EnableNetwork(); - Await(await_pending_writes_2); - EXPECT_EQ(await_pending_writes_2.status(), - FutureStatus::kFutureStatusComplete); -} - -// TODO(wuandy): This test requires to create underlying firestore instance with -// a MockCredentialProvider first. -// TEST_F(FirestoreIntegrationTest, WaitForPendingWritesFailsWhenUserChanges) {} - -TEST_F(FirestoreIntegrationTest, - WaitForPendingWritesResolvesWhenOfflineIfThereIsNoPending) { - Await(TestFirestore()->DisableNetwork()); - Future await_pending_writes = TestFirestore()->WaitForPendingWrites(); - - // `await_pending_writes` resolves immediately because there are no pending - // writes at the time it is created. - Await(await_pending_writes); - EXPECT_EQ(await_pending_writes.status(), FutureStatus::kFutureStatusComplete); -} - -TEST_F(FirestoreIntegrationTest, CanClearPersistenceTestHarnessVerification) { - // Verify that TestFirestore(), DeleteFirestore(), and DeleteApp() behave how - // we expect; otherwise, the tests for ClearPersistence() could yield false - // positives. - Firestore* db = TestFirestore(); - App* app = db->app(); - const std::string app_name = app->name(); - - DocumentReference document = db->Collection("a").Document(); - const std::string path = document.path(); - WriteDocument(document, MapFieldValue{{"foo", FieldValue::Integer(42)}}); - DeleteFirestore(db); - DeleteApp(app); - - Firestore* db_2 = TestFirestore(app_name); - DocumentReference document_2 = db_2->Document(path); - Future get_future = document_2.Get(Source::kCache); - DocumentSnapshot snapshot_2 = *Await(get_future); - EXPECT_THAT(snapshot_2.GetData(), - ContainerEq(MapFieldValue{{"foo", FieldValue::Integer(42)}})); -} - -TEST_F(FirestoreIntegrationTest, CanClearPersistenceAfterRestarting) { - Firestore* db = TestFirestore(); - App* app = db->app(); - const std::string app_name = app->name(); - - DocumentReference document = db->Collection("a").Document("b"); - const std::string path = document.path(); - WriteDocument(document, MapFieldValue{{"foo", FieldValue::Integer(42)}}); - - // Call ClearPersistence(), but call Terminate() first because - // ClearPersistence() requires Firestore to be terminated. - EXPECT_THAT(db->Terminate(), FutureSucceeds()); - EXPECT_THAT(db->ClearPersistence(), FutureSucceeds()); - // Call DeleteFirestore() to ensure that both the App and Firestore instances - // are deleted, which emulates the way an end user would experience their - // application being killed and later re-launched by the user. - DeleteFirestore(db); - DeleteApp(app); - - // We restart the app with the same name and options to check that the - // previous instance's persistent storage is actually cleared after the - // restart. Although calling TestFirestore() with no arguments here would do - // the same thing, we explicitly specify an app_name to be clear that we want - // a new Firestore instance for the same Firebase app. - Firestore* db_2 = TestFirestore(app_name); - DocumentReference document_2 = db_2->Document(path); - Future await_get = document_2.Get(Source::kCache); - Await(await_get); - EXPECT_EQ(await_get.status(), FutureStatus::kFutureStatusComplete); - EXPECT_EQ(await_get.error(), Error::kErrorUnavailable); -} - -TEST_F(FirestoreIntegrationTest, CanClearPersistenceOnANewFirestoreInstance) { - Firestore* db = TestFirestore(); - App* app = db->app(); - const std::string app_name = app->name(); - - DocumentReference document = db->Collection("a").Document("b"); - const std::string path = document.path(); - WriteDocument(document, MapFieldValue{{"foo", FieldValue::Integer(42)}}); - - // Call DeleteFirestore() to ensure that both the App and Firestore instances - // are deleted, which emulates the way an end user would experience their - // application being killed and later re-launched by the user. - DeleteFirestore(db); - DeleteApp(app); - - // We restart the app with the same name and options to check that the - // previous instance's persistent storage is actually cleared after the - // restart. Although calling TestFirestore() with no arguments here would do - // the same thing, we explicitly specify an app_name to be clear that we want - // a new Firestore instance for the same Firebase app. - Firestore* db_2 = TestFirestore(app_name); - EXPECT_THAT(db_2->ClearPersistence(), FutureSucceeds()); - DocumentReference document_2 = db_2->Document(path); - Future await_get = document_2.Get(Source::kCache); - Await(await_get); - EXPECT_EQ(await_get.status(), FutureStatus::kFutureStatusComplete); - EXPECT_EQ(await_get.error(), Error::kErrorUnavailable); -} - -TEST_F(FirestoreIntegrationTest, ClearPersistenceWhileRunningFails) { - // Call EnableNetwork() in order to ensure that Firestore is fully - // initialized before clearing persistence. EnableNetwork() is chosen because - // it is easy to call. - Await(TestFirestore()->EnableNetwork()); - Future await_clear_persistence = TestFirestore()->ClearPersistence(); - Await(await_clear_persistence); - EXPECT_EQ(await_clear_persistence.status(), - FutureStatus::kFutureStatusComplete); - EXPECT_EQ(await_clear_persistence.error(), Error::kErrorFailedPrecondition); -} - -// Note: this test only exists in C++. -TEST_F(FirestoreIntegrationTest, DomainObjectsReferToSameFirestoreInstance) { - EXPECT_EQ(TestFirestore(), TestFirestore()->Document("foo/bar").firestore()); - EXPECT_EQ(TestFirestore(), TestFirestore()->Collection("foo").firestore()); -} - -TEST_F(FirestoreIntegrationTest, AuthWorks) { - // This test only works locally or on guitar because it depends on a live - // Auth backend. - if (getenv("UNITTEST_ON_FORGE") != nullptr) { - LogWarning("Skipped AuthWorks test: incompatible with Forge"); - return; - } - - // This app instance is managed by the text fixture. - App* app = GetApp(); - EXPECT_NE(app, nullptr); - - InitResult init_result; - auto auth = UniquePtr(Auth::GetAuth(app, &init_result)); -#if defined(__ANDROID__) - if (init_result != kInitResultSuccess) { - // On Android, it's possible for the Auth library built at head to be too - // new for the version of Play Services available in the Android emulator. - // In this case, Auth will fail to initialize. Meanwhile, there's no simple - // way to detect if the Android app is running in an emulator running on - // Forge. Consequently, just punt if Auth fails to initialize. - LogWarning("Skipped AuthWorks test: Auth missing or failed to initialize"); - return; - } -#else - ASSERT_EQ(init_result, kInitResultSuccess); -#endif - - auto db = UniquePtr(Firestore::GetInstance(app, &init_result)); - EXPECT_EQ(init_result, kInitResultSuccess); - - // Performing a write will initialize Firestore's worker and get the current - // user and token from Auth. - DocumentReference doc = db->Collection(util::CreateAutoId()).Document(); - WriteDocument(doc, MapFieldValue{{"foo", FieldValue::Integer(42)}}); - - // Signing in should trigger an AuthStateListener event. - auto signin = auth->SignInAnonymously(); - Await(signin); - FailIfUnsuccessful("SignInAnonymously", signin); - - // Writing again will trigger another pull of the token. - WriteDocument(doc, MapFieldValue{{"foo", FieldValue::Integer(43)}}); -} - -#if !defined(__ANDROID__) -// This test is to ensure b/172986326 doesn't regress. -// Note: this test only exists in C++. -TEST_F(FirestoreIntegrationTest, FirestoreCanBeDeletedFromTransaction) { - auto* app = App::Create(this->app()->options(), "foo"); - auto* db = Firestore::GetInstance(app); - - auto future = db->RunTransaction( - [](Transaction&, std::string&) { return Error::kErrorOk; }); - - std::future deletion; - std::promise callback_done_promise; - auto callback_done = callback_done_promise.get_future(); - future.AddOnCompletion([&](const Future&) mutable { - deletion = std::async([db] { delete db; }); - callback_done_promise.set_value(); - }); - - Await(future); - callback_done.wait(); - deletion.wait(); -} -#endif // #if !defined(__ANDROID__) - -#if defined(__ANDROID__) -TEST_F(FirestoreAndroidIntegrationTest, - CanDeleteFirestoreInstanceOnJavaMainThread) { - jni::Env env; - Firestore* db = TestFirestore(); - auto runnable = MakeJniRunnable(env, [db] { delete db; }); - - jni::Local task = runnable.RunOnMainThread(env); - - Await(env, task); - EXPECT_TRUE(task.IsSuccessful(env)); - DisownFirestore(db); // Avoid double-deletion of the `db`. -} -#endif // defined(__ANDROID__) - -#endif // defined(FIRESTORE_STUB_BUILD) - -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/includes_test.cc b/firestore/src/tests/includes_test.cc deleted file mode 100644 index de52313812..0000000000 --- a/firestore/src/tests/includes_test.cc +++ /dev/null @@ -1,87 +0,0 @@ -#include - -#include "devtools/build/runtime/get_runfiles_dir.h" -#include "firestore/src/include/firebase/firestore.h" -#include "firestore/src/tests/firestore_integration_test.h" -#include "gtest/gtest.h" - -namespace firebase { -namespace firestore { - -// This class is a friend of `Firestore`, necessary to access `GetTestInstance`. -class IncludesTest : public testing::Test { - public: - Firestore* CreateFirestore(App* app) { - return new Firestore(CreateTestFirestoreInternal(app)); - } -}; - -namespace { - -struct TestListener : EventListener { - void OnEvent(const int&, Error, const std::string&) override {} -}; - -struct TestTransactionFunction : TransactionFunction { - Error Apply(Transaction&, std::string&) override { return Error::kErrorOk; } -}; - -// This test makes sure that all the objects in Firestore public API are -// available from just including "firestore.h". -// If this test compiles, that is sufficient. -// Not using `FirestoreIntegrationTest` to avoid any headers it includes. -TEST_F(IncludesTest, TestIncludingFirestoreHeaderIsSufficient) { - std::string google_json_dir = devtools_build::testonly::GetTestSrcdir() + - "/google3/firebase/firestore/client/cpp/"; - App::SetDefaultConfigPath(google_json_dir.c_str()); - -#if defined(__ANDROID__) - App* app = App::Create(nullptr, nullptr); - -#elif defined(FIRESTORE_STUB_BUILD) - // Stubs don't load values from `GoogleService-Info.plist`/etc., so the app - // has to be configured explicitly. - AppOptions options; - options.set_project_id("foo"); - options.set_app_id("foo"); - options.set_api_key("foo"); - App* app = App::Create(options); - -#else - App* app = App::Create(); - -#endif // defined(__ANDROID__) - - Firestore* firestore = CreateFirestore(app); - - // Check that Firestore isn't just forward-declared. - DocumentReference doc = firestore->Document("foo/bar"); - Future future = doc.Get(); - DocumentChange doc_change; - DocumentReference doc_ref; - DocumentSnapshot doc_snap; - FieldPath field_path; - FieldValue field_value; - ListenerRegistration listener_registration; - MapFieldValue map_field_value; - MetadataChanges metadata_changes = MetadataChanges::kExclude; - Query query; - QuerySnapshot query_snapshot; - SetOptions set_options; - Settings settings; - SnapshotMetadata snapshot_metadata; - Source source = Source::kDefault; - // Cannot default-construct a `Transaction`. - WriteBatch write_batch; - - TestListener test_listener; - TestTransactionFunction test_transaction_function; - - Timestamp timestamp; - GeoPoint geo_point; - Error error = Error::kErrorOk; -} - -} // namespace -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/listener_registration_test.cc b/firestore/src/tests/listener_registration_test.cc deleted file mode 100644 index c1c26cd6d3..0000000000 --- a/firestore/src/tests/listener_registration_test.cc +++ /dev/null @@ -1,182 +0,0 @@ -#include - -#include "firestore/src/include/firebase/firestore.h" -#include "firestore/src/tests/firestore_integration_test.h" -#if defined(__ANDROID__) -#include "firestore/src/android/listener_registration_android.h" -#include "firestore/src/common/wrapper_assertions.h" -#elif defined(FIRESTORE_STUB_BUILD) -#include "firestore/src/stub/listener_registration_stub.h" -#endif // defined(__ANDROID__) - -#include "testing/base/public/gmock.h" -#include "gtest/gtest.h" - -// These test cases are in sync with native iOS client SDK test -// Firestore/Example/Tests/Integration/API/FIRListenerRegistrationTests.mm -// and native Android client SDK test -// firebase_firestore/tests/integration_tests/src/com/google/firebase/firestore/ListenerRegistrationTest.java - -namespace firebase { -namespace firestore { - -using ListenerRegistrationCommonTest = testing::Test; - -class ListenerRegistrationTest : public FirestoreIntegrationTest { - public: - ListenerRegistrationTest() { - TestFirestore()->set_log_level(LogLevel::kLogLevelDebug); - } -}; - -// These tests don't work with stubs. -#if !defined(FIRESTORE_STUB_BUILD) - -TEST_F(ListenerRegistrationTest, TestCanBeRemoved) { - CollectionReference collection = Collection(); - DocumentReference document = collection.Document(); - - TestEventListener listener_one("a listener to be removed"); - TestEventListener listener_two("a listener to be removed"); - ListenerRegistration one = listener_one.AttachTo(&collection); - ListenerRegistration two = listener_two.AttachTo(&document); - - // Initial events - Await(listener_one); - Await(listener_two); - EXPECT_EQ(1, listener_one.event_count()); - EXPECT_EQ(1, listener_two.event_count()); - - // Trigger new events - WriteDocument(document, {{"foo", FieldValue::String("bar")}}); - - // Write events should have triggered - EXPECT_EQ(2, listener_one.event_count()); - EXPECT_EQ(2, listener_two.event_count()); - - // No more events should occur - one.Remove(); - two.Remove(); - - WriteDocument(document, {{"foo", FieldValue::String("new-bar")}}); - - // Assert no events actually occurred - EXPECT_EQ(2, listener_one.event_count()); - EXPECT_EQ(2, listener_two.event_count()); -} - -TEST_F(ListenerRegistrationTest, TestCanBeRemovedTwice) { - CollectionReference collection = Collection(); - DocumentReference document = collection.Document(); - - TestEventListener listener_one("a listener to be removed"); - TestEventListener listener_two("a listener to be removed"); - ListenerRegistration one = listener_one.AttachTo(&collection); - ListenerRegistration two = listener_two.AttachTo(&document); - - one.Remove(); - EXPECT_NO_THROW(one.Remove()); - - two.Remove(); - EXPECT_NO_THROW(two.Remove()); -} - -TEST_F(ListenerRegistrationTest, TestCanBeRemovedIndependently) { - CollectionReference collection = Collection(); - DocumentReference document = collection.Document(); - - TestEventListener listener_one("listener one"); - TestEventListener listener_two("listener two"); - ListenerRegistration one = listener_one.AttachTo(&collection); - ListenerRegistration two = listener_two.AttachTo(&collection); - - // Initial events - Await(listener_one); - Await(listener_two); - - // Triger new events - WriteDocument(document, {{"foo", FieldValue::String("bar")}}); - - // Write events should have triggered - EXPECT_EQ(2, listener_one.event_count()); - EXPECT_EQ(2, listener_two.event_count()); - - // Should leave listener number two unaffected - one.Remove(); - - WriteDocument(document, {{"foo", FieldValue::String("new-bar")}}); - - // Assert only events for listener number two actually occurred - EXPECT_EQ(2, listener_one.event_count()); - EXPECT_EQ(3, listener_two.event_count()); - - // No more events should occur - two.Remove(); - - // The following check does not exist in the corresponding Android and iOS - // native client SDKs tests. - WriteDocument(document, {{"foo", FieldValue::String("brand-new-bar")}}); - EXPECT_EQ(2, listener_one.event_count()); - EXPECT_EQ(3, listener_two.event_count()); -} - -#endif // defined(FIRESTORE_STUB_BUILD) - -#if defined(__ANDROID__) -// TODO(b/136011600): the mechanism for creating internals doesn't work on iOS. -// The most valuable test is making sure that a copy of a registration can be -// used to remove the listener. - -TEST_F(ListenerRegistrationCommonTest, Construction) { - auto* internal = testutil::NewInternal(); - auto registration = MakePublic(internal); - EXPECT_EQ(internal, GetInternal(registration)); - - ListenerRegistration reg_default; - EXPECT_EQ(nullptr, GetInternal(reg_default)); - - ListenerRegistration reg_copy(registration); - EXPECT_EQ(internal, GetInternal(reg_copy)); - - ListenerRegistration reg_move(std::move(registration)); - EXPECT_EQ(internal, GetInternal(reg_move)); - - // ListenerRegistrations are normally owned by FirestoreInternal so the - // public ListenerRegistration does not delete the internal instance. - delete internal; -} - -TEST_F(ListenerRegistrationCommonTest, Assignment) { - auto* internal = testutil::NewInternal(); - auto registration = MakePublic(internal); - ListenerRegistration reg_copy; - reg_copy = registration; - EXPECT_EQ(internal, GetInternal(reg_copy)); - - ListenerRegistration reg_move; - reg_move = std::move(registration); - EXPECT_EQ(internal, GetInternal(reg_move)); - - // ListenerRegistrations are normally owned by FirestoreInternal so the - // public ListenerRegistration does not delete the internal instance. - delete internal; -} - -TEST_F(ListenerRegistrationCommonTest, Remove) { - auto* internal = testutil::NewInternal(); - auto registration = MakePublic(internal); - ListenerRegistration reg_copy; - reg_copy = registration; - - registration.Remove(); - reg_copy.Remove(); - - // ListenerRegistrations are normally owned by FirestoreInternal so the - // public ListenerRegistration does not delete the internal instance. - delete internal; -} - -#endif // defined(__ANDROID__) - -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/numeric_transforms_test.cc b/firestore/src/tests/numeric_transforms_test.cc deleted file mode 100644 index a45e377092..0000000000 --- a/firestore/src/tests/numeric_transforms_test.cc +++ /dev/null @@ -1,248 +0,0 @@ -#include - -#include "firestore/src/include/firebase/firestore.h" -#include "firestore/src/tests/firestore_integration_test.h" -#include "firestore/src/tests/util/event_accumulator.h" -#include "testing/base/public/gmock.h" -#include "gtest/gtest.h" - -namespace firebase { -namespace firestore { - -using Type = FieldValue::Type; - -using ServerTimestampBehavior = DocumentSnapshot::ServerTimestampBehavior; - -const char* TypeName(Type type) { - switch (type) { - case Type::kNull: - return "kNull"; - case Type::kBoolean: - return "kBoolean"; - case Type::kInteger: - return "kInteger"; - case Type::kDouble: - return "kDouble"; - case Type::kTimestamp: - return "kTimestamp"; - case Type::kString: - return "kString"; - case Type::kBlob: - return "kBlob"; - case Type::kReference: - return "kReference"; - case Type::kGeoPoint: - return "kGeoPoint"; - case Type::kArray: - return "kArray"; - case Type::kMap: - return "kMap"; - case Type::kDelete: - return "kDelete"; - case Type::kServerTimestamp: - return "kServerTimestamp"; - case Type::kArrayUnion: - return "kArrayUnion"; - case Type::kArrayRemove: - return "kArrayRemove"; - case Type::kIncrementInteger: - return "kIncrementInteger"; - case Type::kIncrementDouble: - return "kIncrementDouble"; - } -} - -void PrintTo(const Type& type, std::ostream* os) { - *os << "Type::" << TypeName(type); -} - -void PrintTo(const FieldValue& f, std::ostream* os) { - *os << f.ToString() << " ("; - PrintTo(f.type(), os); - *os << ")"; -} - -class NumericTransformsTest : public FirestoreIntegrationTest { - public: - NumericTransformsTest() { - doc_ref_ = Document(); - listener_ = - accumulator_.listener()->AttachTo(&doc_ref_, MetadataChanges::kInclude); - - // Wait for initial null snapshot to avoid potential races. - DocumentSnapshot initial_snapshot = accumulator_.AwaitServerEvent(); - EXPECT_FALSE(initial_snapshot.exists()); - } - - ~NumericTransformsTest() override { listener_.Remove(); } - - protected: - /** Writes values and waits for the corresponding snapshot. */ - void WriteInitialData(const MapFieldValue& doc) { - WriteDocument(doc_ref_, doc); - - accumulator_.AwaitRemoteEvent(); - } - - void ExpectLocalAndRemoteValue(int value) { - DocumentSnapshot snap = accumulator_.AwaitLocalEvent(); - ASSERT_EQ(snap.Get("sum"), FieldValue::Integer(value)); - snap = accumulator_.AwaitRemoteEvent(); - ASSERT_EQ(snap.Get("sum"), FieldValue::Integer(value)); - } - - void ExpectLocalAndRemoteValue(double value) { - DocumentSnapshot snap = accumulator_.AwaitLocalEvent(); - ASSERT_EQ(snap.Get("sum"), FieldValue::Double(value)); - snap = accumulator_.AwaitRemoteEvent(); - ASSERT_EQ(snap.Get("sum"), FieldValue::Double(value)); - } - - // A document reference to read and write. - DocumentReference doc_ref_; - - // Accumulator used to capture events during the test. - EventAccumulator accumulator_; - - // Listener registration for a listener maintained during the course of the - // test. - ListenerRegistration listener_; -}; - -TEST_F(NumericTransformsTest, CreateDocumentWithIncrement) { - Await(doc_ref_.Set({{"sum", FieldValue::Increment(1337)}})); - - ExpectLocalAndRemoteValue(1337); -} - -TEST_F(NumericTransformsTest, MergeOnNonExistingDocumentWithIncrement) { - MapFieldValue data = {{"sum", FieldValue::Integer(1337)}}; - - Await(doc_ref_.Set(data, SetOptions::Merge())); - - ExpectLocalAndRemoteValue(1337); -} - -TEST_F(NumericTransformsTest, IntegerIncrementWithExistingInteger) { - WriteInitialData({{"sum", FieldValue::Integer(1337)}}); - - Await(doc_ref_.Update({{"sum", FieldValue::Increment(1)}})); - - ExpectLocalAndRemoteValue(1338); -} - -TEST_F(NumericTransformsTest, DoubleIncrementWithExistingDouble) { - WriteInitialData({{"sum", FieldValue::Double(0.5)}}); - - Await(doc_ref_.Update({{"sum", FieldValue::Increment(0.25)}})); - - ExpectLocalAndRemoteValue(0.75); -} - -TEST_F(NumericTransformsTest, IntegerIncrementWithExistingDouble) { - WriteInitialData({{"sum", FieldValue::Double(0.5)}}); - - Await(doc_ref_.Update({{"sum", FieldValue::Increment(1)}})); - - ExpectLocalAndRemoteValue(1.5); -} - -TEST_F(NumericTransformsTest, DoubleIncrementWithExistingInteger) { - WriteInitialData({{"sum", FieldValue::Integer(1)}}); - - Await(doc_ref_.Update({{"sum", FieldValue::Increment(0.5)}})); - - ExpectLocalAndRemoteValue(1.5); -} - -TEST_F(NumericTransformsTest, IntegerIncrementWithExistingString) { - WriteInitialData({{"sum", FieldValue::String("overwrite")}}); - - Await(doc_ref_.Update({{"sum", FieldValue::Increment(1337)}})); - - ExpectLocalAndRemoteValue(1337); -} - -TEST_F(NumericTransformsTest, DoubleIncrementWithExistingString) { - WriteInitialData({{"sum", FieldValue::String("overwrite")}}); - - Await(doc_ref_.Update({{"sum", FieldValue::Increment(1.5)}})); - - ExpectLocalAndRemoteValue(1.5); -} - -TEST_F(NumericTransformsTest, MultipleDoubleIncrements) { - WriteInitialData({{"sum", FieldValue::Double(0.0)}}); - - DisableNetwork(); - - doc_ref_.Update({{"sum", FieldValue::Increment(0.5)}}); - doc_ref_.Update({{"sum", FieldValue::Increment(1.0)}}); - doc_ref_.Update({{"sum", FieldValue::Increment(2.0)}}); - - DocumentSnapshot snap = accumulator_.AwaitLocalEvent(); - - EXPECT_EQ(snap.Get("sum"), FieldValue::Double(0.5)); - - snap = accumulator_.AwaitLocalEvent(); - EXPECT_EQ(snap.Get("sum"), FieldValue::Double(1.5)); - - snap = accumulator_.AwaitLocalEvent(); - EXPECT_EQ(snap.Get("sum"), FieldValue::Double(3.5)); - - EnableNetwork(); - - snap = accumulator_.AwaitRemoteEvent(); - EXPECT_EQ(snap.Get("sum"), FieldValue::Double(3.5)); -} - -TEST_F(NumericTransformsTest, IncrementTwiceInABatch) { - WriteInitialData({{"sum", FieldValue::String("overwrite")}}); - - WriteBatch batch = TestFirestore()->batch(); - - batch.Update(doc_ref_, {{"sum", FieldValue::Increment(1)}}); - batch.Update(doc_ref_, {{"sum", FieldValue::Increment(1)}}); - - Await(batch.Commit()); - - ExpectLocalAndRemoteValue(2); -} - -TEST_F(NumericTransformsTest, IncrementDeleteIncrementInABatch) { - WriteInitialData({{"sum", FieldValue::String("overwrite")}}); - - WriteBatch batch = TestFirestore()->batch(); - - batch.Update(doc_ref_, {{"sum", FieldValue::Increment(1)}}); - batch.Update(doc_ref_, {{"sum", FieldValue::Delete()}}); - batch.Update(doc_ref_, {{"sum", FieldValue::Increment(3)}}); - - Await(batch.Commit()); - - ExpectLocalAndRemoteValue(3); -} - -TEST_F(NumericTransformsTest, ServerTimestampAndIncrement) { - DisableNetwork(); - - doc_ref_.Set({{"sum", FieldValue::ServerTimestamp()}}); - doc_ref_.Set({{"sum", FieldValue::Increment(1)}}); - - DocumentSnapshot snapshot = accumulator_.AwaitLocalEvent(); - EXPECT_EQ(snapshot.Get("sum", ServerTimestampBehavior::kEstimate).type(), - Type::kTimestamp); - - DocumentSnapshot snap = accumulator_.AwaitLocalEvent(); - EXPECT_TRUE(snap.Get("sum").is_integer()); - EXPECT_EQ(snap.Get("sum"), FieldValue::Integer(1)); - - EnableNetwork(); - - snap = accumulator_.AwaitRemoteEvent(); - EXPECT_TRUE(snap.Get("sum").is_integer()); - EXPECT_EQ(1, snap.Get("sum").integer_value()); -} - -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/query_network_test.cc b/firestore/src/tests/query_network_test.cc deleted file mode 100644 index 0d1efe7ba5..0000000000 --- a/firestore/src/tests/query_network_test.cc +++ /dev/null @@ -1,150 +0,0 @@ -#include -#include - -#include "firestore/src/include/firebase/firestore.h" -#include "firestore/src/tests/firestore_integration_test.h" -#include "firestore/src/tests/util/event_accumulator.h" -#if defined(__ANDROID__) -#include "firestore/src/android/query_android.h" -#elif defined(FIRESTORE_STUB_BUILD) -#include "firestore/src/stub/query_stub.h" -#endif // defined(__ANDROID__) - -#include "testing/base/public/gmock.h" -#include "gtest/gtest.h" - -// These test cases are in sync with native iOS client SDK test -// Firestore/Example/Tests/Integration/API/FIRQueryTests.mm -// and native Android client SDK test -// firebase_firestore/tests/integration_tests/src/com/google/firebase/firestore/QueryTest.java - -namespace firebase { -namespace firestore { - -class QueryNetworkTest : public FirestoreIntegrationTest { - protected: - void TestCanHaveMultipleMutationsWhileOfflineImpl() { - CollectionReference collection = Collection(); - - // set a few docs to known values - WriteDocuments(collection, - {{"doc1", {{"key1", FieldValue::String("value1")}}}, - {"doc2", {{"key2", FieldValue::String("value2")}}}}); - - // go offline for the rest of this test - Await(TestFirestore()->DisableNetwork()); - - // apply *multiple* mutations while offline - collection.Document("doc1").Set({{"key1b", FieldValue::String("value1b")}}); - collection.Document("doc2").Set({{"key2b", FieldValue::String("value2b")}}); - - QuerySnapshot snapshot = ReadDocuments(collection); - EXPECT_TRUE(snapshot.metadata().is_from_cache()); - EXPECT_THAT(QuerySnapshotToValues(snapshot), - testing::ElementsAre( - MapFieldValue{{"key1b", FieldValue::String("value1b")}}, - MapFieldValue{{"key2b", FieldValue::String("value2b")}})); - - Await(TestFirestore()->EnableNetwork()); - } - - void TestWatchSurvivesNetworkDisconnectImpl() { - CollectionReference collection = Collection(); - EventAccumulator accumulator; - accumulator.listener()->set_print_debug_info(true); - ListenerRegistration registration = accumulator.listener()->AttachTo( - &collection, MetadataChanges::kInclude); - EXPECT_TRUE(accumulator.AwaitRemoteEvent().empty()); - - Await(TestFirestore()->DisableNetwork()); - auto added = - collection.Add(MapFieldValue{{"foo", FieldValue::ServerTimestamp()}}); - Await(TestFirestore()->EnableNetwork()); - Await(added); - - QuerySnapshot snapshot = accumulator.AwaitServerEvent(); - EXPECT_FALSE(snapshot.empty()); - EXPECT_EQ(1, snapshot.size()); - - registration.Remove(); - } - - void TestQueriesFireFromCacheWhenOfflineImpl() { - CollectionReference collection = - Collection({{"a", {{"foo", FieldValue::Integer(1)}}}}); - EventAccumulator accumulator; - accumulator.listener()->set_print_debug_info(true); - ListenerRegistration registration = accumulator.listener()->AttachTo( - &collection, MetadataChanges::kInclude); - - // initial event - QuerySnapshot snapshot = accumulator.AwaitServerEvent(); - EXPECT_THAT( - QuerySnapshotToValues(snapshot), - testing::ElementsAre(MapFieldValue{{"foo", FieldValue::Integer(1)}})); - EXPECT_FALSE(snapshot.metadata().is_from_cache()); - - // offline event with is_from_cache=true - Await(TestFirestore()->DisableNetwork()); - snapshot = accumulator.Await(); - EXPECT_TRUE(snapshot.metadata().is_from_cache()); - - // back online event with is_from_cache=false - Await(TestFirestore()->EnableNetwork()); - snapshot = accumulator.Await(); - EXPECT_FALSE(snapshot.metadata().is_from_cache()); - registration.Remove(); - } -}; - -#if defined(__ANDROID__) -// Due to how the integration test is set on Android, we cannot make the tests -// that call DisableNetwork/EnableNetwork run in parallel. So we manually make -// them here in a separate test file and run in serial. - -TEST_F(QueryNetworkTest, EnableDisableNetwork) { - std::cout - << "[ RUN ] " - "FirestoreIntegrationTest.TestCanHaveMultipleMutationsWhileOffline" - << std::endl; - TestCanHaveMultipleMutationsWhileOfflineImpl(); - std::cout - << "[ DONE ] " - "FirestoreIntegrationTest.TestCanHaveMultipleMutationsWhileOffline" - << std::endl; - - std::cout - << "[ RUN ] FirestoreIntegrationTest.WatchSurvivesNetworkDisconnect" - << std::endl; - TestWatchSurvivesNetworkDisconnectImpl(); - std::cout - << "[ DONE ] FirestoreIntegrationTest.WatchSurvivesNetworkDisconnect" - << std::endl; - - std::cout << "[ RUN ] " - "FirestoreIntegrationTest.TestQueriesFireFromCacheWhenOffline" - << std::endl; - TestQueriesFireFromCacheWhenOfflineImpl(); - std::cout << "[ DONE ] " - "FirestoreIntegrationTest.TestQueriesFireFromCacheWhenOffline" - << std::endl; -} - -#else - -TEST_F(QueryNetworkTest, TestCanHaveMultipleMutationsWhileOffline) { - TestCanHaveMultipleMutationsWhileOfflineImpl(); -} - -TEST_F(QueryNetworkTest, TestWatchSurvivesNetworkDisconnect) { - TestWatchSurvivesNetworkDisconnectImpl(); -} - -TEST_F(QueryNetworkTest, TestQueriesFireFromCacheWhenOffline) { - TestQueriesFireFromCacheWhenOfflineImpl(); -} - -#endif // defined(__ANDROID__) - -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/query_snapshot_test.cc b/firestore/src/tests/query_snapshot_test.cc deleted file mode 100644 index f61e1117df..0000000000 --- a/firestore/src/tests/query_snapshot_test.cc +++ /dev/null @@ -1,34 +0,0 @@ -#include - -#include "firestore/src/common/wrapper_assertions.h" -#include "firestore/src/include/firebase/firestore.h" -#if defined(__ANDROID__) -#include "firestore/src/android/query_snapshot_android.h" -#elif defined(FIRESTORE_STUB_BUILD) -#include "firestore/src/stub/query_snapshot_stub.h" -#endif // defined(__ANDROID__) - -#include "testing/base/public/gmock.h" -#include "gtest/gtest.h" - -namespace firebase { -namespace firestore { - -#if defined(__ANDROID__) || defined(FIRESTORE_STUB_BUILD) - -using QuerySnapshotTest = testing::Test; - -TEST_F(QuerySnapshotTest, Construction) { - testutil::AssertWrapperConstructionContract(); -} - -TEST_F(QuerySnapshotTest, Assignment) { - testutil::AssertWrapperAssignmentContract(); -} - -#endif // defined(__ANDROID__) || defined(FIRESTORE_STUB_BUILD) - -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/query_test.cc b/firestore/src/tests/query_test.cc deleted file mode 100644 index a562baf298..0000000000 --- a/firestore/src/tests/query_test.cc +++ /dev/null @@ -1,1013 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "firestore/src/common/macros.h" -#include "firestore/src/include/firebase/firestore.h" -#include "firestore/src/include/firebase/firestore/field_value.h" -#include "firestore/src/include/firebase/firestore/map_field_value.h" -#include "firestore/src/tests/firestore_integration_test.h" -#include "firestore/src/tests/util/event_accumulator.h" - -#if defined(__ANDROID__) -#include "firestore/src/android/query_android.h" -#include "firestore/src/common/wrapper_assertions.h" -#elif defined(FIRESTORE_STUB_BUILD) -#include "firestore/src/common/wrapper_assertions.h" -#include "firestore/src/stub/query_stub.h" -#endif // defined(__ANDROID__) - -#include "testing/base/public/gmock.h" -#include "gtest/gtest.h" -#include "firebase/firestore/firestore_errors.h" -#include "Firestore/core/src/util/firestore_exceptions.h" - -// These test cases are in sync with native iOS client SDK test -// Firestore/Example/Tests/Integration/API/FIRQueryTests.mm -// and native Android client SDK test -// firebase_firestore/tests/integration_tests/src/com/google/firebase/firestore/QueryTest.java -// -// Some test cases are moved to query_network_test.cc. Check that file for more -// details. - -namespace firebase { -namespace firestore { -namespace { - -using ::testing::ElementsAreArray; -using ::testing::IsEmpty; - -std::vector AllDocsExcept( - const std::map& docs, - const std::vector& excluded) { - std::vector expected_docs; - for (const auto& e : docs) { - if (std::find(excluded.begin(), excluded.end(), e.first) == - excluded.end()) { - expected_docs.push_back(e.second); - } - } - return expected_docs; -} - -} // namespace - -#if !defined(FIRESTORE_STUB_BUILD) - -TEST_F(FirestoreIntegrationTest, TestLimitQueries) { - CollectionReference collection = - Collection({{"a", {{"k", FieldValue::String("a")}}}, - {"b", {{"k", FieldValue::String("b")}}}, - {"c", {{"k", FieldValue::String("c")}}}}); - QuerySnapshot snapshot = ReadDocuments(collection.Limit(2)); - EXPECT_EQ(std::vector({{{"k", FieldValue::String("a")}}, - {{"k", FieldValue::String("b")}}}), - QuerySnapshotToValues(snapshot)); -} - -TEST_F(FirestoreIntegrationTest, TestLimitQueriesUsingDescendingSortOrder) { - CollectionReference collection = Collection( - {{"a", - {{"k", FieldValue::String("a")}, {"sort", FieldValue::Integer(0)}}}, - {"b", - {{"k", FieldValue::String("b")}, {"sort", FieldValue::Integer(1)}}}, - {"c", - {{"k", FieldValue::String("c")}, {"sort", FieldValue::Integer(1)}}}, - {"d", - {{"k", FieldValue::String("d")}, {"sort", FieldValue::Integer(2)}}}}); - QuerySnapshot snapshot = ReadDocuments(collection.Limit(2).OrderBy( - FieldPath({"sort"}), Query::Direction::kDescending)); - EXPECT_EQ( - std::vector( - {{{"k", FieldValue::String("d")}, {"sort", FieldValue::Integer(2)}}, - {{"k", FieldValue::String("c")}, {"sort", FieldValue::Integer(1)}}}), - QuerySnapshotToValues(snapshot)); -} - -#if defined(__ANDROID__) && FIRESTORE_HAVE_EXCEPTIONS -TEST_F(FirestoreIntegrationTest, TestLimitToLastMustAlsoHaveExplicitOrderBy) { - CollectionReference collection = Collection(); - - EXPECT_THROW(Await(collection.LimitToLast(2).Get()), std::logic_error); -} -#endif // defined(__ANDROID__) && FIRESTORE_HAVE_EXCEPTIONS - -// Two queries that mapped to the same target ID are referred to as "mirror -// queries". An example for a mirror query is a LimitToLast() query and a -// Limit() query that share the same backend Target ID. Since LimitToLast() -// queries are sent to the backend with a modified OrderBy() clause, they can -// map to the same target representation as Limit() query, even if both queries -// appear separate to the user. -TEST_F(FirestoreIntegrationTest, - TestListenUnlistenRelistenSequenceOfMirrorQueries) { - CollectionReference collection = Collection( - {{"a", - {{"k", FieldValue::String("a")}, {"sort", FieldValue::Integer(0)}}}, - {"b", - {{"k", FieldValue::String("b")}, {"sort", FieldValue::Integer(1)}}}, - {"c", - {{"k", FieldValue::String("c")}, {"sort", FieldValue::Integer(1)}}}, - {"d", - {{"k", FieldValue::String("d")}, {"sort", FieldValue::Integer(2)}}}}); - - // Set up `limit` query. - Query limit = - collection.Limit(2).OrderBy("sort", Query::Direction::kAscending); - EventAccumulator limit_accumulator; - ListenerRegistration limit_registration = - limit_accumulator.listener()->AttachTo(&limit); - - // Set up mirroring `limitToLast` query. - Query limit_to_last = - collection.LimitToLast(2).OrderBy("sort", Query::Direction::kDescending); - EventAccumulator limit_to_last_accumulator; - ListenerRegistration limit_to_last_registration = - limit_to_last_accumulator.listener()->AttachTo(&limit_to_last); - - // Verify both queries get expected result. - QuerySnapshot snapshot = limit_accumulator.Await(); - EXPECT_THAT( - QuerySnapshotToValues(snapshot), - testing::ElementsAre(MapFieldValue{{"k", FieldValue::String("a")}, - {"sort", FieldValue::Integer(0)}}, - MapFieldValue{{"k", FieldValue::String("b")}, - {"sort", FieldValue::Integer(1)}})); - snapshot = limit_to_last_accumulator.Await(); - EXPECT_THAT( - QuerySnapshotToValues(snapshot), - testing::ElementsAre(MapFieldValue{{"k", FieldValue::String("b")}, - {"sort", FieldValue::Integer(1)}}, - MapFieldValue{{"k", FieldValue::String("a")}, - {"sort", FieldValue::Integer(0)}})); - - // Unlisten then re-listen to the `limit` query. - limit_registration.Remove(); - limit_registration = limit_accumulator.listener()->AttachTo(&limit); - - // Verify `limit` query still works. - snapshot = limit_accumulator.Await(); - EXPECT_THAT( - QuerySnapshotToValues(snapshot), - testing::ElementsAre(MapFieldValue{{"k", FieldValue::String("a")}, - {"sort", FieldValue::Integer(0)}}, - MapFieldValue{{"k", FieldValue::String("b")}, - {"sort", FieldValue::Integer(1)}})); - - // Add a document that would change the result set. - Await(collection.Add(MapFieldValue{{"k", FieldValue::String("e")}, - {"sort", FieldValue::Integer(-1)}})); - - // Verify both queries get expected result. - snapshot = limit_accumulator.Await(); - EXPECT_THAT( - QuerySnapshotToValues(snapshot), - testing::ElementsAre(MapFieldValue{{"k", FieldValue::String("e")}, - {"sort", FieldValue::Integer(-1)}}, - MapFieldValue{{"k", FieldValue::String("a")}, - {"sort", FieldValue::Integer(0)}})); - snapshot = limit_to_last_accumulator.Await(); - EXPECT_THAT( - QuerySnapshotToValues(snapshot), - testing::ElementsAre(MapFieldValue{{"k", FieldValue::String("a")}, - {"sort", FieldValue::Integer(0)}}, - MapFieldValue{{"k", FieldValue::String("e")}, - {"sort", FieldValue::Integer(-1)}})); - - // Unlisten to `LimitToLast`, update a doc, then relisten to `LimitToLast` - limit_to_last_registration.Remove(); - Await(collection.Document("a").Update(MapFieldValue{ - {"k", FieldValue::String("a")}, {"sort", FieldValue::Integer(-2)}})); - limit_to_last_registration = - limit_to_last_accumulator.listener()->AttachTo(&limit_to_last); - - // Verify both queries get expected result. - snapshot = limit_accumulator.Await(); - EXPECT_THAT( - QuerySnapshotToValues(snapshot), - testing::ElementsAre(MapFieldValue{{"k", FieldValue::String("a")}, - {"sort", FieldValue::Integer(-2)}}, - MapFieldValue{{"k", FieldValue::String("e")}, - {"sort", FieldValue::Integer(-1)}})); - snapshot = limit_to_last_accumulator.Await(); - EXPECT_THAT( - QuerySnapshotToValues(snapshot), - testing::ElementsAre(MapFieldValue{{"k", FieldValue::String("e")}, - {"sort", FieldValue::Integer(-1)}}, - MapFieldValue{{"k", FieldValue::String("a")}, - {"sort", FieldValue::Integer(-2)}})); -} - -TEST_F(FirestoreIntegrationTest, - TestKeyOrderIsDescendingForDescendingInequality) { - CollectionReference collection = - Collection({{"a", {{"foo", FieldValue::Integer(42)}}}, - {"b", {{"foo", FieldValue::Double(42.0)}}}, - {"c", {{"foo", FieldValue::Integer(42)}}}, - {"d", {{"foo", FieldValue::Integer(21)}}}, - {"e", {{"foo", FieldValue::Double(21.0)}}}, - {"f", {{"foo", FieldValue::Integer(66)}}}, - {"g", {{"foo", FieldValue::Double(66.0)}}}}); - QuerySnapshot snapshot = ReadDocuments( - collection.WhereGreaterThan("foo", FieldValue::Integer(21)) - .OrderBy(FieldPath({"foo"}), Query::Direction::kDescending)); - EXPECT_EQ(std::vector({"g", "f", "c", "b", "a"}), - QuerySnapshotToIds(snapshot)); -} - -TEST_F(FirestoreIntegrationTest, TestUnaryFilterQueries) { - CollectionReference collection = Collection( - {{"a", {{"null", FieldValue::Null()}, {"nan", FieldValue::Double(NAN)}}}, - {"b", {{"null", FieldValue::Null()}, {"nan", FieldValue::Integer(0)}}}, - {"c", - {{"null", FieldValue::Boolean(false)}, - {"nan", FieldValue::Double(NAN)}}}}); - QuerySnapshot snapshot = - ReadDocuments(collection.WhereEqualTo("null", FieldValue::Null()) - .WhereEqualTo("nan", FieldValue::Double(NAN))); - EXPECT_EQ(std::vector({{{"null", FieldValue::Null()}, - {"nan", FieldValue::Double(NAN)}}}), - QuerySnapshotToValues(snapshot)); -} - -TEST_F(FirestoreIntegrationTest, TestQueryWithFieldPaths) { - CollectionReference collection = - Collection({{"a", {{"a", FieldValue::Integer(1)}}}, - {"b", {{"a", FieldValue::Integer(2)}}}, - {"c", {{"a", FieldValue::Integer(3)}}}}); - QuerySnapshot snapshot = ReadDocuments( - collection.WhereLessThan(FieldPath({"a"}), FieldValue::Integer(3)) - .OrderBy(FieldPath({"a"}), Query::Direction::kDescending)); - EXPECT_EQ(std::vector({"b", "a"}), QuerySnapshotToIds(snapshot)); -} - -TEST_F(FirestoreIntegrationTest, TestFilterOnInfinity) { - CollectionReference collection = - Collection({{"a", {{"inf", FieldValue::Double(INFINITY)}}}, - {"b", {{"inf", FieldValue::Double(-INFINITY)}}}}); - QuerySnapshot snapshot = ReadDocuments( - collection.WhereEqualTo("inf", FieldValue::Double(INFINITY))); - EXPECT_EQ( - std::vector({{{"inf", FieldValue::Double(INFINITY)}}}), - QuerySnapshotToValues(snapshot)); -} - -TEST_F(FirestoreIntegrationTest, TestWillNotGetMetadataOnlyUpdates) { - CollectionReference collection = - Collection({{"a", {{"v", FieldValue::String("a")}}}, - {"b", {{"v", FieldValue::String("b")}}}}); - - TestEventListener listener("no metadata-only update"); - ListenerRegistration registration = listener.AttachTo(&collection); - Await(listener); - EXPECT_EQ(1, listener.event_count()); - EXPECT_EQ(std::vector({{{"v", FieldValue::String("a")}}, - {{"v", FieldValue::String("b")}}}), - QuerySnapshotToValues(listener.last_result())); - - WriteDocument(collection.Document("a"), {{"v", FieldValue::String("a1")}}); - EXPECT_EQ(2, listener.event_count()); - EXPECT_EQ(std::vector({{{"v", FieldValue::String("a1")}}, - {{"v", FieldValue::String("b")}}}), - QuerySnapshotToValues(listener.last_result())); - - registration.Remove(); -} - -TEST_F(FirestoreIntegrationTest, - TestCanListenForTheSameQueryWithDifferentOptions) { - CollectionReference collection = Collection(); - WriteDocuments(collection, {{"a", {{"v", FieldValue::String("a")}}}, - {"b", {{"v", FieldValue::String("b")}}}}); - - // Add two listeners, one tracking metadata-change while the other not. - TestEventListener listener("no metadata-only update"); - TestEventListener listener_full("include metadata update"); - - ListenerRegistration registration_full = - listener_full.AttachTo(&collection, MetadataChanges::kInclude); - ListenerRegistration registration = listener.AttachTo(&collection); - - Await(listener); - Await(listener_full, 2); // Let's make sure both events triggered. - - EXPECT_EQ(1, listener.event_count()); - EXPECT_EQ(std::vector({{{"v", FieldValue::String("a")}}, - {{"v", FieldValue::String("b")}}}), - QuerySnapshotToValues(listener.last_result())); - EXPECT_EQ(2, listener_full.event_count()); - EXPECT_EQ(std::vector({{{"v", FieldValue::String("a")}}, - {{"v", FieldValue::String("b")}}}), - QuerySnapshotToValues(listener_full.last_result(1))); - EXPECT_EQ(std::vector({{{"v", FieldValue::String("a")}}, - {{"v", FieldValue::String("b")}}}), - QuerySnapshotToValues(listener_full.last_result())); - EXPECT_TRUE(listener_full.last_result(1).metadata().is_from_cache()); - EXPECT_FALSE(listener_full.last_result().metadata().is_from_cache()); - - // Change document to trigger the listeners. - WriteDocument(collection.Document("a"), {{"v", FieldValue::String("a1")}}); - // Only one event without options - EXPECT_EQ(2, listener.event_count()); - EXPECT_EQ(std::vector({{{"v", FieldValue::String("a1")}}, - {{"v", FieldValue::String("b")}}}), - QuerySnapshotToValues(listener.last_result())); - // Expect two events for the write, once from latency compensation and once - // from the acknowledgement from the server. - Await(listener_full, 4); // Let's make sure both events triggered. - EXPECT_EQ(4, listener_full.event_count()); - EXPECT_EQ(std::vector({{{"v", FieldValue::String("a1")}}, - {{"v", FieldValue::String("b")}}}), - QuerySnapshotToValues(listener_full.last_result(1))); - EXPECT_EQ(std::vector({{{"v", FieldValue::String("a1")}}, - {{"v", FieldValue::String("b")}}}), - QuerySnapshotToValues(listener_full.last_result())); - EXPECT_TRUE(listener_full.last_result(1).metadata().has_pending_writes()); - EXPECT_FALSE(listener_full.last_result().metadata().has_pending_writes()); - - // Change document again to trigger the listeners. - WriteDocument(collection.Document("b"), {{"v", FieldValue::String("b1")}}); - // Only one event without options - EXPECT_EQ(3, listener.event_count()); - EXPECT_EQ(std::vector({{{"v", FieldValue::String("a1")}}, - {{"v", FieldValue::String("b1")}}}), - QuerySnapshotToValues(listener.last_result())); - // Expect two events for the write, once from latency compensation and once - // from the acknowledgement from the server. - Await(listener_full, 6); // Let's make sure both events triggered. - EXPECT_EQ(6, listener_full.event_count()); - EXPECT_EQ(std::vector({{{"v", FieldValue::String("a1")}}, - {{"v", FieldValue::String("b1")}}}), - QuerySnapshotToValues(listener_full.last_result(1))); - EXPECT_EQ(std::vector({{{"v", FieldValue::String("a1")}}, - {{"v", FieldValue::String("b1")}}}), - QuerySnapshotToValues(listener_full.last_result())); - EXPECT_TRUE(listener_full.last_result(1).metadata().has_pending_writes()); - EXPECT_FALSE(listener_full.last_result().metadata().has_pending_writes()); - - // Unregister listeners. - registration.Remove(); - registration_full.Remove(); -} - -TEST_F(FirestoreIntegrationTest, TestCanListenForQueryMetadataChanges) { - CollectionReference collection = - Collection({{"1", - {{"sort", FieldValue::Double(1.0)}, - {"filter", FieldValue::Boolean(true)}, - {"key", FieldValue::Integer(1)}}}, - {"2", - {{"sort", FieldValue::Double(2.0)}, - {"filter", FieldValue::Boolean(true)}, - {"key", FieldValue::Integer(2)}}}, - {"3", - {{"sort", FieldValue::Double(3.0)}, - {"filter", FieldValue::Boolean(true)}, - {"key", FieldValue::Integer(3)}}}, - {"4", - {{"sort", FieldValue::Double(4.0)}, - {"filter", FieldValue::Boolean(false)}, - {"key", FieldValue::Integer(4)}}}}); - - // The first query does not have any document cached. - TestEventListener listener1("listener to the first query"); - Query collection_with_filter1 = - collection.WhereLessThan("key", FieldValue::Integer(4)); - ListenerRegistration registration1 = - listener1.AttachTo(&collection_with_filter1); - Await(listener1); - EXPECT_EQ(1, listener1.event_count()); - EXPECT_EQ(std::vector({"1", "2", "3"}), - QuerySnapshotToIds(listener1.last_result())); - - // The second query has document cached from the first query. - TestEventListener listener2("listener to the second query"); - Query collection_with_filter2 = - collection.WhereEqualTo("filter", FieldValue::Boolean(true)); - ListenerRegistration registration2 = - listener2.AttachTo(&collection_with_filter2, MetadataChanges::kInclude); - Await(listener2, 2); // Let's make sure both events triggered. - EXPECT_EQ(2, listener2.event_count()); - EXPECT_EQ(std::vector({"1", "2", "3"}), - QuerySnapshotToIds(listener2.last_result(1))); - EXPECT_EQ(std::vector({"1", "2", "3"}), - QuerySnapshotToIds(listener2.last_result())); - EXPECT_TRUE(listener2.last_result(1).metadata().is_from_cache()); - EXPECT_FALSE(listener2.last_result().metadata().is_from_cache()); - - // Unregister listeners. - registration1.Remove(); - registration2.Remove(); -} - -TEST_F(FirestoreIntegrationTest, TestCanExplicitlySortByDocumentId) { - CollectionReference collection = - Collection({{"a", {{"key", FieldValue::String("a")}}}, - {"b", {{"key", FieldValue::String("b")}}}, - {"c", {{"key", FieldValue::String("c")}}}}); - // Ideally this would be descending to validate it's different than - // the default, but that requires an extra index - QuerySnapshot snapshot = - ReadDocuments(collection.OrderBy(FieldPath::DocumentId())); - EXPECT_EQ(std::vector({"a", "b", "c"}), - QuerySnapshotToIds(snapshot)); -} - -TEST_F(FirestoreIntegrationTest, TestCanQueryByDocumentId) { - CollectionReference collection = - Collection({{"aa", {{"key", FieldValue::String("aa")}}}, - {"ab", {{"key", FieldValue::String("ab")}}}, - {"ba", {{"key", FieldValue::String("ba")}}}, - {"bb", {{"key", FieldValue::String("bb")}}}}); - - // Query by Document Id. - QuerySnapshot snapshot1 = ReadDocuments(collection.WhereEqualTo( - FieldPath::DocumentId(), FieldValue::String("ab"))); - EXPECT_EQ(std::vector({"ab"}), QuerySnapshotToIds(snapshot1)); - - // Query by Document Ids. - QuerySnapshot snapshot2 = ReadDocuments( - collection - .WhereGreaterThan(FieldPath::DocumentId(), FieldValue::String("aa")) - .WhereLessThanOrEqualTo(FieldPath::DocumentId(), - FieldValue::String("ba"))); - EXPECT_EQ(std::vector({"ab", "ba"}), - QuerySnapshotToIds(snapshot2)); -} - -TEST_F(FirestoreIntegrationTest, TestCanQueryByDocumentIdUsingRefs) { - CollectionReference collection = - Collection({{"aa", {{"key", FieldValue::String("aa")}}}, - {"ab", {{"key", FieldValue::String("ab")}}}, - {"ba", {{"key", FieldValue::String("ba")}}}, - {"bb", {{"key", FieldValue::String("bb")}}}}); - - // Query by Document Id. - QuerySnapshot snapshot1 = ReadDocuments(collection.WhereEqualTo( - FieldPath::DocumentId(), - FieldValue::Reference(collection.Document("ab")))); - EXPECT_EQ(std::vector({"ab"}), QuerySnapshotToIds(snapshot1)); - - // Query by Document Ids. - QuerySnapshot snapshot2 = ReadDocuments( - collection - .WhereGreaterThan(FieldPath::DocumentId(), - FieldValue::Reference(collection.Document("aa"))) - .WhereLessThanOrEqualTo( - FieldPath::DocumentId(), - FieldValue::Reference(collection.Document("ba")))); - EXPECT_EQ(std::vector({"ab", "ba"}), - QuerySnapshotToIds(snapshot2)); -} - -TEST_F(FirestoreIntegrationTest, TestCanQueryWithAndWithoutDocumentKey) { - CollectionReference collection = Collection(); - Await(collection.Add({})); - QuerySnapshot snapshot1 = ReadDocuments(collection.OrderBy( - FieldPath::DocumentId(), Query::Direction::kAscending)); - QuerySnapshot snapshot2 = ReadDocuments(collection); - - EXPECT_EQ(QuerySnapshotToValues(snapshot1), QuerySnapshotToValues(snapshot2)); -} - -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotEqualFilters) { - // These documents are ordered by value in "zip" since the NotEqual filter is - // an inequality, which results in documents being sorted by value. - std::map docs = { - {"a", {{"zip", FieldValue::Double(NAN)}}}, - {"b", {{"zip", FieldValue::Integer(91102)}}}, - {"c", {{"zip", FieldValue::Integer(98101)}}}, - {"d", {{"zip", FieldValue::String("98101")}}}, - {"e", {{"zip", FieldValue::Array({FieldValue::Integer(98101)})}}}, - {"f", - {{"zip", FieldValue::Array({FieldValue::Integer(98101), - FieldValue::Integer(98102)})}}}, - {"g", - {{"zip", FieldValue::Array({FieldValue::String("98101"), - FieldValue::Map({{"zip", FieldValue::Integer( - 98101)}})})}}}, - {"h", {{"zip", FieldValue::Map({{"code", FieldValue::Integer(500)}})}}}, - {"i", {{"code", FieldValue::Integer(500)}}}, - {"j", MapFieldValue{{"zip", FieldValue::Null()}}}, - }; - CollectionReference collection = Collection(docs); - - // Search for zips not matching 98101. - QuerySnapshot snapshot = ReadDocuments( - collection.WhereNotEqualTo("zip", FieldValue::Integer(98101))); - EXPECT_THAT(QuerySnapshotToValues(snapshot), - ElementsAreArray(AllDocsExcept(docs, {"c", "i", "j"}))); -} - -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotEqualFiltersWithObject) { - // These documents are ordered by value in "zip" since the NotEqual filter is - // an inequality, which results in documents being sorted by value. - std::map docs = { - {"a", {{"zip", FieldValue::Double(NAN)}}}, - {"b", {{"zip", FieldValue::Integer(91102)}}}, - {"c", {{"zip", FieldValue::Integer(98101)}}}, - {"d", {{"zip", FieldValue::String("98101")}}}, - {"e", {{"zip", FieldValue::Array({FieldValue::Integer(98101)})}}}, - {"f", - {{"zip", FieldValue::Array({FieldValue::Integer(98101), - FieldValue::Integer(98102)})}}}, - {"g", - {{"zip", FieldValue::Array({FieldValue::String("98101"), - FieldValue::Map({{"zip", FieldValue::Integer( - 98101)}})})}}}, - {"h", {{"zip", FieldValue::Map({{"code", FieldValue::Integer(500)}})}}}, - {"i", {{"code", FieldValue::Integer(500)}}}, - {"j", MapFieldValue{{"zip", FieldValue::Null()}}}, - }; - CollectionReference collection = Collection(docs); - - QuerySnapshot snapshot = ReadDocuments(collection.WhereNotEqualTo( - "zip", FieldValue::Map({{"code", FieldValue::Integer(500)}}))); - EXPECT_THAT(QuerySnapshotToValues(snapshot), - ElementsAreArray(AllDocsExcept(docs, {"h", "i", "j"}))); -} - -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotEqualFiltersWithNull) { - // These documents are ordered by value in "zip" since the NotEqual filter is - // an inequality, which results in documents being sorted by value. - std::map docs = { - {"a", {{"zip", FieldValue::Double(NAN)}}}, - {"b", {{"zip", FieldValue::Integer(91102)}}}, - {"c", {{"zip", FieldValue::Integer(98101)}}}, - {"d", {{"zip", FieldValue::String("98101")}}}, - {"e", {{"zip", FieldValue::Array({FieldValue::Integer(98101)})}}}, - {"f", - {{"zip", FieldValue::Array({FieldValue::Integer(98101), - FieldValue::Integer(98102)})}}}, - {"g", - {{"zip", FieldValue::Array({FieldValue::String("98101"), - FieldValue::Map({{"zip", FieldValue::Integer( - 98101)}})})}}}, - {"h", {{"zip", FieldValue::Map({{"code", FieldValue::Integer(500)}})}}}, - {"i", {{"code", FieldValue::Integer(500)}}}, - {"j", MapFieldValue{{"zip", FieldValue::Null()}}}, - }; - CollectionReference collection = Collection(docs); - - // With Null. - QuerySnapshot snapshot = ReadDocuments(collection.WhereNotEqualTo( - "zip", FieldValue::Map({{"code", FieldValue::Null()}}))); - EXPECT_THAT(QuerySnapshotToValues(snapshot), - ElementsAreArray(AllDocsExcept(docs, {"i", "j"}))); -} - -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotEqualFiltersWithNan) { - // These documents are ordered by value in "zip" since the NotEqual filter is - // an inequality, which results in documents being sorted by value. - std::map docs = { - {"a", {{"zip", FieldValue::Double(NAN)}}}, - {"b", {{"zip", FieldValue::Integer(91102)}}}, - {"c", {{"zip", FieldValue::Integer(98101)}}}, - {"d", {{"zip", FieldValue::String("98101")}}}, - {"e", {{"zip", FieldValue::Array({FieldValue::Integer(98101)})}}}, - {"f", - {{"zip", FieldValue::Array({FieldValue::Integer(98101), - FieldValue::Integer(98102)})}}}, - {"g", - {{"zip", FieldValue::Array({FieldValue::String("98101"), - FieldValue::Map({{"zip", FieldValue::Integer( - 98101)}})})}}}, - {"h", {{"zip", FieldValue::Map({{"code", FieldValue::Integer(500)}})}}}, - {"i", {{"code", FieldValue::Integer(500)}}}, - {"j", MapFieldValue{{"zip", FieldValue::Null()}}}, - }; - CollectionReference collection = Collection(docs); - - QuerySnapshot snapshot = - ReadDocuments(collection.WhereNotEqualTo("zip", FieldValue::Double(NAN))); - EXPECT_THAT(QuerySnapshotToValues(snapshot), - ElementsAreArray(AllDocsExcept(docs, {"a", "i", "j"}))); -} - -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotEqualFiltersWithDocIds) { - MapFieldValue doc_a = {{"key", FieldValue::String("aa")}}; - MapFieldValue doc_b = {{"key", FieldValue::String("ab")}}; - MapFieldValue doc_c = {{"key", FieldValue::String("ba")}}; - MapFieldValue doc_d = {{"key", FieldValue::String("bb")}}; - - CollectionReference collection = - Collection({{"aa", doc_a}, {"ab", doc_b}, {"ba", doc_c}, {"bb", doc_d}}); - - QuerySnapshot snapshot = ReadDocuments(collection.WhereNotEqualTo( - FieldPath::DocumentId(), FieldValue::String("aa"))); - EXPECT_EQ(std::vector({doc_b, doc_c, doc_d}), - QuerySnapshotToValues(snapshot)); -} - -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseArrayContainsFilters) { - CollectionReference collection = Collection( - {{"a", {{"array", FieldValue::Array({FieldValue::Integer(42)})}}}, - {"b", - {{"array", - FieldValue::Array({FieldValue::String("a"), FieldValue::Integer(42), - FieldValue::String("c")})}}}, - {"c", - {{"array", - FieldValue::Array( - {FieldValue::Double(41.999), FieldValue::String("42"), - FieldValue::Map( - {{"a", FieldValue::Array({FieldValue::Integer(42)})}})})}}}, - {"d", - {{"array", FieldValue::Array({FieldValue::Integer(42)})}, - {"array2", FieldValue::Array({FieldValue::String("bingo")})}}}}); - // Search for 42 - QuerySnapshot snapshot = ReadDocuments( - collection.WhereArrayContains("array", FieldValue::Integer(42))); - EXPECT_EQ( - std::vector( - {{{"array", FieldValue::Array({FieldValue::Integer(42)})}}, - {{"array", FieldValue::Array({FieldValue::String("a"), - FieldValue::Integer(42), - FieldValue::String("c")})}}, - {{"array", FieldValue::Array({FieldValue::Integer(42)})}, - {"array2", FieldValue::Array({FieldValue::String("bingo")})}}}), - QuerySnapshotToValues(snapshot)); - - // NOTE: The backend doesn't currently support null, NaN, objects, or arrays, - // so there isn't much of anything else interesting to test. -} - -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseInFilters) { - CollectionReference collection = Collection( - {{"a", {{"zip", FieldValue::Integer(98101)}}}, - {"b", {{"zip", FieldValue::Integer(98102)}}}, - {"c", {{"zip", FieldValue::Integer(98103)}}}, - {"d", {{"zip", FieldValue::Array({FieldValue::Integer(98101)})}}}, - {"e", - {{"zip", - FieldValue::Array( - {FieldValue::String("98101"), - FieldValue::Map({{"zip", FieldValue::Integer(98101)}})})}}}, - {"f", {{"zip", FieldValue::Map({{"code", FieldValue::Integer(500)}})}}}, - {"g", - {{"zip", FieldValue::Array({FieldValue::Integer(98101), - FieldValue::Integer(98102)})}}}}); - // Search for zips matching 98101, 98103, or [98101, 98102]. - QuerySnapshot snapshot = ReadDocuments(collection.WhereIn( - "zip", {FieldValue::Integer(98101), FieldValue::Integer(98103), - FieldValue::Array( - {FieldValue::Integer(98101), FieldValue::Integer(98102)})})); - EXPECT_EQ(std::vector( - {{{"zip", FieldValue::Integer(98101)}}, - {{"zip", FieldValue::Integer(98103)}}, - {{"zip", FieldValue::Array({FieldValue::Integer(98101), - FieldValue::Integer(98102)})}}}), - QuerySnapshotToValues(snapshot)); - - // With objects. - snapshot = ReadDocuments(collection.WhereIn( - "zip", {FieldValue::Map({{"code", FieldValue::Integer(500)}})})); - EXPECT_EQ( - std::vector( - {{{"zip", FieldValue::Map({{"code", FieldValue::Integer(500)}})}}}), - QuerySnapshotToValues(snapshot)); -} - -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseInFiltersWithDocIds) { - CollectionReference collection = - Collection({{"aa", {{"key", FieldValue::String("aa")}}}, - {"ab", {{"key", FieldValue::String("ab")}}}, - {"ba", {{"key", FieldValue::String("ba")}}}, - {"bb", {{"key", FieldValue::String("bb")}}}}); - - QuerySnapshot snapshot = ReadDocuments( - collection.WhereIn(FieldPath::DocumentId(), - {FieldValue::String("aa"), FieldValue::String("ab")})); - EXPECT_EQ(std::vector({{{"key", FieldValue::String("aa")}}, - {{"key", FieldValue::String("ab")}}}), - QuerySnapshotToValues(snapshot)); -} - -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotInFilters) { - // These documents are ordered by value in "zip" since the NotEqual filter is - // an inequality, which results in documents being sorted by value. - std::map docs = { - {"a", {{"zip", FieldValue::Double(NAN)}}}, - {"b", {{"zip", FieldValue::Integer(91102)}}}, - {"c", {{"zip", FieldValue::Integer(98101)}}}, - {"d", {{"zip", FieldValue::Integer(98103)}}}, - {"e", {{"zip", FieldValue::Array({FieldValue::Integer(98101)})}}}, - {"f", - {{"zip", FieldValue::Array({FieldValue::Integer(98101), - FieldValue::Integer(98102)})}}}, - {"g", - {{"zip", FieldValue::Array({FieldValue::String("98101"), - FieldValue::Map({{"zip", FieldValue::Integer( - 98101)}})})}}}, - {"h", {{"zip", FieldValue::Map({{"code", FieldValue::Integer(500)}})}}}, - {"i", {{"code", FieldValue::Integer(500)}}}, - {"j", MapFieldValue{{"zip", FieldValue::Null()}}}, - }; - CollectionReference collection = Collection(docs); - - // Search for zips not matching 98101, 98103 or [98101, 98102]. - QuerySnapshot snapshot = ReadDocuments(collection.WhereNotIn( - "zip", {{FieldValue::Integer(98101), FieldValue::Integer(98103), - FieldValue::Array({{FieldValue::Integer(98101), - FieldValue::Integer(98102)}})}})); - EXPECT_THAT(QuerySnapshotToValues(snapshot), - ElementsAreArray(AllDocsExcept(docs, {"c", "d", "f", "i", "j"}))); -} - -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotInFiltersWithObject) { - // These documents are ordered by value in "zip" since the NotEqual filter is - // an inequality, which results in documents being sorted by value. - std::map docs = { - {"a", {{"zip", FieldValue::Double(NAN)}}}, - {"b", {{"zip", FieldValue::Integer(91102)}}}, - {"c", {{"zip", FieldValue::Integer(98101)}}}, - {"d", {{"zip", FieldValue::Integer(98103)}}}, - {"e", {{"zip", FieldValue::Array({FieldValue::Integer(98101)})}}}, - {"f", - {{"zip", FieldValue::Array({FieldValue::Integer(98101), - FieldValue::Integer(98102)})}}}, - {"g", - {{"zip", FieldValue::Array({FieldValue::String("98101"), - FieldValue::Map({{"zip", FieldValue::Integer( - 98101)}})})}}}, - {"h", {{"zip", FieldValue::Map({{"code", FieldValue::Integer(500)}})}}}, - {"i", {{"code", FieldValue::Integer(500)}}}, - {"j", MapFieldValue{{"zip", FieldValue::Null()}}}, - }; - CollectionReference collection = Collection(docs); - - QuerySnapshot snapshot = ReadDocuments(collection.WhereNotIn( - "zip", {{FieldValue::Map({{"code", FieldValue::Integer(500)}})}})); - EXPECT_THAT(QuerySnapshotToValues(snapshot), - ElementsAreArray(AllDocsExcept(docs, {"h", "i", "j"}))); -} - -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotInFiltersWithNull) { - // These documents are ordered by value in "zip" since the NotEqual filter is - // an inequality, which results in documents being sorted by value. - std::map docs = { - {"a", {{"zip", FieldValue::Double(NAN)}}}, - {"b", {{"zip", FieldValue::Integer(91102)}}}, - {"c", {{"zip", FieldValue::Integer(98101)}}}, - {"d", {{"zip", FieldValue::Integer(98103)}}}, - {"e", {{"zip", FieldValue::Array({FieldValue::Integer(98101)})}}}, - {"f", - {{"zip", FieldValue::Array({FieldValue::Integer(98101), - FieldValue::Integer(98102)})}}}, - {"g", - {{"zip", FieldValue::Array({FieldValue::String("98101"), - FieldValue::Map({{"zip", FieldValue::Integer( - 98101)}})})}}}, - {"h", {{"zip", FieldValue::Map({{"code", FieldValue::Integer(500)}})}}}, - {"i", {{"code", FieldValue::Integer(500)}}}, - {"j", MapFieldValue{{"zip", FieldValue::Null()}}}, - }; - CollectionReference collection = Collection(docs); - - // With Null, this leads to no result. - QuerySnapshot snapshot = - ReadDocuments(collection.WhereNotIn("zip", {{FieldValue::Null()}})); - EXPECT_THAT(QuerySnapshotToValues(snapshot), IsEmpty()); -} - -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotInFiltersWithNan) { - // These documents are ordered by value in "zip" since the NotEqual filter is - // an inequality, which results in documents being sorted by value. - std::map docs = { - {"a", {{"zip", FieldValue::Double(NAN)}}}, - {"b", {{"zip", FieldValue::Integer(91102)}}}, - {"c", {{"zip", FieldValue::Integer(98101)}}}, - {"d", {{"zip", FieldValue::Integer(98103)}}}, - {"e", {{"zip", FieldValue::Array({FieldValue::Integer(98101)})}}}, - {"f", - {{"zip", FieldValue::Array({FieldValue::Integer(98101), - FieldValue::Integer(98102)})}}}, - {"g", - {{"zip", FieldValue::Array({FieldValue::String("98101"), - FieldValue::Map({{"zip", FieldValue::Integer( - 98101)}})})}}}, - {"h", {{"zip", FieldValue::Map({{"code", FieldValue::Integer(500)}})}}}, - {"i", {{"code", FieldValue::Integer(500)}}}, - {"j", MapFieldValue{{"zip", FieldValue::Null()}}}, - }; - CollectionReference collection = Collection(docs); - - // With NAN. - QuerySnapshot snapshot = - ReadDocuments(collection.WhereNotIn("zip", {{FieldValue::Double(NAN)}})); - EXPECT_THAT(QuerySnapshotToValues(snapshot), - ElementsAreArray(AllDocsExcept(docs, {"a", "i", "j"}))); -} - -TEST_F(FirestoreIntegrationTest, - TestQueriesCanUseNotInFiltersWithNanAndNumber) { - // These documents are ordered by value in "zip" since the NotEqual filter is - // an inequality, which results in documents being sorted by value. - std::map docs = { - {"a", {{"zip", FieldValue::Double(NAN)}}}, - {"b", {{"zip", FieldValue::Integer(91102)}}}, - {"c", {{"zip", FieldValue::Integer(98101)}}}, - {"d", {{"zip", FieldValue::Integer(98103)}}}, - {"e", {{"zip", FieldValue::Array({FieldValue::Integer(98101)})}}}, - {"f", - {{"zip", FieldValue::Array({FieldValue::Integer(98101), - FieldValue::Integer(98102)})}}}, - {"g", - {{"zip", FieldValue::Array({FieldValue::String("98101"), - FieldValue::Map({{"zip", FieldValue::Integer( - 98101)}})})}}}, - {"h", {{"zip", FieldValue::Map({{"code", FieldValue::Integer(500)}})}}}, - {"i", {{"code", FieldValue::Integer(500)}}}, - {"j", MapFieldValue{{"zip", FieldValue::Null()}}}, - }; - CollectionReference collection = Collection(docs); - - QuerySnapshot snapshot = ReadDocuments(collection.WhereNotIn( - "zip", {{FieldValue::Double(NAN), FieldValue::Integer(98101)}})); - EXPECT_THAT(QuerySnapshotToValues(snapshot), - ElementsAreArray(AllDocsExcept(docs, {"a", "c", "i", "j"}))); -} - -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseNotInFiltersWithDocIds) { - MapFieldValue doc_a = {{"key", FieldValue::String("aa")}}; - MapFieldValue doc_b = {{"key", FieldValue::String("ab")}}; - MapFieldValue doc_c = {{"key", FieldValue::String("ba")}}; - MapFieldValue doc_d = {{"key", FieldValue::String("bb")}}; - - CollectionReference collection = - Collection({{"aa", doc_a}, {"ab", doc_b}, {"ba", doc_c}, {"bb", doc_d}}); - - QuerySnapshot snapshot = ReadDocuments(collection.WhereNotIn( - FieldPath::DocumentId(), - {{FieldValue::String("aa"), FieldValue::String("ab")}})); - EXPECT_EQ(std::vector({doc_c, doc_d}), - QuerySnapshotToValues(snapshot)); -} - -TEST_F(FirestoreIntegrationTest, TestQueriesCanUseArrayContainsAnyFilters) { - CollectionReference collection = Collection( - {{"a", {{"array", FieldValue::Array({FieldValue::Integer(42)})}}}, - {"b", - {{"array", - FieldValue::Array({FieldValue::String("a"), FieldValue::Integer(42), - FieldValue::String("c")})}}}, - {"c", - {{"array", - FieldValue::Array( - {FieldValue::Double(41.999), FieldValue::String("42"), - FieldValue::Map( - {{"a", FieldValue::Array({FieldValue::Integer(42)})}})})}}}, - {"d", - {{"array", FieldValue::Array({FieldValue::Integer(42)})}, - {"array2", FieldValue::Array({FieldValue::String("bingo")})}}}, - {"e", {{"array", FieldValue::Array({FieldValue::Integer(43)})}}}, - {"f", - {{"array", FieldValue::Array( - {FieldValue::Map({{"a", FieldValue::Integer(42)}})})}}}, - {"g", {{"array", FieldValue::Integer(42)}}}}); - - // Search for "array" to contain [42, 43] - QuerySnapshot snapshot = ReadDocuments(collection.WhereArrayContainsAny( - "array", {FieldValue::Integer(42), FieldValue::Integer(43)})); - EXPECT_EQ(std::vector( - {{{"array", FieldValue::Array({FieldValue::Integer(42)})}}, - {{"array", FieldValue::Array({FieldValue::String("a"), - FieldValue::Integer(42), - FieldValue::String("c")})}}, - {{"array", FieldValue::Array({FieldValue::Integer(42)})}, - {"array2", FieldValue::Array({FieldValue::String("bingo")})}}, - {{"array", FieldValue::Array({FieldValue::Integer(43)})}}}), - QuerySnapshotToValues(snapshot)); - - // With objects - snapshot = ReadDocuments(collection.WhereArrayContainsAny( - "array", {FieldValue::Map({{"a", FieldValue::Integer(42)}})})); - EXPECT_EQ(std::vector( - {{{"array", FieldValue::Array({FieldValue::Map( - {{"a", FieldValue::Integer(42)}})})}}}), - QuerySnapshotToValues(snapshot)); -} - -TEST_F(FirestoreIntegrationTest, TestCollectionGroupQueries) { - Firestore* db = TestFirestore(); - // Use .Document() to get a random collection group name to use but ensure it - // starts with 'b' for predictable ordering. - std::string collection_group = "b" + db->Collection("foo").Document().id(); - - std::string doc_paths[] = { - "abc/123/" + collection_group + "/cg-doc1", - "abc/123/" + collection_group + "/cg-doc2", - collection_group + "/cg-doc3", - collection_group + "/cg-doc4", - "def/456/" + collection_group + "/cg-doc5", - collection_group + "/virtual-doc/nested-coll/not-cg-doc", - "x" + collection_group + "/not-cg-doc", - collection_group + "x/not-cg-doc", - "abc/123/" + collection_group + "x/not-cg-doc", - "abc/123/x" + collection_group + "/not-cg-doc", - "abc/" + collection_group, - }; - - WriteBatch batch = db->batch(); - for (const auto& doc_path : doc_paths) { - batch.Set(db->Document(doc_path), {{"x", FieldValue::Integer(1)}}); - } - Await(batch.Commit()); - - QuerySnapshot query_snapshot = - ReadDocuments(db->CollectionGroup(collection_group)); - EXPECT_EQ(std::vector( - {"cg-doc1", "cg-doc2", "cg-doc3", "cg-doc4", "cg-doc5"}), - QuerySnapshotToIds(query_snapshot)); -} - -TEST_F(FirestoreIntegrationTest, - TestCollectionGroupQueriesWithStartAtEndAtWithArbitraryDocumentIds) { - Firestore* db = TestFirestore(); - // Use .Document() to get a random collection group name to use but ensure it - // starts with 'b' for predictable ordering. - std::string collection_group = "b" + db->Collection("foo").Document().id(); - - std::string doc_paths[] = { - "a/a/" + collection_group + "/cg-doc1", - "a/b/a/b/" + collection_group + "/cg-doc2", - "a/b/" + collection_group + "/cg-doc3", - "a/b/c/d/" + collection_group + "/cg-doc4", - "a/c/" + collection_group + "/cg-doc5", - collection_group + "/cg-doc6", - "a/b/nope/nope", - }; - - WriteBatch batch = db->batch(); - for (const auto& doc_path : doc_paths) { - batch.Set(db->Document(doc_path), {{"x", FieldValue::Integer(1)}}); - } - Await(batch.Commit()); - - QuerySnapshot query_snapshot = - ReadDocuments(db->CollectionGroup(collection_group) - .OrderBy(FieldPath::DocumentId()) - .StartAt({FieldValue::String("a/b")}) - .EndAt({FieldValue::String("a/b0")})); - EXPECT_EQ(std::vector({"cg-doc2", "cg-doc3", "cg-doc4"}), - QuerySnapshotToIds(query_snapshot)); -} - -TEST_F(FirestoreIntegrationTest, - TestCollectionGroupQueriesWithWhereFiltersOnArbitraryDocumentIds) { - Firestore* db = TestFirestore(); - // Use .Document() to get a random collection group name to use but ensure it - // starts with 'b' for predictable ordering. - std::string collection_group = "b" + db->Collection("foo").Document().id(); - - std::string doc_paths[] = { - "a/a/" + collection_group + "/cg-doc1", - "a/b/a/b/" + collection_group + "/cg-doc2", - "a/b/" + collection_group + "/cg-doc3", - "a/b/c/d/" + collection_group + "/cg-doc4", - "a/c/" + collection_group + "/cg-doc5", - collection_group + "/cg-doc6", - "a/b/nope/nope", - }; - - WriteBatch batch = db->batch(); - for (const auto& doc_path : doc_paths) { - batch.Set(db->Document(doc_path), {{"x", FieldValue::Integer(1)}}); - } - Await(batch.Commit()); - - QuerySnapshot query_snapshot = - ReadDocuments(db->CollectionGroup(collection_group) - .WhereGreaterThanOrEqualTo(FieldPath::DocumentId(), - FieldValue::String("a/b")) - .WhereLessThanOrEqualTo(FieldPath::DocumentId(), - FieldValue::String("a/b0"))); - EXPECT_EQ(std::vector({"cg-doc2", "cg-doc3", "cg-doc4"}), - QuerySnapshotToIds(query_snapshot)); - - query_snapshot = ReadDocuments( - db->CollectionGroup(collection_group) - .WhereGreaterThan(FieldPath::DocumentId(), FieldValue::String("a/b")) - .WhereLessThan( - FieldPath::DocumentId(), - FieldValue::String("a/b/" + collection_group + "/cg-doc3"))); - EXPECT_EQ(std::vector({"cg-doc2"}), - QuerySnapshotToIds(query_snapshot)); -} - -#endif // !defined(FIRESTORE_STUB_BUILD) - -#if defined(__ANDROID__) || defined(FIRESTORE_STUB_BUILD) -TEST(QueryTest, Construction) { - testutil::AssertWrapperConstructionContract(); -} - -TEST(QueryTest, Assignment) { - testutil::AssertWrapperAssignmentContract(); -} -#endif // defined(__ANDROID__) || defined(FIRESTORE_STUB_BUILD) - -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/sanity_test.cc b/firestore/src/tests/sanity_test.cc deleted file mode 100644 index 0fdc4be14b..0000000000 --- a/firestore/src/tests/sanity_test.cc +++ /dev/null @@ -1,38 +0,0 @@ -// This is a sanity test using gtest. The goal of this test is to make sure the -// way we setup Android C++ test harness actually works. We write test in a -// cross-platform way with gtest and run test with Android JUnit4 test runner -// for Android. We want this sanity test be as simple as possible while using -// the most critical mechanism of gtest. We also print information to stdout -// for debugging if anything goes wrong. - -#include -#include -#include "gtest/gtest.h" - -class SanityTest : public testing::Test { - protected: - void SetUp() override { printf("==== SetUp ====\n"); } - void TearDown() override { printf("==== TearDown ====\n"); } -}; - -// So far, Android native method cannot be inside namespace. So this has to be -// defined outside of any namespace. -TEST_F(SanityTest, TestSanity) { - printf("==== running %s ====\n", __PRETTY_FUNCTION__); - EXPECT_TRUE(true); -} - -TEST_F(SanityTest, TestAnotherSanity) { - printf("==== running %s ====\n", __PRETTY_FUNCTION__); - EXPECT_EQ(1, 1); -} - -// Generally we do not put test inside #if's because Android test harness will -// generate JUnit test whether macro is true or false. It is fine here since the -// test is enabled for Android. -#if __cpp_exceptions -TEST_F(SanityTest, TestThrow) { - printf("==== running %s ====\n", __PRETTY_FUNCTION__); - EXPECT_ANY_THROW({ throw "exception"; }); -} -#endif // __cpp_exceptions diff --git a/firestore/src/tests/server_timestamp_test.cc b/firestore/src/tests/server_timestamp_test.cc deleted file mode 100644 index cf55af35fc..0000000000 --- a/firestore/src/tests/server_timestamp_test.cc +++ /dev/null @@ -1,294 +0,0 @@ -#include -#include -#include - -#include "firestore/src/include/firebase/firestore.h" -#include "firestore/src/tests/firestore_integration_test.h" -#include "firestore/src/tests/util/event_accumulator.h" -#include "testing/base/public/gmock.h" -#include "gtest/gtest.h" - -// These test cases are in sync with native iOS client SDK test -// Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.mm -// and native Android client SDK test -// firebase_firestore/tests/integration_tests/src/com/google/firebase/firestore/ServerTimestampTest.java - -namespace firebase { -namespace firestore { - -using ServerTimestampBehavior = DocumentSnapshot::ServerTimestampBehavior; - -class ServerTimestampTest : public FirestoreIntegrationTest { - public: - ~ServerTimestampTest() override {} - - protected: - void SetUp() override { - doc_ = Document(); - listener_registration_ = - accumulator_.listener()->AttachTo(&doc_, MetadataChanges::kInclude); - - // Wait for initial null snapshot to avoid potential races. - DocumentSnapshot initial_snapshot = accumulator_.AwaitServerEvent(); - EXPECT_FALSE(initial_snapshot.exists()); - } - - void TearDown() override { listener_registration_.Remove(); } - - /** Returns the expected data, with the specified timestamp substituted in. */ - MapFieldValue ExpectedDataWithTimestamp(const FieldValue& timestamp) { - return MapFieldValue{{"a", FieldValue::Integer(42)}, - {"when", timestamp}, - {"deep", FieldValue::Map({{"when", timestamp}})}}; - } - - /** Writes initial_data_ and waits for the corresponding snapshot. */ - void WriteInitialData() { - WriteDocument(doc_, initial_data_); - DocumentSnapshot initial_data_snapshot = accumulator_.Await(); - EXPECT_THAT(initial_data_snapshot.GetData(), - testing::ContainerEq(initial_data_)); - initial_data_snapshot = accumulator_.Await(); - EXPECT_THAT(initial_data_snapshot.GetData(), - testing::ContainerEq(initial_data_)); - } - - /** - * Verifies a snapshot containing set_data_ but with null for the timestamps. - */ - void VerifyTimestampsAreNull(const DocumentSnapshot& snapshot) { - EXPECT_THAT( - snapshot.GetData(), - testing::ContainerEq(ExpectedDataWithTimestamp(FieldValue::Null()))); - } - - /** - * Verifies a snapshot containing set_data_ but with resolved server - * timestamps. - */ - void VerifyTimestampsAreResolved(const DocumentSnapshot& snapshot) { - ASSERT_TRUE(snapshot.exists()); - ASSERT_TRUE(snapshot.Get("when").is_timestamp()); - Timestamp when = snapshot.Get("when").timestamp_value(); - // Tolerate up to 48*60*60 seconds of clock skew between client and server. - // This should be more than enough to compensate for timezone issues (even - // after taking daylight saving into account) and should allow local clocks - // to deviate from true time slightly and still pass the test. PORT_NOTE: - // For the tolerance here, Android uses 48*60*60 seconds while iOS uses 10 - // seconds. - int delta_sec = 48 * 60 * 60; - Timestamp now = Timestamp::Now(); - EXPECT_LT(abs(when.seconds() - now.seconds()), delta_sec) - << "resolved timestamp (" << when.ToString() << ") should be within " - << delta_sec << "s of now (" << now.ToString() << ")"; - - // Validate the rest of the document. - EXPECT_THAT(snapshot.GetData(), - testing::ContainerEq( - ExpectedDataWithTimestamp(FieldValue::Timestamp(when)))); - } - - /** - * Verifies a snapshot containing set_data_ but with local estimates for - * server timestamps. - */ - void VerifyTimestampsAreEstimates(const DocumentSnapshot& snapshot) { - ASSERT_TRUE(snapshot.exists()); - FieldValue when = snapshot.Get("when", ServerTimestampBehavior::kEstimate); - ASSERT_TRUE(when.is_timestamp()); - EXPECT_THAT(snapshot.GetData(ServerTimestampBehavior::kEstimate), - testing::ContainerEq(ExpectedDataWithTimestamp(when))); - } - - /** - * Verifies a snapshot containing set_data_ but using the previous field value - * for server timestamps. - */ - void VerifyTimestampsUsePreviousValue(const DocumentSnapshot& snapshot, - const FieldValue& previous) { - ASSERT_TRUE(snapshot.exists()); - ASSERT_TRUE(previous.is_null() || previous.is_timestamp()); - EXPECT_THAT(snapshot.GetData(ServerTimestampBehavior::kPrevious), - testing::ContainerEq(ExpectedDataWithTimestamp(previous))); - } - - // Data written in tests via set. - const MapFieldValue set_data_ = MapFieldValue{ - {"a", FieldValue::Integer(42)}, - {"when", FieldValue::ServerTimestamp()}, - {"deep", FieldValue::Map({{"when", FieldValue::ServerTimestamp()}})}}; - - // Base and update data used for update tests. - const MapFieldValue initial_data_ = - MapFieldValue{{"a", FieldValue::Integer(42)}}; - const MapFieldValue update_data_ = MapFieldValue{ - {"when", FieldValue::ServerTimestamp()}, - {"deep", FieldValue::Map({{"when", FieldValue::ServerTimestamp()}})}}; - - // A document reference to read and write to. - DocumentReference doc_; - - // Accumulator used to capture events during the test. - EventAccumulator accumulator_; - - // Listener registration for a listener maintained during the course of the - // test. - ListenerRegistration listener_registration_; -}; - -TEST_F(ServerTimestampTest, TestServerTimestampsWorkViaSet) { - WriteDocument(doc_, set_data_); - VerifyTimestampsAreNull(accumulator_.AwaitLocalEvent()); - VerifyTimestampsAreResolved(accumulator_.AwaitRemoteEvent()); -} - -TEST_F(ServerTimestampTest, TestServerTimestampsWorkViaUpdate) { - WriteInitialData(); - UpdateDocument(doc_, update_data_); - VerifyTimestampsAreNull(accumulator_.AwaitLocalEvent()); - VerifyTimestampsAreResolved(accumulator_.AwaitRemoteEvent()); -} - -TEST_F(ServerTimestampTest, TestServerTimestampsCanReturnEstimatedValue) { - WriteDocument(doc_, set_data_); - VerifyTimestampsAreEstimates(accumulator_.AwaitLocalEvent()); - VerifyTimestampsAreResolved(accumulator_.AwaitRemoteEvent()); -} - -TEST_F(ServerTimestampTest, TestServerTimestampsCanReturnPreviousValue) { - WriteDocument(doc_, set_data_); - VerifyTimestampsUsePreviousValue(accumulator_.AwaitLocalEvent(), - FieldValue::Null()); - DocumentSnapshot previous_snapshot = accumulator_.AwaitRemoteEvent(); - VerifyTimestampsAreResolved(previous_snapshot); - - UpdateDocument(doc_, update_data_); - VerifyTimestampsUsePreviousValue(accumulator_.AwaitLocalEvent(), - previous_snapshot.Get("when")); - VerifyTimestampsAreResolved(accumulator_.AwaitRemoteEvent()); -} - -TEST_F(ServerTimestampTest, - TestServerTimestampsCanReturnPreviousValueOfDifferentType) { - WriteInitialData(); - UpdateDocument(doc_, MapFieldValue{{"a", FieldValue::ServerTimestamp()}}); - - DocumentSnapshot local_snapshot = accumulator_.AwaitLocalEvent(); - EXPECT_TRUE(local_snapshot.Get("a").is_null()); - EXPECT_TRUE(local_snapshot.Get("a", ServerTimestampBehavior::kEstimate) - .is_timestamp()); - EXPECT_TRUE( - local_snapshot.Get("a", ServerTimestampBehavior::kPrevious).is_integer()); - EXPECT_EQ(42, local_snapshot.Get("a", ServerTimestampBehavior::kPrevious) - .integer_value()); - - DocumentSnapshot remote_snapshot = accumulator_.AwaitRemoteEvent(); - EXPECT_TRUE(remote_snapshot.Get("a").is_timestamp()); - EXPECT_TRUE(remote_snapshot.Get("a", ServerTimestampBehavior::kEstimate) - .is_timestamp()); - EXPECT_TRUE(remote_snapshot.Get("a", ServerTimestampBehavior::kPrevious) - .is_timestamp()); -} - -TEST_F(ServerTimestampTest, - TestServerTimestampsCanRetainPreviousValueThroughConsecutiveUpdates) { - WriteInitialData(); - Await(TestFirestore()->DisableNetwork()); - accumulator_.AwaitRemoteEvent(); - - doc_.Update(MapFieldValue{{"a", FieldValue::ServerTimestamp()}}); - DocumentSnapshot local_snapshot = accumulator_.AwaitLocalEvent(); - EXPECT_TRUE( - local_snapshot.Get("a", ServerTimestampBehavior::kPrevious).is_integer()); - EXPECT_EQ(42, local_snapshot.Get("a", ServerTimestampBehavior::kPrevious) - .integer_value()); - - doc_.Update(MapFieldValue{{"a", FieldValue::ServerTimestamp()}}); - local_snapshot = accumulator_.AwaitLocalEvent(); - EXPECT_TRUE( - local_snapshot.Get("a", ServerTimestampBehavior::kPrevious).is_integer()); - EXPECT_EQ(42, local_snapshot.Get("a", ServerTimestampBehavior::kPrevious) - .integer_value()); - - Await(TestFirestore()->EnableNetwork()); - - DocumentSnapshot remote_snapshot = accumulator_.AwaitRemoteEvent(); - EXPECT_TRUE(remote_snapshot.Get("a").is_timestamp()); -} - -TEST_F(ServerTimestampTest, - TestServerTimestampsUsesPreviousValueFromLocalMutation) { - WriteInitialData(); - Await(TestFirestore()->DisableNetwork()); - accumulator_.AwaitRemoteEvent(); - - doc_.Update(MapFieldValue{{"a", FieldValue::ServerTimestamp()}}); - DocumentSnapshot local_snapshot = accumulator_.AwaitLocalEvent(); - EXPECT_TRUE( - local_snapshot.Get("a", ServerTimestampBehavior::kPrevious).is_integer()); - EXPECT_EQ(42, local_snapshot.Get("a", ServerTimestampBehavior::kPrevious) - .integer_value()); - - doc_.Update(MapFieldValue{{"a", FieldValue::Integer(1337)}}); - accumulator_.AwaitLocalEvent(); - - doc_.Update(MapFieldValue{{"a", FieldValue::ServerTimestamp()}}); - local_snapshot = accumulator_.AwaitLocalEvent(); - EXPECT_TRUE( - local_snapshot.Get("a", ServerTimestampBehavior::kPrevious).is_integer()); - EXPECT_EQ(1337, local_snapshot.Get("a", ServerTimestampBehavior::kPrevious) - .integer_value()); - - Await(TestFirestore()->EnableNetwork()); - - DocumentSnapshot remote_snapshot = accumulator_.AwaitRemoteEvent(); - EXPECT_TRUE(remote_snapshot.Get("a").is_timestamp()); -} - -TEST_F(ServerTimestampTest, TestServerTimestampsWorkViaTransactionSet) { -#if defined(FIREBASE_USE_STD_FUNCTION) - Await(TestFirestore()->RunTransaction( - [this](Transaction& transaction, std::string&) -> Error { - transaction.Set(doc_, set_data_); - return Error::kErrorOk; - })); - VerifyTimestampsAreResolved(accumulator_.AwaitRemoteEvent()); -#endif // defined(FIREBASE_USE_STD_FUNCTION) -} - -TEST_F(ServerTimestampTest, TestServerTimestampsWorkViaTransactionUpdate) { -#if defined(FIREBASE_USE_STD_FUNCTION) - WriteInitialData(); - Await(TestFirestore()->RunTransaction( - [this](Transaction& transaction, std::string&) -> Error { - transaction.Update(doc_, update_data_); - return Error::kErrorOk; - })); - VerifyTimestampsAreResolved(accumulator_.AwaitRemoteEvent()); -#endif // defined(FIREBASE_USE_STD_FUNCTION) -} - -TEST_F(ServerTimestampTest, - TestServerTimestampsFailViaTransactionUpdateOnNonexistentDocument) { -#if defined(FIREBASE_USE_STD_FUNCTION) - Future future = TestFirestore()->RunTransaction( - [this](Transaction& transaction, std::string&) -> Error { - transaction.Update(doc_, update_data_); - return Error::kErrorOk; - }); - Await(future); - EXPECT_EQ(FutureStatus::kFutureStatusComplete, future.status()); - EXPECT_EQ(Error::kErrorNotFound, future.error()); -#endif // defined(FIREBASE_USE_STD_FUNCTION) -} - -TEST_F(ServerTimestampTest, - TestServerTimestampsFailViaUpdateOnNonexistentDocument) { - Future future = doc_.Update(update_data_); - Await(future); - EXPECT_EQ(FutureStatus::kFutureStatusComplete, future.status()); - EXPECT_EQ(Error::kErrorNotFound, future.error()); -} - -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/smoke_test.cc b/firestore/src/tests/smoke_test.cc deleted file mode 100644 index 5d43811a87..0000000000 --- a/firestore/src/tests/smoke_test.cc +++ /dev/null @@ -1,165 +0,0 @@ -#include - -#include "firestore/src/include/firebase/firestore.h" -#include "firestore/src/tests/firestore_integration_test.h" -#include "firestore/src/tests/util/event_accumulator.h" -#include "testing/base/public/gmock.h" -#include "gtest/gtest.h" - -// These test cases are in sync with native iOS client SDK test -// Firestore/Example/Tests/Integration/API/FSTSmokeTests.mm -// and native Android client SDK test -// firebase_firestore/tests/integration_tests/src/com/google/firebase/firestore/SmokeTest.java - -namespace firebase { -namespace firestore { - -using TypeTest = FirestoreIntegrationTest; - -TEST_F(TypeTest, TestCanWriteASingleDocument) { - const MapFieldValue test_data{ - {"name", FieldValue::String("Patryk")}, - {"message", FieldValue::String("We are actually writing data!")}}; - CollectionReference collection = Collection(); - Await(collection.Add(test_data)); -} - -TEST_F(TypeTest, TestCanReadAWrittenDocument) { - const MapFieldValue test_data{{"foo", FieldValue::String("bar")}}; - CollectionReference collection = Collection(); - - DocumentReference new_reference = *Await(collection.Add(test_data)); - DocumentSnapshot result = *Await(new_reference.Get()); - EXPECT_THAT( - result.GetData(), - testing::ContainerEq(MapFieldValue{{"foo", FieldValue::String("bar")}})); -} - -TEST_F(TypeTest, TestObservesExistingDocument) { - const MapFieldValue test_data{{"foo", FieldValue::String("bar")}}; - DocumentReference writer_reference = - TestFirestore("writer")->Collection("collection").Document(); - DocumentReference reader_reference = TestFirestore("reader") - ->Collection("collection") - .Document(writer_reference.id()); - Await(writer_reference.Set(test_data)); - - EventAccumulator accumulator; - ListenerRegistration registration = accumulator.listener()->AttachTo( - &reader_reference, MetadataChanges::kInclude); - - DocumentSnapshot doc = accumulator.Await(); - EXPECT_THAT( - doc.GetData(), - testing::ContainerEq(MapFieldValue{{"foo", FieldValue::String("bar")}})); - registration.Remove(); -} - -TEST_F(TypeTest, TestObservesNewDocument) { - CollectionReference collection = Collection(); - DocumentReference writer_reference = collection.Document(); - DocumentReference reader_reference = - collection.Document(writer_reference.id()); - - EventAccumulator accumulator; - ListenerRegistration registration = accumulator.listener()->AttachTo( - &reader_reference, MetadataChanges::kInclude); - - DocumentSnapshot doc = accumulator.Await(); - EXPECT_FALSE(doc.exists()); - - const MapFieldValue test_data{{"foo", FieldValue::String("bar")}}; - Await(writer_reference.Set(test_data)); - - doc = accumulator.Await(); - EXPECT_THAT( - doc.GetData(), - testing::ContainerEq(MapFieldValue{{"foo", FieldValue::String("bar")}})); - EXPECT_TRUE(doc.metadata().has_pending_writes()); - - doc = accumulator.Await(); - EXPECT_THAT( - doc.GetData(), - testing::ContainerEq(MapFieldValue{{"foo", FieldValue::String("bar")}})); - EXPECT_FALSE(doc.metadata().has_pending_writes()); - - registration.Remove(); -} - -TEST_F(TypeTest, TestWillFireValueEventsForEmptyCollections) { - CollectionReference collection = Collection(); - EventAccumulator accumulator; - ListenerRegistration registration = - accumulator.listener()->AttachTo(&collection, MetadataChanges::kInclude); - - QuerySnapshot query_snapshot = accumulator.Await(); - EXPECT_EQ(0, query_snapshot.size()); - EXPECT_TRUE(query_snapshot.empty()); - - registration.Remove(); -} - -TEST_F(TypeTest, TestGetCollectionQuery) { - const std::map test_data{ - {"1", - {{"name", FieldValue::String("Patryk")}, - {"message", FieldValue::String("Real data, yo!")}}}, - {"2", - {{"name", FieldValue::String("Gil")}, - {"message", FieldValue::String("Yep!")}}}, - {"3", - {{"name", FieldValue::String("Jonny")}, - {"message", FieldValue::String("Back to work!")}}}}; - CollectionReference collection = Collection(test_data); - QuerySnapshot result = *Await(collection.Get()); - EXPECT_FALSE(result.empty()); - EXPECT_THAT( - QuerySnapshotToValues(result), - testing::ElementsAre( - MapFieldValue{{"name", FieldValue::String("Patryk")}, - {"message", FieldValue::String("Real data, yo!")}}, - MapFieldValue{{"name", FieldValue::String("Gil")}, - {"message", FieldValue::String("Yep!")}}, - MapFieldValue{{"name", FieldValue::String("Jonny")}, - {"message", FieldValue::String("Back to work!")}})); -} - -// TODO(klimt): This test is disabled because we can't create compound indexes -// programmatically. -TEST_F(TypeTest, DISABLED_TestQueryByFieldAndUseOrderBy) { - const std::map test_data{ - {"1", - {{"sort", FieldValue::Double(1.0)}, - {"filter", FieldValue::Boolean(true)}, - {"key", FieldValue::String("1")}}}, - {"2", - {{"sort", FieldValue::Double(2.0)}, - {"filter", FieldValue::Boolean(true)}, - {"key", FieldValue::String("2")}}}, - {"3", - {{"sort", FieldValue::Double(2.0)}, - {"filter", FieldValue::Boolean(true)}, - {"key", FieldValue::String("3")}}}, - {"4", - {{"sort", FieldValue::Double(3.0)}, - {"filter", FieldValue::Boolean(false)}, - {"key", FieldValue::String("4")}}}}; - CollectionReference collection = Collection(test_data); - Query query = collection.WhereEqualTo("filter", FieldValue::Boolean(true)) - .OrderBy("sort", Query::Direction::kDescending); - QuerySnapshot result = *Await(query.Get()); - EXPECT_THAT( - QuerySnapshotToValues(result), - testing::ElementsAre(MapFieldValue{{"sort", FieldValue::Double(2.0)}, - {"filter", FieldValue::Boolean(true)}, - {"key", FieldValue::String("2")}}, - MapFieldValue{{"sort", FieldValue::Double(2.0)}, - {"filter", FieldValue::Boolean(true)}, - {"key", FieldValue::String("3")}}, - MapFieldValue{{"sort", FieldValue::Double(1.0)}, - {"filter", FieldValue::Boolean(true)}, - {"key", FieldValue::String("1")}})); -} - -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/transaction_extra_test.cc b/firestore/src/tests/transaction_extra_test.cc deleted file mode 100644 index f8f3a70c18..0000000000 --- a/firestore/src/tests/transaction_extra_test.cc +++ /dev/null @@ -1,112 +0,0 @@ -#include "app/src/time.h" -#include "firestore/src/include/firebase/firestore.h" -#include "firestore/src/tests/firestore_integration_test.h" -#include "testing/base/public/gmock.h" -#include "gtest/gtest.h" -#include "firebase/firestore/firestore_errors.h" -#if defined(__ANDROID__) -#include "firestore/src/android/transaction_android.h" -#elif defined(FIRESTORE_STUB_BUILD) -#include "firestore/src/stub/transaction_stub.h" -#endif // defined(__ANDROID__) - -// These test cases are in sync with native iOS client SDK test -// Firestore/Example/Tests/Integration/API/FSTTransactionTests.mm -// and native Android client SDK test -// firebase_firestore/tests/integration_tests/src/com/google/firebase/firestore/TransactionTest.java - -namespace firebase { -namespace firestore { - -// We will be using lambda in the test instead of defining a -// TransactionFunction for each of the test case. -// -// We do have a TransactionFunction-version of the test -// TestGetNonexistentDocumentThenCreate to test the non-lambda API. - -using TransactionExtraTest = FirestoreIntegrationTest; - -#if defined(FIREBASE_USE_STD_FUNCTION) - -TEST_F(TransactionExtraTest, - TestRetriesWhenDocumentThatWasReadWithoutBeingWrittenChanges) { - DocumentReference doc1 = TestFirestore()->Collection("counter").Document(); - DocumentReference doc2 = TestFirestore()->Collection("counter").Document(); - WriteDocument(doc1, MapFieldValue{{"count", FieldValue::Integer(15)}}); - // Use these two as a portable way to mimic atomic integer. - Mutex mutex; - int transaction_runs_count = 0; - - Future future = TestFirestore()->RunTransaction( - [&doc1, &doc2, &mutex, &transaction_runs_count]( - Transaction& transaction, std::string& error_message) -> Error { - { - MutexLock lock(mutex); - ++transaction_runs_count; - } - // Get the first doc. - Error error = Error::kErrorOk; - DocumentSnapshot snapshot1 = - transaction.Get(doc1, &error, &error_message); - EXPECT_EQ(Error::kErrorOk, error); - // Do a write outside of the transaction. The first time the - // transaction is tried, this will bump the version, which - // will cause the write to doc2 to fail. The second time, it - // will be a no-op and not bump the version. - // Now try to update the other doc from within the transaction. - Await(doc1.Set(MapFieldValue{{"count", FieldValue::Integer(1234)}})); - // Now try to update the other doc from within the transaction. - // This should fail once, because we read 15 earlier. - transaction.Set(doc2, - MapFieldValue{{"count", FieldValue::Integer(16)}}); - return Error::kErrorOk; - }); - Await(future); - EXPECT_EQ(Error::kErrorOk, future.error()); - EXPECT_EQ(2, transaction_runs_count); - DocumentSnapshot snapshot = ReadDocument(doc1); - EXPECT_EQ(1234, snapshot.Get("count").integer_value()); -} - -TEST_F(TransactionExtraTest, TestReadingADocTwiceWithDifferentVersions) { - int counter = 0; - DocumentReference doc = TestFirestore()->Collection("counters").Document(); - WriteDocument(doc, MapFieldValue{{"count", FieldValue::Double(15.0)}}); - - Future future = TestFirestore()->RunTransaction( - [&doc, &counter](Transaction& transaction, - std::string& error_message) -> Error { - Error error = Error::kErrorOk; - // Get the doc once. - DocumentSnapshot snapshot1 = - transaction.Get(doc, &error, &error_message); - EXPECT_EQ(Error::kErrorOk, error); - // Do a write outside of the transaction. Because the transaction will - // retry, set the document to a different value each time. - Await(doc.Set( - MapFieldValue{{"count", FieldValue::Double(1234.0 + counter)}})); - ++counter; - // Get the doc again in the transaction with the new version. - DocumentSnapshot snapshot2 = - transaction.Get(doc, &error, &error_message); - // We cannot check snapshot2, which is invalid as the second read would - // have already failed. - - // Now try to update the doc from within the transaction. - // This should fail, because we read 15 earlier. - transaction.Set(doc, - MapFieldValue{{"count", FieldValue::Double(16.0)}}); - return error; - }); - Await(future); - EXPECT_EQ(Error::kErrorAborted, future.error()); - EXPECT_STREQ("Document version changed between two reads.", - future.error_message()); - - DocumentSnapshot snapshot = ReadDocument(doc); -} - -#endif // defined(FIREBASE_USE_STD_FUNCTION) - -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/transaction_test.cc b/firestore/src/tests/transaction_test.cc deleted file mode 100644 index 993ad7df4b..0000000000 --- a/firestore/src/tests/transaction_test.cc +++ /dev/null @@ -1,750 +0,0 @@ -#include -#include - -#if !defined(FIRESTORE_STUB_BUILD) -#include "app/src/mutex.h" -#include "app/src/semaphore.h" -#include "app/src/time.h" -#endif - -#include "firestore/src/include/firebase/firestore.h" -#include "firestore/src/tests/firestore_integration_test.h" -#include "firestore/src/tests/util/event_accumulator.h" -#include "testing/base/public/gmock.h" -#include "gtest/gtest.h" -#include "absl/strings/str_join.h" -#include "firebase/firestore/firestore_errors.h" -#if defined(__ANDROID__) -#include "firestore/src/android/transaction_android.h" -#elif defined(FIRESTORE_STUB_BUILD) -#include "firestore/src/stub/transaction_stub.h" - -#endif // defined(__ANDROID__) - -// These test cases are in sync with native iOS client SDK test -// Firestore/Example/Tests/Integration/API/FSTTransactionTests.mm -// and native Android client SDK test -// firebase_firestore/tests/integration_tests/src/com/google/firebase/firestore/TransactionTest.java -// -// Some test cases are moved to transaction_extra_test.cc. If run together, the -// test will run too long and timeout. - -namespace firebase { -namespace firestore { - -// These tests don't work with the stubs. -#if !defined(FIRESTORE_STUB_BUILD) - -using ::testing::HasSubstr; - -// We will be using lambda in the test instead of defining a -// TransactionFunction for each of the test case. -// -// We do have a TransactionFunction-version of the test -// TestGetNonexistentDocumentThenCreate to test the non-lambda API. - -class TransactionTest : public FirestoreIntegrationTest { - protected: -#if defined(FIREBASE_USE_STD_FUNCTION) - // We occasionally get transient error like "Could not reach Cloud Firestore - // backend. Backend didn't respond within 10 seconds". Transaction requires - // online and thus will not retry. So we do the retry in the testcase. - void RunTransactionAndExpect( - Error error, const char* message, - std::function update) { - Future future; - // Re-try 5 times in case server is unavailable. - for (int i = 0; i < 5; ++i) { - future = TestFirestore()->RunTransaction(update); - Await(future); - if (future.error() == Error::kErrorUnavailable) { - std::cout << "Could not reach backend. Retrying transaction test." - << std::endl; - } else { - break; - } - } - EXPECT_EQ(error, future.error()); - EXPECT_THAT(future.error_message(), HasSubstr(message)); - } - - void RunTransactionAndExpect( - Error error, std::function update) { - switch (error) { - case Error::kErrorOk: - RunTransactionAndExpect(Error::kErrorOk, "", std::move(update)); - break; - case Error::kErrorAborted: - RunTransactionAndExpect( -#if defined(__APPLE__) - Error::kErrorFailedPrecondition, -#else - Error::kErrorAborted, -#endif - "Transaction failed all retries.", std::move(update)); - break; - case Error::kErrorFailedPrecondition: - // Here specifies error message of the most common cause. There are - // other causes for FailedPrecondition as well. Use the one with message - // parameter if the expected error message is different. - RunTransactionAndExpect(Error::kErrorFailedPrecondition, - "Can't update a document that doesn't exist.", - std::move(update)); - break; - default: - FAIL() << "Unexpected error code: " << error; - } - } -#endif // defined(FIREBASE_USE_STD_FUNCTION) -}; - -class TestTransactionFunction : public TransactionFunction { - public: - TestTransactionFunction(DocumentReference doc) : doc_(doc) {} - - Error Apply(Transaction& transaction, std::string& error_message) override { - Error error = Error::kErrorUnknown; - DocumentSnapshot snapshot = transaction.Get(doc_, &error, &error_message); - EXPECT_EQ(Error::kErrorOk, error); - EXPECT_FALSE(snapshot.exists()); - transaction.Set(doc_, MapFieldValue{{key_, FieldValue::String(value_)}}); - return error; - } - - std::string key() { return key_; } - std::string value() { return value_; } - - private: - DocumentReference doc_; - const std::string key_{"foo"}; - const std::string value_{"bar"}; -}; - -TEST_F(TransactionTest, TestGetNonexistentDocumentThenCreatePortableVersion) { - DocumentReference doc = TestFirestore()->Collection("towns").Document(); - TestTransactionFunction transaction{doc}; - Future future = TestFirestore()->RunTransaction(&transaction); - Await(future); - - EXPECT_EQ(Error::kErrorOk, future.error()); - DocumentSnapshot snapshot = ReadDocument(doc); - EXPECT_EQ(FieldValue::String(transaction.value()), - snapshot.Get(transaction.key())); -} - -#if defined(FIREBASE_USE_STD_FUNCTION) - -class TransactionStage { - public: - TransactionStage( - std::string tag, - std::function func) - : tag_(std::move(tag)), func_(std::move(func)) {} - - const std::string& tag() const { return tag_; } - - void operator()(Transaction* transaction, - const DocumentReference& doc) const { - func_(transaction, doc); - } - - bool operator==(const TransactionStage& rhs) const { - return tag_ == rhs.tag_; - } - - bool operator!=(const TransactionStage& rhs) const { - return tag_ != rhs.tag_; - } - - private: - std::string tag_; - std::function func_; -}; - -/** - * The transaction stages that follow are postfixed by numbers to indicate the - * calling order. For example, calling `set1` followed by `set2` should result - * in the document being set to the value specified by `set2`. - */ -const auto delete1 = new TransactionStage( - "delete", [](Transaction* transaction, const DocumentReference& doc) { - transaction->Delete(doc); - }); - -const auto update1 = new TransactionStage("update", [](Transaction* transaction, - const DocumentReference& - doc) { - transaction->Update(doc, MapFieldValue{{"foo", FieldValue::String("bar1")}}); -}); - -const auto update2 = new TransactionStage("update", [](Transaction* transaction, - const DocumentReference& - doc) { - transaction->Update(doc, MapFieldValue{{"foo", FieldValue::String("bar2")}}); -}); - -const auto set1 = new TransactionStage( - "set", [](Transaction* transaction, const DocumentReference& doc) { - transaction->Set(doc, MapFieldValue{{"foo", FieldValue::String("bar1")}}); - }); - -const auto set2 = new TransactionStage( - "set", [](Transaction* transaction, const DocumentReference& doc) { - transaction->Set(doc, MapFieldValue{{"foo", FieldValue::String("bar2")}}); - }); - -const auto get = new TransactionStage( - "get", [](Transaction* transaction, const DocumentReference& doc) { - Error error; - std::string msg; - transaction->Get(doc, &error, &msg); - }); - -/** - * Used for testing that all possible combinations of executing transactions - * result in the desired document value or error. - * - * `Run()`, `WithExistingDoc()`, and `WithNonexistentDoc()` don't actually do - * anything except assign variables into the `TransactionTester`. - * - * `ExpectDoc()`, `ExpectNoDoc()`, and `ExpectError()` will trigger the - * transaction to run and assert that the end result matches the input. - */ -class TransactionTester { - public: - explicit TransactionTester(Firestore* db) : db_(db) {} - - template - TransactionTester& Run(Args... args) { - stages_ = {*args...}; - return *this; - } - - TransactionTester& WithExistingDoc() { - from_existing_doc_ = true; - return *this; - } - - TransactionTester& WithNonexistentDoc() { - from_existing_doc_ = false; - return *this; - } - - void ExpectDoc(const MapFieldValue& expected) { - PrepareDoc(); - RunSuccessfulTransaction(); - Future future = doc_.Get(); - const DocumentSnapshot* snapshot = FirestoreIntegrationTest::Await(future); - EXPECT_TRUE(snapshot->exists()); - EXPECT_THAT(snapshot->GetData(), expected); - stages_.clear(); - } - - void ExpectNoDoc() { - PrepareDoc(); - RunSuccessfulTransaction(); - Future future = doc_.Get(); - const DocumentSnapshot* snapshot = FirestoreIntegrationTest::Await(future); - EXPECT_FALSE(snapshot->exists()); - stages_.clear(); - } - - void ExpectError(Error error) { - PrepareDoc(); - RunFailingTransaction(error); - stages_.clear(); - } - - private: - void PrepareDoc() { - doc_ = db_->Collection("tx-tester").Document(); - if (from_existing_doc_) { - FirestoreIntegrationTest::Await( - doc_.Set(MapFieldValue{{"foo", FieldValue::String("bar0")}})); - } - } - - void RunSuccessfulTransaction() { - Future future = db_->RunTransaction( - [this](Transaction& transaction, std::string& error_message) { - for (const auto& stage : stages_) { - stage(&transaction, doc_); - } - return Error::kErrorOk; - }); - FirestoreIntegrationTest::Await(future); - EXPECT_EQ(Error::kErrorOk, future.error()) - << "Expected the sequence (" + ListStages() + ") to succeed, but got " + - std::to_string(future.error()); - } - - void RunFailingTransaction(Error error) { - Future future = db_->RunTransaction( - [this](Transaction& transaction, std::string& error_message) { - for (const auto& stage : stages_) { - stage(&transaction, doc_); - } - return Error::kErrorOk; - }); - FirestoreIntegrationTest::Await(future); - EXPECT_EQ(error, future.error()) - << "Expected the sequence (" + ListStages() + - ") to fail with the error " + std::to_string(error); - } - - std::string ListStages() const { - std::vector stages; - for (const auto& stage : stages_) { - stages.push_back(stage.tag()); - } - return absl::StrJoin(stages, ","); - } - - Firestore* db_ = nullptr; - DocumentReference doc_; - bool from_existing_doc_ = false; - std::vector stages_; -}; - -TEST_F(TransactionTest, TestRunsTransactionsAfterGettingNonexistentDoc) { - SCOPED_TRACE("TestRunsTransactionsAfterGettingNonexistentDoc"); - - TransactionTester tt = TransactionTester(TestFirestore()); - tt.WithExistingDoc().Run(get, delete1, delete1).ExpectNoDoc(); - tt.WithExistingDoc() - .Run(get, delete1, update2) - .ExpectError(Error::kErrorInvalidArgument); - tt.WithExistingDoc() - .Run(get, delete1, set2) - .ExpectDoc(MapFieldValue{{"foo", FieldValue::String("bar2")}}); - - tt.WithExistingDoc().Run(get, update1, delete1).ExpectNoDoc(); - tt.WithExistingDoc() - .Run(get, update1, update2) - .ExpectDoc(MapFieldValue{{"foo", FieldValue::String("bar2")}}); - tt.WithExistingDoc() - .Run(get, update1, set2) - .ExpectDoc(MapFieldValue{{"foo", FieldValue::String("bar2")}}); - - tt.WithExistingDoc().Run(get, set1, delete1).ExpectNoDoc(); - tt.WithExistingDoc() - .Run(get, set1, update2) - .ExpectDoc(MapFieldValue{{"foo", FieldValue::String("bar2")}}); - tt.WithExistingDoc() - .Run(get, set1, set2) - .ExpectDoc(MapFieldValue{{"foo", FieldValue::String("bar2")}}); -} - -TEST_F(TransactionTest, TestRunsTransactionsAfterGettingExistingDoc) { - SCOPED_TRACE("TestRunsTransactionsAfterGettingExistingDoc"); - - TransactionTester tt = TransactionTester(TestFirestore()); - tt.WithNonexistentDoc().Run(get, delete1, delete1).ExpectNoDoc(); - tt.WithNonexistentDoc() - .Run(get, delete1, update2) - .ExpectError(Error::kErrorInvalidArgument); - tt.WithNonexistentDoc() - .Run(get, delete1, set2) - .ExpectDoc(MapFieldValue{{"foo", FieldValue::String("bar2")}}); - - tt.WithNonexistentDoc() - .Run(get, update1, delete1) - .ExpectError(Error::kErrorInvalidArgument); - tt.WithNonexistentDoc() - .Run(get, update1, update2) - .ExpectError(Error::kErrorInvalidArgument); - tt.WithNonexistentDoc() - .Run(get, update1, set2) - .ExpectError(Error::kErrorInvalidArgument); - - tt.WithNonexistentDoc().Run(get, set1, delete1).ExpectNoDoc(); - tt.WithNonexistentDoc() - .Run(get, set1, update2) - .ExpectDoc(MapFieldValue{{"foo", FieldValue::String("bar2")}}); - tt.WithNonexistentDoc() - .Run(get, set1, set2) - .ExpectDoc(MapFieldValue{{"foo", FieldValue::String("bar2")}}); -} - -TEST_F(TransactionTest, TestRunsTransactionsOnExistingDoc) { - SCOPED_TRACE("TestRunTransactionsOnExistingDoc"); - - TransactionTester tt = TransactionTester(TestFirestore()); - tt.WithExistingDoc().Run(delete1, delete1).ExpectNoDoc(); - tt.WithExistingDoc() - .Run(delete1, update2) - .ExpectError(Error::kErrorInvalidArgument); - tt.WithExistingDoc() - .Run(get, delete1, set2) - .ExpectDoc(MapFieldValue{{"foo", FieldValue::String("bar2")}}); - - tt.WithExistingDoc().Run(update1, delete1).ExpectNoDoc(); - tt.WithExistingDoc() - .Run(update1, update2) - .ExpectDoc(MapFieldValue{{"foo", FieldValue::String("bar2")}}); - tt.WithExistingDoc() - .Run(update1, set2) - .ExpectDoc(MapFieldValue{{"foo", FieldValue::String("bar2")}}); - - tt.WithExistingDoc().Run(set1, delete1).ExpectNoDoc(); - tt.WithExistingDoc() - .Run(set1, update2) - .ExpectDoc(MapFieldValue{{"foo", FieldValue::String("bar2")}}); - tt.WithExistingDoc() - .Run(set1, set2) - .ExpectDoc(MapFieldValue{{"foo", FieldValue::String("bar2")}}); -} - -TEST_F(TransactionTest, TestRunsTransactionsOnNonexistentDoc) { - SCOPED_TRACE("TestRunsTransactionsOnNonexistentDoc"); - - TransactionTester tt = TransactionTester(TestFirestore()); - tt.WithNonexistentDoc().Run(delete1, delete1).ExpectNoDoc(); - tt.WithNonexistentDoc() - .Run(delete1, update2) - .ExpectError(Error::kErrorInvalidArgument); - tt.WithNonexistentDoc() - .Run(delete1, set2) - .ExpectDoc(MapFieldValue{{"foo", FieldValue::String("bar2")}}); - - tt.WithNonexistentDoc() - .Run(update1, delete1) - .ExpectError(Error::kErrorNotFound); - tt.WithNonexistentDoc() - .Run(update1, update2) - .ExpectError(Error::kErrorNotFound); - tt.WithNonexistentDoc().Run(update1, set2).ExpectError(Error::kErrorNotFound); - - tt.WithNonexistentDoc().Run(set1, delete1).ExpectNoDoc(); - tt.WithNonexistentDoc() - .Run(set1, update2) - .ExpectDoc(MapFieldValue{{"foo", FieldValue::String("bar2")}}); - tt.WithNonexistentDoc() - .Run(set1, set2) - .ExpectDoc(MapFieldValue{{"foo", FieldValue::String("bar2")}}); -} - -TEST_F(TransactionTest, TestGetNonexistentDocumentThenFailPatch) { - DocumentReference doc = TestFirestore()->Collection("towns").Document(); - - SCOPED_TRACE("TestGetNonexistentDocumentThenFailPatch"); - RunTransactionAndExpect( - Error::kErrorInvalidArgument, - "Can't update a document that doesn't exist.", - [doc](Transaction& transaction, std::string& error_message) -> Error { - Error error = Error::kErrorOk; - DocumentSnapshot snapshot = - transaction.Get(doc, &error, &error_message); - EXPECT_EQ(Error::kErrorOk, error); - EXPECT_FALSE(snapshot.exists()); - transaction.Update(doc, - MapFieldValue{{"foo", FieldValue::String("bar")}}); - return error; - }); -} - -TEST_F(TransactionTest, TestSetDocumentWithMerge) { - DocumentReference doc = TestFirestore()->Collection("towns").Document(); - - SCOPED_TRACE("TestSetDocumentWithMerge"); - RunTransactionAndExpect( - Error::kErrorOk, - [doc](Transaction& transaction, std::string& error_message) -> Error { - transaction.Set( - doc, - MapFieldValue{{"a", FieldValue::String("b")}, - {"nested", FieldValue::Map(MapFieldValue{ - {"a", FieldValue::String("b")}})}}); - transaction.Set( - doc, - MapFieldValue{{"c", FieldValue::String("d")}, - {"nested", FieldValue::Map(MapFieldValue{ - {"c", FieldValue::String("d")}})}}, - SetOptions::Merge()); - return Error::kErrorOk; - }); - - DocumentSnapshot snapshot = ReadDocument(doc); - EXPECT_THAT(snapshot.GetData(), - testing::ContainerEq(MapFieldValue{ - {"a", FieldValue::String("b")}, - {"c", FieldValue::String("d")}, - {"nested", FieldValue::Map(MapFieldValue{ - {"a", FieldValue::String("b")}, - {"c", FieldValue::String("d")}})}})); -} - -TEST_F(TransactionTest, TestCannotUpdateNonExistentDocument) { - DocumentReference doc = TestFirestore()->Collection("towns").Document(); - - SCOPED_TRACE("TestCannotUpdateNonExistentDocument"); - RunTransactionAndExpect( - Error::kErrorNotFound, "", - [doc](Transaction& transaction, std::string& error_message) -> Error { - transaction.Update(doc, - MapFieldValue{{"foo", FieldValue::String("bar")}}); - return Error::kErrorOk; - }); - - DocumentSnapshot snapshot = ReadDocument(doc); - EXPECT_FALSE(snapshot.exists()); -} - -TEST_F(TransactionTest, TestIncrementTransactionally) { - // A set of concurrent transactions. - std::vector> transaction_tasks; - // A barrier to make sure every transaction reaches the same spot. - Semaphore write_barrier{0}; - // Use these two as a portable way to mimic atomic integer. - Mutex started_locker; - int started = 0; - - DocumentReference doc = TestFirestore()->Collection("counters").Document(); - WriteDocument(doc, MapFieldValue{{"count", FieldValue::Double(5.0)}}); - - // Make 3 transactions that will all increment. - // Note: Visual Studio 2015 incorrectly requires `kTotal` to be captured in - // the lambda, even though it's a constant expression. Adding `static` as - // a workaround. - static constexpr int kTotal = 3; - for (int i = 0; i < kTotal; ++i) { - transaction_tasks.push_back(TestFirestore()->RunTransaction( - [doc, &write_barrier, &started_locker, &started]( - Transaction& transaction, std::string& error_message) -> Error { - Error error = Error::kErrorOk; - DocumentSnapshot snapshot = - transaction.Get(doc, &error, &error_message); - EXPECT_EQ(Error::kErrorOk, error); - { - MutexLock lock(started_locker); - ++started; - // Once all of the transactions have read, allow the first write. - if (started == kTotal) { - write_barrier.Post(); - } - } - - // Let all of the transactions fetch the old value and stop once. - write_barrier.Wait(); - // Refill the barrier so that the other transactions and retries - // succeed. - write_barrier.Post(); - - double new_count = snapshot.Get("count").double_value() + 1.0; - transaction.Set( - doc, MapFieldValue{{"count", FieldValue::Double(new_count)}}); - return error; - })); - } - - // Until we have another Await() that waits for multiple Futures, we do the - // wait in one by one. - while (!transaction_tasks.empty()) { - Future future = transaction_tasks.back(); - transaction_tasks.pop_back(); - Await(future); - EXPECT_EQ(Error::kErrorOk, future.error()); - } - // Now all transaction should be completed, so check the result. - DocumentSnapshot snapshot = ReadDocument(doc); - EXPECT_DOUBLE_EQ(5.0 + kTotal, snapshot.Get("count").double_value()); -} - -TEST_F(TransactionTest, TestUpdateTransactionally) { - // A set of concurrent transactions. - std::vector> transaction_tasks; - // A barrier to make sure every transaction reaches the same spot. - Semaphore write_barrier{0}; - // Use these two as a portable way to mimic atomic integer. - Mutex started_locker; - int started = 0; - - DocumentReference doc = TestFirestore()->Collection("counters").Document(); - WriteDocument(doc, MapFieldValue{{"count", FieldValue::Double(5.0)}, - {"other", FieldValue::String("yes")}}); - - // Make 3 transactions that will all increment. - static const constexpr int kTotal = 3; - for (int i = 0; i < kTotal; ++i) { - transaction_tasks.push_back(TestFirestore()->RunTransaction( - [doc, &write_barrier, &started_locker, &started]( - Transaction& transaction, std::string& error_message) -> Error { - Error error = Error::kErrorOk; - DocumentSnapshot snapshot = - transaction.Get(doc, &error, &error_message); - EXPECT_EQ(Error::kErrorOk, error); - { - MutexLock lock(started_locker); - ++started; - // Once all of the transactions have read, allow the first write. - if (started == kTotal) { - write_barrier.Post(); - } - } - - // Let all of the transactions fetch the old value and stop once. - write_barrier.Wait(); - // Refill the barrier so that the other transactions and retries - // succeed. - write_barrier.Post(); - - double new_count = snapshot.Get("count").double_value() + 1.0; - transaction.Update( - doc, MapFieldValue{{"count", FieldValue::Double(new_count)}}); - return error; - })); - } - - // Until we have another Await() that waits for multiple Futures, we do the - // wait in backward order. - while (!transaction_tasks.empty()) { - Future future = transaction_tasks.back(); - transaction_tasks.pop_back(); - Await(future); - EXPECT_EQ(Error::kErrorOk, future.error()); - } - // Now all transaction should be completed, so check the result. - DocumentSnapshot snapshot = ReadDocument(doc); - EXPECT_DOUBLE_EQ(5.0 + kTotal, snapshot.Get("count").double_value()); - EXPECT_EQ("yes", snapshot.Get("other").string_value()); -} - -TEST_F(TransactionTest, TestUpdateFieldsWithDotsTransactionally) { - DocumentReference doc = TestFirestore()->Collection("fieldnames").Document(); - WriteDocument(doc, MapFieldValue{{"a.b", FieldValue::String("old")}, - {"c.d", FieldValue::String("old")}, - {"e.f", FieldValue::String("old")}}); - - SCOPED_TRACE("TestUpdateFieldsWithDotsTransactionally"); - RunTransactionAndExpect( - Error::kErrorOk, - [doc](Transaction& transaction, std::string& error_message) -> Error { - transaction.Update(doc, MapFieldPathValue{{FieldPath{"a.b"}, - FieldValue::String("new")}}); - transaction.Update(doc, MapFieldPathValue{{FieldPath{"c.d"}, - FieldValue::String("new")}}); - return Error::kErrorOk; - }); - - DocumentSnapshot snapshot = ReadDocument(doc); - EXPECT_THAT(snapshot.GetData(), testing::ContainerEq(MapFieldValue{ - {"a.b", FieldValue::String("new")}, - {"c.d", FieldValue::String("new")}, - {"e.f", FieldValue::String("old")}})); -} - -TEST_F(TransactionTest, TestUpdateNestedFieldsTransactionally) { - DocumentReference doc = TestFirestore()->Collection("fieldnames").Document(); - WriteDocument( - doc, MapFieldValue{ - {"a", FieldValue::Map({{"b", FieldValue::String("old")}})}, - {"c", FieldValue::Map({{"d", FieldValue::String("old")}})}, - {"e", FieldValue::Map({{"f", FieldValue::String("old")}})}}); - - SCOPED_TRACE("TestUpdateNestedFieldsTransactionally"); - RunTransactionAndExpect( - Error::kErrorOk, - [doc](Transaction& transaction, std::string& error_message) -> Error { - transaction.Update(doc, - MapFieldValue{{"a.b", FieldValue::String("new")}}); - transaction.Update(doc, - MapFieldValue{{"c.d", FieldValue::String("new")}}); - return Error::kErrorOk; - }); - - DocumentSnapshot snapshot = ReadDocument(doc); - EXPECT_THAT(snapshot.GetData(), - testing::ContainerEq(MapFieldValue{ - {"a", FieldValue::Map({{"b", FieldValue::String("new")}})}, - {"c", FieldValue::Map({{"d", FieldValue::String("new")}})}, - {"e", FieldValue::Map({{"f", FieldValue::String("old")}})}})); -} - -#if defined(__ANDROID__) -// TODO(b/136012313): on iOS, this triggers assertion failure. -TEST_F(TransactionTest, TestCannotReadAfterWriting) { - DocumentReference doc = TestFirestore()->Collection("anything").Document(); - DocumentSnapshot snapshot; - - SCOPED_TRACE("TestCannotReadAfterWriting"); - RunTransactionAndExpect( - Error::kErrorInvalidArgument, - "Firestore transactions require all reads to be " - "executed before all writes.", - [doc, &snapshot](Transaction& transaction, - std::string& error_message) -> Error { - Error error = Error::kErrorOk; - transaction.Set(doc, MapFieldValue{{"foo", FieldValue::String("bar")}}); - snapshot = transaction.Get(doc, &error, &error_message); - return error; - }); - - snapshot = ReadDocument(doc); - EXPECT_FALSE(snapshot.exists()); -} -#endif - -TEST_F(TransactionTest, TestCanHaveGetsWithoutMutations) { - DocumentReference doc1 = TestFirestore()->Collection("foo").Document(); - DocumentReference doc2 = TestFirestore()->Collection("foo").Document(); - WriteDocument(doc1, MapFieldValue{{"foo", FieldValue::String("bar")}}); - DocumentSnapshot snapshot; - - SCOPED_TRACE("TestCanHaveGetsWithoutMutations"); - RunTransactionAndExpect( - Error::kErrorOk, - [doc1, doc2, &snapshot](Transaction& transaction, - std::string& error_message) -> Error { - Error error = Error::kErrorOk; - transaction.Get(doc2, &error, &error_message); - EXPECT_EQ(Error::kErrorOk, error); - snapshot = transaction.Get(doc1, &error, &error_message); - EXPECT_EQ(Error::kErrorOk, error); - return error; - }); - EXPECT_TRUE(snapshot.exists()); - EXPECT_THAT( - snapshot.GetData(), - testing::ContainerEq(MapFieldValue{{"foo", FieldValue::String("bar")}})); -} - -TEST_F(TransactionTest, TestSuccessWithNoTransactionOperations) { - SCOPED_TRACE("TestSuccessWithNoTransactionOperations"); - RunTransactionAndExpect( - Error::kErrorOk, - [](Transaction&, std::string&) -> Error { return Error::kErrorOk; }); -} - -TEST_F(TransactionTest, TestCancellationOnError) { - DocumentReference doc = TestFirestore()->Collection("towns").Document(); - // Use these two as a portable way to mimic atomic integer. - Mutex count_locker; - int count = 0; - - SCOPED_TRACE("TestCancellationOnError"); - RunTransactionAndExpect( - Error::kErrorDeadlineExceeded, "no", - [doc, &count_locker, &count](Transaction& transaction, - std::string& error_message) -> Error { - { - MutexLock lock{count_locker}; - ++count; - } - transaction.Set(doc, MapFieldValue{{"foo", FieldValue::String("bar")}}); - error_message = "no"; - return Error::kErrorDeadlineExceeded; - }); - - // TODO(varconst): uncomment. Currently, there is no way in C++ to distinguish - // user error, so the transaction gets retried, and the counter goes up to 6. - // EXPECT_EQ(1, count); - DocumentSnapshot snapshot = ReadDocument(doc); - EXPECT_FALSE(snapshot.exists()); -} - -#endif // defined(FIREBASE_USE_STD_FUNCTION) - -#endif // defined(__ANDROID__) || defined(__APPLE__) - -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/type_test.cc b/firestore/src/tests/type_test.cc deleted file mode 100644 index 328397600c..0000000000 --- a/firestore/src/tests/type_test.cc +++ /dev/null @@ -1,71 +0,0 @@ -#include "app/src/log.h" -#include "firestore/src/include/firebase/firestore.h" -#include "firestore/src/tests/firestore_integration_test.h" - -// These test cases are in sync with native iOS client SDK test -// Firestore/Example/Tests/Integration/API/FIRTypeTests.mm -// and native Android client SDK test -// firebase_firestore/tests/integration_tests/src/com/google/firebase/firestore/TypeTest.java - -namespace firebase { -namespace firestore { - -class TypeTest : public FirestoreIntegrationTest { - public: - // Write the specified data to Firestore as a document and read that document. - // Check the data read from that document matches with the original data. - void AssertSuccessfulRoundTrip(MapFieldValue data) { - TestFirestore()->set_log_level(LogLevel::kLogLevelDebug); - DocumentReference reference = TestFirestore()->Document("rooms/eros"); - WriteDocument(reference, data); - DocumentSnapshot snapshot = ReadDocument(reference); - EXPECT_TRUE(snapshot.exists()); - EXPECT_EQ(snapshot.GetData(), data); - } -}; - -TEST_F(TypeTest, TestCanReadAndWriteNullFields) { - AssertSuccessfulRoundTrip( - {{"a", FieldValue::Integer(1)}, {"b", FieldValue::Null()}}); -} - -TEST_F(TypeTest, TestCanReadAndWriteArrayFields) { - AssertSuccessfulRoundTrip( - {{"array", FieldValue::Array( - {FieldValue::Integer(1), FieldValue::String("foo"), - FieldValue::Map({{"deep", FieldValue::Boolean(true)}}), - FieldValue::Null()})}}); -} - -TEST_F(TypeTest, TestCanReadAndWriteBlobFields) { - uint8_t blob[3] = {0, 1, 2}; - AssertSuccessfulRoundTrip({{"blob", FieldValue::Blob(blob, 3)}}); -} - -TEST_F(TypeTest, TestCanReadAndWriteGeoPointFields) { - AssertSuccessfulRoundTrip({{"geoPoint", FieldValue::GeoPoint({1.23, 4.56})}}); -} - -TEST_F(TypeTest, TestCanReadAndWriteDateFields) { - AssertSuccessfulRoundTrip( - {{"date", FieldValue::Timestamp(Timestamp::FromTimeT(1491847082))}}); -} - -TEST_F(TypeTest, TestCanReadAndWriteTimestampFields) { - AssertSuccessfulRoundTrip( - {{"date", FieldValue::Timestamp({123456, 123456000})}}); -} - -TEST_F(TypeTest, TestCanReadAndWriteDocumentReferences) { - AssertSuccessfulRoundTrip({{"a", FieldValue::Integer(42)}, - {"ref", FieldValue::Reference(Document())}}); -} - -TEST_F(TypeTest, TestCanReadAndWriteDocumentReferencesInArrays) { - AssertSuccessfulRoundTrip( - {{"a", FieldValue::Integer(42)}, - {"refs", FieldValue::Array({FieldValue::Reference(Document())})}}); -} - -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/validation_test.cc b/firestore/src/tests/validation_test.cc deleted file mode 100644 index 0ff12762b0..0000000000 --- a/firestore/src/tests/validation_test.cc +++ /dev/null @@ -1,954 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "app/src/log.h" -#include "auth/src/include/firebase/auth.h" -#include "firestore/src/common/macros.h" -#include "firestore/src/include/firebase/firestore.h" -#include "firestore/src/tests/firestore_integration_test.h" -#include "firestore/src/tests/util/event_accumulator.h" -#include "testing/base/public/gmock.h" -#include "gtest/gtest.h" -#include "firebase/firestore/firestore_errors.h" -#include "Firestore/core/src/util/firestore_exceptions.h" - -// These test cases are in sync with native iOS client SDK test -// Firestore/Example/Tests/Integration/API/FIRValidationTests.mm -// and native Android client SDK test -// firebase_firestore/tests/integration_tests/src/com/google/firebase/firestore/ValidationTest.java -// -// PORT_NOTE: C++ API Guidelines (http://g3doc/firebase/g3doc/cpp-api-style.md) -// discourage the use of exceptions in the Firebase Games's SDK. So in release, -// we do not throw exception while only dump exception info to logs. However, in -// order to test this behavior, we enable exception here and check exceptions. - -namespace firebase { -namespace firestore { - -// This eventually works for iOS as well and becomes the cross-platform test for -// C++ client SDK. For now, only enabled for Android platform. - -#if FIRESTORE_HAVE_EXCEPTIONS - -using firebase::auth::Auth; -using testing::AnyOf; -using testing::Property; -using testing::StrEq; -using testing::Throws; - -#define EXPECT_ERROR(stmt, message) \ - EXPECT_THAT([&] { stmt; }, Throws(Property( \ - &std::exception::what, StrEq(message)))); - -/** - * Creates a Matcher that matches any of a number of alternative error messages. - * This is useful temporarily because the different platforms produce different - * error messages from the same validation criteria. - * - * Every usage of this method is an indication of a disagreement between these - * platforms and ultimately must be eliminated. - * - * TODO(b/171990785): Unify Android and C++ validation error messages. - * Remove this method once that's done. - */ -template -auto AnyMessageEq(Messages&&... messages) - -> decltype(AnyOf(StrEq(messages)...)) { - return AnyOf(StrEq(messages)...); -} - -class ValidationTest : public FirestoreIntegrationTest { - protected: - /** - * Performs a write using each write API and makes sure it fails with the - * expected reason. - */ - void ExpectWriteError(const MapFieldValue& data, const std::string& reason) { - ExpectWriteError(data, reason, /*include_sets=*/true, - /*include_updates=*/true); - } - - /** - * Performs a write using each update API and makes sure it fails with the - * expected reason. - */ - void ExpectUpdateError(const MapFieldValue& data, const std::string& reason) { - ExpectWriteError(data, reason, /*include_sets=*/false, - /*include_updates=*/true); - } - - /** - * Performs a write using each set API and makes sure it fails with the - * expected reason. - */ - void ExpectSetError(const MapFieldValue& data, const std::string& reason) { - ExpectWriteError(data, reason, /*include_sets=*/true, - /*include_updates=*/false); - } - - /** - * Performs a write using each set and/or update API and makes sure it fails - * with the expected reason. - */ - void ExpectWriteError(const MapFieldValue& data, const std::string& reason, - bool include_sets, bool include_updates) { - DocumentReference document = Document(); - - if (include_sets) { - EXPECT_ERROR(document.Set(data), reason); - EXPECT_ERROR(TestFirestore()->batch().Set(document, data), reason); - } - - if (include_updates) { - EXPECT_ERROR(document.Update(data), reason); - EXPECT_ERROR(TestFirestore()->batch().Update(document, data), reason); - } - -#if defined(FIREBASE_USE_STD_FUNCTION) - Await(TestFirestore()->RunTransaction( - [data, reason, include_sets, include_updates, document]( - Transaction& transaction, std::string& error_message) -> Error { - if (include_sets) { - EXPECT_ERROR(transaction.Set(document, data), reason); - } - if (include_updates) { - EXPECT_ERROR(transaction.Update(document, data), reason); - } - return Error::kErrorOk; - })); -#endif // defined(FIREBASE_USE_STD_FUNCTION) - } - - /** - * Tests a field path with all of our APIs that accept field paths and ensures - * they fail with the specified reason. - */ - // TODO(varconst): this function is pretty much commented out. - void VerifyFieldPathThrows(const std::string& path, - const std::string& reason) { - // Get an arbitrary snapshot we can use for testing. - DocumentReference document = Document(); - WriteDocument(document, MapFieldValue{{"test", FieldValue::Integer(1)}}); - DocumentSnapshot snapshot = ReadDocument(document); - - // snapshot paths - try { - snapshot.Get(path); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_EQ(reason, exception.what()); - } - - // Query filter / order fields - CollectionReference collection = Collection(); - // WhereLessThan(), etc. omitted for brevity since the code path is - // trivially shared. - try { - collection.WhereEqualTo(path, FieldValue::Integer(1)); - FAIL() << "should throw exception" << path; - } catch (const std::invalid_argument& exception) { - EXPECT_EQ(reason, exception.what()); - } - try { - collection.OrderBy(path); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_EQ(reason, exception.what()); - } - - // update() paths. - try { - document.Update(MapFieldValue{{path, FieldValue::Integer(1)}}); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_THAT( - exception.what(), - AnyMessageEq( - reason, - "Use FieldPath.of() for field names containing '~*/[]'.")); - } - } -}; - -// PORT_NOTE: Does not apply to C++ as host parameter is passed by value. -TEST_F(ValidationTest, FirestoreSettingsNullHostFails) {} - -TEST_F(ValidationTest, ChangingSettingsAfterUseFails) { - DocumentReference reference = Document(); - // Force initialization of the underlying client - WriteDocument(reference, MapFieldValue{{"key", FieldValue::String("value")}}); - Settings setting; - setting.set_host("foo"); - try { - TestFirestore()->set_settings(setting); - FAIL() << "should throw exception"; - } catch (const std::logic_error& exception) { - EXPECT_THAT( - exception.what(), - AnyMessageEq( - "Firestore instance has already been started and its settings can " - "no longer be changed. You can only set settings before calling " - "any other methods on a Firestore instance.", - "FirebaseFirestore has already been started and its settings can " - "no longer be changed. You can only call setFirestoreSettings() " - "before calling any other methods on a FirebaseFirestore object.")); - } -} - -TEST_F(ValidationTest, DisableSslWithoutSettingHostFails) { - Settings setting; - setting.set_ssl_enabled(false); - try { - TestFirestore()->set_settings(setting); - FAIL() << "should throw exception"; - } catch (const std::logic_error& exception) { - EXPECT_STREQ( - "You can't set the 'sslEnabled' setting unless you also set a " - "non-default 'host'.", - exception.what()); - } -} - -// PORT_NOTE: Does not apply to C++ as host parameter is passed by value. -TEST_F(ValidationTest, FirestoreGetInstanceWithNullAppFails) {} - -TEST_F(ValidationTest, - FirestoreGetInstanceWithNonNullAppReturnsNonNullInstance) { - try { - // The app instance is deleted by FirestoreIntegrationTest. - auto* app = this->app(); - - InitResult init_result; - auto auth = UniquePtr(Auth::GetAuth(app, &init_result)); -#if defined(__ANDROID__) - if (init_result != kInitResultSuccess) { - // On Android, it's possible for the Auth library built at head to be too - // new for the version of Play Services available in the Android emulator. - // In this case, Auth will fail to initialize. Meanwhile, there's no - // simple way to detect if the Android app is running in an emulator - // running on Forge. Consequently, just punt if Auth fails to initialize. - LogWarning( - "Skipped FirestoreGetInstanceWithNonNullAppReturnsNonNullInstance " - "test: Auth missing or failed to initialize"); - return; - } -#else - ASSERT_EQ(init_result, kInitResultSuccess); -#endif - - auto db = UniquePtr(Firestore::GetInstance(app, &init_result)); - EXPECT_EQ(kInitResultSuccess, init_result); - } catch (const std::exception& exception) { - FAIL() << "shouldn't throw exception"; - } -} - -TEST_F(ValidationTest, CollectionPathsMustBeOddLength) { - Firestore* db = TestFirestore(); - DocumentReference base_document = db->Document("foo/bar"); - std::vector bad_absolute_paths = {"foo/bar", "foo/bar/baz/quu"}; - std::vector bad_relative_paths = {"/", "baz/quu"}; - std::vector expect_errors = { - "Invalid collection reference. Collection references must have an odd " - "number of segments, but foo/bar has 2", - "Invalid collection reference. Collection references must have an odd " - "number of segments, but foo/bar/baz/quu has 4", - }; - for (int i = 0; i < expect_errors.size(); ++i) { - try { - db->Collection(bad_absolute_paths[i]); - FAIL() << "should throw exception"; - } catch (const std::exception& exception) { - EXPECT_EQ(expect_errors[i], exception.what()); - } - try { - base_document.Collection(bad_relative_paths[i]); - FAIL() << "should throw exception"; - } catch (const std::exception& exception) { - EXPECT_EQ(expect_errors[i], exception.what()); - } - } -} - -TEST_F(ValidationTest, PathsMustNotHaveEmptySegments) { - Firestore* db = TestFirestore(); - // NOTE: leading / trailing slashes are okay. - db->Collection("/foo/"); - db->Collection("/foo"); - db->Collection("foo/"); - - std::vector bad_paths = {"foo//bar//baz", "//foo", "foo//"}; - CollectionReference collection = db->Collection("test-collection"); - DocumentReference document = collection.Document("test-document"); - for (const std::string& path : bad_paths) { - std::string reason = - "Invalid path (" + path + "). Paths must not contain // in them."; - try { - db->Collection(path); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_EQ(reason, exception.what()); - } - try { - db->Document(path); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_EQ(reason, exception.what()); - } - try { - collection.Document(path); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_EQ(reason, exception.what()); - } - try { - document.Collection(path); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_EQ(reason, exception.what()); - } - } -} - -TEST_F(ValidationTest, DocumentPathsMustBeEvenLength) { - Firestore* db = TestFirestore(); - CollectionReference base_collection = db->Collection("foo"); - std::vector bad_absolute_paths = {"foo", "foo/bar/baz"}; - std::vector bad_relative_paths = {"/", "bar/baz"}; - std::vector expect_errors = { - "Invalid document reference. Document references must have an even " - "number of segments, but foo has 1", - "Invalid document reference. Document references must have an even " - "number of segments, but foo/bar/baz has 3", - }; - for (int i = 0; i < expect_errors.size(); ++i) { - try { - db->Document(bad_absolute_paths[i]); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_EQ(expect_errors[i], exception.what()); - } - try { - base_collection.Document(bad_relative_paths[i]); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_EQ(expect_errors[i], exception.what()); - } - } -} - -// PORT_NOTE: Does not apply to C++ which is strong-typed. -TEST_F(ValidationTest, WritesMustBeMapsOrPOJOs) {} - -TEST_F(ValidationTest, WritesMustNotContainDirectlyNestedLists) { - SCOPED_TRACE("WritesMustNotContainDirectlyNestedLists"); - - ExpectWriteError( - MapFieldValue{ - {"nested-array", - FieldValue::Array({FieldValue::Integer(1), - FieldValue::Array({FieldValue::Integer(2)})})}}, - "Invalid data. Nested arrays are not supported"); -} - -TEST_F(ValidationTest, WritesMayContainIndirectlyNestedLists) { - MapFieldValue data = { - {"nested-array", - FieldValue::Array( - {FieldValue::Integer(1), - FieldValue::Map({{"foo", FieldValue::Integer(2)}})})}}; - - CollectionReference collection = Collection(); - DocumentReference document = collection.Document(); - DocumentReference another_document = collection.Document(); - - Await(document.Set(data)); - Await(TestFirestore()->batch().Set(document, data).Commit()); - - Await(document.Update(data)); - Await(TestFirestore()->batch().Update(document, data).Commit()); - -#if defined(FIREBASE_USE_STD_FUNCTION) - Await(TestFirestore()->RunTransaction( - [data, document, another_document](Transaction& transaction, - std::string& error_message) -> Error { - // Note another_document does not exist at this point so set that and - // update document. - transaction.Update(document, data); - transaction.Set(another_document, data); - return Error::kErrorOk; - })); -#endif // defined(FIREBASE_USE_STD_FUNCTION) -} - -// TODO(zxu): There is no way to create Firestore with different project id yet. -TEST_F(ValidationTest, WritesMustNotContainReferencesToADifferentDatabase) {} - -TEST_F(ValidationTest, WritesMustNotContainReservedFieldNames) { - SCOPED_TRACE("WritesMustNotContainReservedFieldNames"); - - ExpectWriteError(MapFieldValue{{"__baz__", FieldValue::Integer(1)}}, - "Invalid data. Document fields cannot begin and end with " - "\"__\" (found in field __baz__)"); - ExpectWriteError( - MapFieldValue{ - {"foo", FieldValue::Map({{"__baz__", FieldValue::Integer(1)}})}}, - "Invalid data. Document fields cannot begin and end with \"__\" (found " - "in field foo.__baz__)"); - ExpectWriteError( - MapFieldValue{ - {"__baz__", FieldValue::Map({{"foo", FieldValue::Integer(1)}})}}, - "Invalid data. Document fields cannot begin and end with \"__\" (found " - "in field __baz__)"); - - ExpectUpdateError(MapFieldValue{{"__baz__", FieldValue::Integer(1)}}, - "Invalid data. Document fields cannot begin and end with " - "\"__\" (found in field __baz__)"); - ExpectUpdateError(MapFieldValue{{"baz.__foo__", FieldValue::Integer(1)}}, - "Invalid data. Document fields cannot begin and end with " - "\"__\" (found in field baz.__foo__)"); -} - -TEST_F(ValidationTest, SetsMustNotContainFieldValueDelete) { - SCOPED_TRACE("SetsMustNotContainFieldValueDelete"); - - // TODO(b/171990785): Unify Android and C++ validation error messages. -#if defined(__ANDROID__) - std::string message = - "Invalid data. FieldValue.delete() can only be used with update() and " - "set() with SetOptions.merge() (found in field foo)"; -#else - std::string message = - "Invalid data. FieldValue::Delete() can only be used with Update() and " - "Set() with merge == true (found in field foo)"; -#endif // defined(__ANDROID__) - - ExpectSetError(MapFieldValue{{"foo", FieldValue::Delete()}}, message); -} - -TEST_F(ValidationTest, UpdatesMustNotContainNestedFieldValueDeletes) { - SCOPED_TRACE("UpdatesMustNotContainNestedFieldValueDeletes"); - - // TODO(b/171990785): Unify Android and C++ validation error messages. -#if defined(__ANDROID__) - std::string message = - "Invalid data. FieldValue.delete() can only appear at the top level of " - "your update data (found in field foo.bar)"; -#else - std::string message = - "Invalid data. FieldValue::Delete() can only appear at the top level of " - "your update data (found in field foo.bar)"; -#endif // defined(__ANDROID__) - - ExpectUpdateError( - MapFieldValue{{"foo", FieldValue::Map({{"bar", FieldValue::Delete()}})}}, - message); -} - -TEST_F(ValidationTest, BatchWritesRequireCorrectDocumentReferences) { - DocumentReference bad_document = - TestFirestore("another")->Document("foo/bar"); - - WriteBatch batch = TestFirestore()->batch(); - try { - batch.Set(bad_document, MapFieldValue{{"foo", FieldValue::Integer(1)}}); - FAIL() << "should throw exception"; - } catch (const std::exception& exception) { - EXPECT_STREQ( - "Provided document reference is from a different Cloud Firestore " - "instance.", - exception.what()); - } -} - -TEST_F(ValidationTest, TransactionsRequireCorrectDocumentReferences) {} - -TEST_F(ValidationTest, FieldPathsMustNotHaveEmptySegments) { - SCOPED_TRACE("FieldPathsMustNotHaveEmptySegments"); - - std::vector bad_field_paths = {"", "foo..baz", ".foo", "foo."}; - - for (const auto field_path : bad_field_paths) { - std::string reason = "Invalid field path (" + field_path + - "). Paths must not be empty, begin with '.', end with " - "'.', or contain '..'"; - VerifyFieldPathThrows(field_path, reason); - } -} - -TEST_F(ValidationTest, FieldPathsMustNotHaveInvalidSegments) { - SCOPED_TRACE("FieldPathsMustNotHaveInvalidSegments"); - - std::vector bad_field_paths = { - "foo~bar", "foo*bar", "foo/bar", "foo[1", "foo]1", "foo[1]", - }; - - for (const auto field_path : bad_field_paths) { - std::string reason = "Invalid field path (" + field_path + - "). Paths must not contain '~', '*', '/', '[', or ']'"; - VerifyFieldPathThrows(field_path, reason); - } -} - -TEST_F(ValidationTest, FieldNamesMustNotBeEmpty) { - DocumentSnapshot snapshot = ReadDocument(Document()); - // PORT_NOTE: We do not enforce any logic for invalid C++ object. In - // particular the creation of invalid object should be valid (for using - // standard container). We have not defined the behavior to call API with - // invalid object yet. - // try { - // snapshot.Get(FieldPath{}); - // FAIL() << "should throw exception"; - // } catch (const std::exception& exception) { - // EXPECT_STREQ("Invalid field path. Provided path must not be empty.", - // exception.what()); - // } - try { - snapshot.Get(FieldPath{""}); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_THAT( - exception.what(), - AnyMessageEq( - "Invalid field name at index 0. Field names must not be empty.", - "Invalid field name at argument 1. Field names must not be null or " - "empty.")); - } - try { - snapshot.Get(FieldPath{"foo", ""}); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_THAT( - exception.what(), - AnyMessageEq( - "Invalid field name at index 1. Field names must not be empty.", - "Invalid field name at argument 2. Field names must not be null or " - "empty.")); - } -} - -TEST_F(ValidationTest, ArrayTransformsFailInQueries) { - CollectionReference collection = Collection(); - try { - collection.WhereEqualTo( - "test", - FieldValue::Map( - {{"test", FieldValue::ArrayUnion({FieldValue::Integer(1)})}})); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_THAT( - exception.what(), - AnyMessageEq( - "Invalid data. FieldValue::ArrayUnion() can only be used with " - "Update() and Set() (found in field test)", - "Invalid data. FieldValue.arrayUnion() can only be used with set() " - "and update() (found in field test)")); - } - - try { - collection.WhereEqualTo( - "test", - FieldValue::Map( - {{"test", FieldValue::ArrayRemove({FieldValue::Integer(1)})}})); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_THAT( - exception.what(), - AnyMessageEq( - "Invalid data. FieldValue::ArrayRemove() can only be used with " - "Update() and Set() (found in field test)", - "Invalid data. FieldValue.arrayRemove() can only be used with " - "set() and update() (found in field test)")); - } -} - -// PORT_NOTE: Does not apply to C++ which is strong-typed. -TEST_F(ValidationTest, ArrayTransformsRejectInvalidElements) {} - -TEST_F(ValidationTest, ArrayTransformsRejectArrays) { - DocumentReference document = Document(); - // This would result in a directly nested array which is not supported. - try { - document.Set(MapFieldValue{ - {"x", FieldValue::ArrayUnion( - {FieldValue::Integer(1), - FieldValue::Array({FieldValue::String("nested")})})}}); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_STREQ("Invalid data. Nested arrays are not supported", - exception.what()); - } - try { - document.Set(MapFieldValue{ - {"x", FieldValue::ArrayRemove( - {FieldValue::Integer(1), - FieldValue::Array({FieldValue::String("nested")})})}}); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_STREQ("Invalid data. Nested arrays are not supported", - exception.what()); - } -} - -TEST_F(ValidationTest, QueriesWithNonPositiveLimitFail) { - CollectionReference collection = Collection(); - try { - collection.Limit(0); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_STREQ( - "Invalid Query. Query limit (0) is invalid. Limit must be positive.", - exception.what()); - } - try { - collection.Limit(-1); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_STREQ( - "Invalid Query. Query limit (-1) is invalid. Limit must be positive.", - exception.what()); - } -} - -TEST_F(ValidationTest, QueriesCannotBeCreatedFromDocumentsMissingSortValues) { - CollectionReference collection = - Collection(std::map{ - {"f", MapFieldValue{{"k", FieldValue::String("f")}, - {"nosort", FieldValue::Double(1.0)}}}}); - - Query query = collection.OrderBy("sort"); - DocumentSnapshot snapshot = ReadDocument(collection.Document("f")); - - EXPECT_THAT(snapshot.GetData(), testing::ContainerEq(MapFieldValue{ - {"k", FieldValue::String("f")}, - {"nosort", FieldValue::Double(1.0)}})); - - auto matches = AnyMessageEq( - "Invalid query. You are trying to start or end a query using a document " - "for which the field 'sort' (used as the order by) does not exist.", - "Invalid query. You are trying to start or end a query using a document " - "for which the field 'sort' (used as the orderBy) does not exist."); - - try { - query.StartAt(snapshot); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_THAT(exception.what(), matches); - } - try { - query.StartAfter(snapshot); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_THAT(exception.what(), matches); - } - try { - query.EndBefore(snapshot); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_THAT(exception.what(), matches); - } - try { - query.EndAt(snapshot); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_THAT(exception.what(), matches); - } -} - -TEST_F(ValidationTest, QueriesCannotBeSortedByAnUncommittedServerTimestamp) { - CollectionReference collection = Collection(); - EventAccumulator accumulator; - accumulator.listener()->AttachTo(&collection); - - Await(TestFirestore()->DisableNetwork()); - - Future future = collection.Document("doc").Set( - {{"timestamp", FieldValue::ServerTimestamp()}}); - - QuerySnapshot snapshot = accumulator.Await(); - EXPECT_FALSE(snapshot.metadata().has_pending_writes()); - - snapshot = accumulator.Await(); - EXPECT_TRUE(snapshot.metadata().has_pending_writes()); - - EXPECT_THROW(collection.OrderBy(FieldPath({"timestamp"})) - .EndAt(snapshot.documents().at(0)) - .AddSnapshotListener( - [](const QuerySnapshot&, Error, const std::string&) {}), - std::exception); - - Await(TestFirestore()->EnableNetwork()); - Await(future); - - snapshot = accumulator.AwaitRemoteEvent(); - EXPECT_FALSE(snapshot.metadata().has_pending_writes()); - EXPECT_NO_THROW(collection.OrderBy(FieldPath({"timestamp"})) - .EndAt(snapshot.documents().at(0)) - .AddSnapshotListener([](const QuerySnapshot&, Error, - const std::string&) {})); -} - -TEST_F(ValidationTest, QueriesMustNotHaveMoreComponentsThanOrderBy) { - CollectionReference collection = Collection(); - Query query = collection.OrderBy("foo"); - - auto matches = AnyMessageEq( - "Invalid query. You are trying to start or end a query using more values " - "than were specified in the order by.", - "Too many arguments provided to startAt(). The number of arguments must " - "be less than or equal to the number of orderBy() clauses."); - try { - query.StartAt({FieldValue::Integer(1), FieldValue::Integer(2)}); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_THAT(exception.what(), matches); - } - try { - query.OrderBy("bar").StartAt({FieldValue::Integer(1), - FieldValue::Integer(2), - FieldValue::Integer(3)}); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_THAT(exception.what(), matches); - } -} - -TEST_F(ValidationTest, QueryOrderByKeyBoundsMustBeStringsWithoutSlashes) { - CollectionReference collection = Collection(); - Query query = collection.OrderBy(FieldPath::DocumentId()); - try { - query.StartAt({FieldValue::Integer(1)}); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_THAT( - exception.what(), - AnyMessageEq( - "Invalid query. Expected a string for the document ID.", - "Invalid query. Expected a string for document ID in startAt(), " - "but got 1.")); - } - try { - query.StartAt({FieldValue::String("foo/bar")}); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_THAT( - exception.what(), - AnyMessageEq( - "Invalid query. When querying a collection and ordering by " - "document ID, you must pass a plain document ID, but 'foo/bar' " - "contains a slash.", - "Invalid query. When querying a collection and ordering by " - "FieldPath.documentId(), the value passed to startAt() must be a " - "plain document ID, but 'foo/bar' contains a slash.")); - } -} - -TEST_F(ValidationTest, QueriesWithDifferentInequalityFieldsFail) { - try { - Collection() - .WhereGreaterThan("x", FieldValue::Integer(32)) - .WhereLessThan("y", FieldValue::String("cat")); - FAIL() << "should throw exception"; - } catch (const std::invalid_argument& exception) { - EXPECT_THAT( - exception.what(), - AnyMessageEq( - "Invalid Query. All where filters with an inequality (notEqual, " - "lessThan, lessThanOrEqual, greaterThan, or greaterThanOrEqual) " - "must be on the same field. But you have inequality filters on 'x' " - "and 'y'", - "All where filters with an inequality (notEqualTo, notIn, " - "lessThan, lessThanOrEqualTo, greaterThan, or " - "greaterThanOrEqualTo) must be on the same field. But you have " - "filters on 'x' and 'y'")); - } -} - -TEST_F(ValidationTest, QueriesWithInequalityDifferentThanFirstOrderByFail) { - // TODO(b/171990785): Unify Android and C++ validation error messages. -#if defined(__ANDROID__) - const char* reason = - "Invalid query. You have an inequality where filter (whereLessThan(), " - "whereGreaterThan(), etc.) on field 'x' and so you must also have 'x' as " - "your first orderBy() field, but your first orderBy() is currently on " - "field 'y' instead."; -#else - const char* reason = - "Invalid query. You have a where filter with an inequality (notEqual, " - "lessThan, lessThanOrEqual, greaterThan, or greaterThanOrEqual) on field " - "'x' and so you must also use 'x' as your first queryOrderedBy field, " - "but your first queryOrderedBy is currently on field 'y' instead."; -#endif // defined(__ANDROID__) - - CollectionReference collection = Collection(); - try { - collection.WhereGreaterThan("x", FieldValue::Integer(32)).OrderBy("y"); - FAIL() << "should throw exception"; - } catch (const std::exception& exception) { - EXPECT_STREQ(exception.what(), reason); - } - try { - collection.OrderBy("y").WhereGreaterThan("x", FieldValue::Integer(32)); - FAIL() << "should throw exception"; - } catch (const std::exception& exception) { - EXPECT_STREQ(exception.what(), reason); - } - try { - collection.WhereGreaterThan("x", FieldValue::Integer(32)) - .OrderBy("y") - .OrderBy("x"); - FAIL() << "should throw exception"; - } catch (const std::exception& exception) { - EXPECT_STREQ(exception.what(), reason); - } - try { - collection.OrderBy("y").OrderBy("x").WhereGreaterThan( - "x", FieldValue::Integer(32)); - FAIL() << "should throw exception"; - } catch (const std::exception& exception) { - EXPECT_STREQ(exception.what(), reason); - } -} - -TEST_F(ValidationTest, QueriesWithMultipleArrayContainsFiltersFail) { - try { - Collection() - .WhereArrayContains("foo", FieldValue::Integer(1)) - .WhereArrayContains("foo", FieldValue::Integer(2)); - FAIL() << "should throw exception"; - } catch (const std::exception& exception) { - EXPECT_THAT( - exception.what(), - AnyMessageEq( - "Invalid Query. You cannot use more than one 'arrayContains' " - "filter.", - "Invalid Query. You cannot use more than one 'array_contains' " - "filter.")); - } -} - -TEST_F(ValidationTest, QueriesMustNotSpecifyStartingOrEndingPointAfterOrderBy) { - CollectionReference collection = Collection(); - Query query = collection.OrderBy("foo"); - try { - query.StartAt({FieldValue::Integer(1)}).OrderBy("bar"); - FAIL() << "should throw exception"; - } catch (const std::exception& exception) { - EXPECT_THAT( - exception.what(), - AnyMessageEq( - "Invalid query. You must not specify a starting point before " - "specifying the order by.", - "Invalid query. You must not call Query.startAt() or " - "Query.startAfter() before calling Query.orderBy().")); - } - try { - query.StartAfter({FieldValue::Integer(1)}).OrderBy("bar"); - FAIL() << "should throw exception"; - } catch (const std::exception& exception) { - EXPECT_THAT( - exception.what(), - AnyMessageEq( - "Invalid query. You must not specify a starting point before " - "specifying the order by.", - "Invalid query. You must not call Query.startAt() or " - "Query.startAfter() before calling Query.orderBy().")); - } - try { - query.EndAt({FieldValue::Integer(1)}).OrderBy("bar"); - FAIL() << "should throw exception"; - } catch (const std::exception& exception) { - EXPECT_THAT( - exception.what(), - AnyMessageEq( - "Invalid query. You must not specify an ending point before " - "specifying the order by.", - "Invalid query. You must not call Query.endAt() or " - "Query.endBefore() before calling Query.orderBy().")); - } - try { - query.EndBefore({FieldValue::Integer(1)}).OrderBy("bar"); - FAIL() << "should throw exception"; - } catch (const std::exception& exception) { - EXPECT_THAT( - exception.what(), - AnyMessageEq( - "Invalid query. You must not specify an ending point before " - "specifying the order by.", - "Invalid query. You must not call Query.endAt() or " - "Query.endBefore() before calling Query.orderBy().")); - } -} - -TEST_F(ValidationTest, - QueriesFilteredByDocumentIDMustUseStringsOrDocumentReferences) { - CollectionReference collection = Collection(); - try { - collection.WhereGreaterThanOrEqualTo(FieldPath::DocumentId(), - FieldValue::String("")); - FAIL() << "should throw exception"; - } catch (const std::exception& exception) { - EXPECT_THAT( - exception.what(), - AnyMessageEq( - "Invalid query. When querying by document ID you must provide a " - "valid document ID, but it was an empty string.", - "Invalid query. When querying with FieldPath.documentId() you must " - "provide a valid document ID, but it was an empty string.")); - } - - try { - collection.WhereGreaterThanOrEqualTo(FieldPath::DocumentId(), - FieldValue::String("foo/bar/baz")); - FAIL() << "should throw exception"; - } catch (const std::exception& exception) { - EXPECT_THAT( - exception.what(), - AnyMessageEq( - "Invalid query. When querying a collection by document ID you must " - "provide a plain document ID, but 'foo/bar/baz' contains a '/' " - "character.", - "Invalid query. When querying a collection by " - "FieldPath.documentId() you must provide a plain document ID, but " - "'foo/bar/baz' contains a '/' character.")); - } - - try { - collection.WhereGreaterThanOrEqualTo(FieldPath::DocumentId(), - FieldValue::Integer(1)); - FAIL() << "should throw exception"; - } catch (const std::exception& exception) { - EXPECT_THAT( - exception.what(), - AnyMessageEq( - "Invalid query. When querying by document ID you must provide a " - "valid string or DocumentReference, but it was of type: " - "FieldValue::Integer()", - "Invalid query. When querying with FieldPath.documentId() you must " - "provide a valid String or DocumentReference, but it was of type: " - "java.lang.Long")); - } - - try { - collection.WhereArrayContains(FieldPath::DocumentId(), - FieldValue::Integer(1)); - FAIL() << "should throw exception"; - } catch (const std::exception& exception) { - EXPECT_THAT( - exception.what(), - AnyMessageEq( - "Invalid query. You can't perform arrayContains queries on " - "document ID since document IDs are not arrays.", - "Invalid query. You can't perform 'array_contains' queries on " - "FieldPath.documentId().")); - } -} - -#endif // FIRESTORE_HAVE_EXCEPTIONS - -} // namespace firestore -} // namespace firebase diff --git a/firestore/src/tests/write_batch_test.cc b/firestore/src/tests/write_batch_test.cc deleted file mode 100644 index dd1474a147..0000000000 --- a/firestore/src/tests/write_batch_test.cc +++ /dev/null @@ -1,314 +0,0 @@ -#include - -#include "firestore/src/include/firebase/firestore.h" -#include "firestore/src/tests/firestore_integration_test.h" -#include "firestore/src/tests/util/event_accumulator.h" -#if defined(__ANDROID__) -#include "firestore/src/android/write_batch_android.h" -#include "firestore/src/common/wrapper_assertions.h" -#endif // defined(__ANDROID__) - -#include "testing/base/public/gmock.h" -#include "gtest/gtest.h" - -// These test cases are in sync with native iOS client SDK test -// Firestore/Example/Tests/Integration/API/FIRWriteBatchTests.mm -// and native Android client SDK test -// firebase_firestore/tests/integration_tests/src/com/google/firebase/firestore/WriteBatchTest.java -// The test cases between the two native client SDK divert quite a lot. The port -// here is an effort to do a superset and cover both cases. - -namespace firebase { -namespace firestore { - -using WriteBatchCommonTest = testing::Test; - -using WriteBatchTest = FirestoreIntegrationTest; - -TEST_F(WriteBatchTest, TestSupportEmptyBatches) { - Await(TestFirestore()->batch().Commit()); -} - -TEST_F(WriteBatchTest, TestSetDocuments) { - DocumentReference doc = Document(); - Await(TestFirestore() - ->batch() - .Set(doc, MapFieldValue{{"a", FieldValue::String("b")}}) - .Set(doc, MapFieldValue{{"c", FieldValue::String("d")}}) - .Set(doc, MapFieldValue{{"foo", FieldValue::String("bar")}}) - .Commit()); - DocumentSnapshot snapshot = ReadDocument(doc); - ASSERT_TRUE(snapshot.exists()); - EXPECT_THAT( - snapshot.GetData(), - testing::ContainerEq(MapFieldValue{{"foo", FieldValue::String("bar")}})); -} - -TEST_F(WriteBatchTest, TestSetDocumentWithMerge) { - DocumentReference doc = Document(); - Await(TestFirestore() - ->batch() - .Set(doc, - MapFieldValue{ - {"a", FieldValue::String("b")}, - {"nested", - FieldValue::Map({{"a", FieldValue::String("remove")}})}}, - SetOptions::Merge()) - .Commit()); - Await(TestFirestore() - ->batch() - .Set(doc, - MapFieldValue{ - {"c", FieldValue::String("d")}, - {"ignore", FieldValue::Boolean(true)}, - {"nested", - FieldValue::Map({{"c", FieldValue::String("d")}})}}, - SetOptions::MergeFields({"c", "nested"})) - .Commit()); - Await(TestFirestore() - ->batch() - .Set(doc, - MapFieldValue{ - {"e", FieldValue::String("f")}, - {"nested", FieldValue::Map( - {{"e", FieldValue::String("f")}, - {"ignore", FieldValue::Boolean(true)}})}}, - SetOptions::MergeFieldPaths({{"e"}, {"nested", "e"}})) - .Commit()); - DocumentSnapshot snapshot = ReadDocument(doc); - ASSERT_TRUE(snapshot.exists()); - EXPECT_THAT( - snapshot.GetData(), - testing::ContainerEq(MapFieldValue{ - {"a", FieldValue::String("b")}, - {"c", FieldValue::String("d")}, - {"e", FieldValue::String("f")}, - {"nested", FieldValue::Map({{"c", FieldValue::String("d")}, - {"e", FieldValue::String("f")}})}})); -} - -TEST_F(WriteBatchTest, TestUpdateDocuments) { - DocumentReference doc = Document(); - WriteDocument(doc, MapFieldValue{{"foo", FieldValue::String("bar")}}); - Await(TestFirestore() - ->batch() - .Update(doc, MapFieldValue{{"baz", FieldValue::Integer(42)}}) - .Commit()); - DocumentSnapshot snapshot = ReadDocument(doc); - ASSERT_TRUE(snapshot.exists()); - EXPECT_THAT(snapshot.GetData(), testing::ContainerEq(MapFieldValue{ - {"foo", FieldValue::String("bar")}, - {"baz", FieldValue::Integer(42)}})); -} - -TEST_F(WriteBatchTest, TestCannotUpdateNonexistentDocuments) { - DocumentReference doc = Document(); - Await(TestFirestore() - ->batch() - .Update(doc, MapFieldValue{{"baz", FieldValue::Integer(42)}}) - .Commit()); - DocumentSnapshot snapshot = ReadDocument(doc); - EXPECT_FALSE(snapshot.exists()); -} - -TEST_F(WriteBatchTest, TestDeleteDocuments) { - DocumentReference doc = Document(); - WriteDocument(doc, MapFieldValue{{"foo", FieldValue::String("bar")}}); - DocumentSnapshot snapshot = ReadDocument(doc); - - EXPECT_TRUE(snapshot.exists()); - Await(TestFirestore()->batch().Delete(doc).Commit()); - snapshot = ReadDocument(doc); - EXPECT_FALSE(snapshot.exists()); -} - -TEST_F(WriteBatchTest, TestBatchesCommitAtomicallyRaisingCorrectEvents) { - CollectionReference collection = Collection(); - DocumentReference doc_a = collection.Document("a"); - DocumentReference doc_b = collection.Document("b"); - EventAccumulator accumulator; - accumulator.listener()->AttachTo(&collection, MetadataChanges::kInclude); - QuerySnapshot initial_snapshot = accumulator.Await(); - EXPECT_EQ(0, initial_snapshot.size()); - - // Atomically write two documents. - Await(TestFirestore() - ->batch() - .Set(doc_a, MapFieldValue{{"a", FieldValue::Integer(1)}}) - .Set(doc_b, MapFieldValue{{"b", FieldValue::Integer(2)}}) - .Commit()); - - QuerySnapshot local_snapshot = accumulator.Await(); - EXPECT_TRUE(local_snapshot.metadata().has_pending_writes()); - EXPECT_THAT( - QuerySnapshotToValues(local_snapshot), - testing::ElementsAre(MapFieldValue{{"a", FieldValue::Integer(1)}}, - MapFieldValue{{"b", FieldValue::Integer(2)}})); - - QuerySnapshot server_snapshot = accumulator.Await(); - EXPECT_FALSE(server_snapshot.metadata().has_pending_writes()); - EXPECT_THAT( - QuerySnapshotToValues(server_snapshot), - testing::ElementsAre(MapFieldValue{{"a", FieldValue::Integer(1)}}, - MapFieldValue{{"b", FieldValue::Integer(2)}})); -} - -TEST_F(WriteBatchTest, TestBatchesFailAtomicallyRaisingCorrectEvents) { - CollectionReference collection = Collection(); - DocumentReference doc_a = collection.Document("a"); - DocumentReference doc_b = collection.Document("b"); - EventAccumulator accumulator; - accumulator.listener()->AttachTo(&collection, MetadataChanges::kInclude); - QuerySnapshot initial_snapshot = accumulator.Await(); - EXPECT_EQ(0, initial_snapshot.size()); - - // Atomically write 1 document and update a nonexistent document. - Future future = - TestFirestore() - ->batch() - .Set(doc_a, MapFieldValue{{"a", FieldValue::Integer(1)}}) - .Update(doc_b, MapFieldValue{{"b", FieldValue::Integer(2)}}) - .Commit(); - Await(future); - EXPECT_EQ(FutureStatus::kFutureStatusComplete, future.status()); - EXPECT_EQ(Error::kErrorNotFound, future.error()); - - // Local event with the set document. - QuerySnapshot local_snapshot = accumulator.Await(); - EXPECT_TRUE(local_snapshot.metadata().has_pending_writes()); - EXPECT_THAT( - QuerySnapshotToValues(local_snapshot), - testing::ElementsAre(MapFieldValue{{"a", FieldValue::Integer(1)}})); - - // Server event with the set reverted - QuerySnapshot server_snapshot = accumulator.Await(); - EXPECT_FALSE(server_snapshot.metadata().has_pending_writes()); - EXPECT_EQ(0, server_snapshot.size()); -} - -TEST_F(WriteBatchTest, TestWriteTheSameServerTimestampAcrossWrites) { - CollectionReference collection = Collection(); - DocumentReference doc_a = collection.Document("a"); - DocumentReference doc_b = collection.Document("b"); - EventAccumulator accumulator; - accumulator.listener()->AttachTo(&collection, MetadataChanges::kInclude); - QuerySnapshot initial_snapshot = accumulator.Await(); - EXPECT_EQ(0, initial_snapshot.size()); - - // Atomically write two documents with server timestamps. - Await(TestFirestore() - ->batch() - .Set(doc_a, MapFieldValue{{"when", FieldValue::ServerTimestamp()}}) - .Set(doc_b, MapFieldValue{{"when", FieldValue::ServerTimestamp()}}) - .Commit()); - - QuerySnapshot local_snapshot = accumulator.Await(); - EXPECT_TRUE(local_snapshot.metadata().has_pending_writes()); - EXPECT_THAT( - QuerySnapshotToValues(local_snapshot), - testing::ElementsAre(MapFieldValue{{"when", FieldValue::Null()}}, - MapFieldValue{{"when", FieldValue::Null()}})); - - QuerySnapshot server_snapshot = accumulator.AwaitRemoteEvent(); - EXPECT_FALSE(server_snapshot.metadata().has_pending_writes()); - EXPECT_EQ(2, server_snapshot.size()); - const FieldValue when = server_snapshot.documents()[0].Get("when"); - EXPECT_EQ(FieldValue::Type::kTimestamp, when.type()); - EXPECT_THAT(QuerySnapshotToValues(server_snapshot), - testing::ElementsAre(MapFieldValue{{"when", when}}, - MapFieldValue{{"when", when}})); -} - -TEST_F(WriteBatchTest, TestCanWriteTheSameDocumentMultipleTimes) { - DocumentReference doc = Document(); - EventAccumulator accumulator; - accumulator.listener()->AttachTo(&doc, MetadataChanges::kInclude); - DocumentSnapshot initial_snapshot = accumulator.Await(); - EXPECT_FALSE(initial_snapshot.exists()); - - Await(TestFirestore() - ->batch() - .Delete(doc) - .Set(doc, MapFieldValue{{"a", FieldValue::Integer(1)}, - {"b", FieldValue::Integer(1)}, - {"when", FieldValue::String("when")}}) - .Update(doc, MapFieldValue{{"b", FieldValue::Integer(2)}, - {"when", FieldValue::ServerTimestamp()}}) - .Commit()); - DocumentSnapshot local_snapshot = accumulator.Await(); - EXPECT_TRUE(local_snapshot.metadata().has_pending_writes()); - EXPECT_THAT(local_snapshot.GetData(), testing::ContainerEq(MapFieldValue{ - {"a", FieldValue::Integer(1)}, - {"b", FieldValue::Integer(2)}, - {"when", FieldValue::Null()}})); - - DocumentSnapshot server_snapshot = accumulator.Await(); - EXPECT_FALSE(server_snapshot.metadata().has_pending_writes()); - const FieldValue when = server_snapshot.Get("when"); - EXPECT_EQ(FieldValue::Type::kTimestamp, when.type()); - EXPECT_THAT(server_snapshot.GetData(), - testing::ContainerEq(MapFieldValue{{"a", FieldValue::Integer(1)}, - {"b", FieldValue::Integer(2)}, - {"when", when}})); -} - -TEST_F(WriteBatchTest, TestUpdateFieldsWithDots) { - DocumentReference doc = Document(); - WriteDocument(doc, MapFieldValue{{"a.b", FieldValue::String("old")}, - {"c.d", FieldValue::String("old")}}); - Await(TestFirestore() - ->batch() - .Update(doc, MapFieldPathValue{{FieldPath{"a.b"}, - FieldValue::String("new")}}) - .Commit()); - Await(TestFirestore() - ->batch() - .Update(doc, MapFieldPathValue{{FieldPath{"c.d"}, - FieldValue::String("new")}}) - .Commit()); - DocumentSnapshot snapshot = ReadDocument(doc); - ASSERT_TRUE(snapshot.exists()); - EXPECT_THAT(snapshot.GetData(), testing::ContainerEq(MapFieldValue{ - {"a.b", FieldValue::String("new")}, - {"c.d", FieldValue::String("new")}})); -} - -TEST_F(WriteBatchTest, TestUpdateNestedFields) { - DocumentReference doc = Document(); - WriteDocument( - doc, MapFieldValue{ - {"a", FieldValue::Map({{"b", FieldValue::String("old")}})}, - {"c", FieldValue::Map({{"d", FieldValue::String("old")}})}, - {"e", FieldValue::Map({{"f", FieldValue::String("old")}})}}); - Await(TestFirestore() - ->batch() - .Update(doc, MapFieldValue({{"a.b", FieldValue::String("new")}})) - .Commit()); - Await(TestFirestore() - ->batch() - .Update(doc, MapFieldPathValue({{FieldPath{"c", "d"}, - FieldValue::String("new")}})) - .Commit()); - DocumentSnapshot snapshot = ReadDocument(doc); - ASSERT_TRUE(snapshot.exists()); - EXPECT_THAT(snapshot.GetData(), - testing::ContainerEq(MapFieldValue{ - {"a", FieldValue::Map({{"b", FieldValue::String("new")}})}, - {"c", FieldValue::Map({{"d", FieldValue::String("new")}})}, - {"e", FieldValue::Map({{"f", FieldValue::String("old")}})}})); -} - -#if defined(__ANDROID__) || defined(FIRESTORE_STUB_BUILD) - -TEST_F(WriteBatchCommonTest, Construction) { - testutil::AssertWrapperConstructionContract(); -} - -TEST_F(WriteBatchCommonTest, Assignment) { - testutil::AssertWrapperAssignmentContract(); -} - -#endif // defined(__ANDROID__) || defined(FIRESTORE_STUB_BUILD) - -} // namespace firestore -} // namespace firebase diff --git a/scripts/gha/build_testapps.py b/scripts/gha/build_testapps.py index 3553c40b8d..a79d03d496 100644 --- a/scripts/gha/build_testapps.py +++ b/scripts/gha/build_testapps.py @@ -168,6 +168,10 @@ message="Valid platforms: " + ",".join(_SUPPORTED_PLATFORMS), flag_values=FLAGS) +flags.DEFINE_bool( + "short_output_paths", False, + "Use short directory names for output paths. Useful to avoid hitting file " + "path limits on Windows.") def main(argv): if len(argv) > 1: @@ -185,7 +189,11 @@ def main(argv): timestamp = datetime.datetime.now().strftime("%Y_%m_%d-%H_%M_%S") else: timestamp = "" - output_dir = os.path.join(output_dir, "testapps" + timestamp) + + if FLAGS.short_output_paths: + output_dir = os.path.join(output_dir, "ta") + else: + output_dir = os.path.join(output_dir, "testapps" + timestamp) ios_framework_dir = os.path.join(sdk_dir, "xcframeworks") ios_framework_exist = os.path.isdir(ios_framework_dir) @@ -215,30 +223,47 @@ def main(argv): failures = [] for testapp in testapps: - logging.info("BEGIN building for %s", testapp) - failures += _build( - testapp=testapp, - platforms=platforms, - api_config=config.get_api(testapp), - output_dir=output_dir, - sdk_dir=sdk_dir, - ios_framework_exist=ios_framework_exist, - repo_dir=repo_dir, - ios_sdk=FLAGS.ios_sdk, - cmake_flags=cmake_flags) - logging.info("END building for %s", testapp) + api_config = config.get_api(testapp) + if FLAGS.repo_dir and api_config.internal_testapp_path: + testapp_dirs = [api_config.internal_testapp_path] + else: + testapp_dirs = [api_config.testapp_path] + for testapp_dir in testapp_dirs: + logging.info("BEGIN building for %s: %s", testapp, testapp_dir) + failures += _build( + testapp=testapp, + platforms=platforms, + api_config=config.get_api(testapp), + testapp_dir=testapp_dir, + output_dir=output_dir, + sdk_dir=sdk_dir, + ios_framework_exist=ios_framework_exist, + repo_dir=repo_dir, + ios_sdk=FLAGS.ios_sdk, + cmake_flags=cmake_flags, + short_output_paths=FLAGS.short_output_paths) + logging.info("END building for %s", testapp) _summarize_results(testapps, platforms, failures, output_dir) return 1 if failures else 0 def _build( - testapp, platforms, api_config, output_dir, sdk_dir, ios_framework_exist, - repo_dir, ios_sdk, cmake_flags): + testapp, platforms, api_config, testapp_dir, output_dir, sdk_dir, ios_framework_exist, + repo_dir, ios_sdk, cmake_flags, short_output_paths): """Builds one testapp on each of the specified platforms.""" - testapp_dir = os.path.join(repo_dir, api_config.testapp_path) - project_dir = os.path.join( - output_dir, api_config.full_name, os.path.basename(testapp_dir)) + os.chdir(repo_dir) + project_dir = os.path.join(output_dir, api_config.name) + if short_output_paths: + # Combining the first letter of every part separated by underscore for + # testapp paths. This is a trick to reduce file path length as we were + # exceeding the limit on Windows. + testapp_dir_parts = os.path.basename(testapp_dir).split('_') + output_testapp_dir = ''.join([x[0] for x in testapp_dir_parts]) + else: + output_testapp_dir = os.path.basename(testapp_dir) + + project_dir = os.path.join(project_dir, output_testapp_dir) logging.info("Copying testapp project to %s", project_dir) os.makedirs(project_dir) @@ -465,6 +490,9 @@ def _build_ios( "--XCodeCPP.target", api_config.ios_target, "--XCodeCPP.frameworks", ",".join(framework_paths) ] + # Internal integration tests require the SDK root as an include path. + if repo_dir and api_config.internal_testapp_path: + xcode_patcher_args.extend(("--XCodeCPP.include", repo_dir)) if os.path.isfile(entitlements_path): # Not all testapps require entitlements logging.info("Entitlements file detected.") xcode_patcher_args.extend(("--XCodeCPP.entitlement", entitlements_path)) diff --git a/scripts/gha/desktop_tester.py b/scripts/gha/desktop_tester.py index 53d0e959b3..3a0647e15f 100644 --- a/scripts/gha/desktop_tester.py +++ b/scripts/gha/desktop_tester.py @@ -103,7 +103,7 @@ def run(self): stderr=subprocess.STDOUT, text=True, check=False, - timeout=300) + timeout=900) except subprocess.TimeoutExpired as e: logging.error("Testapp timed out!") # e.output will sometimes be bytes, sometimes string. Decode if needed. diff --git a/scripts/gha/integration_testing/build_testapps.json b/scripts/gha/integration_testing/build_testapps.json index 9f63d913db..13ea722bab 100755 --- a/scripts/gha/integration_testing/build_testapps.json +++ b/scripts/gha/integration_testing/build_testapps.json @@ -153,6 +153,7 @@ "bundle_id": "com.google.firebase.cpp.firestore.testapp", "ios_target": "integration_test", "testapp_path": "firestore/integration_test", + "internal_testapp_path": "firestore/integration_test_internal", "frameworks": [ "firebase_firestore.xcframework", "firebase_auth.xcframework", diff --git a/scripts/gha/integration_testing/config_reader.py b/scripts/gha/integration_testing/config_reader.py index 78826b4ec5..36666704e2 100644 --- a/scripts/gha/integration_testing/config_reader.py +++ b/scripts/gha/integration_testing/config_reader.py @@ -91,6 +91,7 @@ def read_config(path=None): ios_target=api["ios_target"], scheme=api["ios_target"], # Scheme assumed to be same as target. testapp_path=api["testapp_path"], + internal_testapp_path=api.get("internal_testapp_path", None), frameworks=api["frameworks"], provision=api["provision"], minify=api.get("minify", None)) @@ -127,6 +128,7 @@ class APIConfig(object): ios_target = attr.ib() scheme = attr.ib() testapp_path = attr.ib() # Integration test dir relative to sdk root + internal_testapp_path = attr.ib() # Internal integration test dir relative to sdk root frameworks = attr.ib() # Required custom xcode frameworks provision = attr.ib() # Path to the local mobile provision minify = attr.ib() # (Optional) Android minification. diff --git a/scripts/gha/integration_testing/xcode_tool.rb b/scripts/gha/integration_testing/xcode_tool.rb index 2c8fb89d81..9165ff0762 100644 --- a/scripts/gha/integration_testing/xcode_tool.rb +++ b/scripts/gha/integration_testing/xcode_tool.rb @@ -29,6 +29,8 @@ # --XCodeCPP.frameworks # -e [entitlement_path], Path to entitlements (optional) # --XCodeCPP.entitlement +# -i [include_path], Path to additional include files (optional) +# --XCodeCPP.include require 'optparse' require 'xcodeproj' @@ -54,6 +56,10 @@ def main 'Path to entitlements (optional)') do |entitlement_path| @entitlement_path = entitlement_path end + opts.on('-i', '--XCodeCPP.include [include_path]', + 'Path to additional include files (optional)') do |include_path| + @include_path = include_path + end end.parse! raise OptionParser::MissingArgument,'-d' if @xcode_project_dir.nil? @@ -79,6 +85,9 @@ def main framework_dir = "#@xcode_project_dir/Frameworks" set_build_setting('FRAMEWORK_SEARCH_PATHS', ['${inherited}', framework_dir]) + if !@include_path.nil? + append_to_build_setting('HEADER_SEARCH_PATHS', @include_path) + end @frameworks.each do |framework| add_custom_framework(framework) @@ -169,6 +178,12 @@ def set_build_setting(key, value) end end +def append_to_build_setting(key, value) + @target.build_configurations.each do |config| + config.build_settings[key].append(value) + end +end + if __FILE__ == $0 main() end diff --git a/scripts/gha/restore_secrets.py b/scripts/gha/restore_secrets.py index ca9c879f42..a31e1ae6b0 100644 --- a/scripts/gha/restore_secrets.py +++ b/scripts/gha/restore_secrets.py @@ -77,16 +77,21 @@ def main(argv): # //auth/integration_test/google-services.json api = os.path.basename(os.path.dirname(path)) file_name = os.path.basename(path).replace(".gpg", "") - dest_path = os.path.join(repo_dir, api, "integration_test", file_name) + dest_paths = [os.path.join(repo_dir, api, "integration_test", file_name)] + # Some apis like Firestore also have internal integration tests. + if os.path.exists( os.path.join(repo_dir, api, "integration_test_internal")): + dest_paths.append(os.path.join(repo_dir, api, + "integration_test_internal", file_name)) decrypted_text = _decrypt(path, passphrase) - with open(dest_path, "w") as f: - f.write(decrypted_text) - print("Copied decrypted google service file to %s" % dest_path) - # We use a Google Service file as the source of truth for the reverse id - # that needs to be patched into the Info.plist files. - if dest_path.endswith(".plist"): - _patch_reverse_id(dest_path) - _patch_bundle_id(dest_path) + for dest_path in dest_paths: + with open(dest_path, "w") as f: + f.write(decrypted_text) + print("Copied decrypted google service file to %s" % dest_path) + # We use a Google Service file as the source of truth for the reverse id + # that needs to be patched into the Info.plist files. + if dest_path.endswith(".plist"): + _patch_reverse_id(dest_path) + _patch_bundle_id(dest_path) print("Attempting to patch Dynamic Links uri prefix.") uri_path = os.path.join(secrets_dir, "dynamic_links", "uri_prefix.txt.gpg") diff --git a/scripts/gha/test_lab.py b/scripts/gha/test_lab.py index 7133b89c25..9cb83bf73e 100644 --- a/scripts/gha/test_lab.py +++ b/scripts/gha/test_lab.py @@ -95,7 +95,6 @@ "iOS version for desired device. See module docstring for details on how" " to find available values. If none, will use FTL's default.") - def main(argv): if len(argv) > 1: raise app.UsageError("Too many command-line arguments.") @@ -239,7 +238,7 @@ def _gcloud_command(self): "--app", self.testapp_path, "--results-bucket", gcs.PROJECT_ID, "--results-dir", self.results_dir, - "--timeout", "300s" + "--timeout", "900s" ] def _get_testapp_log_text_from_gcs(self): diff --git a/setup_integration_tests.py b/setup_integration_tests.py index 1c55fba67d..143ec3ae4b 100755 --- a/setup_integration_tests.py +++ b/setup_integration_tests.py @@ -38,6 +38,7 @@ 'database/integration_test', 'dynamic_links/integration_test', 'firestore/integration_test', + 'firestore/integration_test_internal', 'functions/integration_test', 'installations/integration_test', 'instance_id/integration_test', diff --git a/testing/test_framework/src/firebase_test_framework.h b/testing/test_framework/src/firebase_test_framework.h index daf98dd672..13bfeecbae 100644 --- a/testing/test_framework/src/firebase_test_framework.h +++ b/testing/test_framework/src/firebase_test_framework.h @@ -49,6 +49,27 @@ namespace firebase_test_framework { #define TEST_REQUIRES_USER_INTERACTION_ON_ANDROID ((void)0) #endif // TARGET_OS_IPHONE +// Macros for skipping tests on various platforms. +// +// Simply place the macro at the top of the test to skip that test on +// the given platform. +// For example: +// TEST_F(MyFirebaseTest, TestThatFailsOnDesktop) { +// SKIP_TEST_ON_DESKTOP; +// EXPECT_TRUE(do_test_things(...)) +// } +// +// SKIP_TEST_ON_MOBILE +// SKIP_TEST_ON_IOS +// SKIP_TEST_ON_ANDROID +// SKIP_TEST_ON_DESKTOP +// SKIP_TEST_ON_LINUX +// SKIP_TEST_ON_WINDOWS +// SKIP_TEST_ON_MACOS +// +// Also includes a special macro SKIP_TEST_IF_USING_STLPORT if compiling for Android +// STLPort, which does not fully support C++11. + #if !defined(ANDROID) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) #define SKIP_TEST_ON_DESKTOP \ { \ @@ -60,6 +81,39 @@ namespace firebase_test_framework { #define SKIP_TEST_ON_DESKTOP ((void)0) #endif // !defined(ANDROID) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) +#if (defined(TARGET_OS_OSX) && TARGET_OS_OSX) +#define SKIP_TEST_ON_MACOS \ + { \ + app_framework::LogInfo("Skipping %s on MacOS.", test_info_->name()); \ + GTEST_SKIP(); \ + return; \ + } +#else +#define SKIP_TEST_ON_MACOS ((void)0) +#endif // (defined(TARGET_OS_OSX) && TARGET_OS_OSX) + +#if defined(_WIN32) +#define SKIP_TEST_ON_WINDOWS \ + { \ + app_framework::LogInfo("Skipping %s on Windows.", test_info_->name()); \ + GTEST_SKIP(); \ + return; \ + } +#else +#define SKIP_TEST_ON_WINDOWS ((void)0) +#endif // defined(_WIN32) + +#if defined(__linux__) +#define SKIP_TEST_ON_LINUX \ + { \ + app_framework::LogInfo("Skipping %s on Linux.", test_info_->name()); \ + GTEST_SKIP(); \ + return; \ + } +#else +#define SKIP_TEST_ON_LINUX ((void)0) +#endif // defined(__linux__) + #if defined(ANDROID) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) #define SKIP_TEST_ON_MOBILE \ { \ @@ -71,6 +125,28 @@ namespace firebase_test_framework { #define SKIP_TEST_ON_MOBILE ((void)0) #endif // defined(ANDROID) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) +#if (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) +#define SKIP_TEST_ON_IOS \ + { \ + app_framework::LogInfo("Skipping %s on iOS.", test_info_->name()); \ + GTEST_SKIP(); \ + return; \ + } +#else +#define SKIP_TEST_ON_IOS ((void)0) +#endif // (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) + +#if defined(ANDROID) +#define SKIP_TEST_ON_ANDROID \ + { \ + app_framework::LogInfo("Skipping %s on Android.", test_info_->name()); \ + GTEST_SKIP(); \ + return; \ + } +#else +#define SKIP_TEST_ON_ANDROID ((void)0) +#endif // defined(ANDROID) + #if defined(STLPORT) #define SKIP_TEST_IF_USING_STLPORT \ { \