Adds the pydantic-settings Settings with validated GITEA_URL, OAuth scopes and toolsets, the argparse CLI skeleton, catalog.model, identity and GiteaAPIError. Test fixtures now isolate env, cwd and state paths in tmp.
118 lines
3.4 KiB
Python
118 lines
3.4 KiB
Python
"""Tests for the catalog data model."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from aegis_gitea_mcp.catalog import Operation, Parameter
|
|
|
|
|
|
def sample_dict() -> dict[str, Any]:
|
|
return {
|
|
"tool_name": "gitea_issue_create",
|
|
"domain": "issue",
|
|
"operation_id": "issueCreateIssue",
|
|
"method": "POST",
|
|
"path": "/repos/{owner}/{repo}/issues",
|
|
"summary": "Create an issue",
|
|
"description": "",
|
|
"parameters": [
|
|
{
|
|
"name": "owner",
|
|
"arg_name": "owner",
|
|
"location": "path",
|
|
"required": True,
|
|
"schema": {"type": "string"},
|
|
"is_file": False,
|
|
},
|
|
{
|
|
"name": "body",
|
|
"arg_name": "body",
|
|
"location": "body",
|
|
"required": False,
|
|
"schema": {"type": "object"},
|
|
},
|
|
],
|
|
"input_schema": {"type": "object", "additionalProperties": False},
|
|
"consumes": ["application/json"],
|
|
"produces": ["application/json"],
|
|
"read_only": False,
|
|
"destructive": False,
|
|
"idempotent": False,
|
|
"paginated": False,
|
|
}
|
|
|
|
|
|
def test_operation_round_trip_through_json() -> None:
|
|
data = sample_dict()
|
|
operation = Operation.from_dict(data)
|
|
|
|
assert operation.method == "POST"
|
|
assert isinstance(operation.parameters, tuple)
|
|
assert isinstance(operation.consumes, tuple)
|
|
assert operation.parameters[0].location == "path"
|
|
assert operation.parameters[1].is_file is False
|
|
|
|
rebuilt = Operation.from_dict(json.loads(json.dumps(operation.to_dict())))
|
|
assert rebuilt == operation
|
|
|
|
|
|
def test_to_dict_uses_lists_and_is_json_serializable() -> None:
|
|
dumped = Operation.from_dict(sample_dict()).to_dict()
|
|
assert isinstance(dumped["parameters"], list)
|
|
assert isinstance(dumped["consumes"], list)
|
|
json.dumps(dumped)
|
|
|
|
|
|
def test_method_is_upper_cased() -> None:
|
|
data = sample_dict()
|
|
data["method"] = "post"
|
|
assert Operation.from_dict(data).method == "POST"
|
|
|
|
|
|
def test_optional_fields_default() -> None:
|
|
data = sample_dict()
|
|
del data["summary"], data["description"], data["consumes"], data["produces"]
|
|
operation = Operation.from_dict(data)
|
|
assert operation.summary == ""
|
|
assert operation.consumes == ()
|
|
assert operation.produces == ()
|
|
|
|
|
|
def test_invalid_location_rejected() -> None:
|
|
with pytest.raises(ValueError, match="location"):
|
|
Parameter.from_dict(
|
|
{"name": "x", "arg_name": "x", "location": "header", "required": True, "schema": {}}
|
|
)
|
|
|
|
|
|
def test_missing_required_key_raises() -> None:
|
|
data = sample_dict()
|
|
del data["tool_name"]
|
|
with pytest.raises(KeyError):
|
|
Operation.from_dict(data)
|
|
|
|
|
|
def test_operation_is_frozen() -> None:
|
|
operation = Operation.from_dict(sample_dict())
|
|
with pytest.raises(AttributeError):
|
|
operation.method = "GET" # type: ignore[misc]
|
|
|
|
|
|
def test_file_parameter() -> None:
|
|
parameter = Parameter.from_dict(
|
|
{
|
|
"name": "attachment",
|
|
"arg_name": "attachment",
|
|
"location": "formData",
|
|
"required": True,
|
|
"schema": {"type": "string", "contentEncoding": "base64"},
|
|
"is_file": True,
|
|
}
|
|
)
|
|
assert parameter.is_file is True
|
|
assert parameter.to_dict()["location"] == "formData"
|