Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""Tests for logRotate BSON type validation.

The value field accepts numeric/bool types and the string "server"; other
types are rejected with TypeMismatch. The comment field accepts all types.
"""

import pytest

from documentdb_tests.framework.assertions import assertFailureCode, assertSuccessPartial
from documentdb_tests.framework.bson_type_validator import (
BsonType,
BsonTypeTestCase,
generate_bson_acceptance_test_cases,
generate_bson_rejection_test_cases,
)
from documentdb_tests.framework.error_codes import FILE_RENAME_FAILED_ERROR, TYPE_MISMATCH_ERROR
from documentdb_tests.framework.executor import (
execute_admin_command,
execute_admin_with_retry_command,
)

pytestmark = [pytest.mark.admin, pytest.mark.no_parallel]


LOG_ROTATE_VALUE_PARAMS = [
BsonTypeTestCase(
id="logRotate_value",
msg="logRotate value should accept numeric, bool, and the 'server' string",
keyword="logRotate",
valid_types=[
BsonType.INT,
BsonType.DOUBLE,
BsonType.LONG,
BsonType.BOOL,
BsonType.DECIMAL,
BsonType.STRING,
],
valid_inputs={BsonType.STRING: "server"},
default_error_code=TYPE_MISMATCH_ERROR,
),
]

COMMENT_PARAMS = [
BsonTypeTestCase(
id="comment",
msg="logRotate should accept all BSON types for the comment field",
keyword="comment",
valid_types=list(BsonType),
),
]


LOG_ROTATE_VALUE_ACCEPTANCE = generate_bson_acceptance_test_cases(LOG_ROTATE_VALUE_PARAMS)
LOG_ROTATE_VALUE_REJECTIONS = generate_bson_rejection_test_cases(LOG_ROTATE_VALUE_PARAMS)
COMMENT_ACCEPTANCE = generate_bson_acceptance_test_cases(COMMENT_PARAMS)


@pytest.mark.parametrize("bson_type,sample_value,spec", LOG_ROTATE_VALUE_ACCEPTANCE)
def test_logRotate_value_bson_type_accepted(collection, bson_type, sample_value, spec):
"""Test logRotate accepts numeric, bool, and the 'server' string value."""
result = execute_admin_with_retry_command(
collection, {"logRotate": sample_value}, retry_code=FILE_RENAME_FAILED_ERROR
)
assertSuccessPartial(
result,
{"ok": 1.0},
msg=f"{spec.msg} (bson_type={bson_type.value})",
)


@pytest.mark.parametrize("bson_type,sample_value,spec", COMMENT_ACCEPTANCE)
def test_logRotate_comment_bson_type_accepted(collection, bson_type, sample_value, spec):
"""Test comment field accepts all BSON types."""
result = execute_admin_with_retry_command(
collection, {"logRotate": 1, "comment": sample_value}, retry_code=FILE_RENAME_FAILED_ERROR
)
assertSuccessPartial(
result,
{"ok": 1.0},
msg=f"comment should accept {bson_type.value}",
)


@pytest.mark.parametrize("bson_type,sample_value,spec", LOG_ROTATE_VALUE_REJECTIONS)
def test_logRotate_value_bson_type_rejected(collection, bson_type, sample_value, spec):
"""Test logRotate value rejects non-numeric, non-string BSON types."""
result = execute_admin_command(collection, {"logRotate": sample_value})
assertFailureCode(
result,
spec.expected_code(bson_type),
msg=f"logRotate should reject {bson_type.value} for the command value",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Tests for logRotate command error cases."""

import pytest

from documentdb_tests.compatibility.tests.system.administration.utils.admin_test_case import (
AdminTestCase,
)
from documentdb_tests.framework.assertions import assertFailureCode
from documentdb_tests.framework.error_codes import (
NO_SUCH_KEY_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, pytest.mark.no_parallel]


INVALID_STRING_TESTS: list[AdminTestCase] = [
AdminTestCase(
"invalid_string",
command={"logRotate": "invalid"},
error_code=NO_SUCH_KEY_ERROR,
msg="Should reject invalid string value",
),
AdminTestCase(
"empty_string",
command={"logRotate": ""},
error_code=NO_SUCH_KEY_ERROR,
msg="Should reject empty string",
),
AdminTestCase(
"case_sensitive_SERVER",
command={"logRotate": "SERVER"},
error_code=NO_SUCH_KEY_ERROR,
msg="Should reject uppercase SERVER (case-sensitive)",
),
AdminTestCase(
"case_sensitive_Audit",
command={"logRotate": "Audit"},
error_code=NO_SUCH_KEY_ERROR,
msg="Should reject mixed-case Audit (case-sensitive)",
),
]


@pytest.mark.parametrize("test", pytest_params(INVALID_STRING_TESTS))
def test_logRotate_invalid_arguments(collection, test):
"""Test that logRotate rejects invalid string values."""
result = execute_admin_command(collection, test.command)
assertFailureCode(result, test.error_code, msg=test.msg)


def test_logRotate_unrecognized_field(collection):
"""Test that logRotate rejects unrecognized top-level fields."""
result = execute_admin_command(collection, {"logRotate": 1, "unknownField": 1})
assertFailureCode(
result, UNRECOGNIZED_COMMAND_FIELD_ERROR, msg="Should reject unrecognized fields"
)


def test_logRotate_non_admin_database(collection):
"""Test that logRotate fails when run on a non-admin database."""
result = execute_command(collection, {"logRotate": 1})
assertFailureCode(result, UNAUTHORIZED_ERROR, msg="Should fail on non-admin database")


