From de0faa8e69045b8074a560f9c024fa7f19fd5ccd Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Thu, 26 Jul 2018 14:19:58 -0600 Subject: [PATCH] Add Firestore conformance tests (#2207) * Update Query#select to replace not add if called repeatedly. [BREAKING CHANGE] * Update Batch/Transaction#update validations: * Raise ArgumentError for duplicate field paths. * Raise ArgumentError if one field is the prefix of another. * Update Query#where to raise ArgumentError for operators unknown to Firestore. * Remove previous handwritten versions of these conformance tests. --- google-cloud-firestore/.rubocop.yml | 1 + google-cloud-firestore/Rakefile | 6 + .../conformance/test-definition.proto | 196 ++++ .../conformance/test-definition_pb.rb | 158 +++ .../conformance/test-suite.binproto | Bin 0 -> 38337 bytes .../lib/google/cloud/firestore/convert.rb | 70 +- .../lib/google/cloud/firestore/query.rb | 66 +- .../collection_reference/query_test.rb | 4 +- .../cloud/firestore/conformance_test.rb | 334 ++++++ .../firestore/convert/writes_for_set_test.rb | 4 +- .../convert/writes_for_update_test.rb | 6 +- .../cloud/firestore/generated/create_test.rb | 310 ------ .../cloud/firestore/generated/delete_test.rb | 72 -- .../cloud/firestore/generated/get_test.rb | 38 - .../cloud/firestore/generated/set_test.rb | 425 -------- .../cloud/firestore/generated/update_test.rb | 979 ------------------ .../cloud/firestore/query/cursors_test.rb | 76 +- .../google/cloud/firestore/query/get_test.rb | 2 +- 18 files changed, 819 insertions(+), 1928 deletions(-) create mode 100644 google-cloud-firestore/conformance/test-definition.proto create mode 100644 google-cloud-firestore/conformance/test-definition_pb.rb create mode 100644 google-cloud-firestore/conformance/test-suite.binproto create mode 100644 google-cloud-firestore/test/google/cloud/firestore/conformance_test.rb delete mode 100644 google-cloud-firestore/test/google/cloud/firestore/generated/create_test.rb delete mode 100644 google-cloud-firestore/test/google/cloud/firestore/generated/delete_test.rb delete mode 100644 google-cloud-firestore/test/google/cloud/firestore/generated/get_test.rb delete mode 100644 google-cloud-firestore/test/google/cloud/firestore/generated/set_test.rb delete mode 100644 google-cloud-firestore/test/google/cloud/firestore/generated/update_test.rb diff --git a/google-cloud-firestore/.rubocop.yml b/google-cloud-firestore/.rubocop.yml index ca5d7cb89725..bbd941882431 100644 --- a/google-cloud-firestore/.rubocop.yml +++ b/google-cloud-firestore/.rubocop.yml @@ -7,6 +7,7 @@ AllCops: - "support/**/*" - "Rakefile" - "acceptance/**/*" + - "conformance/*" - "test/**/*" Documentation: diff --git a/google-cloud-firestore/Rakefile b/google-cloud-firestore/Rakefile index b695f692e65a..91f798852ec8 100644 --- a/google-cloud-firestore/Rakefile +++ b/google-cloud-firestore/Rakefile @@ -37,6 +37,12 @@ namespace :test do Rake::Task["test"].invoke end + + desc "Run conformance tests separately from other unit tests." + task :conformance do + $LOAD_PATH.unshift "lib", "test" + require_relative "test/google/cloud/firestore/conformance_test.rb" + end end # Acceptance tests diff --git a/google-cloud-firestore/conformance/test-definition.proto b/google-cloud-firestore/conformance/test-definition.proto new file mode 100644 index 000000000000..dad76d1fd220 --- /dev/null +++ b/google-cloud-firestore/conformance/test-definition.proto @@ -0,0 +1,196 @@ +// Tests for firestore clients. + +syntax = "proto3"; + +package tests; + +option php_namespace = "Google\\Cloud\\Firestore\\Tests\\Conformance"; +option csharp_namespace = "Google.Cloud.Firestore.Tests.Proto"; +option java_package = "com.google.cloud.firestore.conformance"; + +import "google/firestore/v1beta1/common.proto"; +import "google/firestore/v1beta1/document.proto"; +import "google/firestore/v1beta1/firestore.proto"; +import "google/firestore/v1beta1/query.proto"; +import "google/protobuf/timestamp.proto"; + +// A collection of tests. +message TestSuite { + repeated Test tests = 1; +} + +// A Test describes a single client method call and its expected result. +message Test { + string description = 1; // short description of the test + + oneof test { + GetTest get = 2; + CreateTest create = 3; + SetTest set = 4; + UpdateTest update = 5; + UpdatePathsTest update_paths = 6; + DeleteTest delete = 7; + QueryTest query = 8; + ListenTest listen = 9; + } +} + +// Call to the DocumentRef.Get method. +message GetTest { + // The path of the doc, e.g. "projects/projectID/databases/(default)/documents/C/d" + string doc_ref_path = 1; + + // The request that the call should send to the Firestore service. + google.firestore.v1beta1.GetDocumentRequest request = 2; +} + +// Call to DocumentRef.Create. +message CreateTest { + // The path of the doc, e.g. "projects/projectID/databases/(default)/documents/C/d" + string doc_ref_path = 1; + + // The data passed to Create, as JSON. The strings "Delete" and "ServerTimestamp" + // denote the two special sentinel values. Values that could be interpreted as integers + // (i.e. digit strings) should be treated as integers. + string json_data = 2; + + // The request that the call should generate. + google.firestore.v1beta1.CommitRequest request = 3; + + // If true, the call should result in an error without generating a request. + // If this is true, request should not be set. + bool is_error = 4; +} + +// A call to DocumentRef.Set. +message SetTest { + string doc_ref_path = 1; // path of doc + SetOption option = 2; // option to the Set call, if any + string json_data = 3; // data (see CreateTest.json_data) + google.firestore.v1beta1.CommitRequest request = 4; // expected request + bool is_error = 5; // call signals an error +} + +// A call to the form of DocumentRef.Update that represents the data as a map +// or dictionary. +message UpdateTest { + string doc_ref_path = 1; // path of doc + google.firestore.v1beta1.Precondition precondition = 2; // precondition in call, if any + string json_data = 3; // data (see CreateTest.json_data) + google.firestore.v1beta1.CommitRequest request = 4; // expected request + bool is_error = 5; // call signals an error +} + +// A call to the form of DocumentRef.Update that represents the data as a list +// of field paths and their values. +message UpdatePathsTest { + string doc_ref_path = 1; // path of doc + google.firestore.v1beta1.Precondition precondition = 2; // precondition in call, if any + // parallel sequences: field_paths[i] corresponds to json_values[i] + repeated FieldPath field_paths = 3; // the argument field paths + repeated string json_values = 4; // the argument values, as JSON + google.firestore.v1beta1.CommitRequest request = 5; // expected rquest + bool is_error = 6; // call signals an error +} + +// A call to DocmentRef.Delete +message DeleteTest { + string doc_ref_path = 1; // path of doc + google.firestore.v1beta1.Precondition precondition = 2; + google.firestore.v1beta1.CommitRequest request = 3; // expected rquest + bool is_error = 4; // call signals an error +} + +// An option to the DocumentRef.Set call. +message SetOption { + bool all = 1; // if true, merge all fields ("fields" is ignored). + repeated FieldPath fields = 2; // field paths for a Merge option +} + +message QueryTest { + string coll_path = 1; // path of collection, e.g. "projects/projectID/databases/(default)/documents/C" + repeated Clause clauses = 2; + google.firestore.v1beta1.StructuredQuery query = 3; + bool is_error = 4; +} + +message Clause { + oneof clause { + Select select = 1; + Where where = 2; + OrderBy order_by = 3; + int32 offset = 4; + int32 limit = 5; + Cursor start_at = 6; + Cursor start_after = 7; + Cursor end_at = 8; + Cursor end_before = 9; + } +} + +message Select { + repeated FieldPath fields = 1; +} + +message Where { + FieldPath path = 1; + string op = 2; + string json_value = 3; +} + +message OrderBy { + FieldPath path = 1; + string direction = 2; // "asc" or "desc" +} + +message Cursor { + // one of: + DocSnapshot doc_snapshot = 1; + repeated string json_values = 2; +} + +message DocSnapshot { + string path = 1; + string json_data = 2; +} + +message FieldPath { + repeated string field = 1; +} + +// A test of the Listen streaming RPC (a.k.a. FireStore watch). +// If the sequence of responses is provided to the implementation, +// it should produce the sequence of snapshots. +// If is_error is true, an error should occur after the snapshots. +// +// The tests assume that the query is +// Collection("projects/projectID/databases/(default)/documents/C").OrderBy("a", Ascending) +// +// The watch target ID used in these tests is 1. Test interpreters +// should either change their client's ID for testing, +// or change the ID in the tests before running them. +message ListenTest { + repeated google.firestore.v1beta1.ListenResponse responses = 1; + repeated Snapshot snapshots = 2; + bool is_error = 3; +} + +message Snapshot { + repeated google.firestore.v1beta1.Document docs = 1; + repeated DocChange changes = 2; + google.protobuf.Timestamp read_time = 3; +} + +message DocChange { + enum Kind { + KIND_UNSPECIFIED = 0; + ADDED = 1; + REMOVED = 2; + MODIFIED = 3; + } + + Kind kind = 1; + google.firestore.v1beta1.Document doc = 2; + int32 old_index = 3; + int32 new_index = 4; +} diff --git a/google-cloud-firestore/conformance/test-definition_pb.rb b/google-cloud-firestore/conformance/test-definition_pb.rb new file mode 100644 index 000000000000..5819efe33cf9 --- /dev/null +++ b/google-cloud-firestore/conformance/test-definition_pb.rb @@ -0,0 +1,158 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: test-definition.proto + +require 'google/protobuf' + +require 'google/firestore/v1beta1/common_pb' +require 'google/firestore/v1beta1/document_pb' +require 'google/firestore/v1beta1/firestore_pb' +require 'google/firestore/v1beta1/query_pb' +require 'google/protobuf/timestamp_pb' +Google::Protobuf::DescriptorPool.generated_pool.build do + add_message "tests.TestSuite" do + repeated :tests, :message, 1, "tests.Test" + end + add_message "tests.Test" do + optional :description, :string, 1 + oneof :test do + optional :get, :message, 2, "tests.GetTest" + optional :create, :message, 3, "tests.CreateTest" + optional :set, :message, 4, "tests.SetTest" + optional :update, :message, 5, "tests.UpdateTest" + optional :update_paths, :message, 6, "tests.UpdatePathsTest" + optional :delete, :message, 7, "tests.DeleteTest" + optional :query, :message, 8, "tests.QueryTest" + optional :listen, :message, 9, "tests.ListenTest" + end + end + add_message "tests.GetTest" do + optional :doc_ref_path, :string, 1 + optional :request, :message, 2, "google.firestore.v1beta1.GetDocumentRequest" + end + add_message "tests.CreateTest" do + optional :doc_ref_path, :string, 1 + optional :json_data, :string, 2 + optional :request, :message, 3, "google.firestore.v1beta1.CommitRequest" + optional :is_error, :bool, 4 + end + add_message "tests.SetTest" do + optional :doc_ref_path, :string, 1 + optional :option, :message, 2, "tests.SetOption" + optional :json_data, :string, 3 + optional :request, :message, 4, "google.firestore.v1beta1.CommitRequest" + optional :is_error, :bool, 5 + end + add_message "tests.UpdateTest" do + optional :doc_ref_path, :string, 1 + optional :precondition, :message, 2, "google.firestore.v1beta1.Precondition" + optional :json_data, :string, 3 + optional :request, :message, 4, "google.firestore.v1beta1.CommitRequest" + optional :is_error, :bool, 5 + end + add_message "tests.UpdatePathsTest" do + optional :doc_ref_path, :string, 1 + optional :precondition, :message, 2, "google.firestore.v1beta1.Precondition" + repeated :field_paths, :message, 3, "tests.FieldPath" + repeated :json_values, :string, 4 + optional :request, :message, 5, "google.firestore.v1beta1.CommitRequest" + optional :is_error, :bool, 6 + end + add_message "tests.DeleteTest" do + optional :doc_ref_path, :string, 1 + optional :precondition, :message, 2, "google.firestore.v1beta1.Precondition" + optional :request, :message, 3, "google.firestore.v1beta1.CommitRequest" + optional :is_error, :bool, 4 + end + add_message "tests.SetOption" do + optional :all, :bool, 1 + repeated :fields, :message, 2, "tests.FieldPath" + end + add_message "tests.QueryTest" do + optional :coll_path, :string, 1 + repeated :clauses, :message, 2, "tests.Clause" + optional :query, :message, 3, "google.firestore.v1beta1.StructuredQuery" + optional :is_error, :bool, 4 + end + add_message "tests.Clause" do + oneof :clause do + optional :select, :message, 1, "tests.Select" + optional :where, :message, 2, "tests.Where" + optional :order_by, :message, 3, "tests.OrderBy" + optional :offset, :int32, 4 + optional :limit, :int32, 5 + optional :start_at, :message, 6, "tests.Cursor" + optional :start_after, :message, 7, "tests.Cursor" + optional :end_at, :message, 8, "tests.Cursor" + optional :end_before, :message, 9, "tests.Cursor" + end + end + add_message "tests.Select" do + repeated :fields, :message, 1, "tests.FieldPath" + end + add_message "tests.Where" do + optional :path, :message, 1, "tests.FieldPath" + optional :op, :string, 2 + optional :json_value, :string, 3 + end + add_message "tests.OrderBy" do + optional :path, :message, 1, "tests.FieldPath" + optional :direction, :string, 2 + end + add_message "tests.Cursor" do + optional :doc_snapshot, :message, 1, "tests.DocSnapshot" + repeated :json_values, :string, 2 + end + add_message "tests.DocSnapshot" do + optional :path, :string, 1 + optional :json_data, :string, 2 + end + add_message "tests.FieldPath" do + repeated :field, :string, 1 + end + add_message "tests.ListenTest" do + repeated :responses, :message, 1, "google.firestore.v1beta1.ListenResponse" + repeated :snapshots, :message, 2, "tests.Snapshot" + optional :is_error, :bool, 3 + end + add_message "tests.Snapshot" do + repeated :docs, :message, 1, "google.firestore.v1beta1.Document" + repeated :changes, :message, 2, "tests.DocChange" + optional :read_time, :message, 3, "google.protobuf.Timestamp" + end + add_message "tests.DocChange" do + optional :kind, :enum, 1, "tests.DocChange.Kind" + optional :doc, :message, 2, "google.firestore.v1beta1.Document" + optional :old_index, :int32, 3 + optional :new_index, :int32, 4 + end + add_enum "tests.DocChange.Kind" do + value :KIND_UNSPECIFIED, 0 + value :ADDED, 1 + value :REMOVED, 2 + value :MODIFIED, 3 + end +end + +module Tests + TestSuite = Google::Protobuf::DescriptorPool.generated_pool.lookup("tests.TestSuite").msgclass + Test = Google::Protobuf::DescriptorPool.generated_pool.lookup("tests.Test").msgclass + GetTest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tests.GetTest").msgclass + CreateTest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tests.CreateTest").msgclass + SetTest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tests.SetTest").msgclass + UpdateTest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tests.UpdateTest").msgclass + UpdatePathsTest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tests.UpdatePathsTest").msgclass + DeleteTest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tests.DeleteTest").msgclass + SetOption = Google::Protobuf::DescriptorPool.generated_pool.lookup("tests.SetOption").msgclass + QueryTest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tests.QueryTest").msgclass + Clause = Google::Protobuf::DescriptorPool.generated_pool.lookup("tests.Clause").msgclass + Select = Google::Protobuf::DescriptorPool.generated_pool.lookup("tests.Select").msgclass + Where = Google::Protobuf::DescriptorPool.generated_pool.lookup("tests.Where").msgclass + OrderBy = Google::Protobuf::DescriptorPool.generated_pool.lookup("tests.OrderBy").msgclass + Cursor = Google::Protobuf::DescriptorPool.generated_pool.lookup("tests.Cursor").msgclass + DocSnapshot = Google::Protobuf::DescriptorPool.generated_pool.lookup("tests.DocSnapshot").msgclass + FieldPath = Google::Protobuf::DescriptorPool.generated_pool.lookup("tests.FieldPath").msgclass + ListenTest = Google::Protobuf::DescriptorPool.generated_pool.lookup("tests.ListenTest").msgclass + Snapshot = Google::Protobuf::DescriptorPool.generated_pool.lookup("tests.Snapshot").msgclass + DocChange = Google::Protobuf::DescriptorPool.generated_pool.lookup("tests.DocChange").msgclass + DocChange::Kind = Google::Protobuf::DescriptorPool.generated_pool.lookup("tests.DocChange.Kind").enummodule +end diff --git a/google-cloud-firestore/conformance/test-suite.binproto b/google-cloud-firestore/conformance/test-suite.binproto new file mode 100644 index 0000000000000000000000000000000000000000..38b7e8804682a8775ce94483a6c93d786a27c468 GIT binary patch literal 38337 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 literal 0 HcmV?d00001 diff --git a/google-cloud-firestore/lib/google/cloud/firestore/convert.rb b/google-cloud-firestore/lib/google/cloud/firestore/convert.rb index 8ef7f6f7d8e5..0802fef20cb0 100644 --- a/google-cloud-firestore/lib/google/cloud/firestore/convert.rb +++ b/google-cloud-firestore/lib/google/cloud/firestore/convert.rb @@ -164,13 +164,15 @@ def writes_for_set doc_path, data, merge: nil if merge == true # extract the leaf node field paths from data field_paths = identify_leaf_nodes data + allow_empty = true else field_paths = Array(merge).map do |field_path| field_path = FieldPath.parse field_path unless field_path.is_a? FieldPath field_path end + allow_empty = false end - return writes_for_set_merge doc_path, data, field_paths + return writes_for_set_merge doc_path, data, field_paths, allow_empty end writes = [] @@ -195,9 +197,11 @@ def writes_for_set doc_path, data, merge: nil writes end - def writes_for_set_merge doc_path, data, field_paths + def writes_for_set_merge doc_path, data, field_paths, allow_empty raise ArgumentError, "data is required" unless data.is_a? Hash + validate_field_paths! field_paths + writes = [] # Ensure provided field paths are valid. @@ -231,22 +235,32 @@ def writes_for_set_merge doc_path, data, field_paths delete_valid_check = delete_valid_check.include? false raise ArgumentError, "deleted field not included in merge" if delete_valid_check + server_time_paths.select! do |server_time_path| + field_paths.any? do |field_path| + server_time_path.formatted_string.start_with? field_path.formatted_string + end + end + # Choose only the data there are field paths for field_paths -= delete_paths field_paths -= server_time_paths data = select_by_field_paths data, field_paths + # Restore delete paths + field_paths += delete_paths - if data.empty? - if server_time_paths.empty? + if data.empty? && !allow_empty + if server_time_paths.empty? && delete_paths.empty? raise ArgumentError, "data required for set with merge" end - else + end + + if data.any? || field_paths.any? || (allow_empty && server_time_paths.empty?) writes << Google::Firestore::V1beta1::Write.new( update: Google::Firestore::V1beta1::Document.new( name: doc_path, fields: hash_to_fields(data)), update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: field_paths.map(&:formatted_string)) + field_paths: field_paths.map(&:formatted_string).sort) ) end @@ -269,18 +283,7 @@ def writes_for_update doc_path, data, update_time: nil end # Duplicate field paths check - dup_keys = new_data_pairs.map(&:first).map(&:formatted_string) - if dup_keys.size != dup_keys.uniq.size - raise ArgumentError, "duplicate field paths" - end - dup_keys.each do |field_path| - prefix_check = dup_keys.select do |this_path| - this_path.start_with? "#{field_path}." - end - if prefix_check.any? - raise ArgumentError, "one field cannot be a prefix of another" - end - end + validate_field_paths! new_data_pairs.map(&:first) delete_paths, new_data_pairs = new_data_pairs.partition do |field_path, value| value.is_a?(FieldValue) && value.type == :delete @@ -301,10 +304,9 @@ def writes_for_update doc_path, data, update_time: nil data, nested_server_time_paths = remove_field_value_from data, :server_time - server_time_paths = root_server_time_paths + nested_server_time_paths server_time_paths = root_server_time_paths + nested_server_time_paths - field_paths = (field_paths - (field_paths - identify_all_file_paths(data)) + delete_paths).uniq + field_paths = (field_paths + delete_paths).uniq field_paths.each do |field_path| raise ArgumentError, "empty paths not allowed" if field_path.fields.empty? end @@ -319,7 +321,7 @@ def writes_for_update doc_path, data, update_time: nil name: doc_path, fields: hash_to_fields(data)), update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: field_paths.map(&:formatted_string)), + field_paths: (field_paths).map(&:formatted_string).sort), current_document: Google::Firestore::V1beta1::Precondition.new( exists: true) ) @@ -464,13 +466,15 @@ def select_field_path hash, field_path tmp_hash = ret_hash prev_hash = ret_hash dup_hash = hash.dup - fields = field_path.fields + fields = field_path.fields.dup last_field = nil # squash fields until the key exists? - until dup_hash.key? fields.first - fields.unshift "#{fields.shift}.#{fields.shift}" - break if fields.count <= 1 + if fields.count > 1 + until dup_hash.key? fields.first + fields.unshift "#{fields.shift}.#{fields.shift}" + break if fields.count <= 1 + end end fields.each do |field| @@ -482,9 +486,25 @@ def select_field_path hash, field_path dup_hash = dup_hash[field] end prev_hash[last_field] = dup_hash + prev_hash.delete_if { |_k, v| v.nil? } ret_hash end + def validate_field_paths! field_paths + field_paths_strings = field_paths.map(&:formatted_string) + if field_paths_strings.size != field_paths_strings.uniq.size + raise ArgumentError, "duplicate field paths" + end + field_paths_strings.each do |field_path| + prefix_check = field_paths_strings.select do |this_path| + this_path.start_with? "#{field_path}." + end + if prefix_check.any? + raise ArgumentError, "one field cannot be a prefix of another" + end + end + end + def deep_merge_hashes left_hash, right_hash right_hash.each_pair do |key, right_value| left_value = left_hash[key] diff --git a/google-cloud-firestore/lib/google/cloud/firestore/query.rb b/google-cloud-firestore/lib/google/cloud/firestore/query.rb index 8c3a7ccc7195..40a9c4e6f493 100644 --- a/google-cloud-firestore/lib/google/cloud/firestore/query.rb +++ b/google-cloud-firestore/lib/google/cloud/firestore/query.rb @@ -88,13 +88,15 @@ def select *fields new_query = @query.dup new_query ||= StructuredQuery.new + fields = Array(fields).flatten.compact + fields = [FieldPath.document_id] if fields.empty? field_refs = fields.flatten.compact.map do |field| field = FieldPath.parse field unless field.is_a? FieldPath StructuredQuery::FieldReference.new \ field_path: field.formatted_string end - new_query.select ||= StructuredQuery::Projection.new + new_query.select = StructuredQuery::Projection.new field_refs.each do |field_ref| new_query.select.fields << field_ref end @@ -130,7 +132,7 @@ def all_descendants new_query ||= StructuredQuery.new if new_query.from.empty? - raise "missing collection_id to specify descendants." + raise "missing collection_id to specify descendants" end new_query.from.last.all_descendants = true @@ -166,7 +168,7 @@ def direct_descendants new_query ||= StructuredQuery.new if new_query.from.empty? - raise "missing collection_id to specify descendants." + raise "missing collection_id to specify descendants" end new_query.from.last.all_descendants = false @@ -223,9 +225,8 @@ def where field, operator, value field = FieldPath.parse field unless field.is_a? FieldPath - new_query.where ||= default_filter - new_query.where.composite_filter.filters << \ - filter(field.formatted_string, operator, value) + new_filter = filter field.formatted_string, operator, value + add_filters_to_query new_query, new_filter Query.start new_query, parent_path, client end @@ -862,33 +863,49 @@ def self.start query, parent_path, client def filter name, op, value field = StructuredQuery::FieldReference.new field_path: name.to_s - op = FILTER_OPS[op.to_s.downcase] || :EQUAL + operator = FILTER_OPS[op.to_s.downcase] + raise ArgumentError, "unknown operator #{op}" if operator.nil? is_value_nan = value.respond_to?(:nan?) && value.nan? if UNARY_VALUES.include?(value) || is_value_nan - if op != :EQUAL + if operator != :EQUAL raise ArgumentError, - "can only check equality for #{value} values." + "can only check equality for #{value} values" end - op = :IS_NULL - op = :IS_NAN if UNARY_NAN_VALUES.include?(value) || is_value_nan + operator = :IS_NULL + if UNARY_NAN_VALUES.include?(value) || is_value_nan + operator = :IS_NAN + end return StructuredQuery::Filter.new(unary_filter: - StructuredQuery::UnaryFilter.new(field: field, op: op)) + StructuredQuery::UnaryFilter.new(field: field, op: operator)) end value = Convert.raw_to_value value StructuredQuery::Filter.new(field_filter: - StructuredQuery::FieldFilter.new(field: field, op: op, + StructuredQuery::FieldFilter.new(field: field, op: operator, value: value)) end - def default_filter + def composite_filter StructuredQuery::Filter.new(composite_filter: StructuredQuery::CompositeFilter.new(op: :AND)) end + def add_filters_to_query query, filter + if query.where.nil? + query.where = filter + elsif query.where.filter_type == :composite_filter + query.where.composite_filter.filters << filter + else + old_filter = query.where + query.where = composite_filter + query.where.composite_filter.filters << old_filter + query.where.composite_filter.filters << filter + end + end + def order_direction direction return :DESCENDING if direction.to_s.downcase.start_with? "d".freeze :ASCENDING @@ -921,6 +938,10 @@ def values_to_cursor values, query end def snapshot_to_cursor snapshot, query + if snapshot.parent.path != query_collection_path + raise ArgumentError, "cursor snapshot must belong to collection" + end + # first, add any inequality filters missing from existing order_by ensure_inequality_field_paths_in_order_by! query @@ -970,7 +991,12 @@ def inequality_filter_field_paths query return [] if query.where.nil? # The way we construct a query, where is always a CompositeFilter - ineq_filters = query.where.composite_filter.filters.select do |filter| + filters = if query.where.filter_type == :composite_filter + query.where.composite_filter.filters + else + [query.where] + end + ineq_filters = filters.select do |filter| if filter.filter_type == :field_filter filter.field_filter.op != :EQUAL end @@ -990,15 +1016,19 @@ def last_order_direction query def document_reference document_path if document_path.to_s.split("/").count.even? - raise ArgumentError, "document_path must refer to a document." + raise ArgumentError, "document_path must refer to a document" end DocumentReference.from_path( - "#{parent_path}/#{collection_id}/#{document_path}", client + "#{query_collection_path}/#{document_path}", client ) end - def collection_id + def query_collection_path + "#{parent_path}/#{query_collection_id}" + end + + def query_collection_id # We trust that query.from is always set, since Query cannot be # created without it. return nil if query.from.empty? diff --git a/google-cloud-firestore/test/google/cloud/firestore/collection_reference/query_test.rb b/google-cloud-firestore/test/google/cloud/firestore/collection_reference/query_test.rb index d3d582a8260f..44c2ec8abba9 100644 --- a/google-cloud-firestore/test/google/cloud/firestore/collection_reference/query_test.rb +++ b/google-cloud-firestore/test/google/cloud/firestore/collection_reference/query_test.rb @@ -74,12 +74,10 @@ assert_results_enum results_enum end - it "runs a query with multiple select calls" do + it "runs a query with multiple select calls only uses the last one" do expected_query = Google::Firestore::V1beta1::StructuredQuery.new( select: Google::Firestore::V1beta1::StructuredQuery::Projection.new( fields: [ - Google::Firestore::V1beta1::StructuredQuery::FieldReference.new(field_path: "name"), - Google::Firestore::V1beta1::StructuredQuery::FieldReference.new(field_path: "status"), Google::Firestore::V1beta1::StructuredQuery::FieldReference.new(field_path: "activity") ] ), diff --git a/google-cloud-firestore/test/google/cloud/firestore/conformance_test.rb b/google-cloud-firestore/test/google/cloud/firestore/conformance_test.rb new file mode 100644 index 000000000000..ec213993100e --- /dev/null +++ b/google-cloud-firestore/test/google/cloud/firestore/conformance_test.rb @@ -0,0 +1,334 @@ +# Copyright 2018 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 +# +# https://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. + +require "helper.rb" +require_relative "../../../../conformance/test-definition_pb" + +## +# This suite of unit tests is dynamically generated from the contents of +# `conformance/test-suite.binproto`, using the protobuf types defined in +# `conformance/test-definition_pb.rb`, which was manually generated from +# `conformance/test-definition.proto`. See [Protocol Buffers - Ruby Generated +# Code](https://developers.google.com/protocol-buffers/docs/reference/ruby-generated) +# for instructions in case `test-definition.proto` is updated. +# +# This code was adapted from google-cloud-dotnet +# [ProtoTest.cs](https://github.com/GoogleCloudPlatform/google-cloud-dotnet/blob/master/apis/Google.Cloud.Firestore/Google.Cloud.Firestore.Tests/Proto/ProtoTest.cs). +# +class ConformanceTest < MockFirestore + def doc_ref_from_path doc_path + Google::Cloud::Firestore::DocumentReference.from_path doc_path, firestore + end + + def doc_snap_from_path_and_json_data doc_path, json_data + Google::Cloud::Firestore::DocumentSnapshot.new.tap do |s| + s.grpc = Google::Firestore::V1beta1::Document.new( + name: doc_path, + fields: Google::Cloud::Firestore::Convert.hash_to_fields(data_from_json(json_data)) + ) + s.instance_variable_set :@ref, doc_ref_from_path(doc_path) + end + end + + def data_from_json data_json + convert_values JSON.parse data_json + end + + def convert_values data + if data == "Delete" + firestore.field_delete + elsif data == "ServerTimestamp" + firestore.field_server_time + elsif data == "NaN" + Float::NAN + elsif data.is_a? Hash + Hash[data.map { |k, v| [k, convert_values(v)] }] + elsif data.is_a? Array + data.map { |v| convert_values(v) } + else + data + end + end +end + +class ConformanceCreate < ConformanceTest + let(:commit_time) { Time.now } + let :commit_resp do + Google::Firestore::V1beta1::CommitResponse.new( + commit_time: Google::Cloud::Firestore::Convert.time_to_timestamp(commit_time), + write_results: [Google::Firestore::V1beta1::WriteResult.new( + update_time: Google::Cloud::Firestore::Convert.time_to_timestamp(commit_time))] + ) + end + + def self.build_test_for description, test, i + define_method("test_#{i}: #{description}") do + doc_ref = doc_ref_from_path test.doc_ref_path + data = data_from_json test.json_data + + if test.is_error + expect do + doc_ref.create data + end.must_raise ArgumentError + else + firestore_mock.expect :commit, commit_resp, [test.request.database, test.request.writes, options: default_options] + + doc_ref.create data + end + end + end +end + +class ConformanceSet < ConformanceTest + let(:commit_time) { Time.now } + let :commit_resp do + Google::Firestore::V1beta1::CommitResponse.new( + commit_time: Google::Cloud::Firestore::Convert.time_to_timestamp(commit_time), + write_results: [Google::Firestore::V1beta1::WriteResult.new( + update_time: Google::Cloud::Firestore::Convert.time_to_timestamp(commit_time))] + ) + end + + def self.build_test_for description, test, i + define_method("test_#{i}: #{description}") do + doc_ref = doc_ref_from_path test.doc_ref_path + data = data_from_json test.json_data + merge = if test.option && test.option.all + true + elsif test.option && !test.option.fields.empty? + test.option.fields.map do |fp| + firestore.field_path fp.field + end + end + + if test.is_error + expect do + doc_ref.set data, merge: merge + end.must_raise ArgumentError + else + firestore_mock.expect :commit, commit_resp, [test.request.database, test.request.writes, options: default_options] + + doc_ref.set data, merge: merge + end + end + end +end + +class ConformanceUpdate < ConformanceTest + let(:commit_time) { Time.now } + let :commit_resp do + Google::Firestore::V1beta1::CommitResponse.new( + commit_time: Google::Cloud::Firestore::Convert.time_to_timestamp(commit_time), + write_results: [Google::Firestore::V1beta1::WriteResult.new( + update_time: Google::Cloud::Firestore::Convert.time_to_timestamp(commit_time))] + ) + end + + def self.build_test_for description, test, i + define_method("test_#{i}: #{description}") do + if test.precondition && test.precondition.exists + skip "The ruby implementation does not allow exists on update" + end + + doc_ref = doc_ref_from_path test.doc_ref_path + data = data_from_json test.json_data + update_time = if test.precondition && test.precondition.update_time + Time.at(test.precondition.update_time.seconds) + end + + if test.is_error + expect do + doc_ref.update data, update_time: update_time + end.must_raise ArgumentError + else + firestore_mock.expect :commit, commit_resp, [test.request.database, test.request.writes, options: default_options] + + doc_ref.update data, update_time: update_time + end + end + end +end + +class ConformanceUpdatePaths < ConformanceTest + let(:commit_time) { Time.now } + let :commit_resp do + Google::Firestore::V1beta1::CommitResponse.new( + commit_time: Google::Cloud::Firestore::Convert.time_to_timestamp(commit_time), + write_results: [Google::Firestore::V1beta1::WriteResult.new( + update_time: Google::Cloud::Firestore::Convert.time_to_timestamp(commit_time))] + ) + end + + def self.build_test_for description, test, i + define_method("test_#{i}: #{description}") do + if test.precondition && test.precondition.exists + skip "The ruby implementation does not allow exists on update" + end + + doc_ref = doc_ref_from_path test.doc_ref_path + update_time = if test.precondition && test.precondition.update_time + Time.at(test.precondition.update_time.seconds) + end + + if test.is_error + expect do + data = data_from_field_paths_and_json test.field_paths, test.json_values + + doc_ref.update data, update_time: update_time + end.must_raise ArgumentError + else + firestore_mock.expect :commit, commit_resp, [test.request.database, test.request.writes, options: default_options] + + data = data_from_field_paths_and_json test.field_paths, test.json_values + + doc_ref.update data, update_time: update_time + end + end + end + + def data_from_field_paths_and_json field_paths, json_values + raise ArgumentError, "bad test data" if field_paths.size != json_values.size + + hash_args = Hash[field_paths.zip(json_values).map do |field_path, data_json| + [firestore.field_path(field_path.field), data_from_json(data_json)] + end] + + raise ArgumentError, "cannot duplicate args when using a hash in ruby" if hash_args.size != field_paths.size + + hash_args + end +end + +class ConformanceDelete < ConformanceTest + let(:commit_time) { Time.now } + let :commit_resp do + Google::Firestore::V1beta1::CommitResponse.new( + commit_time: Google::Cloud::Firestore::Convert.time_to_timestamp(commit_time), + write_results: [Google::Firestore::V1beta1::WriteResult.new( + update_time: Google::Cloud::Firestore::Convert.time_to_timestamp(commit_time))] + ) + end + + def self.build_test_for description, test, i + define_method("test_#{i}: #{description}") do + doc_ref = doc_ref_from_path test.doc_ref_path + opts = {} + if test.precondition && test.precondition.exists + opts[:exists] = test.precondition.exists + end + if test.precondition && test.precondition.update_time + opts[:update_time] = Time.at(test.precondition.update_time.seconds) + end + if test.is_error + expect do + doc_ref.delete opts + end.must_raise ArgumentError + else + firestore_mock.expect :commit, commit_resp, [test.request.database, test.request.writes, options: default_options] + + doc_ref.delete opts + end + end + end +end + +class ConformanceQuery < ConformanceTest + def self.build_test_for description, test, i + define_method("test_#{i}: #{description}") do + if test.is_error + expect do + build_query test, get_collection_reference(test.coll_path) + end.must_raise ArgumentError + else + query = build_query test, get_collection_reference(test.coll_path) + assert_equal test.query, query + end + end + end + + def get_collection_reference resource_name + col_path = resource_name.split("/documents/")[1] + firestore.col col_path + end + + def build_query test, col + test.clauses.each do |clause| + col = if clause.select + col.select(clause.select.fields.map(&:field)) + elsif where = clause.where + field_path = convert_field_path where + where_value = data_from_json where.json_value + col.where(field_path, where.op, where_value) + elsif clause.order_by + direction = clause.order_by.direction + col.order convert_field_path(clause.order_by), direction + elsif clause.offset && clause.offset != 0 + col.offset clause.offset + elsif clause.limit && clause.limit != 0 + col.limit clause.limit + elsif clause.start_at + col.start_at *convert_cursor(clause.start_at) + elsif clause.start_after + col.start_after *convert_cursor(clause.start_after) + elsif clause.end_at + col.end_at *convert_cursor(clause.end_at) + elsif clause.end_before + col.end_before *convert_cursor(clause.end_before) + else + raise "Unexpected Clause state: #{clause.inspect}" + end + end + col.query + end + + def convert_cursor clause_val + if clause_val.doc_snapshot + return [doc_snap_from_path_and_json_data(clause_val.doc_snapshot.path, clause_val.doc_snapshot.json_data)] + end + + clause_val.json_values.map {|x| data_from_json x } + end + + def convert_field_path clause_val + firestore.field_path clause_val.path.field + end +end + +proto_file = File.expand_path "../../../../conformance/test-suite.binproto", __dir__ +proto_contents = File.read proto_file, mode: "rb" +test_suite = Tests::TestSuite.decode proto_contents + +test_suite.tests.each_with_index do |wrapper, i| + case wrapper.test + when :get + next # Google::Firestore::V1beta1::GetDocumentRequest is not used. + when :create + ConformanceCreate.build_test_for wrapper.description, wrapper.create, i + when :set + ConformanceSet.build_test_for wrapper.description, wrapper.set, i + when :update + ConformanceUpdate.build_test_for wrapper.description, wrapper.update, i + when :update_paths + ConformanceUpdatePaths.build_test_for wrapper.description, wrapper.update_paths, i + when :delete + ConformanceDelete.build_test_for wrapper.description, wrapper.delete, i + when :query + ConformanceQuery.build_test_for wrapper.description, wrapper.query, i + when :listen + next + # TODO + else + raise "Unexpected test: #{wrapper.inspect}" + end +end diff --git a/google-cloud-firestore/test/google/cloud/firestore/convert/writes_for_set_test.rb b/google-cloud-firestore/test/google/cloud/firestore/convert/writes_for_set_test.rb index f333dcb0889b..0e229985aac9 100644 --- a/google-cloud-firestore/test/google/cloud/firestore/convert/writes_for_set_test.rb +++ b/google-cloud-firestore/test/google/cloud/firestore/convert/writes_for_set_test.rb @@ -360,7 +360,7 @@ } ), update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["h.g", "h.f"] + field_paths: ["h.f", "h.g"] ) ) ] @@ -374,7 +374,7 @@ data = {} error = expect do - Google::Cloud::Firestore::Convert.writes_for_set document_path, data, merge: true + Google::Cloud::Firestore::Convert.writes_for_set document_path, data, merge: [] end.must_raise ArgumentError error.message.must_equal "data required for set with merge" end diff --git a/google-cloud-firestore/test/google/cloud/firestore/convert/writes_for_update_test.rb b/google-cloud-firestore/test/google/cloud/firestore/convert/writes_for_update_test.rb index 18110984461c..03bdc2a6d89a 100644 --- a/google-cloud-firestore/test/google/cloud/firestore/convert/writes_for_update_test.rb +++ b/google-cloud-firestore/test/google/cloud/firestore/convert/writes_for_update_test.rb @@ -421,7 +421,7 @@ })) } ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a", "b.d", "b.c"]), + update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a", "b.c", "b.d"]), current_document: Google::Firestore::V1beta1::Precondition.new(exists: true) ) ] @@ -549,7 +549,7 @@ "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1) } ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a"]), + update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a", "c"]), current_document: Google::Firestore::V1beta1::Precondition.new(exists: true) ), Google::Firestore::V1beta1::Write.new( @@ -585,7 +585,7 @@ "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1) } ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a"]), + update_mask: Google::Firestore::V1beta1::DocumentMask.new(field_paths: ["a", "b"]), current_document: Google::Firestore::V1beta1::Precondition.new(exists: true) ), Google::Firestore::V1beta1::Write.new( diff --git a/google-cloud-firestore/test/google/cloud/firestore/generated/create_test.rb b/google-cloud-firestore/test/google/cloud/firestore/generated/create_test.rb deleted file mode 100644 index f52f10822d1a..000000000000 --- a/google-cloud-firestore/test/google/cloud/firestore/generated/create_test.rb +++ /dev/null @@ -1,310 +0,0 @@ -# Copyright 2017 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 -# -# https://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. - -require "helper" - -describe "Cross-Language Create Tests", :mock_firestore do - let(:document_path) { "C/d" } - let(:database_path) { "projects/#{project}/databases/(default)" } - let(:documents_path) { "#{database_path}/documents" } - - let(:commit_time) { Time.now } - let :commit_resp do - Google::Firestore::V1beta1::CommitResponse.new( - commit_time: Google::Cloud::Firestore::Convert.time_to_timestamp(commit_time), - write_results: [Google::Firestore::V1beta1::WriteResult.new( - update_time: Google::Cloud::Firestore::Convert.time_to_timestamp(commit_time))] - ) - end - - it "basic" do - create_json = "{\"a\": 1}" - create_data = JSON.parse create_json - - create_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - } - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: false) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, create_writes, options: default_options] - - firestore.batch { |b| b.create document_path, create_data } - end - - it "complex" do - create_json = "{\"a\": [1, 2.5], \"b\": {\"c\": [\"three\", {\"d\": true}]}}" - create_data = JSON.parse create_json - - create_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(array_value: Google::Firestore::V1beta1::ArrayValue.new(values: [ - Google::Firestore::V1beta1::Value.new(integer_value: 1), - Google::Firestore::V1beta1::Value.new(double_value: 2.5) - ])), - "b" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "c" => Google::Firestore::V1beta1::Value.new(array_value: Google::Firestore::V1beta1::ArrayValue.new(values: [ - Google::Firestore::V1beta1::Value.new(string_value: "three"), - Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "d" => Google::Firestore::V1beta1::Value.new(boolean_value: true) - })) - ])) - })) - } - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: false) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, create_writes, options: default_options] - - firestore.batch { |b| b.create document_path, create_data } - end - - it "creating or setting an empty map" do - create_json = "{}" - create_data = JSON.parse create_json - - create_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: false) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, create_writes, options: default_options] - - firestore.batch { |b| b.create document_path, create_data } - end - - it "don't split on dots" do - create_json = "{ \"a.b\": { \"c.d\": 1 }, \"e\": 2 }" - create_data = JSON.parse create_json - - create_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a.b" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "c.d" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - })), - "e" => Google::Firestore::V1beta1::Value.new(integer_value: 2) - } - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: false) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, create_writes, options: default_options] - - firestore.batch { |b| b.create document_path, create_data } - end - - it "non-alpha characters in map keys" do - create_json = "{ \"*\": { \".\": 1 }, \"~\": 2 }" - create_data = JSON.parse create_json - - create_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "*" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "." => Google::Firestore::V1beta1::Value.new(integer_value: 1) - })), - "~" => Google::Firestore::V1beta1::Value.new(integer_value: 2) - } - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: false) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, create_writes, options: default_options] - - firestore.batch { |b| b.create document_path, create_data } - end - - describe :field_delete do - it "DELETE cannot appear in data" do - create_data = { a: 1, b: firestore.field_delete} - - error = expect do - firestore.batch { |b| b.create document_path, create_data } - end.must_raise ArgumentError - error.message.must_equal "DELETE not allowed on create" - end - end - - describe :field_server_time do - - it "SERVER_TIME alone" do - create_data = { a: firestore.field_server_time } - - create_writes = [ - Google::Firestore::V1beta1::Write.new( - transform: Google::Firestore::V1beta1::DocumentTransform.new( - document: "projects/projectID/databases/(default)/documents/C/d", - field_transforms: [ - Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new( - field_path: "a", - set_to_server_value: :REQUEST_TIME - ) - ] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: false) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, create_writes, options: default_options] - - firestore.batch { |b| b.create document_path, create_data } - end - - it "multiple SERVER_TIME fields" do - create_data = { a: 1, b: firestore.field_server_time, c: { d: firestore.field_server_time } } - - create_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - } - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: false) - ), - Google::Firestore::V1beta1::Write.new( - transform: Google::Firestore::V1beta1::DocumentTransform.new( - document: "projects/projectID/databases/(default)/documents/C/d", - field_transforms: [ - Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new( - field_path: "b", - set_to_server_value: :REQUEST_TIME - ), - Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new( - field_path: "c.d", - set_to_server_value: :REQUEST_TIME - ) - ] - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, create_writes, options: default_options] - - firestore.batch { |b| b.create document_path, create_data } - end - - it "nested SERVER_TIME field" do - create_data = { a: 1, b: { c: firestore.field_server_time } } - - create_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - } - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: false) - ), - Google::Firestore::V1beta1::Write.new( - transform: Google::Firestore::V1beta1::DocumentTransform.new( - document: "projects/projectID/databases/(default)/documents/C/d", - field_transforms: [ - Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new( - field_path: "b.c", - set_to_server_value: :REQUEST_TIME - ) - ] - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, create_writes, options: default_options] - - firestore.batch { |b| b.create document_path, create_data } - end - - it "SERVER_TIME cannot be anywhere inside an array value" do - create_data = { a: [1, { b: firestore.field_server_time }] } - - error = expect do - firestore.batch { |b| b.create document_path, create_data } - end.must_raise ArgumentError - error.message.must_equal "cannot nest server_time under arrays" - end - - it "SERVER_TIME cannot be in an array value" do - create_data = { a: [1, 2, firestore.field_server_time] } - - error = expect do - firestore.batch { |b| b.create document_path, create_data } - end.must_raise ArgumentError - error.message.must_equal "cannot nest server_time under arrays" - end - - it "SERVER_TIME with data" do - create_data = { a: 1, b: firestore.field_server_time } - - create_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - } - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: false) - ), - Google::Firestore::V1beta1::Write.new( - transform: Google::Firestore::V1beta1::DocumentTransform.new( - document: "projects/projectID/databases/(default)/documents/C/d", - field_transforms: [ - Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new( - field_path: "b", - set_to_server_value: :REQUEST_TIME - ) - ] - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, create_writes, options: default_options] - - firestore.batch { |b| b.create document_path, create_data } - end - end -end diff --git a/google-cloud-firestore/test/google/cloud/firestore/generated/delete_test.rb b/google-cloud-firestore/test/google/cloud/firestore/generated/delete_test.rb deleted file mode 100644 index 13deb80bb0a0..000000000000 --- a/google-cloud-firestore/test/google/cloud/firestore/generated/delete_test.rb +++ /dev/null @@ -1,72 +0,0 @@ -# Copyright 2017 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 -# -# https://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. - -require "helper" - -describe "Cross-Language Delete Tests", :mock_firestore do - let(:document_path) { "C/d" } - let(:database_path) { "projects/#{project}/databases/(default)" } - let(:documents_path) { "#{database_path}/documents" } - - let(:commit_time) { Time.now } - let :commit_resp do - Google::Firestore::V1beta1::CommitResponse.new( - commit_time: Google::Cloud::Firestore::Convert.time_to_timestamp(commit_time), - write_results: [Google::Firestore::V1beta1::WriteResult.new( - update_time: Google::Cloud::Firestore::Convert.time_to_timestamp(commit_time))] - ) - end - - it "delete with exists precondition" do - delete_writes = [ - Google::Firestore::V1beta1::Write.new( - delete: "projects/projectID/databases/(default)/documents/C/d", - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, delete_writes, options: default_options] - - firestore.batch { |b| b.delete document_path, exists: true } - end - - it "delete without precondition" do - delete_writes = [ - Google::Firestore::V1beta1::Write.new( - delete: "projects/projectID/databases/(default)/documents/C/d" - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, delete_writes, options: default_options] - - firestore.batch { |b| b.delete document_path } - end - - it "delete with last-update-time precondition" do - delete_time = Time.now - 42 #42 seconds ago - - delete_writes = [ - Google::Firestore::V1beta1::Write.new( - delete: "projects/projectID/databases/(default)/documents/C/d", - current_document: Google::Firestore::V1beta1::Precondition.new( - update_time: Google::Cloud::Firestore::Convert.time_to_timestamp(delete_time)) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, delete_writes, options: default_options] - - firestore.batch { |b| b.delete document_path, update_time: delete_time } - end -end diff --git a/google-cloud-firestore/test/google/cloud/firestore/generated/get_test.rb b/google-cloud-firestore/test/google/cloud/firestore/generated/get_test.rb deleted file mode 100644 index c59197397296..000000000000 --- a/google-cloud-firestore/test/google/cloud/firestore/generated/get_test.rb +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright 2017 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 -# -# https://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. - -require "helper" - -describe "Cross-Language Get Tests", :mock_firestore do - let(:document_path) { "C/d" } - let(:database_path) { "projects/#{project}/databases/(default)" } - let(:documents_path) { "#{database_path}/documents" } - - it "get a document" do - get_doc_resp = [ - Google::Firestore::V1beta1::BatchGetDocumentsResponse.new( - read_time: Google::Cloud::Firestore::Convert.time_to_timestamp(Time.now), - found: Google::Firestore::V1beta1::Document.new( - name: "projects/#{project}/databases/(default)/documents/users/mike", - fields: { "name" => Google::Firestore::V1beta1::Value.new(string_value: "Mike") }, - create_time: Google::Cloud::Firestore::Convert.time_to_timestamp(Time.now), - update_time: Google::Cloud::Firestore::Convert.time_to_timestamp(Time.now) - )) - ] - - firestore_mock.expect :batch_get_documents, get_doc_resp.to_enum, [database_path, ["projects/projectID/databases/(default)/documents/C/d"], mask: nil, options: default_options] - - firestore.doc(document_path).get - end -end diff --git a/google-cloud-firestore/test/google/cloud/firestore/generated/set_test.rb b/google-cloud-firestore/test/google/cloud/firestore/generated/set_test.rb deleted file mode 100644 index 9473efd3dd68..000000000000 --- a/google-cloud-firestore/test/google/cloud/firestore/generated/set_test.rb +++ /dev/null @@ -1,425 +0,0 @@ -# Copyright 2017 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 -# -# https://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. - -require "helper" - -describe "Cross-Language Set Tests", :mock_firestore do - let(:document_path) { "C/d" } - let(:database_path) { "projects/#{project}/databases/(default)" } - let(:documents_path) { "#{database_path}/documents" } - - let(:commit_time) { Time.now } - let :commit_resp do - Google::Firestore::V1beta1::CommitResponse.new( - commit_time: Google::Cloud::Firestore::Convert.time_to_timestamp(commit_time), - write_results: [Google::Firestore::V1beta1::WriteResult.new( - update_time: Google::Cloud::Firestore::Convert.time_to_timestamp(commit_time))] - ) - end - - it "basic" do - set_json = "{\"a\": 1}" - set_data = JSON.parse set_json - - set_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - } - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, set_writes, options: default_options] - - firestore.batch { |b| b.set document_path, set_data } - end - - it "complex" do - set_json = "{\"a\": [1, 2.5], \"b\": {\"c\": [\"three\", {\"d\": true}]}}" - set_data = JSON.parse set_json - - set_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(array_value: Google::Firestore::V1beta1::ArrayValue.new(values: [ - Google::Firestore::V1beta1::Value.new(integer_value: 1), - Google::Firestore::V1beta1::Value.new(double_value: 2.5) - ])), - "b" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "c" => Google::Firestore::V1beta1::Value.new(array_value: Google::Firestore::V1beta1::ArrayValue.new(values: [ - Google::Firestore::V1beta1::Value.new(string_value: "three"), - Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "d" => Google::Firestore::V1beta1::Value.new(boolean_value: true) - })) - ])) - })) - } - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, set_writes, options: default_options] - - firestore.batch { |b| b.set document_path, set_data } - end - - it "DELETE cannot be anywhere inside an array value" do - set_data = { a: [1, { b: firestore.field_delete }] } - - error = expect do - firestore.batch { |b| b.set document_path, set_data } - end.must_raise ArgumentError - error.message.must_equal "cannot nest delete under arrays" - end - - it "DELETE cannot be in an array value" do - set_data = { a: [1, 2, firestore.field_delete] } - - error = expect do - firestore.batch { |b| b.set document_path, set_data } - end.must_raise ArgumentError - error.message.must_equal "cannot nest delete under arrays" - end - - it "DELETE cannot appear in data" do - set_data = { a: 1, b: firestore.field_delete } - - error = expect do - firestore.batch { |b| b.set document_path, set_data } - end.must_raise ArgumentError - error.message.must_equal "DELETE not allowed on set" - end - - it "creating or setting an empty map" do - set_json = "{}" - set_data = JSON.parse set_json - - set_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, set_writes, options: default_options] - - firestore.batch { |b| b.set document_path, set_data } - end - - it "don't split on dots" do - set_json = "{ \"a.b\": { \"c.d\": 1 }, \"e\": 2 }" - set_data = JSON.parse set_json - - set_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a.b" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "c.d" => Google::Firestore::V1beta1::Value.new(integer_value: 1), - })), - "e" => Google::Firestore::V1beta1::Value.new(integer_value: 2) - } - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, set_writes, options: default_options] - - firestore.batch { |b| b.set document_path, set_data } - end - - describe :merge do - it "DELETE cannot appear in an unmerged field" do - merge_data = { a: 1, b: firestore.field_delete } - - error = expect do - firestore.batch { |b| b.set document_path, merge_data, merge: [:a] } - end.must_raise ArgumentError - error.message.must_equal "deleted field not included in merge" - end - - it "Merge with FieldPaths (array)" do - set_json = "{\"*\": {\"~\": true}}" - set_data = JSON.parse set_json - - set_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "*" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "~" => Google::Firestore::V1beta1::Value.new(boolean_value: true) - })) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["`*`.`~`"] - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, set_writes, options: default_options] - - firestore.batch { |b| b.set document_path, set_data, merge: [["*", "~"]] } - end - - it "Merge with FieldPaths (FieldPath)" do - set_json = "{\"*\": {\"~\": true}}" - set_data = JSON.parse set_json - - set_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "*" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "~" => Google::Firestore::V1beta1::Value.new(boolean_value: true) - })) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["`*`.`~`"] - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, set_writes, options: default_options] - - firestore.batch { |b| b.set document_path, set_data, merge: firestore.field_path("*", "~") } - end - - it "Merge with a nested field (array)" do - set_json = "{\"h\": {\"g\": 4, \"f\": 5}}" - set_data = JSON.parse set_json - - set_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "h" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "g" => Google::Firestore::V1beta1::Value.new(integer_value: 4) - })) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["h.g"] - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, set_writes, options: default_options] - - firestore.batch { |b| b.set document_path, set_data, merge: [["h", "g"]] } - end - - it "Merge with a nested field (FieldPath)" do - set_json = "{\"h\": {\"g\": 4, \"f\": 5}}" - set_data = JSON.parse set_json - - set_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "h" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "g" => Google::Firestore::V1beta1::Value.new(integer_value: 4) - })) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["h.g"] - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, set_writes, options: default_options] - - firestore.batch { |b| b.set document_path, set_data, merge: firestore.field_path("h", "g") } - end - - it "Merge with a nested field (string)" do - set_json = "{\"h\": {\"g\": 4, \"f\": 5}}" - set_data = JSON.parse set_json - - set_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "h" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "g" => Google::Firestore::V1beta1::Value.new(integer_value: 4) - })) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["h.g"] - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, set_writes, options: default_options] - - firestore.batch { |b| b.set document_path, set_data, merge: ["h.g"] } - end - - it "Merge field is not a leaf" do - set_json = "{\"h\": {\"g\": 5, \"f\": 6}, \"e\": 7}" - set_data = JSON.parse set_json - - set_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "h" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "g" => Google::Firestore::V1beta1::Value.new(integer_value: 5), - "f" => Google::Firestore::V1beta1::Value.new(integer_value: 6) - })) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["h"] - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, set_writes, options: default_options] - - firestore.batch { |b| b.set document_path, set_data, merge: ["h"] } - end - - it "If no ordinary values in Merge, no write" do - set_data = { a: 1, b: firestore.field_server_time } - - set_writes = [ - Google::Firestore::V1beta1::Write.new( - transform: Google::Firestore::V1beta1::DocumentTransform.new( - document: "projects/projectID/databases/(default)/documents/C/d", - field_transforms: [ - Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new( - field_path: "b", - set_to_server_value: :REQUEST_TIME - ) - ] - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, set_writes, options: default_options] - - firestore.batch { |b| b.set document_path, set_data, merge: "b" } - end - - it "Merge fields must all be present in data" do - set_json = "{\"a\": 1}" - set_data = JSON.parse set_json - - error = expect do - firestore.batch { |b| b.set document_path, set_data, merge: ["b", "a"] } - end.must_raise ArgumentError - error.message.must_equal "all fields must be in data" - end - - it "Merge with a field" do - set_json = "{\"a\": 1, \"b\": 2}" - set_data = JSON.parse set_json - - set_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["a"] - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, set_writes, options: default_options] - - firestore.batch { |b| b.set document_path, set_data, merge: "a" } - end - - it "MergeAll cannot be specified with empty data" do - set_json = "{}" - set_data = JSON.parse set_json - - error = expect do - firestore.batch { |b| b.set document_path, set_data, merge: true } - end.must_raise ArgumentError - error.message.must_equal "data required for set with merge" - end - - it "MergeAll with nested fields" do - set_json = "{\"h\": { \"g\": 3, \"f\": 4 }}" - set_data = JSON.parse set_json - - set_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "h" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "g" => Google::Firestore::V1beta1::Value.new(integer_value: 3), - "f" => Google::Firestore::V1beta1::Value.new(integer_value: 4) - })) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["h.g", "h.f"] - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, set_writes, options: default_options] - - firestore.batch { |b| b.set document_path, set_data, merge: true } - end - - it "MergeAll" do - set_json = "{\"a\": 1, \"b\": 2}" - set_data = JSON.parse set_json - - set_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1), - "b" => Google::Firestore::V1beta1::Value.new(integer_value: 2) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["a", "b"] - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, set_writes, options: default_options] - - firestore.batch { |b| b.set document_path, set_data, merge: true } - end - end -end diff --git a/google-cloud-firestore/test/google/cloud/firestore/generated/update_test.rb b/google-cloud-firestore/test/google/cloud/firestore/generated/update_test.rb deleted file mode 100644 index 67ac47b334c3..000000000000 --- a/google-cloud-firestore/test/google/cloud/firestore/generated/update_test.rb +++ /dev/null @@ -1,979 +0,0 @@ -# Copyright 2017 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 -# -# https://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. - -require "helper" - -describe "Cross-Language Update Tests", :mock_firestore do - let(:document_path) { "C/d" } - let(:database_path) { "projects/#{project}/databases/(default)" } - let(:documents_path) { "#{database_path}/documents" } - - let(:commit_time) { Time.now } - let :commit_resp do - Google::Firestore::V1beta1::CommitResponse.new( - commit_time: Google::Cloud::Firestore::Convert.time_to_timestamp(commit_time), - write_results: [Google::Firestore::V1beta1::WriteResult.new( - update_time: Google::Cloud::Firestore::Convert.time_to_timestamp(commit_time))] - ) - end - - it "basic" do - update_json = "{\"a\": 1}" - update_data = JSON.parse update_json - - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["a"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, update_data } - end - - it "complex" do - update_json = "{\"a\": [1, 2.5], \"b\": {\"c\": [\"three\", {\"d\": true}]}}" - update_data = JSON.parse update_json - - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(array_value: Google::Firestore::V1beta1::ArrayValue.new(values: [ - Google::Firestore::V1beta1::Value.new(integer_value: 1), - Google::Firestore::V1beta1::Value.new(double_value: 2.5) - ])), - "b" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "c" => Google::Firestore::V1beta1::Value.new(array_value: Google::Firestore::V1beta1::ArrayValue.new(values: [ - Google::Firestore::V1beta1::Value.new(string_value: "three"), - Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "d" => Google::Firestore::V1beta1::Value.new(boolean_value: true) - })) - ])) - })) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["a", "b"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, update_data } - end - - it "invalid character" do - update_json = "{\"a~b\": 1}" - update_data = JSON.parse update_json - - error = expect do - firestore.batch { |b| b.update document_path, update_data } - end.must_raise ArgumentError - error.message.must_equal "invalid character, use FieldPath instead" - end - - it "empty field path component" do - - update_json = "{\"a..b\": 1}" - update_data = JSON.parse update_json - - error = expect do - firestore.batch { |b| b.update document_path, update_data } - end.must_raise ArgumentError - error.message.must_equal "empty paths not allowed" - end - - it "no paths" do - update_json = "{}" - update_data = JSON.parse update_json - - error = expect do - firestore.batch { |b| b.update document_path, update_data } - end.must_raise ArgumentError - error.message.must_equal "data is required" - end - - it "prefix #1" do - update_json = "{\"a.b\": 1, \"a\": 2}" - update_data = JSON.parse update_json - - error = expect do - firestore.batch { |b| b.update document_path, update_data } - end.must_raise ArgumentError - error.message.must_equal "one field cannot be a prefix of another" - end - - it "prefix #2" do - update_json = "{\"a\": 1, \"a.b\": 2}" - update_data = JSON.parse update_json - - error = expect do - firestore.batch { |b| b.update document_path, update_data } - end.must_raise ArgumentError - error.message.must_equal "one field cannot be a prefix of another" - end - - it "prefix #3" do - update_json = "{\"a\": {\"b\": 1}, \"a.d\": 2}" - update_data = JSON.parse update_json - - error = expect do - firestore.batch { |b| b.update document_path, update_data } - end.must_raise ArgumentError - error.message.must_equal "one field cannot be a prefix of another" - end - - it "non-letter starting chars are quoted, except underscore" do - update_json = "{\"_0.1.+2\": 1}" - update_data = JSON.parse update_json - - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "_0" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "1" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "+2" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - })) - })) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["_0.`1`.`+2`"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, update_data } - end - - it "Split on dots for top-level keys only" do - update_json = "{\"h.g\": {\"j.k\": 6}}" - update_data = JSON.parse update_json - - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "h" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "g" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "j.k" => Google::Firestore::V1beta1::Value.new(integer_value: 6) - })) - })) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["h.g"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, update_data } - end - - it "split on dots" do - update_json = "{\"a.b.c\": 1}" - update_data = JSON.parse update_json - - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "b" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "c" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - })) - })) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["a.b.c"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, update_data } - end - - it "last-update-time precondition" do - last_updated_at = Time.now - 42 #42 seconds ago - - update_json = "{\"a\": 1}" - update_data = JSON.parse update_json - - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["a"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - update_time: Google::Cloud::Firestore::Convert.time_to_timestamp(last_updated_at)) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, update_data, update_time: last_updated_at } - end - - describe :field_delete do - it "Delete" do - update_data = { a: 1, b: firestore.field_delete } - - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["a", "b"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, update_data } - end - - it "Delete alone" do - update_data = { a: firestore.field_delete } - - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d" - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["a"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, update_data } - end - - it "Delete with a dotted field" do - update_data = { a: 1, "b.c" => firestore.field_delete, "b.d" => 2 } - - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1), - "b" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "d" => Google::Firestore::V1beta1::Value.new(integer_value: 2) - })) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["a", "b.d", "b.c"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, update_data } - end - - it "DELETE cannot be nested" do - update_data = { a: { b: firestore.field_delete } } - - error = expect do - firestore.batch { |b| b.update document_path, update_data } - end.must_raise ArgumentError - error.message.must_equal "DELETE cannot be nested" - end - - it "DELETE cannot be anywhere inside an array value" do - update_data = { a: [1, { b: firestore.field_delete }] } - - error = expect do - firestore.batch { |b| b.update document_path, update_data } - end.must_raise ArgumentError - error.message.must_equal "cannot nest delete under arrays" - end - - it "DELETE cannot be in an array value" do - update_data = { a: [1, 2, firestore.field_delete] } - - error = expect do - firestore.batch { |b| b.update document_path, update_data } - end.must_raise ArgumentError - error.message.must_equal "cannot nest delete under arrays" - end - end - - describe :field_server_time do - it "SERVER_TIME alone" do - update_data = { a: firestore.field_server_time } - - update_writes = [ - Google::Firestore::V1beta1::Write.new( - transform: Google::Firestore::V1beta1::DocumentTransform.new( - document: "projects/projectID/databases/(default)/documents/C/d", - field_transforms: [ - Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new( - field_path: "a", - set_to_server_value: :REQUEST_TIME - ) - ] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, update_data } - end - - it "SERVER_TIME with dotted field" do - update_data = { "a.b.c" => firestore.field_server_time } - - update_writes = [ - Google::Firestore::V1beta1::Write.new( - transform: Google::Firestore::V1beta1::DocumentTransform.new( - document: "projects/projectID/databases/(default)/documents/C/d", - field_transforms: [ - Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new( - field_path: "a.b.c", - set_to_server_value: :REQUEST_TIME - ) - ] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, update_data } - end - - it "multiple SERVER_TIME fields" do - update_data = { a: 1, b: firestore.field_server_time, c: { d: firestore.field_server_time } } - - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["a"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ), - Google::Firestore::V1beta1::Write.new( - transform: Google::Firestore::V1beta1::DocumentTransform.new( - document: "projects/projectID/databases/(default)/documents/C/d", - field_transforms: [ - Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new( - field_path: "b", - set_to_server_value: :REQUEST_TIME - ), - Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new( - field_path: "c.d", - set_to_server_value: :REQUEST_TIME - ) - ] - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, update_data } - end - - it "nested SERVER_TIME field" do - update_data = { a: 1, b: { c: firestore.field_server_time } } - - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["a"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ), - Google::Firestore::V1beta1::Write.new( - transform: Google::Firestore::V1beta1::DocumentTransform.new( - document: "projects/projectID/databases/(default)/documents/C/d", - field_transforms: [ - Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new( - field_path: "b.c", - set_to_server_value: :REQUEST_TIME - ) - ] - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, update_data } - end - - it "SERVER_TIME cannot be anywhere inside an array value" do - update_data = { a: [1, { b: firestore.field_server_time }] } - - error = expect do - firestore.batch { |b| b.update document_path, update_data } - end.must_raise ArgumentError - error.message.must_equal "cannot nest server_time under arrays" - end - - it "SERVER_TIME cannot be in an array value" do - update_data = { a: [1, 2, firestore.field_server_time] } - - error = expect do - firestore.batch { |b| b.update document_path, update_data } - end.must_raise ArgumentError - error.message.must_equal "cannot nest server_time under arrays" - end - - it "SERVER_TIME with data" do - update_data = { a: 1, b: firestore.field_server_time } - - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["a"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ), - Google::Firestore::V1beta1::Write.new( - transform: Google::Firestore::V1beta1::DocumentTransform.new( - document: "projects/projectID/databases/(default)/documents/C/d", - field_transforms: [ - Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new( - field_path: "b", - set_to_server_value: :REQUEST_TIME - ) - ] - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, update_data } - end - end - - describe "using field paths" do - it "basic" do - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["a"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, a: 1 } - end - - it "complex" do - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(array_value: Google::Firestore::V1beta1::ArrayValue.new(values: [ - Google::Firestore::V1beta1::Value.new(integer_value: 1), - Google::Firestore::V1beta1::Value.new(double_value: 2.5) - ])), - "b" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "c" => Google::Firestore::V1beta1::Value.new(array_value: Google::Firestore::V1beta1::ArrayValue.new(values: [ - Google::Firestore::V1beta1::Value.new(string_value: "three"), - Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "d" => Google::Firestore::V1beta1::Value.new(boolean_value: true) - })) - ])) - })) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["a", "b"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, a: [1, 2.5], b: { c: ["three", { d: true }] } } - end - - it "multiple-element field path" do - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "b" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - })) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["a.b"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, [:a, :b] => 1 } - end - - it "elements are not split on dots (array)" do - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a.b" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "f.g" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "n.o" => Google::Firestore::V1beta1::Value.new(integer_value: 7) - })) - })) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["`a.b`.`f.g`"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, ["a.b", "f.g"] => { "n.o" => 7 } } - end - - it "elements are not split on dots (FieldPath)" do - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a.b" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "f.g" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "n.o" => Google::Firestore::V1beta1::Value.new(integer_value: 7) - })) - })) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["`a.b`.`f.g`"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, firestore.field_path("a.b", "f.g") => { "n.o" => 7 } } - end - - it "special characters" do - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "*" => Google::Firestore::V1beta1::Value.new(map_value: Google::Firestore::V1beta1::MapValue.new(fields: { - "`" => Google::Firestore::V1beta1::Value.new(integer_value: 2), - "~" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - })) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["`*`.`~`", "`*`.`\\``"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, [:*, :~] => 1, [:*, :`] => 2 } - end - - it "duplicate field path" do - error = expect do - firestore.batch { |b| b.update document_path, a: 1, b: 2, "a" => 3 } - end.must_raise ArgumentError - error.message.must_equal "duplicate field paths" - end - - it "empty field path" do - error = expect do - firestore.batch { |b| b.update document_path, [""] => 1 } - end.must_raise ArgumentError - error.message.must_equal "empty paths not allowed" - - error = expect do - firestore.batch { |b| b.update document_path, firestore.field_path("") => 1 } - end.must_raise ArgumentError - error.message.must_equal "empty paths not allowed" - - error = expect do - firestore.batch { |b| b.update document_path, "" => 1 } - end.must_raise ArgumentError - error.message.must_equal "empty paths not allowed" - end - - it "empty field path component" do - error = expect do - firestore.batch { |b| b.update document_path, ["*", ""] => 1 } - end.must_raise ArgumentError - error.message.must_equal "empty paths not allowed" - - error = expect do - firestore.batch { |b| b.update document_path, firestore.field_path("*", "") => 1 } - end.must_raise ArgumentError - error.message.must_equal "empty paths not allowed" - end - - it "no paths" do - error = expect do - firestore.batch { |b| b.update document_path } - end.must_raise ArgumentError - error.message.must_include "wrong number of arguments" - - error = expect do - firestore.batch { |b| b.update document_path, nil } - end.must_raise ArgumentError - error.message.must_equal "data is required" - end - - it "prefix #1" do - error = expect do - firestore.batch { |b| b.update document_path, ["a", "b"] => 1, ["a"] => 2 } - end.must_raise ArgumentError - error.message.must_equal "one field cannot be a prefix of another" - end - - it "prefix #2" do - error = expect do - firestore.batch { |b| b.update document_path, ["a"] => 1, ["a", "b"] => 2 } - end.must_raise ArgumentError - error.message.must_equal "one field cannot be a prefix of another" - end - - it "prefix #3" do - error = expect do - firestore.batch { |b| b.update document_path, ["a"] => { b: 1 }, ["a", "d"] => 2 } - end.must_raise ArgumentError - error.message.must_equal "one field cannot be a prefix of another" - end - - it "last-update-time precondition" do - last_updated_at = Time.now - 42 #42 seconds ago - - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["a"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - update_time: Google::Cloud::Firestore::Convert.time_to_timestamp(last_updated_at)) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, { ["a"] => 1 }, update_time: last_updated_at } - end - - describe :field_delete do - it "Delete (inline)" do - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["a", "b"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, a: 1, b: firestore.field_delete } - end - - it "Delete alone" do - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d" - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["a"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, a: firestore.field_delete } - end - end - - describe :field_server_time do - it "SERVER_TIME alone" do - update_writes = [ - Google::Firestore::V1beta1::Write.new( - transform: Google::Firestore::V1beta1::DocumentTransform.new( - document: "projects/projectID/databases/(default)/documents/C/d", - field_transforms: [ - Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new( - field_path: "a", - set_to_server_value: :REQUEST_TIME - ) - ] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, ["a"] => firestore.field_server_time } - end - - it "multiple SERVER_TIME fields" do - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["a"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ), - Google::Firestore::V1beta1::Write.new( - transform: Google::Firestore::V1beta1::DocumentTransform.new( - document: "projects/projectID/databases/(default)/documents/C/d", - field_transforms: [ - Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new( - field_path: "b", - set_to_server_value: :REQUEST_TIME - ), - Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new( - field_path: "c.d", - set_to_server_value: :REQUEST_TIME - ) - ] - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, ["a"] => 1, ["b"] => firestore.field_server_time, ["c"] => { d: firestore.field_server_time } } - end - - it "nested SERVER_TIME field" do - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["a"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ), - Google::Firestore::V1beta1::Write.new( - transform: Google::Firestore::V1beta1::DocumentTransform.new( - document: "projects/projectID/databases/(default)/documents/C/d", - field_transforms: [ - Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new( - field_path: "b.c", - set_to_server_value: :REQUEST_TIME - ) - ] - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, ["a"] => 1, ["b"] => { c: firestore.field_server_time } } - end - - it "SERVER_TIME cannot be anywhere inside an array value" do - error = expect do - firestore.batch { |b| b.update document_path, ["a"] => [1, { b: firestore.field_server_time }] } - end.must_raise ArgumentError - error.message.must_equal "cannot nest server_time under arrays" - end - - it "SERVER_TIME cannot be in an array value" do - error = expect do - firestore.batch { |b| b.update document_path, "a" => [1, 2, firestore.field_server_time] } - end.must_raise ArgumentError - error.message.must_equal "cannot nest server_time under arrays" - end - - it "SERVER_TIME with data" do - update_writes = [ - Google::Firestore::V1beta1::Write.new( - update: Google::Firestore::V1beta1::Document.new( - name: "projects/projectID/databases/(default)/documents/C/d", - fields: { - "a" => Google::Firestore::V1beta1::Value.new(integer_value: 1) - } - ), - update_mask: Google::Firestore::V1beta1::DocumentMask.new( - field_paths: ["a"] - ), - current_document: Google::Firestore::V1beta1::Precondition.new( - exists: true) - ), - Google::Firestore::V1beta1::Write.new( - transform: Google::Firestore::V1beta1::DocumentTransform.new( - document: "projects/projectID/databases/(default)/documents/C/d", - field_transforms: [ - Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new( - field_path: "b", - set_to_server_value: :REQUEST_TIME - ) - ] - ) - ) - ] - - firestore_mock.expect :commit, commit_resp, [database_path, update_writes, options: default_options] - - firestore.batch { |b| b.update document_path, ["a"] => 1, ["b"] => firestore.field_server_time } - end - end - end -end diff --git a/google-cloud-firestore/test/google/cloud/firestore/query/cursors_test.rb b/google-cloud-firestore/test/google/cloud/firestore/query/cursors_test.rb index e4284711d37e..da06c6b92d8d 100644 --- a/google-cloud-firestore/test/google/cloud/firestore/query/cursors_test.rb +++ b/google-cloud-firestore/test/google/cloud/firestore/query/cursors_test.rb @@ -48,19 +48,12 @@ expected_query = Google::Firestore::V1beta1::StructuredQuery.new( from: [Google::Firestore::V1beta1::StructuredQuery::CollectionSelector.new(collection_id: "C")], where: Google::Firestore::V1beta1::StructuredQuery::Filter.new( - composite_filter: Google::Firestore::V1beta1::StructuredQuery::CompositeFilter.new( - op: :AND, - filters: [ - Google::Firestore::V1beta1::StructuredQuery::Filter.new( - field_filter: Google::Firestore::V1beta1::StructuredQuery::FieldFilter.new( - field: Google::Firestore::V1beta1::StructuredQuery::FieldReference.new( - field_path: "a" - ), - op: :EQUAL, - value: Google::Firestore::V1beta1::Value.new(integer_value: 3) - ) - ) - ] + field_filter: Google::Firestore::V1beta1::StructuredQuery::FieldFilter.new( + field: Google::Firestore::V1beta1::StructuredQuery::FieldReference.new( + field_path: "a" + ), + op: :EQUAL, + value: Google::Firestore::V1beta1::Value.new(integer_value: 3) ) ), order_by: [ @@ -89,19 +82,12 @@ expected_query = Google::Firestore::V1beta1::StructuredQuery.new( from: [Google::Firestore::V1beta1::StructuredQuery::CollectionSelector.new(collection_id: "C")], where: Google::Firestore::V1beta1::StructuredQuery::Filter.new( - composite_filter: Google::Firestore::V1beta1::StructuredQuery::CompositeFilter.new( - op: :AND, - filters: [ - Google::Firestore::V1beta1::StructuredQuery::Filter.new( - field_filter: Google::Firestore::V1beta1::StructuredQuery::FieldFilter.new( - field: Google::Firestore::V1beta1::StructuredQuery::FieldReference.new( - field_path: "a" - ), - op: :LESS_THAN_OR_EQUAL, - value: Google::Firestore::V1beta1::Value.new(integer_value: 3) - ) - ) - ] + field_filter: Google::Firestore::V1beta1::StructuredQuery::FieldFilter.new( + field: Google::Firestore::V1beta1::StructuredQuery::FieldReference.new( + field_path: "a" + ), + op: :LESS_THAN_OR_EQUAL, + value: Google::Firestore::V1beta1::Value.new(integer_value: 3) ) ), order_by: [ @@ -135,19 +121,12 @@ expected_query = Google::Firestore::V1beta1::StructuredQuery.new( from: [Google::Firestore::V1beta1::StructuredQuery::CollectionSelector.new(collection_id: "C")], where: Google::Firestore::V1beta1::StructuredQuery::Filter.new( - composite_filter: Google::Firestore::V1beta1::StructuredQuery::CompositeFilter.new( - op: :AND, - filters: [ - Google::Firestore::V1beta1::StructuredQuery::Filter.new( - field_filter: Google::Firestore::V1beta1::StructuredQuery::FieldFilter.new( - field: Google::Firestore::V1beta1::StructuredQuery::FieldReference.new( - field_path: "a" - ), - op: :LESS_THAN, - value: Google::Firestore::V1beta1::Value.new(integer_value: 4) - ) - ) - ] + field_filter: Google::Firestore::V1beta1::StructuredQuery::FieldFilter.new( + field: Google::Firestore::V1beta1::StructuredQuery::FieldReference.new( + field_path: "a" + ), + op: :LESS_THAN, + value: Google::Firestore::V1beta1::Value.new(integer_value: 4) ) ), order_by: [ @@ -181,19 +160,12 @@ expected_query = Google::Firestore::V1beta1::StructuredQuery.new( from: [Google::Firestore::V1beta1::StructuredQuery::CollectionSelector.new(collection_id: "C")], where: Google::Firestore::V1beta1::StructuredQuery::Filter.new( - composite_filter: Google::Firestore::V1beta1::StructuredQuery::CompositeFilter.new( - op: :AND, - filters: [ - Google::Firestore::V1beta1::StructuredQuery::Filter.new( - field_filter: Google::Firestore::V1beta1::StructuredQuery::FieldFilter.new( - field: Google::Firestore::V1beta1::StructuredQuery::FieldReference.new( - field_path: "a" - ), - op: :LESS_THAN, - value: Google::Firestore::V1beta1::Value.new(integer_value: 4) - ) - ) - ] + field_filter: Google::Firestore::V1beta1::StructuredQuery::FieldFilter.new( + field: Google::Firestore::V1beta1::StructuredQuery::FieldReference.new( + field_path: "a" + ), + op: :LESS_THAN, + value: Google::Firestore::V1beta1::Value.new(integer_value: 4) ) ), order_by: [ diff --git a/google-cloud-firestore/test/google/cloud/firestore/query/get_test.rb b/google-cloud-firestore/test/google/cloud/firestore/query/get_test.rb index 302069ca8c3c..d5857cad60e3 100644 --- a/google-cloud-firestore/test/google/cloud/firestore/query/get_test.rb +++ b/google-cloud-firestore/test/google/cloud/firestore/query/get_test.rb @@ -77,7 +77,7 @@ ) firestore_mock.expect :run_query, query_results_enum, ["projects/#{project}/databases/(default)/documents", structured_query: expected_query, options: default_options] - results_enum = query.select(:name).select("status").select(:activity).get + results_enum = query.select(:name, "status", :activity).get assert_results_enum results_enum end