File size: 4,982 Bytes
2322c22
677e796
2322c22
 
 
 
 
 
 
677e796
 
 
 
2322c22
 
677e796
 
 
2322c22
677e796
 
 
2322c22
677e796
 
 
2322c22
677e796
 
2322c22
 
 
677e796
 
 
 
2322c22
 
 
 
 
 
 
 
 
 
 
 
677e796
 
2322c22
 
 
 
 
 
 
 
 
677e796
 
2322c22
 
 
 
 
677e796
 
2322c22
677e796
 
 
 
2322c22
677e796
 
 
2322c22
 
 
 
 
677e796
2322c22
 
 
 
 
 
677e796
2322c22
 
 
 
 
677e796
2322c22
 
677e796
2322c22
 
 
 
 
677e796
2322c22
 
 
677e796
2322c22
 
 
 
 
 
 
 
 
 
677e796
 
2322c22
677e796
2322c22
 
 
 
 
 
 
 
677e796
2322c22
 
 
 
 
 
 
 
 
 
 
677e796
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import asyncio
import logging
import os
import sys
import types
import pytest
from fastapi import APIRouter
from fastapi.testclient import TestClient

root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
if root_dir not in sys.path:
    sys.path.insert(0, root_dir)


def create_mock_modules():
    sem = types.ModuleType("mock_semantic")
    sem_router_mod = types.ModuleType("mock_semantic.router")
    r = APIRouter()

    @r.get("/ping")
    def ping():
        return {"project": "semantic", "status": "ok"}

    @r.post("/echo")
    def echo(data: dict):
        return {"project": "semantic", "echo": data}

    @r.get("/slow")
    async def slow():
        await asyncio.sleep(0.5)
        return {"status": "finished"}

    sem_router_mod.router = r
    sem.router = sem_router_mod
    sys.modules["mock_semantic"] = sem
    sys.modules["mock_semantic.router"] = sem_router_mod


def test_host_with_mocks(monkeypatch, caplog):
    create_mock_modules()

    monkeypatch.setenv("CORS_ALLOWED_ORIGINS", "https://allowed.com,https://example.com")
    monkeypatch.setenv("MAX_BODY_SIZE_BYTES", "1024")
    monkeypatch.setenv("REQUEST_TIMEOUT_SECONDS", "0.2")
    monkeypatch.setenv("DISABLE_PROJECTS", "")

    import registry
    registry.PROJECT_REGISTRY = {
        "/semantic": "mock_semantic.router:router",
        "/broken": "nonexistent_module.router:router",
    }

    if "main" in sys.modules:
        del sys.modules["main"]

    with caplog.at_level(logging.INFO):
        import main
        client = TestClient(main.app)

    assert "[/semantic] Successfully mounted at /semantic" in caplog.text
    assert "[/broken] Failed to mount router" in caplog.text

    res_health = client.get("/health")
    assert res_health.status_code == 200
    health_data = res_health.json()
    assert health_data["status"] == "degraded"
    assert "/semantic" in health_data["loaded"]
    assert "/broken" in health_data["failed"]

    # Verify GET / returns same info as /health
    res_root = client.get("/")
    assert res_root.status_code == 200
    assert res_root.json() == health_data

    res_sem = client.get("/semantic/ping")
    assert res_sem.status_code == 200
    assert res_sem.json() == {"project": "semantic", "status": "ok"}

    for doc_url in ["/docs", "/redoc", "/openapi.json"]:
        assert client.get(doc_url).status_code == 404

    cors_allowed = client.get(
        "/semantic/ping",
        headers={"Origin": "https://allowed.com"}
    )
    assert cors_allowed.status_code == 200
    assert cors_allowed.headers.get("access-control-allow-origin") == "https://allowed.com"

    cors_rejected = client.get(
        "/semantic/ping",
        headers={"Origin": "https://disallowed.com"}
    )
    assert cors_rejected.headers.get("access-control-allow-origin") is None

    oversized_data = {"text": "a" * 2000}
    res_oversized = client.post("/semantic/echo", json=oversized_data)
    assert res_oversized.status_code == 413

    res_slow = client.get("/semantic/slow")
    assert res_slow.status_code == 504


def test_kill_switch(monkeypatch):
    create_mock_modules()
    monkeypatch.setenv("DISABLE_PROJECTS", "semantic")

    import registry
    registry.PROJECT_REGISTRY = {
        "/semantic": "mock_semantic.router:router",
    }

    if "main" in sys.modules:
        del sys.modules["main"]

    import main
    client = TestClient(main.app)

    res_health = client.get("/health")
    health_data = res_health.json()
    assert "/semantic" not in health_data["loaded"]
    assert "/semantic" not in health_data["failed"]

    assert client.get("/semantic/ping").status_code == 404


def test_healthy_status_when_all_load(monkeypatch):
    create_mock_modules()
    monkeypatch.setenv("DISABLE_PROJECTS", "")

    import registry
    registry.PROJECT_REGISTRY = {
        "/semantic": "mock_semantic.router:router",
    }

    if "main" in sys.modules:
        del sys.modules["main"]

    import main
    client = TestClient(main.app)

    res_health = client.get("/health")
    assert res_health.status_code == 200
    assert res_health.json()["status"] == "healthy"
    assert "/semantic" in res_health.json()["loaded"]

    res_root = client.get("/")
    assert res_root.status_code == 200
    assert res_root.json() == res_health.json()


def test_real_registry():
    import importlib
    import registry
    importlib.reload(registry)
    assert list(registry.PROJECT_REGISTRY.keys()) == ["/semantic"]
    assert registry.PROJECT_REGISTRY["/semantic"] == "semantic_backend.router:router"

    if "main" in sys.modules:
        del sys.modules["main"]

    import main
    client = TestClient(main.app)

    res = client.get("/health")
    assert res.status_code == 200
    data = res.json()
    assert data["status"] == "healthy"
    assert "/semantic" in data["loaded"]
    assert not data["failed"]

    res_root = client.get("/")
    assert res_root.status_code == 200
    assert res_root.json() == data