This commit is contained in:
@@ -1,74 +1,482 @@
|
|||||||
|
import json
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from src.core.parser import parse_openapi_spec, load_spec_file, ParseError
|
|
||||||
|
from src.core.parser import OpenAPIParser, ParseError, SpecValidationError
|
||||||
|
from src.core.models import OpenAPISpec
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
class TestOpenAPIParser:
|
||||||
def sample_spec(tmp_path):
|
"""Test cases for OpenAPIParser."""
|
||||||
spec = {
|
|
||||||
"openapi": "3.0.0",
|
def test_parse_valid_json_spec(self):
|
||||||
"info": {
|
"""Test parsing a valid JSON OpenAPI spec."""
|
||||||
"title": "Test API",
|
spec = {
|
||||||
"version": "1.0.0"
|
"openapi": "3.0.0",
|
||||||
},
|
"info": {
|
||||||
"paths": {
|
"title": "Test API",
|
||||||
"/users": {
|
"version": "1.0.0"
|
||||||
"get": {
|
},
|
||||||
"summary": "Get users",
|
"paths": {}
|
||||||
"description": "Retrieve a list of users",
|
}
|
||||||
"tags": ["users"],
|
|
||||||
"responses": {
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
||||||
"200": {"description": "Successful response"}
|
json.dump(spec, f)
|
||||||
|
f.flush()
|
||||||
|
|
||||||
|
parser = OpenAPIParser(f.name)
|
||||||
|
result = parser.parse()
|
||||||
|
|
||||||
|
assert isinstance(result, OpenAPISpec)
|
||||||
|
assert result.openapi == "3.0.0"
|
||||||
|
assert result.info.title == "Test API"
|
||||||
|
assert result.info.version == "1.0.0"
|
||||||
|
|
||||||
|
Path(f.name).unlink()
|
||||||
|
|
||||||
|
def test_parse_valid_yaml_spec(self):
|
||||||
|
"""Test parsing a valid YAML OpenAPI spec."""
|
||||||
|
spec_content = """
|
||||||
|
openapi: 3.0.0
|
||||||
|
info:
|
||||||
|
title: Test API
|
||||||
|
version: 1.0.0
|
||||||
|
paths: {}
|
||||||
|
"""
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
|
||||||
|
f.write(spec_content)
|
||||||
|
f.flush()
|
||||||
|
|
||||||
|
parser = OpenAPIParser(f.name)
|
||||||
|
result = parser.parse()
|
||||||
|
|
||||||
|
assert isinstance(result, OpenAPISpec)
|
||||||
|
assert result.openapi == "3.0.0"
|
||||||
|
assert result.info.title == "Test API"
|
||||||
|
|
||||||
|
Path(f.name).unlink()
|
||||||
|
|
||||||
|
def test_parse_missing_file(self):
|
||||||
|
"""Test parsing a non-existent file."""
|
||||||
|
parser = OpenAPIParser("nonexistent.json")
|
||||||
|
|
||||||
|
with pytest.raises(ParseError) as exc_info:
|
||||||
|
parser.parse()
|
||||||
|
|
||||||
|
assert "not found" in str(exc_info.value).lower()
|
||||||
|
|
||||||
|
def test_parse_invalid_json(self):
|
||||||
|
"""Test parsing invalid JSON file."""
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
||||||
|
f.write("{ invalid json }")
|
||||||
|
f.flush()
|
||||||
|
|
||||||
|
parser = OpenAPIParser(f.name)
|
||||||
|
|
||||||
|
with pytest.raises(ParseError):
|
||||||
|
parser.parse()
|
||||||
|
|
||||||
|
Path(f.name).unlink()
|
||||||
|
|
||||||
|
def test_parse_invalid_yaml(self):
|
||||||
|
"""Test parsing invalid YAML file."""
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
|
||||||
|
f.write(" - invalid: yaml: content:")
|
||||||
|
f.flush()
|
||||||
|
|
||||||
|
parser = OpenAPIParser(f.name)
|
||||||
|
|
||||||
|
with pytest.raises(ParseError):
|
||||||
|
parser.parse()
|
||||||
|
|
||||||
|
Path(f.name).unlink()
|
||||||
|
|
||||||
|
def test_parse_unsupported_format(self):
|
||||||
|
"""Test parsing unsupported file format."""
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
|
||||||
|
f.write("test content")
|
||||||
|
f.flush()
|
||||||
|
|
||||||
|
parser = OpenAPIParser(f.name)
|
||||||
|
|
||||||
|
with pytest.raises(ParseError) as exc_info:
|
||||||
|
parser.parse()
|
||||||
|
|
||||||
|
assert "unsupported" in str(exc_info.value).lower()
|
||||||
|
|
||||||
|
Path(f.name).unlink()
|
||||||
|
|
||||||
|
def test_parse_spec_with_endpoints(self):
|
||||||
|
"""Test parsing spec with endpoints."""
|
||||||
|
spec = {
|
||||||
|
"openapi": "3.0.0",
|
||||||
|
"info": {
|
||||||
|
"title": "Pet Store API",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"/pets": {
|
||||||
|
"get": {
|
||||||
|
"summary": "List pets",
|
||||||
|
"description": "Get all pets",
|
||||||
|
"responses": {
|
||||||
|
"200": {"description": "Success"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"summary": "Create pet",
|
||||||
|
"responses": {
|
||||||
|
"201": {"description": "Created"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/pets/{petId}": {
|
||||||
|
"get": {
|
||||||
|
"summary": "Get pet",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "petId",
|
||||||
|
"in": "path",
|
||||||
|
"required": True,
|
||||||
|
"schema": {"type": "string"}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {"description": "Success"},
|
||||||
|
"404": {"description": "Not found"}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
path = tmp_path / "test.json"
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
||||||
path.write_text(__import__('json').dumps(spec))
|
json.dump(spec, f)
|
||||||
return path
|
f.flush()
|
||||||
|
|
||||||
|
parser = OpenAPIParser(f.name)
|
||||||
|
result = parser.parse()
|
||||||
|
|
||||||
|
endpoints = result.get_endpoints()
|
||||||
|
assert len(endpoints) == 3
|
||||||
|
|
||||||
|
endpoint_paths = [e.path for e in endpoints]
|
||||||
|
assert "/pets" in endpoint_paths
|
||||||
|
assert "/pets/{petId}" in endpoint_paths
|
||||||
|
|
||||||
|
endpoint_methods = [e.method for e in endpoints]
|
||||||
|
assert "GET" in endpoint_methods
|
||||||
|
assert "POST" in endpoint_methods
|
||||||
|
|
||||||
|
Path(f.name).unlink()
|
||||||
|
|
||||||
|
def test_parse_spec_with_schemas(self):
|
||||||
|
"""Test parsing spec with schema definitions."""
|
||||||
|
spec = {
|
||||||
|
"openapi": "3.0.0",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"paths": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"Pet": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {"type": "string"},
|
||||||
|
"name": {"type": "string"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"User": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {"type": "integer"},
|
||||||
|
"username": {"type": "string"},
|
||||||
|
"email": {"type": "string", "format": "email"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
||||||
|
json.dump(spec, f)
|
||||||
|
f.flush()
|
||||||
|
|
||||||
|
parser = OpenAPIParser(f.name)
|
||||||
|
result = parser.parse()
|
||||||
|
|
||||||
|
schemas = result.get_schemas()
|
||||||
|
assert "Pet" in schemas
|
||||||
|
assert "User" in schemas
|
||||||
|
|
||||||
|
Path(f.name).unlink()
|
||||||
|
|
||||||
|
def test_parse_spec_with_tags(self):
|
||||||
|
"""Test parsing spec with tag definitions."""
|
||||||
|
spec = {
|
||||||
|
"openapi": "3.0.0",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"paths": {},
|
||||||
|
"tags": [
|
||||||
|
{"name": "pets", "description": "Pet operations"},
|
||||||
|
{"name": "users", "description": "User operations"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
||||||
|
json.dump(spec, f)
|
||||||
|
f.flush()
|
||||||
|
|
||||||
|
parser = OpenAPIParser(f.name)
|
||||||
|
result = parser.parse()
|
||||||
|
|
||||||
|
tags = result.get_tags()
|
||||||
|
assert len(tags) == 2
|
||||||
|
tag_names = [t.name for t in tags]
|
||||||
|
assert "pets" in tag_names
|
||||||
|
assert "users" in tag_names
|
||||||
|
|
||||||
|
Path(f.name).unlink()
|
||||||
|
|
||||||
|
def test_parse_spec_with_parameters(self):
|
||||||
|
"""Test parsing spec with endpoint parameters."""
|
||||||
|
spec = {
|
||||||
|
"openapi": "3.0.0",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"/items": {
|
||||||
|
"get": {
|
||||||
|
"summary": "List items",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {"type": "integer"},
|
||||||
|
"description": "Max items to return"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "offset",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {"type": "integer"},
|
||||||
|
"description": "Items to skip"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {"200": {"description": "Success"}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
||||||
|
json.dump(spec, f)
|
||||||
|
f.flush()
|
||||||
|
|
||||||
|
parser = OpenAPIParser(f.name)
|
||||||
|
result = parser.parse()
|
||||||
|
|
||||||
|
endpoints = result.get_endpoints()
|
||||||
|
assert len(endpoints) == 1
|
||||||
|
|
||||||
|
params = endpoints[0].parameters or []
|
||||||
|
assert len(params) == 2
|
||||||
|
param_names = [p.name for p in params]
|
||||||
|
assert "limit" in param_names
|
||||||
|
assert "offset" in param_names
|
||||||
|
|
||||||
|
Path(f.name).unlink()
|
||||||
|
|
||||||
|
|
||||||
def test_parse_valid_spec(sample_spec):
|
class TestSpecValidation:
|
||||||
result = parse_openapi_spec(str(sample_spec))
|
"""Test cases for OpenAPI spec validation."""
|
||||||
assert result['valid'] == True
|
|
||||||
assert result['title'] == 'Test API'
|
def test_validate_valid_spec(self):
|
||||||
assert result['version_num'] == '1.0.0'
|
"""Test validating a valid spec."""
|
||||||
|
spec = {
|
||||||
|
"openapi": "3.0.0",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"paths": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
||||||
|
json.dump(spec, f)
|
||||||
|
f.flush()
|
||||||
|
|
||||||
|
parser = OpenAPIParser(f.name)
|
||||||
|
is_valid, errors = parser.validate()
|
||||||
|
|
||||||
|
assert is_valid is True
|
||||||
|
assert len(errors) == 0
|
||||||
|
|
||||||
|
Path(f.name).unlink()
|
||||||
|
|
||||||
|
def test_validate_spec_missing_info(self):
|
||||||
|
"""Test validating spec without info section."""
|
||||||
|
spec = {
|
||||||
|
"openapi": "3.0.0",
|
||||||
|
"paths": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
||||||
|
json.dump(spec, f)
|
||||||
|
f.flush()
|
||||||
|
|
||||||
|
parser = OpenAPIParser(f.name)
|
||||||
|
is_valid, errors = parser.validate()
|
||||||
|
|
||||||
|
assert is_valid is False
|
||||||
|
assert len(errors) > 0
|
||||||
|
|
||||||
|
Path(f.name).unlink()
|
||||||
|
|
||||||
|
def test_validate_spec_missing_paths(self):
|
||||||
|
"""Test validating spec without paths section."""
|
||||||
|
spec = {
|
||||||
|
"openapi": "3.0.0",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
||||||
|
json.dump(spec, f)
|
||||||
|
f.flush()
|
||||||
|
|
||||||
|
parser = OpenAPIParser(f.name)
|
||||||
|
is_valid, errors = parser.validate()
|
||||||
|
|
||||||
|
assert is_valid is False
|
||||||
|
|
||||||
|
Path(f.name).unlink()
|
||||||
|
|
||||||
|
|
||||||
def test_parse_invalid_spec(tmp_path):
|
class TestParseWithExamples:
|
||||||
invalid = tmp_path / "invalid.json"
|
"""Test cases for parsing with generated examples."""
|
||||||
invalid.write_text('{"invalid": "spec"}')
|
|
||||||
result = parse_openapi_spec(str(invalid))
|
def test_parse_with_examples(self):
|
||||||
assert result['valid'] == False
|
"""Test parsing spec and generating examples."""
|
||||||
assert 'errors' in result
|
spec = {
|
||||||
|
"openapi": "3.0.0",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"/users": {
|
||||||
|
"get": {
|
||||||
|
"summary": "List users",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {"type": "integer"},
|
||||||
|
"name": {"type": "string"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"User": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {"type": "integer"},
|
||||||
|
"name": {"type": "string", "format": "email"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
||||||
|
json.dump(spec, f)
|
||||||
|
f.flush()
|
||||||
|
|
||||||
|
parser = OpenAPIParser(f.name)
|
||||||
|
result = parser.parse_with_examples()
|
||||||
|
|
||||||
|
assert "spec" in result
|
||||||
|
assert "endpoints" in result
|
||||||
|
assert "schemas" in result
|
||||||
|
|
||||||
|
assert len(result["endpoints"]) == 1
|
||||||
|
assert len(result["schemas"]) == 1
|
||||||
|
|
||||||
|
Path(f.name).unlink()
|
||||||
|
|
||||||
|
|
||||||
def test_load_json_spec(sample_spec):
|
class TestConvenienceFunctions:
|
||||||
spec = load_spec_file(str(sample_spec))
|
"""Test cases for convenience functions."""
|
||||||
assert spec['info']['title'] == 'Test API'
|
|
||||||
|
|
||||||
|
def test_parse_spec_file(self):
|
||||||
|
"""Test parse_spec_file convenience function."""
|
||||||
|
from src.core.parser import parse_spec_file
|
||||||
|
|
||||||
def test_load_yaml_spec(tmp_path):
|
spec = {
|
||||||
content = '''
|
"openapi": "3.0.0",
|
||||||
openapi: "3.0.0"
|
"info": {
|
||||||
info:
|
"title": "Test API",
|
||||||
title: Test API
|
"version": "1.0.0"
|
||||||
version: "1.0.0"
|
},
|
||||||
paths: {}
|
"paths": {}
|
||||||
'''
|
}
|
||||||
path = tmp_path / "test.yaml"
|
|
||||||
path.write_text(content)
|
|
||||||
spec = load_spec_file(str(path))
|
|
||||||
assert spec['info']['title'] == 'Test API'
|
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
||||||
|
json.dump(spec, f)
|
||||||
|
f.flush()
|
||||||
|
|
||||||
def test_parse_error_missing_file():
|
result = parse_spec_file(f.name)
|
||||||
with pytest.raises(ParseError):
|
|
||||||
load_spec_file('/nonexistent/path.json')
|
|
||||||
|
|
||||||
|
assert isinstance(result, OpenAPISpec)
|
||||||
|
assert result.info.title == "Test API"
|
||||||
|
|
||||||
def test_parse_error_unsupported_format(tmp_path):
|
Path(f.name).unlink()
|
||||||
path = tmp_path / "test.txt"
|
|
||||||
path.write_text('some content')
|
def test_validate_spec_file(self):
|
||||||
with pytest.raises(ParseError):
|
"""Test validate_spec_file convenience function."""
|
||||||
load_spec_file(str(path))
|
from src.core.parser import validate_spec_file
|
||||||
|
|
||||||
|
spec = {
|
||||||
|
"openapi": "3.0.0",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"paths": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
||||||
|
json.dump(spec, f)
|
||||||
|
f.flush()
|
||||||
|
|
||||||
|
is_valid, errors = validate_spec_file(f.name)
|
||||||
|
|
||||||
|
assert is_valid is True
|
||||||
|
|
||||||
|
Path(f.name).unlink()
|
||||||
|
|||||||
Reference in New Issue
Block a user