# 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.

import unittest2


try:
    # pylint: disable=unused-import
    import gcloud.pubsub._gax
    # pylint: enable=unused-import
except ImportError:  # pragma: NO COVER
    _HAVE_GAX = False
else:
    _HAVE_GAX = True


class _Base(object):
    PROJECT = 'PROJECT'
    PROJECT_PATH = 'projects/%s' % (PROJECT,)
    FILTER = 'logName:syslog AND severity>=ERROR'

    def _makeOne(self, *args, **kw):
        return self._getTargetClass()(*args, **kw)


@unittest2.skipUnless(_HAVE_GAX, 'No gax-python')
class Test_LoggingAPI(_Base, unittest2.TestCase):
    LOG_NAME = 'log_name'

    def _getTargetClass(self):
        from gcloud.logging._gax import _LoggingAPI
        return _LoggingAPI

    def test_ctor(self):
        gax_api = _GAXLoggingAPI()
        api = self._makeOne(gax_api)
        self.assertTrue(api._gax_api is gax_api)

    def test_list_entries_no_paging(self):
        from google.gax import INITIAL_PAGE
        from gcloud.logging import DESCENDING
        from gcloud._testing import _GAXPageIterator
        TOKEN = 'TOKEN'
        TEXT = 'TEXT'
        response = _GAXPageIterator(
            [_LogEntryPB(self.LOG_NAME, text_payload=TEXT)], TOKEN)
        gax_api = _GAXLoggingAPI(_list_log_entries_response=response)
        api = self._makeOne(gax_api)

        entries, next_token = api.list_entries(
            [self.PROJECT], self.FILTER, DESCENDING)

        self.assertEqual(len(entries), 1)
        entry = entries[0]
        self.assertIsInstance(entry, dict)
        self.assertEqual(entry['logName'], self.LOG_NAME)
        self.assertEqual(entry['resource'], {'type': 'global'})
        self.assertEqual(entry['textPayload'], TEXT)
        self.assertEqual(next_token, TOKEN)

        projects, filter_, order_by, page_size, options = (
            gax_api._list_log_entries_called_with)
        self.assertEqual(projects, [self.PROJECT])
        self.assertEqual(filter_, self.FILTER)
        self.assertEqual(order_by, DESCENDING)
        self.assertEqual(page_size, 0)
        self.assertTrue(options.page_token is INITIAL_PAGE)

    def test_list_entries_with_paging(self):
        from gcloud._testing import _GAXPageIterator
        SIZE = 23
        TOKEN = 'TOKEN'
        NEW_TOKEN = 'NEW_TOKEN'
        PAYLOAD = {'message': 'MESSAGE', 'weather': 'sunny'}
        response = _GAXPageIterator(
            [_LogEntryPB(self.LOG_NAME, json_payload=PAYLOAD)], NEW_TOKEN)
        gax_api = _GAXLoggingAPI(_list_log_entries_response=response)
        api = self._makeOne(gax_api)

        entries, next_token = api.list_entries(
            [self.PROJECT], page_size=SIZE, page_token=TOKEN)

        self.assertEqual(len(entries), 1)
        entry = entries[0]
        self.assertIsInstance(entry, dict)
        self.assertEqual(entry['logName'], self.LOG_NAME)
        self.assertEqual(entry['resource'], {'type': 'global'})
        self.assertEqual(entry['jsonPayload'], PAYLOAD)
        self.assertEqual(next_token, NEW_TOKEN)

        projects, filter_, order_by, page_size, options = (
            gax_api._list_log_entries_called_with)
        self.assertEqual(projects, [self.PROJECT])
        self.assertEqual(filter_, '')
        self.assertEqual(order_by, '')
        self.assertEqual(page_size, SIZE)
        self.assertEqual(options.page_token, TOKEN)

    def test_list_entries_with_extra_properties(self):
        from datetime import datetime
        from gcloud._testing import _GAXPageIterator
        from gcloud._helpers import UTC
        from gcloud._helpers import _datetime_to_rfc3339
        from gcloud._helpers import _datetime_to_pb_timestamp
        NOW = datetime.utcnow().replace(tzinfo=UTC)
        SIZE = 23
        TOKEN = 'TOKEN'
        NEW_TOKEN = 'NEW_TOKEN'
        PAYLOAD = {'message': 'MESSAGE', 'weather': 'sunny'}
        SEVERITY = 'WARNING'
        LABELS = {
            'foo': 'bar',
        }
        IID = 'IID'
        request = _HTTPRequestPB()
        operation = _LogEntryOperationPB()
        EXTRAS = {
            'severity': SEVERITY,
            'labels': LABELS,
            'insert_id': IID,
            'http_request': request,
            'operation': operation,
        }
        ENTRY = _LogEntryPB(self.LOG_NAME, proto_payload=PAYLOAD, **EXTRAS)
        ENTRY.resource.labels['foo'] = 'bar'
        ENTRY.timestamp = _datetime_to_pb_timestamp(NOW)
        response = _GAXPageIterator([ENTRY], NEW_TOKEN)
        gax_api = _GAXLoggingAPI(_list_log_entries_response=response)
        api = self._makeOne(gax_api)

        entries, next_token = api.list_entries(
            [self.PROJECT], page_size=SIZE, page_token=TOKEN)

        self.assertEqual(len(entries), 1)
        entry = entries[0]
        self.assertIsInstance(entry, dict)
        self.assertEqual(entry['logName'], self.LOG_NAME)
        self.assertEqual(entry['resource'],
                         {'type': 'global', 'labels': {'foo': 'bar'}})
        self.assertEqual(entry['protoPayload'], PAYLOAD)
        self.assertEqual(entry['severity'], SEVERITY)
        self.assertEqual(entry['labels'], LABELS)
        self.assertEqual(entry['insertId'], IID)
        self.assertEqual(entry['timestamp'], _datetime_to_rfc3339(NOW))
        EXPECTED_REQUEST = {
            'request_method': request.request_method,
            'request_url': request.request_url,
            'status': request.status,
            'request_size': request.request_size,
            'response_size': request.response_size,
            'referer': request.referer,
            'user_agent': request.user_agent,
            'remote_ip': request.remote_ip,
            'cache_hit': request.cache_hit,
        }
        self.assertEqual(entry['httpRequest'], EXPECTED_REQUEST)
        EXPECTED_OPERATION = {
            'producer': operation.producer,
            'id': operation.id,
            'first': operation.first,
            'last': operation.last,
        }
        self.assertEqual(entry['operation'], EXPECTED_OPERATION)
        self.assertEqual(next_token, NEW_TOKEN)

        projects, filter_, order_by, page_size, options = (
            gax_api._list_log_entries_called_with)
        self.assertEqual(projects, [self.PROJECT])
        self.assertEqual(filter_, '')
        self.assertEqual(order_by, '')
        self.assertEqual(page_size, SIZE)
        self.assertEqual(options.page_token, TOKEN)

    def test_write_entries_single(self):
        from google.logging.v2.log_entry_pb2 import LogEntry
        TEXT = 'TEXT'
        LOG_PATH = 'projects/%s/logs/%s' % (self.PROJECT, self.LOG_NAME)
        ENTRY = {
            'logName': LOG_PATH,
            'resource': {'type': 'global'},
            'textPayload': TEXT,
        }
        gax_api = _GAXLoggingAPI()
        api = self._makeOne(gax_api)

        api.write_entries([ENTRY])

        entries, log_name, resource, labels, partial_success, options = (
            gax_api._write_log_entries_called_with)
        self.assertEqual(len(entries), 1)

        entry = entries[0]
        self.assertTrue(isinstance(entry, LogEntry))
        self.assertEqual(entry.log_name, LOG_PATH)
        self.assertEqual(entry.resource.type, 'global')
        self.assertEqual(entry.labels, {})
        self.assertEqual(entry.text_payload, TEXT)

        self.assertEqual(log_name, None)
        self.assertEqual(resource, None)
        self.assertEqual(labels, None)
        self.assertEqual(partial_success, False)
        self.assertEqual(options, None)

    def test_write_entries_w_extra_properties(self):
        # pylint: disable=too-many-statements
        from datetime import datetime
        from google.logging.type.log_severity_pb2 import WARNING
        from google.logging.v2.log_entry_pb2 import LogEntry
        from gcloud._helpers import UTC, _pb_timestamp_to_datetime
        NOW = datetime.utcnow().replace(tzinfo=UTC)
        TEXT = 'TEXT'
        LOG_PATH = 'projects/%s/logs/%s' % (self.PROJECT, self.LOG_NAME)
        SEVERITY = 'WARNING'
        LABELS = {
            'foo': 'bar',
        }
        IID = 'IID'
        REQUEST_METHOD = 'GET'
        REQUEST_URL = 'http://example.com/requested'
        STATUS = 200
        REQUEST_SIZE = 256
        RESPONSE_SIZE = 1024
        REFERRER_URL = 'http://example.com/referer'
        USER_AGENT = 'Agent/1.0'
        REMOTE_IP = '1.2.3.4'
        REQUEST = {
            'requestMethod': REQUEST_METHOD,
            'requestUrl': REQUEST_URL,
            'status': STATUS,
            'requestSize': REQUEST_SIZE,
            'responseSize': RESPONSE_SIZE,
            'referer': REFERRER_URL,
            'userAgent': USER_AGENT,
            'remoteIp': REMOTE_IP,
            'cacheHit': False,
        }
        PRODUCER = 'PRODUCER'
        OPID = 'OPID'
        OPERATION = {
            'producer': PRODUCER,
            'id': OPID,
            'first': False,
            'last': True,
        }
        ENTRY = {
            'logName': LOG_PATH,
            'resource': {'type': 'global'},
            'textPayload': TEXT,
            'severity': SEVERITY,
            'labels': LABELS,
            'insertId': IID,
            'timestamp': NOW,
            'httpRequest': REQUEST,
            'operation': OPERATION,
        }
        gax_api = _GAXLoggingAPI()
        api = self._makeOne(gax_api)

        api.write_entries([ENTRY])

        entries, log_name, resource, labels, partial_success, options = (
            gax_api._write_log_entries_called_with)
        self.assertEqual(len(entries), 1)

        entry = entries[0]
        self.assertTrue(isinstance(entry, LogEntry))
        self.assertEqual(entry.log_name, LOG_PATH)
        self.assertEqual(entry.resource.type, 'global')
        self.assertEqual(entry.text_payload, TEXT)
        self.assertEqual(entry.severity, WARNING)
        self.assertEqual(entry.labels, LABELS)
        self.assertEqual(entry.insert_id, IID)
        stamp = _pb_timestamp_to_datetime(entry.timestamp)
        self.assertEqual(stamp, NOW)

        request = entry.http_request
        self.assertEqual(request.request_method, REQUEST_METHOD)
        self.assertEqual(request.request_url, REQUEST_URL)
        self.assertEqual(request.status, STATUS)
        self.assertEqual(request.request_size, REQUEST_SIZE)
        self.assertEqual(request.response_size, RESPONSE_SIZE)
        self.assertEqual(request.referer, REFERRER_URL)
        self.assertEqual(request.user_agent, USER_AGENT)
        self.assertEqual(request.remote_ip, REMOTE_IP)
        self.assertEqual(request.cache_hit, False)

        operation = entry.operation
        self.assertEqual(operation.producer, PRODUCER)
        self.assertEqual(operation.id, OPID)
        self.assertFalse(operation.first)
        self.assertTrue(operation.last)

        self.assertEqual(log_name, None)
        self.assertEqual(resource, None)
        self.assertEqual(labels, None)
        self.assertEqual(partial_success, False)
        self.assertEqual(options, None)
        # pylint: enable=too-many-statements

    def test_write_entries_multiple(self):
        # pylint: disable=too-many-statements
        import datetime
        from google.logging.type.log_severity_pb2 import WARNING
        from google.logging.v2.log_entry_pb2 import LogEntry
        from google.protobuf.any_pb2 import Any
        from google.protobuf.struct_pb2 import Struct
        from gcloud._helpers import _datetime_to_rfc3339, UTC
        TEXT = 'TEXT'
        NOW = datetime.datetime.utcnow().replace(tzinfo=UTC)
        TIMESTAMP_TYPE_URL = 'type.googleapis.com/google.protobuf.Timestamp'
        JSON = {'payload': 'PAYLOAD', 'type': 'json'}
        PROTO = {
            '@type': TIMESTAMP_TYPE_URL,
            'value': _datetime_to_rfc3339(NOW),
        }
        PRODUCER = 'PRODUCER'
        OPID = 'OPID'
        URL = 'http://example.com/'
        ENTRIES = [
            {'textPayload': TEXT,
             'severity': WARNING},
            {'jsonPayload': JSON,
             'operation': {'producer': PRODUCER, 'id': OPID}},
            {'protoPayload': PROTO,
             'httpRequest': {'requestUrl': URL}},
        ]
        LOG_PATH = 'projects/%s/logs/%s' % (self.PROJECT, self.LOG_NAME)
        RESOURCE = {
            'type': 'global',
        }
        LABELS = {
            'foo': 'bar',
        }
        gax_api = _GAXLoggingAPI()
        api = self._makeOne(gax_api)

        api.write_entries(ENTRIES, LOG_PATH, RESOURCE, LABELS)

        entries, log_name, resource, labels, partial_success, options = (
            gax_api._write_log_entries_called_with)
        self.assertEqual(len(entries), len(ENTRIES))

        entry = entries[0]
        self.assertTrue(isinstance(entry, LogEntry))
        self.assertEqual(entry.log_name, '')
        self.assertEqual(entry.resource.type, '')
        self.assertEqual(entry.labels, {})
        self.assertEqual(entry.text_payload, TEXT)
        self.assertEqual(entry.severity, WARNING)

        entry = entries[1]
        self.assertTrue(isinstance(entry, LogEntry))
        self.assertEqual(entry.log_name, '')
        self.assertEqual(entry.resource.type, '')
        self.assertEqual(entry.labels, {})
        json_struct = entry.json_payload
        self.assertTrue(isinstance(json_struct, Struct))
        self.assertEqual(json_struct.fields['payload'].string_value,
                         JSON['payload'])
        operation = entry.operation
        self.assertEqual(operation.producer, PRODUCER)
        self.assertEqual(operation.id, OPID)

        entry = entries[2]
        self.assertTrue(isinstance(entry, LogEntry))
        self.assertEqual(entry.log_name, '')
        self.assertEqual(entry.resource.type, '')
        self.assertEqual(entry.labels, {})
        proto = entry.proto_payload
        self.assertTrue(isinstance(proto, Any))
        self.assertEqual(proto.type_url, TIMESTAMP_TYPE_URL)
        request = entry.http_request
        self.assertEqual(request.request_url, URL)

        self.assertEqual(log_name, LOG_PATH)
        self.assertEqual(resource, RESOURCE)
        self.assertEqual(labels, LABELS)
        self.assertEqual(partial_success, False)
        self.assertEqual(options, None)
        # pylint: enable=too-many-statements

    def test_logger_delete(self):
        LOG_PATH = 'projects/%s/logs/%s' % (self.PROJECT, self.LOG_NAME)
        gax_api = _GAXLoggingAPI()
        api = self._makeOne(gax_api)

        api.logger_delete(self.PROJECT, self.LOG_NAME)

        log_name, options = gax_api._delete_log_called_with
        self.assertEqual(log_name, LOG_PATH)
        self.assertEqual(options, None)


