-
Notifications
You must be signed in to change notification settings - Fork 37
Add $toInt, $toDouble, $toLong, $toDecimal tests #684
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
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 b73ce4a
Merge branch 'main' of https://github.com/PatersonProjects/functional…
PatersonProjects ec9e7fc
Merge branch 'main' of https://github.com/PatersonProjects/functional…
PatersonProjects 1c87758
Merge branch 'main' of https://github.com/PatersonProjects/functional…
PatersonProjects df20a26
Base toDouble Tests
PatersonProjects 8115270
Started parametrization edits
PatersonProjects b92395a
toDouble migrated
PatersonProjects b2fdff0
Migrated toInt tests
PatersonProjects fa51cef
Migrated toDecimal tests, added lazy to large string tests and replac…
PatersonProjects 8330dd3
migrate: add operator tests to documentdb_tests
PatersonProjects 6599361
migrate: step 1.5 $toLong — refactor rejection tests to bson_type_val…
PatersonProjects 08f1d07
migrate: step 3 $toLong — fill two coverage gaps from old suite
PatersonProjects 565fefa
migrate: step 3.5 $toLong — replace inline literals with named constants
PatersonProjects 01f4ca7
migrate: step 4 review fixups — promote computed literals to named co…
PatersonProjects 810ec74
revert: remove DOUBLE_BELOW_INT64_MIN from test_constants
PatersonProjects 4acdef8
Parametrized standalone test cases
PatersonProjects 5bb1e3a
Addressed PR Comments
PatersonProjects b517ca2
Merge branch 'main' into toNumber_tests
eerxuan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Empty file.
80 changes: 80 additions & 0 deletions
80
...ests/compatibility/tests/core/operator/expressions/type/toDecimal/test_toDecimal_arity.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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): | ||
| """$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 | ||
| ) | ||
169 changes: 169 additions & 0 deletions
169
...s/compatibility/tests/core/operator/expressions/type/toDecimal/test_toDecimal_datetime.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| ) |
140 changes: 140 additions & 0 deletions
140
...compatibility/tests/core/operator/expressions/type/toDecimal/test_toDecimal_decimal128.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| ) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.