Index: third_party/google-endpoints/test/test_check_request.py |
diff --git a/third_party/google-endpoints/test/test_check_request.py b/third_party/google-endpoints/test/test_check_request.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2b49ee7c71d4f2153c79fb5f7ea5ac4c19414966 |
--- /dev/null |
+++ b/third_party/google-endpoints/test/test_check_request.py |
@@ -0,0 +1,498 @@ |
+# Copyright 2016 Google Inc. All Rights Reserved. |
+# |
+# Licensed under the Apache License, Version 2.0 (the "License"); |
+# you may not use this file except in compliance with the License. |
+# You may obtain a copy of the License at |
+# |
+# http://www.apache.org/licenses/LICENSE-2.0 |
+# |
+# Unless required by applicable law or agreed to in writing, software |
+# distributed under the License is distributed on an "AS IS" BASIS, |
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
+# See the License for the specific language governing permissions and |
+# limitations under the License. |
+ |
+from __future__ import absolute_import |
+ |
+import datetime |
+import httplib |
+import unittest2 |
+from expects import be_none, equal, expect, raise_error |
+ |
+from apitools.base.py import encoding |
+ |
+from google.api.control import caches, label_descriptor, timestamp |
+from google.api.control import check_request, messages, metric_value |
+ |
+ |
+class TestSign(unittest2.TestCase): |
+ |
+ def setUp(self): |
+ op = messages.Operation( |
+ consumerId=_TEST_CONSUMER_ID, |
+ operationName=_TEST_OP_NAME |
+ ) |
+ self.test_check_request = messages.CheckRequest(operation=op) |
+ self.test_op = op |
+ |
+ def test_should_fail_if_operation_is_not_set(self): |
+ testf = lambda: check_request.sign(messages.CheckRequest()) |
+ expect(testf).to(raise_error(ValueError)) |
+ |
+ def test_should_fail_on_invalid_input(self): |
+ testf = lambda: check_request.sign(None) |
+ expect(testf).to(raise_error(ValueError)) |
+ testf = lambda: check_request.sign(object()) |
+ expect(testf).to(raise_error(ValueError)) |
+ |
+ def test_should_fail_if_operation_has_no_operation_name(self): |
+ op = messages.Operation(consumerId=_TEST_CONSUMER_ID) |
+ testf = lambda: check_request.sign(messages.CheckRequest(operation=op)) |
+ expect(testf).to(raise_error(ValueError)) |
+ |
+ def test_should_fail_if_operation_has_no_consumer_id(self): |
+ op = messages.Operation(operationName=_TEST_OP_NAME) |
+ testf = lambda: check_request.sign(messages.CheckRequest(operation=op)) |
+ expect(testf).to(raise_error(ValueError)) |
+ |
+ def test_should_sign_a_valid_check_request(self): |
+ check_request.sign(self.test_check_request) |
+ |
+ def test_should_change_signature_when_labels_are_added(self): |
+ without_labels = check_request.sign(self.test_check_request) |
+ self.test_op.labels = encoding.PyValueToMessage( |
+ messages.Operation.LabelsValue, { |
+ 'key1': 'value1', |
+ 'key2': 'value2' |
+ }) |
+ with_labels = check_request.sign(self.test_check_request) |
+ expect(with_labels).not_to(equal(without_labels)) |
+ |
+ def test_should_change_signature_when_metric_values_are_added(self): |
+ without_mvs = check_request.sign(self.test_check_request) |
+ self.test_op.metricValueSets = [ |
+ messages.MetricValueSet( |
+ metricName='a_float', |
+ metricValues=[ |
+ metric_value.create( |
+ labels={ |
+ 'key1': 'value1', |
+ 'key2': 'value2' |
+ }, |
+ doubleValue=1.1, |
+ ), |
+ ] |
+ ) |
+ ] |
+ with_mvs = check_request.sign(self.test_check_request) |
+ expect(with_mvs).not_to(equal(without_mvs)) |
+ |
+ def test_should_change_signature_quota_properties_are_specified(self): |
+ without_qprops = check_request.sign(self.test_check_request) |
+ self.test_op.quotaProperties = messages.QuotaProperties() |
+ with_qprops = check_request.sign(self.test_check_request) |
+ expect(with_qprops).not_to(equal(without_qprops)) |
+ |
+ |
+class TestAggregatorCheck(unittest2.TestCase): |
+ SERVICE_NAME = 'service.check' |
+ FAKE_OPERATION_ID = 'service.general.check' |
+ |
+ def setUp(self): |
+ self.timer = _DateTimeTimer() |
+ self.agg = check_request.Aggregator( |
+ self.SERVICE_NAME, caches.CheckOptions()) |
+ |
+ def test_should_fail_if_req_is_bad(self): |
+ testf = lambda: self.agg.check(object()) |
+ expect(testf).to(raise_error(ValueError)) |
+ testf = lambda: self.agg.check(None) |
+ expect(testf).to(raise_error(ValueError)) |
+ |
+ def test_should_fail_if_service_name_does_not_match(self): |
+ req = _make_test_request(self.SERVICE_NAME + '-will-not-match') |
+ testf = lambda: self.agg.check(req) |
+ expect(testf).to(raise_error(ValueError)) |
+ |
+ def test_should_fail_if_check_request_is_missing(self): |
+ req = messages.ServicecontrolServicesCheckRequest( |
+ serviceName=self.SERVICE_NAME) |
+ testf = lambda: self.agg.check(req) |
+ expect(testf).to(raise_error(ValueError)) |
+ |
+ def test_should_fail_if_operation_is_missing(self): |
+ req = messages.ServicecontrolServicesCheckRequest( |
+ serviceName=self.SERVICE_NAME, |
+ checkRequest=messages.CheckRequest()) |
+ testf = lambda: self.agg.check(req) |
+ expect(testf).to(raise_error(ValueError)) |
+ |
+ def test_should_return_none_initially_as_req_is_not_cached(self): |
+ req = _make_test_request(self.SERVICE_NAME) |
+ fake_response = messages.CheckResponse( |
+ operationId=self.FAKE_OPERATION_ID) |
+ agg = self.agg |
+ expect(agg.check(req)).to(be_none) |
+ |
+ |
+class TestAggregatorThatCannotCache(unittest2.TestCase): |
+ SERVICE_NAME = 'service.no_cache' |
+ FAKE_OPERATION_ID = 'service.no_cache.op_id' |
+ |
+ def setUp(self): |
+ # -ve num_entries means no cache is present |
+ self.agg = check_request.Aggregator( |
+ self.SERVICE_NAME, |
+ caches.CheckOptions(num_entries=-1)) |
+ |
+ def test_should_not_cache_responses(self): |
+ req = _make_test_request(self.SERVICE_NAME) |
+ fake_response = messages.CheckResponse( |
+ operationId=self.FAKE_OPERATION_ID) |
+ agg = self.agg |
+ expect(agg.check(req)).to(be_none) |
+ agg.add_response(req, fake_response) |
+ expect(agg.check(req)).to(be_none) |
+ agg.clear() |
+ expect(agg.check(req)).to(be_none) |
+ |
+ def test_should_have_empty_flush_response(self): |
+ expect(len(self.agg.flush())).to(equal(0)) |
+ |
+ def test_should_have_none_as_flush_interval(self): |
+ expect(self.agg.flush_interval).to(be_none) |
+ |
+ |
+ |
+class _DateTimeTimer(object): |
+ def __init__(self, auto=False): |
+ self.auto = auto |
+ self.time = datetime.datetime.utcfromtimestamp(0) |
+ |
+ def __call__(self): |
+ if self.auto: |
+ self.tick() |
+ return self.time |
+ |
+ def tick(self): |
+ self.time += datetime.timedelta(seconds=1) |
+ |
+ |
+class TestCachingAggregator(unittest2.TestCase): |
+ SERVICE_NAME = 'service.with_cache' |
+ FAKE_OPERATION_ID = 'service.with_cache.op_id' |
+ |
+ def setUp(self): |
+ self.timer = _DateTimeTimer() |
+ self.expiration = datetime.timedelta(seconds=2) |
+ options = caches.CheckOptions( |
+ flush_interval=datetime.timedelta(seconds=1), |
+ expiration=self.expiration) |
+ self.agg = check_request.Aggregator( |
+ self.SERVICE_NAME, options, timer=self.timer) |
+ |
+ def test_should_have_expiration_as_flush_interval(self): |
+ expect(self.agg.flush_interval).to(equal(self.expiration)) |
+ |
+ def test_should_cache_responses(self): |
+ req = _make_test_request(self.SERVICE_NAME) |
+ fake_response = messages.CheckResponse( |
+ operationId=self.FAKE_OPERATION_ID) |
+ agg = self.agg |
+ expect(agg.check(req)).to(be_none) |
+ agg.add_response(req, fake_response) |
+ expect(agg.check(req)).to(equal(fake_response)) |
+ |
+ def test_should_not_cache_requests_with_important_operations(self): |
+ req = _make_test_request( |
+ self.SERVICE_NAME, |
+ importance=messages.Operation.ImportanceValueValuesEnum.HIGH) |
+ fake_response = messages.CheckResponse( |
+ operationId=self.FAKE_OPERATION_ID) |
+ agg = self.agg |
+ expect(agg.check(req)).to(be_none) |
+ agg.add_response(req, fake_response) |
+ expect(agg.check(req)).to(be_none) |
+ |
+ def test_signals_a_resend_on_1st_call_after_flush_interval(self): |
+ req = _make_test_request(self.SERVICE_NAME) |
+ fake_response = messages.CheckResponse( |
+ operationId=self.FAKE_OPERATION_ID) |
+ agg = self.agg |
+ expect(agg.check(req)).to(be_none) |
+ agg.add_response(req, fake_response) |
+ expect(agg.check(req)).to(equal(fake_response)) |
+ |
+ # Now flush interval is reached, but not the response expiry |
+ self.timer.tick() # now past the flush_interval |
+ expect(agg.check(req)).to(be_none) # none signals the resend |
+ |
+ # Until expiry, the response will continue to be returned |
+ expect(agg.check(req)).to(equal(fake_response)) |
+ expect(agg.check(req)).to(equal(fake_response)) |
+ |
+ # Once expired the cached response is no longer returned |
+ # expire |
+ self.timer.tick() |
+ self.timer.tick() # now expired |
+ expect(agg.check(req)).to(be_none) |
+ expect(agg.check(req)).to(be_none) # 2nd check is None as well |
+ |
+ def test_signals_resend_on_1st_call_after_flush_interval_with_errors(self): |
+ req = _make_test_request(self.SERVICE_NAME) |
+ failure_code = messages.CheckError.CodeValueValuesEnum.NOT_FOUND |
+ fake_response = messages.CheckResponse( |
+ operationId=self.FAKE_OPERATION_ID, checkErrors=[ |
+ messages.CheckError(code=failure_code) |
+ ]) |
+ agg = self.agg |
+ expect(agg.check(req)).to(be_none) |
+ agg.add_response(req, fake_response) |
+ expect(agg.check(req)).to(equal(fake_response)) |
+ |
+ # Now flush interval is reached, but not the response expiry |
+ self.timer.tick() # now past the flush_interval |
+ expect(agg.check(req)).to(be_none) # first response is null |
+ |
+ # until expiry, the response will continue to be returned |
+ expect(agg.check(req)).to(equal(fake_response)) |
+ expect(agg.check(req)).to(equal(fake_response)) |
+ |
+ # expire |
+ self.timer.tick() |
+ self.timer.tick() # now expired |
+ expect(agg.check(req)).to(be_none) |
+ expect(agg.check(req)).to(be_none) # 2nd check is None as well |
+ |
+ def test_should_extend_expiration_on_receipt_of_a_response(self): |
+ req = _make_test_request(self.SERVICE_NAME) |
+ fake_response = messages.CheckResponse( |
+ operationId=self.FAKE_OPERATION_ID |
+ ) |
+ agg = self.agg |
+ expect(agg.check(req)).to(be_none) |
+ agg.add_response(req, fake_response) |
+ expect(agg.check(req)).to(equal(fake_response)) |
+ |
+ # Now flush interval is reached, but not the response expiry |
+ self.timer.tick() # now past the flush_interval |
+ expect(agg.check(req)).to(be_none) # first response is null |
+ |
+ # until expiry, the response will continue to be returned |
+ expect(agg.check(req)).to(equal(fake_response)) |
+ expect(agg.check(req)).to(equal(fake_response)) |
+ |
+ # add a response as the request expires |
+ self.timer.tick() |
+ agg.add_response(req, fake_response) |
+ # it would have expired, but because the response was added it does not |
+ expect(agg.check(req)).to(equal(fake_response)) |
+ expect(agg.check(req)).to(equal(fake_response)) |
+ self.timer.tick() # now past the flush interval again |
+ expect(agg.check(req)).to(be_none) |
+ expect(agg.check(req)).to(equal(fake_response)) |
+ |
+ def test_does_not_flush_request_that_has_not_been_updated(self): |
+ req = _make_test_request(self.SERVICE_NAME) |
+ fake_response = messages.CheckResponse( |
+ operationId=self.FAKE_OPERATION_ID |
+ ) |
+ agg = self.agg |
+ expect(agg.check(req)).to(be_none) |
+ agg.add_response(req, fake_response) |
+ self.timer.tick() # now past the flush_interval |
+ expect(len(agg.flush())).to(equal(0)) # nothing expired |
+ self.timer.tick() # now past expiry |
+ self.timer.tick() # now past expiry |
+ expect(agg.check(req)).to(be_none) # confirm nothing in cache |
+ expect(agg.check(req)).to(be_none) # confirm nothing in cache |
+ expect(len(agg.flush())).to(equal(0)) # no cached check request |
+ |
+ def test_does_flush_requests_that_have_been_updated(self): |
+ req = _make_test_request(self.SERVICE_NAME) |
+ fake_response = messages.CheckResponse( |
+ operationId=self.FAKE_OPERATION_ID |
+ ) |
+ agg = self.agg |
+ expect(agg.check(req)).to(be_none) |
+ agg.add_response(req, fake_response) |
+ expect(agg.check(req)).to(equal(fake_response)) |
+ self.timer.tick() # now past the flush_interval |
+ expect(len(agg.flush())).to(equal(0)) # nothing expired |
+ self.timer.tick() # now past expiry |
+ self.timer.tick() # now past expiry |
+ expect(len(agg.flush())).to(equal(1)) # got the cached check request |
+ |
+ def test_should_clear_requests(self): |
+ req = _make_test_request(self.SERVICE_NAME) |
+ fake_response = messages.CheckResponse( |
+ operationId=self.FAKE_OPERATION_ID |
+ ) |
+ agg = self.agg |
+ expect(agg.check(req)).to(be_none) |
+ agg.add_response(req, fake_response) |
+ expect(agg.check(req)).to(equal(fake_response)) |
+ agg.clear() |
+ expect(agg.check(req)).to(be_none) |
+ expect(len(agg.flush())).to(equal(0)) |
+ |
+ |
+_TEST_CONSUMER_ID = 'testConsumerID' |
+_TEST_OP_NAME = 'testOperationName' |
+ |
+ |
+def _make_test_request(service_name, importance=None): |
+ if importance is None: |
+ importance = messages.Operation.ImportanceValueValuesEnum.LOW |
+ op = messages.Operation( |
+ consumerId=_TEST_CONSUMER_ID, |
+ operationName=_TEST_OP_NAME, |
+ importance=importance |
+ ) |
+ check_request = messages.CheckRequest(operation=op) |
+ return messages.ServicecontrolServicesCheckRequest( |
+ serviceName=service_name, |
+ checkRequest=check_request) |
+ |
+ |
+_WANTED_USER_AGENT = label_descriptor.USER_AGENT |
+_START_OF_EPOCH = timestamp.to_rfc3339(datetime.datetime(1970, 1, 1, 0, 0, 0)) |
+_TEST_SERVICE_NAME = 'a_service_name' |
+_INFO_TESTS = [ |
+ (check_request.Info( |
+ operation_id='an_op_id', |
+ operation_name='an_op_name', |
+ referer='a_referer', |
+ service_name=_TEST_SERVICE_NAME), |
+ messages.Operation( |
+ importance=messages.Operation.ImportanceValueValuesEnum.LOW, |
+ labels = encoding.PyValueToMessage( |
+ messages.Operation.LabelsValue, { |
+ 'servicecontrol.googleapis.com/user_agent': _WANTED_USER_AGENT, |
+ 'servicecontrol.googleapis.com/referer': 'a_referer' |
+ }), |
+ operationId='an_op_id', |
+ operationName='an_op_name', |
+ startTime=_START_OF_EPOCH, |
+ endTime=_START_OF_EPOCH)), |
+ (check_request.Info( |
+ api_key='an_api_key', |
+ api_key_valid=True, |
+ operation_id='an_op_id', |
+ operation_name='an_op_name', |
+ referer='a_referer', |
+ service_name=_TEST_SERVICE_NAME), |
+ messages.Operation( |
+ importance=messages.Operation.ImportanceValueValuesEnum.LOW, |
+ consumerId='api_key:an_api_key', |
+ labels = encoding.PyValueToMessage( |
+ messages.Operation.LabelsValue, { |
+ 'servicecontrol.googleapis.com/user_agent': _WANTED_USER_AGENT, |
+ 'servicecontrol.googleapis.com/referer': 'a_referer' |
+ }), |
+ operationId='an_op_id', |
+ operationName='an_op_name', |
+ startTime=_START_OF_EPOCH, |
+ endTime=_START_OF_EPOCH)), |
+ (check_request.Info( |
+ api_key='an_api_key', |
+ api_key_valid=False, |
+ client_ip='127.0.0.1', |
+ consumer_project_id='project_id', |
+ operation_id='an_op_id', |
+ operation_name='an_op_name', |
+ referer='a_referer', |
+ service_name=_TEST_SERVICE_NAME), |
+ messages.Operation( |
+ importance=messages.Operation.ImportanceValueValuesEnum.LOW, |
+ consumerId='project:project_id', |
+ labels = encoding.PyValueToMessage( |
+ messages.Operation.LabelsValue, { |
+ 'servicecontrol.googleapis.com/caller_ip': '127.0.0.1', |
+ 'servicecontrol.googleapis.com/user_agent': _WANTED_USER_AGENT, |
+ 'servicecontrol.googleapis.com/referer': 'a_referer' |
+ }), |
+ operationId='an_op_id', |
+ operationName='an_op_name', |
+ startTime=_START_OF_EPOCH, |
+ endTime=_START_OF_EPOCH)), |
+] |
+_INCOMPLETE_INFO_TESTS = [ |
+ check_request.Info( |
+ operation_name='an_op_name', |
+ service_name=_TEST_SERVICE_NAME), |
+ check_request.Info( |
+ operation_id='an_op_id', |
+ service_name=_TEST_SERVICE_NAME), |
+ check_request.Info( |
+ operation_id='an_op_id', |
+ operation_name='an_op_name') |
+] |
+ |
+ |
+class TestInfo(unittest2.TestCase): |
+ |
+ def test_should_construct_with_no_args(self): |
+ expect(check_request.Info()).not_to(be_none) |
+ |
+ def test_should_convert_using_as_check_request(self): |
+ timer = _DateTimeTimer() |
+ for info, want in _INFO_TESTS: |
+ got = info.as_check_request(timer=timer) |
+ expect(got.checkRequest.operation).to(equal(want)) |
+ expect(got.serviceName).to(equal(_TEST_SERVICE_NAME)) |
+ |
+ def test_should_fail_as_check_request_on_incomplete_info(self): |
+ timer = _DateTimeTimer() |
+ for info in _INCOMPLETE_INFO_TESTS: |
+ testf = lambda: info.as_check_request(timer=timer) |
+ expect(testf).to(raise_error(ValueError)) |
+ |
+ |
+class TestConvertResponse(unittest2.TestCase): |
+ PROJECT_ID = 'test_convert_response' |
+ |
+ def test_should_be_ok_with_no_errors(self): |
+ code, message, _ = check_request.convert_response( |
+ messages.CheckResponse(), self.PROJECT_ID) |
+ expect(code).to(equal(httplib.OK)) |
+ expect(message).to(equal('')) |
+ |
+ def test_should_include_project_id_in_error_text_when_needed(self): |
+ resp = messages.CheckResponse( |
+ checkErrors = [ |
+ messages.CheckError( |
+ code=messages.CheckError.CodeValueValuesEnum.PROJECT_DELETED) |
+ ] |
+ ) |
+ code, got, _ = check_request.convert_response(resp, self.PROJECT_ID) |
+ want = 'Project %s has been deleted' % (self.PROJECT_ID,) |
+ expect(code).to(equal(httplib.FORBIDDEN)) |
+ expect(got).to(equal(want)) |
+ |
+ def test_should_include_detail_in_error_text_when_needed(self): |
+ detail = 'details, details, details' |
+ resp = messages.CheckResponse( |
+ checkErrors = [ |
+ messages.CheckError( |
+ code=messages.CheckError.CodeValueValuesEnum.IP_ADDRESS_BLOCKED, |
+ detail=detail) |
+ ] |
+ ) |
+ code, got, _ = check_request.convert_response(resp, self.PROJECT_ID) |
+ expect(code).to(equal(httplib.FORBIDDEN)) |
+ expect(got).to(equal(detail)) |
+ |
+ |
+class _DateTimeTimer(object): |
+ def __init__(self, auto=False): |
+ self.auto = auto |
+ self.time = datetime.datetime(1970, 1, 1) |
+ |
+ def __call__(self): |
+ if self.auto: |
+ self.tick() |
+ return self.time |
+ |
+ def tick(self): |
+ self.time += datetime.timedelta(seconds=1) |