Skip to content

Commit fa26c29

Browse files
simorenohCopilot
andauthored
[Cosmos] add backwards compatibility tests for irrelevant kwargs (#40150)
* add backwards compatibility tests * refactor mocking tests * Update sdk/cosmos/azure-cosmos/tests/test_backwards_compatibility_async.py Co-authored-by: Copilot <[email protected]> * Update sdk/cosmos/azure-cosmos/tests/test_backwards_compatibility.py Co-authored-by: Copilot <[email protected]> * wrong checks * add missing hooks * refactor failing tests --------- Co-authored-by: Copilot <[email protected]>
1 parent 32ca6c8 commit fa26c29

File tree

3 files changed

+283
-60
lines changed

3 files changed

+283
-60
lines changed

sdk/cosmos/azure-cosmos/tests/test_backwards_compatibility.py

Lines changed: 111 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,20 @@
22
# Copyright (c) Microsoft Corporation. All rights reserved.
33

44
import unittest
5-
from unittest.mock import MagicMock
5+
import uuid
66

77
import pytest
88

99
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
1113

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'
1219

1320
@pytest.mark.cosmosEmulator
1421
class TestBackwardsCompatibility(unittest.TestCase):
@@ -42,42 +49,113 @@ def test_offer_methods(self):
4249
self.assertTrue(isinstance(database_offer, Offer))
4350
self.assertTrue(isinstance(container_offer, Offer))
4451

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-
5552
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)
6755

6856
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()))
7274
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()))
7697
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)
81159

