From 13de1c23d12e00eabdbe6f40bbd39ff537da84ec Mon Sep 17 00:00:00 2001 From: PatersonProjects Date: Mon, 22 Jun 2026 16:37:44 -0700 Subject: [PATCH 1/8] Base tests Signed-off-by: PatersonProjects --- .../getLog/test_getLog_argument_validation.py | 63 +++++++++++ .../getLog/test_getLog_core_behavior.py | 93 +++++++++++++++ .../commands/getLog/test_getLog_errors.py | 69 +++++++++++ .../getLog/test_getLog_log_behavior.py | 107 ++++++++++++++++++ 4 files changed, 332 insertions(+) create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_argument_validation.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_core_behavior.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_errors.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_argument_validation.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_argument_validation.py new file mode 100644 index 000000000..639a867f1 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_argument_validation.py @@ -0,0 +1,63 @@ +"""Tests for getLog command argument validation. + +Covers BSON type handling for the ``getLog`` field value. Only a string is +accepted; every non-string type is rejected with TypeMismatch. Acceptance is +verified against the three documented filter strings ("global", +"startupWarnings", "*") rather than a generic string sample, because an +arbitrary string is not a valid log component. + +Invalid string values (e.g. unknown components, the deprecated "rs") and +unrecognized command fields are covered in test_getLog_errors.py. +""" + +import pytest + +from documentdb_tests.framework.assertions import assertFailureCode, assertSuccessPartial +from documentdb_tests.framework.bson_type_validator import ( + BsonTypeTestCase, + generate_bson_rejection_test_cases, +) +from documentdb_tests.framework.error_codes import MISSING_FIELD_ERROR, TYPE_MISMATCH_ERROR +from documentdb_tests.framework.executor import execute_admin_command +from documentdb_tests.framework.test_constants import BsonType + +pytestmark = pytest.mark.admin + +# getLog accepts only a string value; every other BSON type is rejected with +# TypeMismatch, except a null value, which is treated as an absent required +# field (MissingField). +BSON_TYPE_PARAMS = [ + BsonTypeTestCase( + id="getLog_value", + msg="getLog should reject non-string value types", + keyword="getLog", + valid_types=[BsonType.STRING], + default_error_code=TYPE_MISMATCH_ERROR, + error_code_overrides={BsonType.NULL: MISSING_FIELD_ERROR}, + ), +] + +REJECTION_CASES = generate_bson_rejection_test_cases(BSON_TYPE_PARAMS) + +# The three documented filter values accepted by getLog, paired with stable ids. +VALID_FILTERS = [ + ("global", "filter_global"), + ("startupWarnings", "filter_startupWarnings"), + ("*", "filter_wildcard"), +] +VALID_FILTER_IDS = [fid for _, fid in VALID_FILTERS] +VALID_FILTER_VALUES = [value for value, _ in VALID_FILTERS] + + +@pytest.mark.parametrize("bson_type,sample_value,spec", REJECTION_CASES) +def test_getLog_rejects_non_string_value(collection, bson_type, sample_value, spec): + """Test getLog rejects each non-string BSON type for its value.""" + result = execute_admin_command(collection, {"getLog": sample_value}) + assertFailureCode(result, spec.expected_code(bson_type), msg=spec.msg) + + +@pytest.mark.parametrize("value", VALID_FILTER_VALUES, ids=VALID_FILTER_IDS) +def test_getLog_accepts_valid_filter(collection, value): + """Test getLog accepts each documented filter string and returns ok:1.""" + result = execute_admin_command(collection, {"getLog": value}) + assertSuccessPartial(result, {"ok": 1.0}, msg=f"getLog should accept '{value}'") diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_core_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_core_behavior.py new file mode 100644 index 000000000..9cd6633c7 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_core_behavior.py @@ -0,0 +1,93 @@ +"""Tests for getLog command core behavior and response structure. + +Covers the response shape for the "global" and "startupWarnings" filters +(totalLinesWritten, log array, string log entries, ok) and for the "*" +filter (names array listing the available filters, ok). Each test asserts a +single response property. +""" + +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_admin_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.property_checks import ContainsElement, Eq, Gte, IsType + +pytestmark = pytest.mark.admin + + +RESPONSE_TESTS: list[DiagnosticTestCase] = [ + # "global" filter response structure + DiagnosticTestCase( + "global_totalLinesWritten_number", + command={"getLog": "global"}, + checks={"totalLinesWritten": Gte(0)}, + msg="global should return a non-negative totalLinesWritten", + ), + DiagnosticTestCase( + "global_log_is_array", + command={"getLog": "global"}, + checks={"log": IsType("array")}, + msg="global should return a log array", + ), + DiagnosticTestCase( + "global_log_entry_is_string", + command={"getLog": "global"}, + checks={"log.0": IsType("string")}, + msg="global log entries should be JSON-formatted strings", + ), + DiagnosticTestCase( + "global_ok", + command={"getLog": "global"}, + checks={"ok": Eq(1.0)}, + msg="global should return ok:1", + ), + # "startupWarnings" filter response structure (log array may be empty) + DiagnosticTestCase( + "startupWarnings_log_is_array", + command={"getLog": "startupWarnings"}, + checks={"log": IsType("array")}, + msg="startupWarnings should return a log array", + ), + DiagnosticTestCase( + "startupWarnings_ok", + command={"getLog": "startupWarnings"}, + checks={"ok": Eq(1.0)}, + msg="startupWarnings should return ok:1", + ), + # "*" filter lists the available log filters + DiagnosticTestCase( + "wildcard_names_is_array", + command={"getLog": "*"}, + checks={"names": IsType("array")}, + msg="'*' should return a names array", + ), + DiagnosticTestCase( + "wildcard_names_contains_global", + command={"getLog": "*"}, + checks={"names": ContainsElement("global")}, + msg="'*' names should include 'global'", + ), + DiagnosticTestCase( + "wildcard_names_contains_startupWarnings", + command={"getLog": "*"}, + checks={"names": ContainsElement("startupWarnings")}, + msg="'*' names should include 'startupWarnings'", + ), + DiagnosticTestCase( + "wildcard_ok", + command={"getLog": "*"}, + checks={"ok": Eq(1.0)}, + msg="'*' should return ok:1", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(RESPONSE_TESTS)) +def test_getLog_response_properties(collection, test): + """Verify a getLog response field exists and has the expected type or value.""" + result = execute_admin_command(collection, test.command) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_errors.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_errors.py new file mode 100644 index 000000000..6e04ad2ce --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_errors.py @@ -0,0 +1,69 @@ +"""Tests for getLog command error conditions. + +Covers invalid log component names (unknown component, the deprecated "rs" +value, empty string), unrecognized command fields, and the admin-database +requirement. + +BSON type rejection/acceptance for the value is covered in +test_getLog_argument_validation.py. +""" + +import pytest + +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 ( + OPERATION_FAILED_ERROR, + UNAUTHORIZED_ERROR, + UNRECOGNIZED_COMMAND_FIELD_ERROR, +) +from documentdb_tests.framework.executor import execute_admin_command, execute_command +from documentdb_tests.framework.parametrize import pytest_params + +pytestmark = pytest.mark.admin + + +# An unknown, deprecated, or empty log component name fails with OperationFailed. +ERROR_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "unknown_component", + command={"getLog": "invalid"}, + error_code=OPERATION_FAILED_ERROR, + msg="Unknown log component name should error", + ), + DiagnosticTestCase( + "deprecated_rs", + command={"getLog": "rs"}, + error_code=OPERATION_FAILED_ERROR, + msg="Deprecated 'rs' value should error", + ), + DiagnosticTestCase( + "empty_string", + command={"getLog": ""}, + error_code=OPERATION_FAILED_ERROR, + msg="Empty string component should error", + ), + DiagnosticTestCase( + "unrecognized_field", + command={"getLog": "global", "unknownField": 1}, + error_code=UNRECOGNIZED_COMMAND_FIELD_ERROR, + msg="Unrecognized command field should error", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(ERROR_TESTS)) +def test_getLog_error(collection, test): + """Test getLog returns the expected error code for invalid arguments.""" + result = execute_admin_command(collection, test.command) + assertFailureCode(result, test.error_code, msg=test.msg) + + +def test_getLog_non_admin_database(collection): + """Test getLog run against a non-admin database returns Unauthorized.""" + result = execute_command(collection, {"getLog": "global"}) + assertFailureCode( + result, UNAUTHORIZED_ERROR, msg="getLog should only run on the admin database" + ) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py new file mode 100644 index 000000000..618232ba6 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py @@ -0,0 +1,107 @@ +"""Tests for getLog "global" log content behavior. + +Covers the stable, deterministic parts of getLog's logging contract: a running +server returns log entries from its RAM cache, totalLinesWritten is consistent +with the returned log array, the array is capped at 1024 entries, entries are +JSON-parseable strings, and totalLinesWritten does not decrease across a +logRotate. + +Non-deterministic behaviors documented for getLog — exact >1024-character line +truncation, specific character-escape sequences, and which particular messages +appear after a logRotate — depend on runtime log content and are intentionally +not asserted here. +""" + +import json + +import pytest + +from documentdb_tests.framework.assertions import assertProperties, assertSuccess +from documentdb_tests.framework.executor import execute_admin_command +from documentdb_tests.framework.property_checks import Exists, Gte + +pytestmark = pytest.mark.admin + +# getLog "global" returns at most the most recent 1024 logged events. +MAX_LOG_EVENTS = 1024 + + +def _parses_as_json(entry: object) -> bool: + """Return True if entry is a string that parses as a JSON document.""" + if not isinstance(entry, str): + return False + try: + json.loads(entry) + return True + except ValueError: + return False + + +def test_getLog_global_has_log_entries(collection): + """Test getLog 'global' returns at least one log entry from the RAM cache.""" + result = execute_admin_command(collection, {"getLog": "global"}) + assertProperties( + result, {"log.0": Exists()}, msg="A running server should have log entries", raw_res=True + ) + + +def test_getLog_global_totalLinesWritten_non_zero(collection): + """Test getLog 'global' reports a non-zero totalLinesWritten on a running server.""" + result = execute_admin_command(collection, {"getLog": "global"}) + assertProperties( + result, + {"totalLinesWritten": Gte(1)}, + msg="totalLinesWritten should be >= 1 on a running server", + raw_res=True, + ) + + +def test_getLog_global_totalLinesWritten_gte_log_length(collection): + """Test totalLinesWritten is >= the number of returned log entries.""" + result = execute_admin_command(collection, {"getLog": "global"}) + assertSuccess( + result, + True, + msg="totalLinesWritten should be >= len(log)", + raw_res=True, + transform=lambda r: r["totalLinesWritten"] >= len(r["log"]), + ) + + +def test_getLog_global_log_capped_at_1024(collection): + """Test getLog 'global' returns at most 1024 log entries.""" + result = execute_admin_command(collection, {"getLog": "global"}) + assertSuccess( + result, + True, + msg="log array should contain at most 1024 entries", + raw_res=True, + transform=lambda r: len(r["log"]) <= MAX_LOG_EVENTS, + ) + + +def test_getLog_global_entries_parse_as_json(collection): + """Test every getLog 'global' log entry is a JSON-parseable string.""" + result = execute_admin_command(collection, {"getLog": "global"}) + assertSuccess( + result, + True, + msg="log entries should be JSON-parseable strings", + raw_res=True, + transform=lambda r: all(_parses_as_json(entry) for entry in r["log"]), + ) + + +def test_getLog_totalLinesWritten_non_decreasing_after_logRotate(collection): + """Test totalLinesWritten does not decrease across a logRotate.""" + before = execute_admin_command(collection, {"getLog": "global"}) + before_count = before["totalLinesWritten"] + execute_admin_command(collection, {"logRotate": 1}) + after = execute_admin_command(collection, {"getLog": "global"}) + assertSuccess( + after, + True, + msg="totalLinesWritten should not decrease after logRotate", + raw_res=True, + transform=lambda r: r["totalLinesWritten"] >= before_count, + ) From 467062a4697c17a14e56d0eea41dde69f74fed23 Mon Sep 17 00:00:00 2001 From: PatersonProjects Date: Mon, 22 Jun 2026 16:57:26 -0700 Subject: [PATCH 2/8] Parametrized tests Signed-off-by: PatersonProjects --- .../getLog/test_getLog_argument_validation.py | 23 +---- .../commands/getLog/test_getLog_errors.py | 20 ++-- .../getLog/test_getLog_log_behavior.py | 99 +++++++++---------- 3 files changed, 61 insertions(+), 81 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_argument_validation.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_argument_validation.py index 639a867f1..fb2de4b7a 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_argument_validation.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_argument_validation.py @@ -1,10 +1,7 @@ """Tests for getLog command argument validation. Covers BSON type handling for the ``getLog`` field value. Only a string is -accepted; every non-string type is rejected with TypeMismatch. Acceptance is -verified against the three documented filter strings ("global", -"startupWarnings", "*") rather than a generic string sample, because an -arbitrary string is not a valid log component. +accepted; every non-string type is rejected with TypeMismatch. Invalid string values (e.g. unknown components, the deprecated "rs") and unrecognized command fields are covered in test_getLog_errors.py. @@ -12,7 +9,7 @@ import pytest -from documentdb_tests.framework.assertions import assertFailureCode, assertSuccessPartial +from documentdb_tests.framework.assertions import assertFailureCode from documentdb_tests.framework.bson_type_validator import ( BsonTypeTestCase, generate_bson_rejection_test_cases, @@ -39,25 +36,9 @@ REJECTION_CASES = generate_bson_rejection_test_cases(BSON_TYPE_PARAMS) -# The three documented filter values accepted by getLog, paired with stable ids. -VALID_FILTERS = [ - ("global", "filter_global"), - ("startupWarnings", "filter_startupWarnings"), - ("*", "filter_wildcard"), -] -VALID_FILTER_IDS = [fid for _, fid in VALID_FILTERS] -VALID_FILTER_VALUES = [value for value, _ in VALID_FILTERS] - @pytest.mark.parametrize("bson_type,sample_value,spec", REJECTION_CASES) def test_getLog_rejects_non_string_value(collection, bson_type, sample_value, spec): """Test getLog rejects each non-string BSON type for its value.""" result = execute_admin_command(collection, {"getLog": sample_value}) assertFailureCode(result, spec.expected_code(bson_type), msg=spec.msg) - - -@pytest.mark.parametrize("value", VALID_FILTER_VALUES, ids=VALID_FILTER_IDS) -def test_getLog_accepts_valid_filter(collection, value): - """Test getLog accepts each documented filter string and returns ok:1.""" - result = execute_admin_command(collection, {"getLog": value}) - assertSuccessPartial(result, {"ok": 1.0}, msg=f"getLog should accept '{value}'") diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_errors.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_errors.py index 6e04ad2ce..5d8cdb4eb 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_errors.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_errors.py @@ -51,19 +51,21 @@ error_code=UNRECOGNIZED_COMMAND_FIELD_ERROR, msg="Unrecognized command field should error", ), + DiagnosticTestCase( + "non_admin_database", + command={"getLog": "global"}, + use_admin=False, + error_code=UNAUTHORIZED_ERROR, + msg="getLog should only run on the admin database", + ), ] @pytest.mark.parametrize("test", pytest_params(ERROR_TESTS)) def test_getLog_error(collection, test): """Test getLog returns the expected error code for invalid arguments.""" - result = execute_admin_command(collection, test.command) + if test.use_admin: + result = execute_admin_command(collection, test.command) + else: + result = execute_command(collection, test.command) assertFailureCode(result, test.error_code, msg=test.msg) - - -def test_getLog_non_admin_database(collection): - """Test getLog run against a non-admin database returns Unauthorized.""" - result = execute_command(collection, {"getLog": "global"}) - assertFailureCode( - result, UNAUTHORIZED_ERROR, msg="getLog should only run on the admin database" - ) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py index 618232ba6..cbd1110b3 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py @@ -16,8 +16,12 @@ import pytest +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) from documentdb_tests.framework.assertions import assertProperties, assertSuccess from documentdb_tests.framework.executor import execute_admin_command +from documentdb_tests.framework.parametrize import pytest_params from documentdb_tests.framework.property_checks import Exists, Gte pytestmark = pytest.mark.admin @@ -37,59 +41,52 @@ def _parses_as_json(entry: object) -> bool: return False -def test_getLog_global_has_log_entries(collection): - """Test getLog 'global' returns at least one log entry from the RAM cache.""" - result = execute_admin_command(collection, {"getLog": "global"}) - assertProperties( - result, {"log.0": Exists()}, msg="A running server should have log entries", raw_res=True - ) - - -def test_getLog_global_totalLinesWritten_non_zero(collection): - """Test getLog 'global' reports a non-zero totalLinesWritten on a running server.""" - result = execute_admin_command(collection, {"getLog": "global"}) - assertProperties( - result, - {"totalLinesWritten": Gte(1)}, +PROPERTY_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "has_log_entries", + command={"getLog": "global"}, + checks={"log.0": Exists()}, + msg="A running server should have log entries", + ), + DiagnosticTestCase( + "totalLinesWritten_non_zero", + command={"getLog": "global"}, + checks={"totalLinesWritten": Gte(1)}, msg="totalLinesWritten should be >= 1 on a running server", - raw_res=True, - ) - - -def test_getLog_global_totalLinesWritten_gte_log_length(collection): - """Test totalLinesWritten is >= the number of returned log entries.""" + ), +] + +BEHAVIOR_CASES = [ + pytest.param( + "totalLinesWritten should be >= len(log)", + lambda r: r["totalLinesWritten"] >= len(r["log"]), + id="totalLinesWritten_gte_log_length", + ), + pytest.param( + "log array should contain at most 1024 entries", + lambda r: len(r["log"]) <= MAX_LOG_EVENTS, + id="log_capped_at_1024", + ), + pytest.param( + "log entries should be JSON-parseable strings", + lambda r: all(_parses_as_json(entry) for entry in r["log"]), + id="entries_parse_as_json", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(PROPERTY_TESTS)) +def test_getLog_global_properties(collection, test): + """Verify a getLog 'global' response field exists and has the expected type or value.""" + result = execute_admin_command(collection, test.command) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) + + +@pytest.mark.parametrize("msg,transform", BEHAVIOR_CASES) +def test_getLog_global_invariants(collection, msg, transform): + """Verify stable invariants of the getLog 'global' log array and line counter.""" result = execute_admin_command(collection, {"getLog": "global"}) - assertSuccess( - result, - True, - msg="totalLinesWritten should be >= len(log)", - raw_res=True, - transform=lambda r: r["totalLinesWritten"] >= len(r["log"]), - ) - - -def test_getLog_global_log_capped_at_1024(collection): - """Test getLog 'global' returns at most 1024 log entries.""" - result = execute_admin_command(collection, {"getLog": "global"}) - assertSuccess( - result, - True, - msg="log array should contain at most 1024 entries", - raw_res=True, - transform=lambda r: len(r["log"]) <= MAX_LOG_EVENTS, - ) - - -def test_getLog_global_entries_parse_as_json(collection): - """Test every getLog 'global' log entry is a JSON-parseable string.""" - result = execute_admin_command(collection, {"getLog": "global"}) - assertSuccess( - result, - True, - msg="log entries should be JSON-parseable strings", - raw_res=True, - transform=lambda r: all(_parses_as_json(entry) for entry in r["log"]), - ) + assertSuccess(result, True, msg=msg, raw_res=True, transform=transform) def test_getLog_totalLinesWritten_non_decreasing_after_logRotate(collection): From 37c0d29c4e10ef72f7f9695378c29594155a4518 Mon Sep 17 00:00:00 2001 From: PatersonProjects Date: Tue, 23 Jun 2026 10:01:13 -0700 Subject: [PATCH 3/8] Reorganized Tests, removed out of scope cases Signed-off-by: PatersonProjects --- .../getLog/test_getLog_log_behavior.py | 104 ------------------ ...r.py => test_getLog_response_structure.py} | 11 +- documentdb_tests/framework/property_checks.py | 19 ++++ 3 files changed, 29 insertions(+), 105 deletions(-) delete mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py rename documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/{test_getLog_core_behavior.py => test_getLog_response_structure.py} (90%) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py deleted file mode 100644 index cbd1110b3..000000000 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py +++ /dev/null @@ -1,104 +0,0 @@ -"""Tests for getLog "global" log content behavior. - -Covers the stable, deterministic parts of getLog's logging contract: a running -server returns log entries from its RAM cache, totalLinesWritten is consistent -with the returned log array, the array is capped at 1024 entries, entries are -JSON-parseable strings, and totalLinesWritten does not decrease across a -logRotate. - -Non-deterministic behaviors documented for getLog — exact >1024-character line -truncation, specific character-escape sequences, and which particular messages -appear after a logRotate — depend on runtime log content and are intentionally -not asserted here. -""" - -import json - -import pytest - -from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( - DiagnosticTestCase, -) -from documentdb_tests.framework.assertions import assertProperties, assertSuccess -from documentdb_tests.framework.executor import execute_admin_command -from documentdb_tests.framework.parametrize import pytest_params -from documentdb_tests.framework.property_checks import Exists, Gte - -pytestmark = pytest.mark.admin - -# getLog "global" returns at most the most recent 1024 logged events. -MAX_LOG_EVENTS = 1024 - - -def _parses_as_json(entry: object) -> bool: - """Return True if entry is a string that parses as a JSON document.""" - if not isinstance(entry, str): - return False - try: - json.loads(entry) - return True - except ValueError: - return False - - -PROPERTY_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( - "has_log_entries", - command={"getLog": "global"}, - checks={"log.0": Exists()}, - msg="A running server should have log entries", - ), - DiagnosticTestCase( - "totalLinesWritten_non_zero", - command={"getLog": "global"}, - checks={"totalLinesWritten": Gte(1)}, - msg="totalLinesWritten should be >= 1 on a running server", - ), -] - -BEHAVIOR_CASES = [ - pytest.param( - "totalLinesWritten should be >= len(log)", - lambda r: r["totalLinesWritten"] >= len(r["log"]), - id="totalLinesWritten_gte_log_length", - ), - pytest.param( - "log array should contain at most 1024 entries", - lambda r: len(r["log"]) <= MAX_LOG_EVENTS, - id="log_capped_at_1024", - ), - pytest.param( - "log entries should be JSON-parseable strings", - lambda r: all(_parses_as_json(entry) for entry in r["log"]), - id="entries_parse_as_json", - ), -] - - -@pytest.mark.parametrize("test", pytest_params(PROPERTY_TESTS)) -def test_getLog_global_properties(collection, test): - """Verify a getLog 'global' response field exists and has the expected type or value.""" - result = execute_admin_command(collection, test.command) - assertProperties(result, test.checks, msg=test.msg, raw_res=True) - - -@pytest.mark.parametrize("msg,transform", BEHAVIOR_CASES) -def test_getLog_global_invariants(collection, msg, transform): - """Verify stable invariants of the getLog 'global' log array and line counter.""" - result = execute_admin_command(collection, {"getLog": "global"}) - assertSuccess(result, True, msg=msg, raw_res=True, transform=transform) - - -def test_getLog_totalLinesWritten_non_decreasing_after_logRotate(collection): - """Test totalLinesWritten does not decrease across a logRotate.""" - before = execute_admin_command(collection, {"getLog": "global"}) - before_count = before["totalLinesWritten"] - execute_admin_command(collection, {"logRotate": 1}) - after = execute_admin_command(collection, {"getLog": "global"}) - assertSuccess( - after, - True, - msg="totalLinesWritten should not decrease after logRotate", - raw_res=True, - transform=lambda r: r["totalLinesWritten"] >= before_count, - ) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_core_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_response_structure.py similarity index 90% rename from documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_core_behavior.py rename to documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_response_structure.py index 9cd6633c7..dda1c5b6a 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_core_behavior.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_response_structure.py @@ -14,10 +14,13 @@ from documentdb_tests.framework.assertions import assertProperties from documentdb_tests.framework.executor import execute_admin_command from documentdb_tests.framework.parametrize import pytest_params -from documentdb_tests.framework.property_checks import ContainsElement, Eq, Gte, IsType +from documentdb_tests.framework.property_checks import ContainsElement, Eq, Gte, IsType, LenLte pytestmark = pytest.mark.admin +# getLog "global" returns at most the most recent 1024 logged events. +MAX_LOG_EVENTS = 1024 + RESPONSE_TESTS: list[DiagnosticTestCase] = [ # "global" filter response structure @@ -33,6 +36,12 @@ checks={"log": IsType("array")}, msg="global should return a log array", ), + DiagnosticTestCase( + "global_log_capped_at_1024", + command={"getLog": "global"}, + checks={"log": LenLte(MAX_LOG_EVENTS)}, + msg="global log array should contain at most 1024 entries", + ), DiagnosticTestCase( "global_log_entry_is_string", command={"getLog": "global"}, diff --git a/documentdb_tests/framework/property_checks.py b/documentdb_tests/framework/property_checks.py index 0ffb575cf..14c371637 100644 --- a/documentdb_tests/framework/property_checks.py +++ b/documentdb_tests/framework/property_checks.py @@ -165,6 +165,25 @@ def __repr__(self) -> str: return f"{type(self).__name__}({self.expected!r})" +class LenLte(Check): + """Assert that the field is a list whose length is at most ``maximum``.""" + + def __init__(self, maximum: int) -> None: + self.maximum = maximum + + def check(self, value: Any, path: str) -> str | None: + if value is _FIELD_ABSENT: + return f"expected '{path}' to have length <= {self.maximum}, but field is missing" + if not isinstance(value, list): + return f"expected '{path}' to be a list, got {type(value).__name__}" + if len(value) > self.maximum: + return f"expected '{path}' length <= {self.maximum}, got {len(value)}" + return None + + def __repr__(self) -> str: + return f"{type(self).__name__}({self.maximum!r})" + + class Contains(Check): """Assert that a list contains a dict where ``key`` equals ``value``.""" From bdde7c8a85a98b460d8e3d660257b35a5bd010c9 Mon Sep 17 00:00:00 2001 From: PatersonProjects Date: Tue, 23 Jun 2026 11:23:46 -0700 Subject: [PATCH 4/8] Added log behavior tests and needed checks Signed-off-by: PatersonProjects --- .../getLog/test_getLog_log_behavior.py | 57 +++++++++++++++++ documentdb_tests/framework/property_checks.py | 63 +++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py new file mode 100644 index 000000000..b48d1b6ba --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py @@ -0,0 +1,57 @@ +"""Tests for getLog log-entry behavior. + +Validates documented properties of the "global" and "startupWarnings" log +payloads: each entry is a Relaxed Extended JSON v2.0 string with special +characters properly escaped (quotes and backslashes escaped, no raw control +characters). +""" + +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_admin_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.property_checks import StringsMaxLength, WellFormedJsonStrings + +pytestmark = pytest.mark.admin + +# getLog truncates any log event longer than 1024 characters. +MAX_LOG_LINE_CHARS = 1024 + + +LOG_BEHAVIOR_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "global_log_entries_escaped", + command={"getLog": "global"}, + checks={"log": WellFormedJsonStrings()}, + msg="global log entries should be JSON strings with special characters escaped", + ), + DiagnosticTestCase( + "startupWarnings_log_entries_escaped", + command={"getLog": "startupWarnings"}, + checks={"log": WellFormedJsonStrings()}, + msg="startupWarnings log entries should be JSON strings with special characters escaped", + ), + DiagnosticTestCase( + "global_log_entries_truncated_at_1024", + command={"getLog": "global"}, + checks={"log": StringsMaxLength(MAX_LOG_LINE_CHARS)}, + msg="global log entries should be truncated to at most 1024 characters", + ), + DiagnosticTestCase( + "startupWarnings_log_entries_truncated_at_1024", + command={"getLog": "startupWarnings"}, + checks={"log": StringsMaxLength(MAX_LOG_LINE_CHARS)}, + msg="startupWarnings log entries should be truncated to at most 1024 characters", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(LOG_BEHAVIOR_TESTS)) +def test_getLog_log_behavior(collection, test): + """Verify documented getLog log-entry behavior (escaping and truncation).""" + result = execute_admin_command(collection, test.command) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/framework/property_checks.py b/documentdb_tests/framework/property_checks.py index 14c371637..b8be72143 100644 --- a/documentdb_tests/framework/property_checks.py +++ b/documentdb_tests/framework/property_checks.py @@ -6,6 +6,7 @@ from __future__ import annotations +import json from datetime import datetime as _datetime from typing import Any @@ -347,3 +348,65 @@ def check(self, value: Any, path: str) -> str | None: def __repr__(self) -> str: return f"{type(self).__name__}()" + + +class WellFormedJsonStrings(Check): + """Assert a field is a list of well-formed, properly escaped JSON strings. + + Each entry must be a string that parses as JSON -- which fails if a quote + or backslash is left unescaped -- and must contain no raw C0 control + characters (newline, tab, etc.), which a Relaxed Extended JSON v2.0 log + line is required to emit as escape sequences rather than literal bytes. + """ + + _CONTROL_CHARS = frozenset(chr(c) for c in range(0x20)) + + def check(self, value: Any, path: str) -> str | None: + if value is _FIELD_ABSENT: + return f"expected '{path}' to exist" + if not isinstance(value, list): + return f"expected '{path}' to be a list, got {type(value).__name__}" + for i, entry in enumerate(value): + if not isinstance(entry, str): + return f"expected '{path}.{i}' to be a string, got {type(entry).__name__}" + try: + json.loads(entry) + except ValueError as exc: + return f"expected '{path}.{i}' to be valid escaped JSON, got {entry!r}: {exc}" + raw = self._CONTROL_CHARS.intersection(entry) + if raw: + return ( + f"expected '{path}.{i}' to escape control characters, " + f"found raw {sorted(ord(c) for c in raw)!r} in {entry!r}" + ) + return None + + def __repr__(self) -> str: + return f"{type(self).__name__}()" + + +class StringsMaxLength(Check): + """Assert a field is a list of strings each at most ``maximum`` characters. + + Used for fields whose entries are length-capped (e.g. getLog truncates any + log event longer than 1024 characters), so every returned entry must + respect the cap. + """ + + def __init__(self, maximum: int) -> None: + self.maximum = maximum + + def check(self, value: Any, path: str) -> str | None: + if value is _FIELD_ABSENT: + return f"expected '{path}' to exist" + if not isinstance(value, list): + return f"expected '{path}' to be a list, got {type(value).__name__}" + for i, entry in enumerate(value): + if not isinstance(entry, str): + return f"expected '{path}.{i}' to be a string, got {type(entry).__name__}" + if len(entry) > self.maximum: + return f"expected '{path}.{i}' length <= {self.maximum}, got {len(entry)}" + return None + + def __repr__(self) -> str: + return f"{type(self).__name__}({self.maximum!r})" From 963555c3f64338168a65066deee8bc9c6dca7868 Mon Sep 17 00:00:00 2001 From: PatersonProjects Date: Tue, 23 Jun 2026 12:29:29 -0700 Subject: [PATCH 5/8] Test cleanup, added missing coverage Signed-off-by: PatersonProjects --- .../system/diagnostic/commands/getLog/__init__.py | 0 .../commands/getLog/test_getLog_log_behavior.py | 12 ------------ .../getLog/test_getLog_response_structure.py | 6 ++++++ 3 files changed, 6 insertions(+), 12 deletions(-) create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/__init__.py diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/__init__.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py index b48d1b6ba..d331fc923 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py @@ -29,24 +29,12 @@ checks={"log": WellFormedJsonStrings()}, msg="global log entries should be JSON strings with special characters escaped", ), - DiagnosticTestCase( - "startupWarnings_log_entries_escaped", - command={"getLog": "startupWarnings"}, - checks={"log": WellFormedJsonStrings()}, - msg="startupWarnings log entries should be JSON strings with special characters escaped", - ), DiagnosticTestCase( "global_log_entries_truncated_at_1024", command={"getLog": "global"}, checks={"log": StringsMaxLength(MAX_LOG_LINE_CHARS)}, msg="global log entries should be truncated to at most 1024 characters", ), - DiagnosticTestCase( - "startupWarnings_log_entries_truncated_at_1024", - command={"getLog": "startupWarnings"}, - checks={"log": StringsMaxLength(MAX_LOG_LINE_CHARS)}, - msg="startupWarnings log entries should be truncated to at most 1024 characters", - ), ] diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_response_structure.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_response_structure.py index dda1c5b6a..fc823ea65 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_response_structure.py @@ -67,6 +67,12 @@ checks={"ok": Eq(1.0)}, msg="startupWarnings should return ok:1", ), + DiagnosticTestCase( + "startupWarnings_totalLinesWritten_number", + command={"getLog": "startupWarnings"}, + checks={"totalLinesWritten": Gte(0)}, + msg="startupWarnings should return a non-negative totalLinesWritten", + ), # "*" filter lists the available log filters DiagnosticTestCase( "wildcard_names_is_array", From e8edf8eb8235493121d3181e520fed322905835f Mon Sep 17 00:00:00 2001 From: PatersonProjects Date: Tue, 23 Jun 2026 13:15:58 -0700 Subject: [PATCH 6/8] Dropped log formatting test Signed-off-by: PatersonProjects --- .../getLog/test_getLog_log_behavior.py | 45 ------------------- .../getLog/test_getLog_response_structure.py | 23 ++++++++-- documentdb_tests/framework/property_checks.py | 36 --------------- 3 files changed, 19 insertions(+), 85 deletions(-) delete mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py deleted file mode 100644 index d331fc923..000000000 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_log_behavior.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Tests for getLog log-entry behavior. - -Validates documented properties of the "global" and "startupWarnings" log -payloads: each entry is a Relaxed Extended JSON v2.0 string with special -characters properly escaped (quotes and backslashes escaped, no raw control -characters). -""" - -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_admin_command -from documentdb_tests.framework.parametrize import pytest_params -from documentdb_tests.framework.property_checks import StringsMaxLength, WellFormedJsonStrings - -pytestmark = pytest.mark.admin - -# getLog truncates any log event longer than 1024 characters. -MAX_LOG_LINE_CHARS = 1024 - - -LOG_BEHAVIOR_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( - "global_log_entries_escaped", - command={"getLog": "global"}, - checks={"log": WellFormedJsonStrings()}, - msg="global log entries should be JSON strings with special characters escaped", - ), - DiagnosticTestCase( - "global_log_entries_truncated_at_1024", - command={"getLog": "global"}, - checks={"log": StringsMaxLength(MAX_LOG_LINE_CHARS)}, - msg="global log entries should be truncated to at most 1024 characters", - ), -] - - -@pytest.mark.parametrize("test", pytest_params(LOG_BEHAVIOR_TESTS)) -def test_getLog_log_behavior(collection, test): - """Verify documented getLog log-entry behavior (escaping and truncation).""" - result = execute_admin_command(collection, test.command) - assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_response_structure.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_response_structure.py index fc823ea65..e5502feba 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_response_structure.py @@ -1,9 +1,9 @@ """Tests for getLog command core behavior and response structure. Covers the response shape for the "global" and "startupWarnings" filters -(totalLinesWritten, log array, string log entries, ok) and for the "*" -filter (names array listing the available filters, ok). Each test asserts a -single response property. +(totalLinesWritten, log array, string log entries truncated at 1024 chars, ok) +and for the "*" filter (names array listing the available filters, ok). Each +test asserts a single response property. """ import pytest @@ -14,12 +14,21 @@ from documentdb_tests.framework.assertions import assertProperties from documentdb_tests.framework.executor import execute_admin_command from documentdb_tests.framework.parametrize import pytest_params -from documentdb_tests.framework.property_checks import ContainsElement, Eq, Gte, IsType, LenLte +from documentdb_tests.framework.property_checks import ( + ContainsElement, + Eq, + Gte, + IsType, + LenLte, + StringsMaxLength, +) pytestmark = pytest.mark.admin # getLog "global" returns at most the most recent 1024 logged events. MAX_LOG_EVENTS = 1024 +# getLog truncates any individual log event longer than 1024 characters. +MAX_LOG_LINE_CHARS = 1024 RESPONSE_TESTS: list[DiagnosticTestCase] = [ @@ -48,6 +57,12 @@ checks={"log.0": IsType("string")}, msg="global log entries should be JSON-formatted strings", ), + DiagnosticTestCase( + "global_log_entries_truncated_at_1024", + command={"getLog": "global"}, + checks={"log": StringsMaxLength(MAX_LOG_LINE_CHARS)}, + msg="global log entries should be truncated to at most 1024 characters", + ), DiagnosticTestCase( "global_ok", command={"getLog": "global"}, diff --git a/documentdb_tests/framework/property_checks.py b/documentdb_tests/framework/property_checks.py index b8be72143..0a62b56c7 100644 --- a/documentdb_tests/framework/property_checks.py +++ b/documentdb_tests/framework/property_checks.py @@ -6,7 +6,6 @@ from __future__ import annotations -import json from datetime import datetime as _datetime from typing import Any @@ -350,41 +349,6 @@ def __repr__(self) -> str: return f"{type(self).__name__}()" -class WellFormedJsonStrings(Check): - """Assert a field is a list of well-formed, properly escaped JSON strings. - - Each entry must be a string that parses as JSON -- which fails if a quote - or backslash is left unescaped -- and must contain no raw C0 control - characters (newline, tab, etc.), which a Relaxed Extended JSON v2.0 log - line is required to emit as escape sequences rather than literal bytes. - """ - - _CONTROL_CHARS = frozenset(chr(c) for c in range(0x20)) - - def check(self, value: Any, path: str) -> str | None: - if value is _FIELD_ABSENT: - return f"expected '{path}' to exist" - if not isinstance(value, list): - return f"expected '{path}' to be a list, got {type(value).__name__}" - for i, entry in enumerate(value): - if not isinstance(entry, str): - return f"expected '{path}.{i}' to be a string, got {type(entry).__name__}" - try: - json.loads(entry) - except ValueError as exc: - return f"expected '{path}.{i}' to be valid escaped JSON, got {entry!r}: {exc}" - raw = self._CONTROL_CHARS.intersection(entry) - if raw: - return ( - f"expected '{path}.{i}' to escape control characters, " - f"found raw {sorted(ord(c) for c in raw)!r} in {entry!r}" - ) - return None - - def __repr__(self) -> str: - return f"{type(self).__name__}()" - - class StringsMaxLength(Check): """Assert a field is a list of strings each at most ``maximum`` characters. From da6cac0047db1317ec87bac309d9b4cab6489674 Mon Sep 17 00:00:00 2001 From: PatersonProjects Date: Tue, 23 Jun 2026 13:35:10 -0700 Subject: [PATCH 7/8] Removed comments Signed-off-by: PatersonProjects --- .../getLog/test_getLog_argument_validation.py | 3 --- .../commands/getLog/test_getLog_errors.py | 1 - .../getLog/test_getLog_response_structure.py | 15 +++++---------- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_argument_validation.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_argument_validation.py index fb2de4b7a..d5b960f4a 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_argument_validation.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_argument_validation.py @@ -20,9 +20,6 @@ pytestmark = pytest.mark.admin -# getLog accepts only a string value; every other BSON type is rejected with -# TypeMismatch, except a null value, which is treated as an absent required -# field (MissingField). BSON_TYPE_PARAMS = [ BsonTypeTestCase( id="getLog_value", diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_errors.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_errors.py index 5d8cdb4eb..9caa0f330 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_errors.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_errors.py @@ -25,7 +25,6 @@ pytestmark = pytest.mark.admin -# An unknown, deprecated, or empty log component name fails with OperationFailed. ERROR_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( "unknown_component", diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_response_structure.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_response_structure.py index e5502feba..814d2011d 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_response_structure.py @@ -1,9 +1,9 @@ -"""Tests for getLog command core behavior and response structure. +"""Tests for getLog command response structure. -Covers the response shape for the "global" and "startupWarnings" filters -(totalLinesWritten, log array, string log entries truncated at 1024 chars, ok) -and for the "*" filter (names array listing the available filters, ok). Each -test asserts a single response property. +Covers response fields for the "global" filter (totalLinesWritten, log array +capped at 1024 entries with each entry truncated at 1024 characters, ok), the +"startupWarnings" filter (totalLinesWritten, log array, ok), and the "*" +filter (names array, ok). Each test asserts a single response property. """ import pytest @@ -25,14 +25,11 @@ pytestmark = pytest.mark.admin -# getLog "global" returns at most the most recent 1024 logged events. MAX_LOG_EVENTS = 1024 -# getLog truncates any individual log event longer than 1024 characters. MAX_LOG_LINE_CHARS = 1024 RESPONSE_TESTS: list[DiagnosticTestCase] = [ - # "global" filter response structure DiagnosticTestCase( "global_totalLinesWritten_number", command={"getLog": "global"}, @@ -69,7 +66,6 @@ checks={"ok": Eq(1.0)}, msg="global should return ok:1", ), - # "startupWarnings" filter response structure (log array may be empty) DiagnosticTestCase( "startupWarnings_log_is_array", command={"getLog": "startupWarnings"}, @@ -88,7 +84,6 @@ checks={"totalLinesWritten": Gte(0)}, msg="startupWarnings should return a non-negative totalLinesWritten", ), - # "*" filter lists the available log filters DiagnosticTestCase( "wildcard_names_is_array", command={"getLog": "*"}, From e5ae6a28813a88be7ecd02de3daca0ff87f9d6b5 Mon Sep 17 00:00:00 2001 From: PatersonProjects Date: Tue, 23 Jun 2026 13:41:11 -0700 Subject: [PATCH 8/8] Dropped out of scope test Signed-off-by: PatersonProjects --- .../getLog/test_getLog_response_structure.py | 22 +++------------ documentdb_tests/framework/property_checks.py | 27 ------------------- 2 files changed, 4 insertions(+), 45 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_response_structure.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_response_structure.py index 814d2011d..2ddc57cea 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/getLog/test_getLog_response_structure.py @@ -1,9 +1,9 @@ """Tests for getLog command response structure. Covers response fields for the "global" filter (totalLinesWritten, log array -capped at 1024 entries with each entry truncated at 1024 characters, ok), the -"startupWarnings" filter (totalLinesWritten, log array, ok), and the "*" -filter (names array, ok). Each test asserts a single response property. +capped at 1024 entries, string log entries, ok), the "startupWarnings" filter +(totalLinesWritten, log array, ok), and the "*" filter (names array, ok). +Each test asserts a single response property. """ import pytest @@ -14,19 +14,11 @@ from documentdb_tests.framework.assertions import assertProperties from documentdb_tests.framework.executor import execute_admin_command from documentdb_tests.framework.parametrize import pytest_params -from documentdb_tests.framework.property_checks import ( - ContainsElement, - Eq, - Gte, - IsType, - LenLte, - StringsMaxLength, -) +from documentdb_tests.framework.property_checks import ContainsElement, Eq, Gte, IsType, LenLte pytestmark = pytest.mark.admin MAX_LOG_EVENTS = 1024 -MAX_LOG_LINE_CHARS = 1024 RESPONSE_TESTS: list[DiagnosticTestCase] = [ @@ -54,12 +46,6 @@ checks={"log.0": IsType("string")}, msg="global log entries should be JSON-formatted strings", ), - DiagnosticTestCase( - "global_log_entries_truncated_at_1024", - command={"getLog": "global"}, - checks={"log": StringsMaxLength(MAX_LOG_LINE_CHARS)}, - msg="global log entries should be truncated to at most 1024 characters", - ), DiagnosticTestCase( "global_ok", command={"getLog": "global"}, diff --git a/documentdb_tests/framework/property_checks.py b/documentdb_tests/framework/property_checks.py index 0a62b56c7..14c371637 100644 --- a/documentdb_tests/framework/property_checks.py +++ b/documentdb_tests/framework/property_checks.py @@ -347,30 +347,3 @@ def check(self, value: Any, path: str) -> str | None: def __repr__(self) -> str: return f"{type(self).__name__}()" - - -class StringsMaxLength(Check): - """Assert a field is a list of strings each at most ``maximum`` characters. - - Used for fields whose entries are length-capped (e.g. getLog truncates any - log event longer than 1024 characters), so every returned entry must - respect the cap. - """ - - def __init__(self, maximum: int) -> None: - self.maximum = maximum - - def check(self, value: Any, path: str) -> str | None: - if value is _FIELD_ABSENT: - return f"expected '{path}' to exist" - if not isinstance(value, list): - return f"expected '{path}' to be a list, got {type(value).__name__}" - for i, entry in enumerate(value): - if not isinstance(entry, str): - return f"expected '{path}.{i}' to be a string, got {type(entry).__name__}" - if len(entry) > self.maximum: - return f"expected '{path}.{i}' length <= {self.maximum}, got {len(entry)}" - return None - - def __repr__(self) -> str: - return f"{type(self).__name__}({self.maximum!r})"