From 5ce53dc175789357b94a236e23b8753a12a82ad9 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 14 Jul 2025 10:47:38 -0500 Subject: [PATCH] PYTHON-5374 Assert unset BulkWriteException.partialResult in CRUD prose tests (#2425) Co-authored-by: Noah Stapp --- test/asynchronous/test_client_bulk_write.py | 36 +++++++++++-------- .../test_read_write_concern_spec.py | 4 +++ test/test_client_bulk_write.py | 36 +++++++++++-------- test/test_read_write_concern_spec.py | 4 +++ 4 files changed, 50 insertions(+), 30 deletions(-) diff --git a/test/asynchronous/test_client_bulk_write.py b/test/asynchronous/test_client_bulk_write.py index cf863979b..49f969fa3 100644 --- a/test/asynchronous/test_client_bulk_write.py +++ b/test/asynchronous/test_client_bulk_write.py @@ -84,6 +84,7 @@ class TestClientBulkWrite(AsyncIntegrationTest): # https://github.com/mongodb/specifications/tree/master/source/crud/tests +# Note: tests 1 and 2 are in test_read_write_concern_spec.py class TestClientBulkWriteCRUD(AsyncIntegrationTest): async def asyncSetUp(self): await super().asyncSetUp() @@ -92,7 +93,7 @@ class TestClientBulkWriteCRUD(AsyncIntegrationTest): self.max_message_size_bytes = await async_client_context.max_message_size_bytes @async_client_context.require_version_min(8, 0, 0, -24) - async def test_batch_splits_if_num_operations_too_large(self): + async def test_3_batch_splits_if_num_operations_too_large(self): listener = OvertCommandListener() client = await self.async_rs_or_single_client(event_listeners=[listener]) @@ -116,7 +117,7 @@ class TestClientBulkWriteCRUD(AsyncIntegrationTest): self.assertEqual(first_event.operation_id, second_event.operation_id) @async_client_context.require_version_min(8, 0, 0, -24) - async def test_batch_splits_if_ops_payload_too_large(self): + async def test_4_batch_splits_if_ops_payload_too_large(self): listener = OvertCommandListener() client = await self.async_rs_or_single_client(event_listeners=[listener]) @@ -148,7 +149,7 @@ class TestClientBulkWriteCRUD(AsyncIntegrationTest): @async_client_context.require_version_min(8, 0, 0, -24) @async_client_context.require_failCommand_fail_point - async def test_collects_write_concern_errors_across_batches(self): + async def test_5_collects_write_concern_errors_across_batches(self): listener = OvertCommandListener() client = await self.async_rs_or_single_client( event_listeners=[listener], @@ -189,7 +190,7 @@ class TestClientBulkWriteCRUD(AsyncIntegrationTest): self.assertEqual(len(bulk_write_events), 2) @async_client_context.require_version_min(8, 0, 0, -24) - async def test_collects_write_errors_across_batches_unordered(self): + async def test_6_collects_write_errors_across_batches_unordered(self): listener = OvertCommandListener() client = await self.async_rs_or_single_client(event_listeners=[listener]) @@ -218,7 +219,7 @@ class TestClientBulkWriteCRUD(AsyncIntegrationTest): self.assertEqual(len(bulk_write_events), 2) @async_client_context.require_version_min(8, 0, 0, -24) - async def test_collects_write_errors_across_batches_ordered(self): + async def test_6_collects_write_errors_across_batches_ordered(self): listener = OvertCommandListener() client = await self.async_rs_or_single_client(event_listeners=[listener]) @@ -247,7 +248,7 @@ class TestClientBulkWriteCRUD(AsyncIntegrationTest): self.assertEqual(len(bulk_write_events), 1) @async_client_context.require_version_min(8, 0, 0, -24) - async def test_handles_cursor_requiring_getMore(self): + async def test_7_handles_cursor_requiring_getMore(self): listener = OvertCommandListener() client = await self.async_rs_or_single_client(event_listeners=[listener]) @@ -287,7 +288,7 @@ class TestClientBulkWriteCRUD(AsyncIntegrationTest): @async_client_context.require_version_min(8, 0, 0, -24) @async_client_context.require_no_standalone - async def test_handles_cursor_requiring_getMore_within_transaction(self): + async def test_8_handles_cursor_requiring_getMore_within_transaction(self): listener = OvertCommandListener() client = await self.async_rs_or_single_client(event_listeners=[listener]) @@ -329,7 +330,7 @@ class TestClientBulkWriteCRUD(AsyncIntegrationTest): @async_client_context.require_version_min(8, 0, 0, -24) @async_client_context.require_failCommand_fail_point - async def test_handles_getMore_error(self): + async def test_9_handles_getMore_error(self): listener = OvertCommandListener() client = await self.async_rs_or_single_client(event_listeners=[listener]) @@ -382,7 +383,7 @@ class TestClientBulkWriteCRUD(AsyncIntegrationTest): self.assertTrue(kill_cursors_event) @async_client_context.require_version_min(8, 0, 0, -24) - async def test_returns_error_if_unacknowledged_too_large_insert(self): + async def test_10_returns_error_if_unacknowledged_too_large_insert(self): listener = OvertCommandListener() client = await self.async_rs_or_single_client(event_listeners=[listener]) @@ -441,7 +442,7 @@ class TestClientBulkWriteCRUD(AsyncIntegrationTest): return num_models, models @async_client_context.require_version_min(8, 0, 0, -24) - async def test_no_batch_splits_if_new_namespace_is_not_too_large(self): + async def test_11_no_batch_splits_if_new_namespace_is_not_too_large(self): listener = OvertCommandListener() client = await self.async_rs_or_single_client(event_listeners=[listener]) @@ -471,7 +472,7 @@ class TestClientBulkWriteCRUD(AsyncIntegrationTest): self.assertEqual(event.command["nsInfo"][0]["ns"], "db.coll") @async_client_context.require_version_min(8, 0, 0, -24) - async def test_batch_splits_if_new_namespace_is_too_large(self): + async def test_11_batch_splits_if_new_namespace_is_too_large(self): listener = OvertCommandListener() client = await self.async_rs_or_single_client(event_listeners=[listener]) @@ -508,25 +509,27 @@ class TestClientBulkWriteCRUD(AsyncIntegrationTest): self.assertEqual(second_event.command["nsInfo"][0]["ns"], namespace) @async_client_context.require_version_min(8, 0, 0, -24) - async def test_returns_error_if_no_writes_can_be_added_to_ops(self): + async def test_12_returns_error_if_no_writes_can_be_added_to_ops(self): client = await self.async_rs_or_single_client() # Document too large. b_repeated = "b" * self.max_message_size_bytes models = [InsertOne(namespace="db.coll", document={"a": b_repeated})] - with self.assertRaises(DocumentTooLarge): + with self.assertRaises(DocumentTooLarge) as context: await client.bulk_write(models=models) + self.assertIsNone(context.exception.partial_result) # Namespace too large. c_repeated = "c" * self.max_message_size_bytes namespace = f"db.{c_repeated}" models = [InsertOne(namespace=namespace, document={"a": "b"})] - with self.assertRaises(DocumentTooLarge): + with self.assertRaises(DocumentTooLarge) as context: await client.bulk_write(models=models) + self.assertIsNone(context.exception.partial_result) @async_client_context.require_version_min(8, 0, 0, -24) @unittest.skipUnless(_HAVE_PYMONGOCRYPT, "pymongocrypt is not installed") - async def test_returns_error_if_auto_encryption_configured(self): + async def test_13_returns_error_if_auto_encryption_configured(self): opts = AutoEncryptionOpts( key_vault_namespace="db.coll", kms_providers={"aws": {"accessKeyId": "foo", "secretAccessKey": "bar"}}, @@ -536,6 +539,7 @@ class TestClientBulkWriteCRUD(AsyncIntegrationTest): models = [InsertOne(namespace="db.coll", document={"a": "b"})] with self.assertRaises(InvalidOperation) as context: await client.bulk_write(models=models) + self.assertIsNone(context.exception.partial_result) self.assertIn( "bulk_write does not currently support automatic encryption", context.exception._message ) @@ -579,6 +583,8 @@ class TestClientBulkWriteCRUD(AsyncIntegrationTest): self.assertEqual(result.update_results[1].did_upsert, True) self.assertEqual(result.update_results[2].did_upsert, False) + # Note: test 14 is optional and intentionally not implemented because we provide multiple APIs to specify explain. + @async_client_context.require_version_min(8, 0, 0, -24) async def test_15_unacknowledged_write_across_batches(self): listener = OvertCommandListener() diff --git a/test/asynchronous/test_read_write_concern_spec.py b/test/asynchronous/test_read_write_concern_spec.py index 86f79fd28..b5cb32932 100644 --- a/test/asynchronous/test_read_write_concern_spec.py +++ b/test/asynchronous/test_read_write_concern_spec.py @@ -180,6 +180,8 @@ class TestReadWriteConcernSpec(AsyncIntegrationTest): WriteConcern(w=async_client_context.w, wtimeout=1), WTimeoutError ) + # https://github.com/mongodb/specifications/tree/master/source/crud/tests + # Test 1 (included here instead of test_client_bulk_write.py) @async_client_context.require_failCommand_fail_point async def test_error_includes_errInfo(self): expected_wce = { @@ -214,6 +216,8 @@ class TestReadWriteConcernSpec(AsyncIntegrationTest): } self.assertEqual(ctx.exception.details, expected_details) + # https://github.com/mongodb/specifications/tree/master/source/crud/tests + # Test 2 (included here instead of test_client_bulk_write.py) @async_client_context.require_version_min(4, 9) async def test_write_error_details_exposes_errinfo(self): listener = OvertCommandListener() diff --git a/test/test_client_bulk_write.py b/test/test_client_bulk_write.py index 1614e9f3c..0cb684509 100644 --- a/test/test_client_bulk_write.py +++ b/test/test_client_bulk_write.py @@ -84,6 +84,7 @@ class TestClientBulkWrite(IntegrationTest): # https://github.com/mongodb/specifications/tree/master/source/crud/tests +# Note: tests 1 and 2 are in test_read_write_concern_spec.py class TestClientBulkWriteCRUD(IntegrationTest): def setUp(self): super().setUp() @@ -92,7 +93,7 @@ class TestClientBulkWriteCRUD(IntegrationTest): self.max_message_size_bytes = client_context.max_message_size_bytes @client_context.require_version_min(8, 0, 0, -24) - def test_batch_splits_if_num_operations_too_large(self): + def test_3_batch_splits_if_num_operations_too_large(self): listener = OvertCommandListener() client = self.rs_or_single_client(event_listeners=[listener]) @@ -116,7 +117,7 @@ class TestClientBulkWriteCRUD(IntegrationTest): self.assertEqual(first_event.operation_id, second_event.operation_id) @client_context.require_version_min(8, 0, 0, -24) - def test_batch_splits_if_ops_payload_too_large(self): + def test_4_batch_splits_if_ops_payload_too_large(self): listener = OvertCommandListener() client = self.rs_or_single_client(event_listeners=[listener]) @@ -148,7 +149,7 @@ class TestClientBulkWriteCRUD(IntegrationTest): @client_context.require_version_min(8, 0, 0, -24) @client_context.require_failCommand_fail_point - def test_collects_write_concern_errors_across_batches(self): + def test_5_collects_write_concern_errors_across_batches(self): listener = OvertCommandListener() client = self.rs_or_single_client( event_listeners=[listener], @@ -189,7 +190,7 @@ class TestClientBulkWriteCRUD(IntegrationTest): self.assertEqual(len(bulk_write_events), 2) @client_context.require_version_min(8, 0, 0, -24) - def test_collects_write_errors_across_batches_unordered(self): + def test_6_collects_write_errors_across_batches_unordered(self): listener = OvertCommandListener() client = self.rs_or_single_client(event_listeners=[listener]) @@ -218,7 +219,7 @@ class TestClientBulkWriteCRUD(IntegrationTest): self.assertEqual(len(bulk_write_events), 2) @client_context.require_version_min(8, 0, 0, -24) - def test_collects_write_errors_across_batches_ordered(self): + def test_6_collects_write_errors_across_batches_ordered(self): listener = OvertCommandListener() client = self.rs_or_single_client(event_listeners=[listener]) @@ -247,7 +248,7 @@ class TestClientBulkWriteCRUD(IntegrationTest): self.assertEqual(len(bulk_write_events), 1) @client_context.require_version_min(8, 0, 0, -24) - def test_handles_cursor_requiring_getMore(self): + def test_7_handles_cursor_requiring_getMore(self): listener = OvertCommandListener() client = self.rs_or_single_client(event_listeners=[listener]) @@ -287,7 +288,7 @@ class TestClientBulkWriteCRUD(IntegrationTest): @client_context.require_version_min(8, 0, 0, -24) @client_context.require_no_standalone - def test_handles_cursor_requiring_getMore_within_transaction(self): + def test_8_handles_cursor_requiring_getMore_within_transaction(self): listener = OvertCommandListener() client = self.rs_or_single_client(event_listeners=[listener]) @@ -329,7 +330,7 @@ class TestClientBulkWriteCRUD(IntegrationTest): @client_context.require_version_min(8, 0, 0, -24) @client_context.require_failCommand_fail_point - def test_handles_getMore_error(self): + def test_9_handles_getMore_error(self): listener = OvertCommandListener() client = self.rs_or_single_client(event_listeners=[listener]) @@ -382,7 +383,7 @@ class TestClientBulkWriteCRUD(IntegrationTest): self.assertTrue(kill_cursors_event) @client_context.require_version_min(8, 0, 0, -24) - def test_returns_error_if_unacknowledged_too_large_insert(self): + def test_10_returns_error_if_unacknowledged_too_large_insert(self): listener = OvertCommandListener() client = self.rs_or_single_client(event_listeners=[listener]) @@ -437,7 +438,7 @@ class TestClientBulkWriteCRUD(IntegrationTest): return num_models, models @client_context.require_version_min(8, 0, 0, -24) - def test_no_batch_splits_if_new_namespace_is_not_too_large(self): + def test_11_no_batch_splits_if_new_namespace_is_not_too_large(self): listener = OvertCommandListener() client = self.rs_or_single_client(event_listeners=[listener]) @@ -467,7 +468,7 @@ class TestClientBulkWriteCRUD(IntegrationTest): self.assertEqual(event.command["nsInfo"][0]["ns"], "db.coll") @client_context.require_version_min(8, 0, 0, -24) - def test_batch_splits_if_new_namespace_is_too_large(self): + def test_11_batch_splits_if_new_namespace_is_too_large(self): listener = OvertCommandListener() client = self.rs_or_single_client(event_listeners=[listener]) @@ -504,25 +505,27 @@ class TestClientBulkWriteCRUD(IntegrationTest): self.assertEqual(second_event.command["nsInfo"][0]["ns"], namespace) @client_context.require_version_min(8, 0, 0, -24) - def test_returns_error_if_no_writes_can_be_added_to_ops(self): + def test_12_returns_error_if_no_writes_can_be_added_to_ops(self): client = self.rs_or_single_client() # Document too large. b_repeated = "b" * self.max_message_size_bytes models = [InsertOne(namespace="db.coll", document={"a": b_repeated})] - with self.assertRaises(DocumentTooLarge): + with self.assertRaises(DocumentTooLarge) as context: client.bulk_write(models=models) + self.assertIsNone(context.exception.partial_result) # Namespace too large. c_repeated = "c" * self.max_message_size_bytes namespace = f"db.{c_repeated}" models = [InsertOne(namespace=namespace, document={"a": "b"})] - with self.assertRaises(DocumentTooLarge): + with self.assertRaises(DocumentTooLarge) as context: client.bulk_write(models=models) + self.assertIsNone(context.exception.partial_result) @client_context.require_version_min(8, 0, 0, -24) @unittest.skipUnless(_HAVE_PYMONGOCRYPT, "pymongocrypt is not installed") - def test_returns_error_if_auto_encryption_configured(self): + def test_13_returns_error_if_auto_encryption_configured(self): opts = AutoEncryptionOpts( key_vault_namespace="db.coll", kms_providers={"aws": {"accessKeyId": "foo", "secretAccessKey": "bar"}}, @@ -532,6 +535,7 @@ class TestClientBulkWriteCRUD(IntegrationTest): models = [InsertOne(namespace="db.coll", document={"a": "b"})] with self.assertRaises(InvalidOperation) as context: client.bulk_write(models=models) + self.assertIsNone(context.exception.partial_result) self.assertIn( "bulk_write does not currently support automatic encryption", context.exception._message ) @@ -575,6 +579,8 @@ class TestClientBulkWriteCRUD(IntegrationTest): self.assertEqual(result.update_results[1].did_upsert, True) self.assertEqual(result.update_results[2].did_upsert, False) + # Note: test 14 is optional and intentionally not implemented because we provide multiple APIs to specify explain. + @client_context.require_version_min(8, 0, 0, -24) def test_15_unacknowledged_write_across_batches(self): listener = OvertCommandListener() diff --git a/test/test_read_write_concern_spec.py b/test/test_read_write_concern_spec.py index 383dc7090..4b816b7af 100644 --- a/test/test_read_write_concern_spec.py +++ b/test/test_read_write_concern_spec.py @@ -178,6 +178,8 @@ class TestReadWriteConcernSpec(IntegrationTest): self.disable_replication(client_context.client) self.assertWriteOpsRaise(WriteConcern(w=client_context.w, wtimeout=1), WTimeoutError) + # https://github.com/mongodb/specifications/tree/master/source/crud/tests + # Test 1 (included here instead of test_client_bulk_write.py) @client_context.require_failCommand_fail_point def test_error_includes_errInfo(self): expected_wce = { @@ -212,6 +214,8 @@ class TestReadWriteConcernSpec(IntegrationTest): } self.assertEqual(ctx.exception.details, expected_details) + # https://github.com/mongodb/specifications/tree/master/source/crud/tests + # Test 2 (included here instead of test_client_bulk_write.py) @client_context.require_version_min(4, 9) def test_write_error_details_exposes_errinfo(self): listener = OvertCommandListener()