dotb_ai-platform/tests/test_rule_validation.py
2026-09-25 09:55:25 +07:00

92 lines
3.6 KiB
Python

from copy import deepcopy
import pytest
from pydantic import ValidationError
from ai_platform.presentation.http.schemas import RunRequest
@pytest.mark.parametrize(
("field", "operator", "value"),
[
("custom_fields.interest.region", "eq", "north"),
("lead_profile.email", "exists", None),
("lead_profile.custom_fields.course", "contains", "IELTS"),
("metrics.profile_field_count", "gte", 1),
("metrics.conversation_count", "gte", 1),
("metrics.conversation_turn_count", "gte", 3),
("metrics.contact_started_conversation_count", "eq", 1),
("metrics.contact_message_recency_hours", "gte", 1.5),
("metrics.customer_initiated_return", "eq", True),
("metrics.customer_initiated_return", "ne", False),
("metrics.customer_message_count", "exists", None),
],
)
def test_rules_accept_supported_customer_field_paths(valid_run_payload, field, operator, value):
data = deepcopy(valid_run_payload)
data["scoring_config"]["rules"][0].update(field=field, operator=operator, value=value)
request = RunRequest.model_validate(data)
assert request.scoring_config.rules[0].field == field
@pytest.mark.parametrize(
("field", "operator", "value", "points"),
[
("snapshot.conversations", "exists", None, 1),
("custom_fields..region", "eq", "north", 1),
("metrics.unknown_metric", "eq", 1, 1),
("metrics.conversation_turn_count", "eq", 1.0, 1),
("metrics.contact_started_conversation_count", "contains", 1, 1),
("metrics.customer_message_count", "gt", True, 1),
("metrics.contact_message_recency_days", "contains", 1, 1),
("metrics.contact_message_recency_hours", "contains", 1, 1),
("metrics.customer_initiated_return", "eq", 1, 1),
("metrics.customer_initiated_return", "ne", 0, 1),
("metrics.customer_initiated_return", "gt", True, 1),
("metrics.customer_initiated_return", "contains", True, 1),
("metrics.customer_initiated_return", "exists", None, 1),
("custom_fields.age", "gte", "18", 1),
("custom_fields.age", "gt", True, 1),
("custom_fields.age", "eq", None, 1),
("custom_fields.age", "exists", "not-null", 1),
("custom_fields.region", "eq", {"value": "north"}, 1),
("custom_fields.tags", "contains", ["nested"], 1),
("custom_fields.age", "eq", 18, True),
("custom_fields.age", "eq", 18, 1.5),
],
)
def test_rules_reject_invalid_paths_values_and_points(
valid_run_payload, field, operator, value, points
):
data = deepcopy(valid_run_payload)
data["scoring_config"]["rules"][0].update(
field=field, operator=operator, value=value, points=points
)
with pytest.raises(ValidationError):
RunRequest.model_validate(data)
def test_rule_values_enforce_size_limits(valid_run_payload):
data = deepcopy(valid_run_payload)
data["scoring_config"]["rules"][0]["value"] = "x" * 1_001
with pytest.raises(ValidationError, match="1000 characters"):
RunRequest.model_validate(data)
data["scoring_config"]["rules"][0].update(operator="contains", value=["x"] * 101)
with pytest.raises(ValidationError, match="100 items"):
RunRequest.model_validate(data)
def test_scoring_weights_reject_boolean_and_string_types(valid_run_payload):
for rule_weight, ai_weight in ((True, 0), (0.5, "0.5")):
data = deepcopy(valid_run_payload)
data["scoring_config"]["rule_weight"] = rule_weight
data["scoring_config"]["ai_weight"] = ai_weight
with pytest.raises(ValidationError):
RunRequest.model_validate(data)