diff --git a/docs/testing/TEST_COVERAGE.md b/docs/testing/TEST_COVERAGE.md index 95991bfa7..ba3139851 100644 --- a/docs/testing/TEST_COVERAGE.md +++ b/docs/testing/TEST_COVERAGE.md @@ -424,6 +424,7 @@ For each invalid_type in [string, object, array, ...]: - Pass-through stages (`$limit`, `$skip`, `$sort`, `$match`) must preserve all BSON types unchanged, including deprecated types - Reshaping stages (`$project`, `$set`, `$unset`, `$addFields`) must be tested with all BSON types as values - New-document stages (`$count`, `$group`, `$bucket`, `$sortByCount`) must verify output field names and types +- `_id`-synthesizing stages (`$bucket`, `$bucketAuto`) must verify `_id` in `output`: `$bucket` rejects it; `$bucketAuto` lets an `_id` accumulator override the default `{min, max}` boundary **Stage Interactions**: - Multi-stage interaction tests belong in the parent `stages/` directory, not in individual stage folders. Per `FOLDER_STRUCTURE.md`, interactions between same-level features go in the parent folder (e.g., `stages/test_stages_combination_sort.py`, `stages/test_stages_position_match.py`). diff --git a/documentdb_tests/compatibility/tests/core/operator/stages/bucketAuto/test_bucketAuto_output.py b/documentdb_tests/compatibility/tests/core/operator/stages/bucketAuto/test_bucketAuto_output.py index f217e0545..772061cfb 100644 --- a/documentdb_tests/compatibility/tests/core/operator/stages/bucketAuto/test_bucketAuto_output.py +++ b/documentdb_tests/compatibility/tests/core/operator/stages/bucketAuto/test_bucketAuto_output.py @@ -225,6 +225,57 @@ ), ] +# Property [_id Output Override]: unlike $bucket (which reserves _id), $bucketAuto +# permits _id in the output specification; an _id accumulator replaces the default +# {min, max} boundary document with the accumulated value. +BUCKET_AUTO_ID_OVERRIDE_TESTS: list[StageTestCase] = [ + StageTestCase( + "id_override_replaces_boundary", + docs=[{"_id": 1, "x": 1, "v": 10}, {"_id": 2, "x": 5, "v": 20}], + pipeline=[ + { + "$bucketAuto": { + "groupBy": "$x", + "buckets": 1, + "output": {"_id": {"$sum": "$v"}}, + } + } + ], + expected=[{"_id": 30}], + msg="$bucketAuto output _id accumulator should replace the default {min, max} boundary _id", + ), + StageTestCase( + "id_override_with_other_output_fields", + docs=[{"_id": 1, "x": 1, "v": 10}, {"_id": 2, "x": 5, "v": 20}], + pipeline=[ + { + "$bucketAuto": { + "groupBy": "$x", + "buckets": 1, + "output": {"_id": {"$max": "$v"}, "count": {"$sum": 1}}, + } + } + ], + expected=[{"_id": 20, "count": 2}], + msg="$bucketAuto output _id override should coexist with other output fields", + ), + StageTestCase( + "id_override_applies_per_bucket", + docs=[{"_id": 1, "x": 1, "v": 10}, {"_id": 2, "x": 5, "v": 20}], + pipeline=[ + { + "$bucketAuto": { + "groupBy": "$x", + "buckets": 2, + "output": {"_id": {"$sum": "$v"}}, + } + } + ], + expected=[{"_id": 10}, {"_id": 20}], + msg="$bucketAuto output _id override should be computed independently per bucket", + ), +] + # Property [Duplicate Output Field Names]: duplicate output field names resolve # to the last definition. BUCKET_AUTO_DUPLICATE_FIELD_NAME_TESTS: list[StageTestCase] = [ @@ -257,6 +308,7 @@ + BUCKET_AUTO_NESTED_EXPR_TESTS + BUCKET_AUTO_PUSH_SYSTEM_VAR_TESTS + BUCKET_AUTO_OUTPUT_FIELD_NAME_TESTS + + BUCKET_AUTO_ID_OVERRIDE_TESTS + BUCKET_AUTO_DUPLICATE_FIELD_NAME_TESTS ) diff --git a/documentdb_tests/compatibility/tests/core/operator/stages/bucketAuto/test_bucketAuto_output_errors.py b/documentdb_tests/compatibility/tests/core/operator/stages/bucketAuto/test_bucketAuto_output_errors.py index 54f52da31..1e1d7d313 100644 --- a/documentdb_tests/compatibility/tests/core/operator/stages/bucketAuto/test_bucketAuto_output_errors.py +++ b/documentdb_tests/compatibility/tests/core/operator/stages/bucketAuto/test_bucketAuto_output_errors.py @@ -198,11 +198,26 @@ def _out(value): ), ] +# Property [Output Field _id Must Be Accumulator]: unlike $bucket, which reserves +# _id and rejects it in output entirely, $bucketAuto permits _id in output but +# still requires it to be an accumulator object; a non-accumulator _id is rejected +# with the same error as any other non-accumulator output field. +BUCKET_AUTO_OUTPUT_ID_NOT_ACCUMULATOR_TESTS: list[StageTestCase] = [ + StageTestCase( + "output_id_non_accumulator_int", + docs=[{"_id": 1}], + pipeline=_out({"_id": 5}), + error_code=BUCKET_OUTPUT_NOT_ACCUMULATOR_ERROR, + msg="$bucketAuto should reject a non-accumulator _id value in output", + ), +] + BUCKET_AUTO_OUTPUT_ERROR_TESTS = ( BUCKET_AUTO_OUTPUT_TYPE_TESTS + BUCKET_AUTO_OUTPUT_DOLLAR_PREFIX_TESTS + BUCKET_AUTO_OUTPUT_DOT_TESTS + BUCKET_AUTO_OUTPUT_NOT_ACCUMULATOR_TESTS + + BUCKET_AUTO_OUTPUT_ID_NOT_ACCUMULATOR_TESTS )