feat: complete phase 3 -- OpenAPI auto-discovery, SSRF protection, tool generation
- SSRF protection: private IP blocking, DNS rebinding defense, redirect validation - OpenAPI fetcher with SSRF guard, JSON/YAML auto-detection, 10MB limit - Structural spec validator (3.0.x/3.1.x) - Endpoint parser with $ref resolution, auto-generated operation IDs - Heuristic + LLM endpoint classifier with Protocol interface - Review API at /api/openapi (import, job status, classification CRUD, approve) - @tool code generator + Agent YAML generator - Import orchestrator (fetch -> validate -> parse -> classify pipeline) - 125 new tests, 322 total passing, 93.23% coverage
This commit is contained in:
93
backend/tests/unit/openapi/test_validator.py
Normal file
93
backend/tests/unit/openapi/test_validator.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""Tests for OpenAPI spec validator module.
|
||||
|
||||
RED phase: written before implementation.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
_VALID_SPEC = {
|
||||
"openapi": "3.0.0",
|
||||
"info": {"title": "Test API", "version": "1.0.0"},
|
||||
"paths": {
|
||||
"/items": {
|
||||
"get": {
|
||||
"summary": "List items",
|
||||
"responses": {"200": {"description": "OK"}},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class TestValidateSpec:
|
||||
"""Tests for validate_spec function."""
|
||||
|
||||
def test_valid_minimal_spec_passes(self) -> None:
|
||||
"""A valid minimal spec returns empty error list."""
|
||||
from app.openapi.validator import validate_spec
|
||||
|
||||
errors = validate_spec(_VALID_SPEC)
|
||||
assert errors == []
|
||||
|
||||
def test_missing_openapi_key_returns_error(self) -> None:
|
||||
"""Missing 'openapi' field returns an error."""
|
||||
from app.openapi.validator import validate_spec
|
||||
|
||||
spec = {k: v for k, v in _VALID_SPEC.items() if k != "openapi"}
|
||||
errors = validate_spec(spec)
|
||||
assert len(errors) > 0
|
||||
assert any("openapi" in e.lower() for e in errors)
|
||||
|
||||
def test_missing_info_returns_error(self) -> None:
|
||||
"""Missing 'info' field returns an error."""
|
||||
from app.openapi.validator import validate_spec
|
||||
|
||||
spec = {k: v for k, v in _VALID_SPEC.items() if k != "info"}
|
||||
errors = validate_spec(spec)
|
||||
assert len(errors) > 0
|
||||
assert any("info" in e.lower() for e in errors)
|
||||
|
||||
def test_missing_paths_returns_error(self) -> None:
|
||||
"""Missing 'paths' field returns an error."""
|
||||
from app.openapi.validator import validate_spec
|
||||
|
||||
spec = {k: v for k, v in _VALID_SPEC.items() if k != "paths"}
|
||||
errors = validate_spec(spec)
|
||||
assert len(errors) > 0
|
||||
assert any("paths" in e.lower() for e in errors)
|
||||
|
||||
def test_non_dict_input_returns_error(self) -> None:
|
||||
"""Non-dict input returns an error without raising."""
|
||||
from app.openapi.validator import validate_spec
|
||||
|
||||
errors = validate_spec("not a dict") # type: ignore[arg-type]
|
||||
assert len(errors) > 0
|
||||
|
||||
def test_empty_dict_returns_multiple_errors(self) -> None:
|
||||
"""Empty dict returns errors for all required fields."""
|
||||
from app.openapi.validator import validate_spec
|
||||
|
||||
errors = validate_spec({})
|
||||
# Should have at least one error for each required field
|
||||
assert len(errors) >= 3
|
||||
|
||||
def test_invalid_openapi_version_returns_error(self) -> None:
|
||||
"""Unsupported openapi version string returns an error."""
|
||||
from app.openapi.validator import validate_spec
|
||||
|
||||
spec = {**_VALID_SPEC, "openapi": "1.0.0"}
|
||||
errors = validate_spec(spec)
|
||||
assert len(errors) > 0
|
||||
|
||||
def test_errors_are_descriptive_strings(self) -> None:
|
||||
"""All returned errors are non-empty strings."""
|
||||
from app.openapi.validator import validate_spec
|
||||
|
||||
errors = validate_spec({})
|
||||
for e in errors:
|
||||
assert isinstance(e, str)
|
||||
assert len(e) > 0
|
||||
Reference in New Issue
Block a user