aegis-stack 0.2.0rc2__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.
- aegis/__init__.py +5 -0
- aegis/__main__.py +51 -0
- aegis/cli/__init__.py +6 -0
- aegis/cli/callbacks.py +114 -0
- aegis/cli/interactive.py +611 -0
- aegis/cli/utils.py +70 -0
- aegis/cli/validators.py +34 -0
- aegis/commands/__init__.py +6 -0
- aegis/commands/add.py +353 -0
- aegis/commands/add_service.py +332 -0
- aegis/commands/components.py +35 -0
- aegis/commands/init.py +370 -0
- aegis/commands/remove.py +227 -0
- aegis/commands/services.py +52 -0
- aegis/commands/update.py +252 -0
- aegis/commands/version.py +12 -0
- aegis/config/__init__.py +1 -0
- aegis/config/shared_files.py +136 -0
- aegis/core/CLAUDE.md +377 -0
- aegis/core/__init__.py +6 -0
- aegis/core/component_files.py +228 -0
- aegis/core/component_utils.py +220 -0
- aegis/core/components.py +127 -0
- aegis/core/copier_manager.py +315 -0
- aegis/core/copier_updater.py +475 -0
- aegis/core/dependency_resolver.py +119 -0
- aegis/core/manual_updater.py +554 -0
- aegis/core/post_gen_tasks.py +547 -0
- aegis/core/service_resolver.py +261 -0
- aegis/core/services.py +157 -0
- aegis/core/template_generator.py +266 -0
- aegis/core/version_compatibility.py +259 -0
- aegis/templates/CLAUDE.md +591 -0
- aegis/templates/cookiecutter-aegis-project/cookiecutter.json +39 -0
- aegis/templates/cookiecutter-aegis-project/hooks/post_gen_project.py +214 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.dockerignore +71 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.env.example.j2 +130 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.gitignore +131 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Dockerfile +53 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Makefile +236 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/README.md.j2 +196 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/alembic.ini.j2 +111 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/env.py.j2 +91 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/script.py.mako +25 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/versions/001_initial_auth.py.j2 +51 -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/ai.py.j2 +700 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/ai_rendering.py +361 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/auth.py.j2 +253 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/health.py.j2 +419 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/load_test.py.j2 +656 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/main.py.j2 +65 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/marko_terminal_renderer.py +489 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/tasks.py.j2 +328 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/{% if cookiecutter.include_scheduler == /"yes/" %}tasks.py{% endif %}" +340 -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/ai/__init__.py +8 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/ai/router.py +329 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/auth/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/auth/router.py +64 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/deps.py +58 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/health.py +163 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/models.py.j2 +280 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/routing.py.j2 +32 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/scheduler.py.j2 +121 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/worker.py.j2 +478 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/hooks.py +144 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/main.py +31 -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 +418 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/database_init.py.j2 +83 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/__init__.py +5 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/__init__.py +27 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/table.py +78 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/text.py +142 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/__init__.py.j2 +47 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/ai_card.py +287 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/auth_card.py +198 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/base_card.py +256 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/card_factory.py +227 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/card_utils.py +333 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/database_card.py +420 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/fastapi_card.py +328 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/flet_card.py +267 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/redis_card.py +322 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/scheduler_card.py +352 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/services_card.py +233 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/worker_card.py +684 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/main.py.j2 +653 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/theme.py +48 -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.j2 +156 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/CLAUDE.md.j2 +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 +97 -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 +55 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/media.py +49 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/system.py +44 -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 +120 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/load_tasks.py +507 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/simple_system_tasks.py +33 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/system_tasks.py +281 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/config.py.j2 +178 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/constants.py +58 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/db.py.j2 +176 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/log.py +92 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/security.py +62 -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 +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/models/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/models/user.py +44 -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/ai/__init__.py +8 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/config.py +130 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/conversation.py +213 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/health.py +96 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/models.py +229 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/providers.py +370 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/service.py +388 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/auth_service.py +41 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/health.py +164 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/user_service.py +83 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/middleware_inspector.py.j2 +223 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/models.py.j2 +70 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/route_inspector.py.j2 +155 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test.py +679 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test_models.py +266 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/__init__.py.j2 +21 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/models.py.j2 +119 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/scheduled_task_manager.py.j2 +273 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/task_monitor.py.j2 +189 -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/backup.py.j2 +119 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/health.py.j2 +1333 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/models.py +243 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/ui.py +52 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto-dark.png +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto-square-backup.png +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto.png +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.dockerignore +71 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.env.example.j2 +64 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.gitignore +131 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/Dockerfile +53 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/Makefile +211 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/README.md.j2 +172 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/docker-compose.yml.j2 +78 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/mkdocs.yml.j2 +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/pyproject.toml.j2 +120 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/uv.lock +1673 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docker-compose.yml.j2 +200 -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 +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md.j2 +621 -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 +131 -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 +93 -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_auth_endpoints.py.j2 +307 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_health_endpoints.py.j2 +262 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_scheduler_endpoints.py.j2 +214 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_worker_endpoints.py.j2 +165 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/cli/test_ai_rendering.py +427 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/cli/test_conversation_memory.py +465 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/components/test_scheduler.py +43 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/conftest.py.j2 +195 -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/ai/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/conftest.py +78 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_health.py +157 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_models.py +164 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_service.py +198 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_auth_integration.py.j2 +528 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_component_integration.py.j2 +387 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_conversation_persistence.py +342 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_health_logic.py.j2 +663 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_models.py +619 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_service.py +603 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_middleware_inspector.py.j2 +248 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_scheduled_task_manager.py.j2 +292 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_system_service.py +98 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_worker_health_registration.py.j2 +257 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/test_core.py +49 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/uv.lock +1673 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/.copier-answers.yml.jinja +21 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/.dockerignore +71 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/.env.example.jinja +130 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/.gitignore +131 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/Dockerfile +53 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/Makefile.jinja +236 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/README.md.jinja +196 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/alembic.ini.jinja +111 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/env.py.jinja +91 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/script.py.mako +25 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/versions/001_initial_auth.py.jinja +51 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/__init__.py.jinja +5 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/__init__.py.jinja +6 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/ai.py.jinja +700 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/ai_rendering.py +360 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/auth.py.jinja +253 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/health.py.jinja +419 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/load_test.py.jinja +656 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/main.py.jinja +65 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/marko_terminal_renderer.py +489 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/tasks.py.jinja +328 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/__init__.py +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/__init__.py +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/ai/__init__.py +8 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/ai/router.py +329 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/auth/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/auth/router.py +64 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/deps.py +58 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/health.py.jinja +163 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/models.py.jinja +280 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/routing.py.jinja +32 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/scheduler.py.jinja +121 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/worker.py.jinja +478 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/hooks.py +144 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/main.py +31 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/middleware/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/middleware/cors.py +20 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/shutdown/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/shutdown/cleanup.py +14 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/component_health.py.jinja +418 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/database_init.py.jinja +83 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/__init__.py +5 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/__init__.py +27 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/table.py +78 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/text.py +142 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/__init__.py.jinja +47 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/ai_card.py +287 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/auth_card.py +198 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/base_card.py +256 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/card_factory.py +227 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/card_utils.py +333 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/database_card.py +420 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/fastapi_card.py +328 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/flet_card.py +267 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/redis_card.py +322 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/scheduler_card.py +352 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/services_card.py +233 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/worker_card.py +684 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/main.py.jinja +653 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/theme.py +48 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/scheduler/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/scheduler/main.py.jinja +156 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/CLAUDE.md.jinja +213 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/__init__.py +6 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/constants.py.jinja +30 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/pools.py +97 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/load_test.py +55 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/media.py +49 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/system.py +44 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/registry.py +139 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/__init__.py +120 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/load_tasks.py +507 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/simple_system_tasks.py +33 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/system_tasks.py +281 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/config.py.jinja +178 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/constants.py +58 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/db.py.jinja +176 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/log.py +92 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/security.py +62 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/scheduler.py.jinja +21 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/webserver.py +39 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/integrations/__init__.py +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/integrations/main.py +61 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/models/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/models/user.py +44 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/py.typed +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/__init__.py +8 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/config.py +130 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/conversation.py +213 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/health.py +96 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/models.py +229 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/providers.py.jinja +370 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/service.py +387 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/auth_service.py +40 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/health.py +162 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/user_service.py +82 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/middleware_inspector.py.jinja +223 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/models.py.jinja +70 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/route_inspector.py.jinja +155 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/load_test.py +678 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/load_test_models.py +265 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/__init__.py.jinja +21 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/models.py.jinja +119 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/scheduled_task_manager.py.jinja +273 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/task_monitor.py.jinja +189 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/shared/__init__.py +15 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/shared/models.py +26 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/__init__.py +52 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/alerts.py +94 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/backup.py.jinja +119 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/health.py.jinja +1333 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/models.py +243 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/ui.py +52 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57223!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57224!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57225!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57533!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57534!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57538!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57897!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57898!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57904!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58315!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58316!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58324!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58837!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58838!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58849!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/.env.example.jinja +64 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/README.md.jinja +172 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/docker-compose.yml.jinja +78 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/mkdocs.yml.jinja +62 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/pyproject.toml.jinja +120 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docker-compose.yml.jinja +200 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/api.md.jinja +191 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/components/scheduler.md +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/components/scheduler.md.jinja +621 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/development.md.jinja +215 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/health.md.jinja +240 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/javascripts/mermaid-config.js +62 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/stylesheets/mermaid.css +95 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/mkdocs.yml.jinja +62 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/pyproject.toml.jinja +131 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/entrypoint.sh +87 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/entrypoint.sh.jinja +93 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/gen_docs.py +16 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_auth_endpoints.py.jinja +307 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_health_endpoints.py.jinja +262 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_scheduler_endpoints.py.jinja +214 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_worker_endpoints.py.jinja +165 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/cli/test_ai_rendering.py +427 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/cli/test_conversation_memory.py +465 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/components/test_scheduler.py +43 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/conftest.py.jinja +195 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/conftest.py +78 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_health.py +157 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_models.py +164 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_service.py +198 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_auth_integration.py.jinja +528 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_component_integration.py.jinja +387 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_conversation_persistence.py +342 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_health_logic.py.jinja +663 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_load_test_models.py +619 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_load_test_service.py +603 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_middleware_inspector.py.jinja +248 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_scheduled_task_manager.py.jinja +292 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_system_service.py +98 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_worker_health_registration.py.jinja +257 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/test_core.py +49 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/uv.lock +1673 -0
- aegis_stack-0.2.0rc2.dist-info/METADATA +165 -0
- aegis_stack-0.2.0rc2.dist-info/RECORD +392 -0
- aegis_stack-0.2.0rc2.dist-info/WHEEL +4 -0
- aegis_stack-0.2.0rc2.dist-info/entry_points.txt +3 -0
- aegis_stack-0.2.0rc2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""Tests for FastAPI middleware introspection service."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from unittest.mock import Mock, MagicMock
|
|
5
|
+
from fastapi import FastAPI
|
|
6
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
7
|
+
|
|
8
|
+
from app.services.backend.middleware_inspector import (
|
|
9
|
+
FastAPIMiddlewareInspector,
|
|
10
|
+
get_fastapi_middleware_metadata,
|
|
11
|
+
)
|
|
12
|
+
from app.services.backend.models import MiddlewareInfo, MiddlewareMetadata
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestFastAPIMiddlewareInspector:
|
|
16
|
+
"""Test cases for FastAPI middleware introspection."""
|
|
17
|
+
|
|
18
|
+
def test_configured_app_middleware_detection(self, app: FastAPI):
|
|
19
|
+
"""Test middleware detection with configured app (includes CORS by default)."""
|
|
20
|
+
inspector = FastAPIMiddlewareInspector(app)
|
|
21
|
+
|
|
22
|
+
metadata = inspector.get_middleware_metadata()
|
|
23
|
+
|
|
24
|
+
assert isinstance(metadata, MiddlewareMetadata)
|
|
25
|
+
# The configured app should have at least CORS middleware
|
|
26
|
+
assert metadata.total_middleware >= 1
|
|
27
|
+
assert metadata.security_count >= 1
|
|
28
|
+
assert "CORSMiddleware" in metadata.security_middleware
|
|
29
|
+
assert metadata.error is None
|
|
30
|
+
assert metadata.fallback is False
|
|
31
|
+
|
|
32
|
+
def test_empty_middleware_stack(self):
|
|
33
|
+
"""Test with FastAPI app that has no middleware."""
|
|
34
|
+
app = FastAPI()
|
|
35
|
+
inspector = FastAPIMiddlewareInspector(app)
|
|
36
|
+
|
|
37
|
+
metadata = inspector.get_middleware_metadata()
|
|
38
|
+
|
|
39
|
+
assert isinstance(metadata, MiddlewareMetadata)
|
|
40
|
+
assert metadata.total_middleware == 0
|
|
41
|
+
assert metadata.security_count == 0
|
|
42
|
+
assert metadata.middleware_stack == []
|
|
43
|
+
assert metadata.security_middleware == []
|
|
44
|
+
assert metadata.error is None
|
|
45
|
+
assert metadata.fallback is False
|
|
46
|
+
|
|
47
|
+
def test_cors_middleware_detection(self):
|
|
48
|
+
"""Test detection and configuration extraction for CORS middleware."""
|
|
49
|
+
app = FastAPI()
|
|
50
|
+
app.add_middleware(
|
|
51
|
+
CORSMiddleware,
|
|
52
|
+
allow_origins=["https://example.com"],
|
|
53
|
+
allow_credentials=True,
|
|
54
|
+
allow_methods=["GET", "POST"],
|
|
55
|
+
allow_headers=["X-Test-Header"],
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
inspector = FastAPIMiddlewareInspector(app)
|
|
59
|
+
metadata = inspector.get_middleware_metadata()
|
|
60
|
+
|
|
61
|
+
assert metadata.total_middleware >= 1
|
|
62
|
+
assert metadata.security_count >= 1
|
|
63
|
+
assert "CORSMiddleware" in metadata.security_middleware
|
|
64
|
+
|
|
65
|
+
# Find the CORS middleware in the stack
|
|
66
|
+
cors_middleware = None
|
|
67
|
+
for mw in metadata.middleware_stack:
|
|
68
|
+
if mw.type == "CORSMiddleware":
|
|
69
|
+
cors_middleware = mw
|
|
70
|
+
break
|
|
71
|
+
|
|
72
|
+
assert cors_middleware is not None
|
|
73
|
+
assert cors_middleware.is_security is True
|
|
74
|
+
assert cors_middleware.config.get("allow_origins") == ["https://example.com"]
|
|
75
|
+
assert cors_middleware.config.get("allow_credentials") is True
|
|
76
|
+
|
|
77
|
+
def test_security_middleware_identification(self):
|
|
78
|
+
"""Test identification of security-related middleware."""
|
|
79
|
+
inspector = FastAPIMiddlewareInspector(FastAPI())
|
|
80
|
+
|
|
81
|
+
# Test security keyword detection
|
|
82
|
+
assert inspector._is_security_middleware(
|
|
83
|
+
"CORSMiddleware", "fastapi.middleware.cors"
|
|
84
|
+
)
|
|
85
|
+
assert inspector._is_security_middleware(
|
|
86
|
+
"AuthMiddleware", "app.middleware.auth"
|
|
87
|
+
)
|
|
88
|
+
assert inspector._is_security_middleware("JWTMiddleware", "app.middleware.jwt")
|
|
89
|
+
assert inspector._is_security_middleware(
|
|
90
|
+
"RateLimitMiddleware", "app.middleware.rate"
|
|
91
|
+
)
|
|
92
|
+
assert inspector._is_security_middleware(
|
|
93
|
+
"SecurityHeadersMiddleware", "app.middleware.security"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Test non-security middleware
|
|
97
|
+
assert not inspector._is_security_middleware(
|
|
98
|
+
"GZipMiddleware", "fastapi.middleware.gzip"
|
|
99
|
+
)
|
|
100
|
+
assert not inspector._is_security_middleware(
|
|
101
|
+
"LoggingMiddleware", "app.middleware.logging"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def test_middleware_order_detection(self):
|
|
105
|
+
"""Test that middleware order is correctly detected."""
|
|
106
|
+
app = FastAPI()
|
|
107
|
+
|
|
108
|
+
# Add middleware in specific order
|
|
109
|
+
# Note: FastAPI adds in reverse order of execution
|
|
110
|
+
app.add_middleware(CORSMiddleware)
|
|
111
|
+
|
|
112
|
+
# Mock a custom middleware for testing
|
|
113
|
+
class CustomMiddleware:
|
|
114
|
+
def __init__(self, app):
|
|
115
|
+
self.app = app
|
|
116
|
+
|
|
117
|
+
async def __call__(self, scope, receive, send):
|
|
118
|
+
return await self.app(scope, receive, send)
|
|
119
|
+
|
|
120
|
+
# We can't easily test the exact ordering without a more complex setup,
|
|
121
|
+
# but we can test that order numbers are assigned
|
|
122
|
+
inspector = FastAPIMiddlewareInspector(app)
|
|
123
|
+
metadata = inspector.get_middleware_metadata()
|
|
124
|
+
|
|
125
|
+
# Verify that middleware have order numbers starting from 0
|
|
126
|
+
for idx, middleware in enumerate(metadata.middleware_stack):
|
|
127
|
+
assert middleware.order == idx
|
|
128
|
+
|
|
129
|
+
def test_middleware_config_extraction(self):
|
|
130
|
+
"""Test extraction of middleware-specific configuration."""
|
|
131
|
+
inspector = FastAPIMiddlewareInspector(FastAPI())
|
|
132
|
+
|
|
133
|
+
# Mock CORS middleware
|
|
134
|
+
cors_mock = Mock()
|
|
135
|
+
cors_mock.allow_origins = ["http://localhost:3000"]
|
|
136
|
+
cors_mock.allow_methods = ["*"]
|
|
137
|
+
cors_mock.allow_headers = ["*"]
|
|
138
|
+
cors_mock.allow_credentials = True
|
|
139
|
+
|
|
140
|
+
config = inspector._extract_middleware_config(cors_mock)
|
|
141
|
+
|
|
142
|
+
assert config["allow_origins"] == ["http://localhost:3000"]
|
|
143
|
+
assert config["allow_methods"] == ["*"]
|
|
144
|
+
assert config["allow_headers"] == ["*"]
|
|
145
|
+
assert config["allow_credentials"] is True
|
|
146
|
+
|
|
147
|
+
def test_middleware_introspection_error_handling(self):
|
|
148
|
+
"""Test error handling in middleware introspection."""
|
|
149
|
+
# Mock an app that raises an exception during introspection
|
|
150
|
+
app_mock = Mock()
|
|
151
|
+
app_mock.app = app_mock # Circular reference to break the loop
|
|
152
|
+
|
|
153
|
+
# Force an exception during middleware traversal
|
|
154
|
+
def side_effect(*args, **kwargs):
|
|
155
|
+
raise ValueError("Test error")
|
|
156
|
+
|
|
157
|
+
app_mock.__getattribute__ = side_effect
|
|
158
|
+
|
|
159
|
+
inspector = FastAPIMiddlewareInspector(app_mock)
|
|
160
|
+
metadata = inspector.get_middleware_metadata()
|
|
161
|
+
|
|
162
|
+
# Should return fallback metadata
|
|
163
|
+
assert metadata.fallback is True
|
|
164
|
+
assert metadata.error is not None
|
|
165
|
+
assert "Mock" in metadata.error # Error is actually about Mock object iteration
|
|
166
|
+
assert metadata.total_middleware == 0
|
|
167
|
+
|
|
168
|
+
def test_extract_middleware_info_error_handling(self):
|
|
169
|
+
"""Test error handling in individual middleware info extraction."""
|
|
170
|
+
inspector = FastAPIMiddlewareInspector(FastAPI())
|
|
171
|
+
|
|
172
|
+
# Mock middleware that raises an exception by overriding __module__ access
|
|
173
|
+
class BadMiddleware:
|
|
174
|
+
__name__ = "BadMiddleware"
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def __module__(self) -> str:
|
|
178
|
+
raise ZeroDivisionError("Test exception")
|
|
179
|
+
|
|
180
|
+
bad_middleware = BadMiddleware()
|
|
181
|
+
|
|
182
|
+
result = inspector._extract_middleware_info(bad_middleware, 0)
|
|
183
|
+
|
|
184
|
+
# Should return None on error
|
|
185
|
+
assert result is None
|
|
186
|
+
|
|
187
|
+
def test_get_fastapi_middleware_metadata_convenience_function(self):
|
|
188
|
+
"""Test the convenience function for getting middleware metadata."""
|
|
189
|
+
app = FastAPI()
|
|
190
|
+
app.add_middleware(CORSMiddleware)
|
|
191
|
+
|
|
192
|
+
metadata = get_fastapi_middleware_metadata(app)
|
|
193
|
+
|
|
194
|
+
assert isinstance(metadata, MiddlewareMetadata)
|
|
195
|
+
assert metadata.total_middleware >= 1
|
|
196
|
+
|
|
197
|
+
def test_middleware_metadata_model_dump(self):
|
|
198
|
+
"""Test that middleware metadata can be dumped for ComponentStatus."""
|
|
199
|
+
app = FastAPI()
|
|
200
|
+
app.add_middleware(CORSMiddleware)
|
|
201
|
+
|
|
202
|
+
metadata = get_fastapi_middleware_metadata(app)
|
|
203
|
+
dumped = metadata.model_dump_for_metadata()
|
|
204
|
+
|
|
205
|
+
assert isinstance(dumped, dict)
|
|
206
|
+
assert "middleware_stack" in dumped
|
|
207
|
+
assert "total_middleware" in dumped
|
|
208
|
+
assert "security_count" in dumped
|
|
209
|
+
assert "security_middleware" in dumped
|
|
210
|
+
|
|
211
|
+
def test_middleware_info_model_creation(self):
|
|
212
|
+
"""Test MiddlewareInfo model creation and validation."""
|
|
213
|
+
middleware_info = MiddlewareInfo(
|
|
214
|
+
type="CORSMiddleware",
|
|
215
|
+
module="fastapi.middleware.cors",
|
|
216
|
+
order=0,
|
|
217
|
+
config={"allow_origins": ["*"]},
|
|
218
|
+
is_security=True,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
assert middleware_info.type == "CORSMiddleware"
|
|
222
|
+
assert middleware_info.module == "fastapi.middleware.cors"
|
|
223
|
+
assert middleware_info.order == 0
|
|
224
|
+
assert middleware_info.config == {"allow_origins": ["*"]}
|
|
225
|
+
assert middleware_info.is_security is True
|
|
226
|
+
|
|
227
|
+
def test_middleware_metadata_model_creation(self):
|
|
228
|
+
"""Test MiddlewareMetadata model creation and validation."""
|
|
229
|
+
middleware_info = MiddlewareInfo(
|
|
230
|
+
type="CORSMiddleware",
|
|
231
|
+
module="fastapi.middleware.cors",
|
|
232
|
+
order=0,
|
|
233
|
+
is_security=True,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
metadata = MiddlewareMetadata(
|
|
237
|
+
middleware_stack=[middleware_info],
|
|
238
|
+
total_middleware=1,
|
|
239
|
+
security_middleware=["CORSMiddleware"],
|
|
240
|
+
security_count=1,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
assert len(metadata.middleware_stack) == 1
|
|
244
|
+
assert metadata.total_middleware == 1
|
|
245
|
+
assert metadata.security_middleware == ["CORSMiddleware"]
|
|
246
|
+
assert metadata.security_count == 1
|
|
247
|
+
assert metadata.error is None
|
|
248
|
+
assert metadata.fallback is False
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
{%- if scheduler_backend != "memory" %}
|
|
2
|
+
"""
|
|
3
|
+
Tests for ScheduledTaskManager service.
|
|
4
|
+
|
|
5
|
+
Tests the service layer for scheduled task management, including database
|
|
6
|
+
operations and task data transformations.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import pickle
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from unittest.mock import AsyncMock, MagicMock, patch
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
from app.services.scheduler.scheduled_task_manager import ScheduledTaskManager
|
|
17
|
+
from app.services.scheduler.models import APSchedulerJob, ScheduledTask, TaskStatistics
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MockTrigger:
|
|
21
|
+
"""Simple mock trigger that can be pickled for testing."""
|
|
22
|
+
|
|
23
|
+
def __init__(self):
|
|
24
|
+
self.__class__.__name__ = "IntervalTrigger"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TestScheduledTaskManager:
|
|
28
|
+
"""Test the ScheduledTaskManager service layer."""
|
|
29
|
+
|
|
30
|
+
@pytest.fixture
|
|
31
|
+
def manager(self) -> ScheduledTaskManager:
|
|
32
|
+
"""Create a ScheduledTaskManager instance for testing."""
|
|
33
|
+
return ScheduledTaskManager()
|
|
34
|
+
|
|
35
|
+
@pytest.fixture
|
|
36
|
+
def mock_job_data(self) -> dict[str, Any]:
|
|
37
|
+
"""Mock job data as stored by APScheduler."""
|
|
38
|
+
return {
|
|
39
|
+
"name": "Test Job",
|
|
40
|
+
"func": "test.module.function",
|
|
41
|
+
"trigger": MockTrigger(),
|
|
42
|
+
"max_instances": 1,
|
|
43
|
+
"coalesce": True,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@pytest.fixture
|
|
47
|
+
def mock_apscheduler_job(self, mock_job_data: dict[str, Any]) -> APSchedulerJob:
|
|
48
|
+
"""Create a mock APSchedulerJob for testing."""
|
|
49
|
+
# Create job state as APScheduler would pickle it
|
|
50
|
+
job_state = pickle.dumps(mock_job_data)
|
|
51
|
+
|
|
52
|
+
return APSchedulerJob(
|
|
53
|
+
id="test_job_id",
|
|
54
|
+
next_run_time=datetime.now().timestamp(),
|
|
55
|
+
job_state=job_state
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@pytest.mark.asyncio
|
|
59
|
+
async def test_has_persistence_table_exists(
|
|
60
|
+
self, manager: ScheduledTaskManager
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Test has_persistence returns True when apscheduler_jobs table exists."""
|
|
63
|
+
with patch(
|
|
64
|
+
"app.services.scheduler.scheduled_task_manager.async_engine"
|
|
65
|
+
) as mock_engine:
|
|
66
|
+
mock_conn = AsyncMock()
|
|
67
|
+
mock_conn.run_sync.return_value = ["apscheduler_jobs", "other_table"]
|
|
68
|
+
|
|
69
|
+
mock_engine.begin.return_value.__aenter__.return_value = mock_conn
|
|
70
|
+
|
|
71
|
+
result = await manager.has_persistence()
|
|
72
|
+
assert result is True
|
|
73
|
+
|
|
74
|
+
@pytest.mark.asyncio
|
|
75
|
+
async def test_has_persistence_table_missing(
|
|
76
|
+
self, manager: ScheduledTaskManager
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Test has_persistence returns False when apscheduler_jobs table missing."""
|
|
79
|
+
with patch(
|
|
80
|
+
"app.services.scheduler.scheduled_task_manager.async_engine"
|
|
81
|
+
) as mock_engine:
|
|
82
|
+
mock_conn = AsyncMock()
|
|
83
|
+
mock_conn.run_sync.return_value = ["other_table"]
|
|
84
|
+
|
|
85
|
+
mock_engine.begin.return_value.__aenter__.return_value = mock_conn
|
|
86
|
+
|
|
87
|
+
result = await manager.has_persistence()
|
|
88
|
+
assert result is False
|
|
89
|
+
|
|
90
|
+
@pytest.mark.asyncio
|
|
91
|
+
async def test_has_persistence_database_error(
|
|
92
|
+
self, manager: ScheduledTaskManager
|
|
93
|
+
) -> None:
|
|
94
|
+
"""Test has_persistence handles database errors gracefully."""
|
|
95
|
+
with patch(
|
|
96
|
+
"app.services.scheduler.scheduled_task_manager.async_engine"
|
|
97
|
+
) as mock_engine:
|
|
98
|
+
mock_engine.begin.side_effect = Exception("Database error")
|
|
99
|
+
|
|
100
|
+
result = await manager.has_persistence()
|
|
101
|
+
assert result is False
|
|
102
|
+
|
|
103
|
+
@pytest.mark.asyncio
|
|
104
|
+
async def test_list_tasks_no_persistence(
|
|
105
|
+
self, manager: ScheduledTaskManager
|
|
106
|
+
) -> None:
|
|
107
|
+
"""Test list_tasks raises RuntimeError when persistence not available."""
|
|
108
|
+
with patch.object(manager, "has_persistence", return_value=False):
|
|
109
|
+
with pytest.raises(RuntimeError, match="persistence"):
|
|
110
|
+
await manager.list_tasks()
|
|
111
|
+
|
|
112
|
+
@pytest.mark.asyncio
|
|
113
|
+
async def test_list_tasks_with_jobs(
|
|
114
|
+
self, manager: ScheduledTaskManager, mock_apscheduler_job: APSchedulerJob
|
|
115
|
+
) -> None:
|
|
116
|
+
"""Test list_tasks returns properly formatted tasks."""
|
|
117
|
+
with patch.object(manager, "has_persistence", return_value=True), \
|
|
118
|
+
patch(
|
|
119
|
+
"app.services.scheduler.scheduled_task_manager.get_async_session"
|
|
120
|
+
) as mock_session:
|
|
121
|
+
|
|
122
|
+
# Mock session and query result
|
|
123
|
+
mock_session_instance = AsyncMock()
|
|
124
|
+
mock_session.return_value.__aenter__.return_value = mock_session_instance
|
|
125
|
+
|
|
126
|
+
mock_result = MagicMock()
|
|
127
|
+
mock_result.all.return_value = [mock_apscheduler_job]
|
|
128
|
+
mock_session_instance.exec.return_value = mock_result
|
|
129
|
+
|
|
130
|
+
tasks = await manager.list_tasks()
|
|
131
|
+
|
|
132
|
+
assert len(tasks) == 1
|
|
133
|
+
task = tasks[0]
|
|
134
|
+
assert isinstance(task, ScheduledTask)
|
|
135
|
+
assert task.job_id == "test_job_id"
|
|
136
|
+
assert task.name == "Test Job"
|
|
137
|
+
assert task.status == "active" # Has next_run_time
|
|
138
|
+
|
|
139
|
+
@pytest.mark.asyncio
|
|
140
|
+
async def test_list_tasks_empty_database(
|
|
141
|
+
self, manager: ScheduledTaskManager
|
|
142
|
+
) -> None:
|
|
143
|
+
"""Test list_tasks returns empty list when no jobs in database."""
|
|
144
|
+
with patch.object(manager, "has_persistence", return_value=True), \
|
|
145
|
+
patch(
|
|
146
|
+
"app.services.scheduler.scheduled_task_manager.get_async_session"
|
|
147
|
+
) as mock_session:
|
|
148
|
+
|
|
149
|
+
mock_session_instance = AsyncMock()
|
|
150
|
+
mock_session.return_value.__aenter__.return_value = mock_session_instance
|
|
151
|
+
|
|
152
|
+
mock_result = MagicMock()
|
|
153
|
+
mock_result.all.return_value = []
|
|
154
|
+
mock_session_instance.exec.return_value = mock_result
|
|
155
|
+
|
|
156
|
+
tasks = await manager.list_tasks()
|
|
157
|
+
assert len(tasks) == 0
|
|
158
|
+
|
|
159
|
+
@pytest.mark.asyncio
|
|
160
|
+
async def test_get_task_found(
|
|
161
|
+
self, manager: ScheduledTaskManager, mock_apscheduler_job: APSchedulerJob
|
|
162
|
+
) -> None:
|
|
163
|
+
"""Test get_task returns task when found."""
|
|
164
|
+
with patch(
|
|
165
|
+
"app.services.scheduler.scheduled_task_manager.get_async_session"
|
|
166
|
+
) as mock_session:
|
|
167
|
+
mock_session_instance = AsyncMock()
|
|
168
|
+
mock_session.return_value.__aenter__.return_value = mock_session_instance
|
|
169
|
+
|
|
170
|
+
mock_result = MagicMock()
|
|
171
|
+
mock_result.first.return_value = mock_apscheduler_job
|
|
172
|
+
mock_session_instance.exec.return_value = mock_result
|
|
173
|
+
|
|
174
|
+
task = await manager.get_task("test_job_id")
|
|
175
|
+
|
|
176
|
+
assert task is not None
|
|
177
|
+
assert isinstance(task, ScheduledTask)
|
|
178
|
+
assert task.job_id == "test_job_id"
|
|
179
|
+
assert task.name == "Test Job"
|
|
180
|
+
|
|
181
|
+
@pytest.mark.asyncio
|
|
182
|
+
async def test_get_task_not_found(
|
|
183
|
+
self, manager: ScheduledTaskManager
|
|
184
|
+
) -> None:
|
|
185
|
+
"""Test get_task returns None when task not found."""
|
|
186
|
+
with patch(
|
|
187
|
+
"app.services.scheduler.scheduled_task_manager.get_async_session"
|
|
188
|
+
) as mock_session:
|
|
189
|
+
mock_session_instance = AsyncMock()
|
|
190
|
+
mock_session.return_value.__aenter__.return_value = mock_session_instance
|
|
191
|
+
|
|
192
|
+
mock_result = MagicMock()
|
|
193
|
+
mock_result.first.return_value = None
|
|
194
|
+
mock_session_instance.exec.return_value = mock_result
|
|
195
|
+
|
|
196
|
+
task = await manager.get_task("non_existent_job")
|
|
197
|
+
assert task is None
|
|
198
|
+
|
|
199
|
+
@pytest.mark.asyncio
|
|
200
|
+
async def test_get_statistics(self, manager: ScheduledTaskManager) -> None:
|
|
201
|
+
"""Test get_statistics returns proper TaskStatistics."""
|
|
202
|
+
# Mock list_tasks to return test data
|
|
203
|
+
mock_tasks = [
|
|
204
|
+
ScheduledTask(
|
|
205
|
+
job_id="job1", name="Job 1", function="test.func1", schedule="Every 1m",
|
|
206
|
+
trigger_type="interval", status="active", max_instances=1, coalesce=True
|
|
207
|
+
),
|
|
208
|
+
ScheduledTask(
|
|
209
|
+
job_id="job2", name="Job 2", function="test.func2", schedule="Every 5m",
|
|
210
|
+
trigger_type="interval", status="paused", max_instances=1, coalesce=True
|
|
211
|
+
),
|
|
212
|
+
ScheduledTask(
|
|
213
|
+
job_id="job3", name="Job 3", function="test.func3", schedule="Daily",
|
|
214
|
+
trigger_type="cron", status="active", max_instances=1, coalesce=True
|
|
215
|
+
),
|
|
216
|
+
]
|
|
217
|
+
|
|
218
|
+
with patch.object(manager, "list_tasks", return_value=mock_tasks):
|
|
219
|
+
stats = await manager.get_statistics()
|
|
220
|
+
|
|
221
|
+
assert isinstance(stats, TaskStatistics)
|
|
222
|
+
assert stats.total_tasks == 3
|
|
223
|
+
assert stats.active_tasks == 2
|
|
224
|
+
assert stats.paused_tasks == 1
|
|
225
|
+
|
|
226
|
+
def test_format_trigger_interval(self, manager: ScheduledTaskManager) -> None:
|
|
227
|
+
"""Test _format_trigger handles interval triggers correctly."""
|
|
228
|
+
# Mock IntervalTrigger
|
|
229
|
+
mock_trigger = MagicMock()
|
|
230
|
+
mock_trigger.__class__.__name__ = "IntervalTrigger"
|
|
231
|
+
|
|
232
|
+
# Test seconds
|
|
233
|
+
mock_trigger.interval.total_seconds.return_value = 30
|
|
234
|
+
result = manager._format_trigger(mock_trigger)
|
|
235
|
+
assert result == "Every 30s"
|
|
236
|
+
|
|
237
|
+
# Test minutes
|
|
238
|
+
mock_trigger.interval.total_seconds.return_value = 300 # 5 minutes
|
|
239
|
+
result = manager._format_trigger(mock_trigger)
|
|
240
|
+
assert result == "Every 5m"
|
|
241
|
+
|
|
242
|
+
# Test hours
|
|
243
|
+
mock_trigger.interval.total_seconds.return_value = 7200 # 2 hours
|
|
244
|
+
result = manager._format_trigger(mock_trigger)
|
|
245
|
+
assert result == "Every 2h"
|
|
246
|
+
|
|
247
|
+
def test_format_trigger_cron(self, manager: ScheduledTaskManager) -> None:
|
|
248
|
+
"""Test _format_trigger handles cron triggers correctly."""
|
|
249
|
+
mock_trigger = MagicMock()
|
|
250
|
+
mock_trigger.__class__.__name__ = "CronTrigger"
|
|
251
|
+
|
|
252
|
+
# Mock fields
|
|
253
|
+
mock_field = MagicMock()
|
|
254
|
+
mock_field.name = "hour"
|
|
255
|
+
mock_field.__str__ = MagicMock(return_value="2")
|
|
256
|
+
mock_trigger.fields = [mock_field]
|
|
257
|
+
|
|
258
|
+
result = manager._format_trigger(mock_trigger)
|
|
259
|
+
assert result == "Cron: hour=2"
|
|
260
|
+
|
|
261
|
+
def test_format_trigger_unknown(self, manager: ScheduledTaskManager) -> None:
|
|
262
|
+
"""Test _format_trigger handles unknown triggers."""
|
|
263
|
+
result = manager._format_trigger(None)
|
|
264
|
+
assert result == "Unknown"
|
|
265
|
+
|
|
266
|
+
mock_trigger = MagicMock()
|
|
267
|
+
mock_trigger.__class__.__name__ = "UnknownTrigger"
|
|
268
|
+
result = manager._format_trigger(mock_trigger)
|
|
269
|
+
assert result == "Unknown"
|
|
270
|
+
|
|
271
|
+
def test_get_trigger_type(self, manager: ScheduledTaskManager) -> None:
|
|
272
|
+
"""Test _get_trigger_type extracts trigger types correctly."""
|
|
273
|
+
# Test interval
|
|
274
|
+
mock_trigger = MagicMock()
|
|
275
|
+
mock_trigger.__class__.__name__ = "IntervalTrigger"
|
|
276
|
+
result = manager._get_trigger_type(mock_trigger)
|
|
277
|
+
assert result == "interval"
|
|
278
|
+
|
|
279
|
+
# Test cron
|
|
280
|
+
mock_trigger.__class__.__name__ = "CronTrigger"
|
|
281
|
+
result = manager._get_trigger_type(mock_trigger)
|
|
282
|
+
assert result == "cron"
|
|
283
|
+
|
|
284
|
+
# Test date
|
|
285
|
+
mock_trigger.__class__.__name__ = "DateTrigger"
|
|
286
|
+
result = manager._get_trigger_type(mock_trigger)
|
|
287
|
+
assert result == "date"
|
|
288
|
+
|
|
289
|
+
# Test unknown
|
|
290
|
+
result = manager._get_trigger_type(None)
|
|
291
|
+
assert result == "unknown"
|
|
292
|
+
{%- endif %}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Test system monitoring functions."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from app.services.system import (
|
|
5
|
+
ComponentStatus,
|
|
6
|
+
get_system_status,
|
|
7
|
+
is_system_healthy,
|
|
8
|
+
register_health_check,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestSystemService:
|
|
13
|
+
"""Test the system monitoring functions."""
|
|
14
|
+
|
|
15
|
+
@pytest.mark.asyncio
|
|
16
|
+
async def test_component_status_creation(self) -> None:
|
|
17
|
+
"""Test component status Pydantic model."""
|
|
18
|
+
status = ComponentStatus(
|
|
19
|
+
name="test_component",
|
|
20
|
+
message="All good",
|
|
21
|
+
response_time_ms=100.0,
|
|
22
|
+
metadata={"version": "1.0"},
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
assert status.name == "test_component"
|
|
26
|
+
assert status.healthy is True
|
|
27
|
+
assert status.message == "All good"
|
|
28
|
+
assert status.response_time_ms == 100.0
|
|
29
|
+
assert status.metadata == {"version": "1.0"}
|
|
30
|
+
|
|
31
|
+
@pytest.mark.asyncio
|
|
32
|
+
async def test_system_status_properties(self) -> None:
|
|
33
|
+
"""Test system status Pydantic model properties."""
|
|
34
|
+
status = await get_system_status()
|
|
35
|
+
|
|
36
|
+
assert isinstance(status.overall_healthy, bool)
|
|
37
|
+
assert len(status.components) >= 1
|
|
38
|
+
assert isinstance(status.healthy_components, list)
|
|
39
|
+
assert isinstance(status.unhealthy_components, list)
|
|
40
|
+
assert isinstance(status.health_percentage, float)
|
|
41
|
+
|
|
42
|
+
@pytest.mark.asyncio
|
|
43
|
+
async def test_system_health_checks(self) -> None:
|
|
44
|
+
"""Test basic health checks functionality."""
|
|
45
|
+
status = await get_system_status()
|
|
46
|
+
|
|
47
|
+
# Test that we get valid system information
|
|
48
|
+
assert hasattr(status, "components")
|
|
49
|
+
assert hasattr(status, "overall_healthy")
|
|
50
|
+
assert hasattr(status, "timestamp")
|
|
51
|
+
assert hasattr(status, "system_info")
|
|
52
|
+
|
|
53
|
+
# Verify components exist (at least core system checks)
|
|
54
|
+
assert len(status.components) > 0
|
|
55
|
+
|
|
56
|
+
# Check that each component has required fields
|
|
57
|
+
for component_name, component_status in status.components.items():
|
|
58
|
+
assert isinstance(component_name, str)
|
|
59
|
+
assert isinstance(component_status.healthy, bool)
|
|
60
|
+
assert isinstance(component_status.message, str)
|
|
61
|
+
assert isinstance(component_status.name, str)
|
|
62
|
+
|
|
63
|
+
@pytest.mark.asyncio
|
|
64
|
+
async def test_is_system_healthy(self) -> None:
|
|
65
|
+
"""Test quick health check function."""
|
|
66
|
+
healthy = await is_system_healthy()
|
|
67
|
+
assert isinstance(healthy, bool)
|
|
68
|
+
|
|
69
|
+
@pytest.mark.asyncio
|
|
70
|
+
async def test_custom_health_check_registration(self) -> None:
|
|
71
|
+
"""Test custom health check registration."""
|
|
72
|
+
|
|
73
|
+
async def custom_check() -> ComponentStatus:
|
|
74
|
+
return ComponentStatus(
|
|
75
|
+
name="custom_test",
|
|
76
|
+
message="Custom check passed",
|
|
77
|
+
response_time_ms=None,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Register custom check
|
|
81
|
+
register_health_check("custom_test", custom_check)
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
# Get status and verify custom check is included under aegis component
|
|
85
|
+
status = await get_system_status()
|
|
86
|
+
assert "aegis" in status.components
|
|
87
|
+
aegis_component = status.components["aegis"]
|
|
88
|
+
assert "components" in aegis_component.sub_components
|
|
89
|
+
components_group = aegis_component.sub_components["components"]
|
|
90
|
+
assert "custom_test" in components_group.sub_components
|
|
91
|
+
assert components_group.sub_components["custom_test"].name == "custom_test"
|
|
92
|
+
assert components_group.sub_components["custom_test"].healthy is True
|
|
93
|
+
finally:
|
|
94
|
+
# Clean up the custom health check registration
|
|
95
|
+
from app.services.system.health import _health_checks
|
|
96
|
+
|
|
97
|
+
if "custom_test" in _health_checks:
|
|
98
|
+
del _health_checks["custom_test"]
|