diff --git a/test/asynchronous/test_collation.py b/test/asynchronous/test_collation.py index d7fd85b168..15d191c570 100644 --- a/test/asynchronous/test_collation.py +++ b/test/asynchronous/test_collation.py @@ -17,9 +17,11 @@ import functools import warnings -from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest -from test.utils import EventListener, OvertCommandListener -from typing import Any +from test.asynchronous.conftest import async_rs_or_single_client +from test.utils import OvertCommandListener + +import pytest +import pytest_asyncio from pymongo.asynchronous.helpers import anext from pymongo.collation import ( @@ -43,241 +45,285 @@ _IS_SYNC = False -class TestCollationObject(unittest.TestCase): - def test_constructor(self): - self.assertRaises(TypeError, Collation, locale=42) - # Fill in a locale to test the other options. - _Collation = functools.partial(Collation, "en_US") - # No error. - _Collation(caseFirst=CollationCaseFirst.UPPER) - self.assertRaises(TypeError, _Collation, caseLevel="true") - self.assertRaises(ValueError, _Collation, strength="six") - self.assertRaises(TypeError, _Collation, numericOrdering="true") - self.assertRaises(TypeError, _Collation, alternate=5) - self.assertRaises(TypeError, _Collation, maxVariable=2) - self.assertRaises(TypeError, _Collation, normalization="false") - self.assertRaises(TypeError, _Collation, backwards="true") - - # No errors. - Collation("en_US", future_option="bar", another_option=42) - collation = Collation( - "en_US", - caseLevel=True, - caseFirst=CollationCaseFirst.UPPER, - strength=CollationStrength.QUATERNARY, - numericOrdering=True, - alternate=CollationAlternate.SHIFTED, - maxVariable=CollationMaxVariable.SPACE, - normalization=True, - backwards=True, - ) - - self.assertEqual( - { - "locale": "en_US", - "caseLevel": True, - "caseFirst": "upper", - "strength": 4, - "numericOrdering": True, - "alternate": "shifted", - "maxVariable": "space", - "normalization": True, - "backwards": True, - }, - collation.document, - ) - - self.assertEqual( - {"locale": "en_US", "backwards": True}, Collation("en_US", backwards=True).document +def test_constructor(): + with pytest.raises(TypeError): + Collation(locale=42) + # Fill in a locale to test the other options. + _Collation = functools.partial(Collation, "en_US") + # No error. + _Collation(caseFirst=CollationCaseFirst.UPPER) + with pytest.raises(TypeError): + _Collation(caseLevel="true") + with pytest.raises(ValueError): + _Collation(strength="six") + with pytest.raises(TypeError): + _Collation(numericOrdering="true") + with pytest.raises(TypeError): + _Collation(alternate=5) + with pytest.raises(TypeError): + _Collation(maxVariable=2) + with pytest.raises(TypeError): + _Collation(normalization="false") + with pytest.raises(TypeError): + _Collation(backwards="true") + # No errors. + Collation("en_US", future_option="bar", another_option=42) + collation = Collation( + "en_US", + caseLevel=True, + caseFirst=CollationCaseFirst.UPPER, + strength=CollationStrength.QUATERNARY, + numericOrdering=True, + alternate=CollationAlternate.SHIFTED, + maxVariable=CollationMaxVariable.SPACE, + normalization=True, + backwards=True, + ) + + expected_document = { + "locale": "en_US", + "caseLevel": True, + "caseFirst": "upper", + "strength": 4, + "numericOrdering": True, + "alternate": "shifted", + "maxVariable": "space", + "normalization": True, + "backwards": True, + } + assert expected_document == collation.document + + assert {"locale": "en_US", "backwards": True} == Collation("en_US", + backwards=True).document + + +# Fixture for setup and teardown +@pytest_asyncio.fixture(loop_scope="function") +# @async_client_context_fixture.require_connection +async def async_client(async_client_context_fixture): + listener = OvertCommandListener() + client = await async_rs_or_single_client(async_client_context_fixture, + event_listeners=[listener]) + db = client.pymongo_test + collation = Collation("en_US") + warn_context = warnings.catch_warnings() + warn_context.__enter__() + + yield db, collation, listener, warn_context + warn_context.__exit__(None, None, None) + warn_context = None + listener.reset() + + +@pytest.mark.asyncio +async def test_create_collection(async_client): + db, collation, listener, _ = async_client + await db.test.drop() + await db.create_collection("test", collation=collation) + assert collation.document == listener.started_events[-1].command[ + "collation"] + # Test passing collation as dict + await db.test.drop() + listener.reset() + await db.create_collection("test", collation=collation.document) + assert collation.document == listener.started_events[-1].command[ + "collation"] + + +def test_index_model(): + model = IndexModel([("a", 1), ("b", -1)], collation=Collation("en_US")) + assert Collation("en_US").document == model.document["collation"] + + +@pytest.mark.asyncio +async def test_create_index(async_client): + db, collation, listener, _ = async_client + await db.test.create_index("foo", collation=collation) + ci_cmd = listener.started_events[0].command + assert collation.document == ci_cmd["indexes"][0]["collation"] + + +@pytest.mark.asyncio +async def test_aggregate(async_client): + db, collation, listener, _ = async_client + await db.test.aggregate([{"$group": {"_id": 42}}], collation=collation) + assert collation.document == listener.started_events[-1].command[ + "collation"] + + +@pytest.mark.asyncio +async def test_count_documents(async_client): + db, collation, listener, _ = async_client + await db.test.count_documents({}, collation=collation) + assert collation.document == listener.started_events[-1].command[ + "collation"] + + +@pytest.mark.asyncio +async def test_distinct(async_client): + db, collation, listener, _ = async_client + await db.test.distinct("foo", collation=collation) + assert collation.document == listener.started_events[-1].command[ + "collation"] + + listener.reset() + await db.test.find(collation=collation).distinct("foo") + assert collation.document == listener.started_events[-1].command[ + "collation"] + + +@pytest.mark.asyncio +async def test_find_command(async_client): + db, collation, listener, _ = async_client + await db.test.insert_one({"is this thing on?": True}) + listener.reset() + await anext(db.test.find(collation=collation)) + assert collation.document == listener.started_events[-1].command[ + "collation"] + + +@pytest.mark.asyncio +async def test_explain_command(async_client): + db, collation, listener, _ = async_client + listener.reset() + await db.test.find(collation=collation).explain() + # The collation should be part of the explained command. + assert collation.document == listener.started_events[-1].command["explain"][ + "collation"] + + +@pytest.mark.asyncio +async def test_delete(async_client): + db, collation, listener, _ = async_client + await db.test.delete_one({"foo": 42}, collation=collation) + command = listener.started_events[0].command + assert collation.document == command["deletes"][0]["collation"] + + listener.reset() + await db.test.delete_many({"foo": 42}, collation=collation) + command = listener.started_events[0].command + assert collation.document == command["deletes"][0]["collation"] + + +@pytest.mark.asyncio +async def test_update(async_client): + db, collation, listener, _ = async_client + await db.test.replace_one({"foo": 42}, {"foo": 43}, collation=collation) + command = listener.started_events[0].command + assert collation.document == command["updates"][0]["collation"] + + listener.reset() + await db.test.update_one({"foo": 42}, {"$set": {"foo": 43}}, + collation=collation) + command = listener.started_events[0].command + assert collation.document == command["updates"][0]["collation"] + + listener.reset() + await db.test.update_many({"foo": 42}, {"$set": {"foo": 43}}, + collation=collation) + command = listener.started_events[0].command + assert collation.document == command["updates"][0]["collation"] + + +@pytest.mark.asyncio +async def test_find_and(async_client): + db, collation, listener, _ = async_client + await db.test.find_one_and_delete({"foo": 42}, collation=collation) + assert collation.document == listener.started_events[-1].command[ + "collation"] + + listener.reset() + await db.test.find_one_and_update({"foo": 42}, {"$set": {"foo": 43}}, + collation=collation) + assert collation.document == listener.started_events[-1].command[ + "collation"] + + listener.reset() + await db.test.find_one_and_replace({"foo": 42}, {"foo": 43}, + collation=collation) + assert collation.document == listener.started_events[-1].command[ + "collation"] + + +@pytest.mark.asyncio +async def test_bulk_write(async_client): + db, collation, listener, _ = async_client + await db.test.collection.bulk_write( + [ + DeleteOne({"noCollation": 42}), + DeleteMany({"noCollation": 42}), + DeleteOne({"foo": 42}, collation=collation), + DeleteMany({"foo": 42}, collation=collation), + ReplaceOne({"noCollation": 24}, {"bar": 42}), + UpdateOne({"noCollation": 84}, {"$set": {"bar": 10}}, upsert=True), + UpdateMany({"noCollation": 45}, {"$set": {"bar": 42}}), + ReplaceOne({"foo": 24}, {"foo": 42}, collation=collation), + UpdateOne({"foo": 84}, {"$set": {"foo": 10}}, upsert=True, + collation=collation), + UpdateMany({"foo": 45}, {"$set": {"foo": 42}}, collation=collation), + ] + ) + delete_cmd = listener.started_events[0].command + update_cmd = listener.started_events[1].command + + def check_ops(ops): + for op in ops: + if "noCollation" in op["q"]: + assert "collation" not in op + else: + assert collation.document == op["collation"] + + check_ops(delete_cmd["deletes"]) + check_ops(update_cmd["updates"]) + + +@pytest.mark.asyncio +async def test_indexes_same_keys_different_collations(async_client): + db, _, _, _ = async_client + await db.test.drop() + usa_collation = Collation("en_US") + ja_collation = Collation("ja") + await db.test.create_indexes( + [ + IndexModel("fieldname", collation=usa_collation), + IndexModel("fieldname", name="japanese_version", + collation=ja_collation), + IndexModel("fieldname", name="simple"), + ] + ) + indexes = await db.test.index_information() + assert usa_collation.document["locale"] == \ + indexes["fieldname_1"]["collation"]["locale"] + assert ja_collation.document["locale"] == \ + indexes["japanese_version"]["collation"]["locale"] + assert "collation" not in indexes["simple"] + await db.test.drop_index("fieldname_1") + indexes = await db.test.index_information() + assert "japanese_version" in indexes + assert "simple" in indexes + assert "fieldname" not in indexes + + +@pytest.mark.asyncio +async def test_unacknowledged_write(async_client): + db, collation, _, _ = async_client + unacknowledged = WriteConcern(w=0) + collection = db.get_collection("test", write_concern=unacknowledged) + with pytest.raises(ConfigurationError): + await collection.update_one( + {"hello": "world"}, {"$set": {"hello": "moon"}}, collation=collation ) - - -class TestCollation(AsyncIntegrationTest): - listener: EventListener - warn_context: Any - collation: Collation - - @async_client_context.require_connection - async def asyncSetUp(self) -> None: - await super().asyncSetUp() - self.listener = OvertCommandListener() - self.client = await self.async_rs_or_single_client(event_listeners=[self.listener]) - self.db = self.client.pymongo_test - self.collation = Collation("en_US") - self.warn_context = warnings.catch_warnings() - self.warn_context.__enter__() - - async def asyncTearDown(self) -> None: - self.warn_context.__exit__() - self.warn_context = None - self.listener.reset() - await super().asyncTearDown() - - def last_command_started(self): - return self.listener.started_events[-1].command - - def assertCollationInLastCommand(self): - self.assertEqual(self.collation.document, self.last_command_started()["collation"]) - - async def test_create_collection(self): - await self.db.test.drop() - await self.db.create_collection("test", collation=self.collation) - self.assertCollationInLastCommand() - - # Test passing collation as a dict as well. - await self.db.test.drop() - self.listener.reset() - await self.db.create_collection("test", collation=self.collation.document) - self.assertCollationInLastCommand() - - def test_index_model(self): - model = IndexModel([("a", 1), ("b", -1)], collation=self.collation) - self.assertEqual(self.collation.document, model.document["collation"]) - - async def test_create_index(self): - await self.db.test.create_index("foo", collation=self.collation) - ci_cmd = self.listener.started_events[0].command - self.assertEqual(self.collation.document, ci_cmd["indexes"][0]["collation"]) - - async def test_aggregate(self): - await self.db.test.aggregate([{"$group": {"_id": 42}}], collation=self.collation) - self.assertCollationInLastCommand() - - async def test_count_documents(self): - await self.db.test.count_documents({}, collation=self.collation) - self.assertCollationInLastCommand() - - async def test_distinct(self): - await self.db.test.distinct("foo", collation=self.collation) - self.assertCollationInLastCommand() - - self.listener.reset() - await self.db.test.find(collation=self.collation).distinct("foo") - self.assertCollationInLastCommand() - - async def test_find_command(self): - await self.db.test.insert_one({"is this thing on?": True}) - self.listener.reset() - await anext(self.db.test.find(collation=self.collation)) - self.assertCollationInLastCommand() - - async def test_explain_command(self): - self.listener.reset() - await self.db.test.find(collation=self.collation).explain() - # The collation should be part of the explained command. - self.assertEqual( - self.collation.document, self.last_command_started()["explain"]["collation"] - ) - - async def test_delete(self): - await self.db.test.delete_one({"foo": 42}, collation=self.collation) - command = self.listener.started_events[0].command - self.assertEqual(self.collation.document, command["deletes"][0]["collation"]) - - self.listener.reset() - await self.db.test.delete_many({"foo": 42}, collation=self.collation) - command = self.listener.started_events[0].command - self.assertEqual(self.collation.document, command["deletes"][0]["collation"]) - - async def test_update(self): - await self.db.test.replace_one({"foo": 42}, {"foo": 43}, collation=self.collation) - command = self.listener.started_events[0].command - self.assertEqual(self.collation.document, command["updates"][0]["collation"]) - - self.listener.reset() - await self.db.test.update_one({"foo": 42}, {"$set": {"foo": 43}}, collation=self.collation) - command = self.listener.started_events[0].command - self.assertEqual(self.collation.document, command["updates"][0]["collation"]) - - self.listener.reset() - await self.db.test.update_many({"foo": 42}, {"$set": {"foo": 43}}, collation=self.collation) - command = self.listener.started_events[0].command - self.assertEqual(self.collation.document, command["updates"][0]["collation"]) - - async def test_find_and(self): - await self.db.test.find_one_and_delete({"foo": 42}, collation=self.collation) - self.assertCollationInLastCommand() - - self.listener.reset() - await self.db.test.find_one_and_update( - {"foo": 42}, {"$set": {"foo": 43}}, collation=self.collation - ) - self.assertCollationInLastCommand() - - self.listener.reset() - await self.db.test.find_one_and_replace({"foo": 42}, {"foo": 43}, collation=self.collation) - self.assertCollationInLastCommand() - - async def test_bulk_write(self): - await self.db.test.collection.bulk_write( - [ - DeleteOne({"noCollation": 42}), - DeleteMany({"noCollation": 42}), - DeleteOne({"foo": 42}, collation=self.collation), - DeleteMany({"foo": 42}, collation=self.collation), - ReplaceOne({"noCollation": 24}, {"bar": 42}), - UpdateOne({"noCollation": 84}, {"$set": {"bar": 10}}, upsert=True), - UpdateMany({"noCollation": 45}, {"$set": {"bar": 42}}), - ReplaceOne({"foo": 24}, {"foo": 42}, collation=self.collation), - UpdateOne( - {"foo": 84}, {"$set": {"foo": 10}}, upsert=True, collation=self.collation - ), - UpdateMany({"foo": 45}, {"$set": {"foo": 42}}, collation=self.collation), - ] - ) - - delete_cmd = self.listener.started_events[0].command - update_cmd = self.listener.started_events[1].command - - def check_ops(ops): - for op in ops: - if "noCollation" in op["q"]: - self.assertNotIn("collation", op) - else: - self.assertEqual(self.collation.document, op["collation"]) - - check_ops(delete_cmd["deletes"]) - check_ops(update_cmd["updates"]) - - async def test_indexes_same_keys_different_collations(self): - await self.db.test.drop() - usa_collation = Collation("en_US") - ja_collation = Collation("ja") - await self.db.test.create_indexes( - [ - IndexModel("fieldname", collation=usa_collation), - IndexModel("fieldname", name="japanese_version", collation=ja_collation), - IndexModel("fieldname", name="simple"), - ] - ) - indexes = await self.db.test.index_information() - self.assertEqual( - usa_collation.document["locale"], indexes["fieldname_1"]["collation"]["locale"] - ) - self.assertEqual( - ja_collation.document["locale"], indexes["japanese_version"]["collation"]["locale"] - ) - self.assertNotIn("collation", indexes["simple"]) - await self.db.test.drop_index("fieldname_1") - indexes = await self.db.test.index_information() - self.assertIn("japanese_version", indexes) - self.assertIn("simple", indexes) - self.assertNotIn("fieldname", indexes) - - async def test_unacknowledged_write(self): - unacknowledged = WriteConcern(w=0) - collection = self.db.get_collection("test", write_concern=unacknowledged) - with self.assertRaises(ConfigurationError): - await collection.update_one( - {"hello": "world"}, {"$set": {"hello": "moon"}}, collation=self.collation - ) - update_one = UpdateOne( - {"hello": "world"}, {"$set": {"hello": "moon"}}, collation=self.collation - ) - with self.assertRaises(ConfigurationError): - await collection.bulk_write([update_one]) - - async def test_cursor_collation(self): - await self.db.test.insert_one({"hello": "world"}) - await anext(self.db.test.find().collation(self.collation)) - self.assertCollationInLastCommand() + update_one = UpdateOne( + {"hello": "world"}, {"$set": {"hello": "moon"}}, collation=collation + ) + with pytest.raises(ConfigurationError): + await collection.bulk_write([update_one]) + + +@pytest.mark.asyncio +async def test_cursor_collation(async_client): + db, collation, listener, _ = async_client + await db.test.insert_one({"hello": "world"}) + await anext(db.test.find().collation(collation)) + assert collation.document == listener.started_events[-1].command[ + "collation"] diff --git a/test/test_collation.py b/test/test_collation.py index 06436f0638..238a417064 100644 --- a/test/test_collation.py +++ b/test/test_collation.py @@ -17,9 +17,10 @@ import functools import warnings -from test import IntegrationTest, client_context, unittest -from test.utils import EventListener, OvertCommandListener -from typing import Any +from test.conftest import rs_or_single_client +from test.utils import OvertCommandListener + +import pytest from pymongo.collation import ( Collation, @@ -43,241 +44,285 @@ _IS_SYNC = True -class TestCollationObject(unittest.TestCase): - def test_constructor(self): - self.assertRaises(TypeError, Collation, locale=42) - # Fill in a locale to test the other options. - _Collation = functools.partial(Collation, "en_US") - # No error. - _Collation(caseFirst=CollationCaseFirst.UPPER) - self.assertRaises(TypeError, _Collation, caseLevel="true") - self.assertRaises(ValueError, _Collation, strength="six") - self.assertRaises(TypeError, _Collation, numericOrdering="true") - self.assertRaises(TypeError, _Collation, alternate=5) - self.assertRaises(TypeError, _Collation, maxVariable=2) - self.assertRaises(TypeError, _Collation, normalization="false") - self.assertRaises(TypeError, _Collation, backwards="true") - - # No errors. - Collation("en_US", future_option="bar", another_option=42) - collation = Collation( - "en_US", - caseLevel=True, - caseFirst=CollationCaseFirst.UPPER, - strength=CollationStrength.QUATERNARY, - numericOrdering=True, - alternate=CollationAlternate.SHIFTED, - maxVariable=CollationMaxVariable.SPACE, - normalization=True, - backwards=True, - ) - - self.assertEqual( - { - "locale": "en_US", - "caseLevel": True, - "caseFirst": "upper", - "strength": 4, - "numericOrdering": True, - "alternate": "shifted", - "maxVariable": "space", - "normalization": True, - "backwards": True, - }, - collation.document, - ) - - self.assertEqual( - {"locale": "en_US", "backwards": True}, Collation("en_US", backwards=True).document +def test_constructor(): + with pytest.raises(TypeError): + Collation(locale=42) + # Fill in a locale to test the other options. + _Collation = functools.partial(Collation, "en_US") + # No error. + _Collation(caseFirst=CollationCaseFirst.UPPER) + with pytest.raises(TypeError): + _Collation(caseLevel="true") + with pytest.raises(ValueError): + _Collation(strength="six") + with pytest.raises(TypeError): + _Collation(numericOrdering="true") + with pytest.raises(TypeError): + _Collation(alternate=5) + with pytest.raises(TypeError): + _Collation(maxVariable=2) + with pytest.raises(TypeError): + _Collation(normalization="false") + with pytest.raises(TypeError): + _Collation(backwards="true") + # No errors. + Collation("en_US", future_option="bar", another_option=42) + collation = Collation( + "en_US", + caseLevel=True, + caseFirst=CollationCaseFirst.UPPER, + strength=CollationStrength.QUATERNARY, + numericOrdering=True, + alternate=CollationAlternate.SHIFTED, + maxVariable=CollationMaxVariable.SPACE, + normalization=True, + backwards=True, + ) + + expected_document = { + "locale": "en_US", + "caseLevel": True, + "caseFirst": "upper", + "strength": 4, + "numericOrdering": True, + "alternate": "shifted", + "maxVariable": "space", + "normalization": True, + "backwards": True, + } + assert expected_document == collation.document + + assert {"locale": "en_US", "backwards": True} == Collation("en_US", + backwards=True).document + + +# Fixture for setup and teardown +@pytest.fixture(loop_scope="function") +# @client_context_fixture.require_connection +def async_client(client_context_fixture): + listener = OvertCommandListener() + client = rs_or_single_client(client_context_fixture, + event_listeners=[listener]) + db = client.pymongo_test + collation = Collation("en_US") + warn_context = warnings.catch_warnings() + warn_context.__enter__() + + yield db, collation, listener, warn_context + warn_context.__exit__(None, None, None) + warn_context = None + listener.reset() + + +@pytest.mark.asyncio +def test_create_collection(async_client): + db, collation, listener, _ = async_client + db.test.drop() + db.create_collection("test", collation=collation) + assert collation.document == listener.started_events[-1].command[ + "collation"] + # Test passing collation as dict + db.test.drop() + listener.reset() + db.create_collection("test", collation=collation.document) + assert collation.document == listener.started_events[-1].command[ + "collation"] + + +def test_index_model(): + model = IndexModel([("a", 1), ("b", -1)], collation=Collation("en_US")) + assert Collation("en_US").document == model.document["collation"] + + +@pytest.mark.asyncio +def test_create_index(async_client): + db, collation, listener, _ = async_client + db.test.create_index("foo", collation=collation) + ci_cmd = listener.started_events[0].command + assert collation.document == ci_cmd["indexes"][0]["collation"] + + +@pytest.mark.asyncio +def test_aggregate(async_client): + db, collation, listener, _ = async_client + db.test.aggregate([{"$group": {"_id": 42}}], collation=collation) + assert collation.document == listener.started_events[-1].command[ + "collation"] + + +@pytest.mark.asyncio +def test_count_documents(async_client): + db, collation, listener, _ = async_client + db.test.count_documents({}, collation=collation) + assert collation.document == listener.started_events[-1].command[ + "collation"] + + +@pytest.mark.asyncio +def test_distinct(async_client): + db, collation, listener, _ = async_client + db.test.distinct("foo", collation=collation) + assert collation.document == listener.started_events[-1].command[ + "collation"] + + listener.reset() + db.test.find(collation=collation).distinct("foo") + assert collation.document == listener.started_events[-1].command[ + "collation"] + + +@pytest.mark.asyncio +def test_find_command(async_client): + db, collation, listener, _ = async_client + db.test.insert_one({"is this thing on?": True}) + listener.reset() + next(db.test.find(collation=collation)) + assert collation.document == listener.started_events[-1].command[ + "collation"] + + +@pytest.mark.asyncio +def test_explain_command(async_client): + db, collation, listener, _ = async_client + listener.reset() + db.test.find(collation=collation).explain() + # The collation should be part of the explained command. + assert collation.document == listener.started_events[-1].command["explain"][ + "collation"] + + +@pytest.mark.asyncio +def test_delete(async_client): + db, collation, listener, _ = async_client + db.test.delete_one({"foo": 42}, collation=collation) + command = listener.started_events[0].command + assert collation.document == command["deletes"][0]["collation"] + + listener.reset() + db.test.delete_many({"foo": 42}, collation=collation) + command = listener.started_events[0].command + assert collation.document == command["deletes"][0]["collation"] + + +@pytest.mark.asyncio +def test_update(async_client): + db, collation, listener, _ = async_client + db.test.replace_one({"foo": 42}, {"foo": 43}, collation=collation) + command = listener.started_events[0].command + assert collation.document == command["updates"][0]["collation"] + + listener.reset() + db.test.update_one({"foo": 42}, {"$set": {"foo": 43}}, + collation=collation) + command = listener.started_events[0].command + assert collation.document == command["updates"][0]["collation"] + + listener.reset() + db.test.update_many({"foo": 42}, {"$set": {"foo": 43}}, + collation=collation) + command = listener.started_events[0].command + assert collation.document == command["updates"][0]["collation"] + + +@pytest.mark.asyncio +def test_find_and(async_client): + db, collation, listener, _ = async_client + db.test.find_one_and_delete({"foo": 42}, collation=collation) + assert collation.document == listener.started_events[-1].command[ + "collation"] + + listener.reset() + db.test.find_one_and_update({"foo": 42}, {"$set": {"foo": 43}}, + collation=collation) + assert collation.document == listener.started_events[-1].command[ + "collation"] + + listener.reset() + db.test.find_one_and_replace({"foo": 42}, {"foo": 43}, + collation=collation) + assert collation.document == listener.started_events[-1].command[ + "collation"] + + +@pytest.mark.asyncio +def test_bulk_write(async_client): + db, collation, listener, _ = async_client + db.test.collection.bulk_write( + [ + DeleteOne({"noCollation": 42}), + DeleteMany({"noCollation": 42}), + DeleteOne({"foo": 42}, collation=collation), + DeleteMany({"foo": 42}, collation=collation), + ReplaceOne({"noCollation": 24}, {"bar": 42}), + UpdateOne({"noCollation": 84}, {"$set": {"bar": 10}}, upsert=True), + UpdateMany({"noCollation": 45}, {"$set": {"bar": 42}}), + ReplaceOne({"foo": 24}, {"foo": 42}, collation=collation), + UpdateOne({"foo": 84}, {"$set": {"foo": 10}}, upsert=True, + collation=collation), + UpdateMany({"foo": 45}, {"$set": {"foo": 42}}, collation=collation), + ] + ) + delete_cmd = listener.started_events[0].command + update_cmd = listener.started_events[1].command + + def check_ops(ops): + for op in ops: + if "noCollation" in op["q"]: + assert "collation" not in op + else: + assert collation.document == op["collation"] + + check_ops(delete_cmd["deletes"]) + check_ops(update_cmd["updates"]) + + +@pytest.mark.asyncio +def test_indexes_same_keys_different_collations(async_client): + db, _, _, _ = async_client + db.test.drop() + usa_collation = Collation("en_US") + ja_collation = Collation("ja") + db.test.create_indexes( + [ + IndexModel("fieldname", collation=usa_collation), + IndexModel("fieldname", name="japanese_version", + collation=ja_collation), + IndexModel("fieldname", name="simple"), + ] + ) + indexes = db.test.index_information() + assert usa_collation.document["locale"] == \ + indexes["fieldname_1"]["collation"]["locale"] + assert ja_collation.document["locale"] == \ + indexes["japanese_version"]["collation"]["locale"] + assert "collation" not in indexes["simple"] + db.test.drop_index("fieldname_1") + indexes = db.test.index_information() + assert "japanese_version" in indexes + assert "simple" in indexes + assert "fieldname" not in indexes + + +@pytest.mark.asyncio +def test_unacknowledged_write(async_client): + db, collation, _, _ = async_client + unacknowledged = WriteConcern(w=0) + collection = db.get_collection("test", write_concern=unacknowledged) + with pytest.raises(ConfigurationError): + collection.update_one( + {"hello": "world"}, {"$set": {"hello": "moon"}}, collation=collation ) - - -class TestCollation(IntegrationTest): - listener: EventListener - warn_context: Any - collation: Collation - - @client_context.require_connection - def setUp(self) -> None: - super().setUp() - self.listener = OvertCommandListener() - self.client = self.rs_or_single_client(event_listeners=[self.listener]) - self.db = self.client.pymongo_test - self.collation = Collation("en_US") - self.warn_context = warnings.catch_warnings() - self.warn_context.__enter__() - - def tearDown(self) -> None: - self.warn_context.__exit__() - self.warn_context = None - self.listener.reset() - super().tearDown() - - def last_command_started(self): - return self.listener.started_events[-1].command - - def assertCollationInLastCommand(self): - self.assertEqual(self.collation.document, self.last_command_started()["collation"]) - - def test_create_collection(self): - self.db.test.drop() - self.db.create_collection("test", collation=self.collation) - self.assertCollationInLastCommand() - - # Test passing collation as a dict as well. - self.db.test.drop() - self.listener.reset() - self.db.create_collection("test", collation=self.collation.document) - self.assertCollationInLastCommand() - - def test_index_model(self): - model = IndexModel([("a", 1), ("b", -1)], collation=self.collation) - self.assertEqual(self.collation.document, model.document["collation"]) - - def test_create_index(self): - self.db.test.create_index("foo", collation=self.collation) - ci_cmd = self.listener.started_events[0].command - self.assertEqual(self.collation.document, ci_cmd["indexes"][0]["collation"]) - - def test_aggregate(self): - self.db.test.aggregate([{"$group": {"_id": 42}}], collation=self.collation) - self.assertCollationInLastCommand() - - def test_count_documents(self): - self.db.test.count_documents({}, collation=self.collation) - self.assertCollationInLastCommand() - - def test_distinct(self): - self.db.test.distinct("foo", collation=self.collation) - self.assertCollationInLastCommand() - - self.listener.reset() - self.db.test.find(collation=self.collation).distinct("foo") - self.assertCollationInLastCommand() - - def test_find_command(self): - self.db.test.insert_one({"is this thing on?": True}) - self.listener.reset() - next(self.db.test.find(collation=self.collation)) - self.assertCollationInLastCommand() - - def test_explain_command(self): - self.listener.reset() - self.db.test.find(collation=self.collation).explain() - # The collation should be part of the explained command. - self.assertEqual( - self.collation.document, self.last_command_started()["explain"]["collation"] - ) - - def test_delete(self): - self.db.test.delete_one({"foo": 42}, collation=self.collation) - command = self.listener.started_events[0].command - self.assertEqual(self.collation.document, command["deletes"][0]["collation"]) - - self.listener.reset() - self.db.test.delete_many({"foo": 42}, collation=self.collation) - command = self.listener.started_events[0].command - self.assertEqual(self.collation.document, command["deletes"][0]["collation"]) - - def test_update(self): - self.db.test.replace_one({"foo": 42}, {"foo": 43}, collation=self.collation) - command = self.listener.started_events[0].command - self.assertEqual(self.collation.document, command["updates"][0]["collation"]) - - self.listener.reset() - self.db.test.update_one({"foo": 42}, {"$set": {"foo": 43}}, collation=self.collation) - command = self.listener.started_events[0].command - self.assertEqual(self.collation.document, command["updates"][0]["collation"]) - - self.listener.reset() - self.db.test.update_many({"foo": 42}, {"$set": {"foo": 43}}, collation=self.collation) - command = self.listener.started_events[0].command - self.assertEqual(self.collation.document, command["updates"][0]["collation"]) - - def test_find_and(self): - self.db.test.find_one_and_delete({"foo": 42}, collation=self.collation) - self.assertCollationInLastCommand() - - self.listener.reset() - self.db.test.find_one_and_update( - {"foo": 42}, {"$set": {"foo": 43}}, collation=self.collation - ) - self.assertCollationInLastCommand() - - self.listener.reset() - self.db.test.find_one_and_replace({"foo": 42}, {"foo": 43}, collation=self.collation) - self.assertCollationInLastCommand() - - def test_bulk_write(self): - self.db.test.collection.bulk_write( - [ - DeleteOne({"noCollation": 42}), - DeleteMany({"noCollation": 42}), - DeleteOne({"foo": 42}, collation=self.collation), - DeleteMany({"foo": 42}, collation=self.collation), - ReplaceOne({"noCollation": 24}, {"bar": 42}), - UpdateOne({"noCollation": 84}, {"$set": {"bar": 10}}, upsert=True), - UpdateMany({"noCollation": 45}, {"$set": {"bar": 42}}), - ReplaceOne({"foo": 24}, {"foo": 42}, collation=self.collation), - UpdateOne( - {"foo": 84}, {"$set": {"foo": 10}}, upsert=True, collation=self.collation - ), - UpdateMany({"foo": 45}, {"$set": {"foo": 42}}, collation=self.collation), - ] - ) - - delete_cmd = self.listener.started_events[0].command - update_cmd = self.listener.started_events[1].command - - def check_ops(ops): - for op in ops: - if "noCollation" in op["q"]: - self.assertNotIn("collation", op) - else: - self.assertEqual(self.collation.document, op["collation"]) - - check_ops(delete_cmd["deletes"]) - check_ops(update_cmd["updates"]) - - def test_indexes_same_keys_different_collations(self): - self.db.test.drop() - usa_collation = Collation("en_US") - ja_collation = Collation("ja") - self.db.test.create_indexes( - [ - IndexModel("fieldname", collation=usa_collation), - IndexModel("fieldname", name="japanese_version", collation=ja_collation), - IndexModel("fieldname", name="simple"), - ] - ) - indexes = self.db.test.index_information() - self.assertEqual( - usa_collation.document["locale"], indexes["fieldname_1"]["collation"]["locale"] - ) - self.assertEqual( - ja_collation.document["locale"], indexes["japanese_version"]["collation"]["locale"] - ) - self.assertNotIn("collation", indexes["simple"]) - self.db.test.drop_index("fieldname_1") - indexes = self.db.test.index_information() - self.assertIn("japanese_version", indexes) - self.assertIn("simple", indexes) - self.assertNotIn("fieldname", indexes) - - def test_unacknowledged_write(self): - unacknowledged = WriteConcern(w=0) - collection = self.db.get_collection("test", write_concern=unacknowledged) - with self.assertRaises(ConfigurationError): - collection.update_one( - {"hello": "world"}, {"$set": {"hello": "moon"}}, collation=self.collation - ) - update_one = UpdateOne( - {"hello": "world"}, {"$set": {"hello": "moon"}}, collation=self.collation - ) - with self.assertRaises(ConfigurationError): - collection.bulk_write([update_one]) - - def test_cursor_collation(self): - self.db.test.insert_one({"hello": "world"}) - next(self.db.test.find().collation(self.collation)) - self.assertCollationInLastCommand() + update_one = UpdateOne( + {"hello": "world"}, {"$set": {"hello": "moon"}}, collation=collation + ) + with pytest.raises(ConfigurationError): + collection.bulk_write([update_one]) + + +@pytest.mark.asyncio +def test_cursor_collation(async_client): + db, collation, listener, _ = async_client + db.test.insert_one({"hello": "world"}) + next(db.test.find().collation(collation)) + assert collation.document == listener.started_events[-1].command[ + "collation"]