Skip to content

PYTHON-5374 Assert unset BulkWriteException.partialResult in CRUD prose tests #2425

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 14, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 21 additions & 15 deletions test/asynchronous/test_client_bulk_write.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ async def test_formats_write_error_correctly(self):


# 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()
Expand All @@ -92,7 +93,7 @@ async def asyncSetUp(self):
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])

Expand All @@ -116,7 +117,7 @@ async def test_batch_splits_if_num_operations_too_large(self):
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])

Expand Down Expand Up @@ -148,7 +149,7 @@ async def test_batch_splits_if_ops_payload_too_large(self):

@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],
Expand Down Expand Up @@ -189,7 +190,7 @@ async def test_collects_write_concern_errors_across_batches(self):
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])

Expand Down Expand Up @@ -218,7 +219,7 @@ async def test_collects_write_errors_across_batches_unordered(self):
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])

Expand Down Expand Up @@ -247,7 +248,7 @@ async def test_collects_write_errors_across_batches_ordered(self):
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])

Expand Down Expand Up @@ -287,7 +288,7 @@ async def test_handles_cursor_requiring_getMore(self):

@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])

Expand Down Expand Up @@ -329,7 +330,7 @@ async def test_handles_cursor_requiring_getMore_within_transaction(self):

@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])

Expand Down Expand Up @@ -382,7 +383,7 @@ async def test_handles_getMore_error(self):
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])

Expand Down Expand Up @@ -441,7 +442,7 @@ async def _setup_namespace_test_models(self):
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])

Expand Down Expand Up @@ -471,7 +472,7 @@ async def test_no_batch_splits_if_new_namespace_is_not_too_large(self):
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])

Expand Down Expand Up @@ -508,25 +509,27 @@ async def test_batch_splits_if_new_namespace_is_too_large(self):
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"}},
Expand All @@ -536,6 +539,7 @@ async def test_returns_error_if_auto_encryption_configured(self):
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
)
Expand Down Expand Up @@ -579,6 +583,8 @@ async def test_upserted_result(self):
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 no implemented because we do 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()
Expand Down
4 changes: 4 additions & 0 deletions test/asynchronous/test_read_write_concern_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ async def test_raise_wtimeout(self):
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 = {
Expand Down Expand Up @@ -214,6 +216,8 @@ async def test_error_includes_errInfo(self):
}
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()
Expand Down
36 changes: 21 additions & 15 deletions test/test_client_bulk_write.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ def test_formats_write_error_correctly(self):


# 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()
Expand All @@ -92,7 +93,7 @@ def setUp(self):
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])

Expand All @@ -116,7 +117,7 @@ def test_batch_splits_if_num_operations_too_large(self):
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])

Expand Down Expand Up @@ -148,7 +149,7 @@ def test_batch_splits_if_ops_payload_too_large(self):

@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],
Expand Down Expand Up @@ -189,7 +190,7 @@ def test_collects_write_concern_errors_across_batches(self):
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])

Expand Down Expand Up @@ -218,7 +219,7 @@ def test_collects_write_errors_across_batches_unordered(self):
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])

Expand Down Expand Up @@ -247,7 +248,7 @@ def test_collects_write_errors_across_batches_ordered(self):
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])

Expand Down Expand Up @@ -287,7 +288,7 @@ def test_handles_cursor_requiring_getMore(self):

@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])

Expand Down Expand Up @@ -329,7 +330,7 @@ def test_handles_cursor_requiring_getMore_within_transaction(self):

@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])

Expand Down Expand Up @@ -382,7 +383,7 @@ def test_handles_getMore_error(self):
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])

Expand Down Expand Up @@ -437,7 +438,7 @@ def _setup_namespace_test_models(self):
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])

Expand Down Expand Up @@ -467,7 +468,7 @@ def test_no_batch_splits_if_new_namespace_is_not_too_large(self):
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])

Expand Down Expand Up @@ -504,25 +505,27 @@ def test_batch_splits_if_new_namespace_is_too_large(self):
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"}},
Expand All @@ -532,6 +535,7 @@ def test_returns_error_if_auto_encryption_configured(self):
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
)
Expand Down Expand Up @@ -575,6 +579,8 @@ def test_upserted_result(self):
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 no implemented because we do 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()
Expand Down
4 changes: 4 additions & 0 deletions test/test_read_write_concern_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ def test_raise_wtimeout(self):
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 = {
Expand Down Expand Up @@ -212,6 +214,8 @@ def test_error_includes_errInfo(self):
}
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()
Expand Down
Loading