Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
40ec48e
Reset to restore to main
PatersonProjects Jul 8, 2026
b73ce4a
Merge branch 'main' of https://github.com/PatersonProjects/functional…
PatersonProjects Jul 8, 2026
ec9e7fc
Merge branch 'main' of https://github.com/PatersonProjects/functional…
PatersonProjects Jul 8, 2026
1c87758
Merge branch 'main' of https://github.com/PatersonProjects/functional…
PatersonProjects Jul 10, 2026
df20a26
Base toDouble Tests
PatersonProjects Jul 10, 2026
8115270
Started parametrization edits
PatersonProjects Jul 10, 2026
b92395a
toDouble migrated
PatersonProjects Jul 13, 2026
b2fdff0
Migrated toInt tests
PatersonProjects Jul 13, 2026
fa51cef
Migrated toDecimal tests, added lazy to large string tests and replac…
PatersonProjects Jul 13, 2026
8330dd3
migrate: add operator tests to documentdb_tests
PatersonProjects Jul 13, 2026
6599361
migrate: step 1.5 $toLong — refactor rejection tests to bson_type_val…
PatersonProjects Jul 13, 2026
08f1d07
migrate: step 3 $toLong — fill two coverage gaps from old suite
PatersonProjects Jul 13, 2026
565fefa
migrate: step 3.5 $toLong — replace inline literals with named constants
PatersonProjects Jul 13, 2026
01f4ca7
migrate: step 4 review fixups — promote computed literals to named co…
PatersonProjects Jul 13, 2026
810ec74
revert: remove DOUBLE_BELOW_INT64_MIN from test_constants
PatersonProjects Jul 13, 2026
4acdef8
Parametrized standalone test cases
PatersonProjects Jul 14, 2026
5bb1e3a
Addressed PR Comments
PatersonProjects Jul 14, 2026
b517ca2
Merge branch 'main' into toNumber_tests
eerxuan Jul 15, 2026
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,80 @@
"""$toDecimal arity and field path syntax tests."""

import pytest
from bson import Decimal128

from documentdb_tests.compatibility.tests.core.operator.expressions.utils.expression_test_case import ( # noqa: E501
ExpressionTestCase,
)
from documentdb_tests.compatibility.tests.core.operator.expressions.utils.utils import (
assert_expression_result,
execute_expression,
)
from documentdb_tests.framework.error_codes import (
FAILED_TO_PARSE_ERROR,
INVALID_DOLLAR_FIELD_PATH,
TO_TYPE_ARITY_ERROR,
)
from documentdb_tests.framework.parametrize import pytest_params

# Property [Arity]: $toDecimal unwraps a single-element literal array at parse time;
# empty and multi-element arrays are arity errors.
TODECIMAL_ARITY_TESTS: list[ExpressionTestCase] = [
ExpressionTestCase(
"single_element",
msg="Single-element literal array argument is unwrapped to a scalar",
expression={"$toDecimal": [42]},
expected=Decimal128("42"),
),
ExpressionTestCase(
"single_null",
msg="Single-element literal array wrapping null unwraps and returns null",
expression={"$toDecimal": [None]},
expected=None,
),
ExpressionTestCase(
"empty_array",
msg="Empty literal array argument is an arity error",
expression={"$toDecimal": []},
error_code=TO_TYPE_ARITY_ERROR,
),
ExpressionTestCase(
"multi_element",
msg="Multi-element literal array argument is an arity error",
expression={"$toDecimal": [1, 2]},
error_code=TO_TYPE_ARITY_ERROR,
),
ExpressionTestCase(
"large_array",
msg="Large literal array argument is an arity error",
expression={"$toDecimal": list(range(100))},
error_code=TO_TYPE_ARITY_ERROR,
),
]

# Property [Invalid Field Path]: $toDecimal rejects malformed field path syntax.
TODECIMAL_INVALID_FIELD_PATH_TESTS: list[ExpressionTestCase] = [
ExpressionTestCase(
"bare_dollar",
msg="Bare '$' is an invalid field path",
expression={"$toDecimal": "$"},
error_code=INVALID_DOLLAR_FIELD_PATH,
),
ExpressionTestCase(
"double_dollar",
msg="'$$' is rejected as an empty variable name",
expression={"$toDecimal": "$$"},
error_code=FAILED_TO_PARSE_ERROR,
),
]


