|
2 | 2 | # Copyright (c) Microsoft Corporation. All rights reserved.
|
3 | 3 |
|
4 | 4 | import unittest
|
5 |
| -from unittest.mock import MagicMock |
| 5 | +import uuid |
6 | 6 |
|
7 | 7 | import pytest
|
8 | 8 |
|
9 | 9 | import test_config
|
10 |
| -from azure.cosmos import Offer, http_constants, CosmosClient, DatabaseProxy, ContainerProxy |
| 10 | +from azure.core import MatchConditions |
| 11 | +from azure.cosmos import Offer, http_constants, CosmosClient, DatabaseProxy, ContainerProxy, PartitionKey |
| 12 | +from azure.cosmos.exceptions import CosmosHttpResponseError |
11 | 13 |
|
| 14 | +def check_pk_range_statistics_request_headers(raw_response): |
| 15 | + assert raw_response.http_request.headers[http_constants.HttpHeaders.PopulatePartitionKeyRangeStatistics] == 'True' |
| 16 | + |
| 17 | +def check_quota_info_request_headers(raw_response): |
| 18 | + assert raw_response.http_request.headers[http_constants.HttpHeaders.PopulateQuotaInfo] == 'True' |
12 | 19 |
|
13 | 20 | @pytest.mark.cosmosEmulator
|
14 | 21 | class TestBackwardsCompatibility(unittest.TestCase):
|
@@ -42,42 +49,113 @@ def test_offer_methods(self):
|
42 | 49 | self.assertTrue(isinstance(database_offer, Offer))
|
43 | 50 | self.assertTrue(isinstance(container_offer, Offer))
|
44 | 51 |
|
45 |
| - def side_effect_populate_partition_key_range_statistics(self, *args, **kwargs): |
46 |
| - # Extract request headers from args |
47 |
| - self.assertTrue(args[2][http_constants.HttpHeaders.PopulatePartitionKeyRangeStatistics] is True) |
48 |
| - raise StopIteration |
49 |
| - |
50 |
| - def side_effect_populate_quota_info(self, *args, **kwargs): |
51 |
| - # Extract request headers from args |
52 |
| - self.assertTrue(args[2][http_constants.HttpHeaders.PopulateQuotaInfo] is True) |
53 |
| - raise StopIteration |
54 |
| - |
55 | 52 | def test_populate_quota_info(self):
|
56 |
| - cosmos_client_connection = self.containerForTest.client_connection |
57 |
| - cosmos_client_connection._CosmosClientConnection__Get = MagicMock( |
58 |
| - side_effect=self.side_effect_populate_quota_info) |
59 |
| - try: |
60 |
| - self.containerForTest.read(populate_quota_info=True) |
61 |
| - except StopIteration: |
62 |
| - pass |
63 |
| - try: |
64 |
| - self.containerForTest.read(False, False, True) |
65 |
| - except StopIteration: |
66 |
| - pass |
| 53 | + self.containerForTest.read(populate_quota_info=True, raw_response_hook=check_quota_info_request_headers) |
| 54 | + self.containerForTest.read(False, False, True, raw_response_hook=check_quota_info_request_headers) |
67 | 55 |
|
68 | 56 | def test_populate_partition_key_range_statistics(self):
|
69 |
| - cosmos_client_connection = self.containerForTest.client_connection |
70 |
| - cosmos_client_connection._CosmosClientConnection__Get = MagicMock( |
71 |
| - side_effect=self.side_effect_populate_partition_key_range_statistics) |
| 57 | + self.containerForTest.read(populate_partition_key_range_statistics=True, raw_response_hook=check_pk_range_statistics_request_headers) |
| 58 | + self.containerForTest.read(False, True, raw_response_hook=check_pk_range_statistics_request_headers) |
| 59 | + |
| 60 | + def test_session_token_compatibility(self): |
| 61 | + # Verifying that behavior is unaffected across the board for using `session_token` on irrelevant methods |
| 62 | + # Database |
| 63 | + database = self.client.create_database(str(uuid.uuid4()), session_token=str(uuid.uuid4())) |
| 64 | + assert database is not None |
| 65 | + database2 = self.client.create_database_if_not_exists(str(uuid.uuid4()), session_token=str(uuid.uuid4())) |
| 66 | + assert database2 is not None |
| 67 | + database_list = list(self.client.list_databases(session_token=str(uuid.uuid4()))) |
| 68 | + database_list2 = list(self.client.query_databases(query="select * from c", session_token=str(uuid.uuid4()))) |
| 69 | + assert len(database_list) > 0 |
| 70 | + assert database_list == database_list2 |
| 71 | + database_read = database.read(session_token=str(uuid.uuid4())) |
| 72 | + assert database_read is not None |
| 73 | + self.client.delete_database(database2.id, session_token=str(uuid.uuid4())) |
72 | 74 | try:
|
73 |
| - self.containerForTest.read(populate_partition_key_range_statistics=True) |
74 |
| - except StopIteration: |
75 |
| - pass |
| 75 | + database2.read() |
| 76 | + pytest.fail("Database read should have failed") |
| 77 | + except CosmosHttpResponseError as e: |
| 78 | + assert e.status_code == 404 |
| 79 | + |
| 80 | + # Container |
| 81 | + container = database.create_container(str(uuid.uuid4()), PartitionKey(path="/pk"), session_token=str(uuid.uuid4())) |
| 82 | + assert container is not None |
| 83 | + container2 = database.create_container_if_not_exists(str(uuid.uuid4()), PartitionKey(path="/pk"), session_token=str(uuid.uuid4())) |
| 84 | + assert container2 is not None |
| 85 | + container_list = list(database.list_containers(session_token=str(uuid.uuid4()))) |
| 86 | + container_list2 = list(database.query_containers(query="select * from c", session_token=str(uuid.uuid4()))) |
| 87 | + assert len(container_list) > 0 |
| 88 | + assert container_list == container_list2 |
| 89 | + container2_read = container2.read(session_token=str(uuid.uuid4())) |
| 90 | + assert container2_read is not None |
| 91 | + replace_container = database.replace_container(container2, PartitionKey(path="/pk"), default_ttl=30, session_token=str(uuid.uuid4())) |
| 92 | + replace_container_read = replace_container.read() |
| 93 | + assert replace_container is not None |
| 94 | + assert replace_container_read != container2_read |
| 95 | + assert 'defaultTtl' in replace_container_read # Check for default_ttl as a new additional property |
| 96 | + database.delete_container(replace_container.id, session_token=str(uuid.uuid4())) |
76 | 97 | try:
|
77 |
| - self.containerForTest.read(False, True) |
78 |
| - except StopIteration: |
79 |
| - pass |
80 |
| - |
| 98 | + container2.read() |
| 99 | + pytest.fail("Container read should have failed") |
| 100 | + except CosmosHttpResponseError as e: |
| 101 | + assert e.status_code == 404 |
| 102 | + |
| 103 | + self.client.delete_database(database.id) |
| 104 | + |
| 105 | + def test_etag_match_condition_compatibility(self): |
| 106 | + # Verifying that behavior is unaffected across the board for using `etag`/`match_condition` on irrelevant methods |
| 107 | + # Database |
| 108 | + database = self.client.create_database(str(uuid.uuid4()), etag=str(uuid.uuid4()), match_condition=MatchConditions.IfModified) |
| 109 | + assert database is not None |
| 110 | + database2 = self.client.create_database_if_not_exists(str(uuid.uuid4()), etag=str(uuid.uuid4()), match_condition=MatchConditions.IfNotModified) |
| 111 | + assert database2 is not None |
| 112 | + self.client.delete_database(database2.id, etag=str(uuid.uuid4()), match_condition=MatchConditions.IfModified) |
| 113 | + try: |
| 114 | + database2.read() |
| 115 | + pytest.fail("Database read should have failed") |
| 116 | + except CosmosHttpResponseError as e: |
| 117 | + assert e.status_code == 404 |
| 118 | + |
| 119 | + # Container |
| 120 | + container = database.create_container(str(uuid.uuid4()), PartitionKey(path="/pk"), |
| 121 | + etag=str(uuid.uuid4()), match_condition=MatchConditions.IfModified) |
| 122 | + assert container is not None |
| 123 | + container2 = database.create_container_if_not_exists(str(uuid.uuid4()), PartitionKey(path="/pk"), |
| 124 | + etag=str(uuid.uuid4()), match_condition=MatchConditions.IfNotModified) |
| 125 | + assert container2 is not None |
| 126 | + container2_read = container2.read() |
| 127 | + assert container2_read is not None |
| 128 | + replace_container = database.replace_container(container2, PartitionKey(path="/pk"), default_ttl=30, |
| 129 | + etag=str(uuid.uuid4()), match_condition=MatchConditions.IfModified) |
| 130 | + replace_container_read = replace_container.read() |
| 131 | + assert replace_container is not None |
| 132 | + assert replace_container_read != container2_read |
| 133 | + assert 'defaultTtl' in replace_container_read # Check for default_ttl as a new additional property |
| 134 | + database.delete_container(replace_container.id, etag=str(uuid.uuid4()), match_condition=MatchConditions.IfModified) |
| 135 | + try: |
| 136 | + container2.read() |
| 137 | + pytest.fail("Container read should have failed") |
| 138 | + except CosmosHttpResponseError as e: |
| 139 | + assert e.status_code == 404 |
| 140 | + |
| 141 | + # Item |
| 142 | + item = container.create_item({"id": str(uuid.uuid4()), "pk": 0}, etag=str(uuid.uuid4()), match_condition=MatchConditions.IfModified) |
| 143 | + assert item is not None |
| 144 | + item2 = container.upsert_item({"id": str(uuid.uuid4()), "pk": 0}, etag=str(uuid.uuid4()), |
| 145 | + match_condition=MatchConditions.IfNotModified) |
| 146 | + assert item2 is not None |
| 147 | + batch_operations = [ |
| 148 | + ("create", ({"id": str(uuid.uuid4()), "pk": 0},)), |
| 149 | + ("replace", (item2['id'], {"id": str(uuid.uuid4()), "pk": 0})), |
| 150 | + ("read", (item['id'],)), |
| 151 | + ("upsert", ({"id": str(uuid.uuid4()), "pk": 0},)), |
| 152 | + ] |
| 153 | + batch_results = container.execute_item_batch(batch_operations, partition_key=0, etag=str(uuid.uuid4()), match_condition=MatchConditions.IfModified) |
| 154 | + assert len(batch_results) == 4 |
| 155 | + for result in batch_results: |
| 156 | + assert result['statusCode'] in (200, 201) |
| 157 | + |
| 158 | + self.client.delete_database(database.id) |
81 | 159 |
|
82 | 160 | if __name__ == "__main__":
|
83 | 161 | unittest.main()
|
0 commit comments