/* * Copyright 2018 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef FIRESTORE_CORE_TEST_UNIT_TESTUTIL_TESTUTIL_H_ #define FIRESTORE_CORE_TEST_UNIT_TESTUTIL_TESTUTIL_H_ #include #include #include #include #include #include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" #include "Firestore/core/src/core/core_fwd.h" #include "Firestore/core/src/core/direction.h" #include "Firestore/core/src/model/document_key.h" #include "Firestore/core/src/model/document_set.h" #include "Firestore/core/src/model/field_index.h" #include "Firestore/core/src/model/model_fwd.h" #include "Firestore/core/src/model/precondition.h" #include "Firestore/core/src/model/value_util.h" #include "Firestore/core/src/nanopb/byte_string.h" #include "Firestore/core/src/nanopb/message.h" #include "Firestore/core/src/nanopb/nanopb_util.h" #include "absl/strings/string_view.h" namespace firebase { class Timestamp; namespace firestore { class GeoPoint; namespace nanopb { class ByteString; } // namespace nanopb namespace testutil { namespace details { nanopb::Message BlobValue( std::initializer_list); } // namespace details // A bit pattern for our canonical NaN value. ABSL_CONST_INIT extern const uint64_t kCanonicalNanBits; // Convenience methods for creating instances for tests. nanopb::ByteString Bytes(std::initializer_list); nanopb::Message Value(std::nullptr_t); /** * A type definition that evaluates to type V only if T is exactly type `bool`. */ template using EnableForExactlyBool = typename std::enable_if::value, V>::type; /** * A type definition that evaluates to type V only if T is an integral type but * not `bool`. */ template using EnableForInts = typename std::enable_if::value && !std::is_same::value, V>::type; /** * Creates a boolean FieldValue. * * @tparam T A type that must be exactly bool. Any T that is not bool causes * this declaration to be disabled. * @param bool_value A boolean value that disallows implicit conversions. */ template EnableForExactlyBool> Value( T bool_value) { nanopb::Message result; result->which_value_type = google_firestore_v1_Value_boolean_value_tag; result->boolean_value = bool_value; return result; } /** * Creates an integer FieldValue. * * This is defined as a template to capture all integer literals. Just defining * this as taking int64_t would make integer literals ambiguous because int64_t * and double are equally good choices according to the standard. * * @tparam T Any integral type (but not bool). Types larger than int64_t will * likely generate a warning. * @param value An integer value. */ template EnableForInts> Value(T value) { nanopb::Message result; result->which_value_type = google_firestore_v1_Value_integer_value_tag; result->integer_value = value; return result; } nanopb::Message Value(double value); nanopb::Message Value(Timestamp value); nanopb::Message Value(const char* value); nanopb::Message Value(const std::string& value); nanopb::Message Value( const nanopb::ByteString& value); nanopb::Message Value(const GeoPoint& value); template nanopb::Message BlobValue(Ints... octets) { return details::BlobValue({static_cast(octets)...}); } nanopb::Message Value( nanopb::Message value); nanopb::Message Value( nanopb::Message value); nanopb::Message Value( nanopb::Message value); nanopb::Message Value( const model::ObjectValue& value); namespace details { /** * Recursive base case for AddPairs, below. Returns the map. */ inline nanopb::Message AddPairs( nanopb::Message prior) { return prior; } /** * Inserts the given key-value pair into the map, and then recursively calls * AddPairs to add any remaining arguments. * * @param prior A map into which the values should be inserted. * @param key The key naming the field to insert. * @param value A value to wrap with a call to Value(), above. * @param rest Any remaining arguments * * @return The resulting map. */ template nanopb::Message AddPairs( nanopb::Message prior, const std::string& key, ValueType value, Args... rest) { nanopb::Message result = std::move(prior); result->which_value_type = google_firestore_v1_Value_map_value_tag; size_t new_count = result->map_value.fields_count + 1; result->map_value.fields_count = nanopb::CheckedSize(new_count); result->map_value.fields = nanopb::ResizeArray( result->map_value.fields, new_count); result->map_value.fields[new_count - 1].key = nanopb::MakeBytesArray(key); result->map_value.fields[new_count - 1].value = *Value(std::move(value)).release(); return AddPairs(std::move(result), std::forward(rest)...); } /** * Creates an immutable sorted map from the given key/value pairs. * * @param key_value_pairs Alternating strings naming keys and values that can * be passed to Value(). */ template nanopb::Message MakeMap(Args... key_value_pairs) { nanopb::Message map_value; map_value->which_value_type = google_firestore_v1_Value_map_value_tag; map_value->map_value = {}; return AddPairs(std::move(map_value), std::forward(key_value_pairs)...); } /** * Recursive base case for AddElements, below. */ inline void AddElements(nanopb::Message&, pb_size_t) { } /** * Inserts the given element into the array, and then recursively calls * AddElement to add any remaining arguments. * * @param array_value An array into which the values should be inserted. * @param pos The index of the next element. * @param value The element to insert. * @param rest Any remaining arguments */ template void AddElements(nanopb::Message& array_value, pb_size_t pos, ValueType value, Args&&... rest) { array_value->values[pos] = *Value(std::move(value)).release(); AddElements(array_value, ++pos, std::forward(rest)...); } /** * Inserts the elements into the given array. */ template nanopb::Message MakeArray(Args&&... values) { nanopb::Message array_value; array_value->values_count = nanopb::CheckedSize(sizeof...(Args)); array_value->values = nanopb::MakeArray(array_value->values_count); AddElements(array_value, 0, std::forward(values)...); return array_value; } } // namespace details template nanopb::Message Array(Args&&... values) { return details::MakeArray(std::move(values)...); } /** Wraps an immutable sorted map into an ObjectValue. */ model::ObjectValue WrapObject(nanopb::Message value); /** * Creates an ObjectValue from the given key/value pairs. * * @param key_value_pairs Alternating strings naming keys and values that can * be passed to Value(). */ template model::ObjectValue WrapObject(Args... key_value_pairs) { return WrapObject(details::MakeMap(std::move(key_value_pairs)...)); } /** * Creates an ObjectValue from the given key/value pairs with Type::Object. * * @param key_value_pairs Alternating strings naming keys and values that can * be passed to Value(). */ template nanopb::Message Map(Args... key_value_pairs) { return details::MakeMap(std::move(key_value_pairs)...); } model::DocumentKey Key(absl::string_view path); model::FieldPath Field(absl::string_view field); model::DatabaseId DbId(std::string project = "project/(default)"); nanopb::Message Ref(std::string project, absl::string_view path); model::ResourcePath Resource(absl::string_view field); /** * Creates a snapshot version from the given version timestamp. * * @param version a timestamp in microseconds since the epoch. */ model::SnapshotVersion Version(int64_t version); model::MutableDocument Doc(absl::string_view key, int64_t version = 0); model::MutableDocument Doc(absl::string_view key, int64_t version, nanopb::Message data); /** A convenience method for creating deleted docs for tests. */ model::MutableDocument DeletedDoc(absl::string_view key, int64_t version = 0); /** A convenience method for creating deleted docs for tests. */ model::MutableDocument DeletedDoc(model::DocumentKey key, int64_t version = 0); /** A convenience method for creating unknown docs for tests. */ model::MutableDocument UnknownDoc(absl::string_view key, int64_t version); /** A convenience method for creating invalid (missing) docs for tests. */ model::MutableDocument InvalidDoc(absl::string_view key); /** * Creates a DocumentComparator that will compare Documents by the given * field_path string then by key. */ model::DocumentComparator DocComparator(absl::string_view field_path); /** * Creates a DocumentSet based on the given comparator, initially containing the * given documents. */ model::DocumentSet DocSet(model::DocumentComparator comp, std::vector docs); core::FieldFilter Filter(absl::string_view key, absl::string_view op, nanopb::Message value); core::FieldFilter Filter(absl::string_view key, absl::string_view op, nanopb::Message value); core::FieldFilter Filter(absl::string_view key, absl::string_view op, std::nullptr_t); core::FieldFilter Filter(absl::string_view key, absl::string_view op, const char* value); template EnableForExactlyBool Filter(absl::string_view key, absl::string_view op, T value) { return Filter(key, op, Value(value)); } core::FieldFilter Filter(absl::string_view key, absl::string_view op, int value); core::FieldFilter Filter(absl::string_view key, absl::string_view op, double value); core::Direction Direction(absl::string_view direction); core::OrderBy OrderBy(absl::string_view key, absl::string_view direction = "asc"); core::OrderBy OrderBy(model::FieldPath field_path, core::Direction direction); core::Query Query(absl::string_view path); core::Query CollectionGroupQuery(absl::string_view collection_id); model::SetMutation SetMutation( absl::string_view path, nanopb::Message values, std::vector> transforms = {}); model::PatchMutation PatchMutation( absl::string_view path, nanopb::Message values, std::vector> transforms = {}); model::PatchMutation MergeMutation( absl::string_view path, nanopb::Message values, const std::vector& update_mask, std::vector> transforms = {}); model::PatchMutation PatchMutationHelper( absl::string_view path, nanopb::Message values, std::vector> transforms, model::Precondition precondition, const absl::optional>& update_mask); /** * Creates a pair of field name, TransformOperation that represents a numeric * increment on the given field, suitable for passing to TransformMutation, * above. */ std::pair Increment( std::string field, nanopb::Message operand); /** * Creates a pair of field name, TransformOperation that represents an array * union on the given field, suitable for passing to TransformMutation, * above. */ std::pair ArrayUnion( std::string field, const std::vector>& operands); model::DeleteMutation DeleteMutation(absl::string_view path); model::VerifyMutation VerifyMutation(absl::string_view path, int64_t version); model::MutationResult MutationResult(int64_t version); nanopb::ByteString ResumeToken(int64_t snapshot_version); template std::vector Vector(T&& arg1, Ts&&... args) { return {std::forward(arg1), std::forward(args)...}; } // Degenerate case to end recursion of `MoveIntoVector`. template void MoveIntoVector(std::vector>*) { } template void MoveIntoVector(std::vector>* result, Head head, Tail... tail) { result->push_back(std::move(head)); MoveIntoVector(result, std::move(tail)...); } // Works around the fact that move-only types (in this case, `unique_ptr`) don't // work with `initializer_list`. Desired (doesn't work): // // std::unique_ptr x, y; // std::vector> foo{std::move(x), std::move(y)}; // // Actual: // // std::unique_ptr x, y; // std::vector> foo = Changes(std::move(x), // std::move(y)); template std::vector> VectorOfUniquePtrs(Elems... elems) { std::vector> result; MoveIntoVector(&result, std::move(elems)...); return result; } model::FieldIndex MakeFieldIndex(const std::string& collection_group); model::FieldIndex MakeFieldIndex(const std::string& collection_group, int32_t index_id, model::IndexState state); model::FieldIndex MakeFieldIndex(const std::string& collection_group, const std::string& field, model::Segment::Kind kind); model::FieldIndex MakeFieldIndex(const std::string& collection_group, const std::string& field_1, model::Segment::Kind kind_1, const std::string& field_2, model::Segment::Kind kind_2); model::FieldIndex MakeFieldIndex(const std::string& collection_group, const std::string& field_1, model::Segment::Kind kind_1, const std::string& field_2, model::Segment::Kind kind_2, const std::string& field_3, model::Segment::Kind kind_3); model::FieldIndex MakeFieldIndex(const std::string& collection_group, int32_t index_id, model::IndexState state, const std::string& field_1, model::Segment::Kind kind_1); } // namespace testutil } // namespace firestore } // namespace firebase #endif // FIRESTORE_CORE_TEST_UNIT_TESTUTIL_TESTUTIL_H_