@unittest2.skipUnless(_HAVE_GAX, 'No gax-python')
class Test_SinksAPI(_Base, unittest2.TestCase):
    SINK_NAME = 'sink_name'
    SINK_PATH = 'projects/%s/sinks/%s' % (_Base.PROJECT, SINK_NAME)
    DESTINATION_URI = 'faux.googleapis.com/destination'

    def _getTargetClass(self):
        from gcloud.logging._gax import _SinksAPI
        return _SinksAPI

    def test_ctor(self):
        gax_api = _GAXSinksAPI()
        api = self._makeOne(gax_api)
        self.assertTrue(api._gax_api is gax_api)

    def test_list_sinks_no_paging(self):
        from google.gax import INITIAL_PAGE
        from gcloud._testing import _GAXPageIterator
        TOKEN = 'TOKEN'
        SINKS = [{
            'name': self.SINK_PATH,
            'filter': self.FILTER,
            'destination': self.DESTINATION_URI,
        }]
        response = _GAXPageIterator(
            [_LogSinkPB(self.SINK_PATH, self.DESTINATION_URI, self.FILTER)],
            TOKEN)
        gax_api = _GAXSinksAPI(_list_sinks_response=response)
        api = self._makeOne(gax_api)

        sinks, token = api.list_sinks(self.PROJECT)

        self.assertEqual(sinks, SINKS)
        self.assertEqual(token, TOKEN)

        project, page_size, options = gax_api._list_sinks_called_with
        self.assertEqual(project, self.PROJECT)
        self.assertEqual(page_size, 0)
        self.assertEqual(options.page_token, INITIAL_PAGE)

    def test_list_sinks_w_paging(self):
        from gcloud._testing import _GAXPageIterator
        TOKEN = 'TOKEN'
        PAGE_SIZE = 42
        SINKS = [{
            'name': self.SINK_PATH,
            'filter': self.FILTER,
            'destination': self.DESTINATION_URI,
        }]
        response = _GAXPageIterator(
            [_LogSinkPB(self.SINK_PATH, self.DESTINATION_URI, self.FILTER)],
            None)
        gax_api = _GAXSinksAPI(_list_sinks_response=response)
        api = self._makeOne(gax_api)

        sinks, token = api.list_sinks(
            self.PROJECT, page_size=PAGE_SIZE, page_token=TOKEN)

        self.assertEqual(sinks, SINKS)
        self.assertEqual(token, None)

        project, page_size, options = gax_api._list_sinks_called_with
        self.assertEqual(project, self.PROJECT)
        self.assertEqual(page_size, PAGE_SIZE)
        self.assertEqual(options.page_token, TOKEN)

    def test_sink_create_error(self):
        from google.gax.errors import GaxError
        gax_api = _GAXSinksAPI(_random_gax_error=True)
        api = self._makeOne(gax_api)

        with self.assertRaises(GaxError):
            api.sink_create(
                self.PROJECT, self.SINK_NAME, self.FILTER,
                self.DESTINATION_URI)

    def test_sink_create_conflict(self):
        from gcloud.exceptions import Conflict
        gax_api = _GAXSinksAPI(_create_sink_conflict=True)
        api = self._makeOne(gax_api)

        with self.assertRaises(Conflict):
            api.sink_create(
                self.PROJECT, self.SINK_NAME, self.FILTER,
                self.DESTINATION_URI)

    def test_sink_create_ok(self):
        from google.logging.v2.logging_config_pb2 import LogSink
        gax_api = _GAXSinksAPI()
        api = self._makeOne(gax_api)

        api.sink_create(
            self.PROJECT, self.SINK_NAME, self.FILTER, self.DESTINATION_URI)

        parent, sink, options = (
            gax_api._create_sink_called_with)
        self.assertEqual(parent, self.PROJECT_PATH)
        self.assertTrue(isinstance(sink, LogSink))
        self.assertEqual(sink.name, self.SINK_NAME)
        self.assertEqual(sink.filter, self.FILTER)
        self.assertEqual(sink.destination, self.DESTINATION_URI)
        self.assertEqual(options, None)

    def test_sink_get_error(self):
        from gcloud.exceptions import NotFound
        gax_api = _GAXSinksAPI()
        api = self._makeOne(gax_api)

        with self.assertRaises(NotFound):
            api.sink_get(self.PROJECT, self.SINK_NAME)

    def test_sink_get_miss(self):
        from google.gax.errors import GaxError
        gax_api = _GAXSinksAPI(_random_gax_error=True)
        api = self._makeOne(gax_api)

        with self.assertRaises(GaxError):
            api.sink_get(self.PROJECT, self.SINK_NAME)

    def test_sink_get_hit(self):
        RESPONSE = {
            'name': self.SINK_PATH,
            'filter': self.FILTER,
            'destination': self.DESTINATION_URI,
        }
        sink_pb = _LogSinkPB(
            self.SINK_PATH, self.DESTINATION_URI, self.FILTER)
        gax_api = _GAXSinksAPI(_get_sink_response=sink_pb)
        api = self._makeOne(gax_api)

        response = api.sink_get(self.PROJECT, self.SINK_NAME)

        self.assertEqual(response, RESPONSE)

        sink_name, options = gax_api._get_sink_called_with
        self.assertEqual(sink_name, self.SINK_PATH)
        self.assertEqual(options, None)

    def test_sink_update_error(self):
        from google.gax.errors import GaxError
        gax_api = _GAXSinksAPI(_random_gax_error=True)
        api = self._makeOne(gax_api)

        with self.assertRaises(GaxError):
            api.sink_update(
                self.PROJECT, self.SINK_NAME, self.FILTER,
                self.DESTINATION_URI)

    def test_sink_update_miss(self):
        from gcloud.exceptions import NotFound
        gax_api = _GAXSinksAPI()
        api = self._makeOne(gax_api)

        with self.assertRaises(NotFound):
            api.sink_update(
                self.PROJECT, self.SINK_NAME, self.FILTER,
                self.DESTINATION_URI)

    def test_sink_update_hit(self):
        from google.logging.v2.logging_config_pb2 import LogSink
        response = _LogSinkPB(
            self.SINK_NAME, self.FILTER, self.DESTINATION_URI)
        gax_api = _GAXSinksAPI(_update_sink_response=response)
        api = self._makeOne(gax_api)

        api.sink_update(
            self.PROJECT, self.SINK_NAME, self.FILTER, self.DESTINATION_URI)

        sink_name, sink, options = (
            gax_api._update_sink_called_with)
        self.assertEqual(sink_name, self.SINK_PATH)
        self.assertTrue(isinstance(sink, LogSink))
        self.assertEqual(sink.name, self.SINK_PATH)
        self.assertEqual(sink.filter, self.FILTER)
        self.assertEqual(sink.destination, self.DESTINATION_URI)
        self.assertEqual(options, None)

    def test_sink_delete_error(self):
        from google.gax.errors import GaxError
        gax_api = _GAXSinksAPI(_random_gax_error=True)
        api = self._makeOne(gax_api)

        with self.assertRaises(GaxError):
            api.sink_delete(self.PROJECT, self.SINK_NAME)

    def test_sink_delete_miss(self):
        from gcloud.exceptions import NotFound
        gax_api = _GAXSinksAPI(_sink_not_found=True)
        api = self._makeOne(gax_api)

        with self.assertRaises(NotFound):
            api.sink_delete(self.PROJECT, self.SINK_NAME)

    def test_sink_delete_hit(self):
        gax_api = _GAXSinksAPI()
        api = self._makeOne(gax_api)

        api.sink_delete(self.PROJECT, self.SINK_NAME)

        sink_name, options = gax_api._delete_sink_called_with
        self.assertEqual(sink_name, self.SINK_PATH)
        self.assertEqual(options, None)