def test_logRotate_audit_target_rejected_when_disabled(collection):
"""Test that the 'audit' log target is rejected when auditing is disabled."""
result = execute_admin_command(collection, {"logRotate": "audit"})
assertFailureCode(
result, NO_SUCH_KEY_ERROR, msg="Should reject audit target when auditing is disabled"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Tests for logRotate acceptance of specific scalar values.

Covers values the shared BSON-type harness does not sample: boolean `false`, `0`,
and negative integers/longs (it only feeds `True` and `INT32_MAX`/`INT64_MAX`).
Each rotation goes through `execute_admin_with_retry_command`, which retries past the
transient FileRenameFailed so the test can assert a clean success.
"""

import pytest
from bson.int64 import Int64

from documentdb_tests.framework.assertions import assertSuccessPartial
from documentdb_tests.framework.error_codes import FILE_RENAME_FAILED_ERROR
from documentdb_tests.framework.executor import execute_admin_with_retry_command

pytestmark = [pytest.mark.admin, pytest.mark.no_parallel]


def test_logRotate_value_bool_false_accepted(collection):
"""Test logRotate accepts boolean false (the BSON sample only covers true)."""
result = execute_admin_with_retry_command(
collection, {"logRotate": False}, retry_code=FILE_RENAME_FAILED_ERROR
)
assertSuccessPartial(result, {"ok": 1.0}, msg="logRotate value should accept boolean false")


@pytest.mark.parametrize(
"value",
[0, -1, Int64(-5)],
ids=["zero", "negative_int", "negative_long"],
)
def test_logRotate_value_zero_and_negative_accepted(collection, value):
"""Test logRotate accepts 0 and negative integers (BSON samples only cover max values)."""
result = execute_admin_with_retry_command(
collection, {"logRotate": value}, retry_code=FILE_RENAME_FAILED_ERROR
)
assertSuccessPartial(result, {"ok": 1.0}, msg=f"logRotate value should accept {value!r}")
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
"""
Smoke test for logRotate command.

Tests basic logRotate functionality.
"""
"""Smoke test for logRotate command."""

import pytest

from documentdb_tests.framework.assertions import assertSuccessPartial
from documentdb_tests.framework.executor import execute_admin_command
from documentdb_tests.framework.error_codes import FILE_RENAME_FAILED_ERROR
from documentdb_tests.framework.executor import execute_admin_with_retry_command

pytestmark = [pytest.mark.smoke, pytest.mark.no_parallel]


def test_smoke_logRotate(collection):
"""Test basic logRotate behavior."""
result = execute_admin_command(collection, {"logRotate": 1})

expected = {"ok": 1.0}
assertSuccessPartial(result, expected, msg="Should support logRotate command")
result = execute_admin_with_retry_command(
collection, {"logRotate": 1}, retry_code=FILE_RENAME_FAILED_ERROR
)
assertSuccessPartial(result, {"ok": 1.0}, msg="Should support logRotate command")
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Shared test case for administration command tests."""

from dataclasses import dataclass
from typing import Any, Dict, Optional

from documentdb_tests.framework.test_case import BaseTestCase


@dataclass(frozen=True)
class AdminTestCase(BaseTestCase):
"""Test case for administration command tests.

Inherits ``id``, ``expected``, ``error_code``, ``msg``, and ``marks`` from
``BaseTestCase``.

Attributes:
command: The command document to execute.
use_admin: If True, execute against the admin database.
"""

command: Optional[Dict[str, Any]] = None
use_admin: bool = True
1 change: 1 addition & 0 deletions documentdb_tests/framework/error_codes.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
NAMESPACE_NOT_FOUND_ERROR = 26
INDEX_NOT_FOUND_ERROR = 27
PATH_NOT_VIABLE_ERROR = 28
FILE_RENAME_FAILED_ERROR = 37
CONFLICTING_UPDATE_OPERATORS_ERROR = 40
CURSOR_NOT_FOUND_ERROR = 43
NAMESPACE_EXISTS_ERROR = 48
Expand Down
29 changes: 29 additions & 0 deletions documentdb_tests/framework/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Unified execution and assertion utilities for tests.
"""

import time
from datetime import timezone
from typing import Any, Dict

Expand Down Expand Up @@ -49,3 +50,31 @@ def execute_admin_command(collection, command: Dict) -> Any:
return result
except Exception as e:
return e


def execute_admin_with_retry_command(
collection, command: Dict, *, retry_code: int, timeout: float = 30.0, interval: float = 0.2
) -> Any:
"""
Run an admin command, retrying while it fails with ``retry_code``.

Any other result (success or a different error) is returned immediately. On
timeout, the last result is returned as-is.

Args:
collection: DocumentDB collection
command: Command to execute via runCommand on the admin database
retry_code: Error code to treat as transient and retry past
timeout: Maximum seconds to keep retrying before returning the last result
interval: Seconds to wait between attempts

Returns:
Result if successful, Exception if failed
"""
deadline = time.monotonic() + timeout
while True:
result = execute_admin_command(collection, command)
should_retry = isinstance(result, Exception) and getattr(result, "code", None) == retry_code
if not should_retry or time.monotonic() >= deadline:
return result
time.sleep(interval)
1 change: 1 addition & 0 deletions documentdb_tests/framework/test_format_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def validate_test_format(file_path: str) -> list[str]:
"execute_project_with_insert",
"execute_expression",
"execute_expression_with_insert",
"execute_admin_with_retry_command",
]
)

Expand Down
Loading