diff --git a/tests/unit/test_detectors.py b/tests/unit/test_detectors.py new file mode 100644 index 0000000..b472aa0 --- /dev/null +++ b/tests/unit/test_detectors.py @@ -0,0 +1,266 @@ +"""Tests for endpoint detectors.""" + +import pytest +import tempfile +import os +from pathlib import Path +from docgen.models import HTTPMethod + + +class TestPythonDetector: + """Tests for PythonDetector.""" + + def test_fastapi_detection(self): + """Test FastAPI endpoint detection.""" + code = ''' +from fastapi import FastAPI, APIRouter +app = FastAPI() + +@app.get("/users") +async def get_users(): + return {"users": []} + +@app.post("/users") +async def create_user(): + return {"id": 1} + +router = APIRouter() +@router.get("/items") +async def get_items(): + return {"items": []} +''' + with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f: + f.write(code) + f.flush() + + from docgen.detectors import PythonDetector + detector = PythonDetector() + endpoints = detector.detect_endpoints(Path(f.name)) + + os.unlink(f.name) + + assert len(endpoints) >= 3 + methods = [e.method for e in endpoints] + assert HTTPMethod.GET in methods + assert HTTPMethod.POST in methods + + def test_flask_detection(self): + """Test Flask endpoint detection.""" + code = ''' +from flask import Flask, Blueprint + +app = Flask(__name__) +blueprint = Blueprint("api", __name__) + +@blueprint.route("/items") +def get_items(): + return {"items": []} + +@blueprint.route("/items", methods=["POST"]) +def create_item(): + return {"id": 1} +''' + with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f: + f.write(code) + f.flush() + + from docgen.detectors import PythonDetector + detector = PythonDetector() + endpoints = detector.detect_endpoints(Path(f.name)) + + os.unlink(f.name) + + assert len(endpoints) >= 2 + + def test_django_detection(self): + """Test Django URL pattern detection.""" + code = ''' +from django.urls import path +from . import views + +urlpatterns = [ + path("users/", views.UserListView.as_view()), + path("users//", views.UserDetailView.as_view()), +] +''' + with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f: + f.write(code) + f.flush() + + from docgen.detectors import PythonDetector + detector = PythonDetector() + endpoints = detector.detect_endpoints(Path(f.name)) + + os.unlink(f.name) + + assert len(endpoints) == 2 + assert endpoints[0].path == "users/" + assert endpoints[1].path == "users//" + + +class TestJavaScriptDetector: + """Tests for JavaScriptDetector.""" + + def test_express_detection(self): + """Test Express endpoint detection.""" + code = ''' +const express = require("express"); +const router = express.Router(); + +router.get("/users", (req, res) => { + res.json({ users: [] }); +}); + +router.post("/users", (req, res) => { + res.json({ id: 1 }); +}); +''' + with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False) as f: + f.write(code) + f.flush() + + from docgen.detectors import JavaScriptDetector + detector = JavaScriptDetector() + endpoints = detector.detect_endpoints(Path(f.name)) + + os.unlink(f.name) + + assert len(endpoints) >= 2 + + def test_fastify_detection(self): + """Test Fastify endpoint detection.""" + code = ''' +const fastify = require("fastify")(); + +fastify.get("/items", async (request, reply) => { + return { items: [] }; +}); + +fastify.post("/items", async (request, reply) => { + return { id: 1 }; +}); +''' + with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False) as f: + f.write(code) + f.flush() + + from docgen.detectors import JavaScriptDetector + detector = JavaScriptDetector() + endpoints = detector.detect_endpoints(Path(f.name)) + + os.unlink(f.name) + + assert len(endpoints) >= 2 + + +class TestGoDetector: + """Tests for GoDetector.""" + + def test_gin_detection(self): + """Test Gin endpoint detection.""" + code = ''' +package main + +import ( + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + r.GET("/users", func(c *gin.Context) { + c.JSON(200, gin.H{"users": []}) + }) + r.POST("/users", func(c *gin.Context) { + c.JSON(201, gin.H{"id": 1}) + }) +} +''' + with tempfile.NamedTemporaryFile(mode='w', suffix='.go', delete=False) as f: + f.write(code) + f.flush() + + from docgen.detectors import GoDetector + detector = GoDetector() + endpoints = detector.detect_endpoints(Path(f.name)) + + os.unlink(f.name) + + assert len(endpoints) >= 2 + + def test_chi_detection(self): + """Test chi endpoint detection.""" + code = ''' +package main + +import ( + "github.com/go-chi/chi" +) + +func main() { + r := chi.NewRouter() + r.GET("/items", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"items": []}`)) + }) + r.POST("/items", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"id": 1}`)) + }) +} +''' + with tempfile.NamedTemporaryFile(mode='w', suffix='.go', delete=False) as f: + f.write(code) + f.flush() + + from docgen.detectors import GoDetector + detector = GoDetector() + endpoints = detector.detect_endpoints(Path(f.name)) + + os.unlink(f.name) + + assert len(endpoints) >= 2 + + +class TestRustDetector: + """Tests for RustDetector.""" + + def test_actix_detection(self): + """Test Actix-web endpoint detection.""" + code = ''' +use actix_web::{web, App, HttpResponse}; + +#[get("/users")] +async fn list_users() -> HttpResponse { + HttpResponse::Ok().json(vec![]) +} + +#[post("/users")] +async fn create_user() -> HttpResponse { + HttpResponse::Created().json(serde_json::json!({"id": 1})) +} +''' + with tempfile.NamedTemporaryFile(mode='w', suffix='.rs', delete=False) as f: + f.write(code) + f.flush() + + from docgen.detectors import RustDetector + detector = RustDetector() + endpoints = detector.detect_endpoints(Path(f.name)) + + os.unlink(f.name) + + assert len(endpoints) >= 0 + + +class TestBaseDetector: + """Tests for BaseDetector functionality.""" + + def test_can_detect(self): + """Test file extension detection.""" + from docgen.detectors import PythonDetector, JavaScriptDetector + + py_detector = PythonDetector() + js_detector = JavaScriptDetector() + + assert py_detector.can_detect(Path("test.py")) is True + assert py_detector.can_detect(Path("test.js")) is False + assert js_detector.can_detect(Path("test.ts")) is True + assert js_detector.can_detect(Path("test.py")) is False