@pytest.mark.parametrize(
"test", pytest_params(TODECIMAL_ARITY_TESTS + TODECIMAL_INVALID_FIELD_PATH_TESTS)
)
def test_toDecimal_arity(collection, test: ExpressionTestCase):
Comment thread
PatersonProjects marked this conversation as resolved.
"""$toDecimal literal array arguments are unwrapped or rejected based on arity."""
result = execute_expression(collection, test.expression)
assert_expression_result(
result, expected=test.expected, error_code=test.error_code, msg=test.msg
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
"""$toDecimal datetime conversion tests and unsupported BSON type errors."""

from datetime import datetime, timezone

import pytest
from bson import Binary, Code, Decimal128, MaxKey, MinKey, ObjectId, Regex, Timestamp

from documentdb_tests.compatibility.tests.core.operator.expressions.utils.expression_test_case import ( # noqa: E501
ExpressionTestCase,
)
from documentdb_tests.compatibility.tests.core.operator.expressions.utils.utils import (
assert_expression_result,
execute_expression,
)
from documentdb_tests.framework.error_codes import CONVERSION_FAILURE_ERROR
from documentdb_tests.framework.parametrize import pytest_params
from documentdb_tests.framework.test_constants import (
DATE_BEFORE_EPOCH,
DATE_EPOCH,
DATE_YEAR_1,
DATE_YEAR_9999,
DECIMAL128_ZERO,
)

# Property [Datetime]: $toDecimal converts datetime to milliseconds since Unix epoch as Decimal128.
TODECIMAL_DATETIME_TESTS: list[ExpressionTestCase] = [
ExpressionTestCase(
"datetime_epoch",
msg="Epoch datetime converts to Decimal128('0')",
expression={"$toDecimal": DATE_EPOCH},
expected=DECIMAL128_ZERO,
),
ExpressionTestCase(
"datetime_1ms_after_epoch",
msg="1 ms after epoch converts to Decimal128('1')",
expression={"$toDecimal": datetime(1970, 1, 1, 0, 0, 0, 1_000, tzinfo=timezone.utc)},
expected=Decimal128("1"),
),
ExpressionTestCase(
"datetime_one_day",
msg="One day after epoch converts to Decimal128('86400000')",
expression={"$toDecimal": datetime(1970, 1, 2, 0, 0, 0, tzinfo=timezone.utc)},
expected=Decimal128("86400000"),
),
ExpressionTestCase(
"datetime_before_epoch",
msg="1 ms before epoch converts to Decimal128('-1')",
expression={"$toDecimal": DATE_BEFORE_EPOCH},
expected=Decimal128("-1"),
),
ExpressionTestCase(
"datetime_pre_epoch",
msg="Pre-epoch date (1960-01-01) converts to negative ms value",
expression={"$toDecimal": datetime(1960, 1, 1, tzinfo=timezone.utc)},
expected=Decimal128("-315619200000"),
),
ExpressionTestCase(
"datetime_with_millis",
msg="Datetime with sub-second precision preserves milliseconds",
expression={"$toDecimal": datetime(2018, 3, 27, 5, 4, 47, 890_000, tzinfo=timezone.utc)},
expected=Decimal128("1522127087890"),
),
ExpressionTestCase(
"datetime_2024",
msg="A modern date converts to its ms-since-epoch value",
expression={"$toDecimal": datetime(2024, 1, 1, 0, 0, 0, tzinfo=timezone.utc)},
expected=Decimal128("1704067200000"),
),
ExpressionTestCase(
"datetime_millisecond_precision",
msg="Datetime preserves millisecond precision",
expression={"$toDecimal": datetime(1970, 1, 1, 0, 0, 0, 500_000, tzinfo=timezone.utc)},
expected=Decimal128("500"),
),
ExpressionTestCase(
"datetime_far_past",
msg="Far-past datetime (year 1) converts to its ms-since-epoch value",
expression={"$toDecimal": DATE_YEAR_1},
expected=Decimal128("-62135596800000"),
),
ExpressionTestCase(
"datetime_far_future",
msg="Far-future datetime (year 9999) converts to its ms-since-epoch value",
expression={"$toDecimal": DATE_YEAR_9999},
expected=Decimal128("253402300799999"),
),
]

# Property [Unsupported Types]: $toDecimal fails with a conversion error for BSON types it
# cannot convert (object, binary, ObjectId, regex, timestamp, code, MinKey, MaxKey, array).
TODECIMAL_UNSUPPORTED_TYPE_TESTS: list[ExpressionTestCase] = [
ExpressionTestCase(
"type_object",
msg="Object BSON type is a conversion failure",
expression={"$toDecimal": {"$literal": {"a": 1}}},
error_code=CONVERSION_FAILURE_ERROR,
),
ExpressionTestCase(
"type_binary",
msg="Binary BSON type is a conversion failure",
expression={"$toDecimal": Binary(b"data")},
error_code=CONVERSION_FAILURE_ERROR,
),
ExpressionTestCase(
"type_binary_uuid",
msg="Binary UUID subtype 4 is a conversion failure",
expression={"$toDecimal": Binary(b"\x00" * 16, 4)},
error_code=CONVERSION_FAILURE_ERROR,
),
ExpressionTestCase(
"type_objectid",
msg="ObjectId BSON type is a conversion failure",
expression={"$toDecimal": ObjectId("507f1f77bcf86cd799439011")},
error_code=CONVERSION_FAILURE_ERROR,
),
ExpressionTestCase(
"type_regex",
msg="Regex BSON type is a conversion failure",
expression={"$toDecimal": Regex("abc")},
error_code=CONVERSION_FAILURE_ERROR,
),
ExpressionTestCase(
"type_timestamp",
msg="Timestamp BSON type is a conversion failure",
expression={"$toDecimal": Timestamp(1, 1)},
error_code=CONVERSION_FAILURE_ERROR,
),
ExpressionTestCase(
"type_code",
msg="Code BSON type is a conversion failure",
expression={"$toDecimal": Code("function() {}")},
error_code=CONVERSION_FAILURE_ERROR,
),
ExpressionTestCase(
"type_minkey",
msg="MinKey BSON type is a conversion failure",
expression={"$toDecimal": MinKey()},
error_code=CONVERSION_FAILURE_ERROR,
),
ExpressionTestCase(
"type_maxkey",
msg="MaxKey BSON type is a conversion failure",
expression={"$toDecimal": MaxKey()},
error_code=CONVERSION_FAILURE_ERROR,
),
ExpressionTestCase(
"type_array",
msg="Array value (from $literal) is a conversion failure",
expression={"$toDecimal": {"$literal": [1, 2]}},
error_code=CONVERSION_FAILURE_ERROR,
),
ExpressionTestCase(
"type_nested_array",
msg="Nested literal array after single-element unwrap is a conversion failure",
expression={"$toDecimal": [["hello"]]},
error_code=CONVERSION_FAILURE_ERROR,
),
]

TODECIMAL_DATETIME_TESTS = TODECIMAL_DATETIME_TESTS + TODECIMAL_UNSUPPORTED_TYPE_TESTS


@pytest.mark.parametrize("test", pytest_params(TODECIMAL_DATETIME_TESTS))
def test_toDecimal_datetime(collection, test: ExpressionTestCase):
"""$toDecimal converts datetime to ms-since-epoch Decimal128; rejects unsupported types."""
result = execute_expression(collection, test.expression)
assert_expression_result(
result, expected=test.expected, error_code=test.error_code, msg=test.msg
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
"""$toDecimal Decimal128 passthrough tests: identity, trailing zeros, exponent, and specials."""

import pytest
from bson import Decimal128

from documentdb_tests.compatibility.tests.core.operator.expressions.utils.expression_test_case import ( # noqa: E501
ExpressionTestCase,
)
from documentdb_tests.compatibility.tests.core.operator.expressions.utils.utils import (
assert_expression_result,
execute_expression,
)
from documentdb_tests.framework.parametrize import pytest_params
from documentdb_tests.framework.test_constants import (
DECIMAL128_INFINITY,
DECIMAL128_LARGE_EXPONENT,
DECIMAL128_MANY_TRAILING_ZEROS,
DECIMAL128_MAX,
DECIMAL128_MAX_COEFFICIENT,
DECIMAL128_MAX_NEGATIVE,
DECIMAL128_MIN,
DECIMAL128_MIN_POSITIVE,
DECIMAL128_NAN,
DECIMAL128_NEGATIVE_INFINITY,
DECIMAL128_NEGATIVE_NAN,
DECIMAL128_NEGATIVE_ONE_AND_HALF,
DECIMAL128_NEGATIVE_ZERO,
DECIMAL128_TRAILING_ZERO,
DECIMAL128_ZERO,
)

# Property [Decimal128 Passthrough]: $toDecimal is the identity function for Decimal128 inputs,
# preserving trailing zeros, exponent form, sign bits, and special values exactly.
TODECIMAL_DECIMAL128_TESTS: list[ExpressionTestCase] = [
ExpressionTestCase(
"dec128_zero",
msg="Decimal128 zero passes through unchanged",
expression={"$toDecimal": DECIMAL128_ZERO},
expected=DECIMAL128_ZERO,
),
ExpressionTestCase(
"dec128_negative_zero",
msg="Decimal128 negative zero passes through preserving sign",
expression={"$toDecimal": DECIMAL128_NEGATIVE_ZERO},
expected=DECIMAL128_NEGATIVE_ZERO,
),
ExpressionTestCase(
"dec128_one",
msg="Decimal128 1 passes through unchanged",
expression={"$toDecimal": Decimal128("1")},
expected=Decimal128("1"),
),
ExpressionTestCase(
"dec128_trailing_zero",
msg="Decimal128 trailing zero (1.0) passes through preserving the trailing zero",
expression={"$toDecimal": DECIMAL128_TRAILING_ZERO},
expected=DECIMAL128_TRAILING_ZERO,
),
ExpressionTestCase(
"dec128_many_trailing_zeros",
msg="Decimal128 with many trailing zeros passes through preserving all zeros",
expression={"$toDecimal": DECIMAL128_MANY_TRAILING_ZEROS},
expected=DECIMAL128_MANY_TRAILING_ZEROS,
),
ExpressionTestCase(
"dec128_exponent_form",
msg="Decimal128 in exponent form (1E+6144) passes through preserving exponent",
expression={"$toDecimal": DECIMAL128_LARGE_EXPONENT},
expected=DECIMAL128_LARGE_EXPONENT,
),
ExpressionTestCase(
"dec128_negative",
msg="Decimal128 negative value passes through unchanged",
expression={"$toDecimal": DECIMAL128_NEGATIVE_ONE_AND_HALF},
expected=DECIMAL128_NEGATIVE_ONE_AND_HALF,
),
ExpressionTestCase(
"dec128_max",
msg="Decimal128 max value passes through unchanged",
expression={"$toDecimal": DECIMAL128_MAX},
expected=DECIMAL128_MAX,
),
ExpressionTestCase(
"dec128_min",
msg="Decimal128 min value passes through unchanged",
expression={"$toDecimal": DECIMAL128_MIN},
expected=DECIMAL128_MIN,
),
ExpressionTestCase(
"dec128_min_positive",
msg="Decimal128 min positive value (1E-6176) passes through unchanged",
expression={"$toDecimal": DECIMAL128_MIN_POSITIVE},
expected=DECIMAL128_MIN_POSITIVE,
),
ExpressionTestCase(
"dec128_max_negative",
msg="Decimal128 max negative value (-1E-6176) passes through unchanged",
expression={"$toDecimal": DECIMAL128_MAX_NEGATIVE},
expected=DECIMAL128_MAX_NEGATIVE,
),
ExpressionTestCase(
"dec128_nan",
msg="Decimal128 NaN passes through unchanged",
expression={"$toDecimal": DECIMAL128_NAN},
expected=DECIMAL128_NAN,
),
ExpressionTestCase(
"dec128_negative_nan",
msg="Decimal128 -NaN passes through preserving sign bit",
expression={"$toDecimal": DECIMAL128_NEGATIVE_NAN},
expected=DECIMAL128_NEGATIVE_NAN,
),
ExpressionTestCase(
"dec128_infinity",
msg="Decimal128 Infinity passes through unchanged",
expression={"$toDecimal": DECIMAL128_INFINITY},
expected=DECIMAL128_INFINITY,
),
ExpressionTestCase(
"dec128_negative_infinity",
msg="Decimal128 -Infinity passes through unchanged",
expression={"$toDecimal": DECIMAL128_NEGATIVE_INFINITY},
expected=DECIMAL128_NEGATIVE_INFINITY,
),
ExpressionTestCase(
"dec128_high_precision",
msg="Decimal128 maximum 34-digit coefficient passes through at full precision",
expression={"$toDecimal": DECIMAL128_MAX_COEFFICIENT},
expected=DECIMAL128_MAX_COEFFICIENT,
),
]


@pytest.mark.parametrize("test", pytest_params(TODECIMAL_DECIMAL128_TESTS))
def test_toDecimal_decimal128(collection, test: ExpressionTestCase):
"""$toDecimal is the identity function for Decimal128 inputs."""
result = execute_expression(collection, test.expression)
assert_expression_result(
result, expected=test.expected, error_code=test.error_code, msg=test.msg
)
Loading
Loading