1# Copyright 2016 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import unittest 16 17import mock 18 19 20def _make_credentials(): 21 import google.auth.credentials 22 23 return mock.Mock(spec=google.auth.credentials.Credentials) 24 25 26class TestConnection(unittest.TestCase): 27 28 PROJECT = "project" 29 FILTER = "logName:syslog AND severity>=ERROR" 30 31 @staticmethod 32 def _get_default_timeout(): 33 from google.cloud.logging_v2._http import _http 34 35 return _http._DEFAULT_TIMEOUT 36 37 @staticmethod 38 def _get_target_class(): 39 from google.cloud.logging_v2._http import Connection 40 41 return Connection 42 43 def _make_one(self, *args, **kw): 44 return self._get_target_class()(*args, **kw) 45 46 def test_default_url(self): 47 client = object() 48 conn = self._make_one(client) 49 self.assertIs(conn._client, client) 50 51 def test_build_api_url_w_custom_endpoint(self): 52 from urllib.parse import parse_qsl 53 from urllib.parse import urlsplit 54 55 custom_endpoint = "https://foo-logging.googleapis.com" 56 conn = self._make_one(object(), api_endpoint=custom_endpoint) 57 uri = conn.build_api_url("/foo") 58 scheme, netloc, path, qs, _ = urlsplit(uri) 59 self.assertEqual("%s://%s" % (scheme, netloc), custom_endpoint) 60 self.assertEqual(path, "/".join(["", conn.API_VERSION, "foo"])) 61 parms = dict(parse_qsl(qs)) 62 pretty_print = parms.pop("prettyPrint", "false") 63 self.assertEqual(pretty_print, "false") 64 self.assertEqual(parms, {}) 65 66 def test_extra_headers(self): 67 import requests 68 from google.cloud import _http as base_http 69 70 http = mock.create_autospec(requests.Session, instance=True) 71 response = requests.Response() 72 response.status_code = 200 73 data = b"brent-spiner" 74 response._content = data 75 http.request.return_value = response 76 client = mock.Mock(_http=http, spec=["_http"]) 77 78 conn = self._make_one(client) 79 req_data = "req-data-boring" 80 result = conn.api_request("GET", "/rainbow", data=req_data, expect_json=False) 81 self.assertEqual(result, data) 82 83 expected_headers = { 84 "Accept-Encoding": "gzip", 85 base_http.CLIENT_INFO_HEADER: conn.user_agent, 86 "User-Agent": conn.user_agent, 87 } 88 expected_uri = conn.build_api_url("/rainbow") 89 http.request.assert_called_once_with( 90 data=req_data, 91 headers=expected_headers, 92 method="GET", 93 url=expected_uri, 94 timeout=self._get_default_timeout(), 95 ) 96 97 98class Test_LoggingAPI(unittest.TestCase): 99 100 PROJECT = "project" 101 PROJECT_PATH = "projects/project" 102 LIST_ENTRIES_PATH = "entries:list" 103 WRITE_ENTRIES_PATH = "entries:write" 104 LOGGER_NAME = "LOGGER_NAME" 105 LOGGER_PATH = "projects/project/logs/LOGGER_NAME" 106 FILTER = "logName:syslog AND severity>=ERROR" 107 108 @staticmethod 109 def _get_target_class(): 110 from google.cloud.logging_v2._http import _LoggingAPI 111 112 return _LoggingAPI 113 114 def _make_one(self, *args, **kw): 115 return self._get_target_class()(*args, **kw) 116 117 def test_ctor(self): 118 connection = _Connection() 119 client = _Client(connection) 120 api = self._make_one(client) 121 self.assertIs(api._client, client) 122 self.assertEqual(api.api_request, connection.api_request) 123 124 @staticmethod 125 def _make_timestamp(): 126 import datetime 127 from google.cloud._helpers import UTC 128 129 NOW = datetime.datetime.utcnow().replace(tzinfo=UTC) 130 return NOW, _datetime_to_rfc3339_w_nanos(NOW) 131 132 def test_list_entries_no_paging(self): 133 from google.cloud.logging import Client 134 from google.cloud.logging import TextEntry 135 from google.cloud.logging import Logger 136 137 NOW, TIMESTAMP = self._make_timestamp() 138 IID = "IID" 139 TEXT = "TEXT" 140 SENT = {"resourceNames": [self.PROJECT_PATH]} 141 TOKEN = "TOKEN" 142 RETURNED = { 143 "entries": [ 144 { 145 "textPayload": TEXT, 146 "insertId": IID, 147 "resource": {"type": "global"}, 148 "timestamp": TIMESTAMP, 149 "logName": f"projects/{self.PROJECT}/logs/{self.LOGGER_NAME}", 150 } 151 ], 152 "nextPageToken": TOKEN, 153 } 154 client = Client( 155 project=self.PROJECT, credentials=_make_credentials(), _use_grpc=False 156 ) 157 client._connection = _Connection(RETURNED) 158 api = self._make_one(client) 159 160 iterator = api.list_entries([self.PROJECT_PATH]) 161 page = next(iterator.pages) 162 entries = list(page) 163 token = iterator.next_page_token 164 165 # First check the token. 166 self.assertEqual(token, TOKEN) 167 # Then check the entries returned. 168 self.assertEqual(len(entries), 1) 169 entry = entries[0] 170 self.assertIsInstance(entry, TextEntry) 171 self.assertEqual(entry.payload, TEXT) 172 self.assertIsInstance(entry.logger, Logger) 173 self.assertEqual(entry.logger.name, self.LOGGER_NAME) 174 self.assertEqual(entry.insert_id, IID) 175 self.assertEqual(entry.timestamp, NOW) 176 self.assertIsNone(entry.labels) 177 self.assertIsNone(entry.severity) 178 self.assertIsNone(entry.http_request) 179 180 called_with = client._connection._called_with 181 expected_path = "/%s" % (self.LIST_ENTRIES_PATH,) 182 self.assertEqual( 183 called_with, {"method": "POST", "path": expected_path, "data": SENT} 184 ) 185 186 def test_list_entries_w_paging(self): 187 from google.cloud.logging import DESCENDING 188 from google.cloud.logging import Client 189 from google.cloud.logging import Logger 190 from google.cloud.logging import ProtobufEntry 191 from google.cloud.logging import StructEntry 192 193 PROJECT1 = "PROJECT1" 194 PROJECT1_PATH = f"projects/{PROJECT1}" 195 PROJECT2 = "PROJECT2" 196 PROJECT2_PATH = f"projects/{PROJECT2}" 197 NOW, TIMESTAMP = self._make_timestamp() 198 IID1 = "IID1" 199 IID2 = "IID2" 200 PAYLOAD = {"message": "MESSAGE", "weather": "partly cloudy"} 201 PROTO_PAYLOAD = PAYLOAD.copy() 202 PROTO_PAYLOAD["@type"] = "type.googleapis.com/testing.example" 203 TOKEN = "TOKEN" 204 PAGE_SIZE = 42 205 SENT = { 206 "resourceNames": [PROJECT1_PATH, PROJECT2_PATH], 207 "filter": self.FILTER, 208 "orderBy": DESCENDING, 209 "pageSize": PAGE_SIZE, 210 "pageToken": TOKEN, 211 } 212 RETURNED = { 213 "entries": [ 214 { 215 "jsonPayload": PAYLOAD, 216 "insertId": IID1, 217 "resource": {"type": "global"}, 218 "timestamp": TIMESTAMP, 219 "logName": "projects/%s/logs/%s" % (self.PROJECT, self.LOGGER_NAME), 220 }, 221 { 222 "protoPayload": PROTO_PAYLOAD, 223 "insertId": IID2, 224 "resource": {"type": "global"}, 225 "timestamp": TIMESTAMP, 226 "logName": "projects/%s/logs/%s" % (self.PROJECT, self.LOGGER_NAME), 227 }, 228 ] 229 } 230 client = Client( 231 project=self.PROJECT, credentials=_make_credentials(), _use_grpc=False 232 ) 233 client._connection = _Connection(RETURNED) 234 api = self._make_one(client) 235 236 iterator = api.list_entries( 237 resource_names=[PROJECT1_PATH, PROJECT2_PATH], 238 filter_=self.FILTER, 239 order_by=DESCENDING, 240 page_size=PAGE_SIZE, 241 page_token=TOKEN, 242 ) 243 entries = list(iterator) 244 token = iterator.next_page_token 245 246 # First check the token. 247 self.assertIsNone(token) 248 # Then check the entries returned. 249 self.assertEqual(len(entries), 2) 250 entry1 = entries[0] 251 self.assertIsInstance(entry1, StructEntry) 252 self.assertEqual(entry1.payload, PAYLOAD) 253 self.assertIsInstance(entry1.logger, Logger) 254 self.assertEqual(entry1.logger.name, self.LOGGER_NAME) 255 self.assertEqual(entry1.insert_id, IID1) 256 self.assertEqual(entry1.timestamp, NOW) 257 self.assertIsNone(entry1.labels) 258 self.assertIsNone(entry1.severity) 259 self.assertIsNone(entry1.http_request) 260 261 entry2 = entries[1] 262 self.assertIsInstance(entry2, ProtobufEntry) 263 self.assertEqual(entry2.payload, PROTO_PAYLOAD) 264 self.assertIsInstance(entry2.logger, Logger) 265 self.assertEqual(entry2.logger.name, self.LOGGER_NAME) 266 self.assertEqual(entry2.insert_id, IID2) 267 self.assertEqual(entry2.timestamp, NOW) 268 self.assertIsNone(entry2.labels) 269 self.assertIsNone(entry2.severity) 270 self.assertIsNone(entry2.http_request) 271 272 called_with = client._connection._called_with 273 expected_path = "/%s" % (self.LIST_ENTRIES_PATH,) 274 self.assertEqual( 275 called_with, {"method": "POST", "path": expected_path, "data": SENT} 276 ) 277 278 def test_write_entries_single(self): 279 TEXT = "TEXT" 280 ENTRY = { 281 "textPayload": TEXT, 282 "resource": {"type": "global"}, 283 "logName": "projects/{self.PROJECT}/logs/{self.LOGGER_NAME}", 284 } 285 SENT = {"entries": [ENTRY], "partialSuccess": False, "dry_run": False} 286 conn = _Connection({}) 287 client = _Client(conn) 288 api = self._make_one(client) 289 290 api.write_entries([ENTRY]) 291 292 self.assertEqual(conn._called_with["method"], "POST") 293 path = f"/{self.WRITE_ENTRIES_PATH}" 294 self.assertEqual(conn._called_with["path"], path) 295 self.assertEqual(conn._called_with["data"], SENT) 296 297 def test_write_entries_multiple(self): 298 TEXT = "TEXT" 299 LOG_NAME = f"projects/{self.PROJECT}/logs/{self.LOGGER_NAME}" 300 RESOURCE = {"type": "global"} 301 LABELS = {"baz": "qux", "spam": "eggs"} 302 ENTRY1 = {"textPayload": TEXT} 303 ENTRY2 = {"jsonPayload": {"foo": "bar"}} 304 SENT = { 305 "logName": LOG_NAME, 306 "resource": RESOURCE, 307 "labels": LABELS, 308 "entries": [ENTRY1, ENTRY2], 309 "partialSuccess": False, 310 "dry_run": False, 311 } 312 conn = _Connection({}) 313 client = _Client(conn) 314 api = self._make_one(client) 315 316 api.write_entries( 317 [ENTRY1, ENTRY2], logger_name=LOG_NAME, resource=RESOURCE, labels=LABELS 318 ) 319 320 self.assertEqual(conn._called_with["method"], "POST") 321 path = f"/{self.WRITE_ENTRIES_PATH}" 322 self.assertEqual(conn._called_with["path"], path) 323 self.assertEqual(conn._called_with["data"], SENT) 324 325 def test_logger_delete(self): 326 path = f"/projects/{self.PROJECT}/logs/{self.LOGGER_NAME}" 327 conn = _Connection({}) 328 client = _Client(conn) 329 api = self._make_one(client) 330 331 api.logger_delete(self.LOGGER_PATH) 332 333 self.assertEqual(conn._called_with["method"], "DELETE") 334 self.assertEqual(conn._called_with["path"], path) 335 336 337class Test_SinksAPI(unittest.TestCase): 338 339 PROJECT = "project" 340 PROJECT_PATH = "projects/project" 341 FILTER = "logName:syslog AND severity>=ERROR" 342 LIST_SINKS_PATH = f"projects/{PROJECT}/sinks" 343 SINK_NAME = "sink_name" 344 SINK_PATH = f"projects/{PROJECT}/sinks/{SINK_NAME}" 345 DESTINATION_URI = "faux.googleapis.com/destination" 346 WRITER_IDENTITY = "serviceAccount:project-123@example.com" 347 348 @staticmethod 349 def _get_target_class(): 350 from google.cloud.logging_v2._http import _SinksAPI 351 352 return _SinksAPI 353 354 def _make_one(self, *args, **kw): 355 return self._get_target_class()(*args, **kw) 356 357 def test_ctor(self): 358 connection = _Connection() 359 client = _Client(connection) 360 api = self._make_one(client) 361 self.assertIs(api._client, client) 362 self.assertEqual(api.api_request, connection.api_request) 363 364 def test_list_sinks_no_paging(self): 365 from google.cloud.logging import Sink 366 367 TOKEN = "TOKEN" 368 RETURNED = { 369 "sinks": [ 370 { 371 "name": self.SINK_PATH, 372 "filter": self.FILTER, 373 "destination": self.DESTINATION_URI, 374 } 375 ], 376 "nextPageToken": TOKEN, 377 } 378 conn = _Connection(RETURNED) 379 client = _Client(conn) 380 api = self._make_one(client) 381 382 iterator = api.list_sinks(self.PROJECT_PATH) 383 page = next(iterator.pages) 384 sinks = list(page) 385 token = iterator.next_page_token 386 387 # First check the token. 388 self.assertEqual(token, TOKEN) 389 # Then check the sinks returned. 390 self.assertEqual(len(sinks), 1) 391 sink = sinks[0] 392 self.assertIsInstance(sink, Sink) 393 self.assertEqual(sink.name, self.SINK_PATH) 394 self.assertEqual(sink.filter_, self.FILTER) 395 self.assertEqual(sink.destination, self.DESTINATION_URI) 396 self.assertIs(sink.client, client) 397 398 called_with = conn._called_with 399 path = f"/{self.LIST_SINKS_PATH}" 400 self.assertEqual( 401 called_with, {"method": "GET", "path": path, "query_params": {}} 402 ) 403 404 def test_list_sinks_w_paging(self): 405 from google.cloud.logging import Sink 406 407 TOKEN = "TOKEN" 408 PAGE_SIZE = 42 409 RETURNED = { 410 "sinks": [ 411 { 412 "name": self.SINK_PATH, 413 "filter": self.FILTER, 414 "destination": self.DESTINATION_URI, 415 } 416 ] 417 } 418 conn = _Connection(RETURNED) 419 client = _Client(conn) 420 api = self._make_one(client) 421 422 iterator = api.list_sinks( 423 self.PROJECT_PATH, page_size=PAGE_SIZE, page_token=TOKEN 424 ) 425 sinks = list(iterator) 426 token = iterator.next_page_token 427 428 # First check the token. 429 self.assertIsNone(token) 430 # Then check the sinks returned. 431 self.assertEqual(len(sinks), 1) 432 sink = sinks[0] 433 self.assertIsInstance(sink, Sink) 434 self.assertEqual(sink.name, self.SINK_PATH) 435 self.assertEqual(sink.filter_, self.FILTER) 436 self.assertEqual(sink.destination, self.DESTINATION_URI) 437 self.assertIs(sink.client, client) 438 439 called_with = conn._called_with 440 path = f"/{self.LIST_SINKS_PATH}" 441 self.assertEqual( 442 called_with, 443 { 444 "method": "GET", 445 "path": path, 446 "query_params": {"pageSize": PAGE_SIZE, "pageToken": TOKEN}, 447 }, 448 ) 449 450 def test_sink_create_conflict(self): 451 from google.cloud.exceptions import Conflict 452 453 sent = { 454 "name": self.SINK_NAME, 455 "filter": self.FILTER, 456 "destination": self.DESTINATION_URI, 457 } 458 conn = _Connection() 459 conn._raise_conflict = True 460 client = _Client(conn) 461 api = self._make_one(client) 462 463 with self.assertRaises(Conflict): 464 api.sink_create( 465 self.PROJECT_PATH, self.SINK_NAME, self.FILTER, self.DESTINATION_URI 466 ) 467 468 path = f"/projects/{self.PROJECT}/sinks" 469 expected = { 470 "method": "POST", 471 "path": path, 472 "data": sent, 473 "query_params": {"uniqueWriterIdentity": False}, 474 } 475 self.assertEqual(conn._called_with, expected) 476 477 def test_sink_create_ok(self): 478 sent = { 479 "name": self.SINK_NAME, 480 "filter": self.FILTER, 481 "destination": self.DESTINATION_URI, 482 } 483 after_create = sent.copy() 484 after_create["writerIdentity"] = self.WRITER_IDENTITY 485 conn = _Connection(after_create) 486 client = _Client(conn) 487 api = self._make_one(client) 488 489 returned = api.sink_create( 490 self.PROJECT_PATH, 491 self.SINK_NAME, 492 self.FILTER, 493 self.DESTINATION_URI, 494 unique_writer_identity=True, 495 ) 496 497 self.assertEqual(returned, after_create) 498 path = f"/projects/{self.PROJECT}/sinks" 499 expected = { 500 "method": "POST", 501 "path": path, 502 "data": sent, 503 "query_params": {"uniqueWriterIdentity": True}, 504 } 505 self.assertEqual(conn._called_with, expected) 506 507 def test_sink_get_miss(self): 508 from google.cloud.exceptions import NotFound 509 510 conn = _Connection() 511 client = _Client(conn) 512 api = self._make_one(client) 513 514 with self.assertRaises(NotFound): 515 api.sink_get(self.SINK_PATH) 516 517 self.assertEqual(conn._called_with["method"], "GET") 518 path = f"/projects/{self.PROJECT}/sinks/{self.SINK_NAME}" 519 self.assertEqual(conn._called_with["path"], path) 520 521 def test_sink_get_hit(self): 522 RESPONSE = { 523 "name": self.SINK_PATH, 524 "filter": self.FILTER, 525 "destination": self.DESTINATION_URI, 526 } 527 conn = _Connection(RESPONSE) 528 client = _Client(conn) 529 api = self._make_one(client) 530 531 response = api.sink_get(self.SINK_PATH) 532 533 self.assertEqual(response, RESPONSE) 534 self.assertEqual(conn._called_with["method"], "GET") 535 path = f"/projects/{self.PROJECT}/sinks/{self.SINK_NAME}" 536 self.assertEqual(conn._called_with["path"], path) 537 538 def test_sink_update_miss(self): 539 from google.cloud.exceptions import NotFound 540 541 sent = { 542 "name": self.SINK_NAME, 543 "filter": self.FILTER, 544 "destination": self.DESTINATION_URI, 545 } 546 conn = _Connection() 547 client = _Client(conn) 548 api = self._make_one(client) 549 550 with self.assertRaises(NotFound): 551 api.sink_update(self.SINK_PATH, self.FILTER, self.DESTINATION_URI) 552 553 path = f"/projects/{self.PROJECT}/sinks/{self.SINK_NAME}" 554 expected = { 555 "method": "PUT", 556 "path": path, 557 "data": sent, 558 "query_params": {"uniqueWriterIdentity": False}, 559 } 560 self.assertEqual(conn._called_with, expected) 561 562 def test_sink_update_hit(self): 563 sent = { 564 "name": self.SINK_NAME, 565 "filter": self.FILTER, 566 "destination": self.DESTINATION_URI, 567 } 568 after_update = sent.copy() 569 after_update["writerIdentity"] = self.WRITER_IDENTITY 570 conn = _Connection(after_update) 571 client = _Client(conn) 572 api = self._make_one(client) 573 574 returned = api.sink_update( 575 self.SINK_PATH, 576 self.FILTER, 577 self.DESTINATION_URI, 578 unique_writer_identity=True, 579 ) 580 581 self.assertEqual(returned, after_update) 582 path = f"/projects/{self.PROJECT}/sinks/{self.SINK_NAME}" 583 expected = { 584 "method": "PUT", 585 "path": path, 586 "data": sent, 587 "query_params": {"uniqueWriterIdentity": True}, 588 } 589 self.assertEqual(conn._called_with, expected) 590 591 def test_sink_delete_miss(self): 592 from google.cloud.exceptions import NotFound 593 594 conn = _Connection() 595 client = _Client(conn) 596 api = self._make_one(client) 597 598 with self.assertRaises(NotFound): 599 api.sink_delete(self.SINK_PATH) 600 601 self.assertEqual(conn._called_with["method"], "DELETE") 602 path = f"/projects/{self.PROJECT}/sinks/{self.SINK_NAME}" 603 self.assertEqual(conn._called_with["path"], path) 604 605 def test_sink_delete_hit(self): 606 conn = _Connection({}) 607 client = _Client(conn) 608 api = self._make_one(client) 609 610 api.sink_delete(self.SINK_PATH) 611 612 self.assertEqual(conn._called_with["method"], "DELETE") 613 path = f"/projects/{self.PROJECT}/sinks/{self.SINK_NAME}" 614 self.assertEqual(conn._called_with["path"], path) 615 616 617class Test_MetricsAPI(unittest.TestCase): 618 619 PROJECT = "project" 620 FILTER = "logName:syslog AND severity>=ERROR" 621 LIST_METRICS_PATH = "projects/%s/metrics" % (PROJECT,) 622 METRIC_NAME = "metric_name" 623 METRIC_PATH = "projects/%s/metrics/%s" % (PROJECT, METRIC_NAME) 624 DESCRIPTION = "DESCRIPTION" 625 626 @staticmethod 627 def _get_target_class(): 628 from google.cloud.logging_v2._http import _MetricsAPI 629 630 return _MetricsAPI 631 632 def _make_one(self, *args, **kw): 633 return self._get_target_class()(*args, **kw) 634 635 def test_list_metrics_no_paging(self): 636 from google.cloud.logging import Metric 637 638 TOKEN = "TOKEN" 639 RETURNED = { 640 "metrics": [{"name": self.METRIC_PATH, "filter": self.FILTER}], 641 "nextPageToken": TOKEN, 642 } 643 conn = _Connection(RETURNED) 644 client = _Client(conn) 645 api = self._make_one(client) 646 647 iterator = api.list_metrics(self.PROJECT) 648 page = next(iterator.pages) 649 metrics = list(page) 650 token = iterator.next_page_token 651 652 # First check the token. 653 self.assertEqual(token, TOKEN) 654 # Then check the metrics returned. 655 self.assertEqual(len(metrics), 1) 656 metric = metrics[0] 657 self.assertIsInstance(metric, Metric) 658 self.assertEqual(metric.name, self.METRIC_PATH) 659 self.assertEqual(metric.filter_, self.FILTER) 660 self.assertEqual(metric.description, "") 661 self.assertIs(metric.client, client) 662 663 called_with = conn._called_with 664 path = "/%s" % (self.LIST_METRICS_PATH,) 665 self.assertEqual( 666 called_with, {"method": "GET", "path": path, "query_params": {}} 667 ) 668 669 def test_list_metrics_w_paging(self): 670 from google.cloud.logging import Metric 671 672 TOKEN = "TOKEN" 673 PAGE_SIZE = 42 674 RETURNED = {"metrics": [{"name": self.METRIC_PATH, "filter": self.FILTER}]} 675 conn = _Connection(RETURNED) 676 client = _Client(conn) 677 api = self._make_one(client) 678 679 iterator = api.list_metrics(self.PROJECT, page_size=PAGE_SIZE, page_token=TOKEN) 680 metrics = list(iterator) 681 token = iterator.next_page_token 682 683 # First check the token. 684 self.assertIsNone(token) 685 # Then check the metrics returned. 686 self.assertEqual(len(metrics), 1) 687 metric = metrics[0] 688 self.assertIsInstance(metric, Metric) 689 self.assertEqual(metric.name, self.METRIC_PATH) 690 self.assertEqual(metric.filter_, self.FILTER) 691 self.assertEqual(metric.description, "") 692 self.assertIs(metric.client, client) 693 694 called_with = conn._called_with 695 path = "/%s" % (self.LIST_METRICS_PATH,) 696 self.assertEqual( 697 called_with, 698 { 699 "method": "GET", 700 "path": path, 701 "query_params": {"pageSize": PAGE_SIZE, "pageToken": TOKEN}, 702 }, 703 ) 704 705 def test_metric_create_conflict(self): 706 from google.cloud.exceptions import Conflict 707 708 SENT = { 709 "name": self.METRIC_NAME, 710 "filter": self.FILTER, 711 "description": self.DESCRIPTION, 712 } 713 conn = _Connection() 714 conn._raise_conflict = True 715 client = _Client(conn) 716 api = self._make_one(client) 717 718 with self.assertRaises(Conflict): 719 api.metric_create( 720 self.PROJECT, self.METRIC_NAME, self.FILTER, self.DESCRIPTION 721 ) 722 723 self.assertEqual(conn._called_with["method"], "POST") 724 path = "/projects/%s/metrics" % (self.PROJECT,) 725 self.assertEqual(conn._called_with["path"], path) 726 self.assertEqual(conn._called_with["data"], SENT) 727 728 def test_metric_create_ok(self): 729 SENT = { 730 "name": self.METRIC_NAME, 731 "filter": self.FILTER, 732 "description": self.DESCRIPTION, 733 } 734 conn = _Connection({}) 735 client = _Client(conn) 736 api = self._make_one(client) 737 738 api.metric_create(self.PROJECT, self.METRIC_NAME, self.FILTER, self.DESCRIPTION) 739 740 self.assertEqual(conn._called_with["method"], "POST") 741 path = "/projects/%s/metrics" % (self.PROJECT,) 742 self.assertEqual(conn._called_with["path"], path) 743 self.assertEqual(conn._called_with["data"], SENT) 744 745 def test_metric_get_miss(self): 746 from google.cloud.exceptions import NotFound 747 748 conn = _Connection() 749 client = _Client(conn) 750 api = self._make_one(client) 751 752 with self.assertRaises(NotFound): 753 api.metric_get(self.PROJECT, self.METRIC_NAME) 754 755 self.assertEqual(conn._called_with["method"], "GET") 756 path = "/projects/%s/metrics/%s" % (self.PROJECT, self.METRIC_NAME) 757 self.assertEqual(conn._called_with["path"], path) 758 759 def test_metric_get_hit(self): 760 RESPONSE = { 761 "name": self.METRIC_NAME, 762 "filter": self.FILTER, 763 "description": self.DESCRIPTION, 764 } 765 conn = _Connection(RESPONSE) 766 client = _Client(conn) 767 api = self._make_one(client) 768 769 response = api.metric_get(self.PROJECT, self.METRIC_NAME) 770 771 self.assertEqual(response, RESPONSE) 772 self.assertEqual(conn._called_with["method"], "GET") 773 path = "/projects/%s/metrics/%s" % (self.PROJECT, self.METRIC_NAME) 774 self.assertEqual(conn._called_with["path"], path) 775 776 def test_metric_update_miss(self): 777 from google.cloud.exceptions import NotFound 778 779 SENT = { 780 "name": self.METRIC_NAME, 781 "filter": self.FILTER, 782 "description": self.DESCRIPTION, 783 } 784 conn = _Connection() 785 client = _Client(conn) 786 api = self._make_one(client) 787 788 with self.assertRaises(NotFound): 789 api.metric_update( 790 self.PROJECT, self.METRIC_NAME, self.FILTER, self.DESCRIPTION 791 ) 792 793 self.assertEqual(conn._called_with["method"], "PUT") 794 path = "/projects/%s/metrics/%s" % (self.PROJECT, self.METRIC_NAME) 795 self.assertEqual(conn._called_with["path"], path) 796 self.assertEqual(conn._called_with["data"], SENT) 797 798 def test_metric_update_hit(self): 799 SENT = { 800 "name": self.METRIC_NAME, 801 "filter": self.FILTER, 802 "description": self.DESCRIPTION, 803 } 804 conn = _Connection({}) 805 client = _Client(conn) 806 api = self._make_one(client) 807 808 api.metric_update(self.PROJECT, self.METRIC_NAME, self.FILTER, self.DESCRIPTION) 809 810 self.assertEqual(conn._called_with["method"], "PUT") 811 path = "/projects/%s/metrics/%s" % (self.PROJECT, self.METRIC_NAME) 812 self.assertEqual(conn._called_with["path"], path) 813 self.assertEqual(conn._called_with["data"], SENT) 814 815 def test_metric_delete_miss(self): 816 from google.cloud.exceptions import NotFound 817 818 conn = _Connection() 819 client = _Client(conn) 820 api = self._make_one(client) 821 822 with self.assertRaises(NotFound): 823 api.metric_delete(self.PROJECT, self.METRIC_NAME) 824 825 self.assertEqual(conn._called_with["method"], "DELETE") 826 path = "/projects/%s/metrics/%s" % (self.PROJECT, self.METRIC_NAME) 827 self.assertEqual(conn._called_with["path"], path) 828 829 def test_metric_delete_hit(self): 830 conn = _Connection({}) 831 client = _Client(conn) 832 api = self._make_one(client) 833 834 api.metric_delete(self.PROJECT, self.METRIC_NAME) 835 836 self.assertEqual(conn._called_with["method"], "DELETE") 837 path = "/projects/%s/metrics/%s" % (self.PROJECT, self.METRIC_NAME) 838 self.assertEqual(conn._called_with["path"], path) 839 840 841class _Connection(object): 842 843 _called_with = None 844 _raise_conflict = False 845 846 def __init__(self, *responses): 847 self._responses = responses 848 849 def api_request(self, **kw): 850 from google.cloud.exceptions import Conflict 851 from google.cloud.exceptions import NotFound 852 853 self._called_with = kw 854 if self._raise_conflict: 855 raise Conflict("oops") 856 try: 857 response, self._responses = self._responses[0], self._responses[1:] 858 except IndexError: 859 raise NotFound("miss") 860 return response 861 862 863def _datetime_to_rfc3339_w_nanos(value): 864 from google.cloud._helpers import _RFC3339_NO_FRACTION 865 866 no_fraction = value.strftime(_RFC3339_NO_FRACTION) 867 return "%s.%09dZ" % (no_fraction, value.microsecond * 1000) 868 869 870class _Client(object): 871 def __init__(self, connection): 872 self._connection = connection 873