From c3acd4a04745c93edb2f61bf9be6fa33f439f4b0 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 7 Oct 2020 11:27:23 -0400 Subject: [PATCH] tests: re-enable cross-language conformance tests (#205) Leaving existing (old) conftest JSON files in place for now. in order to get the conftest runner working using existing semantics, before updating the JSON files and making required changes (to use 'update_transforms', for instance) in a future PR. Closes #95. Co-authored-by: Christopher Wilcox --- Makefile_v1 | 45 +- Makefile_v1beta1 | 37 -- tests/unit/v1/conformance_tests.py | 531 ++++++++++++++++++ ...oss_language.py => test_cross_language.py} | 116 ++-- 4 files changed, 620 insertions(+), 109 deletions(-) delete mode 100644 Makefile_v1beta1 create mode 100644 tests/unit/v1/conformance_tests.py rename tests/unit/v1/{_test_cross_language.py => test_cross_language.py} (87%) diff --git a/Makefile_v1 b/Makefile_v1 index af193e3e8..1648687e2 100644 --- a/Makefile_v1 +++ b/Makefile_v1 @@ -11,30 +11,51 @@ GOOGLEAPIS_REPO = $(REPO_DIR)/googleapis TESTS_REPO = $(REPO_DIR)/conformance-tests TEST_PROTO_DIR = $(TESTS_REPO)/firestore/v1 TEST_PROTO_SRC = $(TEST_PROTO_DIR)/proto/google/cloud/conformance/firestore/v1/tests.proto +TESTDATA_DIR = `pwd`/tests/unit/v1/testdata/ TMPDIR = /tmp/python-fs-proto -TMPDIR_FS = $(TMPDIR)/google/cloud/firestore_v1/proto +TMPDIR_FS = $(TMPDIR)/google/cloud/firestore_v1/types TEST_PROTO_COPY = $(TMPDIR_FS)/tests.proto +TEST_GEN_OUT = tests/unit/v1/conformance_tests.py +OUTDIR = /tmp/python-fs-gen -.PHONY: sync-protos gen-protos +.PHONY: sync-protos gen-protos docker-pull -gen-protos: sync-protos tweak-protos - # TODO(jba): Put the generated proto somewhere more suitable. - $(PROTOC) --python_out=. \ - -I $(TMPDIR) \ - -I $(PROTOBUF_REPO)/src \ - -I $(GOOGLEAPIS_REPO) \ - $(TEST_PROTO_COPY) +gen-protos: sync-protos tweak-protos docker-pull gen-protos-raw + +gen-protos-raw: + mkdir -p $(OUTDIR) + docker run \ + --mount type=bind,source=$(TMPDIR),destination="/in",readonly \ + --mount type=bind,source=$(OUTDIR),destination="/out" \ + --rm \ + --user `id -u`:`id -g` \ + gcr.io/gapic-images/gapic-generator-python + cp $(OUTDIR)/google/cloud/firestore_v1/types/tests.py \ + $(TEST_GEN_OUT) + sed -i -e \ + "s@package='google.cloud.firestore_v1'@package='tests.unit.v1'@" \ + $(TEST_GEN_OUT) tweak-protos: mkdir -p $(TMPDIR_FS) cp $(GOOGLEAPIS_REPO)/google/firestore/v1/*.proto $(TMPDIR_FS) - sed -i -e 's@google/firestore/v1@google/cloud/firestore_v1/proto@' $(TMPDIR_FS)/*.proto + sed -i -e 's@google/firestore/v1@google/cloud/firestore_v1/types@' $(TMPDIR_FS)/*.proto + sed -i -e 's@package google\.firestore\.v1@package google.cloud.firestore_v1@' $(TMPDIR_FS)/*.proto cp $(TEST_PROTO_SRC) $(TEST_PROTO_COPY) - sed -i -e 's@package google.cloud.conformance.firestore.v1@package google.cloud.firestore_v1.proto@' $(TEST_PROTO_COPY) - sed -i -e 's@google/firestore/v1@google/cloud/firestore_v1/proto@' $(TEST_PROTO_COPY) + sed -i -e 's@package google\.cloud\.conformance\.firestore\.v1@package google.cloud.firestore_v1@' $(TEST_PROTO_COPY) + sed -i -e 's@google/firestore/v1@google/cloud/firestore_v1/types@' $(TEST_PROTO_COPY) + sed -i -e 's@google\.firestore\.v1@google.cloud.firestore_v1@' $(TEST_PROTO_COPY) + sed -i -e 's@Cursor@Cursor_@' $(TEST_PROTO_COPY) sync-protos: cd $(PROTOBUF_REPO); git pull cd $(GOOGLEAPIS_REPO); git pull cd $(TESTS_REPO); git pull + +docker-pull: + docker pull gcr.io/gapic-images/gapic-generator-python:latest + +copy-testdata: + rm $(TESTDATA_DIR)/*.json + cp $(TEST_PROTO_DIR)/*.json $(TESTDATA_DIR)/ diff --git a/Makefile_v1beta1 b/Makefile_v1beta1 deleted file mode 100644 index 69cf87f41..000000000 --- a/Makefile_v1beta1 +++ /dev/null @@ -1,37 +0,0 @@ -# This makefile builds the protos needed for cross-language Firestore tests. - -# Assume protoc is on the path. The proto compiler must be one that -# supports proto3 syntax. -PROTOC = protoc - -# Dependent repos. -REPO_DIR = $(HOME)/git-repos -PROTOBUF_REPO = $(REPO_DIR)/protobuf -GOOGLEAPIS_REPO = $(REPO_DIR)/googleapis -TESTS_REPO = $(REPO_DIR)/gcp/google-cloud-common - -TMPDIR = /tmp/python-fs-proto -TMPDIR_FS = $(TMPDIR)/google/cloud/firestore_v1beta1/proto - -.PHONY: sync-protos gen-protos - -gen-protos: sync-protos tweak-protos - # TODO(jba): Put the generated proto somewhere more suitable. - $(PROTOC) --python_out=google/cloud/firestore_v1beta1/proto \ - -I $(TMPDIR) \ - -I $(PROTOBUF_REPO)/src \ - -I $(GOOGLEAPIS_REPO) \ - $(TMPDIR)/test_v1beta1.proto - -tweak-protos: - mkdir -p $(TMPDIR_FS) - cp $(GOOGLEAPIS_REPO)/google/firestore/v1beta1/*.proto $(TMPDIR_FS) - sed -i -e 's@google/firestore/v1beta1@google/cloud/firestore_v1beta1/proto@' $(TMPDIR_FS)/*.proto - cp $(TESTS_REPO)/testing/firestore/proto/test_v1beta1.proto $(TMPDIR) - sed -i -e 's@package tests@package tests.v1beta1@' $(TMPDIR)/test_v1beta1.proto - sed -i -e 's@google/firestore/v1beta1@google/cloud/firestore_v1beta1/proto@' $(TMPDIR)/test_v1beta1.proto - -sync-protos: - cd $(PROTOBUF_REPO); git pull - cd $(GOOGLEAPIS_REPO); git pull - #cd $(TESTS_REPO); git pull diff --git a/tests/unit/v1/conformance_tests.py b/tests/unit/v1/conformance_tests.py new file mode 100644 index 000000000..0718f8e5f --- /dev/null +++ b/tests/unit/v1/conformance_tests.py @@ -0,0 +1,531 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import proto # type: ignore + + +from google.cloud.firestore_v1.types import common +from google.cloud.firestore_v1.types import document +from google.cloud.firestore_v1.types import firestore +from google.cloud.firestore_v1.types import query as gcf_query +from google.protobuf import timestamp_pb2 as timestamp # type: ignore + + +__protobuf__ = proto.module( + package="tests.unit.v1", + manifest={ + "TestFile", + "Test", + "GetTest", + "CreateTest", + "SetTest", + "UpdateTest", + "UpdatePathsTest", + "DeleteTest", + "SetOption", + "QueryTest", + "Clause", + "Select", + "Where", + "OrderBy", + "Cursor_", + "DocSnapshot", + "FieldPath", + "ListenTest", + "Snapshot", + "DocChange", + }, +) + + +class TestFile(proto.Message): + r"""A collection of tests. + + Attributes: + tests (Sequence[~.gcf_tests.Test]): + + """ + + tests = proto.RepeatedField(proto.MESSAGE, number=1, message="Test",) + + +class Test(proto.Message): + r"""A Test describes a single client method call and its expected + result. + + Attributes: + description (str): + short description of the test + comment (str): + a comment describing the behavior being + tested + get (~.gcf_tests.GetTest): + + create (~.gcf_tests.CreateTest): + + set_ (~.gcf_tests.SetTest): + + update (~.gcf_tests.UpdateTest): + + update_paths (~.gcf_tests.UpdatePathsTest): + + delete (~.gcf_tests.DeleteTest): + + query (~.gcf_tests.QueryTest): + + listen (~.gcf_tests.ListenTest): + + """ + + description = proto.Field(proto.STRING, number=1) + + comment = proto.Field(proto.STRING, number=10) + + get = proto.Field(proto.MESSAGE, number=2, oneof="test", message="GetTest",) + + create = proto.Field(proto.MESSAGE, number=3, oneof="test", message="CreateTest",) + + set_ = proto.Field(proto.MESSAGE, number=4, oneof="test", message="SetTest",) + + update = proto.Field(proto.MESSAGE, number=5, oneof="test", message="UpdateTest",) + + update_paths = proto.Field( + proto.MESSAGE, number=6, oneof="test", message="UpdatePathsTest", + ) + + delete = proto.Field(proto.MESSAGE, number=7, oneof="test", message="DeleteTest",) + + query = proto.Field(proto.MESSAGE, number=8, oneof="test", message="QueryTest",) + + listen = proto.Field(proto.MESSAGE, number=9, oneof="test", message="ListenTest",) + + +class GetTest(proto.Message): + r"""Call to the DocumentRef.Get method. + + Attributes: + doc_ref_path (str): + The path of the doc, e.g. + "projects/projectID/databases/(default)/documents/C/d". + request (~.firestore.GetDocumentRequest): + The request that the call should send to the + Firestore service. + """ + + doc_ref_path = proto.Field(proto.STRING, number=1) + + request = proto.Field( + proto.MESSAGE, number=2, message=firestore.GetDocumentRequest, + ) + + +class CreateTest(proto.Message): + r"""Call to DocumentRef.Create. + + Attributes: + doc_ref_path (str): + The path of the doc, e.g. + "projects/projectID/databases/(default)/documents/C/d". + json_data (str): + 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. + request (~.firestore.CommitRequest): + The request that the call should generate. + is_error (bool): + If true, the call should result in an error + without generating a request. If this is true, + request should not be set. + """ + + doc_ref_path = proto.Field(proto.STRING, number=1) + + json_data = proto.Field(proto.STRING, number=2) + + request = proto.Field(proto.MESSAGE, number=3, message=firestore.CommitRequest,) + + is_error = proto.Field(proto.BOOL, number=4) + + +class SetTest(proto.Message): + r"""A call to DocumentRef.Set. + + Attributes: + doc_ref_path (str): + path of doc + option (~.gcf_tests.SetOption): + option to the Set call, if any + json_data (str): + data (see CreateTest.json_data) + request (~.firestore.CommitRequest): + expected request + is_error (bool): + call signals an error + """ + + doc_ref_path = proto.Field(proto.STRING, number=1) + + option = proto.Field(proto.MESSAGE, number=2, message="SetOption",) + + json_data = proto.Field(proto.STRING, number=3) + + request = proto.Field(proto.MESSAGE, number=4, message=firestore.CommitRequest,) + + is_error = proto.Field(proto.BOOL, number=5) + + +class UpdateTest(proto.Message): + r"""A call to the form of DocumentRef.Update that represents the + data as a map or dictionary. + + Attributes: + doc_ref_path (str): + path of doc + precondition (~.common.Precondition): + precondition in call, if any + json_data (str): + data (see CreateTest.json_data) + request (~.firestore.CommitRequest): + expected request + is_error (bool): + call signals an error + """ + + doc_ref_path = proto.Field(proto.STRING, number=1) + + precondition = proto.Field(proto.MESSAGE, number=2, message=common.Precondition,) + + json_data = proto.Field(proto.STRING, number=3) + + request = proto.Field(proto.MESSAGE, number=4, message=firestore.CommitRequest,) + + is_error = proto.Field(proto.BOOL, number=5) + + +class UpdatePathsTest(proto.Message): + r"""A call to the form of DocumentRef.Update that represents the + data as a list of field paths and their values. + + Attributes: + doc_ref_path (str): + path of doc + precondition (~.common.Precondition): + precondition in call, if any + field_paths (Sequence[~.gcf_tests.FieldPath]): + parallel sequences: field_paths[i] corresponds to + json_values[i] + json_values (Sequence[str]): + the argument values, as JSON + request (~.firestore.CommitRequest): + expected rquest + is_error (bool): + call signals an error + """ + + doc_ref_path = proto.Field(proto.STRING, number=1) + + precondition = proto.Field(proto.MESSAGE, number=2, message=common.Precondition,) + + field_paths = proto.RepeatedField(proto.MESSAGE, number=3, message="FieldPath",) + + json_values = proto.RepeatedField(proto.STRING, number=4) + + request = proto.Field(proto.MESSAGE, number=5, message=firestore.CommitRequest,) + + is_error = proto.Field(proto.BOOL, number=6) + + +class DeleteTest(proto.Message): + r"""A call to DocmentRef.Delete + + Attributes: + doc_ref_path (str): + path of doc + precondition (~.common.Precondition): + + request (~.firestore.CommitRequest): + expected rquest + is_error (bool): + call signals an error + """ + + doc_ref_path = proto.Field(proto.STRING, number=1) + + precondition = proto.Field(proto.MESSAGE, number=2, message=common.Precondition,) + + request = proto.Field(proto.MESSAGE, number=3, message=firestore.CommitRequest,) + + is_error = proto.Field(proto.BOOL, number=4) + + +class SetOption(proto.Message): + r"""An option to the DocumentRef.Set call. + + Attributes: + all_ (bool): + if true, merge all fields ("fields" is + ignored). + fields (Sequence[~.gcf_tests.FieldPath]): + field paths for a Merge option + """ + + all_ = proto.Field(proto.BOOL, number=1) + + fields = proto.RepeatedField(proto.MESSAGE, number=2, message="FieldPath",) + + +class QueryTest(proto.Message): + r""" + + Attributes: + coll_path (str): + path of collection, e.g. + "projects/projectID/databases/(default)/documents/C". + clauses (Sequence[~.gcf_tests.Clause]): + + query (~.gcf_query.StructuredQuery): + + is_error (bool): + + """ + + coll_path = proto.Field(proto.STRING, number=1) + + clauses = proto.RepeatedField(proto.MESSAGE, number=2, message="Clause",) + + query = proto.Field(proto.MESSAGE, number=3, message=gcf_query.StructuredQuery,) + + is_error = proto.Field(proto.BOOL, number=4) + + +class Clause(proto.Message): + r""" + + Attributes: + select (~.gcf_tests.Select): + + where (~.gcf_tests.Where): + + order_by (~.gcf_tests.OrderBy): + + offset (int): + + limit (int): + + start_at (~.gcf_tests.Cursor_): + + start_after (~.gcf_tests.Cursor_): + + end_at (~.gcf_tests.Cursor_): + + end_before (~.gcf_tests.Cursor_): + + """ + + select = proto.Field(proto.MESSAGE, number=1, oneof="clause", message="Select",) + + where = proto.Field(proto.MESSAGE, number=2, oneof="clause", message="Where",) + + order_by = proto.Field(proto.MESSAGE, number=3, oneof="clause", message="OrderBy",) + + offset = proto.Field(proto.INT32, number=4, oneof="clause") + + limit = proto.Field(proto.INT32, number=5, oneof="clause") + + start_at = proto.Field(proto.MESSAGE, number=6, oneof="clause", message="Cursor_",) + + start_after = proto.Field( + proto.MESSAGE, number=7, oneof="clause", message="Cursor_", + ) + + end_at = proto.Field(proto.MESSAGE, number=8, oneof="clause", message="Cursor_",) + + end_before = proto.Field( + proto.MESSAGE, number=9, oneof="clause", message="Cursor_", + ) + + +class Select(proto.Message): + r""" + + Attributes: + fields (Sequence[~.gcf_tests.FieldPath]): + + """ + + fields = proto.RepeatedField(proto.MESSAGE, number=1, message="FieldPath",) + + +class Where(proto.Message): + r""" + + Attributes: + path (~.gcf_tests.FieldPath): + + op (str): + + json_value (str): + + """ + + path = proto.Field(proto.MESSAGE, number=1, message="FieldPath",) + + op = proto.Field(proto.STRING, number=2) + + json_value = proto.Field(proto.STRING, number=3) + + +class OrderBy(proto.Message): + r""" + + Attributes: + path (~.gcf_tests.FieldPath): + + direction (str): + "asc" or "desc". + """ + + path = proto.Field(proto.MESSAGE, number=1, message="FieldPath",) + + direction = proto.Field(proto.STRING, number=2) + + +class Cursor_(proto.Message): + r""" + + Attributes: + doc_snapshot (~.gcf_tests.DocSnapshot): + one of: + json_values (Sequence[str]): + + """ + + doc_snapshot = proto.Field(proto.MESSAGE, number=1, message="DocSnapshot",) + + json_values = proto.RepeatedField(proto.STRING, number=2) + + +class DocSnapshot(proto.Message): + r""" + + Attributes: + path (str): + + json_data (str): + + """ + + path = proto.Field(proto.STRING, number=1) + + json_data = proto.Field(proto.STRING, number=2) + + +class FieldPath(proto.Message): + r""" + + Attributes: + field (Sequence[str]): + + """ + + field = proto.RepeatedField(proto.STRING, number=1) + + +class ListenTest(proto.Message): + r"""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. + + Attributes: + responses (Sequence[~.firestore.ListenResponse]): + + snapshots (Sequence[~.gcf_tests.Snapshot]): + + is_error (bool): + + """ + + responses = proto.RepeatedField( + proto.MESSAGE, number=1, message=firestore.ListenResponse, + ) + + snapshots = proto.RepeatedField(proto.MESSAGE, number=2, message="Snapshot",) + + is_error = proto.Field(proto.BOOL, number=3) + + +class Snapshot(proto.Message): + r""" + + Attributes: + docs (Sequence[~.document.Document]): + + changes (Sequence[~.gcf_tests.DocChange]): + + read_time (~.timestamp.Timestamp): + + """ + + docs = proto.RepeatedField(proto.MESSAGE, number=1, message=document.Document,) + + changes = proto.RepeatedField(proto.MESSAGE, number=2, message="DocChange",) + + read_time = proto.Field(proto.MESSAGE, number=3, message=timestamp.Timestamp,) + + +class DocChange(proto.Message): + r""" + + Attributes: + kind (~.gcf_tests.DocChange.Kind): + + doc (~.document.Document): + + old_index (int): + + new_index (int): + + """ + + class Kind(proto.Enum): + r"""""" + KIND_UNSPECIFIED = 0 + ADDED = 1 + REMOVED = 2 + MODIFIED = 3 + + kind = proto.Field(proto.ENUM, number=1, enum=Kind,) + + doc = proto.Field(proto.MESSAGE, number=2, message=document.Document,) + + old_index = proto.Field(proto.INT32, number=3) + + new_index = proto.Field(proto.INT32, number=4) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/tests/unit/v1/_test_cross_language.py b/tests/unit/v1/test_cross_language.py similarity index 87% rename from tests/unit/v1/_test_cross_language.py rename to tests/unit/v1/test_cross_language.py index 10fece5eb..49bc11506 100644 --- a/tests/unit/v1/_test_cross_language.py +++ b/tests/unit/v1/test_cross_language.py @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# TODO(microgen): currently cross language tests don't run as part of test pass -# This should be updated (and its makefile) to generate like other proto classes import functools import glob import json @@ -22,19 +20,21 @@ import mock import pytest -from google.protobuf import json_format from google.cloud.firestore_v1.types import document from google.cloud.firestore_v1.types import firestore -from google.cloud.firestore_v1.proto import tests_pb2 from google.cloud.firestore_v1.types import write +from tests.unit.v1 import conformance_tests + def _load_test_json(filename): - with open(filename, "r") as tp_file: - tp_json = json.load(tp_file) - test_file = tests_pb2.TestFile() - json_format.ParseDict(tp_json, test_file) shortname = os.path.split(filename)[-1] + + with open(filename, "r") as tp_file: + tp_json = tp_file.read() + + test_file = conformance_tests.TestFile.from_json(tp_json) + for test_proto in test_file.tests: test_proto.description = test_proto.description + " (%s)" % shortname yield test_proto @@ -48,51 +48,31 @@ def _load_test_json(filename): ALL_TESTPROTOS.extend(_load_test_json(filename)) _CREATE_TESTPROTOS = [ - test_proto - for test_proto in ALL_TESTPROTOS - if test_proto.WhichOneof("test") == "create" + test_proto for test_proto in ALL_TESTPROTOS if "create" in test_proto ] -_GET_TESTPROTOS = [ - test_proto - for test_proto in ALL_TESTPROTOS - if test_proto.WhichOneof("test") == "get" -] +_GET_TESTPROTOS = [test_proto for test_proto in ALL_TESTPROTOS if "get" in test_proto] -_SET_TESTPROTOS = [ - test_proto - for test_proto in ALL_TESTPROTOS - if test_proto.WhichOneof("test") == "set" -] +_SET_TESTPROTOS = [test_proto for test_proto in ALL_TESTPROTOS if "set_" in test_proto] _UPDATE_TESTPROTOS = [ - test_proto - for test_proto in ALL_TESTPROTOS - if test_proto.WhichOneof("test") == "update" + test_proto for test_proto in ALL_TESTPROTOS if "update" in test_proto ] _UPDATE_PATHS_TESTPROTOS = [ - test_proto - for test_proto in ALL_TESTPROTOS - if test_proto.WhichOneof("test") == "update_paths" + test_proto for test_proto in ALL_TESTPROTOS if "update_paths" in test_proto ] _DELETE_TESTPROTOS = [ - test_proto - for test_proto in ALL_TESTPROTOS - if test_proto.WhichOneof("test") == "delete" + test_proto for test_proto in ALL_TESTPROTOS if "delete" in test_proto ] _LISTEN_TESTPROTOS = [ - test_proto - for test_proto in ALL_TESTPROTOS - if test_proto.WhichOneof("test") == "listen" + test_proto for test_proto in ALL_TESTPROTOS if "listen" in test_proto ] _QUERY_TESTPROTOS = [ - test_proto - for test_proto in ALL_TESTPROTOS - if test_proto.WhichOneof("test") == "query" + test_proto for test_proto in ALL_TESTPROTOS if "query" in test_proto ] @@ -125,11 +105,19 @@ def _run_testcase(testcase, call, firestore_api, client): call() else: call() + + wrapped_writes = [ + write.Write.wrap(write_pb) for write_pb in testcase.request.writes + ] + + expected_request = { + "database": client._database_string, + "writes": wrapped_writes, + "transaction": None, + } + firestore_api.commit.assert_called_once_with( - client._database_string, - list(testcase.request.writes), - transaction=None, - metadata=client._rpc_metadata, + request=expected_request, metadata=client._rpc_metadata, ) @@ -153,18 +141,24 @@ def test_get_testprotos(test_proto): doc.get() # No '.textprotos' for errors, field_paths. + expected_request = { + "name": doc._document_path, + "mask": None, + "transaction": None, + } + firestore_api.get_document.assert_called_once_with( - doc._document_path, mask=None, transaction=None, metadata=client._rpc_metadata, + request=expected_request, metadata=client._rpc_metadata, ) @pytest.mark.parametrize("test_proto", _SET_TESTPROTOS) def test_set_testprotos(test_proto): - testcase = test_proto.set + testcase = test_proto.set_ firestore_api = _mock_firestore_api() client, doc = _make_client_document(firestore_api, testcase) data = convert_data(json.loads(testcase.json_data)) - if testcase.HasField("option"): + if "option" in testcase: merge = convert_set_option(testcase.option) else: merge = False @@ -178,7 +172,7 @@ def test_update_testprotos(test_proto): firestore_api = _mock_firestore_api() client, doc = _make_client_document(firestore_api, testcase) data = convert_data(json.loads(testcase.json_data)) - if testcase.HasField("precondition"): + if "precondition" in testcase: option = convert_precondition(testcase.precondition) else: option = None @@ -197,7 +191,7 @@ def test_delete_testprotos(test_proto): testcase = test_proto.delete firestore_api = _mock_firestore_api() client, doc = _make_client_document(firestore_api, testcase) - if testcase.HasField("precondition"): + if "precondition" in testcase: option = convert_precondition(testcase.precondition) else: option = None @@ -245,9 +239,12 @@ def callback(keys, applied_changes, read_time): db_str = "projects/projectID/databases/(default)" watch._firestore._database_string_internal = db_str + wrapped_responses = [ + firestore.ListenResponse.wrap(proto) for proto in testcase.responses + ] if testcase.is_error: try: - for proto in testcase.responses: + for proto in wrapped_responses: watch.on_snapshot(proto) except RuntimeError: # listen-target-add-wrong-id.textpro @@ -255,7 +252,7 @@ def callback(keys, applied_changes, read_time): pass else: - for proto in testcase.responses: + for proto in wrapped_responses: watch.on_snapshot(proto) assert len(snapshots) == len(testcase.snapshots) @@ -328,7 +325,7 @@ def convert_set_option(option): _helpers.FieldPath(*field.field).to_api_repr() for field in option.fields ] - assert option.all + assert option.all_ return True @@ -454,40 +451,39 @@ def parse_query(testcase): query = collection for clause in testcase.clauses: - kind = clause.WhichOneof("clause") - if kind == "select": + if "select" in clause: field_paths = [ ".".join(field_path.field) for field_path in clause.select.fields ] query = query.select(field_paths) - elif kind == "where": + elif "where" in clause: path = ".".join(clause.where.path.field) value = convert_data(json.loads(clause.where.json_value)) query = query.where(path, clause.where.op, value) - elif kind == "order_by": + elif "order_by" in clause: path = ".".join(clause.order_by.path.field) direction = clause.order_by.direction direction = _directions.get(direction, direction) query = query.order_by(path, direction=direction) - elif kind == "offset": + elif "offset" in clause: query = query.offset(clause.offset) - elif kind == "limit": + elif "limit" in clause: query = query.limit(clause.limit) - elif kind == "start_at": + elif "start_at" in clause: cursor = parse_cursor(clause.start_at, client) query = query.start_at(cursor) - elif kind == "start_after": + elif "start_after" in clause: cursor = parse_cursor(clause.start_after, client) query = query.start_after(cursor) - elif kind == "end_at": + elif "end_at" in clause: cursor = parse_cursor(clause.end_at, client) query = query.end_at(cursor) - elif kind == "end_before": + elif "end_before" in clause: cursor = parse_cursor(clause.end_before, client) query = query.end_before(cursor) else: # pragma: NO COVER - raise ValueError("Unknown query clause: {}".format(kind)) + raise ValueError("Unknown query clause: {}".format(clause)) return query @@ -501,7 +497,7 @@ def parse_cursor(cursor, client): from google.cloud.firestore_v1 import DocumentReference from google.cloud.firestore_v1 import DocumentSnapshot - if cursor.HasField("doc_snapshot"): + if "doc_snapshot" in cursor: path = parse_path(cursor.doc_snapshot.path) doc_ref = DocumentReference(*path, client=client)