82160
if __name__ == "__main__":
83161
unittest.main()
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# The MIT License (MIT)
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
4+
import unittest
5+
import uuid
6+
7+
import pytest
8+
9+
import test_config
10+
from azure.core import MatchConditions
11+
from azure.cosmos import PartitionKey
12+
from azure.cosmos.aio import CosmosClient, DatabaseProxy
13+
from azure.cosmos.exceptions import CosmosHttpResponseError
14+
15+
@pytest.mark.cosmosLong
16+
class TestAutoScaleAsync(unittest.IsolatedAsyncioTestCase):
17+
host = test_config.TestConfig.host
18+
masterKey = test_config.TestConfig.masterKey
19+
connectionPolicy = test_config.TestConfig.connectionPolicy
20+
21+
client: CosmosClient = None
22+
created_database: DatabaseProxy = None
23+
24+
TEST_DATABASE_ID = test_config.TestConfig.TEST_DATABASE_ID
25+
26+
@classmethod
27+
def setUpClass(cls):
28+
if (cls.masterKey == '[YOUR_KEY_HERE]' or
29+
cls.host == '[YOUR_ENDPOINT_HERE]'):
30+
raise Exception(
31+
"You must specify your Azure Cosmos account values for "
32+
"'masterKey' and 'host' at the top of this class to run the "
33+
"tests.")
34+
35+
async def asyncSetUp(self):
36+
self.client = CosmosClient(self.host, self.masterKey)
37+
self.created_database = self.client.get_database_client(self.TEST_DATABASE_ID)
38+
39+
async def asyncTearDown(self):
40+
await self.client.close()
41+
42+
async def test_session_token_compatibility_async(self):
43+
# Verifying that behavior is unaffected across the board for using `session_token` on irrelevant methods
44+
# Database
45+
database = await self.client.create_database(str(uuid.uuid4()), session_token=str(uuid.uuid4()))
46+
assert database is not None
47+
database2 = await self.client.create_database_if_not_exists(str(uuid.uuid4()), session_token=str(uuid.uuid4()))
48+
assert database2 is not None
49+
database_list = [db async for db in self.client.list_databases(session_token=str(uuid.uuid4()))]
50+
database_list2 = [db async for db in self.client.query_databases(query="select * from c", session_token=str(uuid.uuid4()))]
51+
assert len(database_list) > 0
52+
assert database_list == database_list2
53+
database_read = await database.read(session_token=str(uuid.uuid4()))
54+
assert database_read is not None
55+
await self.client.delete_database(database2.id, session_token=str(uuid.uuid4()))
56+
try:
57+
await database2.read()
58+
pytest.fail("Database read should have failed")
59+
except CosmosHttpResponseError as e:
60+
assert e.status_code == 404
61+
62+
# Container
63+
container = await database.create_container(str(uuid.uuid4()), PartitionKey(path="/pk"), session_token=str(uuid.uuid4()))
64+
assert container is not None
65+
container2 = await database.create_container_if_not_exists(str(uuid.uuid4()), PartitionKey(path="/pk"), session_token=str(uuid.uuid4()))
66+
assert container2 is not None
67+
container_list = [cont async for cont in database.list_containers(session_token=str(uuid.uuid4()))]
68+
container_list2 = [cont async for cont in database.query_containers(query="select * from c", session_token=str(uuid.uuid4()))]
69+
assert len(container_list) > 0
70+
assert container_list == container_list2
71+
container2_read = await container2.read(session_token=str(uuid.uuid4()))
72+
assert container2_read is not None
73+
replace_container = await database.replace_container(container2, PartitionKey(path="/pk"), default_ttl=30, session_token=str(uuid.uuid4()))
74+
replace_container_read = await replace_container.read()
75+
assert replace_container is not None
76+
assert replace_container_read != container2_read
77+
assert 'defaultTtl' in replace_container_read # Check for default_ttl as a new additional property
78+
assert replace_container_read['default_ttl'] == 30
79+
await database.delete_container(replace_container.id, session_token=str(uuid.uuid4()))
80+
try:
81+
await container2.read()
82+
pytest.fail("Container read should have failed")
83+
except CosmosHttpResponseError as e:
84+
assert e.status_code == 404
85+
86+
await self.client.delete_database(database.id)
87+
88+
async def test_etag_match_condition_compatibility_async(self):
89+
# Verifying that behavior is unaffected across the board for using `etag`/`match_condition` on irrelevant methods
90+
# Database
91+
database = await self.client.create_database(str(uuid.uuid4()), etag=str(uuid.uuid4()), match_condition=MatchConditions.IfModified)
92+
assert database is not None
93+
database2 = await self.client.create_database_if_not_exists(str(uuid.uuid4()), etag=str(uuid.uuid4()), match_condition=MatchConditions.IfNotModified)
94+
assert database2 is not None
95+
await self.client.delete_database(database2.id, etag=str(uuid.uuid4()), match_condition=MatchConditions.IfModified)
96+
try:
97+
await database2.read()
98+
pytest.fail("Database read should have failed")
99+
except CosmosHttpResponseError as e:
100+
assert e.status_code == 404
101+
102+
# Container
103+
container = await database.create_container(str(uuid.uuid4()), PartitionKey(path="/pk"),
104+
etag=str(uuid.uuid4()), match_condition=MatchConditions.IfModified)
105+
assert container is not None
106+
container2 = await database.create_container_if_not_exists(str(uuid.uuid4()), PartitionKey(path="/pk"),
107+
etag=str(uuid.uuid4()), match_condition=MatchConditions.IfNotModified)
108+
assert container2 is not None
109+
container2_read = await container2.read()
110+
assert container2_read is not None
111+
replace_container = await database.replace_container(container2, PartitionKey(path="/pk"), default_ttl=30,
112+
etag=str(uuid.uuid4()), match_condition=MatchConditions.IfModified)
113+
replace_container_read = await replace_container.read()
114+
assert replace_container is not None
115+
assert replace_container_read != container2_read
116+
assert 'defaultTtl' in replace_container_read # Check for default_ttl as a new additional property
117+
await database.delete_container(replace_container.id, etag=str(uuid.uuid4()), match_condition=MatchConditions.IfModified)
118+
try:
119+
await container2.read()
120+
pytest.fail("Container read should have failed")
121+
except CosmosHttpResponseError as e:
122+
assert e.status_code == 404
123+
124+
# Item
125+
item = await container.create_item({"id": str(uuid.uuid4()), "pk": 0}, etag=str(uuid.uuid4()), match_condition=MatchConditions.IfModified)
126+
assert item is not None
127+
item2 = await container.upsert_item({"id": str(uuid.uuid4()), "pk": 0}, etag=str(uuid.uuid4()),
128+
match_condition=MatchConditions.IfNotModified)
129+
assert item2 is not None
130+
batch_operations = [
131+
("create", ({"id": str(uuid.uuid4()), "pk": 0},)),
132+
("replace", (item2['id'], {"id": str(uuid.uuid4()), "pk": 0})),
133+
("read", (item['id'],)),
134+
("upsert", ({"id": str(uuid.uuid4()), "pk": 0},)),
135+
]
136+
batch_results = await container.execute_item_batch(batch_operations, partition_key=0, etag=str(uuid.uuid4()), match_condition=MatchConditions.IfModified)
137+
assert len(batch_results) == 4
138+
for result in batch_results:
139+
assert result['statusCode'] in (200, 201)
140+
141+
await self.client.delete_database(database.id)
142+
143+
144+
if __name__ == '__main__':
145+
unittest.main()

0 commit comments

Comments
 (0)