@unittest2.skipUnless(_HAVE_GAX, 'No gax-python')
class Test_MetricsAPI(_Base, unittest2.TestCase):
    METRIC_NAME = 'metric_name'
    METRIC_PATH = 'projects/%s/metrics/%s' % (_Base.PROJECT, METRIC_NAME)
    DESCRIPTION = 'Description'

    def _getTargetClass(self):
        from gcloud.logging._gax import _MetricsAPI
        return _MetricsAPI

    def test_ctor(self):
        gax_api = _GAXMetricsAPI()
        api = self._makeOne(gax_api)
        self.assertTrue(api._gax_api is gax_api)

    def test_list_metrics_no_paging(self):
        from google.gax import INITIAL_PAGE
        from gcloud._testing import _GAXPageIterator
        TOKEN = 'TOKEN'
        METRICS = [{
            'name': self.METRIC_PATH,
            'filter': self.FILTER,
            'description': self.DESCRIPTION,
        }]
        response = _GAXPageIterator(
            [_LogMetricPB(self.METRIC_PATH, self.DESCRIPTION, self.FILTER)],
            TOKEN)
        gax_api = _GAXMetricsAPI(_list_log_metrics_response=response)
        api = self._makeOne(gax_api)

        metrics, token = api.list_metrics(self.PROJECT)

        self.assertEqual(metrics, METRICS)
        self.assertEqual(token, TOKEN)

        project, page_size, options = gax_api._list_log_metrics_called_with
        self.assertEqual(project, self.PROJECT)
        self.assertEqual(page_size, 0)
        self.assertEqual(options.page_token, INITIAL_PAGE)

    def test_list_metrics_w_paging(self):
        from gcloud._testing import _GAXPageIterator
        TOKEN = 'TOKEN'
        PAGE_SIZE = 42
        METRICS = [{
            'name': self.METRIC_PATH,
            'filter': self.FILTER,
            'description': self.DESCRIPTION,
        }]
        response = _GAXPageIterator(
            [_LogMetricPB(self.METRIC_PATH, self.DESCRIPTION, self.FILTER)],
            None)
        gax_api = _GAXMetricsAPI(_list_log_metrics_response=response)
        api = self._makeOne(gax_api)

        metrics, token = api.list_metrics(
            self.PROJECT, page_size=PAGE_SIZE, page_token=TOKEN)

        self.assertEqual(metrics, METRICS)
        self.assertEqual(token, None)

        project, page_size, options = gax_api._list_log_metrics_called_with
        self.assertEqual(project, self.PROJECT)
        self.assertEqual(page_size, PAGE_SIZE)
        self.assertEqual(options.page_token, TOKEN)

    def test_metric_create_error(self):
        from google.gax.errors import GaxError
        gax_api = _GAXMetricsAPI(_random_gax_error=True)
        api = self._makeOne(gax_api)

        with self.assertRaises(GaxError):
            api.metric_create(
                self.PROJECT, self.METRIC_NAME, self.FILTER,
                self.DESCRIPTION)

    def test_metric_create_conflict(self):
        from gcloud.exceptions import Conflict
        gax_api = _GAXMetricsAPI(_create_log_metric_conflict=True)
        api = self._makeOne(gax_api)

        with self.assertRaises(Conflict):
            api.metric_create(
                self.PROJECT, self.METRIC_NAME, self.FILTER,
                self.DESCRIPTION)

    def test_metric_create_ok(self):
        from google.logging.v2.logging_metrics_pb2 import LogMetric
        gax_api = _GAXMetricsAPI()
        api = self._makeOne(gax_api)

        api.metric_create(
            self.PROJECT, self.METRIC_NAME, self.FILTER, self.DESCRIPTION)

        parent, metric, options = (
            gax_api._create_log_metric_called_with)
        self.assertEqual(parent, self.PROJECT_PATH)
        self.assertTrue(isinstance(metric, LogMetric))
        self.assertEqual(metric.name, self.METRIC_NAME)
        self.assertEqual(metric.filter, self.FILTER)
        self.assertEqual(metric.description, self.DESCRIPTION)
        self.assertEqual(options, None)

    def test_metric_get_error(self):
        from gcloud.exceptions import NotFound
        gax_api = _GAXMetricsAPI()
        api = self._makeOne(gax_api)

        with self.assertRaises(NotFound):
            api.metric_get(self.PROJECT, self.METRIC_NAME)

    def test_metric_get_miss(self):
        from google.gax.errors import GaxError
        gax_api = _GAXMetricsAPI(_random_gax_error=True)
        api = self._makeOne(gax_api)

        with self.assertRaises(GaxError):
            api.metric_get(self.PROJECT, self.METRIC_NAME)

    def test_metric_get_hit(self):
        RESPONSE = {
            'name': self.METRIC_PATH,
            'filter': self.FILTER,
            'description': self.DESCRIPTION,
        }
        metric_pb = _LogMetricPB(
            self.METRIC_PATH, self.DESCRIPTION, self.FILTER)
        gax_api = _GAXMetricsAPI(_get_log_metric_response=metric_pb)
        api = self._makeOne(gax_api)

        response = api.metric_get(self.PROJECT, self.METRIC_NAME)

        self.assertEqual(response, RESPONSE)

        metric_name, options = gax_api._get_log_metric_called_with
        self.assertEqual(metric_name, self.METRIC_PATH)
        self.assertEqual(options, None)

    def test_metric_update_error(self):
        from google.gax.errors import GaxError
        gax_api = _GAXMetricsAPI(_random_gax_error=True)
        api = self._makeOne(gax_api)

        with self.assertRaises(GaxError):
            api.metric_update(
                self.PROJECT, self.METRIC_NAME, self.FILTER,
                self.DESCRIPTION)

    def test_metric_update_miss(self):
        from gcloud.exceptions import NotFound
        gax_api = _GAXMetricsAPI()
        api = self._makeOne(gax_api)

        with self.assertRaises(NotFound):
            api.metric_update(
                self.PROJECT, self.METRIC_NAME, self.FILTER,
                self.DESCRIPTION)

    def test_metric_update_hit(self):
        from google.logging.v2.logging_metrics_pb2 import LogMetric
        response = _LogMetricPB(
            self.METRIC_NAME, self.FILTER, self.DESCRIPTION)
        gax_api = _GAXMetricsAPI(_update_log_metric_response=response)
        api = self._makeOne(gax_api)

        api.metric_update(
            self.PROJECT, self.METRIC_NAME, self.FILTER, self.DESCRIPTION)

        metric_name, metric, options = (
            gax_api._update_log_metric_called_with)
        self.assertEqual(metric_name, self.METRIC_PATH)
        self.assertTrue(isinstance(metric, LogMetric))
        self.assertEqual(metric.name, self.METRIC_PATH)
        self.assertEqual(metric.filter, self.FILTER)
        self.assertEqual(metric.description, self.DESCRIPTION)
        self.assertEqual(options, None)

    def test_metric_delete_error(self):
        from google.gax.errors import GaxError
        gax_api = _GAXMetricsAPI(_random_gax_error=True)
        api = self._makeOne(gax_api)

        with self.assertRaises(GaxError):
            api.metric_delete(self.PROJECT, self.METRIC_NAME)

    def test_metric_delete_miss(self):
        from gcloud.exceptions import NotFound
        gax_api = _GAXMetricsAPI(_log_metric_not_found=True)
        api = self._makeOne(gax_api)

        with self.assertRaises(NotFound):
            api.metric_delete(self.PROJECT, self.METRIC_NAME)

    def test_metric_delete_hit(self):
        gax_api = _GAXMetricsAPI()
        api = self._makeOne(gax_api)

        api.metric_delete(self.PROJECT, self.METRIC_NAME)

        metric_name, options = gax_api._delete_log_metric_called_with
        self.assertEqual(metric_name, self.METRIC_PATH)
        self.assertEqual(options, None)


