aegis-stack 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of aegis-stack might be problematic. Click here for more details.
- aegis/__init__.py +5 -0
- aegis/__main__.py +374 -0
- aegis/core/CLAUDE.md +365 -0
- aegis/core/__init__.py +6 -0
- aegis/core/components.py +115 -0
- aegis/core/dependency_resolver.py +119 -0
- aegis/core/template_generator.py +163 -0
- aegis/templates/CLAUDE.md +306 -0
- aegis/templates/cookiecutter-aegis-project/cookiecutter.json +27 -0
- aegis/templates/cookiecutter-aegis-project/hooks/post_gen_project.py +172 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.dockerignore +71 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.env.example.j2 +70 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.gitignore +127 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Dockerfile +53 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Makefile +211 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/README.md.j2 +196 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/__init__.py +5 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/__init__.py +6 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/health.py +321 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/load_test.py +638 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/main.py +41 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/health.py +134 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/models.py.j2 +247 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/routing.py.j2 +14 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/tasks.py.j2 +596 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/hooks.py +133 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/main.py +16 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/cors.py +20 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/cleanup.py +14 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/component_health.py.j2 +190 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/core/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/core/theme.py +46 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/main.py +687 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/main.py +138 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/CLAUDE.md +213 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/__init__.py +6 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/constants.py.j2 +30 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/pools.py +78 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/load_test.py +48 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/media.py +41 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/system.py +36 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/registry.py +139 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/__init__.py +119 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/load_tasks.py +526 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/simple_system_tasks.py +32 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/system_tasks.py +279 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/config.py.j2 +119 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/constants.py +60 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/db.py +67 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/log.py +85 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/webserver.py +40 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/{% if cookiecutter.include_scheduler == /"yes/" %}scheduler.py{% endif %}" +21 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/main.py +61 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/py.typed +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test.py +661 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test_models.py +269 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/__init__.py +15 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/models.py +26 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/__init__.py +52 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/alerts.py +94 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/health.py.j2 +1105 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/models.py +169 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/ui.py +52 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docker-compose.yml.j2 +195 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/api.md +191 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md +414 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/development.md +215 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/health.md +240 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/javascripts/mermaid-config.js +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/stylesheets/mermaid.css +95 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/mkdocs.yml.j2 +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/pyproject.toml.j2 +156 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh +87 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh.j2 +104 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/gen_docs.py +16 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_health_endpoints.py.j2 +239 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/components/test_scheduler.py +76 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/conftest.py.j2 +81 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_component_integration.py.j2 +376 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_health_logic.py.j2 +633 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_models.py +665 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_service.py +602 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_system_service.py +96 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_worker_health_registration.py.j2 +224 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/test_core.py +50 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/uv.lock +1673 -0
- aegis_stack-0.1.0.dist-info/METADATA +114 -0
- aegis_stack-0.1.0.dist-info/RECORD +103 -0
- aegis_stack-0.1.0.dist-info/WHEEL +4 -0
- aegis_stack-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
{%- if cookiecutter.include_worker == "yes" %}
|
|
2
|
+
"""
|
|
3
|
+
Tests for worker health registration and integration.
|
|
4
|
+
|
|
5
|
+
Tests that worker components are properly registered in the health system
|
|
6
|
+
and appear in health check results.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from unittest.mock import AsyncMock, MagicMock, patch
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from app.services.system import ComponentStatus, ComponentStatusType
|
|
15
|
+
from app.services.system.health import get_system_status, register_health_check, _health_checks
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestWorkerHealthRegistration:
|
|
19
|
+
"""Test worker health registration mechanism."""
|
|
20
|
+
|
|
21
|
+
def test_worker_health_check_is_registered(self) -> None:
|
|
22
|
+
"""Test that worker health check gets registered during startup."""
|
|
23
|
+
# This test verifies that the startup hook actually registers the worker
|
|
24
|
+
# In a real app, this would be done during startup
|
|
25
|
+
|
|
26
|
+
# Clear any existing registrations for clean test
|
|
27
|
+
original_checks = _health_checks.copy()
|
|
28
|
+
_health_checks.clear()
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
# Import and register worker health check (simulating startup)
|
|
32
|
+
from app.services.system.health import check_worker_health
|
|
33
|
+
register_health_check("worker", check_worker_health)
|
|
34
|
+
|
|
35
|
+
# Verify registration worked
|
|
36
|
+
assert "worker" in _health_checks
|
|
37
|
+
assert _health_checks["worker"] == check_worker_health
|
|
38
|
+
|
|
39
|
+
finally:
|
|
40
|
+
# Restore original state
|
|
41
|
+
_health_checks.clear()
|
|
42
|
+
_health_checks.update(original_checks)
|
|
43
|
+
|
|
44
|
+
@pytest.mark.asyncio
|
|
45
|
+
async def test_worker_appears_in_system_status(self) -> None:
|
|
46
|
+
"""Test that registered worker appears in get_system_status results."""
|
|
47
|
+
# Mock worker health check to avoid Redis dependency
|
|
48
|
+
mock_worker_status = ComponentStatus(
|
|
49
|
+
name="worker",
|
|
50
|
+
status=ComponentStatusType.HEALTHY,
|
|
51
|
+
message="arq worker infrastructure: 2/2 workers active",
|
|
52
|
+
response_time_ms=50.0,
|
|
53
|
+
metadata={
|
|
54
|
+
"total_queued": 0,
|
|
55
|
+
"total_completed": 5,
|
|
56
|
+
"total_failed": 0,
|
|
57
|
+
"active_workers": 2,
|
|
58
|
+
},
|
|
59
|
+
sub_components={
|
|
60
|
+
"queues": ComponentStatus(
|
|
61
|
+
name="queues",
|
|
62
|
+
status=ComponentStatusType.HEALTHY,
|
|
63
|
+
message="2 functional queues configured (2 active)",
|
|
64
|
+
response_time_ms=None,
|
|
65
|
+
metadata={"configured_queues": 2, "active_workers": 2},
|
|
66
|
+
sub_components={
|
|
67
|
+
"system": ComponentStatus(
|
|
68
|
+
name="system",
|
|
69
|
+
status=ComponentStatusType.HEALTHY,
|
|
70
|
+
message="System maintenance: idle, 2 completed",
|
|
71
|
+
response_time_ms=None,
|
|
72
|
+
metadata={
|
|
73
|
+
"queue_type": "system",
|
|
74
|
+
"worker_alive": True,
|
|
75
|
+
"jobs_completed": 2,
|
|
76
|
+
"jobs_failed": 0,
|
|
77
|
+
},
|
|
78
|
+
),
|
|
79
|
+
"load_test": ComponentStatus(
|
|
80
|
+
name="load_test",
|
|
81
|
+
status=ComponentStatusType.HEALTHY,
|
|
82
|
+
message="Performance testing: idle, 3 completed",
|
|
83
|
+
response_time_ms=None,
|
|
84
|
+
metadata={
|
|
85
|
+
"queue_type": "load_test",
|
|
86
|
+
"worker_alive": True,
|
|
87
|
+
"jobs_completed": 3,
|
|
88
|
+
"jobs_failed": 0,
|
|
89
|
+
},
|
|
90
|
+
),
|
|
91
|
+
},
|
|
92
|
+
)
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Clear and register mock worker
|
|
97
|
+
original_checks = _health_checks.copy()
|
|
98
|
+
_health_checks.clear()
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
# Register mock worker health check
|
|
102
|
+
async def mock_worker_health() -> ComponentStatus:
|
|
103
|
+
return mock_worker_status
|
|
104
|
+
|
|
105
|
+
register_health_check("worker", mock_worker_health)
|
|
106
|
+
|
|
107
|
+
# Mock system metrics to avoid psutil dependency
|
|
108
|
+
with patch(
|
|
109
|
+
"app.services.system.health._get_cached_system_metrics"
|
|
110
|
+
) as mock_metrics:
|
|
111
|
+
mock_metrics.return_value = {
|
|
112
|
+
"memory": ComponentStatus(
|
|
113
|
+
name="memory",
|
|
114
|
+
message="Memory usage: 45.2%",
|
|
115
|
+
response_time_ms=None,
|
|
116
|
+
),
|
|
117
|
+
"disk": ComponentStatus(
|
|
118
|
+
name="disk",
|
|
119
|
+
message="Disk usage: 12.1%",
|
|
120
|
+
response_time_ms=None,
|
|
121
|
+
),
|
|
122
|
+
"cpu": ComponentStatus(
|
|
123
|
+
name="cpu",
|
|
124
|
+
message="CPU usage: 5.3%",
|
|
125
|
+
response_time_ms=None,
|
|
126
|
+
),
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# Get system status
|
|
130
|
+
system_status = await get_system_status()
|
|
131
|
+
|
|
132
|
+
# Verify worker appears in results
|
|
133
|
+
assert system_status.overall_healthy
|
|
134
|
+
assert "aegis" in system_status.components
|
|
135
|
+
|
|
136
|
+
aegis_component = system_status.components["aegis"]
|
|
137
|
+
assert "worker" in aegis_component.sub_components
|
|
138
|
+
|
|
139
|
+
worker_component = aegis_component.sub_components["worker"]
|
|
140
|
+
assert worker_component.name == "worker"
|
|
141
|
+
assert worker_component.healthy
|
|
142
|
+
assert "workers active" in worker_component.message
|
|
143
|
+
assert "queues" in worker_component.sub_components
|
|
144
|
+
|
|
145
|
+
# Verify queue sub-components
|
|
146
|
+
queues_component = worker_component.sub_components["queues"]
|
|
147
|
+
assert "system" in queues_component.sub_components
|
|
148
|
+
assert "load_test" in queues_component.sub_components
|
|
149
|
+
|
|
150
|
+
finally:
|
|
151
|
+
# Restore original state
|
|
152
|
+
_health_checks.clear()
|
|
153
|
+
_health_checks.update(original_checks)
|
|
154
|
+
|
|
155
|
+
@pytest.mark.asyncio
|
|
156
|
+
async def test_system_status_without_worker_registration(self) -> None:
|
|
157
|
+
"""Test that system status works when worker is not registered."""
|
|
158
|
+
# Clear registrations
|
|
159
|
+
original_checks = _health_checks.copy()
|
|
160
|
+
_health_checks.clear()
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
# Register only basic components (no worker)
|
|
164
|
+
async def mock_backend_health() -> ComponentStatus:
|
|
165
|
+
return ComponentStatus(
|
|
166
|
+
name="backend",
|
|
167
|
+
message="FastAPI backend active",
|
|
168
|
+
response_time_ms=25.0,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
register_health_check("backend", mock_backend_health)
|
|
172
|
+
|
|
173
|
+
# Mock system metrics
|
|
174
|
+
with patch(
|
|
175
|
+
"app.services.system.health._get_cached_system_metrics"
|
|
176
|
+
) as mock_metrics:
|
|
177
|
+
mock_metrics.return_value = {
|
|
178
|
+
"memory": ComponentStatus(
|
|
179
|
+
name="memory",
|
|
180
|
+
message="Memory usage: 45.2%",
|
|
181
|
+
response_time_ms=None,
|
|
182
|
+
),
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# Get system status
|
|
186
|
+
system_status = await get_system_status()
|
|
187
|
+
|
|
188
|
+
# Verify worker does NOT appear in results
|
|
189
|
+
aegis_component = system_status.components["aegis"]
|
|
190
|
+
assert "worker" not in aegis_component.sub_components
|
|
191
|
+
assert "backend" in aegis_component.sub_components
|
|
192
|
+
|
|
193
|
+
finally:
|
|
194
|
+
# Restore original state
|
|
195
|
+
_health_checks.clear()
|
|
196
|
+
_health_checks.update(original_checks)
|
|
197
|
+
|
|
198
|
+
def test_health_registration_is_idempotent(self) -> None:
|
|
199
|
+
"""Test that registering the same health check multiple times works."""
|
|
200
|
+
original_checks = _health_checks.copy()
|
|
201
|
+
_health_checks.clear()
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
async def dummy_health_check() -> ComponentStatus:
|
|
205
|
+
return ComponentStatus(
|
|
206
|
+
name="test",
|
|
207
|
+
message="Test",
|
|
208
|
+
response_time_ms=None,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# Register same check multiple times
|
|
212
|
+
register_health_check("test", dummy_health_check)
|
|
213
|
+
register_health_check("test", dummy_health_check)
|
|
214
|
+
register_health_check("test", dummy_health_check)
|
|
215
|
+
|
|
216
|
+
# Should only be registered once
|
|
217
|
+
assert len(_health_checks) == 1
|
|
218
|
+
assert "test" in _health_checks
|
|
219
|
+
|
|
220
|
+
finally:
|
|
221
|
+
# Restore original state
|
|
222
|
+
_health_checks.clear()
|
|
223
|
+
_health_checks.update(original_checks)
|
|
224
|
+
{%- endif %}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# tests/test_core.py
|
|
2
|
+
"""
|
|
3
|
+
Core functionality tests.
|
|
4
|
+
|
|
5
|
+
These basic tests ensure the fundamental application components work correctly.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from collections.abc import Generator
|
|
9
|
+
|
|
10
|
+
from fastapi.testclient import TestClient
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
from app.integrations.main import create_integrated_app
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture
|
|
17
|
+
def client() -> Generator[TestClient, None, None]:
|
|
18
|
+
"""Create a test client for the FastAPI app."""
|
|
19
|
+
app = create_integrated_app()
|
|
20
|
+
with TestClient(app) as test_client:
|
|
21
|
+
yield test_client
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_health_endpoint(client: TestClient) -> None:
|
|
25
|
+
"""Test that the health endpoint works."""
|
|
26
|
+
response = client.get("/health/")
|
|
27
|
+
# Should return 200 for healthy or 503 for unhealthy, both are valid responses
|
|
28
|
+
assert response.status_code in [200, 503]
|
|
29
|
+
|
|
30
|
+
data = response.json()
|
|
31
|
+
# Check the enhanced health endpoint format
|
|
32
|
+
assert isinstance(data["healthy"], bool)
|
|
33
|
+
assert "status" in data
|
|
34
|
+
assert "components" in data
|
|
35
|
+
assert "timestamp" in data
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_flet_app_mount(client: TestClient) -> None:
|
|
39
|
+
"""Test that the Flet frontend is mounted at /dashboard."""
|
|
40
|
+
response = client.get("/dashboard", follow_redirects=False)
|
|
41
|
+
# Should serve the Flet app (might be redirect or direct serve)
|
|
42
|
+
assert response.status_code in [200, 301, 302, 307, 308]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.mark.asyncio
|
|
46
|
+
async def test_app_creation() -> None:
|
|
47
|
+
"""Test that the app can be created without errors."""
|
|
48
|
+
app = create_integrated_app()
|
|
49
|
+
assert app is not None
|
|
50
|
+
assert hasattr(app, "router")
|