From 2c321e06b3beab0f4c3eba726c2a7cbd0adbab59 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Mon, 15 Jun 2026 14:48:03 -0700 Subject: [PATCH 01/11] initially generated tests Signed-off-by: Alina (Xi) Li --- .../diagnostic/commands/profile/__init__.py | 0 .../profile/test_profile_combined_params.py | 93 ++++ .../profile/test_profile_core_behavior.py | 250 ++++++++++ .../commands/profile/test_profile_errors.py | 427 ++++++++++++++++++ .../commands/profile/test_profile_filter.py | 122 +++++ .../test_profile_response_structure.py | 106 +++++ .../profile/test_profile_samplerate.py | 93 ++++ .../commands/profile/test_profile_slowms.py | 93 ++++ .../profile/test_profile_type_acceptance.py | 228 ++++++++++ 9 files changed, 1412 insertions(+) create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/__init__.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_core_behavior.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_errors.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_samplerate.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_type_acceptance.py diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/__init__.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py new file mode 100644 index 000000000..fe3fc6816 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py @@ -0,0 +1,93 @@ +"""Tests for profile command with multiple parameters and cross-database behavior. + +Validates setting multiple parameters simultaneously, cross-database behavior +(admin database), and consistent reads. All tests in this file verify success +cases only. +""" + +from __future__ import annotations + +import pytest + +from documentdb_tests.framework.assertions import assertProperties +from documentdb_tests.framework.executor import execute_admin_command, execute_command +from documentdb_tests.framework.property_checks import Eq, Exists + + +def test_profile_set_level_slowms_samplerate(collection): + """Test setting profile level, slowms, and sampleRate simultaneously.""" + execute_command(collection, {"profile": 1, "slowms": 50, "sampleRate": 0.5}) + result = execute_command(collection, {"profile": -1}) + assertProperties( + result, + {"was": Eq(1), "slowms": Eq(50), "sampleRate": Eq(0.5)}, + msg="Should apply level, slowms, and sampleRate together", + raw_res=True, + ) + execute_command(collection, {"profile": 0, "sampleRate": 1.0}) + + +def test_profile_set_level_slowms_filter(collection): + """Test setting profile level, slowms, and filter simultaneously.""" + execute_command(collection, {"profile": 1, "slowms": 50, "filter": {"op": "query"}}) + result = execute_command(collection, {"profile": -1}) + assertProperties( + result, + {"was": Eq(1), "slowms": Eq(50), "filter": Exists()}, + msg="Should apply level, slowms, and filter together", + raw_res=True, + ) + execute_command(collection, {"profile": 0, "filter": "unset"}) + + +def test_profile_set_params_while_disabled(collection): + """Test setting slowms and sampleRate while disabling profiler.""" + execute_command(collection, {"profile": 0, "slowms": 200, "sampleRate": 0.8}) + result = execute_command(collection, {"profile": -1}) + assertProperties( + result, + {"was": Eq(0), "slowms": Eq(200), "sampleRate": Eq(0.8)}, + msg="Should apply slowms and sampleRate even when profiler is off", + raw_res=True, + ) + execute_command(collection, {"profile": 0, "sampleRate": 1.0}) + + +def test_profile_works_on_regular_database(collection): + """Test profile command succeeds on a regular (non-admin) database.""" + result = execute_command(collection, {"profile": 0}) + assertProperties( + result, + {"ok": Eq(1.0)}, + msg="profile should succeed on a regular database", + raw_res=True, + ) + + +@pytest.mark.admin +def test_profile_works_on_admin_database(collection): + """Test profile command succeeds on the admin database.""" + result = execute_admin_command(collection, {"profile": -1}) + assertProperties( + result, + {"ok": Eq(1.0)}, + msg="profile should succeed on the admin database", + raw_res=True, + ) + + +def test_profile_consecutive_reads_consistent(collection): + """Test two consecutive profile reads return identical results.""" + execute_command(collection, {"profile": 0}) + result1 = execute_command(collection, {"profile": -1}) + result2 = execute_command(collection, {"profile": -1}) + assertProperties( + result2, + { + "was": Eq(result1["was"]), + "slowms": Eq(result1["slowms"]), + "sampleRate": Eq(result1["sampleRate"]), + }, + msg="Consecutive reads should return identical results", + raw_res=True, + ) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_core_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_core_behavior.py new file mode 100644 index 000000000..f31f3f784 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_core_behavior.py @@ -0,0 +1,250 @@ +"""Tests for profile command core behavior. + +Validates profiling level transitions, the 'was' field, out-of-range levels, +fractional level truncation, idempotency, and level persistence via read. +""" + +from __future__ import annotations + +import pytest + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertProperties +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.property_checks import Eq + +# Property [Valid Levels]: profile levels 0, 1, 2, and -1 all succeed. +VALID_LEVEL_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "level_0", + command={"profile": 0}, + checks={"ok": Eq(1.0)}, + msg="profile level 0 should succeed", + ), + DiagnosticTestCase( + "level_1", + command={"profile": 1}, + checks={"ok": Eq(1.0)}, + msg="profile level 1 should succeed", + ), + DiagnosticTestCase( + "level_2", + command={"profile": 2}, + checks={"ok": Eq(1.0)}, + msg="profile level 2 should succeed", + ), + DiagnosticTestCase( + "level_neg1", + command={"profile": -1}, + checks={"ok": Eq(1.0)}, + msg="profile level -1 should succeed", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(VALID_LEVEL_TESTS)) +def test_profile_valid_levels(collection, test): + """Test profile command accepts valid level values.""" + # Reset to level 0 first. + execute_command(collection, {"profile": 0}) + result = execute_command(collection, test.command) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) + # Clean up. + execute_command(collection, {"profile": 0}) + + +def test_profile_was_reflects_previous_level_0_to_1(collection): + """Test 'was' field reflects previous level when transitioning 0 to 1.""" + execute_command(collection, {"profile": 0}) + result = execute_command(collection, {"profile": 1}) + assertProperties(result, {"was": Eq(0)}, msg="was should be 0 after 0->1", raw_res=True) + execute_command(collection, {"profile": 0}) + + +def test_profile_was_reflects_previous_level_1_to_2(collection): + """Test 'was' field reflects previous level when transitioning 1 to 2.""" + execute_command(collection, {"profile": 1}) + result = execute_command(collection, {"profile": 2}) + assertProperties(result, {"was": Eq(1)}, msg="was should be 1 after 1->2", raw_res=True) + execute_command(collection, {"profile": 0}) + + +def test_profile_was_reflects_previous_level_2_to_0(collection): + """Test 'was' field reflects previous level when transitioning 2 to 0.""" + execute_command(collection, {"profile": 2}) + result = execute_command(collection, {"profile": 0}) + assertProperties(result, {"was": Eq(2)}, msg="was should be 2 after 2->0", raw_res=True) + + +def test_profile_was_reflects_same_level_0_to_0(collection): + """Test 'was' field reflects previous level when setting same level 0.""" + execute_command(collection, {"profile": 0}) + result = execute_command(collection, {"profile": 0}) + assertProperties(result, {"was": Eq(0)}, msg="was should be 0 after 0->0", raw_res=True) + + +def test_profile_read_at_level_0(collection): + """Test reading with -1 returns was=0 when level is 0.""" + execute_command(collection, {"profile": 0}) + result = execute_command(collection, {"profile": -1}) + assertProperties( + result, {"was": Eq(0)}, msg="was should be 0 when read at level 0", raw_res=True + ) + + +def test_profile_read_at_level_1(collection): + """Test reading with -1 returns was=1 when level is 1.""" + execute_command(collection, {"profile": 1}) + result = execute_command(collection, {"profile": -1}) + assertProperties( + result, {"was": Eq(1)}, msg="was should be 1 when read at level 1", raw_res=True + ) + execute_command(collection, {"profile": 0}) + + +def test_profile_read_at_level_2(collection): + """Test reading with -1 returns was=2 when level is 2.""" + execute_command(collection, {"profile": 2}) + result = execute_command(collection, {"profile": -1}) + assertProperties( + result, {"was": Eq(2)}, msg="was should be 2 when read at level 2", raw_res=True + ) + execute_command(collection, {"profile": 0}) + + +# Property [Out-of-Range Levels]: integer levels outside {-1, 0, 1, 2} +# succeed but do not change the profiling level. +OUT_OF_RANGE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "out_of_range_3", + command={"profile": 3}, + checks={"ok": Eq(1.0)}, + msg="profile level 3 should succeed as a no-op", + ), + DiagnosticTestCase( + "out_of_range_neg2", + command={"profile": -2}, + checks={"ok": Eq(1.0)}, + msg="profile level -2 should succeed as a no-op", + ), + DiagnosticTestCase( + "out_of_range_100", + command={"profile": 100}, + checks={"ok": Eq(1.0)}, + msg="profile level 100 should succeed as a no-op", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(OUT_OF_RANGE_TESTS)) +def test_profile_out_of_range_levels(collection, test): + """Test profile command accepts out-of-range levels as no-ops.""" + execute_command(collection, {"profile": 1}) + execute_command(collection, test.command) + result = execute_command(collection, {"profile": -1}) + assertProperties( + result, + {"was": Eq(1)}, + msg=test.msg, + raw_res=True, + ) + execute_command(collection, {"profile": 0}) + + +# Property [Fractional Level Truncation]: non-integer numeric values are +# truncated to integer before being applied. +FRACTIONAL_LEVEL_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "fractional_1_5", + command={"profile": 1.5}, + checks={"ok": Eq(1.0)}, + msg="profile 1.5 should truncate to level 1", + ), + DiagnosticTestCase( + "fractional_0_5", + command={"profile": 0.5}, + checks={"ok": Eq(1.0)}, + msg="profile 0.5 should truncate to level 0", + ), + DiagnosticTestCase( + "fractional_2_9", + command={"profile": 2.9}, + checks={"ok": Eq(1.0)}, + msg="profile 2.9 should truncate to level 2", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(FRACTIONAL_LEVEL_TESTS)) +def test_profile_fractional_levels(collection, test): + """Test profile command accepts fractional levels.""" + execute_command(collection, {"profile": 0}) + result = execute_command(collection, test.command) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) + execute_command(collection, {"profile": 0}) + + +# Property [Fractional Level Read-Back]: after setting a fractional level, +# reading with -1 returns the truncated integer level. +FRACTIONAL_READBACK_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "readback_1_5", + command={"profile": 1.5}, + checks={"was": Eq(1)}, + msg="profile 1.5 should read back as level 1", + ), + DiagnosticTestCase( + "readback_0_5", + command={"profile": 0.5}, + checks={"was": Eq(0)}, + msg="profile 0.5 should read back as level 0", + ), + DiagnosticTestCase( + "readback_2_9", + command={"profile": 2.9}, + checks={"was": Eq(2)}, + msg="profile 2.9 should read back as level 2", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(FRACTIONAL_READBACK_TESTS)) +def test_profile_fractional_level_readback(collection, test): + """Test fractional level is truncated to integer on read-back.""" + execute_command(collection, {"profile": 0}) + execute_command(collection, test.command) + result = execute_command(collection, {"profile": -1}) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) + execute_command(collection, {"profile": 0}) + + +def test_profile_idempotent_set(collection): + """Test running profile 0 twice produces consistent results.""" + execute_command(collection, {"profile": 0}) + result = execute_command(collection, {"profile": 0}) + assertProperties( + result, + {"was": Eq(0), "ok": Eq(1.0)}, + msg="Second profile 0 should show was=0", + raw_res=True, + ) + + +def test_profile_idempotent_read(collection): + """Test running profile -1 twice returns identical results.""" + execute_command(collection, {"profile": 0}) + result1 = execute_command(collection, {"profile": -1}) + result2 = execute_command(collection, {"profile": -1}) + assertProperties( + result2, + { + "was": Eq(result1["was"]), + "slowms": Eq(result1["slowms"]), + "sampleRate": Eq(result1["sampleRate"]), + }, + msg="Two consecutive reads should return identical results", + raw_res=True, + ) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_errors.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_errors.py new file mode 100644 index 000000000..5ce99bcb3 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_errors.py @@ -0,0 +1,427 @@ +"""Tests for profile command error cases. + +Consolidates ALL error cases: type rejection for profile/slowms/sampleRate, +sampleRate range validation, filter rejection, unrecognized fields, case +sensitivity, and null required field. +""" + +from __future__ import annotations + +from datetime import datetime, timezone + +import pytest +from bson import Binary, Code, MaxKey, MinKey, ObjectId, Regex, Timestamp + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertFailureCode +from documentdb_tests.framework.error_codes import ( + BAD_VALUE_ERROR, + COMMAND_NOT_FOUND_ERROR, + MISSING_FIELD_ERROR, + TYPE_MISMATCH_ERROR, + UNRECOGNIZED_COMMAND_FIELD_ERROR, +) +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params + +# Property [Profile Type Rejection]: non-numeric types for the profile field +# are rejected with TYPE_MISMATCH_ERROR. +PROFILE_TYPE_REJECTION_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "profile_bool_true", + command={"profile": True}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject bool true", + ), + DiagnosticTestCase( + "profile_bool_false", + command={"profile": False}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject bool false", + ), + DiagnosticTestCase( + "profile_string", + command={"profile": "hello"}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject string", + ), + DiagnosticTestCase( + "profile_array", + command={"profile": [1]}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject array", + ), + DiagnosticTestCase( + "profile_empty_array", + command={"profile": []}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject empty array", + ), + DiagnosticTestCase( + "profile_object", + command={"profile": {"a": 1}}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject object", + ), + DiagnosticTestCase( + "profile_empty_object", + command={"profile": {}}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject empty object", + ), + DiagnosticTestCase( + "profile_objectid", + command={"profile": ObjectId()}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject ObjectId", + ), + DiagnosticTestCase( + "profile_binary", + command={"profile": Binary(b"")}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject Binary", + ), + DiagnosticTestCase( + "profile_regex", + command={"profile": Regex("test")}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject Regex", + ), + DiagnosticTestCase( + "profile_timestamp", + command={"profile": Timestamp(0, 0)}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject Timestamp", + ), + DiagnosticTestCase( + "profile_code", + command={"profile": Code("function(){}")}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject Code", + ), + DiagnosticTestCase( + "profile_datetime", + command={"profile": datetime(2024, 1, 1, tzinfo=timezone.utc)}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject datetime", + ), + DiagnosticTestCase( + "profile_minkey", + command={"profile": MinKey()}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject MinKey", + ), + DiagnosticTestCase( + "profile_maxkey", + command={"profile": MaxKey()}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject MaxKey", + ), +] + +# Property [Profile Null Rejection]: null for the required profile field +# produces MISSING_FIELD_ERROR. +PROFILE_NULL_REJECTION_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "profile_null", + command={"profile": None}, + error_code=MISSING_FIELD_ERROR, + msg="profile should reject null with missing field error", + ), +] + +# Property [slowms Type Rejection]: non-numeric types for the slowms field +# are rejected with TYPE_MISMATCH_ERROR. +SLOWMS_TYPE_REJECTION_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "slowms_string", + command={"profile": 0, "slowms": "hello"}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject string", + ), + DiagnosticTestCase( + "slowms_bool_true", + command={"profile": 0, "slowms": True}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject bool true", + ), + DiagnosticTestCase( + "slowms_bool_false", + command={"profile": 0, "slowms": False}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject bool false", + ), + DiagnosticTestCase( + "slowms_array", + command={"profile": 0, "slowms": [1]}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject array", + ), + DiagnosticTestCase( + "slowms_object", + command={"profile": 0, "slowms": {"a": 1}}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject object", + ), + DiagnosticTestCase( + "slowms_objectid", + command={"profile": 0, "slowms": ObjectId()}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject ObjectId", + ), + DiagnosticTestCase( + "slowms_regex", + command={"profile": 0, "slowms": Regex("test")}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject Regex", + ), + DiagnosticTestCase( + "slowms_timestamp", + command={"profile": 0, "slowms": Timestamp(0, 0)}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject Timestamp", + ), + DiagnosticTestCase( + "slowms_binary", + command={"profile": 0, "slowms": Binary(b"")}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject Binary", + ), + DiagnosticTestCase( + "slowms_code", + command={"profile": 0, "slowms": Code("function(){}")}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject Code", + ), + DiagnosticTestCase( + "slowms_minkey", + command={"profile": 0, "slowms": MinKey()}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject MinKey", + ), + DiagnosticTestCase( + "slowms_maxkey", + command={"profile": 0, "slowms": MaxKey()}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject MaxKey", + ), + DiagnosticTestCase( + "slowms_datetime", + command={"profile": 0, "slowms": datetime(2024, 1, 1, tzinfo=timezone.utc)}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject datetime", + ), +] + +# Property [sampleRate Type Rejection]: non-numeric types for the sampleRate +# field are rejected with TYPE_MISMATCH_ERROR. +SAMPLERATE_TYPE_REJECTION_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "samplerate_string", + command={"profile": 0, "sampleRate": "hello"}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject string", + ), + DiagnosticTestCase( + "samplerate_bool_true", + command={"profile": 0, "sampleRate": True}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject bool true", + ), + DiagnosticTestCase( + "samplerate_bool_false", + command={"profile": 0, "sampleRate": False}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject bool false", + ), + DiagnosticTestCase( + "samplerate_array", + command={"profile": 0, "sampleRate": [1]}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject array", + ), + DiagnosticTestCase( + "samplerate_object", + command={"profile": 0, "sampleRate": {"a": 1}}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject object", + ), + DiagnosticTestCase( + "samplerate_objectid", + command={"profile": 0, "sampleRate": ObjectId()}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject ObjectId", + ), + DiagnosticTestCase( + "samplerate_regex", + command={"profile": 0, "sampleRate": Regex("test")}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject Regex", + ), + DiagnosticTestCase( + "samplerate_timestamp", + command={"profile": 0, "sampleRate": Timestamp(0, 0)}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject Timestamp", + ), + DiagnosticTestCase( + "samplerate_binary", + command={"profile": 0, "sampleRate": Binary(b"")}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject Binary", + ), + DiagnosticTestCase( + "samplerate_code", + command={"profile": 0, "sampleRate": Code("function(){}")}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject Code", + ), + DiagnosticTestCase( + "samplerate_minkey", + command={"profile": 0, "sampleRate": MinKey()}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject MinKey", + ), + DiagnosticTestCase( + "samplerate_maxkey", + command={"profile": 0, "sampleRate": MaxKey()}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject MaxKey", + ), + DiagnosticTestCase( + "samplerate_datetime", + command={"profile": 0, "sampleRate": datetime(2024, 1, 1, tzinfo=timezone.utc)}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject datetime", + ), +] + +# Property [sampleRate Range Rejection]: sampleRate values outside [0, 1] +# are rejected with BAD_VALUE_ERROR. +SAMPLERATE_RANGE_REJECTION_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "samplerate_neg_0_1", + command={"profile": 0, "sampleRate": -0.1}, + error_code=BAD_VALUE_ERROR, + msg="sampleRate should reject -0.1", + ), + DiagnosticTestCase( + "samplerate_1_1", + command={"profile": 0, "sampleRate": 1.1}, + error_code=BAD_VALUE_ERROR, + msg="sampleRate should reject 1.1", + ), + DiagnosticTestCase( + "samplerate_2_0", + command={"profile": 0, "sampleRate": 2.0}, + error_code=BAD_VALUE_ERROR, + msg="sampleRate should reject 2.0", + ), + DiagnosticTestCase( + "samplerate_neg_1_0", + command={"profile": 0, "sampleRate": -1.0}, + error_code=BAD_VALUE_ERROR, + msg="sampleRate should reject -1.0", + ), +] + +# Property [filter Type Rejection]: non-object, non-"unset" values for the +# filter field are rejected with BAD_VALUE_ERROR. +FILTER_REJECTION_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "filter_string_UNSET", + command={"profile": 1, "filter": "UNSET"}, + error_code=BAD_VALUE_ERROR, + msg="filter should reject 'UNSET' (case-sensitive)", + ), + DiagnosticTestCase( + "filter_string_Unset", + command={"profile": 1, "filter": "Unset"}, + error_code=BAD_VALUE_ERROR, + msg="filter should reject 'Unset' (case-sensitive)", + ), + DiagnosticTestCase( + "filter_empty_string", + command={"profile": 1, "filter": ""}, + error_code=BAD_VALUE_ERROR, + msg="filter should reject empty string", + ), + DiagnosticTestCase( + "filter_string_hello", + command={"profile": 1, "filter": "hello"}, + error_code=BAD_VALUE_ERROR, + msg="filter should reject arbitrary string", + ), + DiagnosticTestCase( + "filter_int", + command={"profile": 1, "filter": 1}, + error_code=BAD_VALUE_ERROR, + msg="filter should reject int", + ), + DiagnosticTestCase( + "filter_bool", + command={"profile": 1, "filter": True}, + error_code=BAD_VALUE_ERROR, + msg="filter should reject bool", + ), + DiagnosticTestCase( + "filter_null", + command={"profile": 1, "filter": None}, + error_code=BAD_VALUE_ERROR, + msg="filter should reject null", + ), + DiagnosticTestCase( + "filter_array", + command={"profile": 1, "filter": []}, + error_code=BAD_VALUE_ERROR, + msg="filter should reject array", + ), +] + +# Property [Unrecognized Fields]: unrecognized fields in the command document +# are rejected with UNRECOGNIZED_COMMAND_FIELD_ERROR. +UNRECOGNIZED_FIELD_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "unrecognized_unknownField", + command={"profile": 0, "unknownField": 1}, + error_code=UNRECOGNIZED_COMMAND_FIELD_ERROR, + msg="profile should reject unrecognized field 'unknownField'", + ), + DiagnosticTestCase( + "unrecognized_extraParam", + command={"profile": 0, "extraParam": "value"}, + error_code=UNRECOGNIZED_COMMAND_FIELD_ERROR, + msg="profile should reject unrecognized field 'extraParam'", + ), +] + +# Property [Case-Sensitive Command Name]: the command name is case-sensitive; +# mismatched case produces COMMAND_NOT_FOUND_ERROR. +CASE_SENSITIVITY_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "case_Profile", + command={"Profile": 0}, + error_code=COMMAND_NOT_FOUND_ERROR, + msg="'Profile' (capital P) should not be recognized", + ), +] + +PROFILE_ERROR_TESTS = ( + PROFILE_TYPE_REJECTION_TESTS + + PROFILE_NULL_REJECTION_TESTS + + SLOWMS_TYPE_REJECTION_TESTS + + SAMPLERATE_TYPE_REJECTION_TESTS + + SAMPLERATE_RANGE_REJECTION_TESTS + + FILTER_REJECTION_TESTS + + UNRECOGNIZED_FIELD_TESTS + + CASE_SENSITIVITY_TESTS +) + + +@pytest.mark.parametrize("test", pytest_params(PROFILE_ERROR_TESTS)) +def test_profile_errors(collection, test): + """Test profile command rejects invalid inputs with correct error codes.""" + result = execute_command(collection, test.command) + assertFailureCode(result, test.error_code, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py new file mode 100644 index 000000000..f5cea93a1 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py @@ -0,0 +1,122 @@ +"""Tests for profile command filter parameter. + +Validates filter object acceptance, the special "unset" string, filter +normalization, and the set/unset lifecycle. All tests in this file verify +success cases only. +""" + +from __future__ import annotations + +import pytest + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertProperties +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.property_checks import Eq, Exists, NotExists + +# Property [Filter Object Acceptance]: the filter field accepts object values. +FILTER_ACCEPT_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "empty_object", + command={"profile": 1, "filter": {}}, + checks={"ok": Eq(1.0)}, + msg="filter should accept an empty object", + ), + DiagnosticTestCase( + "op_query", + command={"profile": 1, "filter": {"op": "query"}}, + checks={"ok": Eq(1.0)}, + msg="filter should accept {op: 'query'}", + ), + DiagnosticTestCase( + "op_in", + command={"profile": 1, "filter": {"op": {"$in": ["query", "command"]}}}, + checks={"ok": Eq(1.0)}, + msg="filter should accept $in expression in filter", + ), + DiagnosticTestCase( + "ns_filter", + command={"profile": 1, "filter": {"ns": "test.users"}}, + checks={"ok": Eq(1.0)}, + msg="filter should accept namespace filter", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(FILTER_ACCEPT_TESTS)) +def test_profile_filter_accept_objects(collection, test): + """Test profile filter parameter accepts valid object values.""" + result = execute_command(collection, test.command) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) + # Clean up: unset filter and disable profiler. + execute_command(collection, {"profile": 0, "filter": "unset"}) + + +def test_profile_filter_unset_string_accepted(collection): + """Test profile filter accepts the exact string 'unset'.""" + execute_command(collection, {"profile": 1, "filter": {"op": "query"}}) + result = execute_command(collection, {"profile": 1, "filter": "unset"}) + assertProperties( + result, + {"ok": Eq(1.0)}, + msg="filter should accept 'unset' string", + raw_res=True, + ) + execute_command(collection, {"profile": 0}) + + +def test_profile_filter_absent_after_unset(collection): + """Test filter field is absent after unsetting with 'unset'.""" + execute_command(collection, {"profile": 1, "filter": {"op": "query"}}) + execute_command(collection, {"profile": 1, "filter": "unset"}) + result = execute_command(collection, {"profile": -1}) + assertProperties( + result, + {"filter": NotExists()}, + msg="filter should be absent after unset", + raw_res=True, + ) + execute_command(collection, {"profile": 0}) + + +def test_profile_filter_normalization(collection): + """Test profile filter normalizes simple equality to $eq form.""" + execute_command(collection, {"profile": 1, "filter": {"op": "query"}}) + result = execute_command(collection, {"profile": -1}) + assertProperties( + result, + {"filter": Eq({"op": {"$eq": "query"}})}, + msg="filter should normalize {op: 'query'} to {op: {$eq: 'query'}}", + raw_res=True, + ) + execute_command(collection, {"profile": 0, "filter": "unset"}) + + +def test_profile_filter_present_after_set(collection): + """Test filter field is present after setting a filter.""" + execute_command(collection, {"profile": 1, "filter": {"op": "query"}}) + result = execute_command(collection, {"profile": -1}) + assertProperties( + result, + {"filter": Exists()}, + msg="filter should be present after setting", + raw_res=True, + ) + execute_command(collection, {"profile": 0, "filter": "unset"}) + + +def test_profile_filter_absent_after_cycle(collection): + """Test filter field is absent after a set/unset cycle.""" + execute_command(collection, {"profile": 1, "filter": {"op": "query"}}) + execute_command(collection, {"profile": 1, "filter": "unset"}) + result = execute_command(collection, {"profile": -1}) + assertProperties( + result, + {"filter": NotExists()}, + msg="filter should be absent after set/unset cycle", + raw_res=True, + ) + execute_command(collection, {"profile": 0}) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py new file mode 100644 index 000000000..4a378b48a --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py @@ -0,0 +1,106 @@ +"""Tests for profile command response structure. + +Validates presence, types, and values of response fields returned by the +profile command, including base fields and filter-related fields. +""" + +from __future__ import annotations + +import pytest + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertProperties +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.property_checks import Eq, Exists, IsType, NotExists + +# Property [Base Response Fields]: every successful profile command response +# includes was (int), slowms (int), sampleRate (double), and ok (1.0). +RESPONSE_BASE_FIELD_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "base_fields_at_level_neg1", + command={"profile": -1}, + checks={ + "was": IsType("int"), + "slowms": IsType("int"), + "sampleRate": IsType("double"), + "ok": Eq(1.0), + }, + msg="profile -1 response should include was, slowms, sampleRate, and ok", + ), + DiagnosticTestCase( + "base_fields_at_level_0", + command={"profile": 0}, + checks={ + "was": IsType("int"), + "slowms": IsType("int"), + "sampleRate": IsType("double"), + "ok": Eq(1.0), + }, + msg="profile 0 response should include was, slowms, sampleRate, and ok", + ), + DiagnosticTestCase( + "was_is_int", + command={"profile": -1}, + checks={"was": IsType("int")}, + msg="'was' field should be type int", + ), + DiagnosticTestCase( + "slowms_is_int", + command={"profile": -1}, + checks={"slowms": IsType("int")}, + msg="'slowms' field should be type int", + ), + DiagnosticTestCase( + "sampleRate_is_double", + command={"profile": -1}, + checks={"sampleRate": IsType("double")}, + msg="'sampleRate' field should be type double", + ), + DiagnosticTestCase( + "ok_is_1", + command={"profile": -1}, + checks={"ok": Eq(1.0)}, + msg="'ok' field should equal 1.0", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(RESPONSE_BASE_FIELD_TESTS)) +def test_profile_response_base_fields(collection, test): + """Test profile response base field presence and types.""" + result = execute_command(collection, test.command) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) + + +def test_profile_response_filter_present(collection): + """Test profile response includes filter and note fields when filter is active.""" + # Set a filter. + execute_command(collection, {"profile": 1, "filter": {"op": "query"}}) + result = execute_command(collection, {"profile": -1}) + assertProperties( + result, + {"filter": Exists(), "note": IsType("string")}, + msg="Response should include filter and note when a filter is active", + raw_res=True, + ) + # Clean up: disable profiler and unset filter. + execute_command(collection, {"profile": 0, "filter": "unset"}) + + +def test_profile_response_filter_absent_after_unset(collection): + """Test profile response excludes filter and note fields after filter is unset.""" + # Set and then unset a filter. + execute_command(collection, {"profile": 1, "filter": {"op": "query"}}) + execute_command(collection, {"profile": 1, "filter": "unset"}) + result = execute_command(collection, {"profile": -1}) + assertProperties( + result, + {"filter": NotExists(), "note": NotExists()}, + msg="Response should not include filter or note after filter is unset", + raw_res=True, + ) + # Clean up. + execute_command(collection, {"profile": 0}) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_samplerate.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_samplerate.py new file mode 100644 index 000000000..bda002384 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_samplerate.py @@ -0,0 +1,93 @@ +"""Tests for profile command sampleRate parameter. + +Validates sampleRate value persistence, boundary values, and null no-op behavior. +All tests in this file verify success cases only. +""" + +from __future__ import annotations + +import pytest + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertProperties +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.property_checks import Eq + +# Property [sampleRate Persistence]: setting sampleRate persists the value +# for subsequent reads. +SAMPLERATE_PERSISTENCE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "persist_0_5", + command={"profile": 0, "sampleRate": 0.5}, + checks={"sampleRate": Eq(0.5)}, + msg="sampleRate should persist value 0.5", + ), + DiagnosticTestCase( + "persist_0_0", + command={"profile": 0, "sampleRate": 0.0}, + checks={"sampleRate": Eq(0.0)}, + msg="sampleRate should persist value 0.0", + ), + DiagnosticTestCase( + "persist_1_0", + command={"profile": 0, "sampleRate": 1.0}, + checks={"sampleRate": Eq(1.0)}, + msg="sampleRate should persist value 1.0", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(SAMPLERATE_PERSISTENCE_TESTS)) +def test_profile_samplerate_persistence(collection, test): + """Test sampleRate value persists after being set.""" + execute_command(collection, test.command) + result = execute_command(collection, {"profile": -1}) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) + # Restore default. + execute_command(collection, {"profile": 0, "sampleRate": 1.0}) + + +# Property [sampleRate Boundary Acceptance]: sampleRate accepts boundary values +# within [0, 1] using int, Int64, and Decimal128 types. +SAMPLERATE_BOUNDARY_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "boundary_int_0", + command={"profile": 0, "sampleRate": 0}, + checks={"sampleRate": Eq(0.0)}, + msg="sampleRate should accept int 0", + ), + DiagnosticTestCase( + "boundary_int_1", + command={"profile": 0, "sampleRate": 1}, + checks={"sampleRate": Eq(1.0)}, + msg="sampleRate should accept int 1", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(SAMPLERATE_BOUNDARY_TESTS)) +def test_profile_samplerate_boundary_values(collection, test): + """Test sampleRate accepts boundary values within the valid range.""" + execute_command(collection, test.command) + result = execute_command(collection, {"profile": -1}) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) + # Restore default. + execute_command(collection, {"profile": 0, "sampleRate": 1.0}) + + +def test_profile_samplerate_null_noop(collection): + """Test sampleRate null is a no-op and does not change the current value.""" + execute_command(collection, {"profile": 0, "sampleRate": 0.5}) + execute_command(collection, {"profile": 0, "sampleRate": None}) + result = execute_command(collection, {"profile": -1}) + assertProperties( + result, + {"sampleRate": Eq(0.5)}, + msg="sampleRate should remain 0.5 after setting null", + raw_res=True, + ) + # Restore default. + execute_command(collection, {"profile": 0, "sampleRate": 1.0}) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py new file mode 100644 index 000000000..9846a5c32 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py @@ -0,0 +1,93 @@ +"""Tests for profile command slowms parameter. + +Validates slowms value persistence, boundary values, and null no-op behavior. +All tests in this file verify success cases only. +""" + +from __future__ import annotations + +import pytest + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertProperties +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.property_checks import Eq + +# Property [slowms Persistence]: setting slowms persists the value for +# subsequent reads. +SLOWMS_PERSISTENCE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "persist_200", + command={"profile": 0, "slowms": 200}, + checks={"slowms": Eq(200)}, + msg="slowms should persist value 200", + ), + DiagnosticTestCase( + "persist_0", + command={"profile": 0, "slowms": 0}, + checks={"slowms": Eq(0)}, + msg="slowms should persist value 0", + ), + DiagnosticTestCase( + "persist_neg1", + command={"profile": 0, "slowms": -1}, + checks={"slowms": Eq(-1)}, + msg="slowms should persist negative value -1", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(SLOWMS_PERSISTENCE_TESTS)) +def test_profile_slowms_persistence(collection, test): + """Test slowms value persists after being set.""" + execute_command(collection, test.command) + result = execute_command(collection, {"profile": -1}) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) + + +# Property [slowms Boundary Values]: slowms accepts the full int32 range +# including negative values. +SLOWMS_BOUNDARY_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "boundary_int32_max", + command={"profile": 0, "slowms": 2_147_483_647}, + checks={"slowms": Eq(2_147_483_647)}, + msg="slowms should accept INT32_MAX", + ), + DiagnosticTestCase( + "boundary_int32_min", + command={"profile": 0, "slowms": -2_147_483_648}, + checks={"slowms": Eq(-2_147_483_648)}, + msg="slowms should accept INT32_MIN", + ), + DiagnosticTestCase( + "boundary_neg100", + command={"profile": 0, "slowms": -100}, + checks={"slowms": Eq(-100)}, + msg="slowms should accept -100", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(SLOWMS_BOUNDARY_TESTS)) +def test_profile_slowms_boundary_values(collection, test): + """Test slowms accepts boundary values across the int32 range.""" + execute_command(collection, test.command) + result = execute_command(collection, {"profile": -1}) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) + + +def test_profile_slowms_null_noop(collection): + """Test slowms null is a no-op and does not change the current value.""" + execute_command(collection, {"profile": 0, "slowms": 500}) + execute_command(collection, {"profile": 0, "slowms": None}) + result = execute_command(collection, {"profile": -1}) + assertProperties( + result, + {"slowms": Eq(500)}, + msg="slowms should remain 500 after setting null", + raw_res=True, + ) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_type_acceptance.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_type_acceptance.py new file mode 100644 index 000000000..ec22028c4 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_type_acceptance.py @@ -0,0 +1,228 @@ +"""Tests for profile command type acceptance. + +Validates that the profile, slowms, and sampleRate fields accept all valid +numeric BSON types. All tests in this file verify success cases only. +""" + +from __future__ import annotations + +import pytest +from bson import Decimal128, Int64 + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertProperties +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.property_checks import Eq + +# Property [Profile Field Type Acceptance]: the profile field accepts int, +# Int64, double, and Decimal128 for valid level values. +PROFILE_TYPE_ACCEPTANCE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "profile_int_0", + command={"profile": 0}, + checks={"ok": Eq(1.0)}, + msg="profile should accept int 0", + ), + DiagnosticTestCase( + "profile_int_1", + command={"profile": 1}, + checks={"ok": Eq(1.0)}, + msg="profile should accept int 1", + ), + DiagnosticTestCase( + "profile_int_2", + command={"profile": 2}, + checks={"ok": Eq(1.0)}, + msg="profile should accept int 2", + ), + DiagnosticTestCase( + "profile_int_neg1", + command={"profile": -1}, + checks={"ok": Eq(1.0)}, + msg="profile should accept int -1", + ), + DiagnosticTestCase( + "profile_int64_0", + command={"profile": Int64(0)}, + checks={"ok": Eq(1.0)}, + msg="profile should accept Int64(0)", + ), + DiagnosticTestCase( + "profile_int64_1", + command={"profile": Int64(1)}, + checks={"ok": Eq(1.0)}, + msg="profile should accept Int64(1)", + ), + DiagnosticTestCase( + "profile_int64_2", + command={"profile": Int64(2)}, + checks={"ok": Eq(1.0)}, + msg="profile should accept Int64(2)", + ), + DiagnosticTestCase( + "profile_double_0", + command={"profile": 0.0}, + checks={"ok": Eq(1.0)}, + msg="profile should accept double 0.0", + ), + DiagnosticTestCase( + "profile_double_1", + command={"profile": 1.0}, + checks={"ok": Eq(1.0)}, + msg="profile should accept double 1.0", + ), + DiagnosticTestCase( + "profile_double_2", + command={"profile": 2.0}, + checks={"ok": Eq(1.0)}, + msg="profile should accept double 2.0", + ), + DiagnosticTestCase( + "profile_double_neg1", + command={"profile": -1.0}, + checks={"ok": Eq(1.0)}, + msg="profile should accept double -1.0", + ), + DiagnosticTestCase( + "profile_decimal128_0", + command={"profile": Decimal128("0")}, + checks={"ok": Eq(1.0)}, + msg="profile should accept Decimal128('0')", + ), + DiagnosticTestCase( + "profile_decimal128_1", + command={"profile": Decimal128("1")}, + checks={"ok": Eq(1.0)}, + msg="profile should accept Decimal128('1')", + ), + DiagnosticTestCase( + "profile_decimal128_2", + command={"profile": Decimal128("2")}, + checks={"ok": Eq(1.0)}, + msg="profile should accept Decimal128('2')", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(PROFILE_TYPE_ACCEPTANCE_TESTS)) +def test_profile_field_type_acceptance(collection, test): + """Test profile field accepts valid numeric BSON types.""" + result = execute_command(collection, test.command) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) + # Reset profiler. + execute_command(collection, {"profile": 0}) + + +# Property [slowms Type Acceptance]: the slowms field accepts numeric BSON +# types (int, Int64, double, Decimal128) and null. +SLOWMS_TYPE_ACCEPTANCE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "slowms_int_100", + command={"profile": 0, "slowms": 100}, + checks={"ok": Eq(1.0)}, + msg="slowms should accept int 100", + ), + DiagnosticTestCase( + "slowms_int64_100", + command={"profile": 0, "slowms": Int64(100)}, + checks={"ok": Eq(1.0)}, + msg="slowms should accept Int64(100)", + ), + DiagnosticTestCase( + "slowms_double_100", + command={"profile": 0, "slowms": 100.0}, + checks={"ok": Eq(1.0)}, + msg="slowms should accept double 100.0", + ), + DiagnosticTestCase( + "slowms_decimal128_100", + command={"profile": 0, "slowms": Decimal128("100")}, + checks={"ok": Eq(1.0)}, + msg="slowms should accept Decimal128('100')", + ), + DiagnosticTestCase( + "slowms_null", + command={"profile": 0, "slowms": None}, + checks={"ok": Eq(1.0)}, + msg="slowms should accept null (no-op)", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(SLOWMS_TYPE_ACCEPTANCE_TESTS)) +def test_profile_slowms_type_acceptance(collection, test): + """Test slowms field accepts valid numeric BSON types and null.""" + result = execute_command(collection, test.command) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) + + +# Property [sampleRate Type Acceptance]: the sampleRate field accepts numeric +# BSON types (int, Int64, double, Decimal128) within [0, 1] and null. +SAMPLERATE_TYPE_ACCEPTANCE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "samplerate_double_0_5", + command={"profile": 0, "sampleRate": 0.5}, + checks={"ok": Eq(1.0)}, + msg="sampleRate should accept double 0.5", + ), + DiagnosticTestCase( + "samplerate_int_0", + command={"profile": 0, "sampleRate": 0}, + checks={"ok": Eq(1.0)}, + msg="sampleRate should accept int 0", + ), + DiagnosticTestCase( + "samplerate_int_1", + command={"profile": 0, "sampleRate": 1}, + checks={"ok": Eq(1.0)}, + msg="sampleRate should accept int 1", + ), + DiagnosticTestCase( + "samplerate_int64_0", + command={"profile": 0, "sampleRate": Int64(0)}, + checks={"ok": Eq(1.0)}, + msg="sampleRate should accept Int64(0)", + ), + DiagnosticTestCase( + "samplerate_int64_1", + command={"profile": 0, "sampleRate": Int64(1)}, + checks={"ok": Eq(1.0)}, + msg="sampleRate should accept Int64(1)", + ), + DiagnosticTestCase( + "samplerate_decimal128_0", + command={"profile": 0, "sampleRate": Decimal128("0")}, + checks={"ok": Eq(1.0)}, + msg="sampleRate should accept Decimal128('0')", + ), + DiagnosticTestCase( + "samplerate_decimal128_0_5", + command={"profile": 0, "sampleRate": Decimal128("0.5")}, + checks={"ok": Eq(1.0)}, + msg="sampleRate should accept Decimal128('0.5')", + ), + DiagnosticTestCase( + "samplerate_decimal128_1", + command={"profile": 0, "sampleRate": Decimal128("1")}, + checks={"ok": Eq(1.0)}, + msg="sampleRate should accept Decimal128('1')", + ), + DiagnosticTestCase( + "samplerate_null", + command={"profile": 0, "sampleRate": None}, + checks={"ok": Eq(1.0)}, + msg="sampleRate should accept null (no-op)", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(SAMPLERATE_TYPE_ACCEPTANCE_TESTS)) +def test_profile_samplerate_type_acceptance(collection, test): + """Test sampleRate field accepts valid numeric BSON types and null.""" + result = execute_command(collection, test.command) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) + # Restore default. + execute_command(collection, {"profile": 0, "sampleRate": 1.0}) From 99fbd14ece5c4bbf16d319503bd1d447b23fe7f5 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Mon, 15 Jun 2026 15:02:50 -0700 Subject: [PATCH 02/11] Use style guide Signed-off-by: Alina (Xi) Li --- .../profile/test_profile_combined_params.py | 107 ++++--- .../profile/test_profile_core_behavior.py | 277 +++++++++--------- .../commands/profile/test_profile_filter.py | 127 ++++---- .../test_profile_response_structure.py | 59 ++-- .../profile/test_profile_samplerate.py | 90 +++--- .../commands/profile/test_profile_slowms.py | 86 +++--- .../profile/test_profile_type_acceptance.py | 29 +- .../commands/profile/utils/__init__.py | 0 .../profile/utils/profile_test_case.py | 23 ++ 9 files changed, 402 insertions(+), 396 deletions(-) create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/utils/__init__.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/utils/profile_test_case.py diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py index fe3fc6816..4bd6cfdd6 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py @@ -1,71 +1,82 @@ """Tests for profile command with multiple parameters and cross-database behavior. -Validates setting multiple parameters simultaneously, cross-database behavior -(admin database), and consistent reads. All tests in this file verify success -cases only. +Validates setting multiple parameters simultaneously, cross-database behavior, +and consistent reads. All tests in this file verify success cases only. """ from __future__ import annotations import pytest +from documentdb_tests.compatibility.tests.system.diagnostic.commands.profile.utils.profile_test_case import ( # noqa: E501 + ProfileTestCase, +) +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) from documentdb_tests.framework.assertions import assertProperties from documentdb_tests.framework.executor import execute_admin_command, execute_command +from documentdb_tests.framework.parametrize import pytest_params from documentdb_tests.framework.property_checks import Eq, Exists +# Property [Multi-Parameter Set]: setting level, slowms, sampleRate, and +# filter simultaneously applies all values. +MULTI_PARAM_TESTS: list[ProfileTestCase] = [ + ProfileTestCase( + "level_slowms_samplerate", + setup=[{"profile": 1, "slowms": 50, "sampleRate": 0.5}], + command={"profile": -1}, + checks={"was": Eq(1), "slowms": Eq(50), "sampleRate": Eq(0.5)}, + msg="profile should apply level, slowms, and sampleRate together", + ), + ProfileTestCase( + "level_slowms_filter", + setup=[{"profile": 1, "slowms": 50, "filter": {"op": "query"}}], + command={"profile": -1}, + checks={"was": Eq(1), "slowms": Eq(50), "filter": Exists()}, + msg="profile should apply level, slowms, and filter together", + ), + ProfileTestCase( + "params_while_disabled", + setup=[{"profile": 0, "slowms": 200, "sampleRate": 0.8}], + command={"profile": -1}, + checks={"was": Eq(0), "slowms": Eq(200), "sampleRate": Eq(0.8)}, + msg="profile should apply slowms and sampleRate even when profiler is off", + ), +] + +# Property [Database Scope]: profile operates on regular databases. +DATABASE_SCOPE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "regular_database", + command={"profile": 0}, + checks={"ok": Eq(1.0)}, + msg="profile should succeed on a regular database", + ), +] -def test_profile_set_level_slowms_samplerate(collection): - """Test setting profile level, slowms, and sampleRate simultaneously.""" - execute_command(collection, {"profile": 1, "slowms": 50, "sampleRate": 0.5}) - result = execute_command(collection, {"profile": -1}) - assertProperties( - result, - {"was": Eq(1), "slowms": Eq(50), "sampleRate": Eq(0.5)}, - msg="Should apply level, slowms, and sampleRate together", - raw_res=True, - ) - execute_command(collection, {"profile": 0, "sampleRate": 1.0}) +COMBINED_TESTS = MULTI_PARAM_TESTS -def test_profile_set_level_slowms_filter(collection): - """Test setting profile level, slowms, and filter simultaneously.""" - execute_command(collection, {"profile": 1, "slowms": 50, "filter": {"op": "query"}}) - result = execute_command(collection, {"profile": -1}) - assertProperties( - result, - {"was": Eq(1), "slowms": Eq(50), "filter": Exists()}, - msg="Should apply level, slowms, and filter together", - raw_res=True, - ) - execute_command(collection, {"profile": 0, "filter": "unset"}) +@pytest.mark.parametrize("test", pytest_params(COMBINED_TESTS)) +def test_profile_combined_params(collection, test): + """Test profile command with multiple parameters.""" + for cmd in test.setup: + execute_command(collection, cmd) + result = execute_command(collection, test.command) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) + execute_command(collection, {"profile": 0, "sampleRate": 1.0, "filter": "unset"}) -def test_profile_set_params_while_disabled(collection): - """Test setting slowms and sampleRate while disabling profiler.""" - execute_command(collection, {"profile": 0, "slowms": 200, "sampleRate": 0.8}) - result = execute_command(collection, {"profile": -1}) - assertProperties( - result, - {"was": Eq(0), "slowms": Eq(200), "sampleRate": Eq(0.8)}, - msg="Should apply slowms and sampleRate even when profiler is off", - raw_res=True, - ) - execute_command(collection, {"profile": 0, "sampleRate": 1.0}) - - -def test_profile_works_on_regular_database(collection): - """Test profile command succeeds on a regular (non-admin) database.""" - result = execute_command(collection, {"profile": 0}) - assertProperties( - result, - {"ok": Eq(1.0)}, - msg="profile should succeed on a regular database", - raw_res=True, - ) +@pytest.mark.parametrize("test", pytest_params(DATABASE_SCOPE_TESTS)) +def test_profile_database_scope(collection, test): + """Test profile command on different databases.""" + result = execute_command(collection, test.command) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) @pytest.mark.admin -def test_profile_works_on_admin_database(collection): +def test_profile_admin_database(collection): """Test profile command succeeds on the admin database.""" result = execute_admin_command(collection, {"profile": -1}) assertProperties( diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_core_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_core_behavior.py index f31f3f784..a10de98a7 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_core_behavior.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_core_behavior.py @@ -8,8 +8,8 @@ import pytest -from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( - DiagnosticTestCase, +from documentdb_tests.compatibility.tests.system.diagnostic.commands.profile.utils.profile_test_case import ( # noqa: E501 + ProfileTestCase, ) from documentdb_tests.framework.assertions import assertProperties from documentdb_tests.framework.executor import execute_command @@ -17,220 +17,205 @@ from documentdb_tests.framework.property_checks import Eq # Property [Valid Levels]: profile levels 0, 1, 2, and -1 all succeed. -VALID_LEVEL_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( +VALID_LEVEL_TESTS: list[ProfileTestCase] = [ + ProfileTestCase( "level_0", + setup=[{"profile": 0}], command={"profile": 0}, checks={"ok": Eq(1.0)}, msg="profile level 0 should succeed", ), - DiagnosticTestCase( + ProfileTestCase( "level_1", + setup=[{"profile": 0}], command={"profile": 1}, checks={"ok": Eq(1.0)}, msg="profile level 1 should succeed", ), - DiagnosticTestCase( + ProfileTestCase( "level_2", + setup=[{"profile": 0}], command={"profile": 2}, checks={"ok": Eq(1.0)}, msg="profile level 2 should succeed", ), - DiagnosticTestCase( + ProfileTestCase( "level_neg1", + setup=[{"profile": 0}], command={"profile": -1}, checks={"ok": Eq(1.0)}, msg="profile level -1 should succeed", ), ] +# Property [Was Field Transitions]: the 'was' field returns the profiling +# level that was active before the current command. +WAS_TRANSITION_TESTS: list[ProfileTestCase] = [ + ProfileTestCase( + "was_0_to_1", + setup=[{"profile": 0}], + command={"profile": 1}, + checks={"was": Eq(0)}, + msg="'was' should be 0 after transition 0->1", + ), + ProfileTestCase( + "was_1_to_2", + setup=[{"profile": 1}], + command={"profile": 2}, + checks={"was": Eq(1)}, + msg="'was' should be 1 after transition 1->2", + ), + ProfileTestCase( + "was_2_to_0", + setup=[{"profile": 2}], + command={"profile": 0}, + checks={"was": Eq(2)}, + msg="'was' should be 2 after transition 2->0", + ), + ProfileTestCase( + "was_0_to_0", + setup=[{"profile": 0}], + command={"profile": 0}, + checks={"was": Eq(0)}, + msg="'was' should be 0 after transition 0->0", + ), +] -@pytest.mark.parametrize("test", pytest_params(VALID_LEVEL_TESTS)) -def test_profile_valid_levels(collection, test): - """Test profile command accepts valid level values.""" - # Reset to level 0 first. - execute_command(collection, {"profile": 0}) - result = execute_command(collection, test.command) - assertProperties(result, test.checks, msg=test.msg, raw_res=True) - # Clean up. - execute_command(collection, {"profile": 0}) - - -def test_profile_was_reflects_previous_level_0_to_1(collection): - """Test 'was' field reflects previous level when transitioning 0 to 1.""" - execute_command(collection, {"profile": 0}) - result = execute_command(collection, {"profile": 1}) - assertProperties(result, {"was": Eq(0)}, msg="was should be 0 after 0->1", raw_res=True) - execute_command(collection, {"profile": 0}) - - -def test_profile_was_reflects_previous_level_1_to_2(collection): - """Test 'was' field reflects previous level when transitioning 1 to 2.""" - execute_command(collection, {"profile": 1}) - result = execute_command(collection, {"profile": 2}) - assertProperties(result, {"was": Eq(1)}, msg="was should be 1 after 1->2", raw_res=True) - execute_command(collection, {"profile": 0}) - - -def test_profile_was_reflects_previous_level_2_to_0(collection): - """Test 'was' field reflects previous level when transitioning 2 to 0.""" - execute_command(collection, {"profile": 2}) - result = execute_command(collection, {"profile": 0}) - assertProperties(result, {"was": Eq(2)}, msg="was should be 2 after 2->0", raw_res=True) - - -def test_profile_was_reflects_same_level_0_to_0(collection): - """Test 'was' field reflects previous level when setting same level 0.""" - execute_command(collection, {"profile": 0}) - result = execute_command(collection, {"profile": 0}) - assertProperties(result, {"was": Eq(0)}, msg="was should be 0 after 0->0", raw_res=True) - - -def test_profile_read_at_level_0(collection): - """Test reading with -1 returns was=0 when level is 0.""" - execute_command(collection, {"profile": 0}) - result = execute_command(collection, {"profile": -1}) - assertProperties( - result, {"was": Eq(0)}, msg="was should be 0 when read at level 0", raw_res=True - ) - - -def test_profile_read_at_level_1(collection): - """Test reading with -1 returns was=1 when level is 1.""" - execute_command(collection, {"profile": 1}) - result = execute_command(collection, {"profile": -1}) - assertProperties( - result, {"was": Eq(1)}, msg="was should be 1 when read at level 1", raw_res=True - ) - execute_command(collection, {"profile": 0}) - - -def test_profile_read_at_level_2(collection): - """Test reading with -1 returns was=2 when level is 2.""" - execute_command(collection, {"profile": 2}) - result = execute_command(collection, {"profile": -1}) - assertProperties( - result, {"was": Eq(2)}, msg="was should be 2 when read at level 2", raw_res=True - ) - execute_command(collection, {"profile": 0}) - +# Property [Level Persistence via Read]: reading with -1 returns the current +# level in the 'was' field. +LEVEL_READ_TESTS: list[ProfileTestCase] = [ + ProfileTestCase( + "read_level_0", + setup=[{"profile": 0}], + command={"profile": -1}, + checks={"was": Eq(0)}, + msg="'was' should be 0 when reading at level 0", + ), + ProfileTestCase( + "read_level_1", + setup=[{"profile": 1}], + command={"profile": -1}, + checks={"was": Eq(1)}, + msg="'was' should be 1 when reading at level 1", + ), + ProfileTestCase( + "read_level_2", + setup=[{"profile": 2}], + command={"profile": -1}, + checks={"was": Eq(2)}, + msg="'was' should be 2 when reading at level 2", + ), +] # Property [Out-of-Range Levels]: integer levels outside {-1, 0, 1, 2} # succeed but do not change the profiling level. -OUT_OF_RANGE_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( +OUT_OF_RANGE_TESTS: list[ProfileTestCase] = [ + ProfileTestCase( "out_of_range_3", - command={"profile": 3}, - checks={"ok": Eq(1.0)}, - msg="profile level 3 should succeed as a no-op", + setup=[{"profile": 1}, {"profile": 3}], + command={"profile": -1}, + checks={"was": Eq(1)}, + msg="profile level 3 should be a no-op", ), - DiagnosticTestCase( + ProfileTestCase( "out_of_range_neg2", - command={"profile": -2}, - checks={"ok": Eq(1.0)}, - msg="profile level -2 should succeed as a no-op", + setup=[{"profile": 2}, {"profile": -2}], + command={"profile": -1}, + checks={"was": Eq(2)}, + msg="profile level -2 should be a no-op", ), - DiagnosticTestCase( + ProfileTestCase( "out_of_range_100", - command={"profile": 100}, - checks={"ok": Eq(1.0)}, - msg="profile level 100 should succeed as a no-op", + setup=[{"profile": 0}, {"profile": 100}], + command={"profile": -1}, + checks={"was": Eq(0)}, + msg="profile level 100 should be a no-op", ), ] - -@pytest.mark.parametrize("test", pytest_params(OUT_OF_RANGE_TESTS)) -def test_profile_out_of_range_levels(collection, test): - """Test profile command accepts out-of-range levels as no-ops.""" - execute_command(collection, {"profile": 1}) - execute_command(collection, test.command) - result = execute_command(collection, {"profile": -1}) - assertProperties( - result, - {"was": Eq(1)}, - msg=test.msg, - raw_res=True, - ) - execute_command(collection, {"profile": 0}) - - # Property [Fractional Level Truncation]: non-integer numeric values are # truncated to integer before being applied. -FRACTIONAL_LEVEL_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( +FRACTIONAL_LEVEL_TESTS: list[ProfileTestCase] = [ + ProfileTestCase( "fractional_1_5", + setup=[{"profile": 0}], command={"profile": 1.5}, checks={"ok": Eq(1.0)}, - msg="profile 1.5 should truncate to level 1", + msg="profile 1.5 should be accepted", ), - DiagnosticTestCase( + ProfileTestCase( "fractional_0_5", + setup=[{"profile": 0}], command={"profile": 0.5}, checks={"ok": Eq(1.0)}, - msg="profile 0.5 should truncate to level 0", + msg="profile 0.5 should be accepted", ), - DiagnosticTestCase( + ProfileTestCase( "fractional_2_9", + setup=[{"profile": 0}], command={"profile": 2.9}, checks={"ok": Eq(1.0)}, - msg="profile 2.9 should truncate to level 2", + msg="profile 2.9 should be accepted", ), ] - -@pytest.mark.parametrize("test", pytest_params(FRACTIONAL_LEVEL_TESTS)) -def test_profile_fractional_levels(collection, test): - """Test profile command accepts fractional levels.""" - execute_command(collection, {"profile": 0}) - result = execute_command(collection, test.command) - assertProperties(result, test.checks, msg=test.msg, raw_res=True) - execute_command(collection, {"profile": 0}) - - # Property [Fractional Level Read-Back]: after setting a fractional level, # reading with -1 returns the truncated integer level. -FRACTIONAL_READBACK_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( +FRACTIONAL_READBACK_TESTS: list[ProfileTestCase] = [ + ProfileTestCase( "readback_1_5", - command={"profile": 1.5}, + setup=[{"profile": 0}, {"profile": 1.5}], + command={"profile": -1}, checks={"was": Eq(1)}, msg="profile 1.5 should read back as level 1", ), - DiagnosticTestCase( + ProfileTestCase( "readback_0_5", - command={"profile": 0.5}, + setup=[{"profile": 0}, {"profile": 0.5}], + command={"profile": -1}, checks={"was": Eq(0)}, msg="profile 0.5 should read back as level 0", ), - DiagnosticTestCase( + ProfileTestCase( "readback_2_9", - command={"profile": 2.9}, + setup=[{"profile": 0}, {"profile": 2.9}], + command={"profile": -1}, checks={"was": Eq(2)}, msg="profile 2.9 should read back as level 2", ), ] +# Property [Idempotency]: repeated identical profile commands produce +# consistent results. +IDEMPOTENCY_TESTS: list[ProfileTestCase] = [ + ProfileTestCase( + "idempotent_set_0", + setup=[{"profile": 0}], + command={"profile": 0}, + checks={"was": Eq(0), "ok": Eq(1.0)}, + msg="Second profile 0 should show was=0", + ), +] -@pytest.mark.parametrize("test", pytest_params(FRACTIONAL_READBACK_TESTS)) -def test_profile_fractional_level_readback(collection, test): - """Test fractional level is truncated to integer on read-back.""" - execute_command(collection, {"profile": 0}) - execute_command(collection, test.command) - result = execute_command(collection, {"profile": -1}) - assertProperties(result, test.checks, msg=test.msg, raw_res=True) - execute_command(collection, {"profile": 0}) +CORE_BEHAVIOR_TESTS = ( + VALID_LEVEL_TESTS + + WAS_TRANSITION_TESTS + + LEVEL_READ_TESTS + + OUT_OF_RANGE_TESTS + + FRACTIONAL_LEVEL_TESTS + + FRACTIONAL_READBACK_TESTS + + IDEMPOTENCY_TESTS +) -def test_profile_idempotent_set(collection): - """Test running profile 0 twice produces consistent results.""" +@pytest.mark.parametrize("test", pytest_params(CORE_BEHAVIOR_TESTS)) +def test_profile_core_behavior(collection, test): + """Test profile command core behavior.""" + for cmd in test.setup: + execute_command(collection, cmd) + result = execute_command(collection, test.command) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) execute_command(collection, {"profile": 0}) - result = execute_command(collection, {"profile": 0}) - assertProperties( - result, - {"was": Eq(0), "ok": Eq(1.0)}, - msg="Second profile 0 should show was=0", - raw_res=True, - ) def test_profile_idempotent_read(collection): diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py index f5cea93a1..b87a56d8b 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py @@ -9,6 +9,9 @@ import pytest +from documentdb_tests.compatibility.tests.system.diagnostic.commands.profile.utils.profile_test_case import ( # noqa: E501 + ProfileTestCase, +) from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( DiagnosticTestCase, ) @@ -17,7 +20,8 @@ from documentdb_tests.framework.parametrize import pytest_params from documentdb_tests.framework.property_checks import Eq, Exists, NotExists -# Property [Filter Object Acceptance]: the filter field accepts object values. +# Property [Filter Object Acceptance]: the filter field accepts object values +# and the special string "unset". FILTER_ACCEPT_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( "empty_object", @@ -43,80 +47,75 @@ checks={"ok": Eq(1.0)}, msg="filter should accept namespace filter", ), + DiagnosticTestCase( + "unset_string", + command={"profile": 1, "filter": "unset"}, + checks={"ok": Eq(1.0)}, + msg="filter should accept the exact string 'unset'", + ), ] @pytest.mark.parametrize("test", pytest_params(FILTER_ACCEPT_TESTS)) -def test_profile_filter_accept_objects(collection, test): - """Test profile filter parameter accepts valid object values.""" +def test_profile_filter_acceptance(collection, test): + """Test profile filter parameter accepts valid values.""" result = execute_command(collection, test.command) assertProperties(result, test.checks, msg=test.msg, raw_res=True) - # Clean up: unset filter and disable profiler. execute_command(collection, {"profile": 0, "filter": "unset"}) -def test_profile_filter_unset_string_accepted(collection): - """Test profile filter accepts the exact string 'unset'.""" - execute_command(collection, {"profile": 1, "filter": {"op": "query"}}) - result = execute_command(collection, {"profile": 1, "filter": "unset"}) - assertProperties( - result, - {"ok": Eq(1.0)}, - msg="filter should accept 'unset' string", - raw_res=True, - ) - execute_command(collection, {"profile": 0}) - - -def test_profile_filter_absent_after_unset(collection): - """Test filter field is absent after unsetting with 'unset'.""" - execute_command(collection, {"profile": 1, "filter": {"op": "query"}}) - execute_command(collection, {"profile": 1, "filter": "unset"}) - result = execute_command(collection, {"profile": -1}) - assertProperties( - result, - {"filter": NotExists()}, - msg="filter should be absent after unset", - raw_res=True, - ) - execute_command(collection, {"profile": 0}) - - -def test_profile_filter_normalization(collection): - """Test profile filter normalizes simple equality to $eq form.""" - execute_command(collection, {"profile": 1, "filter": {"op": "query"}}) - result = execute_command(collection, {"profile": -1}) - assertProperties( - result, - {"filter": Eq({"op": {"$eq": "query"}})}, +# Property [Filter Normalization]: simple equality in the filter is normalized +# to $eq form in the response. +FILTER_NORMALIZATION_TESTS: list[ProfileTestCase] = [ + ProfileTestCase( + "normalize_eq", + setup=[{"profile": 1, "filter": {"op": "query"}}], + command={"profile": -1}, + checks={"filter": Eq({"op": {"$eq": "query"}})}, msg="filter should normalize {op: 'query'} to {op: {$eq: 'query'}}", - raw_res=True, - ) - execute_command(collection, {"profile": 0, "filter": "unset"}) - + ), +] -def test_profile_filter_present_after_set(collection): - """Test filter field is present after setting a filter.""" - execute_command(collection, {"profile": 1, "filter": {"op": "query"}}) - result = execute_command(collection, {"profile": -1}) - assertProperties( - result, - {"filter": Exists()}, +# Property [Filter Lifecycle]: setting a filter makes it visible; unsetting +# removes it from the response. +FILTER_LIFECYCLE_TESTS: list[ProfileTestCase] = [ + ProfileTestCase( + "present_after_set", + setup=[{"profile": 1, "filter": {"op": "query"}}], + command={"profile": -1}, + checks={"filter": Exists()}, msg="filter should be present after setting", - raw_res=True, - ) - execute_command(collection, {"profile": 0, "filter": "unset"}) + ), + ProfileTestCase( + "absent_after_unset", + setup=[ + {"profile": 1, "filter": {"op": "query"}}, + {"profile": 1, "filter": "unset"}, + ], + command={"profile": -1}, + checks={"filter": NotExists()}, + msg="filter should be absent after unsetting", + ), + ProfileTestCase( + "absent_after_cycle", + setup=[ + {"profile": 1, "filter": {"op": "query"}}, + {"profile": 1, "filter": "unset"}, + ], + command={"profile": -1}, + checks={"filter": NotExists()}, + msg="filter should be absent after a set/unset cycle", + ), +] + +FILTER_READBACK_TESTS = FILTER_NORMALIZATION_TESTS + FILTER_LIFECYCLE_TESTS -def test_profile_filter_absent_after_cycle(collection): - """Test filter field is absent after a set/unset cycle.""" - execute_command(collection, {"profile": 1, "filter": {"op": "query"}}) - execute_command(collection, {"profile": 1, "filter": "unset"}) - result = execute_command(collection, {"profile": -1}) - assertProperties( - result, - {"filter": NotExists()}, - msg="filter should be absent after set/unset cycle", - raw_res=True, - ) - execute_command(collection, {"profile": 0}) +@pytest.mark.parametrize("test", pytest_params(FILTER_READBACK_TESTS)) +def test_profile_filter_readback(collection, test): + """Test profile filter state via readback.""" + for cmd in test.setup: + execute_command(collection, cmd) + result = execute_command(collection, test.command) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) + execute_command(collection, {"profile": 0, "filter": "unset"}) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py index 4a378b48a..019ab5705 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py @@ -8,6 +8,9 @@ import pytest +from documentdb_tests.compatibility.tests.system.diagnostic.commands.profile.utils.profile_test_case import ( # noqa: E501 + ProfileTestCase, +) from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( DiagnosticTestCase, ) @@ -75,32 +78,34 @@ def test_profile_response_base_fields(collection, test): assertProperties(result, test.checks, msg=test.msg, raw_res=True) -def test_profile_response_filter_present(collection): - """Test profile response includes filter and note fields when filter is active.""" - # Set a filter. - execute_command(collection, {"profile": 1, "filter": {"op": "query"}}) - result = execute_command(collection, {"profile": -1}) - assertProperties( - result, - {"filter": Exists(), "note": IsType("string")}, - msg="Response should include filter and note when a filter is active", - raw_res=True, - ) - # Clean up: disable profiler and unset filter. - execute_command(collection, {"profile": 0, "filter": "unset"}) +# Property [Filter Response Fields]: when a filter is active the response +# includes filter and note; after unset they are absent. +RESPONSE_FILTER_FIELD_TESTS: list[ProfileTestCase] = [ + ProfileTestCase( + "filter_fields_present", + setup=[{"profile": 1, "filter": {"op": "query"}}], + command={"profile": -1}, + checks={"filter": Exists(), "note": IsType("string")}, + msg="profile response should include filter and note when a filter is active", + ), + ProfileTestCase( + "filter_fields_absent_after_unset", + setup=[ + {"profile": 1, "filter": {"op": "query"}}, + {"profile": 1, "filter": "unset"}, + ], + command={"profile": -1}, + checks={"filter": NotExists(), "note": NotExists()}, + msg="profile response should exclude filter and note after filter is unset", + ), +] -def test_profile_response_filter_absent_after_unset(collection): - """Test profile response excludes filter and note fields after filter is unset.""" - # Set and then unset a filter. - execute_command(collection, {"profile": 1, "filter": {"op": "query"}}) - execute_command(collection, {"profile": 1, "filter": "unset"}) - result = execute_command(collection, {"profile": -1}) - assertProperties( - result, - {"filter": NotExists(), "note": NotExists()}, - msg="Response should not include filter or note after filter is unset", - raw_res=True, - ) - # Clean up. - execute_command(collection, {"profile": 0}) +@pytest.mark.parametrize("test", pytest_params(RESPONSE_FILTER_FIELD_TESTS)) +def test_profile_response_filter_fields(collection, test): + """Test profile response filter-related fields.""" + for cmd in test.setup: + execute_command(collection, cmd) + result = execute_command(collection, test.command) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) + execute_command(collection, {"profile": 0, "filter": "unset"}) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_samplerate.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_samplerate.py index bda002384..45f9db3fd 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_samplerate.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_samplerate.py @@ -8,8 +8,8 @@ import pytest -from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( - DiagnosticTestCase, +from documentdb_tests.compatibility.tests.system.diagnostic.commands.profile.utils.profile_test_case import ( # noqa: E501 + ProfileTestCase, ) from documentdb_tests.framework.assertions import assertProperties from documentdb_tests.framework.executor import execute_command @@ -18,76 +18,72 @@ # Property [sampleRate Persistence]: setting sampleRate persists the value # for subsequent reads. -SAMPLERATE_PERSISTENCE_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( +SAMPLERATE_PERSISTENCE_TESTS: list[ProfileTestCase] = [ + ProfileTestCase( "persist_0_5", - command={"profile": 0, "sampleRate": 0.5}, + setup=[{"profile": 0, "sampleRate": 0.5}], + command={"profile": -1}, checks={"sampleRate": Eq(0.5)}, msg="sampleRate should persist value 0.5", ), - DiagnosticTestCase( + ProfileTestCase( "persist_0_0", - command={"profile": 0, "sampleRate": 0.0}, + setup=[{"profile": 0, "sampleRate": 0.0}], + command={"profile": -1}, checks={"sampleRate": Eq(0.0)}, msg="sampleRate should persist value 0.0", ), - DiagnosticTestCase( + ProfileTestCase( "persist_1_0", - command={"profile": 0, "sampleRate": 1.0}, + setup=[{"profile": 0, "sampleRate": 1.0}], + command={"profile": -1}, checks={"sampleRate": Eq(1.0)}, msg="sampleRate should persist value 1.0", ), ] - -@pytest.mark.parametrize("test", pytest_params(SAMPLERATE_PERSISTENCE_TESTS)) -def test_profile_samplerate_persistence(collection, test): - """Test sampleRate value persists after being set.""" - execute_command(collection, test.command) - result = execute_command(collection, {"profile": -1}) - assertProperties(result, test.checks, msg=test.msg, raw_res=True) - # Restore default. - execute_command(collection, {"profile": 0, "sampleRate": 1.0}) - - -# Property [sampleRate Boundary Acceptance]: sampleRate accepts boundary values -# within [0, 1] using int, Int64, and Decimal128 types. -SAMPLERATE_BOUNDARY_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( +# Property [sampleRate Boundary Acceptance]: sampleRate accepts boundary +# values within [0, 1] using int and double types. +SAMPLERATE_BOUNDARY_TESTS: list[ProfileTestCase] = [ + ProfileTestCase( "boundary_int_0", - command={"profile": 0, "sampleRate": 0}, + setup=[{"profile": 0, "sampleRate": 0}], + command={"profile": -1}, checks={"sampleRate": Eq(0.0)}, msg="sampleRate should accept int 0", ), - DiagnosticTestCase( + ProfileTestCase( "boundary_int_1", - command={"profile": 0, "sampleRate": 1}, + setup=[{"profile": 0, "sampleRate": 1}], + command={"profile": -1}, checks={"sampleRate": Eq(1.0)}, msg="sampleRate should accept int 1", ), ] +# Property [sampleRate Null No-Op]: setting sampleRate to null does not +# change the current value. +SAMPLERATE_NULL_TESTS: list[ProfileTestCase] = [ + ProfileTestCase( + "null_noop", + setup=[ + {"profile": 0, "sampleRate": 0.5}, + {"profile": 0, "sampleRate": None}, + ], + command={"profile": -1}, + checks={"sampleRate": Eq(0.5)}, + msg="sampleRate should remain 0.5 after setting null", + ), +] -@pytest.mark.parametrize("test", pytest_params(SAMPLERATE_BOUNDARY_TESTS)) -def test_profile_samplerate_boundary_values(collection, test): - """Test sampleRate accepts boundary values within the valid range.""" - execute_command(collection, test.command) - result = execute_command(collection, {"profile": -1}) - assertProperties(result, test.checks, msg=test.msg, raw_res=True) - # Restore default. - execute_command(collection, {"profile": 0, "sampleRate": 1.0}) +SAMPLERATE_TESTS = SAMPLERATE_PERSISTENCE_TESTS + SAMPLERATE_BOUNDARY_TESTS + SAMPLERATE_NULL_TESTS -def test_profile_samplerate_null_noop(collection): - """Test sampleRate null is a no-op and does not change the current value.""" - execute_command(collection, {"profile": 0, "sampleRate": 0.5}) - execute_command(collection, {"profile": 0, "sampleRate": None}) - result = execute_command(collection, {"profile": -1}) - assertProperties( - result, - {"sampleRate": Eq(0.5)}, - msg="sampleRate should remain 0.5 after setting null", - raw_res=True, - ) - # Restore default. +@pytest.mark.parametrize("test", pytest_params(SAMPLERATE_TESTS)) +def test_profile_samplerate(collection, test): + """Test profile sampleRate parameter behavior.""" + for cmd in test.setup: + execute_command(collection, cmd) + result = execute_command(collection, test.command) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) execute_command(collection, {"profile": 0, "sampleRate": 1.0}) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py index 9846a5c32..85317a302 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py @@ -8,8 +8,8 @@ import pytest -from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( - DiagnosticTestCase, +from documentdb_tests.compatibility.tests.system.diagnostic.commands.profile.utils.profile_test_case import ( # noqa: E501 + ProfileTestCase, ) from documentdb_tests.framework.assertions import assertProperties from documentdb_tests.framework.executor import execute_command @@ -18,76 +18,78 @@ # Property [slowms Persistence]: setting slowms persists the value for # subsequent reads. -SLOWMS_PERSISTENCE_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( +SLOWMS_PERSISTENCE_TESTS: list[ProfileTestCase] = [ + ProfileTestCase( "persist_200", - command={"profile": 0, "slowms": 200}, + setup=[{"profile": 0, "slowms": 200}], + command={"profile": -1}, checks={"slowms": Eq(200)}, msg="slowms should persist value 200", ), - DiagnosticTestCase( + ProfileTestCase( "persist_0", - command={"profile": 0, "slowms": 0}, + setup=[{"profile": 0, "slowms": 0}], + command={"profile": -1}, checks={"slowms": Eq(0)}, msg="slowms should persist value 0", ), - DiagnosticTestCase( + ProfileTestCase( "persist_neg1", - command={"profile": 0, "slowms": -1}, + setup=[{"profile": 0, "slowms": -1}], + command={"profile": -1}, checks={"slowms": Eq(-1)}, msg="slowms should persist negative value -1", ), ] - -@pytest.mark.parametrize("test", pytest_params(SLOWMS_PERSISTENCE_TESTS)) -def test_profile_slowms_persistence(collection, test): - """Test slowms value persists after being set.""" - execute_command(collection, test.command) - result = execute_command(collection, {"profile": -1}) - assertProperties(result, test.checks, msg=test.msg, raw_res=True) - - # Property [slowms Boundary Values]: slowms accepts the full int32 range # including negative values. -SLOWMS_BOUNDARY_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( +SLOWMS_BOUNDARY_TESTS: list[ProfileTestCase] = [ + ProfileTestCase( "boundary_int32_max", - command={"profile": 0, "slowms": 2_147_483_647}, + setup=[{"profile": 0, "slowms": 2_147_483_647}], + command={"profile": -1}, checks={"slowms": Eq(2_147_483_647)}, msg="slowms should accept INT32_MAX", ), - DiagnosticTestCase( + ProfileTestCase( "boundary_int32_min", - command={"profile": 0, "slowms": -2_147_483_648}, + setup=[{"profile": 0, "slowms": -2_147_483_648}], + command={"profile": -1}, checks={"slowms": Eq(-2_147_483_648)}, msg="slowms should accept INT32_MIN", ), - DiagnosticTestCase( + ProfileTestCase( "boundary_neg100", - command={"profile": 0, "slowms": -100}, + setup=[{"profile": 0, "slowms": -100}], + command={"profile": -1}, checks={"slowms": Eq(-100)}, msg="slowms should accept -100", ), ] +# Property [slowms Null No-Op]: setting slowms to null does not change +# the current value. +SLOWMS_NULL_TESTS: list[ProfileTestCase] = [ + ProfileTestCase( + "null_noop", + setup=[ + {"profile": 0, "slowms": 500}, + {"profile": 0, "slowms": None}, + ], + command={"profile": -1}, + checks={"slowms": Eq(500)}, + msg="slowms should remain 500 after setting null", + ), +] -@pytest.mark.parametrize("test", pytest_params(SLOWMS_BOUNDARY_TESTS)) -def test_profile_slowms_boundary_values(collection, test): - """Test slowms accepts boundary values across the int32 range.""" - execute_command(collection, test.command) - result = execute_command(collection, {"profile": -1}) - assertProperties(result, test.checks, msg=test.msg, raw_res=True) +SLOWMS_TESTS = SLOWMS_PERSISTENCE_TESTS + SLOWMS_BOUNDARY_TESTS + SLOWMS_NULL_TESTS -def test_profile_slowms_null_noop(collection): - """Test slowms null is a no-op and does not change the current value.""" - execute_command(collection, {"profile": 0, "slowms": 500}) - execute_command(collection, {"profile": 0, "slowms": None}) - result = execute_command(collection, {"profile": -1}) - assertProperties( - result, - {"slowms": Eq(500)}, - msg="slowms should remain 500 after setting null", - raw_res=True, - ) +@pytest.mark.parametrize("test", pytest_params(SLOWMS_TESTS)) +def test_profile_slowms(collection, test): + """Test profile slowms parameter behavior.""" + for cmd in test.setup: + execute_command(collection, cmd) + result = execute_command(collection, test.command) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_type_acceptance.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_type_acceptance.py index ec22028c4..b09dd7434 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_type_acceptance.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_type_acceptance.py @@ -106,16 +106,6 @@ ), ] - -@pytest.mark.parametrize("test", pytest_params(PROFILE_TYPE_ACCEPTANCE_TESTS)) -def test_profile_field_type_acceptance(collection, test): - """Test profile field accepts valid numeric BSON types.""" - result = execute_command(collection, test.command) - assertProperties(result, test.checks, msg=test.msg, raw_res=True) - # Reset profiler. - execute_command(collection, {"profile": 0}) - - # Property [slowms Type Acceptance]: the slowms field accepts numeric BSON # types (int, Int64, double, Decimal128) and null. SLOWMS_TYPE_ACCEPTANCE_TESTS: list[DiagnosticTestCase] = [ @@ -151,14 +141,6 @@ def test_profile_field_type_acceptance(collection, test): ), ] - -@pytest.mark.parametrize("test", pytest_params(SLOWMS_TYPE_ACCEPTANCE_TESTS)) -def test_profile_slowms_type_acceptance(collection, test): - """Test slowms field accepts valid numeric BSON types and null.""" - result = execute_command(collection, test.command) - assertProperties(result, test.checks, msg=test.msg, raw_res=True) - - # Property [sampleRate Type Acceptance]: the sampleRate field accepts numeric # BSON types (int, Int64, double, Decimal128) within [0, 1] and null. SAMPLERATE_TYPE_ACCEPTANCE_TESTS: list[DiagnosticTestCase] = [ @@ -218,11 +200,14 @@ def test_profile_slowms_type_acceptance(collection, test): ), ] +TYPE_ACCEPTANCE_TESTS = ( + PROFILE_TYPE_ACCEPTANCE_TESTS + SLOWMS_TYPE_ACCEPTANCE_TESTS + SAMPLERATE_TYPE_ACCEPTANCE_TESTS +) + -@pytest.mark.parametrize("test", pytest_params(SAMPLERATE_TYPE_ACCEPTANCE_TESTS)) -def test_profile_samplerate_type_acceptance(collection, test): - """Test sampleRate field accepts valid numeric BSON types and null.""" +@pytest.mark.parametrize("test", pytest_params(TYPE_ACCEPTANCE_TESTS)) +def test_profile_type_acceptance(collection, test): + """Test profile command field type acceptance.""" result = execute_command(collection, test.command) assertProperties(result, test.checks, msg=test.msg, raw_res=True) - # Restore default. execute_command(collection, {"profile": 0, "sampleRate": 1.0}) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/utils/__init__.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/utils/profile_test_case.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/utils/profile_test_case.py new file mode 100644 index 000000000..69dd36be4 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/utils/profile_test_case.py @@ -0,0 +1,23 @@ +"""Shared test case for profile command tests.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any, Dict, List, Optional + +from documentdb_tests.framework.test_case import BaseTestCase + + +@dataclass(frozen=True) +class ProfileTestCase(BaseTestCase): + """Test case for profile command with optional setup commands. + + Attributes: + setup: Commands to run before the test command to establish state. + command: The command document to execute and assert against. + checks: Mapping of field paths to property check objects. + """ + + setup: List[Dict[str, Any]] = field(default_factory=list) + command: Optional[Dict[str, Any]] = None + checks: Dict[str, Any] = field(default_factory=dict) From 71f679ed26e3fad01f307d836ac7ef158bb80446 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Mon, 15 Jun 2026 15:08:05 -0700 Subject: [PATCH 03/11] use DiagnosticTestCase Signed-off-by: Alina (Xi) Li --- .../profile/test_profile_combined_params.py | 11 ++-- .../profile/test_profile_core_behavior.py | 60 +++++++++---------- .../commands/profile/test_profile_filter.py | 15 ++--- .../test_profile_response_structure.py | 9 +-- .../profile/test_profile_samplerate.py | 22 +++---- .../commands/profile/test_profile_slowms.py | 24 ++++---- .../commands/profile/utils/__init__.py | 0 .../profile/utils/profile_test_case.py | 23 ------- .../diagnostic/utils/diagnostic_test_case.py | 4 +- 9 files changed, 69 insertions(+), 99 deletions(-) delete mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/utils/__init__.py delete mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/utils/profile_test_case.py diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py index 4bd6cfdd6..b4663643f 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py @@ -8,9 +8,6 @@ import pytest -from documentdb_tests.compatibility.tests.system.diagnostic.commands.profile.utils.profile_test_case import ( # noqa: E501 - ProfileTestCase, -) from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( DiagnosticTestCase, ) @@ -21,22 +18,22 @@ # Property [Multi-Parameter Set]: setting level, slowms, sampleRate, and # filter simultaneously applies all values. -MULTI_PARAM_TESTS: list[ProfileTestCase] = [ - ProfileTestCase( +MULTI_PARAM_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( "level_slowms_samplerate", setup=[{"profile": 1, "slowms": 50, "sampleRate": 0.5}], command={"profile": -1}, checks={"was": Eq(1), "slowms": Eq(50), "sampleRate": Eq(0.5)}, msg="profile should apply level, slowms, and sampleRate together", ), - ProfileTestCase( + DiagnosticTestCase( "level_slowms_filter", setup=[{"profile": 1, "slowms": 50, "filter": {"op": "query"}}], command={"profile": -1}, checks={"was": Eq(1), "slowms": Eq(50), "filter": Exists()}, msg="profile should apply level, slowms, and filter together", ), - ProfileTestCase( + DiagnosticTestCase( "params_while_disabled", setup=[{"profile": 0, "slowms": 200, "sampleRate": 0.8}], command={"profile": -1}, diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_core_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_core_behavior.py index a10de98a7..30e97ecb7 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_core_behavior.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_core_behavior.py @@ -8,8 +8,8 @@ import pytest -from documentdb_tests.compatibility.tests.system.diagnostic.commands.profile.utils.profile_test_case import ( # noqa: E501 - ProfileTestCase, +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, ) from documentdb_tests.framework.assertions import assertProperties from documentdb_tests.framework.executor import execute_command @@ -17,29 +17,29 @@ from documentdb_tests.framework.property_checks import Eq # Property [Valid Levels]: profile levels 0, 1, 2, and -1 all succeed. -VALID_LEVEL_TESTS: list[ProfileTestCase] = [ - ProfileTestCase( +VALID_LEVEL_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( "level_0", setup=[{"profile": 0}], command={"profile": 0}, checks={"ok": Eq(1.0)}, msg="profile level 0 should succeed", ), - ProfileTestCase( + DiagnosticTestCase( "level_1", setup=[{"profile": 0}], command={"profile": 1}, checks={"ok": Eq(1.0)}, msg="profile level 1 should succeed", ), - ProfileTestCase( + DiagnosticTestCase( "level_2", setup=[{"profile": 0}], command={"profile": 2}, checks={"ok": Eq(1.0)}, msg="profile level 2 should succeed", ), - ProfileTestCase( + DiagnosticTestCase( "level_neg1", setup=[{"profile": 0}], command={"profile": -1}, @@ -50,29 +50,29 @@ # Property [Was Field Transitions]: the 'was' field returns the profiling # level that was active before the current command. -WAS_TRANSITION_TESTS: list[ProfileTestCase] = [ - ProfileTestCase( +WAS_TRANSITION_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( "was_0_to_1", setup=[{"profile": 0}], command={"profile": 1}, checks={"was": Eq(0)}, msg="'was' should be 0 after transition 0->1", ), - ProfileTestCase( + DiagnosticTestCase( "was_1_to_2", setup=[{"profile": 1}], command={"profile": 2}, checks={"was": Eq(1)}, msg="'was' should be 1 after transition 1->2", ), - ProfileTestCase( + DiagnosticTestCase( "was_2_to_0", setup=[{"profile": 2}], command={"profile": 0}, checks={"was": Eq(2)}, msg="'was' should be 2 after transition 2->0", ), - ProfileTestCase( + DiagnosticTestCase( "was_0_to_0", setup=[{"profile": 0}], command={"profile": 0}, @@ -83,22 +83,22 @@ # Property [Level Persistence via Read]: reading with -1 returns the current # level in the 'was' field. -LEVEL_READ_TESTS: list[ProfileTestCase] = [ - ProfileTestCase( +LEVEL_READ_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( "read_level_0", setup=[{"profile": 0}], command={"profile": -1}, checks={"was": Eq(0)}, msg="'was' should be 0 when reading at level 0", ), - ProfileTestCase( + DiagnosticTestCase( "read_level_1", setup=[{"profile": 1}], command={"profile": -1}, checks={"was": Eq(1)}, msg="'was' should be 1 when reading at level 1", ), - ProfileTestCase( + DiagnosticTestCase( "read_level_2", setup=[{"profile": 2}], command={"profile": -1}, @@ -109,22 +109,22 @@ # Property [Out-of-Range Levels]: integer levels outside {-1, 0, 1, 2} # succeed but do not change the profiling level. -OUT_OF_RANGE_TESTS: list[ProfileTestCase] = [ - ProfileTestCase( +OUT_OF_RANGE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( "out_of_range_3", setup=[{"profile": 1}, {"profile": 3}], command={"profile": -1}, checks={"was": Eq(1)}, msg="profile level 3 should be a no-op", ), - ProfileTestCase( + DiagnosticTestCase( "out_of_range_neg2", setup=[{"profile": 2}, {"profile": -2}], command={"profile": -1}, checks={"was": Eq(2)}, msg="profile level -2 should be a no-op", ), - ProfileTestCase( + DiagnosticTestCase( "out_of_range_100", setup=[{"profile": 0}, {"profile": 100}], command={"profile": -1}, @@ -135,22 +135,22 @@ # Property [Fractional Level Truncation]: non-integer numeric values are # truncated to integer before being applied. -FRACTIONAL_LEVEL_TESTS: list[ProfileTestCase] = [ - ProfileTestCase( +FRACTIONAL_LEVEL_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( "fractional_1_5", setup=[{"profile": 0}], command={"profile": 1.5}, checks={"ok": Eq(1.0)}, msg="profile 1.5 should be accepted", ), - ProfileTestCase( + DiagnosticTestCase( "fractional_0_5", setup=[{"profile": 0}], command={"profile": 0.5}, checks={"ok": Eq(1.0)}, msg="profile 0.5 should be accepted", ), - ProfileTestCase( + DiagnosticTestCase( "fractional_2_9", setup=[{"profile": 0}], command={"profile": 2.9}, @@ -161,22 +161,22 @@ # Property [Fractional Level Read-Back]: after setting a fractional level, # reading with -1 returns the truncated integer level. -FRACTIONAL_READBACK_TESTS: list[ProfileTestCase] = [ - ProfileTestCase( +FRACTIONAL_READBACK_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( "readback_1_5", setup=[{"profile": 0}, {"profile": 1.5}], command={"profile": -1}, checks={"was": Eq(1)}, msg="profile 1.5 should read back as level 1", ), - ProfileTestCase( + DiagnosticTestCase( "readback_0_5", setup=[{"profile": 0}, {"profile": 0.5}], command={"profile": -1}, checks={"was": Eq(0)}, msg="profile 0.5 should read back as level 0", ), - ProfileTestCase( + DiagnosticTestCase( "readback_2_9", setup=[{"profile": 0}, {"profile": 2.9}], command={"profile": -1}, @@ -187,8 +187,8 @@ # Property [Idempotency]: repeated identical profile commands produce # consistent results. -IDEMPOTENCY_TESTS: list[ProfileTestCase] = [ - ProfileTestCase( +IDEMPOTENCY_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( "idempotent_set_0", setup=[{"profile": 0}], command={"profile": 0}, diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py index b87a56d8b..3884005c0 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py @@ -9,9 +9,6 @@ import pytest -from documentdb_tests.compatibility.tests.system.diagnostic.commands.profile.utils.profile_test_case import ( # noqa: E501 - ProfileTestCase, -) from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( DiagnosticTestCase, ) @@ -66,8 +63,8 @@ def test_profile_filter_acceptance(collection, test): # Property [Filter Normalization]: simple equality in the filter is normalized # to $eq form in the response. -FILTER_NORMALIZATION_TESTS: list[ProfileTestCase] = [ - ProfileTestCase( +FILTER_NORMALIZATION_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( "normalize_eq", setup=[{"profile": 1, "filter": {"op": "query"}}], command={"profile": -1}, @@ -78,15 +75,15 @@ def test_profile_filter_acceptance(collection, test): # Property [Filter Lifecycle]: setting a filter makes it visible; unsetting # removes it from the response. -FILTER_LIFECYCLE_TESTS: list[ProfileTestCase] = [ - ProfileTestCase( +FILTER_LIFECYCLE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( "present_after_set", setup=[{"profile": 1, "filter": {"op": "query"}}], command={"profile": -1}, checks={"filter": Exists()}, msg="filter should be present after setting", ), - ProfileTestCase( + DiagnosticTestCase( "absent_after_unset", setup=[ {"profile": 1, "filter": {"op": "query"}}, @@ -96,7 +93,7 @@ def test_profile_filter_acceptance(collection, test): checks={"filter": NotExists()}, msg="filter should be absent after unsetting", ), - ProfileTestCase( + DiagnosticTestCase( "absent_after_cycle", setup=[ {"profile": 1, "filter": {"op": "query"}}, diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py index 019ab5705..1af46f0a4 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py @@ -8,9 +8,6 @@ import pytest -from documentdb_tests.compatibility.tests.system.diagnostic.commands.profile.utils.profile_test_case import ( # noqa: E501 - ProfileTestCase, -) from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( DiagnosticTestCase, ) @@ -80,15 +77,15 @@ def test_profile_response_base_fields(collection, test): # Property [Filter Response Fields]: when a filter is active the response # includes filter and note; after unset they are absent. -RESPONSE_FILTER_FIELD_TESTS: list[ProfileTestCase] = [ - ProfileTestCase( +RESPONSE_FILTER_FIELD_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( "filter_fields_present", setup=[{"profile": 1, "filter": {"op": "query"}}], command={"profile": -1}, checks={"filter": Exists(), "note": IsType("string")}, msg="profile response should include filter and note when a filter is active", ), - ProfileTestCase( + DiagnosticTestCase( "filter_fields_absent_after_unset", setup=[ {"profile": 1, "filter": {"op": "query"}}, diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_samplerate.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_samplerate.py index 45f9db3fd..4ad09e53c 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_samplerate.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_samplerate.py @@ -8,8 +8,8 @@ import pytest -from documentdb_tests.compatibility.tests.system.diagnostic.commands.profile.utils.profile_test_case import ( # noqa: E501 - ProfileTestCase, +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, ) from documentdb_tests.framework.assertions import assertProperties from documentdb_tests.framework.executor import execute_command @@ -18,22 +18,22 @@ # Property [sampleRate Persistence]: setting sampleRate persists the value # for subsequent reads. -SAMPLERATE_PERSISTENCE_TESTS: list[ProfileTestCase] = [ - ProfileTestCase( +SAMPLERATE_PERSISTENCE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( "persist_0_5", setup=[{"profile": 0, "sampleRate": 0.5}], command={"profile": -1}, checks={"sampleRate": Eq(0.5)}, msg="sampleRate should persist value 0.5", ), - ProfileTestCase( + DiagnosticTestCase( "persist_0_0", setup=[{"profile": 0, "sampleRate": 0.0}], command={"profile": -1}, checks={"sampleRate": Eq(0.0)}, msg="sampleRate should persist value 0.0", ), - ProfileTestCase( + DiagnosticTestCase( "persist_1_0", setup=[{"profile": 0, "sampleRate": 1.0}], command={"profile": -1}, @@ -44,15 +44,15 @@ # Property [sampleRate Boundary Acceptance]: sampleRate accepts boundary # values within [0, 1] using int and double types. -SAMPLERATE_BOUNDARY_TESTS: list[ProfileTestCase] = [ - ProfileTestCase( +SAMPLERATE_BOUNDARY_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( "boundary_int_0", setup=[{"profile": 0, "sampleRate": 0}], command={"profile": -1}, checks={"sampleRate": Eq(0.0)}, msg="sampleRate should accept int 0", ), - ProfileTestCase( + DiagnosticTestCase( "boundary_int_1", setup=[{"profile": 0, "sampleRate": 1}], command={"profile": -1}, @@ -63,8 +63,8 @@ # Property [sampleRate Null No-Op]: setting sampleRate to null does not # change the current value. -SAMPLERATE_NULL_TESTS: list[ProfileTestCase] = [ - ProfileTestCase( +SAMPLERATE_NULL_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( "null_noop", setup=[ {"profile": 0, "sampleRate": 0.5}, diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py index 85317a302..31f1ecdba 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py @@ -8,8 +8,8 @@ import pytest -from documentdb_tests.compatibility.tests.system.diagnostic.commands.profile.utils.profile_test_case import ( # noqa: E501 - ProfileTestCase, +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, ) from documentdb_tests.framework.assertions import assertProperties from documentdb_tests.framework.executor import execute_command @@ -18,22 +18,22 @@ # Property [slowms Persistence]: setting slowms persists the value for # subsequent reads. -SLOWMS_PERSISTENCE_TESTS: list[ProfileTestCase] = [ - ProfileTestCase( +SLOWMS_PERSISTENCE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( "persist_200", setup=[{"profile": 0, "slowms": 200}], command={"profile": -1}, checks={"slowms": Eq(200)}, msg="slowms should persist value 200", ), - ProfileTestCase( + DiagnosticTestCase( "persist_0", setup=[{"profile": 0, "slowms": 0}], command={"profile": -1}, checks={"slowms": Eq(0)}, msg="slowms should persist value 0", ), - ProfileTestCase( + DiagnosticTestCase( "persist_neg1", setup=[{"profile": 0, "slowms": -1}], command={"profile": -1}, @@ -44,22 +44,22 @@ # Property [slowms Boundary Values]: slowms accepts the full int32 range # including negative values. -SLOWMS_BOUNDARY_TESTS: list[ProfileTestCase] = [ - ProfileTestCase( +SLOWMS_BOUNDARY_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( "boundary_int32_max", setup=[{"profile": 0, "slowms": 2_147_483_647}], command={"profile": -1}, checks={"slowms": Eq(2_147_483_647)}, msg="slowms should accept INT32_MAX", ), - ProfileTestCase( + DiagnosticTestCase( "boundary_int32_min", setup=[{"profile": 0, "slowms": -2_147_483_648}], command={"profile": -1}, checks={"slowms": Eq(-2_147_483_648)}, msg="slowms should accept INT32_MIN", ), - ProfileTestCase( + DiagnosticTestCase( "boundary_neg100", setup=[{"profile": 0, "slowms": -100}], command={"profile": -1}, @@ -70,8 +70,8 @@ # Property [slowms Null No-Op]: setting slowms to null does not change # the current value. -SLOWMS_NULL_TESTS: list[ProfileTestCase] = [ - ProfileTestCase( +SLOWMS_NULL_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( "null_noop", setup=[ {"profile": 0, "slowms": 500}, diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/utils/__init__.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/utils/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/utils/profile_test_case.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/utils/profile_test_case.py deleted file mode 100644 index 69dd36be4..000000000 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/utils/profile_test_case.py +++ /dev/null @@ -1,23 +0,0 @@ -"""Shared test case for profile command tests.""" - -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import Any, Dict, List, Optional - -from documentdb_tests.framework.test_case import BaseTestCase - - -@dataclass(frozen=True) -class ProfileTestCase(BaseTestCase): - """Test case for profile command with optional setup commands. - - Attributes: - setup: Commands to run before the test command to establish state. - command: The command document to execute and assert against. - checks: Mapping of field paths to property check objects. - """ - - setup: List[Dict[str, Any]] = field(default_factory=list) - command: Optional[Dict[str, Any]] = None - checks: Dict[str, Any] = field(default_factory=dict) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/utils/diagnostic_test_case.py b/documentdb_tests/compatibility/tests/system/diagnostic/utils/diagnostic_test_case.py index 39adb13d7..3d08d60c0 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/utils/diagnostic_test_case.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/utils/diagnostic_test_case.py @@ -1,7 +1,7 @@ """Shared test case for diagnostic command tests.""" from dataclasses import dataclass, field -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional from documentdb_tests.framework.test_case import BaseTestCase @@ -11,11 +11,13 @@ class DiagnosticTestCase(BaseTestCase): """Test case for diagnostic command tests. Attributes: + setup: Commands to run before the test command to establish state. command: The command document to execute. use_admin: If True, execute against the admin database. checks: Mapping of dotted field paths to property check objects. """ + setup: List[Dict[str, Any]] = field(default_factory=list) command: Optional[Dict[str, Any]] = None use_admin: bool = True checks: Dict[str, Any] = field(default_factory=dict) From ba0b28060bfb4c79cd08cf13b9f7ace05583e684 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Wed, 17 Jun 2026 11:20:59 -0700 Subject: [PATCH 04/11] convert test_profile_admin_database Signed-off-by: Alina (Xi) Li --- .../profile/test_profile_combined_params.py | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py index b4663643f..96a4a554f 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py @@ -42,14 +42,22 @@ ), ] -# Property [Database Scope]: profile operates on regular databases. +# Property [Database Scope]: profile operates on both regular and admin databases. DATABASE_SCOPE_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( "regular_database", command={"profile": 0}, + use_admin=False, checks={"ok": Eq(1.0)}, msg="profile should succeed on a regular database", ), + DiagnosticTestCase( + "admin_database", + command={"profile": -1}, + use_admin=True, + checks={"ok": Eq(1.0)}, + msg="profile should succeed on the admin database", + ), ] COMBINED_TESTS = MULTI_PARAM_TESTS @@ -68,22 +76,13 @@ def test_profile_combined_params(collection, test): @pytest.mark.parametrize("test", pytest_params(DATABASE_SCOPE_TESTS)) def test_profile_database_scope(collection, test): """Test profile command on different databases.""" - result = execute_command(collection, test.command) + if test.use_admin: + result = execute_admin_command(collection, test.command) + else: + result = execute_command(collection, test.command) assertProperties(result, test.checks, msg=test.msg, raw_res=True) -@pytest.mark.admin -def test_profile_admin_database(collection): - """Test profile command succeeds on the admin database.""" - result = execute_admin_command(collection, {"profile": -1}) - assertProperties( - result, - {"ok": Eq(1.0)}, - msg="profile should succeed on the admin database", - raw_res=True, - ) - - def test_profile_consecutive_reads_consistent(collection): """Test two consecutive profile reads return identical results.""" execute_command(collection, {"profile": 0}) From 797d907c7db3f2a97e7830c039e76952afc9a726 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Wed, 17 Jun 2026 11:24:33 -0700 Subject: [PATCH 05/11] remove test_profile_consecutive_reads_consistent as it is dup Signed-off-by: Alina (Xi) Li --- .../profile/test_profile_combined_params.py | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py index 96a4a554f..fa29bc3b8 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py @@ -1,7 +1,7 @@ """Tests for profile command with multiple parameters and cross-database behavior. -Validates setting multiple parameters simultaneously, cross-database behavior, -and consistent reads. All tests in this file verify success cases only. +Validates setting multiple parameters simultaneously and cross-database +behavior. All tests in this file verify success cases only. """ from __future__ import annotations @@ -81,20 +81,3 @@ def test_profile_database_scope(collection, test): else: result = execute_command(collection, test.command) assertProperties(result, test.checks, msg=test.msg, raw_res=True) - - -def test_profile_consecutive_reads_consistent(collection): - """Test two consecutive profile reads return identical results.""" - execute_command(collection, {"profile": 0}) - result1 = execute_command(collection, {"profile": -1}) - result2 = execute_command(collection, {"profile": -1}) - assertProperties( - result2, - { - "was": Eq(result1["was"]), - "slowms": Eq(result1["slowms"]), - "sampleRate": Eq(result1["sampleRate"]), - }, - msg="Consecutive reads should return identical results", - raw_res=True, - ) From a24d6c802ae0251d83237a922801184f712a2037 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Wed, 17 Jun 2026 11:33:08 -0700 Subject: [PATCH 06/11] split error tests Signed-off-by: Alina (Xi) Li --- .../commands/profile/test_profile_errors.py | 290 +--------------- .../profile/test_profile_type_errors.py | 310 ++++++++++++++++++ 2 files changed, 314 insertions(+), 286 deletions(-) create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_type_errors.py diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_errors.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_errors.py index 5ce99bcb3..5ec5b8409 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_errors.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_errors.py @@ -1,16 +1,12 @@ -"""Tests for profile command error cases. +"""Tests for profile command validation error cases. -Consolidates ALL error cases: type rejection for profile/slowms/sampleRate, -sampleRate range validation, filter rejection, unrecognized fields, case -sensitivity, and null required field. +Validates sampleRate range rejection, filter value rejection, unrecognized +field rejection, and case-sensitive command name rejection. """ from __future__ import annotations -from datetime import datetime, timezone - import pytest -from bson import Binary, Code, MaxKey, MinKey, ObjectId, Regex, Timestamp from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( DiagnosticTestCase, @@ -19,285 +15,11 @@ from documentdb_tests.framework.error_codes import ( BAD_VALUE_ERROR, COMMAND_NOT_FOUND_ERROR, - MISSING_FIELD_ERROR, - TYPE_MISMATCH_ERROR, UNRECOGNIZED_COMMAND_FIELD_ERROR, ) from documentdb_tests.framework.executor import execute_command from documentdb_tests.framework.parametrize import pytest_params -# Property [Profile Type Rejection]: non-numeric types for the profile field -# are rejected with TYPE_MISMATCH_ERROR. -PROFILE_TYPE_REJECTION_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( - "profile_bool_true", - command={"profile": True}, - error_code=TYPE_MISMATCH_ERROR, - msg="profile should reject bool true", - ), - DiagnosticTestCase( - "profile_bool_false", - command={"profile": False}, - error_code=TYPE_MISMATCH_ERROR, - msg="profile should reject bool false", - ), - DiagnosticTestCase( - "profile_string", - command={"profile": "hello"}, - error_code=TYPE_MISMATCH_ERROR, - msg="profile should reject string", - ), - DiagnosticTestCase( - "profile_array", - command={"profile": [1]}, - error_code=TYPE_MISMATCH_ERROR, - msg="profile should reject array", - ), - DiagnosticTestCase( - "profile_empty_array", - command={"profile": []}, - error_code=TYPE_MISMATCH_ERROR, - msg="profile should reject empty array", - ), - DiagnosticTestCase( - "profile_object", - command={"profile": {"a": 1}}, - error_code=TYPE_MISMATCH_ERROR, - msg="profile should reject object", - ), - DiagnosticTestCase( - "profile_empty_object", - command={"profile": {}}, - error_code=TYPE_MISMATCH_ERROR, - msg="profile should reject empty object", - ), - DiagnosticTestCase( - "profile_objectid", - command={"profile": ObjectId()}, - error_code=TYPE_MISMATCH_ERROR, - msg="profile should reject ObjectId", - ), - DiagnosticTestCase( - "profile_binary", - command={"profile": Binary(b"")}, - error_code=TYPE_MISMATCH_ERROR, - msg="profile should reject Binary", - ), - DiagnosticTestCase( - "profile_regex", - command={"profile": Regex("test")}, - error_code=TYPE_MISMATCH_ERROR, - msg="profile should reject Regex", - ), - DiagnosticTestCase( - "profile_timestamp", - command={"profile": Timestamp(0, 0)}, - error_code=TYPE_MISMATCH_ERROR, - msg="profile should reject Timestamp", - ), - DiagnosticTestCase( - "profile_code", - command={"profile": Code("function(){}")}, - error_code=TYPE_MISMATCH_ERROR, - msg="profile should reject Code", - ), - DiagnosticTestCase( - "profile_datetime", - command={"profile": datetime(2024, 1, 1, tzinfo=timezone.utc)}, - error_code=TYPE_MISMATCH_ERROR, - msg="profile should reject datetime", - ), - DiagnosticTestCase( - "profile_minkey", - command={"profile": MinKey()}, - error_code=TYPE_MISMATCH_ERROR, - msg="profile should reject MinKey", - ), - DiagnosticTestCase( - "profile_maxkey", - command={"profile": MaxKey()}, - error_code=TYPE_MISMATCH_ERROR, - msg="profile should reject MaxKey", - ), -] - -# Property [Profile Null Rejection]: null for the required profile field -# produces MISSING_FIELD_ERROR. -PROFILE_NULL_REJECTION_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( - "profile_null", - command={"profile": None}, - error_code=MISSING_FIELD_ERROR, - msg="profile should reject null with missing field error", - ), -] - -# Property [slowms Type Rejection]: non-numeric types for the slowms field -# are rejected with TYPE_MISMATCH_ERROR. -SLOWMS_TYPE_REJECTION_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( - "slowms_string", - command={"profile": 0, "slowms": "hello"}, - error_code=TYPE_MISMATCH_ERROR, - msg="slowms should reject string", - ), - DiagnosticTestCase( - "slowms_bool_true", - command={"profile": 0, "slowms": True}, - error_code=TYPE_MISMATCH_ERROR, - msg="slowms should reject bool true", - ), - DiagnosticTestCase( - "slowms_bool_false", - command={"profile": 0, "slowms": False}, - error_code=TYPE_MISMATCH_ERROR, - msg="slowms should reject bool false", - ), - DiagnosticTestCase( - "slowms_array", - command={"profile": 0, "slowms": [1]}, - error_code=TYPE_MISMATCH_ERROR, - msg="slowms should reject array", - ), - DiagnosticTestCase( - "slowms_object", - command={"profile": 0, "slowms": {"a": 1}}, - error_code=TYPE_MISMATCH_ERROR, - msg="slowms should reject object", - ), - DiagnosticTestCase( - "slowms_objectid", - command={"profile": 0, "slowms": ObjectId()}, - error_code=TYPE_MISMATCH_ERROR, - msg="slowms should reject ObjectId", - ), - DiagnosticTestCase( - "slowms_regex", - command={"profile": 0, "slowms": Regex("test")}, - error_code=TYPE_MISMATCH_ERROR, - msg="slowms should reject Regex", - ), - DiagnosticTestCase( - "slowms_timestamp", - command={"profile": 0, "slowms": Timestamp(0, 0)}, - error_code=TYPE_MISMATCH_ERROR, - msg="slowms should reject Timestamp", - ), - DiagnosticTestCase( - "slowms_binary", - command={"profile": 0, "slowms": Binary(b"")}, - error_code=TYPE_MISMATCH_ERROR, - msg="slowms should reject Binary", - ), - DiagnosticTestCase( - "slowms_code", - command={"profile": 0, "slowms": Code("function(){}")}, - error_code=TYPE_MISMATCH_ERROR, - msg="slowms should reject Code", - ), - DiagnosticTestCase( - "slowms_minkey", - command={"profile": 0, "slowms": MinKey()}, - error_code=TYPE_MISMATCH_ERROR, - msg="slowms should reject MinKey", - ), - DiagnosticTestCase( - "slowms_maxkey", - command={"profile": 0, "slowms": MaxKey()}, - error_code=TYPE_MISMATCH_ERROR, - msg="slowms should reject MaxKey", - ), - DiagnosticTestCase( - "slowms_datetime", - command={"profile": 0, "slowms": datetime(2024, 1, 1, tzinfo=timezone.utc)}, - error_code=TYPE_MISMATCH_ERROR, - msg="slowms should reject datetime", - ), -] - -# Property [sampleRate Type Rejection]: non-numeric types for the sampleRate -# field are rejected with TYPE_MISMATCH_ERROR. -SAMPLERATE_TYPE_REJECTION_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( - "samplerate_string", - command={"profile": 0, "sampleRate": "hello"}, - error_code=TYPE_MISMATCH_ERROR, - msg="sampleRate should reject string", - ), - DiagnosticTestCase( - "samplerate_bool_true", - command={"profile": 0, "sampleRate": True}, - error_code=TYPE_MISMATCH_ERROR, - msg="sampleRate should reject bool true", - ), - DiagnosticTestCase( - "samplerate_bool_false", - command={"profile": 0, "sampleRate": False}, - error_code=TYPE_MISMATCH_ERROR, - msg="sampleRate should reject bool false", - ), - DiagnosticTestCase( - "samplerate_array", - command={"profile": 0, "sampleRate": [1]}, - error_code=TYPE_MISMATCH_ERROR, - msg="sampleRate should reject array", - ), - DiagnosticTestCase( - "samplerate_object", - command={"profile": 0, "sampleRate": {"a": 1}}, - error_code=TYPE_MISMATCH_ERROR, - msg="sampleRate should reject object", - ), - DiagnosticTestCase( - "samplerate_objectid", - command={"profile": 0, "sampleRate": ObjectId()}, - error_code=TYPE_MISMATCH_ERROR, - msg="sampleRate should reject ObjectId", - ), - DiagnosticTestCase( - "samplerate_regex", - command={"profile": 0, "sampleRate": Regex("test")}, - error_code=TYPE_MISMATCH_ERROR, - msg="sampleRate should reject Regex", - ), - DiagnosticTestCase( - "samplerate_timestamp", - command={"profile": 0, "sampleRate": Timestamp(0, 0)}, - error_code=TYPE_MISMATCH_ERROR, - msg="sampleRate should reject Timestamp", - ), - DiagnosticTestCase( - "samplerate_binary", - command={"profile": 0, "sampleRate": Binary(b"")}, - error_code=TYPE_MISMATCH_ERROR, - msg="sampleRate should reject Binary", - ), - DiagnosticTestCase( - "samplerate_code", - command={"profile": 0, "sampleRate": Code("function(){}")}, - error_code=TYPE_MISMATCH_ERROR, - msg="sampleRate should reject Code", - ), - DiagnosticTestCase( - "samplerate_minkey", - command={"profile": 0, "sampleRate": MinKey()}, - error_code=TYPE_MISMATCH_ERROR, - msg="sampleRate should reject MinKey", - ), - DiagnosticTestCase( - "samplerate_maxkey", - command={"profile": 0, "sampleRate": MaxKey()}, - error_code=TYPE_MISMATCH_ERROR, - msg="sampleRate should reject MaxKey", - ), - DiagnosticTestCase( - "samplerate_datetime", - command={"profile": 0, "sampleRate": datetime(2024, 1, 1, tzinfo=timezone.utc)}, - error_code=TYPE_MISMATCH_ERROR, - msg="sampleRate should reject datetime", - ), -] - # Property [sampleRate Range Rejection]: sampleRate values outside [0, 1] # are rejected with BAD_VALUE_ERROR. SAMPLERATE_RANGE_REJECTION_TESTS: list[DiagnosticTestCase] = [ @@ -409,11 +131,7 @@ ] PROFILE_ERROR_TESTS = ( - PROFILE_TYPE_REJECTION_TESTS - + PROFILE_NULL_REJECTION_TESTS - + SLOWMS_TYPE_REJECTION_TESTS - + SAMPLERATE_TYPE_REJECTION_TESTS - + SAMPLERATE_RANGE_REJECTION_TESTS + SAMPLERATE_RANGE_REJECTION_TESTS + FILTER_REJECTION_TESTS + UNRECOGNIZED_FIELD_TESTS + CASE_SENSITIVITY_TESTS diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_type_errors.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_type_errors.py new file mode 100644 index 000000000..638d0719f --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_type_errors.py @@ -0,0 +1,310 @@ +"""Tests for profile command BSON type rejection. + +Validates that non-numeric types for the profile, slowms, and sampleRate +fields are rejected with TYPE_MISMATCH_ERROR, and that null for the required +profile field produces MISSING_FIELD_ERROR. +""" + +from __future__ import annotations + +from datetime import datetime, timezone + +import pytest +from bson import Binary, Code, MaxKey, MinKey, ObjectId, Regex, Timestamp + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertFailureCode +from documentdb_tests.framework.error_codes import ( + MISSING_FIELD_ERROR, + TYPE_MISMATCH_ERROR, +) +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params + +# Property [Profile Type Rejection]: non-numeric types for the profile field +# are rejected with TYPE_MISMATCH_ERROR. +PROFILE_TYPE_REJECTION_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "profile_bool_true", + command={"profile": True}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject bool true", + ), + DiagnosticTestCase( + "profile_bool_false", + command={"profile": False}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject bool false", + ), + DiagnosticTestCase( + "profile_string", + command={"profile": "hello"}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject string", + ), + DiagnosticTestCase( + "profile_array", + command={"profile": [1]}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject array", + ), + DiagnosticTestCase( + "profile_empty_array", + command={"profile": []}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject empty array", + ), + DiagnosticTestCase( + "profile_object", + command={"profile": {"a": 1}}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject object", + ), + DiagnosticTestCase( + "profile_empty_object", + command={"profile": {}}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject empty object", + ), + DiagnosticTestCase( + "profile_objectid", + command={"profile": ObjectId()}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject ObjectId", + ), + DiagnosticTestCase( + "profile_binary", + command={"profile": Binary(b"")}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject Binary", + ), + DiagnosticTestCase( + "profile_regex", + command={"profile": Regex("test")}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject Regex", + ), + DiagnosticTestCase( + "profile_timestamp", + command={"profile": Timestamp(0, 0)}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject Timestamp", + ), + DiagnosticTestCase( + "profile_code", + command={"profile": Code("function(){}")}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject Code", + ), + DiagnosticTestCase( + "profile_datetime", + command={"profile": datetime(2024, 1, 1, tzinfo=timezone.utc)}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject datetime", + ), + DiagnosticTestCase( + "profile_minkey", + command={"profile": MinKey()}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject MinKey", + ), + DiagnosticTestCase( + "profile_maxkey", + command={"profile": MaxKey()}, + error_code=TYPE_MISMATCH_ERROR, + msg="profile should reject MaxKey", + ), +] + +# Property [Profile Null Rejection]: null for the required profile field +# produces MISSING_FIELD_ERROR. +PROFILE_NULL_REJECTION_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "profile_null", + command={"profile": None}, + error_code=MISSING_FIELD_ERROR, + msg="profile should reject null with missing field error", + ), +] + +# Property [slowms Type Rejection]: non-numeric types for the slowms field +# are rejected with TYPE_MISMATCH_ERROR. +SLOWMS_TYPE_REJECTION_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "slowms_string", + command={"profile": 0, "slowms": "hello"}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject string", + ), + DiagnosticTestCase( + "slowms_bool_true", + command={"profile": 0, "slowms": True}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject bool true", + ), + DiagnosticTestCase( + "slowms_bool_false", + command={"profile": 0, "slowms": False}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject bool false", + ), + DiagnosticTestCase( + "slowms_array", + command={"profile": 0, "slowms": [1]}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject array", + ), + DiagnosticTestCase( + "slowms_object", + command={"profile": 0, "slowms": {"a": 1}}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject object", + ), + DiagnosticTestCase( + "slowms_objectid", + command={"profile": 0, "slowms": ObjectId()}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject ObjectId", + ), + DiagnosticTestCase( + "slowms_regex", + command={"profile": 0, "slowms": Regex("test")}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject Regex", + ), + DiagnosticTestCase( + "slowms_timestamp", + command={"profile": 0, "slowms": Timestamp(0, 0)}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject Timestamp", + ), + DiagnosticTestCase( + "slowms_binary", + command={"profile": 0, "slowms": Binary(b"")}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject Binary", + ), + DiagnosticTestCase( + "slowms_code", + command={"profile": 0, "slowms": Code("function(){}")}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject Code", + ), + DiagnosticTestCase( + "slowms_minkey", + command={"profile": 0, "slowms": MinKey()}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject MinKey", + ), + DiagnosticTestCase( + "slowms_maxkey", + command={"profile": 0, "slowms": MaxKey()}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject MaxKey", + ), + DiagnosticTestCase( + "slowms_datetime", + command={"profile": 0, "slowms": datetime(2024, 1, 1, tzinfo=timezone.utc)}, + error_code=TYPE_MISMATCH_ERROR, + msg="slowms should reject datetime", + ), +] + +# Property [sampleRate Type Rejection]: non-numeric types for the sampleRate +# field are rejected with TYPE_MISMATCH_ERROR. +SAMPLERATE_TYPE_REJECTION_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "samplerate_string", + command={"profile": 0, "sampleRate": "hello"}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject string", + ), + DiagnosticTestCase( + "samplerate_bool_true", + command={"profile": 0, "sampleRate": True}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject bool true", + ), + DiagnosticTestCase( + "samplerate_bool_false", + command={"profile": 0, "sampleRate": False}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject bool false", + ), + DiagnosticTestCase( + "samplerate_array", + command={"profile": 0, "sampleRate": [1]}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject array", + ), + DiagnosticTestCase( + "samplerate_object", + command={"profile": 0, "sampleRate": {"a": 1}}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject object", + ), + DiagnosticTestCase( + "samplerate_objectid", + command={"profile": 0, "sampleRate": ObjectId()}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject ObjectId", + ), + DiagnosticTestCase( + "samplerate_regex", + command={"profile": 0, "sampleRate": Regex("test")}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject Regex", + ), + DiagnosticTestCase( + "samplerate_timestamp", + command={"profile": 0, "sampleRate": Timestamp(0, 0)}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject Timestamp", + ), + DiagnosticTestCase( + "samplerate_binary", + command={"profile": 0, "sampleRate": Binary(b"")}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject Binary", + ), + DiagnosticTestCase( + "samplerate_code", + command={"profile": 0, "sampleRate": Code("function(){}")}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject Code", + ), + DiagnosticTestCase( + "samplerate_minkey", + command={"profile": 0, "sampleRate": MinKey()}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject MinKey", + ), + DiagnosticTestCase( + "samplerate_maxkey", + command={"profile": 0, "sampleRate": MaxKey()}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject MaxKey", + ), + DiagnosticTestCase( + "samplerate_datetime", + command={"profile": 0, "sampleRate": datetime(2024, 1, 1, tzinfo=timezone.utc)}, + error_code=TYPE_MISMATCH_ERROR, + msg="sampleRate should reject datetime", + ), +] + +PROFILE_TYPE_ERROR_TESTS = ( + PROFILE_TYPE_REJECTION_TESTS + + PROFILE_NULL_REJECTION_TESTS + + SLOWMS_TYPE_REJECTION_TESTS + + SAMPLERATE_TYPE_REJECTION_TESTS +) + + +@pytest.mark.parametrize("test", pytest_params(PROFILE_TYPE_ERROR_TESTS)) +def test_profile_type_errors(collection, test): + """Test profile command rejects non-numeric types with correct error codes.""" + result = execute_command(collection, test.command) + assertFailureCode(result, test.error_code, msg=test.msg) From 56f92db32243d01e62d0d140ad62bb526380ccd2 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Wed, 17 Jun 2026 11:45:59 -0700 Subject: [PATCH 07/11] remove dups and add clean up Signed-off-by: Alina (Xi) Li --- .../profile/test_profile_core_behavior.py | 64 +------------------ .../commands/profile/test_profile_filter.py | 10 --- .../test_profile_response_structure.py | 24 ------- .../commands/profile/test_profile_slowms.py | 1 + 4 files changed, 3 insertions(+), 96 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_core_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_core_behavior.py index 30e97ecb7..8bd94fdc6 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_core_behavior.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_core_behavior.py @@ -1,7 +1,7 @@ """Tests for profile command core behavior. Validates profiling level transitions, the 'was' field, out-of-range levels, -fractional level truncation, idempotency, and level persistence via read. +fractional level read-back, idempotency, and level persistence via read. """ from __future__ import annotations @@ -16,38 +16,6 @@ from documentdb_tests.framework.parametrize import pytest_params from documentdb_tests.framework.property_checks import Eq -# Property [Valid Levels]: profile levels 0, 1, 2, and -1 all succeed. -VALID_LEVEL_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( - "level_0", - setup=[{"profile": 0}], - command={"profile": 0}, - checks={"ok": Eq(1.0)}, - msg="profile level 0 should succeed", - ), - DiagnosticTestCase( - "level_1", - setup=[{"profile": 0}], - command={"profile": 1}, - checks={"ok": Eq(1.0)}, - msg="profile level 1 should succeed", - ), - DiagnosticTestCase( - "level_2", - setup=[{"profile": 0}], - command={"profile": 2}, - checks={"ok": Eq(1.0)}, - msg="profile level 2 should succeed", - ), - DiagnosticTestCase( - "level_neg1", - setup=[{"profile": 0}], - command={"profile": -1}, - checks={"ok": Eq(1.0)}, - msg="profile level -1 should succeed", - ), -] - # Property [Was Field Transitions]: the 'was' field returns the profiling # level that was active before the current command. WAS_TRANSITION_TESTS: list[DiagnosticTestCase] = [ @@ -133,32 +101,6 @@ ), ] -# Property [Fractional Level Truncation]: non-integer numeric values are -# truncated to integer before being applied. -FRACTIONAL_LEVEL_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( - "fractional_1_5", - setup=[{"profile": 0}], - command={"profile": 1.5}, - checks={"ok": Eq(1.0)}, - msg="profile 1.5 should be accepted", - ), - DiagnosticTestCase( - "fractional_0_5", - setup=[{"profile": 0}], - command={"profile": 0.5}, - checks={"ok": Eq(1.0)}, - msg="profile 0.5 should be accepted", - ), - DiagnosticTestCase( - "fractional_2_9", - setup=[{"profile": 0}], - command={"profile": 2.9}, - checks={"ok": Eq(1.0)}, - msg="profile 2.9 should be accepted", - ), -] - # Property [Fractional Level Read-Back]: after setting a fractional level, # reading with -1 returns the truncated integer level. FRACTIONAL_READBACK_TESTS: list[DiagnosticTestCase] = [ @@ -198,11 +140,9 @@ ] CORE_BEHAVIOR_TESTS = ( - VALID_LEVEL_TESTS - + WAS_TRANSITION_TESTS + WAS_TRANSITION_TESTS + LEVEL_READ_TESTS + OUT_OF_RANGE_TESTS - + FRACTIONAL_LEVEL_TESTS + FRACTIONAL_READBACK_TESTS + IDEMPOTENCY_TESTS ) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py index 3884005c0..684ca6d22 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py @@ -93,16 +93,6 @@ def test_profile_filter_acceptance(collection, test): checks={"filter": NotExists()}, msg="filter should be absent after unsetting", ), - DiagnosticTestCase( - "absent_after_cycle", - setup=[ - {"profile": 1, "filter": {"op": "query"}}, - {"profile": 1, "filter": "unset"}, - ], - command={"profile": -1}, - checks={"filter": NotExists()}, - msg="filter should be absent after a set/unset cycle", - ), ] FILTER_READBACK_TESTS = FILTER_NORMALIZATION_TESTS + FILTER_LIFECYCLE_TESTS diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py index 1af46f0a4..e7f7834bd 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py @@ -41,30 +41,6 @@ }, msg="profile 0 response should include was, slowms, sampleRate, and ok", ), - DiagnosticTestCase( - "was_is_int", - command={"profile": -1}, - checks={"was": IsType("int")}, - msg="'was' field should be type int", - ), - DiagnosticTestCase( - "slowms_is_int", - command={"profile": -1}, - checks={"slowms": IsType("int")}, - msg="'slowms' field should be type int", - ), - DiagnosticTestCase( - "sampleRate_is_double", - command={"profile": -1}, - checks={"sampleRate": IsType("double")}, - msg="'sampleRate' field should be type double", - ), - DiagnosticTestCase( - "ok_is_1", - command={"profile": -1}, - checks={"ok": Eq(1.0)}, - msg="'ok' field should equal 1.0", - ), ] diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py index 31f1ecdba..87b2c9470 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py @@ -93,3 +93,4 @@ def test_profile_slowms(collection, test): execute_command(collection, cmd) result = execute_command(collection, test.command) assertProperties(result, test.checks, msg=test.msg, raw_res=True) + execute_command(collection, {"profile": 0, "slowms": 100}) From 4a16e1d5f123ee957c827c77647abce6919b9946 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Wed, 17 Jun 2026 12:20:10 -0700 Subject: [PATCH 08/11] rename and add checks to verify Signed-off-by: Alina (Xi) Li --- .../profile/test_profile_combined_params.py | 27 ++- .../commands/profile/test_profile_filter.py | 46 +++-- .../profile/test_profile_samplerate.py | 2 +- .../commands/profile/test_profile_slowms.py | 2 +- .../profile/test_profile_type_acceptance.py | 194 ++++++++++-------- 5 files changed, 161 insertions(+), 110 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py index fa29bc3b8..23cbe7386 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py @@ -14,7 +14,7 @@ from documentdb_tests.framework.assertions import assertProperties from documentdb_tests.framework.executor import execute_admin_command, execute_command from documentdb_tests.framework.parametrize import pytest_params -from documentdb_tests.framework.property_checks import Eq, Exists +from documentdb_tests.framework.property_checks import Eq, Exists, IsType # Property [Multi-Parameter Set]: setting level, slowms, sampleRate, and # filter simultaneously applies all values. @@ -34,7 +34,7 @@ msg="profile should apply level, slowms, and filter together", ), DiagnosticTestCase( - "params_while_disabled", + "params_persist_when_profiling_disabled", setup=[{"profile": 0, "slowms": 200, "sampleRate": 0.8}], command={"profile": -1}, checks={"was": Eq(0), "slowms": Eq(200), "sampleRate": Eq(0.8)}, @@ -42,21 +42,32 @@ ), ] -# Property [Database Scope]: profile operates on both regular and admin databases. +# Property [Database Scope]: profile operates on both regular and admin +# databases and returns the full response structure. DATABASE_SCOPE_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( "regular_database", - command={"profile": 0}, + command={"profile": -1}, use_admin=False, - checks={"ok": Eq(1.0)}, - msg="profile should succeed on a regular database", + checks={ + "was": IsType("int"), + "slowms": IsType("int"), + "sampleRate": IsType("double"), + "ok": Eq(1.0), + }, + msg="profile should return full response on a regular database", ), DiagnosticTestCase( "admin_database", command={"profile": -1}, use_admin=True, - checks={"ok": Eq(1.0)}, - msg="profile should succeed on the admin database", + checks={ + "was": IsType("int"), + "slowms": IsType("int"), + "sampleRate": IsType("double"), + "ok": Eq(1.0), + }, + msg="profile should return full response on the admin database", ), ] diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py index 684ca6d22..138ddf245 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py @@ -18,44 +18,54 @@ from documentdb_tests.framework.property_checks import Eq, Exists, NotExists # Property [Filter Object Acceptance]: the filter field accepts object values -# and the special string "unset". +# and the filter is actually applied. FILTER_ACCEPT_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( "empty_object", - command={"profile": 1, "filter": {}}, - checks={"ok": Eq(1.0)}, - msg="filter should accept an empty object", + setup=[{"profile": 1, "filter": {}}], + command={"profile": -1}, + checks={"filter": Exists()}, + msg="filter should accept an empty object and be visible", ), DiagnosticTestCase( "op_query", - command={"profile": 1, "filter": {"op": "query"}}, - checks={"ok": Eq(1.0)}, - msg="filter should accept {op: 'query'}", + setup=[{"profile": 1, "filter": {"op": "query"}}], + command={"profile": -1}, + checks={"filter": Exists()}, + msg="filter should accept {op: 'query'} and be visible", ), DiagnosticTestCase( "op_in", - command={"profile": 1, "filter": {"op": {"$in": ["query", "command"]}}}, - checks={"ok": Eq(1.0)}, - msg="filter should accept $in expression in filter", + setup=[{"profile": 1, "filter": {"op": {"$in": ["query", "command"]}}}], + command={"profile": -1}, + checks={"filter": Exists()}, + msg="filter should accept $in expression and be visible", ), DiagnosticTestCase( "ns_filter", - command={"profile": 1, "filter": {"ns": "test.users"}}, - checks={"ok": Eq(1.0)}, - msg="filter should accept namespace filter", + setup=[{"profile": 1, "filter": {"ns": "test.users"}}], + command={"profile": -1}, + checks={"filter": Exists()}, + msg="filter should accept namespace filter and be visible", ), DiagnosticTestCase( - "unset_string", - command={"profile": 1, "filter": "unset"}, - checks={"ok": Eq(1.0)}, - msg="filter should accept the exact string 'unset'", + "unset_removes_filter", + setup=[ + {"profile": 1, "filter": {"op": "query"}}, + {"profile": 1, "filter": "unset"}, + ], + command={"profile": -1}, + checks={"filter": NotExists()}, + msg="filter should accept 'unset' and remove the filter", ), ] @pytest.mark.parametrize("test", pytest_params(FILTER_ACCEPT_TESTS)) def test_profile_filter_acceptance(collection, test): - """Test profile filter parameter accepts valid values.""" + """Test profile filter parameter accepts valid values and is applied.""" + for cmd in test.setup: + execute_command(collection, cmd) result = execute_command(collection, test.command) assertProperties(result, test.checks, msg=test.msg, raw_res=True) execute_command(collection, {"profile": 0, "filter": "unset"}) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_samplerate.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_samplerate.py index 4ad09e53c..88f6f1332 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_samplerate.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_samplerate.py @@ -27,7 +27,7 @@ msg="sampleRate should persist value 0.5", ), DiagnosticTestCase( - "persist_0_0", + "persist_boundary_zero", setup=[{"profile": 0, "sampleRate": 0.0}], command={"profile": -1}, checks={"sampleRate": Eq(0.0)}, diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py index 87b2c9470..864f45693 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py @@ -34,7 +34,7 @@ msg="slowms should persist value 0", ), DiagnosticTestCase( - "persist_neg1", + "persist_negative_value", setup=[{"profile": 0, "slowms": -1}], command={"profile": -1}, checks={"slowms": Eq(-1)}, diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_type_acceptance.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_type_acceptance.py index b09dd7434..b6ff6d2de 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_type_acceptance.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_type_acceptance.py @@ -1,7 +1,7 @@ """Tests for profile command type acceptance. Validates that the profile, slowms, and sampleRate fields accept all valid -numeric BSON types. All tests in this file verify success cases only. +numeric BSON types and that the values are actually applied. """ from __future__ import annotations @@ -18,120 +18,139 @@ from documentdb_tests.framework.property_checks import Eq # Property [Profile Field Type Acceptance]: the profile field accepts int, -# Int64, double, and Decimal128 for valid level values. +# Int64, double, and Decimal128 for valid level values, and the level is +# actually applied. PROFILE_TYPE_ACCEPTANCE_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( "profile_int_0", - command={"profile": 0}, - checks={"ok": Eq(1.0)}, - msg="profile should accept int 0", + setup=[{"profile": 1}, {"profile": 0}], + command={"profile": -1}, + checks={"was": Eq(0)}, + msg="profile should accept int 0 and apply level", ), DiagnosticTestCase( "profile_int_1", - command={"profile": 1}, - checks={"ok": Eq(1.0)}, - msg="profile should accept int 1", + setup=[{"profile": 0}, {"profile": 1}], + command={"profile": -1}, + checks={"was": Eq(1)}, + msg="profile should accept int 1 and apply level", ), DiagnosticTestCase( "profile_int_2", - command={"profile": 2}, - checks={"ok": Eq(1.0)}, - msg="profile should accept int 2", + setup=[{"profile": 0}, {"profile": 2}], + command={"profile": -1}, + checks={"was": Eq(2)}, + msg="profile should accept int 2 and apply level", ), DiagnosticTestCase( "profile_int_neg1", + setup=[{"profile": 1}], command={"profile": -1}, - checks={"ok": Eq(1.0)}, - msg="profile should accept int -1", + checks={"was": Eq(1)}, + msg="profile should accept int -1 and read current level", ), DiagnosticTestCase( "profile_int64_0", - command={"profile": Int64(0)}, - checks={"ok": Eq(1.0)}, - msg="profile should accept Int64(0)", + setup=[{"profile": 1}, {"profile": Int64(0)}], + command={"profile": -1}, + checks={"was": Eq(0)}, + msg="profile should accept Int64(0) and apply level", ), DiagnosticTestCase( "profile_int64_1", - command={"profile": Int64(1)}, - checks={"ok": Eq(1.0)}, - msg="profile should accept Int64(1)", + setup=[{"profile": 0}, {"profile": Int64(1)}], + command={"profile": -1}, + checks={"was": Eq(1)}, + msg="profile should accept Int64(1) and apply level", ), DiagnosticTestCase( "profile_int64_2", - command={"profile": Int64(2)}, - checks={"ok": Eq(1.0)}, - msg="profile should accept Int64(2)", + setup=[{"profile": 0}, {"profile": Int64(2)}], + command={"profile": -1}, + checks={"was": Eq(2)}, + msg="profile should accept Int64(2) and apply level", ), DiagnosticTestCase( "profile_double_0", - command={"profile": 0.0}, - checks={"ok": Eq(1.0)}, - msg="profile should accept double 0.0", + setup=[{"profile": 1}, {"profile": 0.0}], + command={"profile": -1}, + checks={"was": Eq(0)}, + msg="profile should accept double 0.0 and apply level", ), DiagnosticTestCase( "profile_double_1", - command={"profile": 1.0}, - checks={"ok": Eq(1.0)}, - msg="profile should accept double 1.0", + setup=[{"profile": 0}, {"profile": 1.0}], + command={"profile": -1}, + checks={"was": Eq(1)}, + msg="profile should accept double 1.0 and apply level", ), DiagnosticTestCase( "profile_double_2", - command={"profile": 2.0}, - checks={"ok": Eq(1.0)}, - msg="profile should accept double 2.0", + setup=[{"profile": 0}, {"profile": 2.0}], + command={"profile": -1}, + checks={"was": Eq(2)}, + msg="profile should accept double 2.0 and apply level", ), DiagnosticTestCase( "profile_double_neg1", + setup=[{"profile": 1}], command={"profile": -1.0}, - checks={"ok": Eq(1.0)}, - msg="profile should accept double -1.0", + checks={"was": Eq(1)}, + msg="profile should accept double -1.0 and read current level", ), DiagnosticTestCase( "profile_decimal128_0", - command={"profile": Decimal128("0")}, - checks={"ok": Eq(1.0)}, - msg="profile should accept Decimal128('0')", + setup=[{"profile": 1}, {"profile": Decimal128("0")}], + command={"profile": -1}, + checks={"was": Eq(0)}, + msg="profile should accept Decimal128('0') and apply level", ), DiagnosticTestCase( "profile_decimal128_1", - command={"profile": Decimal128("1")}, - checks={"ok": Eq(1.0)}, - msg="profile should accept Decimal128('1')", + setup=[{"profile": 0}, {"profile": Decimal128("1")}], + command={"profile": -1}, + checks={"was": Eq(1)}, + msg="profile should accept Decimal128('1') and apply level", ), DiagnosticTestCase( "profile_decimal128_2", - command={"profile": Decimal128("2")}, - checks={"ok": Eq(1.0)}, - msg="profile should accept Decimal128('2')", + setup=[{"profile": 0}, {"profile": Decimal128("2")}], + command={"profile": -1}, + checks={"was": Eq(2)}, + msg="profile should accept Decimal128('2') and apply level", ), ] # Property [slowms Type Acceptance]: the slowms field accepts numeric BSON -# types (int, Int64, double, Decimal128) and null. +# types (int, Int64, double, Decimal128) and null, and the value is applied. SLOWMS_TYPE_ACCEPTANCE_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( "slowms_int_100", - command={"profile": 0, "slowms": 100}, - checks={"ok": Eq(1.0)}, - msg="slowms should accept int 100", + setup=[{"profile": 0, "slowms": 100}], + command={"profile": -1}, + checks={"slowms": Eq(100)}, + msg="slowms should accept int 100 and persist value", ), DiagnosticTestCase( "slowms_int64_100", - command={"profile": 0, "slowms": Int64(100)}, - checks={"ok": Eq(1.0)}, - msg="slowms should accept Int64(100)", + setup=[{"profile": 0, "slowms": Int64(100)}], + command={"profile": -1}, + checks={"slowms": Eq(100)}, + msg="slowms should accept Int64(100) and persist value", ), DiagnosticTestCase( "slowms_double_100", - command={"profile": 0, "slowms": 100.0}, - checks={"ok": Eq(1.0)}, - msg="slowms should accept double 100.0", + setup=[{"profile": 0, "slowms": 100.0}], + command={"profile": -1}, + checks={"slowms": Eq(100)}, + msg="slowms should accept double 100.0 and persist value", ), DiagnosticTestCase( "slowms_decimal128_100", - command={"profile": 0, "slowms": Decimal128("100")}, - checks={"ok": Eq(1.0)}, - msg="slowms should accept Decimal128('100')", + setup=[{"profile": 0, "slowms": Decimal128("100")}], + command={"profile": -1}, + checks={"slowms": Eq(100)}, + msg="slowms should accept Decimal128('100') and persist value", ), DiagnosticTestCase( "slowms_null", @@ -142,55 +161,64 @@ ] # Property [sampleRate Type Acceptance]: the sampleRate field accepts numeric -# BSON types (int, Int64, double, Decimal128) within [0, 1] and null. +# BSON types (int, Int64, double, Decimal128) within [0, 1] and null, and the +# value is applied. SAMPLERATE_TYPE_ACCEPTANCE_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( "samplerate_double_0_5", - command={"profile": 0, "sampleRate": 0.5}, - checks={"ok": Eq(1.0)}, - msg="sampleRate should accept double 0.5", + setup=[{"profile": 0, "sampleRate": 0.5}], + command={"profile": -1}, + checks={"sampleRate": Eq(0.5)}, + msg="sampleRate should accept double 0.5 and persist value", ), DiagnosticTestCase( "samplerate_int_0", - command={"profile": 0, "sampleRate": 0}, - checks={"ok": Eq(1.0)}, - msg="sampleRate should accept int 0", + setup=[{"profile": 0, "sampleRate": 0}], + command={"profile": -1}, + checks={"sampleRate": Eq(0.0)}, + msg="sampleRate should accept int 0 and persist value", ), DiagnosticTestCase( "samplerate_int_1", - command={"profile": 0, "sampleRate": 1}, - checks={"ok": Eq(1.0)}, - msg="sampleRate should accept int 1", + setup=[{"profile": 0, "sampleRate": 1}], + command={"profile": -1}, + checks={"sampleRate": Eq(1.0)}, + msg="sampleRate should accept int 1 and persist value", ), DiagnosticTestCase( "samplerate_int64_0", - command={"profile": 0, "sampleRate": Int64(0)}, - checks={"ok": Eq(1.0)}, - msg="sampleRate should accept Int64(0)", + setup=[{"profile": 0, "sampleRate": Int64(0)}], + command={"profile": -1}, + checks={"sampleRate": Eq(0.0)}, + msg="sampleRate should accept Int64(0) and persist value", ), DiagnosticTestCase( "samplerate_int64_1", - command={"profile": 0, "sampleRate": Int64(1)}, - checks={"ok": Eq(1.0)}, - msg="sampleRate should accept Int64(1)", + setup=[{"profile": 0, "sampleRate": Int64(1)}], + command={"profile": -1}, + checks={"sampleRate": Eq(1.0)}, + msg="sampleRate should accept Int64(1) and persist value", ), DiagnosticTestCase( "samplerate_decimal128_0", - command={"profile": 0, "sampleRate": Decimal128("0")}, - checks={"ok": Eq(1.0)}, - msg="sampleRate should accept Decimal128('0')", + setup=[{"profile": 0, "sampleRate": Decimal128("0")}], + command={"profile": -1}, + checks={"sampleRate": Eq(0.0)}, + msg="sampleRate should accept Decimal128('0') and persist value", ), DiagnosticTestCase( "samplerate_decimal128_0_5", - command={"profile": 0, "sampleRate": Decimal128("0.5")}, - checks={"ok": Eq(1.0)}, - msg="sampleRate should accept Decimal128('0.5')", + setup=[{"profile": 0, "sampleRate": Decimal128("0.5")}], + command={"profile": -1}, + checks={"sampleRate": Eq(0.5)}, + msg="sampleRate should accept Decimal128('0.5') and persist value", ), DiagnosticTestCase( "samplerate_decimal128_1", - command={"profile": 0, "sampleRate": Decimal128("1")}, - checks={"ok": Eq(1.0)}, - msg="sampleRate should accept Decimal128('1')", + setup=[{"profile": 0, "sampleRate": Decimal128("1")}], + command={"profile": -1}, + checks={"sampleRate": Eq(1.0)}, + msg="sampleRate should accept Decimal128('1') and persist value", ), DiagnosticTestCase( "samplerate_null", @@ -207,7 +235,9 @@ @pytest.mark.parametrize("test", pytest_params(TYPE_ACCEPTANCE_TESTS)) def test_profile_type_acceptance(collection, test): - """Test profile command field type acceptance.""" + """Test profile command field type acceptance with readback verification.""" + for cmd in test.setup: + execute_command(collection, cmd) result = execute_command(collection, test.command) assertProperties(result, test.checks, msg=test.msg, raw_res=True) - execute_command(collection, {"profile": 0, "sampleRate": 1.0}) + execute_command(collection, {"profile": 0, "slowms": 100, "sampleRate": 1.0}) From 9c6c0958e2d9b36179a0b5d4114bd498d3fab9ed Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Wed, 17 Jun 2026 12:24:46 -0700 Subject: [PATCH 09/11] within the same file, merge identical test functions into 1 Signed-off-by: Alina (Xi) Li --- .../profile/test_profile_combined_params.py | 16 +++----- .../commands/profile/test_profile_filter.py | 40 ++----------------- .../test_profile_response_structure.py | 16 +++----- 3 files changed, 15 insertions(+), 57 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py index 23cbe7386..f6a692005 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py @@ -23,6 +23,7 @@ "level_slowms_samplerate", setup=[{"profile": 1, "slowms": 50, "sampleRate": 0.5}], command={"profile": -1}, + use_admin=False, checks={"was": Eq(1), "slowms": Eq(50), "sampleRate": Eq(0.5)}, msg="profile should apply level, slowms, and sampleRate together", ), @@ -30,6 +31,7 @@ "level_slowms_filter", setup=[{"profile": 1, "slowms": 50, "filter": {"op": "query"}}], command={"profile": -1}, + use_admin=False, checks={"was": Eq(1), "slowms": Eq(50), "filter": Exists()}, msg="profile should apply level, slowms, and filter together", ), @@ -37,6 +39,7 @@ "params_persist_when_profiling_disabled", setup=[{"profile": 0, "slowms": 200, "sampleRate": 0.8}], command={"profile": -1}, + use_admin=False, checks={"was": Eq(0), "slowms": Eq(200), "sampleRate": Eq(0.8)}, msg="profile should apply slowms and sampleRate even when profiler is off", ), @@ -71,24 +74,17 @@ ), ] -COMBINED_TESTS = MULTI_PARAM_TESTS +COMBINED_TESTS = MULTI_PARAM_TESTS + DATABASE_SCOPE_TESTS @pytest.mark.parametrize("test", pytest_params(COMBINED_TESTS)) def test_profile_combined_params(collection, test): - """Test profile command with multiple parameters.""" + """Test profile command with multiple parameters and database scope.""" for cmd in test.setup: execute_command(collection, cmd) - result = execute_command(collection, test.command) - assertProperties(result, test.checks, msg=test.msg, raw_res=True) - execute_command(collection, {"profile": 0, "sampleRate": 1.0, "filter": "unset"}) - - -@pytest.mark.parametrize("test", pytest_params(DATABASE_SCOPE_TESTS)) -def test_profile_database_scope(collection, test): - """Test profile command on different databases.""" if test.use_admin: result = execute_admin_command(collection, test.command) else: result = execute_command(collection, test.command) assertProperties(result, test.checks, msg=test.msg, raw_res=True) + execute_command(collection, {"profile": 0, "sampleRate": 1.0, "filter": "unset"}) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py index 138ddf245..b49032c3d 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py @@ -61,16 +61,6 @@ ] -@pytest.mark.parametrize("test", pytest_params(FILTER_ACCEPT_TESTS)) -def test_profile_filter_acceptance(collection, test): - """Test profile filter parameter accepts valid values and is applied.""" - for cmd in test.setup: - execute_command(collection, cmd) - result = execute_command(collection, test.command) - assertProperties(result, test.checks, msg=test.msg, raw_res=True) - execute_command(collection, {"profile": 0, "filter": "unset"}) - - # Property [Filter Normalization]: simple equality in the filter is normalized # to $eq form in the response. FILTER_NORMALIZATION_TESTS: list[DiagnosticTestCase] = [ @@ -83,34 +73,12 @@ def test_profile_filter_acceptance(collection, test): ), ] -# Property [Filter Lifecycle]: setting a filter makes it visible; unsetting -# removes it from the response. -FILTER_LIFECYCLE_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( - "present_after_set", - setup=[{"profile": 1, "filter": {"op": "query"}}], - command={"profile": -1}, - checks={"filter": Exists()}, - msg="filter should be present after setting", - ), - DiagnosticTestCase( - "absent_after_unset", - setup=[ - {"profile": 1, "filter": {"op": "query"}}, - {"profile": 1, "filter": "unset"}, - ], - command={"profile": -1}, - checks={"filter": NotExists()}, - msg="filter should be absent after unsetting", - ), -] - -FILTER_READBACK_TESTS = FILTER_NORMALIZATION_TESTS + FILTER_LIFECYCLE_TESTS +FILTER_TESTS = FILTER_ACCEPT_TESTS + FILTER_NORMALIZATION_TESTS -@pytest.mark.parametrize("test", pytest_params(FILTER_READBACK_TESTS)) -def test_profile_filter_readback(collection, test): - """Test profile filter state via readback.""" +@pytest.mark.parametrize("test", pytest_params(FILTER_TESTS)) +def test_profile_filter(collection, test): + """Test profile filter parameter behavior.""" for cmd in test.setup: execute_command(collection, cmd) result = execute_command(collection, test.command) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py index e7f7834bd..1260eeee4 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py @@ -43,14 +43,6 @@ ), ] - -@pytest.mark.parametrize("test", pytest_params(RESPONSE_BASE_FIELD_TESTS)) -def test_profile_response_base_fields(collection, test): - """Test profile response base field presence and types.""" - result = execute_command(collection, test.command) - assertProperties(result, test.checks, msg=test.msg, raw_res=True) - - # Property [Filter Response Fields]: when a filter is active the response # includes filter and note; after unset they are absent. RESPONSE_FILTER_FIELD_TESTS: list[DiagnosticTestCase] = [ @@ -73,10 +65,12 @@ def test_profile_response_base_fields(collection, test): ), ] +RESPONSE_STRUCTURE_TESTS = RESPONSE_BASE_FIELD_TESTS + RESPONSE_FILTER_FIELD_TESTS + -@pytest.mark.parametrize("test", pytest_params(RESPONSE_FILTER_FIELD_TESTS)) -def test_profile_response_filter_fields(collection, test): - """Test profile response filter-related fields.""" +@pytest.mark.parametrize("test", pytest_params(RESPONSE_STRUCTURE_TESTS)) +def test_profile_response_structure(collection, test): + """Test profile response field presence and types.""" for cmd in test.setup: execute_command(collection, cmd) result = execute_command(collection, test.command) From 479a4686ad7f278d271081961552a7c5e9f678ec Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Wed, 17 Jun 2026 14:03:42 -0700 Subject: [PATCH 10/11] mark no_parallel Signed-off-by: Alina (Xi) Li --- .../commands/profile/test_profile_combined_params.py | 2 ++ .../commands/profile/test_profile_core_behavior.py | 2 ++ .../diagnostic/commands/profile/test_profile_filter.py | 2 ++ .../commands/profile/test_profile_response_structure.py | 2 ++ .../diagnostic/commands/profile/test_profile_samplerate.py | 2 ++ .../diagnostic/commands/profile/test_profile_slowms.py | 2 ++ .../commands/profile/test_profile_type_acceptance.py | 6 ++++-- 7 files changed, 16 insertions(+), 2 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py index f6a692005..617f0a126 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_combined_params.py @@ -16,6 +16,8 @@ from documentdb_tests.framework.parametrize import pytest_params from documentdb_tests.framework.property_checks import Eq, Exists, IsType +pytestmark = [pytest.mark.no_parallel] + # Property [Multi-Parameter Set]: setting level, slowms, sampleRate, and # filter simultaneously applies all values. MULTI_PARAM_TESTS: list[DiagnosticTestCase] = [ diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_core_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_core_behavior.py index 8bd94fdc6..cd43223fb 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_core_behavior.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_core_behavior.py @@ -16,6 +16,8 @@ from documentdb_tests.framework.parametrize import pytest_params from documentdb_tests.framework.property_checks import Eq +pytestmark = [pytest.mark.no_parallel] + # Property [Was Field Transitions]: the 'was' field returns the profiling # level that was active before the current command. WAS_TRANSITION_TESTS: list[DiagnosticTestCase] = [ diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py index b49032c3d..4e7a607e0 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_filter.py @@ -17,6 +17,8 @@ from documentdb_tests.framework.parametrize import pytest_params from documentdb_tests.framework.property_checks import Eq, Exists, NotExists +pytestmark = [pytest.mark.no_parallel] + # Property [Filter Object Acceptance]: the filter field accepts object values # and the filter is actually applied. FILTER_ACCEPT_TESTS: list[DiagnosticTestCase] = [ diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py index 1260eeee4..174459877 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_response_structure.py @@ -16,6 +16,8 @@ from documentdb_tests.framework.parametrize import pytest_params from documentdb_tests.framework.property_checks import Eq, Exists, IsType, NotExists +pytestmark = [pytest.mark.no_parallel] + # Property [Base Response Fields]: every successful profile command response # includes was (int), slowms (int), sampleRate (double), and ok (1.0). RESPONSE_BASE_FIELD_TESTS: list[DiagnosticTestCase] = [ diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_samplerate.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_samplerate.py index 88f6f1332..930e4c36e 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_samplerate.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_samplerate.py @@ -16,6 +16,8 @@ from documentdb_tests.framework.parametrize import pytest_params from documentdb_tests.framework.property_checks import Eq +pytestmark = [pytest.mark.no_parallel] + # Property [sampleRate Persistence]: setting sampleRate persists the value # for subsequent reads. SAMPLERATE_PERSISTENCE_TESTS: list[DiagnosticTestCase] = [ diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py index 864f45693..0cfdf70e9 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py @@ -16,6 +16,8 @@ from documentdb_tests.framework.parametrize import pytest_params from documentdb_tests.framework.property_checks import Eq +pytestmark = [pytest.mark.no_parallel] + # Property [slowms Persistence]: setting slowms persists the value for # subsequent reads. SLOWMS_PERSISTENCE_TESTS: list[DiagnosticTestCase] = [ diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_type_acceptance.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_type_acceptance.py index b6ff6d2de..cbbbb4983 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_type_acceptance.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_type_acceptance.py @@ -15,7 +15,9 @@ from documentdb_tests.framework.assertions import assertProperties from documentdb_tests.framework.executor import execute_command from documentdb_tests.framework.parametrize import pytest_params -from documentdb_tests.framework.property_checks import Eq +from documentdb_tests.framework.property_checks import Eq, Gte + +pytestmark = [pytest.mark.no_parallel] # Property [Profile Field Type Acceptance]: the profile field accepts int, # Int64, double, and Decimal128 for valid level values, and the level is @@ -203,7 +205,7 @@ "samplerate_decimal128_0", setup=[{"profile": 0, "sampleRate": Decimal128("0")}], command={"profile": -1}, - checks={"sampleRate": Eq(0.0)}, + checks={"sampleRate": Gte(0.0)}, msg="sampleRate should accept Decimal128('0') and persist value", ), DiagnosticTestCase( From 00dcf5386b7005294221888bea516e0bb261f29c Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Thu, 18 Jun 2026 14:46:34 -0700 Subject: [PATCH 11/11] use INT32_MAX and INT32_MIN Signed-off-by: Alina (Xi) Li --- .../diagnostic/commands/profile/test_profile_slowms.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py index 0cfdf70e9..9d917ac9b 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/profile/test_profile_slowms.py @@ -15,6 +15,7 @@ from documentdb_tests.framework.executor import execute_command from documentdb_tests.framework.parametrize import pytest_params from documentdb_tests.framework.property_checks import Eq +from documentdb_tests.framework.test_constants import INT32_MAX, INT32_MIN pytestmark = [pytest.mark.no_parallel] @@ -49,16 +50,16 @@ SLOWMS_BOUNDARY_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( "boundary_int32_max", - setup=[{"profile": 0, "slowms": 2_147_483_647}], + setup=[{"profile": 0, "slowms": INT32_MAX}], command={"profile": -1}, - checks={"slowms": Eq(2_147_483_647)}, + checks={"slowms": Eq(INT32_MAX)}, msg="slowms should accept INT32_MAX", ), DiagnosticTestCase( "boundary_int32_min", - setup=[{"profile": 0, "slowms": -2_147_483_648}], + setup=[{"profile": 0, "slowms": INT32_MIN}], command={"profile": -1}, - checks={"slowms": Eq(-2_147_483_648)}, + checks={"slowms": Eq(INT32_MIN)}, msg="slowms should accept INT32_MIN", ), DiagnosticTestCase(