class _GAXBaseAPI(object):

    _random_gax_error = False

    def __init__(self, **kw):
        self.__dict__.update(kw)

    def _make_grpc_error(self, status_code):
        from grpc.framework.interfaces.face.face import AbortionError

        class _DummyException(AbortionError):
            code = status_code

            def __init__(self):
                pass

        return _DummyException()

    def _make_grpc_not_found(self):
        from grpc.beta.interfaces import StatusCode
        return self._make_grpc_error(StatusCode.NOT_FOUND)

    def _make_grpc_failed_precondition(self):
        from grpc.beta.interfaces import StatusCode
        return self._make_grpc_error(StatusCode.FAILED_PRECONDITION)


class _GAXLoggingAPI(_GAXBaseAPI):

    def list_log_entries(
            self, projects, filter_, order_by, page_size, options):
        self._list_log_entries_called_with = (
            projects, filter_, order_by, page_size, options)
        return self._list_log_entries_response

    def write_log_entries(self, entries, log_name, resource, labels,
                          partial_success, options):
        self._write_log_entries_called_with = (
            entries, log_name, resource, labels, partial_success, options)

    def delete_log(self, log_name, options):
        self._delete_log_called_with = log_name, options


class _GAXSinksAPI(_GAXBaseAPI):

    _create_sink_conflict = False
    _sink_not_found = False

    def list_sinks(self, parent, page_size, options):
        self._list_sinks_called_with = parent, page_size, options
        return self._list_sinks_response

    def create_sink(self, parent, sink, options):
        from google.gax.errors import GaxError
        self._create_sink_called_with = parent, sink, options
        if self._random_gax_error:
            raise GaxError('error')
        if self._create_sink_conflict:
            raise GaxError('conflict', self._make_grpc_failed_precondition())

    def get_sink(self, sink_name, options):
        from google.gax.errors import GaxError
        self._get_sink_called_with = sink_name, options
        if self._random_gax_error:
            raise GaxError('error')
        try:
            return self._get_sink_response
        except AttributeError:
            raise GaxError('notfound', self._make_grpc_not_found())

    def update_sink(self, sink_name, sink, options=None):
        from google.gax.errors import GaxError
        self._update_sink_called_with = sink_name, sink, options
        if self._random_gax_error:
            raise GaxError('error')
        try:
            return self._update_sink_response
        except AttributeError:
            raise GaxError('notfound', self._make_grpc_not_found())

    def delete_sink(self, sink_name, options=None):
        from google.gax.errors import GaxError
        self._delete_sink_called_with = sink_name, options
        if self._random_gax_error:
            raise GaxError('error')
        if self._sink_not_found:
            raise GaxError('notfound', self._make_grpc_not_found())


