test_tool_request_models.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. from __future__ import annotations
  2. import pytest
  3. from pydantic import ValidationError
  4. from agency_swarm.integrations.fastapi_utils.tool_request_models import build_request_model
  5. def test_build_request_model_returns_none_for_empty_and_polymorphic_schema() -> None:
  6. assert build_request_model({}, "Empty") is None
  7. assert build_request_model({"type": "object", "properties": {}}, "NoProps") is None
  8. polymorphic = {
  9. "type": "object",
  10. "properties": {
  11. "choice": {
  12. "oneOf": [
  13. {"type": "string"},
  14. {"type": "integer"},
  15. ]
  16. }
  17. },
  18. }
  19. assert build_request_model(polymorphic, "Poly") is None
  20. def test_build_request_model_supports_defs_and_strict_forbid_extra() -> None:
  21. schema = {
  22. "type": "object",
  23. "properties": {
  24. "config": {"$ref": "#/$defs/Config"},
  25. },
  26. "required": ["config"],
  27. "$defs": {
  28. "Config": {
  29. "type": "object",
  30. "properties": {
  31. "enabled": {"type": "boolean"},
  32. },
  33. "required": ["enabled"],
  34. }
  35. },
  36. }
  37. request_model = build_request_model(schema, "ConfigTool", strict=True)
  38. assert request_model is not None
  39. parsed = request_model.model_validate({"config": {"enabled": True}})
  40. assert parsed.config.enabled is True
  41. with pytest.raises(ValidationError):
  42. request_model.model_validate({"config": {"enabled": True, "extra": 1}})
  43. def test_build_request_model_handles_array_object_null_and_untyped_fields() -> None:
  44. schema = {
  45. "type": "object",
  46. "properties": {
  47. "arr": {"type": "array"},
  48. "obj": {"type": "object"},
  49. "maybe_null": {"type": "null"},
  50. "mystery": {"type": "custom"},
  51. "untyped": {},
  52. },
  53. "required": ["arr", "obj", "maybe_null", "mystery", "untyped"],
  54. }
  55. request_model = build_request_model(schema, "Mixed")
  56. assert request_model is not None
  57. parsed = request_model.model_validate(
  58. {
  59. "arr": [1, "two"],
  60. "obj": {"anything": "goes"},
  61. "maybe_null": None,
  62. "mystery": "value",
  63. "untyped": "text",
  64. }
  65. )
  66. assert parsed.arr == [1, "two"]
  67. assert parsed.obj == {"anything": "goes"}
  68. assert parsed.maybe_null is None
  69. assert parsed.mystery == "value"
  70. assert parsed.untyped == "text"
  71. def test_build_request_model_rejects_invalid_refs() -> None:
  72. unsupported_ref = {
  73. "type": "object",
  74. "properties": {
  75. "cfg": {"$ref": "#/components/schemas/Config"},
  76. },
  77. }
  78. assert build_request_model(unsupported_ref, "BadRef") is None
  79. missing_def = {
  80. "type": "object",
  81. "properties": {
  82. "cfg": {"$ref": "#/$defs/Config"},
  83. },
  84. "$defs": {},
  85. }
  86. assert build_request_model(missing_def, "MissingDef") is None
  87. def test_build_request_model_respects_additional_properties_over_strict_flag() -> None:
  88. allow_extra_schema = {
  89. "type": "object",
  90. "additionalProperties": True,
  91. "properties": {"name": {"type": "string"}},
  92. "required": ["name"],
  93. }
  94. allow_model = build_request_model(allow_extra_schema, "AllowExtra", strict=True)
  95. assert allow_model is not None
  96. allow_model.model_validate({"name": "ok", "extra": "ignored"})
  97. forbid_extra_schema = {
  98. "type": "object",
  99. "additionalProperties": False,
  100. "properties": {"name": {"type": "string"}},
  101. "required": ["name"],
  102. }
  103. forbid_model = build_request_model(forbid_extra_schema, "ForbidExtra", strict=False)
  104. assert forbid_model is not None
  105. with pytest.raises(ValidationError):
  106. forbid_model.model_validate({"name": "ok", "extra": "blocked"})