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,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,
)
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,
)
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,
)
Loading
Loading