class _GAXMetricsAPI(_GAXBaseAPI):

    _create_log_metric_conflict = False
    _log_metric_not_found = False

    def list_log_metrics(self, parent, page_size, options):
        self._list_log_metrics_called_with = parent, page_size, options
        return self._list_log_metrics_response

    def create_log_metric(self, parent, metric, options):
        from google.gax.errors import GaxError
        self._create_log_metric_called_with = parent, metric, options
        if self._random_gax_error:
            raise GaxError('error')
        if self._create_log_metric_conflict:
            raise GaxError('conflict', self._make_grpc_failed_precondition())

    def get_log_metric(self, metric_name, options):
        from google.gax.errors import GaxError
        self._get_log_metric_called_with = metric_name, options
        if self._random_gax_error:
            raise GaxError('error')
        try:
            return self._get_log_metric_response
        except AttributeError:
            raise GaxError('notfound', self._make_grpc_not_found())

    def update_log_metric(self, metric_name, metric, options=None):
        from google.gax.errors import GaxError
        self._update_log_metric_called_with = metric_name, metric, options
        if self._random_gax_error:
            raise GaxError('error')
        try:
            return self._update_log_metric_response
        except AttributeError:
            raise GaxError('notfound', self._make_grpc_not_found())

    def delete_log_metric(self, metric_name, options=None):
        from google.gax.errors import GaxError
        self._delete_log_metric_called_with = metric_name, options
        if self._random_gax_error:
            raise GaxError('error')
        if self._log_metric_not_found:
            raise GaxError('notfound', self._make_grpc_not_found())


