874 lines
31 KiB
Python
874 lines
31 KiB
Python
|
# Copyright 2014 Google Inc. All rights reserved.
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
|
||
|
import unittest2
|
||
|
|
||
|
|
||
|
class TestConnection(unittest2.TestCase):
|
||
|
|
||
|
def _getTargetClass(self):
|
||
|
from gcloud.datastore.connection import Connection
|
||
|
|
||
|
return Connection
|
||
|
|
||
|
def _make_key_pb(self, project, id_=1234):
|
||
|
from gcloud.datastore.key import Key
|
||
|
path_args = ('Kind',)
|
||
|
if id_ is not None:
|
||
|
path_args += (id_,)
|
||
|
return Key(*path_args, project=project).to_protobuf()
|
||
|
|
||
|
def _make_query_pb(self, kind):
|
||
|
from gcloud.datastore._generated import query_pb2
|
||
|
pb = query_pb2.Query()
|
||
|
pb.kind.add().name = kind
|
||
|
return pb
|
||
|
|
||
|
def _makeOne(self, *args, **kw):
|
||
|
return self._getTargetClass()(*args, **kw)
|
||
|
|
||
|
def _verifyProtobufCall(self, called_with, URI, conn):
|
||
|
self.assertEqual(called_with['uri'], URI)
|
||
|
self.assertEqual(called_with['method'], 'POST')
|
||
|
self.assertEqual(called_with['headers']['Content-Type'],
|
||
|
'application/x-protobuf')
|
||
|
self.assertEqual(called_with['headers']['User-Agent'],
|
||
|
conn.USER_AGENT)
|
||
|
|
||
|
def test_default_url(self):
|
||
|
klass = self._getTargetClass()
|
||
|
conn = self._makeOne()
|
||
|
self.assertEqual(conn.api_base_url, klass.API_BASE_URL)
|
||
|
|
||
|
def test_custom_url_from_env(self):
|
||
|
import os
|
||
|
from gcloud._testing import _Monkey
|
||
|
from gcloud.connection import API_BASE_URL
|
||
|
from gcloud.environment_vars import GCD_HOST
|
||
|
|
||
|
HOST = 'CURR_HOST'
|
||
|
fake_environ = {GCD_HOST: HOST}
|
||
|
|
||
|
with _Monkey(os, environ=fake_environ):
|
||
|
conn = self._makeOne()
|
||
|
|
||
|
self.assertNotEqual(conn.api_base_url, API_BASE_URL)
|
||
|
self.assertEqual(conn.api_base_url, HOST + '/datastore')
|
||
|
|
||
|
def test_custom_url_from_constructor(self):
|
||
|
from gcloud.connection import API_BASE_URL
|
||
|
|
||
|
HOST = object()
|
||
|
conn = self._makeOne(api_base_url=HOST)
|
||
|
self.assertNotEqual(conn.api_base_url, API_BASE_URL)
|
||
|
self.assertEqual(conn.api_base_url, HOST)
|
||
|
|
||
|
def test_custom_url_constructor_and_env(self):
|
||
|
import os
|
||
|
from gcloud._testing import _Monkey
|
||
|
from gcloud.connection import API_BASE_URL
|
||
|
from gcloud.environment_vars import GCD_HOST
|
||
|
|
||
|
HOST1 = object()
|
||
|
HOST2 = object()
|
||
|
fake_environ = {GCD_HOST: HOST1}
|
||
|
|
||
|
with _Monkey(os, environ=fake_environ):
|
||
|
conn = self._makeOne(api_base_url=HOST2)
|
||
|
|
||
|
self.assertNotEqual(conn.api_base_url, API_BASE_URL)
|
||
|
self.assertNotEqual(conn.api_base_url, HOST1)
|
||
|
self.assertEqual(conn.api_base_url, HOST2)
|
||
|
|
||
|
def test_ctor_defaults(self):
|
||
|
conn = self._makeOne()
|
||
|
self.assertEqual(conn.credentials, None)
|
||
|
|
||
|
def test_ctor_explicit(self):
|
||
|
class Creds(object):
|
||
|
|
||
|
def create_scoped_required(self):
|
||
|
return False
|
||
|
|
||
|
creds = Creds()
|
||
|
conn = self._makeOne(creds)
|
||
|
self.assertTrue(conn.credentials is creds)
|
||
|
|
||
|
def test_http_w_existing(self):
|
||
|
conn = self._makeOne()
|
||
|
conn._http = http = object()
|
||
|
self.assertTrue(conn.http is http)
|
||
|
|
||
|
def test_http_wo_creds(self):
|
||
|
import httplib2
|
||
|
|
||
|
conn = self._makeOne()
|
||
|
self.assertTrue(isinstance(conn.http, httplib2.Http))
|
||
|
|
||
|
def test_http_w_creds(self):
|
||
|
import httplib2
|
||
|
|
||
|
authorized = object()
|
||
|
|
||
|
class Creds(object):
|
||
|
|
||
|
def authorize(self, http):
|
||
|
self._called_with = http
|
||
|
return authorized
|
||
|
|
||
|
def create_scoped_required(self):
|
||
|
return False
|
||
|
|
||
|
creds = Creds()
|
||
|
conn = self._makeOne(creds)
|
||
|
self.assertTrue(conn.http is authorized)
|
||
|
self.assertTrue(isinstance(creds._called_with, httplib2.Http))
|
||
|
|
||
|
def test__request_w_200(self):
|
||
|
PROJECT = 'PROJECT'
|
||
|
METHOD = 'METHOD'
|
||
|
DATA = b'DATA'
|
||
|
conn = self._makeOne()
|
||
|
URI = '/'.join([
|
||
|
conn.api_base_url,
|
||
|
conn.API_VERSION,
|
||
|
'projects',
|
||
|
PROJECT + ':' + METHOD,
|
||
|
])
|
||
|
http = conn._http = Http({'status': '200'}, 'CONTENT')
|
||
|
self.assertEqual(conn._request(PROJECT, METHOD, DATA), 'CONTENT')
|
||
|
self._verifyProtobufCall(http._called_with, URI, conn)
|
||
|
self.assertEqual(http._called_with['body'], DATA)
|
||
|
|
||
|
def test__request_not_200(self):
|
||
|
from gcloud.exceptions import BadRequest
|
||
|
from google.rpc import status_pb2
|
||
|
|
||
|
error = status_pb2.Status()
|
||
|
error.message = 'Entity value is indexed.'
|
||
|
error.code = 9 # FAILED_PRECONDITION
|
||
|
|
||
|
PROJECT = 'PROJECT'
|
||
|
METHOD = 'METHOD'
|
||
|
DATA = 'DATA'
|
||
|
conn = self._makeOne()
|
||
|
conn._http = Http({'status': '400'}, error.SerializeToString())
|
||
|
with self.assertRaises(BadRequest) as e:
|
||
|
conn._request(PROJECT, METHOD, DATA)
|
||
|
expected_message = '400 Entity value is indexed.'
|
||
|
self.assertEqual(str(e.exception), expected_message)
|
||
|
|
||
|
def test__rpc(self):
|
||
|
|
||
|
class ReqPB(object):
|
||
|
|
||
|
def SerializeToString(self):
|
||
|
return REQPB
|
||
|
|
||
|
class RspPB(object):
|
||
|
|
||
|
def __init__(self, pb):
|
||
|
self._pb = pb
|
||
|
|
||
|
@classmethod
|
||
|
def FromString(cls, pb):
|
||
|
return cls(pb)
|
||
|
|
||
|
REQPB = b'REQPB'
|
||
|
PROJECT = 'PROJECT'
|
||
|
METHOD = 'METHOD'
|
||
|
conn = self._makeOne()
|
||
|
URI = '/'.join([
|
||
|
conn.api_base_url,
|
||
|
conn.API_VERSION,
|
||
|
'projects',
|
||
|
PROJECT + ':' + METHOD,
|
||
|
])
|
||
|
http = conn._http = Http({'status': '200'}, 'CONTENT')
|
||
|
response = conn._rpc(PROJECT, METHOD, ReqPB(), RspPB)
|
||
|
self.assertTrue(isinstance(response, RspPB))
|
||
|
self.assertEqual(response._pb, 'CONTENT')
|
||
|
self._verifyProtobufCall(http._called_with, URI, conn)
|
||
|
self.assertEqual(http._called_with['body'], REQPB)
|
||
|
|
||
|
def test_build_api_url_w_default_base_version(self):
|
||
|
PROJECT = 'PROJECT'
|
||
|
METHOD = 'METHOD'
|
||
|
conn = self._makeOne()
|
||
|
URI = '/'.join([
|
||
|
conn.api_base_url,
|
||
|
conn.API_VERSION,
|
||
|
'projects',
|
||
|
PROJECT + ':' + METHOD,
|
||
|
])
|
||
|
self.assertEqual(conn.build_api_url(PROJECT, METHOD), URI)
|
||
|
|
||
|
def test_build_api_url_w_explicit_base_version(self):
|
||
|
BASE = 'http://example.com/'
|
||
|
VER = '3.1415926'
|
||
|
PROJECT = 'PROJECT'
|
||
|
METHOD = 'METHOD'
|
||
|
conn = self._makeOne()
|
||
|
URI = '/'.join([
|
||
|
BASE,
|
||
|
VER,
|
||
|
'projects',
|
||
|
PROJECT + ':' + METHOD,
|
||
|
])
|
||
|
self.assertEqual(conn.build_api_url(PROJECT, METHOD, BASE, VER),
|
||
|
URI)
|
||
|
|
||
|
def test_lookup_single_key_empty_response(self):
|
||
|
from gcloud.datastore._generated import datastore_pb2
|
||
|
|
||
|
PROJECT = 'PROJECT'
|
||
|
key_pb = self._make_key_pb(PROJECT)
|
||
|
rsp_pb = datastore_pb2.LookupResponse()
|
||
|
conn = self._makeOne()
|
||
|
URI = '/'.join([
|
||
|
conn.api_base_url,
|
||
|
conn.API_VERSION,
|
||
|
'projects',
|
||
|
PROJECT + ':lookup',
|
||
|
])
|
||
|
http = conn._http = Http({'status': '200'}, rsp_pb.SerializeToString())
|
||
|
found, missing, deferred = conn.lookup(PROJECT, [key_pb])
|
||
|
self.assertEqual(len(found), 0)
|
||
|
self.assertEqual(len(missing), 0)
|
||
|
self.assertEqual(len(deferred), 0)
|
||
|
cw = http._called_with
|
||
|
self._verifyProtobufCall(cw, URI, conn)
|
||
|
rq_class = datastore_pb2.LookupRequest
|
||
|
request = rq_class()
|
||
|
request.ParseFromString(cw['body'])
|
||
|
keys = list(request.keys)
|
||
|
self.assertEqual(len(keys), 1)
|
||
|
self.assertEqual(key_pb, keys[0])
|
||
|
|
||
|
def test_lookup_single_key_empty_response_w_eventual(self):
|
||
|
from gcloud.datastore._generated import datastore_pb2
|
||
|
|
||
|
PROJECT = 'PROJECT'
|
||
|
key_pb = self._make_key_pb(PROJECT)
|
||
|
rsp_pb = datastore_pb2.LookupResponse()
|
||
|
conn = self._makeOne()
|
||
|
URI = '/'.join([
|
||
|
conn.api_base_url,
|
||
|
conn.API_VERSION,
|
||
|
'projects',
|
||
|
PROJECT + ':lookup',
|
||
|
])
|
||
|
http = conn._http = Http({'status': '200'}, rsp_pb.SerializeToString())
|
||
|
found, missing, deferred = conn.lookup(PROJECT, [key_pb],
|
||
|
eventual=True)
|
||
|
self.assertEqual(len(found), 0)
|
||
|
self.assertEqual(len(missing), 0)
|
||
|
self.assertEqual(len(deferred), 0)
|
||
|
cw = http._called_with
|
||
|
self._verifyProtobufCall(cw, URI, conn)
|
||
|
rq_class = datastore_pb2.LookupRequest
|
||
|
request = rq_class()
|
||
|
request.ParseFromString(cw['body'])
|
||
|
keys = list(request.keys)
|
||
|
self.assertEqual(len(keys), 1)
|
||
|
self.assertEqual(key_pb, keys[0])
|
||
|
self.assertEqual(request.read_options.read_consistency,
|
||
|
datastore_pb2.ReadOptions.EVENTUAL)
|
||
|
self.assertEqual(request.read_options.transaction, b'')
|
||
|
|
||
|
def test_lookup_single_key_empty_response_w_eventual_and_transaction(self):
|
||
|
PROJECT = 'PROJECT'
|
||
|
TRANSACTION = b'TRANSACTION'
|
||
|
key_pb = self._make_key_pb(PROJECT)
|
||
|
conn = self._makeOne()
|
||
|
self.assertRaises(ValueError, conn.lookup, PROJECT, key_pb,
|
||
|
eventual=True, transaction_id=TRANSACTION)
|
||
|
|
||
|
def test_lookup_single_key_empty_response_w_transaction(self):
|
||
|
from gcloud.datastore._generated import datastore_pb2
|
||
|
|
||
|
PROJECT = 'PROJECT'
|
||
|
TRANSACTION = b'TRANSACTION'
|
||
|
key_pb = self._make_key_pb(PROJECT)
|
||
|
rsp_pb = datastore_pb2.LookupResponse()
|
||
|
conn = self._makeOne()
|
||
|
URI = '/'.join([
|
||
|
conn.api_base_url,
|
||
|
conn.API_VERSION,
|
||
|
'projects',
|
||
|
PROJECT + ':lookup',
|
||
|
])
|
||
|
http = conn._http = Http({'status': '200'}, rsp_pb.SerializeToString())
|
||
|
found, missing, deferred = conn.lookup(PROJECT, [key_pb],
|
||
|
transaction_id=TRANSACTION)
|
||
|
self.assertEqual(len(found), 0)
|
||
|
self.assertEqual(len(missing), 0)
|
||
|
self.assertEqual(len(deferred), 0)
|
||
|
cw = http._called_with
|
||
|
self._verifyProtobufCall(cw, URI, conn)
|
||
|
rq_class = datastore_pb2.LookupRequest
|
||
|
request = rq_class()
|
||
|
request.ParseFromString(cw['body'])
|
||
|
keys = list(request.keys)
|
||
|
self.assertEqual(len(keys), 1)
|
||
|
self.assertEqual(key_pb, keys[0])
|
||
|
self.assertEqual(request.read_options.transaction, TRANSACTION)
|
||
|
|
||
|
def test_lookup_single_key_nonempty_response(self):
|
||
|
from gcloud.datastore._generated import datastore_pb2
|
||
|
from gcloud.datastore._generated import entity_pb2
|
||
|
|
||
|
PROJECT = 'PROJECT'
|
||
|
key_pb = self._make_key_pb(PROJECT)
|
||
|
rsp_pb = datastore_pb2.LookupResponse()
|
||
|
entity = entity_pb2.Entity()
|
||
|
entity.key.CopyFrom(key_pb)
|
||
|
rsp_pb.found.add(entity=entity)
|
||
|
conn = self._makeOne()
|
||
|
URI = '/'.join([
|
||
|
conn.api_base_url,
|
||
|
conn.API_VERSION,
|
||
|
'projects',
|
||
|
PROJECT + ':lookup',
|
||
|
])
|
||
|
http = conn._http = Http({'status': '200'}, rsp_pb.SerializeToString())
|
||
|
(found,), missing, deferred = conn.lookup(PROJECT, [key_pb])
|
||
|
self.assertEqual(len(missing), 0)
|
||
|
self.assertEqual(len(deferred), 0)
|
||
|
self.assertEqual(found.key.path[0].kind, 'Kind')
|
||
|
self.assertEqual(found.key.path[0].id, 1234)
|
||
|
cw = http._called_with
|
||
|
self._verifyProtobufCall(cw, URI, conn)
|
||
|
rq_class = datastore_pb2.LookupRequest
|
||
|
request = rq_class()
|
||
|
request.ParseFromString(cw['body'])
|
||
|
keys = list(request.keys)
|
||
|
self.assertEqual(len(keys), 1)
|
||
|
self.assertEqual(key_pb, keys[0])
|
||
|
|
||
|
def test_lookup_multiple_keys_empty_response(self):
|
||
|
from gcloud.datastore._generated import datastore_pb2
|
||
|
|
||
|
PROJECT = 'PROJECT'
|
||
|
key_pb1 = self._make_key_pb(PROJECT)
|
||
|
key_pb2 = self._make_key_pb(PROJECT, id_=2345)
|
||
|
rsp_pb = datastore_pb2.LookupResponse()
|
||
|
conn = self._makeOne()
|
||
|
URI = '/'.join([
|
||
|
conn.api_base_url,
|
||
|
conn.API_VERSION,
|
||
|
'projects',
|
||
|
PROJECT + ':lookup',
|
||
|
])
|
||
|
http = conn._http = Http({'status': '200'}, rsp_pb.SerializeToString())
|
||
|
found, missing, deferred = conn.lookup(PROJECT, [key_pb1, key_pb2])
|
||
|
self.assertEqual(len(found), 0)
|
||
|
self.assertEqual(len(missing), 0)
|
||
|
self.assertEqual(len(deferred), 0)
|
||
|
cw = http._called_with
|
||
|
self._verifyProtobufCall(cw, URI, conn)
|
||
|
rq_class = datastore_pb2.LookupRequest
|
||
|
request = rq_class()
|
||
|
request.ParseFromString(cw['body'])
|
||
|
keys = list(request.keys)
|
||
|
self.assertEqual(len(keys), 2)
|
||
|
self.assertEqual(key_pb1, keys[0])
|
||
|
self.assertEqual(key_pb2, keys[1])
|
||
|
|
||
|
def test_lookup_multiple_keys_w_missing(self):
|
||
|
from gcloud.datastore._generated import datastore_pb2
|
||
|
|
||
|
PROJECT = 'PROJECT'
|
||
|
key_pb1 = self._make_key_pb(PROJECT)
|
||
|
key_pb2 = self._make_key_pb(PROJECT, id_=2345)
|
||
|
rsp_pb = datastore_pb2.LookupResponse()
|
||
|
er_1 = rsp_pb.missing.add()
|
||
|
er_1.entity.key.CopyFrom(key_pb1)
|
||
|
er_2 = rsp_pb.missing.add()
|
||
|
er_2.entity.key.CopyFrom(key_pb2)
|
||
|
conn = self._makeOne()
|
||
|
URI = '/'.join([
|
||
|
conn.api_base_url,
|
||
|
conn.API_VERSION,
|
||
|
'projects',
|
||
|
PROJECT + ':lookup',
|
||
|
])
|
||
|
http = conn._http = Http({'status': '200'}, rsp_pb.SerializeToString())
|
||
|
result, missing, deferred = conn.lookup(PROJECT, [key_pb1, key_pb2])
|
||
|
self.assertEqual(result, [])
|
||
|
self.assertEqual(len(deferred), 0)
|
||
|
self.assertEqual([missed.key for missed in missing],
|
||
|
[key_pb1, key_pb2])
|
||
|
cw = http._called_with
|
||
|
self._verifyProtobufCall(cw, URI, conn)
|
||
|
rq_class = datastore_pb2.LookupRequest
|
||
|
request = rq_class()
|
||
|
request.ParseFromString(cw['body'])
|
||
|
keys = list(request.keys)
|
||
|
self.assertEqual(len(keys), 2)
|
||
|
self.assertEqual(key_pb1, keys[0])
|
||
|
self.assertEqual(key_pb2, keys[1])
|
||
|
|
||
|
def test_lookup_multiple_keys_w_deferred(self):
|
||
|
from gcloud.datastore._generated import datastore_pb2
|
||
|
|
||
|
PROJECT = 'PROJECT'
|
||
|
key_pb1 = self._make_key_pb(PROJECT)
|
||
|
key_pb2 = self._make_key_pb(PROJECT, id_=2345)
|
||
|
rsp_pb = datastore_pb2.LookupResponse()
|
||
|
rsp_pb.deferred.add().CopyFrom(key_pb1)
|
||
|
rsp_pb.deferred.add().CopyFrom(key_pb2)
|
||
|
conn = self._makeOne()
|
||
|
URI = '/'.join([
|
||
|
conn.api_base_url,
|
||
|
conn.API_VERSION,
|
||
|
'projects',
|
||
|
PROJECT + ':lookup',
|
||
|
])
|
||
|
http = conn._http = Http({'status': '200'}, rsp_pb.SerializeToString())
|
||
|
result, missing, deferred = conn.lookup(PROJECT, [key_pb1, key_pb2])
|
||
|
self.assertEqual(result, [])
|
||
|
self.assertEqual(len(missing), 0)
|
||
|
self.assertEqual([def_key for def_key in deferred], [key_pb1, key_pb2])
|
||
|
cw = http._called_with
|
||
|
self._verifyProtobufCall(cw, URI, conn)
|
||
|
self.assertEqual(cw['uri'], URI)
|
||
|
self.assertEqual(cw['method'], 'POST')
|
||
|
self.assertEqual(cw['headers']['Content-Type'],
|
||
|
'application/x-protobuf')
|
||
|
self.assertEqual(cw['headers']['User-Agent'], conn.USER_AGENT)
|
||
|
rq_class = datastore_pb2.LookupRequest
|
||
|
request = rq_class()
|
||
|
request.ParseFromString(cw['body'])
|
||
|
keys = list(request.keys)
|
||
|
self.assertEqual(len(keys), 2)
|
||
|
self.assertEqual(key_pb1, keys[0])
|
||
|
self.assertEqual(key_pb2, keys[1])
|
||
|
|
||
|
def test_run_query_w_eventual_no_transaction(self):
|
||
|
from gcloud.datastore._generated import datastore_pb2
|
||
|
from gcloud.datastore._generated import query_pb2
|
||
|
|
||
|
PROJECT = 'PROJECT'
|
||
|
KIND = 'Nonesuch'
|
||
|
CURSOR = b'\x00'
|
||
|
q_pb = self._make_query_pb(KIND)
|
||
|
rsp_pb = datastore_pb2.RunQueryResponse()
|
||
|
rsp_pb.batch.end_cursor = CURSOR
|
||
|
no_more = query_pb2.QueryResultBatch.NO_MORE_RESULTS
|
||
|
rsp_pb.batch.more_results = no_more
|
||
|
rsp_pb.batch.entity_result_type = query_pb2.EntityResult.FULL
|
||
|
conn = self._makeOne()
|
||
|
URI = '/'.join([
|
||
|
conn.api_base_url,
|
||
|
conn.API_VERSION,
|
||
|
'projects',
|
||
|
PROJECT + ':runQuery',
|
||
|
])
|
||
|
http = conn._http = Http({'status': '200'}, rsp_pb.SerializeToString())
|
||
|
pbs, end, more, skipped = conn.run_query(PROJECT, q_pb,
|
||
|
eventual=True)
|
||
|
self.assertEqual(pbs, [])
|
||
|
self.assertEqual(end, CURSOR)
|
||
|
self.assertTrue(more)
|
||
|
self.assertEqual(skipped, 0)
|
||
|
cw = http._called_with
|
||
|
self._verifyProtobufCall(cw, URI, conn)
|
||
|
rq_class = datastore_pb2.RunQueryRequest
|
||
|
request = rq_class()
|
||
|
request.ParseFromString(cw['body'])
|
||
|
self.assertEqual(request.partition_id.namespace_id, '')
|
||
|
self.assertEqual(request.query, q_pb)
|
||
|
self.assertEqual(request.read_options.read_consistency,
|
||
|
datastore_pb2.ReadOptions.EVENTUAL)
|
||
|
self.assertEqual(request.read_options.transaction, b'')
|
||
|
|
||
|
def test_run_query_wo_eventual_w_transaction(self):
|
||
|
from gcloud.datastore._generated import datastore_pb2
|
||
|
from gcloud.datastore._generated import query_pb2
|
||
|
|
||
|
PROJECT = 'PROJECT'
|
||
|
KIND = 'Nonesuch'
|
||
|
CURSOR = b'\x00'
|
||
|
TRANSACTION = b'TRANSACTION'
|
||
|
q_pb = self._make_query_pb(KIND)
|
||
|
rsp_pb = datastore_pb2.RunQueryResponse()
|
||
|
rsp_pb.batch.end_cursor = CURSOR
|
||
|
no_more = query_pb2.QueryResultBatch.NO_MORE_RESULTS
|
||
|
rsp_pb.batch.more_results = no_more
|
||
|
rsp_pb.batch.entity_result_type = query_pb2.EntityResult.FULL
|
||
|
conn = self._makeOne()
|
||
|
URI = '/'.join([
|
||
|
conn.api_base_url,
|
||
|
conn.API_VERSION,
|
||
|
'projects',
|
||
|
PROJECT + ':runQuery',
|
||
|
])
|
||
|
http = conn._http = Http({'status': '200'}, rsp_pb.SerializeToString())
|
||
|
pbs, end, more, skipped = conn.run_query(
|
||
|
PROJECT, q_pb, transaction_id=TRANSACTION)
|
||
|
self.assertEqual(pbs, [])
|
||
|
self.assertEqual(end, CURSOR)
|
||
|
self.assertTrue(more)
|
||
|
self.assertEqual(skipped, 0)
|
||
|
cw = http._called_with
|
||
|
self._verifyProtobufCall(cw, URI, conn)
|
||
|
rq_class = datastore_pb2.RunQueryRequest
|
||
|
request = rq_class()
|
||
|
request.ParseFromString(cw['body'])
|
||
|
self.assertEqual(request.partition_id.namespace_id, '')
|
||
|
self.assertEqual(request.query, q_pb)
|
||
|
self.assertEqual(
|
||
|
request.read_options.read_consistency,
|
||
|
datastore_pb2.ReadOptions.READ_CONSISTENCY_UNSPECIFIED)
|
||
|
self.assertEqual(request.read_options.transaction, TRANSACTION)
|
||
|
|
||
|
def test_run_query_w_eventual_and_transaction(self):
|
||
|
from gcloud.datastore._generated import datastore_pb2
|
||
|
from gcloud.datastore._generated import query_pb2
|
||
|
|
||
|
PROJECT = 'PROJECT'
|
||
|
KIND = 'Nonesuch'
|
||
|
CURSOR = b'\x00'
|
||
|
TRANSACTION = b'TRANSACTION'
|
||
|
q_pb = self._make_query_pb(KIND)
|
||
|
rsp_pb = datastore_pb2.RunQueryResponse()
|
||
|
rsp_pb.batch.end_cursor = CURSOR
|
||
|
no_more = query_pb2.QueryResultBatch.NO_MORE_RESULTS
|
||
|
rsp_pb.batch.more_results = no_more
|
||
|
rsp_pb.batch.entity_result_type = query_pb2.EntityResult.FULL
|
||
|
conn = self._makeOne()
|
||
|
self.assertRaises(ValueError, conn.run_query, PROJECT, q_pb,
|
||
|
eventual=True, transaction_id=TRANSACTION)
|
||
|
|
||
|
def test_run_query_wo_namespace_empty_result(self):
|
||
|
from gcloud.datastore._generated import datastore_pb2
|
||
|
from gcloud.datastore._generated import query_pb2
|
||
|
|
||
|
PROJECT = 'PROJECT'
|
||
|
KIND = 'Nonesuch'
|
||
|
CURSOR = b'\x00'
|
||
|
q_pb = self._make_query_pb(KIND)
|
||
|
rsp_pb = datastore_pb2.RunQueryResponse()
|
||
|
rsp_pb.batch.end_cursor = CURSOR
|
||
|
no_more = query_pb2.QueryResultBatch.NO_MORE_RESULTS
|
||
|
rsp_pb.batch.more_results = no_more
|
||
|
rsp_pb.batch.entity_result_type = query_pb2.EntityResult.FULL
|
||
|
conn = self._makeOne()
|
||
|
URI = '/'.join([
|
||
|
conn.api_base_url,
|
||
|
conn.API_VERSION,
|
||
|
'projects',
|
||
|
PROJECT + ':runQuery',
|
||
|
])
|
||
|
http = conn._http = Http({'status': '200'}, rsp_pb.SerializeToString())
|
||
|
pbs, end, more, skipped = conn.run_query(PROJECT, q_pb)
|
||
|
self.assertEqual(pbs, [])
|
||
|
self.assertEqual(end, CURSOR)
|
||
|
self.assertTrue(more)
|
||
|
self.assertEqual(skipped, 0)
|
||
|
cw = http._called_with
|
||
|
self._verifyProtobufCall(cw, URI, conn)
|
||
|
rq_class = datastore_pb2.RunQueryRequest
|
||
|
request = rq_class()
|
||
|
request.ParseFromString(cw['body'])
|
||
|
self.assertEqual(request.partition_id.namespace_id, '')
|
||
|
self.assertEqual(request.query, q_pb)
|
||
|
|
||
|
def test_run_query_w_namespace_nonempty_result(self):
|
||
|
from gcloud.datastore._generated import datastore_pb2
|
||
|
from gcloud.datastore._generated import entity_pb2
|
||
|
|
||
|
PROJECT = 'PROJECT'
|
||
|
KIND = 'Kind'
|
||
|
entity = entity_pb2.Entity()
|
||
|
q_pb = self._make_query_pb(KIND)
|
||
|
rsp_pb = datastore_pb2.RunQueryResponse()
|
||
|
rsp_pb.batch.entity_results.add(entity=entity)
|
||
|
rsp_pb.batch.entity_result_type = 1 # FULL
|
||
|
rsp_pb.batch.more_results = 3 # NO_MORE_RESULTS
|
||
|
conn = self._makeOne()
|
||
|
URI = '/'.join([
|
||
|
conn.api_base_url,
|
||
|
conn.API_VERSION,
|
||
|
'projects',
|
||
|
PROJECT + ':runQuery',
|
||
|
])
|
||
|
http = conn._http = Http({'status': '200'}, rsp_pb.SerializeToString())
|
||
|
pbs = conn.run_query(PROJECT, q_pb, 'NS')[0]
|
||
|
self.assertEqual(len(pbs), 1)
|
||
|
cw = http._called_with
|
||
|
self._verifyProtobufCall(cw, URI, conn)
|
||
|
rq_class = datastore_pb2.RunQueryRequest
|
||
|
request = rq_class()
|
||
|
request.ParseFromString(cw['body'])
|
||
|
self.assertEqual(request.partition_id.namespace_id, 'NS')
|
||
|
self.assertEqual(request.query, q_pb)
|
||
|
|
||
|
def test_begin_transaction(self):
|
||
|
from gcloud.datastore._generated import datastore_pb2
|
||
|
|
||
|
PROJECT = 'PROJECT'
|
||
|
TRANSACTION = b'TRANSACTION'
|
||
|
rsp_pb = datastore_pb2.BeginTransactionResponse()
|
||
|
rsp_pb.transaction = TRANSACTION
|
||
|
conn = self._makeOne()
|
||
|
URI = '/'.join([
|
||
|
conn.api_base_url,
|
||
|
conn.API_VERSION,
|
||
|
'projects',
|
||
|
PROJECT + ':beginTransaction',
|
||
|
])
|
||
|
http = conn._http = Http({'status': '200'}, rsp_pb.SerializeToString())
|
||
|
self.assertEqual(conn.begin_transaction(PROJECT), TRANSACTION)
|
||
|
cw = http._called_with
|
||
|
self._verifyProtobufCall(cw, URI, conn)
|
||
|
rq_class = datastore_pb2.BeginTransactionRequest
|
||
|
request = rq_class()
|
||
|
request.ParseFromString(cw['body'])
|
||
|
|
||
|
def test_commit_wo_transaction(self):
|
||
|
from gcloud._testing import _Monkey
|
||
|
from gcloud.datastore._generated import datastore_pb2
|
||
|
from gcloud.datastore import connection as MUT
|
||
|
from gcloud.datastore.helpers import _new_value_pb
|
||
|
|
||
|
PROJECT = 'PROJECT'
|
||
|
key_pb = self._make_key_pb(PROJECT)
|
||
|
rsp_pb = datastore_pb2.CommitResponse()
|
||
|
req_pb = datastore_pb2.CommitRequest()
|
||
|
mutation = req_pb.mutations.add()
|
||
|
insert = mutation.upsert
|
||
|
insert.key.CopyFrom(key_pb)
|
||
|
value_pb = _new_value_pb(insert, 'foo')
|
||
|
value_pb.string_value = u'Foo'
|
||
|
conn = self._makeOne()
|
||
|
URI = '/'.join([
|
||
|
conn.api_base_url,
|
||
|
conn.API_VERSION,
|
||
|
'projects',
|
||
|
PROJECT + ':commit',
|
||
|
])
|
||
|
http = conn._http = Http({'status': '200'}, rsp_pb.SerializeToString())
|
||
|
|
||
|
# Set up mock for parsing the response.
|
||
|
expected_result = object()
|
||
|
_parsed = []
|
||
|
|
||
|
def mock_parse(response):
|
||
|
_parsed.append(response)
|
||
|
return expected_result
|
||
|
|
||
|
with _Monkey(MUT, _parse_commit_response=mock_parse):
|
||
|
result = conn.commit(PROJECT, req_pb, None)
|
||
|
|
||
|
self.assertTrue(result is expected_result)
|
||
|
cw = http._called_with
|
||
|
self._verifyProtobufCall(cw, URI, conn)
|
||
|
rq_class = datastore_pb2.CommitRequest
|
||
|
request = rq_class()
|
||
|
request.ParseFromString(cw['body'])
|
||
|
self.assertEqual(request.transaction, b'')
|
||
|
self.assertEqual(list(request.mutations), [mutation])
|
||
|
self.assertEqual(request.mode, rq_class.NON_TRANSACTIONAL)
|
||
|
self.assertEqual(_parsed, [rsp_pb])
|
||
|
|
||
|
def test_commit_w_transaction(self):
|
||
|
from gcloud._testing import _Monkey
|
||
|
from gcloud.datastore._generated import datastore_pb2
|
||
|
from gcloud.datastore import connection as MUT
|
||
|
from gcloud.datastore.helpers import _new_value_pb
|
||
|
|
||
|
PROJECT = 'PROJECT'
|
||
|
key_pb = self._make_key_pb(PROJECT)
|
||
|
rsp_pb = datastore_pb2.CommitResponse()
|
||
|
req_pb = datastore_pb2.CommitRequest()
|
||
|
mutation = req_pb.mutations.add()
|
||
|
insert = mutation.upsert
|
||
|
insert.key.CopyFrom(key_pb)
|
||
|
value_pb = _new_value_pb(insert, 'foo')
|
||
|
value_pb.string_value = u'Foo'
|
||
|
conn = self._makeOne()
|
||
|
URI = '/'.join([
|
||
|
conn.api_base_url,
|
||
|
conn.API_VERSION,
|
||
|
'projects',
|
||
|
PROJECT + ':commit',
|
||
|
])
|
||
|
http = conn._http = Http({'status': '200'}, rsp_pb.SerializeToString())
|
||
|
|
||
|
# Set up mock for parsing the response.
|
||
|
expected_result = object()
|
||
|
_parsed = []
|
||
|
|
||
|
def mock_parse(response):
|
||
|
_parsed.append(response)
|
||
|
return expected_result
|
||
|
|
||
|
with _Monkey(MUT, _parse_commit_response=mock_parse):
|
||
|
result = conn.commit(PROJECT, req_pb, b'xact')
|
||
|
|
||
|
self.assertTrue(result is expected_result)
|
||
|
cw = http._called_with
|
||
|
self._verifyProtobufCall(cw, URI, conn)
|
||
|
rq_class = datastore_pb2.CommitRequest
|
||
|
request = rq_class()
|
||
|
request.ParseFromString(cw['body'])
|
||
|
self.assertEqual(request.transaction, b'xact')
|
||
|
self.assertEqual(list(request.mutations), [mutation])
|
||
|
self.assertEqual(request.mode, rq_class.TRANSACTIONAL)
|
||
|
self.assertEqual(_parsed, [rsp_pb])
|
||
|
|
||
|
def test_rollback_ok(self):
|
||
|
from gcloud.datastore._generated import datastore_pb2
|
||
|
PROJECT = 'PROJECT'
|
||
|
TRANSACTION = b'xact'
|
||
|
|
||
|
rsp_pb = datastore_pb2.RollbackResponse()
|
||
|
conn = self._makeOne()
|
||
|
URI = '/'.join([
|
||
|
conn.api_base_url,
|
||
|
conn.API_VERSION,
|
||
|
'projects',
|
||
|
PROJECT + ':rollback',
|
||
|
])
|
||
|
http = conn._http = Http({'status': '200'}, rsp_pb.SerializeToString())
|
||
|
self.assertEqual(conn.rollback(PROJECT, TRANSACTION), None)
|
||
|
cw = http._called_with
|
||
|
self._verifyProtobufCall(cw, URI, conn)
|
||
|
rq_class = datastore_pb2.RollbackRequest
|
||
|
request = rq_class()
|
||
|
request.ParseFromString(cw['body'])
|
||
|
self.assertEqual(request.transaction, TRANSACTION)
|
||
|
|
||
|
def test_allocate_ids_empty(self):
|
||
|
from gcloud.datastore._generated import datastore_pb2
|
||
|
|
||
|
PROJECT = 'PROJECT'
|
||
|
rsp_pb = datastore_pb2.AllocateIdsResponse()
|
||
|
conn = self._makeOne()
|
||
|
URI = '/'.join([
|
||
|
conn.api_base_url,
|
||
|
conn.API_VERSION,
|
||
|
'projects',
|
||
|
PROJECT + ':allocateIds',
|
||
|
])
|
||
|
http = conn._http = Http({'status': '200'}, rsp_pb.SerializeToString())
|
||
|
self.assertEqual(conn.allocate_ids(PROJECT, []), [])
|
||
|
cw = http._called_with
|
||
|
self._verifyProtobufCall(cw, URI, conn)
|
||
|
rq_class = datastore_pb2.AllocateIdsRequest
|
||
|
request = rq_class()
|
||
|
request.ParseFromString(cw['body'])
|
||
|
self.assertEqual(list(request.keys), [])
|
||
|
|
||
|
def test_allocate_ids_non_empty(self):
|
||
|
from gcloud.datastore._generated import datastore_pb2
|
||
|
|
||
|
PROJECT = 'PROJECT'
|
||
|
before_key_pbs = [
|
||
|
self._make_key_pb(PROJECT, id_=None),
|
||
|
self._make_key_pb(PROJECT, id_=None),
|
||
|
]
|
||
|
after_key_pbs = [
|
||
|
self._make_key_pb(PROJECT),
|
||
|
self._make_key_pb(PROJECT, id_=2345),
|
||
|
]
|
||
|
rsp_pb = datastore_pb2.AllocateIdsResponse()
|
||
|
rsp_pb.keys.add().CopyFrom(after_key_pbs[0])
|
||
|
rsp_pb.keys.add().CopyFrom(after_key_pbs[1])
|
||
|
conn = self._makeOne()
|
||
|
URI = '/'.join([
|
||
|
conn.api_base_url,
|
||
|
conn.API_VERSION,
|
||
|
'projects',
|
||
|
PROJECT + ':allocateIds',
|
||
|
])
|
||
|
http = conn._http = Http({'status': '200'}, rsp_pb.SerializeToString())
|
||
|
self.assertEqual(conn.allocate_ids(PROJECT, before_key_pbs),
|
||
|
after_key_pbs)
|
||
|
cw = http._called_with
|
||
|
self._verifyProtobufCall(cw, URI, conn)
|
||
|
rq_class = datastore_pb2.AllocateIdsRequest
|
||
|
request = rq_class()
|
||
|
request.ParseFromString(cw['body'])
|
||
|
self.assertEqual(len(request.keys), len(before_key_pbs))
|
||
|
for key_before, key_after in zip(before_key_pbs, request.keys):
|
||
|
self.assertEqual(key_before, key_after)
|
||
|
|
||
|
|
||
|
class Test__parse_commit_response(unittest2.TestCase):
|
||
|
|
||
|
def _callFUT(self, commit_response_pb):
|
||
|
from gcloud.datastore.connection import _parse_commit_response
|
||
|
return _parse_commit_response(commit_response_pb)
|
||
|
|
||
|
def test_it(self):
|
||
|
from gcloud.datastore._generated import datastore_pb2
|
||
|
from gcloud.datastore._generated import entity_pb2
|
||
|
|
||
|
index_updates = 1337
|
||
|
keys = [
|
||
|
entity_pb2.Key(
|
||
|
path=[
|
||
|
entity_pb2.Key.PathElement(
|
||
|
kind='Foo',
|
||
|
id=1234,
|
||
|
),
|
||
|
],
|
||
|
),
|
||
|
entity_pb2.Key(
|
||
|
path=[
|
||
|
entity_pb2.Key.PathElement(
|
||
|
kind='Bar',
|
||
|
name='baz',
|
||
|
),
|
||
|
],
|
||
|
),
|
||
|
]
|
||
|
response = datastore_pb2.CommitResponse(
|
||
|
mutation_results=[
|
||
|
datastore_pb2.MutationResult(key=key) for key in keys
|
||
|
],
|
||
|
index_updates=index_updates,
|
||
|
)
|
||
|
result = self._callFUT(response)
|
||
|
self.assertEqual(result, (index_updates, keys))
|
||
|
|
||
|
|
||
|
class Http(object):
|
||
|
|
||
|
_called_with = None
|
||
|
|
||
|
def __init__(self, headers, content):
|
||
|
from httplib2 import Response
|
||
|
self._response = Response(headers)
|
||
|
self._content = content
|
||
|
|
||
|
def request(self, **kw):
|
||
|
self._called_with = kw
|
||
|
return self._response, self._content
|
||
|
|
||
|
|
||
|
class _PathElementProto(object):
|
||
|
|
||
|
def __init__(self, _id):
|
||
|
self.id = _id
|
||
|
|
||
|
|
||
|
class _KeyProto(object):
|
||
|
|
||
|
def __init__(self, id_):
|
||
|
self.path = [_PathElementProto(id_)]
|