-
Notifications
You must be signed in to change notification settings - Fork 34
Add $facet tests #669
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
Open
PatersonProjects
wants to merge
8
commits into
documentdb:main
Choose a base branch
from
PatersonProjects:facet_tests
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Add $facet tests #669
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
51a482a
Added tests
PatersonProjects 014f2bc
Added docStrings to all files
PatersonProjects 37407ae
Revert "Added docStrings to all files"
PatersonProjects f851712
Revert "Added tests"
PatersonProjects b03fb59
Merge branch 'main' of https://github.com/PatersonProjects/functional…
PatersonProjects b873d7e
Merge branch 'main' of https://github.com/PatersonProjects/functional…
PatersonProjects ac4f271
Added facet tests
PatersonProjects 9dff214
Removed unneeded docstring note
PatersonProjects 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.
75 changes: 75 additions & 0 deletions
75
...db_tests/compatibility/tests/core/operator/stages/facet/test_facet_argument_validation.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,75 @@ | ||
| """Aggregation $facet stage tests - valid argument edge cases. | ||
|
|
||
| Covers the positive TEST_COVERAGE.md §4 (Argument Handling) cases for $facet: | ||
| valid edge cases such as empty sub-pipelines, many sub-pipelines, and | ||
| unusual-but-valid output field names. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import Any | ||
|
|
||
| import pytest | ||
|
|
||
| from documentdb_tests.compatibility.tests.core.operator.stages.utils.stage_test_case import ( | ||
| StageTestCase, | ||
| populate_collection, | ||
| ) | ||
| from documentdb_tests.framework.assertions import assertResult | ||
| from documentdb_tests.framework.executor import execute_command | ||
| from documentdb_tests.framework.parametrize import pytest_params | ||
|
|
||
| DOCS = [{"_id": 1, "cat": "A"}, {"_id": 2, "cat": "B"}] | ||
|
|
||
| LONG_NAME = "f" * 300 | ||
|
|
||
| # Property [Valid Edge Cases]: empty sub-pipelines, many sub-pipelines, and | ||
| # unusual-but-valid output field names are accepted. | ||
| FACET_ARGUMENT_SUCCESS_TESTS: list[StageTestCase] = [ | ||
| StageTestCase( | ||
| id="many_subpipelines", | ||
| docs=DOCS, | ||
| pipeline=[{"$facet": {f"f{i}": [{"$count": "n"}] for i in range(50)}}], | ||
| expected=[{f"f{i}": [{"n": 2}] for i in range(50)}], | ||
| msg="A $facet with many valid sub-pipelines should complete without error", | ||
| ), | ||
| StageTestCase( | ||
| id="very_long_field_name", | ||
| docs=DOCS, | ||
| pipeline=[{"$facet": {LONG_NAME: [{"$count": "n"}]}}], | ||
| expected=[{LONG_NAME: [{"n": 2}]}], | ||
| msg="$facet should accept and preserve a very long output field name", | ||
| ), | ||
| StageTestCase( | ||
| id="unicode_field_name", | ||
| docs=DOCS, | ||
| pipeline=[{"$facet": {"日本語": [{"$count": "n"}]}}], | ||
| expected=[{"日本語": [{"n": 2}]}], | ||
| msg="$facet should accept and correctly retrieve a unicode output field name", | ||
| ), | ||
| ] | ||
|
|
||
| # Combined list for parametrization. | ||
| FACET_ARGUMENT_TESTS = FACET_ARGUMENT_SUCCESS_TESTS | ||
|
|
||
|
|
||
| @pytest.mark.aggregate | ||
| @pytest.mark.parametrize("test_case", pytest_params(FACET_ARGUMENT_TESTS)) | ||
| def test_facet_argument_validation(collection, test_case: StageTestCase): | ||
| """Test $facet valid argument edge cases.""" | ||
| coll = populate_collection(collection, test_case) | ||
| command: dict[str, Any] = { | ||
| "aggregate": coll.name, | ||
| "pipeline": test_case.pipeline, | ||
| "cursor": {}, | ||
| } | ||
| command.update(test_case.extra_command_fields) | ||
| result = execute_command(coll, command) | ||
| assertResult( | ||
| result, | ||
| expected=test_case.expected, | ||
| error_code=test_case.error_code, | ||
| msg=test_case.msg, | ||
| ignore_doc_order=test_case.ignore_doc_order, | ||
| ignore_order_in=test_case.ignore_order_in, | ||
| ) | ||
168 changes: 168 additions & 0 deletions
168
...entdb_tests/compatibility/tests/core/operator/stages/facet/test_facet_bson_passthrough.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,168 @@ | ||
| """Aggregation $facet stage tests - BSON type pass-through. | ||
|
|
||
| Verifies that $facet passes documents of every standard BSON type through to | ||
| sub-pipelines without altering their values (TEST_COVERAGE.md §1 Data Type | ||
| Coverage, applied to the pass-through behaviour of the stage). | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from datetime import datetime, timezone | ||
| from typing import Any | ||
|
|
||
| import pytest | ||
| from bson import Binary, Code, Decimal128, Int64, MaxKey, MinKey, ObjectId, Regex, Timestamp | ||
|
|
||
| from documentdb_tests.compatibility.tests.core.operator.stages.utils.stage_test_case import ( | ||
| StageTestCase, | ||
| populate_collection, | ||
| ) | ||
| from documentdb_tests.framework.assertions import assertResult | ||
| from documentdb_tests.framework.executor import execute_command | ||
| from documentdb_tests.framework.parametrize import pytest_params | ||
|
|
||
| # Property [BSON Pass-Through]: $facet preserves one document of each standard | ||
| # BSON type inside its sub-pipelines. | ||
| FACET_BSON_PASSTHROUGH_TESTS: list[StageTestCase] = [ | ||
| StageTestCase( | ||
| id="int32", | ||
| docs=[{"_id": 1, "val": 42}], | ||
| pipeline=[{"$facet": {"docs": [{"$match": {"_id": 1}}]}}], | ||
| expected=[{"docs": [{"_id": 1, "val": 42}]}], | ||
| msg="$facet should pass an int32 value through to sub-pipelines unchanged", | ||
| ), | ||
| StageTestCase( | ||
| id="int64", | ||
| docs=[{"_id": 1, "val": Int64(42)}], | ||
| pipeline=[{"$facet": {"docs": [{"$match": {"_id": 1}}]}}], | ||
| expected=[{"docs": [{"_id": 1, "val": Int64(42)}]}], | ||
| msg="$facet should pass an int64 value through to sub-pipelines unchanged", | ||
| ), | ||
| StageTestCase( | ||
| id="double", | ||
| docs=[{"_id": 1, "val": 1.5}], | ||
| pipeline=[{"$facet": {"docs": [{"$match": {"_id": 1}}]}}], | ||
| expected=[{"docs": [{"_id": 1, "val": 1.5}]}], | ||
| msg="$facet should pass a double value through to sub-pipelines unchanged", | ||
| ), | ||
| StageTestCase( | ||
| id="decimal128", | ||
| docs=[{"_id": 1, "val": Decimal128("1.5")}], | ||
| pipeline=[{"$facet": {"docs": [{"$match": {"_id": 1}}]}}], | ||
| expected=[{"docs": [{"_id": 1, "val": Decimal128("1.5")}]}], | ||
| msg="$facet should pass a decimal128 value through to sub-pipelines unchanged", | ||
| ), | ||
| StageTestCase( | ||
| id="string", | ||
| docs=[{"_id": 1, "val": "hello"}], | ||
| pipeline=[{"$facet": {"docs": [{"$match": {"_id": 1}}]}}], | ||
| expected=[{"docs": [{"_id": 1, "val": "hello"}]}], | ||
| msg="$facet should pass a string value through to sub-pipelines unchanged", | ||
| ), | ||
| StageTestCase( | ||
| id="bool", | ||
| docs=[{"_id": 1, "val": True}], | ||
| pipeline=[{"$facet": {"docs": [{"$match": {"_id": 1}}]}}], | ||
| expected=[{"docs": [{"_id": 1, "val": True}]}], | ||
| msg="$facet should pass a boolean value through to sub-pipelines unchanged", | ||
| ), | ||
| StageTestCase( | ||
| id="date", | ||
| docs=[{"_id": 1, "val": datetime(2024, 1, 1, tzinfo=timezone.utc)}], | ||
| pipeline=[{"$facet": {"docs": [{"$match": {"_id": 1}}]}}], | ||
| expected=[{"docs": [{"_id": 1, "val": datetime(2024, 1, 1, tzinfo=timezone.utc)}]}], | ||
| msg="$facet should pass a date value through to sub-pipelines unchanged", | ||
| ), | ||
| StageTestCase( | ||
| id="null", | ||
| docs=[{"_id": 1, "val": None}], | ||
| pipeline=[{"$facet": {"docs": [{"$match": {"_id": 1}}]}}], | ||
| expected=[{"docs": [{"_id": 1, "val": None}]}], | ||
| msg="$facet should pass a null value through to sub-pipelines unchanged", | ||
| ), | ||
| StageTestCase( | ||
| id="object", | ||
| docs=[{"_id": 1, "val": {"a": 1, "b": {"c": 2}}}], | ||
| pipeline=[{"$facet": {"docs": [{"$match": {"_id": 1}}]}}], | ||
| expected=[{"docs": [{"_id": 1, "val": {"a": 1, "b": {"c": 2}}}]}], | ||
| msg="$facet should pass an object value through to sub-pipelines unchanged", | ||
| ), | ||
| StageTestCase( | ||
| id="array", | ||
| docs=[{"_id": 1, "val": [1, "two", 3.0]}], | ||
| pipeline=[{"$facet": {"docs": [{"$match": {"_id": 1}}]}}], | ||
| expected=[{"docs": [{"_id": 1, "val": [1, "two", 3.0]}]}], | ||
| msg="$facet should pass an array value through to sub-pipelines unchanged", | ||
| ), | ||
| StageTestCase( | ||
| id="objectId", | ||
| docs=[{"_id": 1, "val": ObjectId("507f1f77bcf86cd799439011")}], | ||
| pipeline=[{"$facet": {"docs": [{"$match": {"_id": 1}}]}}], | ||
| expected=[{"docs": [{"_id": 1, "val": ObjectId("507f1f77bcf86cd799439011")}]}], | ||
| msg="$facet should pass an objectId value through to sub-pipelines unchanged", | ||
| ), | ||
| StageTestCase( | ||
| id="binData", | ||
| docs=[{"_id": 1, "val": Binary(b"payload", 128)}], | ||
| pipeline=[{"$facet": {"docs": [{"$match": {"_id": 1}}]}}], | ||
| expected=[{"docs": [{"_id": 1, "val": Binary(b"payload", 128)}]}], | ||
| msg="$facet should pass a binary value through to sub-pipelines unchanged", | ||
| ), | ||
| StageTestCase( | ||
| id="timestamp", | ||
| docs=[{"_id": 1, "val": Timestamp(123, 4)}], | ||
| pipeline=[{"$facet": {"docs": [{"$match": {"_id": 1}}]}}], | ||
| expected=[{"docs": [{"_id": 1, "val": Timestamp(123, 4)}]}], | ||
| msg="$facet should pass a timestamp value through to sub-pipelines unchanged", | ||
| ), | ||
| StageTestCase( | ||
| id="minKey", | ||
| docs=[{"_id": 1, "val": MinKey()}], | ||
| pipeline=[{"$facet": {"docs": [{"$match": {"_id": 1}}]}}], | ||
| expected=[{"docs": [{"_id": 1, "val": MinKey()}]}], | ||
| msg="$facet should pass a minKey value through to sub-pipelines unchanged", | ||
| ), | ||
| StageTestCase( | ||
| id="maxKey", | ||
| docs=[{"_id": 1, "val": MaxKey()}], | ||
| pipeline=[{"$facet": {"docs": [{"$match": {"_id": 1}}]}}], | ||
| expected=[{"docs": [{"_id": 1, "val": MaxKey()}]}], | ||
| msg="$facet should pass a maxKey value through to sub-pipelines unchanged", | ||
| ), | ||
| StageTestCase( | ||
| id="regex", | ||
| docs=[{"_id": 1, "val": Regex(r"^hello", "i")}], | ||
| pipeline=[{"$facet": {"docs": [{"$match": {"_id": 1}}]}}], | ||
| expected=[{"docs": [{"_id": 1, "val": Regex(r"^hello", "i")}]}], | ||
| msg="$facet should pass a regex value through to sub-pipelines unchanged", | ||
| ), | ||
| StageTestCase( | ||
| id="javascript", | ||
| docs=[{"_id": 1, "val": Code("function() {}")}], | ||
| pipeline=[{"$facet": {"docs": [{"$match": {"_id": 1}}]}}], | ||
| expected=[{"docs": [{"_id": 1, "val": Code("function() {}")}]}], | ||
| msg="$facet should pass a javascript value through to sub-pipelines unchanged", | ||
| ), | ||
| ] | ||
|
|
||
|
|
||
| @pytest.mark.aggregate | ||
| @pytest.mark.parametrize("test_case", pytest_params(FACET_BSON_PASSTHROUGH_TESTS)) | ||
| def test_facet_bson_passthrough(collection, test_case: StageTestCase): | ||
| """$facet passes a document field of each BSON type through unchanged.""" | ||
| coll = populate_collection(collection, test_case) | ||
| command: dict[str, Any] = { | ||
| "aggregate": coll.name, | ||
| "pipeline": test_case.pipeline, | ||
| "cursor": {}, | ||
| } | ||
| command.update(test_case.extra_command_fields) | ||
| result = execute_command(coll, command) | ||
| assertResult( | ||
| result, | ||
| expected=test_case.expected, | ||
| error_code=test_case.error_code, | ||
| msg=test_case.msg, | ||
| ignore_doc_order=test_case.ignore_doc_order, | ||
| ignore_order_in=test_case.ignore_order_in, | ||
| ) |
76 changes: 76 additions & 0 deletions
76
documentdb_tests/compatibility/tests/core/operator/stages/facet/test_facet_collation.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,76 @@ | ||
| """Aggregation $facet stage tests - collation wiring. | ||
|
|
||
| Per TEST_COVERAGE.md §19 (Foundational Spec Behaviors), collation semantics are | ||
| tested comprehensively under tests/core/collation/. These tests only verify | ||
| that $facet correctly wires the command-level collation into its sub-pipelines | ||
| (e.g. a case-insensitive $match and $sortByCount respect the collation). | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import Any | ||
|
|
||
| import pytest | ||
|
|
||
| from documentdb_tests.compatibility.tests.core.operator.stages.utils.stage_test_case import ( | ||
| StageTestCase, | ||
| populate_collection, | ||
| ) | ||
| from documentdb_tests.framework.assertions import assertResult | ||
| from documentdb_tests.framework.executor import execute_command | ||
| from documentdb_tests.framework.parametrize import pytest_params | ||
|
|
||
| CI = {"locale": "en", "strength": 2} # case-insensitive collation | ||
| DOCS = [{"_id": 1, "cat": "a"}, {"_id": 2, "cat": "A"}, {"_id": 3, "cat": "b"}] | ||
|
|
||
| FACET_COLLATION_TESTS: list[StageTestCase] = [ | ||
| StageTestCase( | ||
| id="applies_to_subpipeline_match", | ||
| docs=DOCS, | ||
| pipeline=[{"$facet": {"ci": [{"$match": {"cat": "a"}}, {"$sort": {"_id": 1}}]}}], | ||
| expected=[{"ci": [{"_id": 1, "cat": "a"}, {"_id": 2, "cat": "A"}]}], | ||
| extra_command_fields={"collation": CI}, | ||
| msg="Case-insensitive collation should apply to a $match in a sub-pipeline", | ||
| ), | ||
| StageTestCase( | ||
| id="applies_to_subpipeline_sortByCount", | ||
| docs=DOCS, | ||
| pipeline=[ | ||
| { | ||
| "$facet": { | ||
| "byCat": [ | ||
| {"$sortByCount": "$cat"}, | ||
| {"$project": {"_id": 0, "count": 1}}, | ||
| {"$sort": {"count": -1}}, | ||
| ] | ||
| } | ||
| } | ||
| ], | ||
| expected=[{"byCat": [{"count": 2}, {"count": 1}]}], | ||
| extra_command_fields={"collation": CI}, | ||
| msg="Case-insensitive collation should merge 'a'/'A' in $sortByCount grouping", | ||
| ), | ||
| ] | ||
|
|
||
|
|
||
| @pytest.mark.aggregate | ||
| @pytest.mark.collation | ||
| @pytest.mark.parametrize("test_case", pytest_params(FACET_COLLATION_TESTS)) | ||
| def test_facet_collation(collection, test_case: StageTestCase): | ||
| """A command-level collation applies inside $facet sub-pipelines.""" | ||
| coll = populate_collection(collection, test_case) | ||
| command: dict[str, Any] = { | ||
| "aggregate": coll.name, | ||
| "pipeline": test_case.pipeline, | ||
| "cursor": {}, | ||
| } | ||
| command.update(test_case.extra_command_fields) | ||
| result = execute_command(coll, command) | ||
| assertResult( | ||
| result, | ||
| expected=test_case.expected, | ||
| error_code=test_case.error_code, | ||
| msg=test_case.msg, | ||
| ignore_doc_order=test_case.ignore_doc_order, | ||
| ignore_order_in=test_case.ignore_order_in, | ||
| ) |
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing field name edge cases that should be verified (as either accepted or rejected): spaces (
"my field"), hyphens ("my-field"), underscores ("my_field"), numeric-only ("123"), at-sign ("@field"), null byte ("a\x00b"). These should be tested to document the actual behavior — whether they succeed or error.