hack-house/.venv/lib/python3.12/site-packages/tests/extra/test_validation_dataclass.py
leetcrypt bb1d662ee1 chore: rename project coven → hack-house ⛧
Rebrand the Rust client crate (coven/ → hh/, package+binary "hack-house"),
README, CLI strings, and branch (coven → hack-house). Gitea repo renamed
cmd-chat → hack-house to match. Crypto/server logic unchanged; selftest +
golden-vector test still green, binary is now `hack-house`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 13:29:14 -07:00

411 lines
16 KiB
Python

import sys
from dataclasses import dataclass
from typing import Optional
import pytest
from sanic import json
from sanic.views import HTTPMethodView
from sanic_ext import validate
from sanic_ext.extras.validation.check import check_data
from sanic_ext.extras.validation.schema import make_schema, parse_hint
from . import __models__ as models
SNOOPY_DATA = {"name": "Snoopy", "alter_ego": ["Flying Ace", "Joe Cool"]}
def test_schema():
@dataclass
class Pet:
name: str
@dataclass
class Person:
name: str
age: int
pets: Optional[list[Pet]]
schema = make_schema({}, Person)
assert "Person" in schema
assert schema["Person"]["hints"]["name"] == parse_hint(str)
assert schema["Person"]["hints"]["age"] == parse_hint(int)
assert schema["Person"]["hints"]["pets"] == parse_hint(Optional[list[Pet]])
assert "Pet" in schema
assert schema["Pet"]["hints"]["name"] == parse_hint(str)
def test_should_hydrate():
@dataclass
class Pet:
name: str
@dataclass
class Person:
name: str
age: int
pets: list[Pet]
data = {"name": "Charlie Brown", "age": 8, "pets": [{"name": "Snoopy"}]}
schema = make_schema({}, Person)
cb = check_data(Person, data, schema)
assert cb.name == "Charlie Brown"
assert cb.age == 8
assert cb.pets[0].name == "Snoopy"
@pytest.mark.parametrize(
"data",
(
{"name": "Charlie Brown", "age": 8, "pets": {"name": "Snoopy"}},
{"name": "Charlie Brown", "age": 8, "pets": [{"name": 123}]},
{"name": "Charlie Brown", "age": 8, "pets": [123]},
{"name": "Charlie Brown", "age": 8, "pets": 123},
{"name": "Charlie Brown", "age": "8", "pets": {"name": "Snoopy"}},
{"name": True, "age": 8, "pets": {"name": "Snoopy"}},
),
)
def test_should_not_hydrate(data):
@dataclass
class Pet:
name: str
@dataclass
class Person:
name: str
age: int
pets: list[Pet]
schema = make_schema({}, Person)
with pytest.raises(TypeError):
check_data(Person, data, schema)
@pytest.mark.parametrize(
"model,okay,data",
(
(models.ModelStr, True, {"foo": "bar"}),
(models.ModelStr, False, {"foo": 1}),
(models.ModelStr, False, {"foo": True}),
(models.ModelStr, False, {"foo": ["bar"]}),
(models.ModelStr, False, {"bar": "bar"}),
(models.ModelStr, False, {"foo": None}),
(models.ModelStr, False, 123),
(models.ModelInt, True, {"foo": 1}),
(models.ModelInt, True, {"foo": True}),
(models.ModelInt, False, {"foo": "1"}),
(models.ModelInt, False, {"foo": 1.1}),
(models.ModelInt, False, {"foo": None}),
(models.ModelFloat, True, {"foo": 1.1}),
(models.ModelFloat, False, {"foo": 1}),
(models.ModelFloat, False, {"foo": "1.1"}),
(models.ModelFloat, False, {"foo": None}),
(models.ModelBool, True, {"foo": True}),
(models.ModelBool, True, {"foo": False}),
(models.ModelBool, False, {"foo": 1}),
(models.ModelBool, False, {"foo": 0}),
(models.ModelBool, False, {"foo": 2}),
(models.ModelBool, False, {"foo": "True"}),
(models.ModelBool, False, {"foo": None}),
(models.ModelOptionalStr, True, {"foo": "bar"}),
(models.ModelOptionalStr, True, {"foo": None}),
(models.ModelOptionalStr, False, {"foo": 0}),
(models.ModelUnion, True, {"foo": 1}),
(models.ModelUnion, True, {"foo": 1.1}),
(models.ModelUnion, False, {"foo": "1.1"}),
(models.ModelUnion, False, {"foo": None}),
(models.ModelUnionModels, True, {"foo": {"foo": 1}}),
(models.ModelUnionModels, True, {"foo": {"foo": 1.1}}),
(models.ModelUnionModels, False, {"foo": {"foo": "1.1"}}),
(models.ModelUnionModels, False, {"foo": 1}),
(models.ModelUnionModels, False, {"foo": 1.1}),
(models.ModelUnionModels, False, {"foo": None}),
(models.ModelUnionStrInt, True, {"foo": "1"}),
(models.ModelUnionStrInt, True, {"foo": "1q"}),
(models.ModelUnionStrInt, True, {"foo": 1}),
(models.ModelUnionStrInt, False, {"foo": 1.1}),
(models.ModelUnionStrInt, False, {"foo": None}),
(models.ModelUnionIntStr, True, {"foo": "1"}),
(models.ModelUnionIntStr, True, {"foo": "1q"}),
(models.ModelUnionIntStr, True, {"foo": 1}),
(models.ModelUnionIntStr, False, {"foo": 1.1}),
(models.ModelUnionIntStr, False, {"foo": None}),
(models.ModelOptionalUnionStrInt, True, {"foo": "1"}),
(models.ModelOptionalUnionStrInt, True, {"foo": "1q"}),
(models.ModelOptionalUnionStrInt, True, {"foo": 1}),
(models.ModelOptionalUnionStrInt, False, {"foo": 1.1}),
(models.ModelOptionalUnionStrInt, True, {"foo": None}),
(models.ModelOptionalUnionIntStr, True, {"foo": "1"}),
(models.ModelOptionalUnionIntStr, True, {"foo": "1q"}),
(models.ModelOptionalUnionIntStr, True, {"foo": 1}),
(models.ModelOptionalUnionIntStr, False, {"foo": 1.1}),
(models.ModelOptionalUnionIntStr, True, {"foo": None}),
(models.ModelListStr, True, {"foo": ["bar"]}),
(models.ModelListStr, True, {"foo": ["one", "two"]}),
(models.ModelListStr, False, {"foo": "bar"}),
(models.ModelListStr, False, {"foo": ["one", 2]}),
(models.ModelListStr, False, {"foo": ["one", None]}),
(models.ModelListStr, False, {"foo": None}),
(models.ModelListModel, True, {"foo": [{"foo": "bar"}]}),
(
models.ModelListModel,
True,
{"foo": [{"foo": "one"}, {"foo": "two"}]},
),
(models.ModelListModel, False, {"foo": {"foo": "bar"}}),
(models.ModelListModel, False, {"foo": [{"foo": "bar"}, 2]}),
(models.ModelListModel, False, {"foo": [{"foo": "bar"}, None]}),
(models.ModelListModel, False, {"foo": None}),
(models.ModelOptionalList, True, {"foo": None}),
(models.ModelOptionalList, True, {"foo": ["bar"]}),
(models.ModelOptionalList, False, {"foo": [1]}),
(models.ModelOptionalList, False, {"foo": [None]}),
(models.ModelListUnion, True, {"foo": [1]}),
(models.ModelListUnion, True, {"foo": [1.1]}),
(models.ModelListUnion, True, {"foo": [1, 1.1]}),
(models.ModelListUnion, False, {"foo": [1, 1.1, "one"]}),
(models.ModelListUnion, False, {"foo": [1, 1.1, None]}),
(models.ModelListUnion, False, {"foo": 1}),
(models.ModelListUnion, False, {"foo": 1.1}),
(models.ModelListUnion, False, {"foo": None}),
(models.ModelOptionalListUnion, True, {"foo": [1]}),
(models.ModelOptionalListUnion, True, {"foo": [1.1]}),
(models.ModelOptionalListUnion, True, {"foo": [1, 1.1]}),
(models.ModelOptionalListUnion, True, {"foo": None}),
(models.ModelOptionalListUnion, False, {"foo": [1, 1.1, "one"]}),
(models.ModelOptionalListUnion, False, {"foo": [1, 1.1, None]}),
(models.ModelOptionalListUnion, False, {"foo": 1}),
(models.ModelOptionalListUnion, False, {"foo": 1.1}),
(models.ModelModel, True, {"foo": {"foo": "one"}}),
(models.ModelModel, False, {"foo": {"foo": 1}}),
(models.ModelModel, False, {"foo": {"foo": None}}),
(models.ModelModel, False, {"foo": "one"}),
(models.ModelModel, False, {"foo": None}),
(models.ModelOptionalModel, True, {"foo": {"foo": "one"}}),
(models.ModelOptionalModel, True, {"foo": None}),
(models.ModelOptionalModel, False, {"foo": {"foo": 1}}),
(models.ModelOptionalModel, False, {"foo": {"foo": None}}),
(models.ModelOptionalModel, False, {"foo": "one"}),
(models.ModelDictStr, True, {"foo": {"foo": "one"}}),
(models.ModelDictStr, False, {"foo": {"foo": 1}}),
(models.ModelDictStr, False, {"foo": {"foo": None}}),
(models.ModelDictStr, False, {"foo": "one"}),
(models.ModelDictStr, False, {"foo": None}),
(models.ModelDictModel, True, {"foo": {"foo": {"foo": "one"}}}),
(models.ModelDictModel, False, {"foo": {"foo": {"foo": 1}}}),
(models.ModelDictModel, False, {"foo": {"foo": 1}}),
(models.ModelDictModel, False, {"foo": {"foo": None}}),
(models.ModelDictModel, False, {"foo": "one"}),
(models.ModelDictModel, False, {"foo": None}),
(models.ModelOptionalDict, True, {"foo": {"foo": "one"}}),
(models.ModelOptionalDict, True, {"foo": None}),
(models.ModelOptionalDict, False, {"foo": {"foo": 1}}),
(models.ModelOptionalDict, False, {"foo": {"foo": None}}),
(models.ModelOptionalDict, False, {"foo": "one"}),
(models.ModelDictUnion, True, {"foo": {"foo": 1}}),
(models.ModelDictUnion, True, {"foo": {"foo": 1.1}}),
(models.ModelDictUnion, False, {"foo": {"foo": "one"}}),
(models.ModelDictUnion, False, {"foo": {"foo": None}}),
(models.ModelDictUnion, False, {"foo": "one"}),
(models.ModelDictUnion, False, {"foo": 1}),
(models.ModelDictUnion, False, {"foo": 1.1}),
(models.ModelDictUnion, False, {"foo": None}),
(models.ModelOptionalDictUnion, True, {"foo": {"foo": 1}}),
(models.ModelOptionalDictUnion, True, {"foo": {"foo": 1.1}}),
(models.ModelOptionalDictUnion, True, {"foo": None}),
(models.ModelOptionalDictUnion, False, {"foo": {"foo": "one"}}),
(models.ModelOptionalDictUnion, False, {"foo": {"foo": None}}),
(models.ModelOptionalDictUnion, False, {"foo": "one"}),
(models.ModelOptionalDictUnion, False, {"foo": 1}),
(models.ModelOptionalDictUnion, False, {"foo": 1.1}),
(models.ModelSingleLiteral, True, {"foo": True}),
(models.ModelSingleLiteral, False, {"foo": False}),
(models.ModelSingleLiteral, False, {"foo": "True"}),
(models.ModelSingleLiteral, False, {"foo": None}),
(models.ModelOptionalSingleLiteral, True, {"foo": True}),
(models.ModelOptionalSingleLiteral, True, {"foo": None}),
(models.ModelOptionalSingleLiteral, False, {"foo": False}),
(models.ModelOptionalSingleLiteral, False, {"foo": "True"}),
(models.ModelOptionalMultipleLiteral, True, {"foo": True}),
(models.ModelOptionalMultipleLiteral, True, {"foo": 1}),
(models.ModelOptionalMultipleLiteral, True, {"foo": "y"}),
(models.ModelOptionalMultipleLiteral, True, {"foo": "Y"}),
(models.ModelOptionalMultipleLiteral, True, {"foo": None}),
(models.ModelOptionalMultipleLiteral, False, {"foo": "n"}),
(models.ModelOptionalMultipleLiteral, False, {"foo": False}),
(models.ModelListStrWithDefaultFactory, True, {}),
(models.ModelListStrWithDefaultFactory, True, {"foo": ["bar"]}),
(models.ModelListStrWithDefaultFactory, True, {"foo": []}),
(models.ModelListStrWithDefaultFactory, False, {"foo": [1]}),
(models.ModelListStrWithDefaultFactory, False, {"foo": None}),
),
)
def test_modeling(model, okay, data):
schema = make_schema({}, model)
if okay:
check_data(model, data, schema)
else:
with pytest.raises(TypeError):
check_data(model, data, schema)
@pytest.mark.skipif(
sys.version_info < (3, 10), reason="UnionType added in 3.10"
)
def test_modeling_union_type_ModelUnionTypeStrNone():
schema = make_schema({}, models.ModelUnionTypeStrNone)
check_data(models.ModelUnionTypeStrNone, {"foo": "bar"}, schema)
check_data(models.ModelUnionTypeStrNone, {"foo": None}, schema)
with pytest.raises(TypeError):
check_data(models.ModelUnionTypeStrNone, {"foo": 0}, schema)
@pytest.mark.skipif(
sys.version_info < (3, 10), reason="UnionType added in 3.10"
)
def test_modeling_union_type_ModelUnionTypeStrIntNone():
schema = make_schema({}, models.ModelUnionTypeStrIntNone)
check_data(models.ModelUnionTypeStrIntNone, {"foo": "1"}, schema)
check_data(models.ModelUnionTypeStrIntNone, {"foo": "bar"}, schema)
check_data(models.ModelUnionTypeStrIntNone, {"foo": None}, schema)
check_data(models.ModelUnionTypeStrIntNone, {"foo": 1}, schema)
check_data(models.ModelUnionTypeStrIntNone, {"foo": 0}, schema)
with pytest.raises(TypeError):
check_data(models.ModelUnionTypeStrIntNone, {"foo": 1.1}, schema)
@pytest.mark.skipif(
sys.version_info < (3, 10), reason="UnionType added in 3.10"
)
def test_modeling_union_type_ModelUnionTypeStrInt():
schema = make_schema({}, models.ModelUnionTypeStrInt)
check_data(models.ModelUnionTypeStrInt, {"foo": "1"}, schema)
check_data(models.ModelUnionTypeStrInt, {"foo": "bar"}, schema)
check_data(models.ModelUnionTypeStrInt, {"foo": 1}, schema)
check_data(models.ModelUnionTypeStrInt, {"foo": 0}, schema)
with pytest.raises(TypeError):
check_data(models.ModelUnionTypeStrInt, {"foo": None}, schema)
with pytest.raises(TypeError):
check_data(models.ModelUnionTypeStrInt, {"foo": 1.1}, schema)
def test_validate_json(app):
@dataclass
class Pet:
name: str
alter_ego: list[str]
@app.post("/function")
@validate(json=Pet)
async def handler(_, body: Pet):
return json(
{
"is_pet": isinstance(body, Pet),
"pet": {"name": body.name, "alter_ego": body.alter_ego},
}
)
class MethodView(HTTPMethodView, attach=app, uri="/method"):
decorators = [validate(json=Pet)]
async def post(self, _, body: Pet):
return json(
{
"is_pet": isinstance(body, Pet),
"pet": {"name": body.name, "alter_ego": body.alter_ego},
}
)
_, response = app.test_client.post("/function", json=SNOOPY_DATA)
assert response.status == 200
assert response.json["is_pet"]
assert response.json["pet"] == SNOOPY_DATA
_, response = app.test_client.post("/method", json=SNOOPY_DATA)
assert response.status == 200
assert response.json["is_pet"]
assert response.json["pet"] == SNOOPY_DATA
def test_validate_form(app):
@dataclass
class Pet:
name: str
alter_ego: list[str]
description: Optional[str] = None
@app.post("/function")
@validate(form=Pet)
async def handler(_, body: Pet):
return json(
{
"is_pet": isinstance(body, Pet),
"pet": {"name": body.name, "alter_ego": body.alter_ego},
"description": body.description if body.description else "",
}
)
class MethodView(HTTPMethodView, attach=app, uri="/method"):
decorators = [validate(form=Pet)]
async def post(self, _, body: Pet):
return json(
{
"is_pet": isinstance(body, Pet),
"pet": {"name": body.name, "alter_ego": body.alter_ego},
"description": body.description
if body.description
else "",
}
)
_, response = app.test_client.post("/function", data=SNOOPY_DATA)
assert response.status == 200
assert response.json["is_pet"]
assert response.json["pet"] == SNOOPY_DATA
assert response.json["description"] == ""
_, response = app.test_client.post("/method", data=SNOOPY_DATA)
assert response.status == 200
assert response.json["is_pet"]
assert response.json["pet"] == SNOOPY_DATA
assert response.json["description"] == ""
def test_validate_query(app):
@dataclass
class Search:
q: str
@app.get("/function")
@validate(query=Search)
async def handler(_, query: Search):
return json({"q": query.q, "is_search": isinstance(query, Search)})
class MethodView(HTTPMethodView, attach=app, uri="/method"):
decorators = [validate(query=Search)]
async def get(self, _, query: Search):
return json({"q": query.q, "is_search": isinstance(query, Search)})
_, response = app.test_client.get("/function", params={"q": "Snoopy"})
assert response.status == 200
assert response.json["is_search"]
assert response.json["q"] == "Snoopy"
_, response = app.test_client.get("/method", params={"q": "Snoopy"})
assert response.status == 200
assert response.json["is_search"]
assert response.json["q"] == "Snoopy"