class _HTTPRequestPB(object):

    request_url = 'http://example.com/requested'
    request_method = 'GET'
    status = 200
    referer = 'http://example.com/referer'
    user_agent = 'AGENT'
    cache_hit = False
    request_size = 256
    response_size = 1024
    remote_ip = '1.2.3.4'


class _LogEntryOperationPB(object):

    producer = 'PRODUCER'
    first = last = False
    id = 'OPID'


class _ResourcePB(object):

    def __init__(self, type_='global', **labels):
        self.type = type_
        self.labels = labels


class _LogEntryPB(object):

    severity = 'DEFAULT'
    http_request = operation = insert_id = None
    text_payload = json_payload = proto_payload = None

    def __init__(self, log_name, **kw):
        self.log_name = log_name
        self.resource = _ResourcePB()
        self.timestamp = self._make_timestamp()
        self.labels = kw.pop('labels', {})
        self.__dict__.update(kw)

    @staticmethod
    def _make_timestamp():
        from datetime import datetime
        from gcloud._helpers import UTC
        from gcloud._helpers import _datetime_to_pb_timestamp
        NOW = datetime.utcnow().replace(tzinfo=UTC)
        return _datetime_to_pb_timestamp(NOW)


class _LogSinkPB(object):

    def __init__(self, name, destination, filter_):
        self.name = name
        self.destination = destination
        self.filter = filter_


class _LogMetricPB(object):

    def __init__(self, name, description, filter_):
        self.name = name
        self.description = description
        self.filter = filter_