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
aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_health_endpoints.py.jinja
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for health API endpoints.
|
|
3
|
+
|
|
4
|
+
These tests focus on the HTTP endpoints that CLI health commands call,
|
|
5
|
+
ensuring API responses match expected CLI input format and handle errors correctly.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from collections.abc import AsyncIterator
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
from httpx import AsyncClient, ASGITransport
|
|
12
|
+
|
|
13
|
+
from app.integrations.main import create_integrated_app
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestHealthEndpoints:
|
|
17
|
+
"""Test health API endpoints with various component states."""
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
async def async_client(self) -> AsyncIterator[AsyncClient]:
|
|
21
|
+
"""Async HTTP client for testing."""
|
|
22
|
+
app = create_integrated_app()
|
|
23
|
+
|
|
24
|
+
# Manually trigger startup for health check registration
|
|
25
|
+
from app.components.backend.hooks import backend_hooks
|
|
26
|
+
await backend_hooks.discover_lifespan_hooks()
|
|
27
|
+
await backend_hooks.execute_startup_hooks()
|
|
28
|
+
|
|
29
|
+
transport = ASGITransport(app=app)
|
|
30
|
+
async with AsyncClient(
|
|
31
|
+
transport=transport, base_url="http://test"
|
|
32
|
+
) as client:
|
|
33
|
+
yield client
|
|
34
|
+
|
|
35
|
+
# Clean up after test
|
|
36
|
+
await backend_hooks.execute_shutdown_hooks()
|
|
37
|
+
|
|
38
|
+
@pytest.mark.asyncio
|
|
39
|
+
async def test_basic_health_endpoint_accessible(
|
|
40
|
+
self, async_client: AsyncClient
|
|
41
|
+
) -> None:
|
|
42
|
+
"""Test /health/ endpoint is accessible."""
|
|
43
|
+
response = await async_client.get("/health/")
|
|
44
|
+
|
|
45
|
+
# Should return a valid response (200 for healthy, 503 for unhealthy)
|
|
46
|
+
assert response.status_code in [200, 503]
|
|
47
|
+
data = response.json()
|
|
48
|
+
|
|
49
|
+
# Verify response structure matches HealthResponse model
|
|
50
|
+
assert "healthy" in data
|
|
51
|
+
assert "status" in data
|
|
52
|
+
assert "components" in data
|
|
53
|
+
assert "timestamp" in data
|
|
54
|
+
|
|
55
|
+
# Verify components are included
|
|
56
|
+
assert isinstance(data["components"], dict)
|
|
57
|
+
|
|
58
|
+
@pytest.mark.asyncio
|
|
59
|
+
async def test_detailed_health_endpoint_accessible(
|
|
60
|
+
self, async_client: AsyncClient
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Test /health/detailed endpoint is accessible."""
|
|
63
|
+
response = await async_client.get("/health/detailed")
|
|
64
|
+
|
|
65
|
+
# Should return a valid response
|
|
66
|
+
assert response.status_code in [200, 503]
|
|
67
|
+
data = response.json()
|
|
68
|
+
|
|
69
|
+
if response.status_code == 200:
|
|
70
|
+
# Verify response structure matches DetailedHealthResponse model
|
|
71
|
+
assert "healthy" in data
|
|
72
|
+
assert "status" in data
|
|
73
|
+
assert "service" in data
|
|
74
|
+
assert "version" in data
|
|
75
|
+
assert "components" in data
|
|
76
|
+
assert "system_info" in data
|
|
77
|
+
assert "timestamp" in data
|
|
78
|
+
assert "healthy_components" in data
|
|
79
|
+
assert "unhealthy_components" in data
|
|
80
|
+
assert "health_percentage" in data
|
|
81
|
+
else:
|
|
82
|
+
# For 503 responses, check error structure
|
|
83
|
+
assert "detail" in data
|
|
84
|
+
|
|
85
|
+
@pytest.mark.asyncio
|
|
86
|
+
async def test_health_endpoints_json_format(
|
|
87
|
+
self, async_client: AsyncClient
|
|
88
|
+
) -> None:
|
|
89
|
+
"""Test that health endpoints return valid JSON for CLI consumption."""
|
|
90
|
+
|
|
91
|
+
# Test both endpoints
|
|
92
|
+
endpoints = ["/health/", "/health/detailed"]
|
|
93
|
+
|
|
94
|
+
for endpoint in endpoints:
|
|
95
|
+
response = await async_client.get(endpoint)
|
|
96
|
+
assert response.status_code in [200, 503]
|
|
97
|
+
|
|
98
|
+
# Should be valid JSON
|
|
99
|
+
data = response.json()
|
|
100
|
+
assert isinstance(data, dict)
|
|
101
|
+
|
|
102
|
+
# Should have basic health information
|
|
103
|
+
if response.status_code == 200:
|
|
104
|
+
assert isinstance(data.get("healthy"), bool)
|
|
105
|
+
assert isinstance(data.get("components"), dict)
|
|
106
|
+
elif response.status_code == 503:
|
|
107
|
+
# Error response should have detail
|
|
108
|
+
assert "detail" in data
|
|
109
|
+
|
|
110
|
+
@pytest.mark.asyncio
|
|
111
|
+
async def test_health_endpoint_component_structure(
|
|
112
|
+
self, async_client: AsyncClient
|
|
113
|
+
) -> None:
|
|
114
|
+
"""Test that component structure is suitable for CLI tree display."""
|
|
115
|
+
|
|
116
|
+
response = await async_client.get("/health/detailed")
|
|
117
|
+
assert response.status_code in [200, 503]
|
|
118
|
+
|
|
119
|
+
data = response.json()
|
|
120
|
+
|
|
121
|
+
if response.status_code == 200:
|
|
122
|
+
components = data["components"]
|
|
123
|
+
|
|
124
|
+
# Should have aegis root component
|
|
125
|
+
assert "aegis" in components
|
|
126
|
+
aegis_component = components["aegis"]
|
|
127
|
+
|
|
128
|
+
# Aegis component should have required fields for CLI display
|
|
129
|
+
assert "name" in aegis_component
|
|
130
|
+
assert "healthy" in aegis_component
|
|
131
|
+
assert "message" in aegis_component
|
|
132
|
+
|
|
133
|
+
# Should have sub-components for tree structure
|
|
134
|
+
if "sub_components" in aegis_component:
|
|
135
|
+
sub_components = aegis_component["sub_components"]
|
|
136
|
+
assert isinstance(sub_components, dict)
|
|
137
|
+
|
|
138
|
+
# Each sub-component should have required fields
|
|
139
|
+
for comp_name, comp_data in sub_components.items():
|
|
140
|
+
assert "name" in comp_data
|
|
141
|
+
assert "healthy" in comp_data
|
|
142
|
+
assert "message" in comp_data
|
|
143
|
+
|
|
144
|
+
{%- if include_worker %}
|
|
145
|
+
|
|
146
|
+
@pytest.mark.asyncio
|
|
147
|
+
async def test_worker_component_appears_in_health_response(
|
|
148
|
+
self, async_client: AsyncClient
|
|
149
|
+
) -> None:
|
|
150
|
+
"""Test that worker component appears in health responses when included."""
|
|
151
|
+
|
|
152
|
+
response = await async_client.get("/health/detailed")
|
|
153
|
+
assert response.status_code in [200, 503]
|
|
154
|
+
|
|
155
|
+
data = response.json()
|
|
156
|
+
|
|
157
|
+
if response.status_code == 200:
|
|
158
|
+
components = data["components"]
|
|
159
|
+
|
|
160
|
+
# Should have aegis root component
|
|
161
|
+
assert "aegis" in components
|
|
162
|
+
aegis_component = components["aegis"]
|
|
163
|
+
|
|
164
|
+
if "sub_components" in aegis_component:
|
|
165
|
+
sub_components = aegis_component["sub_components"]
|
|
166
|
+
|
|
167
|
+
# Check if using grouped structure
|
|
168
|
+
if "components" in sub_components:
|
|
169
|
+
components_group = sub_components["components"]
|
|
170
|
+
if "sub_components" in components_group:
|
|
171
|
+
worker_component = components_group["sub_components"].get(
|
|
172
|
+
"worker"
|
|
173
|
+
)
|
|
174
|
+
else:
|
|
175
|
+
# Legacy direct structure
|
|
176
|
+
worker_component = sub_components.get("worker")
|
|
177
|
+
|
|
178
|
+
available_components = (
|
|
179
|
+
list(sub_components.keys())
|
|
180
|
+
if "components" not in sub_components
|
|
181
|
+
else list(components_group.get("sub_components", {}).keys())
|
|
182
|
+
)
|
|
183
|
+
assert worker_component is not None, (
|
|
184
|
+
f"Worker component missing from health response. "
|
|
185
|
+
f"Available: {available_components}"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Worker component should have required structure
|
|
189
|
+
assert "name" in worker_component
|
|
190
|
+
assert worker_component["name"] == "worker"
|
|
191
|
+
assert "healthy" in worker_component
|
|
192
|
+
assert "message" in worker_component
|
|
193
|
+
assert isinstance(worker_component["healthy"], bool)
|
|
194
|
+
|
|
195
|
+
# Worker should have queue sub-components
|
|
196
|
+
if "sub_components" in worker_component:
|
|
197
|
+
worker_sub_components = worker_component["sub_components"]
|
|
198
|
+
|
|
199
|
+
# Should have queues component
|
|
200
|
+
if "queues" in worker_sub_components:
|
|
201
|
+
queues_component = worker_sub_components["queues"]
|
|
202
|
+
assert "name" in queues_component
|
|
203
|
+
assert "healthy" in queues_component
|
|
204
|
+
assert "message" in queues_component
|
|
205
|
+
|
|
206
|
+
# Check for individual queue health if available
|
|
207
|
+
if "sub_components" in queues_component:
|
|
208
|
+
queue_sub_components = queues_component["sub_components"]
|
|
209
|
+
|
|
210
|
+
# Should have system and load_test queues
|
|
211
|
+
for queue_name in ["system", "load_test"]:
|
|
212
|
+
if queue_name in queue_sub_components:
|
|
213
|
+
queue_comp = queue_sub_components[queue_name]
|
|
214
|
+
assert "name" in queue_comp
|
|
215
|
+
assert "healthy" in queue_comp
|
|
216
|
+
assert "message" in queue_comp
|
|
217
|
+
|
|
218
|
+
# Verify queue metadata exists
|
|
219
|
+
if "metadata" in queue_comp:
|
|
220
|
+
metadata = queue_comp["metadata"]
|
|
221
|
+
assert "queue_type" in metadata
|
|
222
|
+
assert metadata["queue_type"] == queue_name
|
|
223
|
+
|
|
224
|
+
@pytest.mark.asyncio
|
|
225
|
+
async def test_basic_health_includes_worker_in_components(
|
|
226
|
+
self, async_client: AsyncClient
|
|
227
|
+
) -> None:
|
|
228
|
+
"""Test that basic health endpoint includes worker in components dict."""
|
|
229
|
+
|
|
230
|
+
response = await async_client.get("/health/")
|
|
231
|
+
assert response.status_code in [200, 503]
|
|
232
|
+
|
|
233
|
+
data = response.json()
|
|
234
|
+
|
|
235
|
+
if response.status_code == 200:
|
|
236
|
+
components = data["components"]
|
|
237
|
+
|
|
238
|
+
# Should have aegis root component with worker
|
|
239
|
+
assert "aegis" in components
|
|
240
|
+
aegis_component = components["aegis"]
|
|
241
|
+
|
|
242
|
+
if "sub_components" in aegis_component:
|
|
243
|
+
sub_components = aegis_component["sub_components"]
|
|
244
|
+
|
|
245
|
+
# Check if using grouped structure
|
|
246
|
+
if "components" in sub_components:
|
|
247
|
+
components_group = sub_components["components"]
|
|
248
|
+
if "sub_components" in components_group:
|
|
249
|
+
worker_component = components_group["sub_components"].get(
|
|
250
|
+
"worker"
|
|
251
|
+
)
|
|
252
|
+
else:
|
|
253
|
+
# Legacy direct structure
|
|
254
|
+
worker_component = sub_components.get("worker")
|
|
255
|
+
|
|
256
|
+
assert worker_component is not None, (
|
|
257
|
+
"Worker component should appear in basic health response"
|
|
258
|
+
)
|
|
259
|
+
assert worker_component["name"] == "worker"
|
|
260
|
+
assert isinstance(worker_component["healthy"], bool)
|
|
261
|
+
|
|
262
|
+
{%- endif %}
|
aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_scheduler_endpoints.py.jinja
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
{%- if scheduler_backend != "memory" %}
|
|
2
|
+
"""
|
|
3
|
+
Tests for scheduler API endpoints.
|
|
4
|
+
|
|
5
|
+
These tests focus on the HTTP endpoints for scheduled task management,
|
|
6
|
+
ensuring API responses match expected format and handle errors correctly.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from collections.abc import AsyncGenerator
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
from unittest.mock import AsyncMock, patch
|
|
13
|
+
from httpx import AsyncClient, ASGITransport
|
|
14
|
+
|
|
15
|
+
from app.integrations.main import create_integrated_app
|
|
16
|
+
from app.services.scheduler.models import ScheduledTask, TaskStatistics
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestSchedulerEndpoints:
|
|
20
|
+
"""Test scheduler API endpoints with various states."""
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
async def async_client(self) -> AsyncGenerator[AsyncClient, None]:
|
|
24
|
+
"""Async HTTP client for testing."""
|
|
25
|
+
app = create_integrated_app()
|
|
26
|
+
|
|
27
|
+
# Manually trigger startup for component registration
|
|
28
|
+
from app.components.backend.hooks import backend_hooks
|
|
29
|
+
await backend_hooks.discover_lifespan_hooks()
|
|
30
|
+
await backend_hooks.execute_startup_hooks()
|
|
31
|
+
|
|
32
|
+
transport = ASGITransport(app=app)
|
|
33
|
+
async with AsyncClient(
|
|
34
|
+
transport=transport, base_url="http://test"
|
|
35
|
+
) as client:
|
|
36
|
+
yield client
|
|
37
|
+
|
|
38
|
+
# Clean up after test
|
|
39
|
+
await backend_hooks.execute_shutdown_hooks()
|
|
40
|
+
|
|
41
|
+
@pytest.mark.asyncio
|
|
42
|
+
async def test_list_scheduled_jobs_endpoint_accessible(
|
|
43
|
+
self, async_client: AsyncClient
|
|
44
|
+
) -> None:
|
|
45
|
+
"""Test /api/v1/scheduler/jobs endpoint is accessible."""
|
|
46
|
+
response = await async_client.get("/api/v1/scheduler/jobs")
|
|
47
|
+
|
|
48
|
+
# Should return a valid response (200 for success, 503 for unavailable,
|
|
49
|
+
# 500 for dependency issues)
|
|
50
|
+
assert response.status_code in [200, 500, 503]
|
|
51
|
+
data = response.json()
|
|
52
|
+
|
|
53
|
+
if response.status_code == 200:
|
|
54
|
+
# Verify response structure matches ScheduledTaskListResponse
|
|
55
|
+
assert "tasks" in data
|
|
56
|
+
assert "total_count" in data
|
|
57
|
+
assert isinstance(data["tasks"], list)
|
|
58
|
+
assert isinstance(data["total_count"], int)
|
|
59
|
+
elif response.status_code == 503:
|
|
60
|
+
# 503 when scheduler unavailable
|
|
61
|
+
assert "detail" in data
|
|
62
|
+
assert "scheduler_unavailable" in data["detail"].get("error", "")
|
|
63
|
+
elif response.status_code == 500:
|
|
64
|
+
# 500 for dependency/internal issues
|
|
65
|
+
assert "detail" in data
|
|
66
|
+
assert "internal_error" in data["detail"].get("error", "")
|
|
67
|
+
|
|
68
|
+
@pytest.mark.asyncio
|
|
69
|
+
async def test_get_scheduled_job_not_found(
|
|
70
|
+
self, async_client: AsyncClient
|
|
71
|
+
) -> None:
|
|
72
|
+
"""Test /api/v1/scheduler/jobs/{job_id} returns 404 for non-existent job."""
|
|
73
|
+
response = await async_client.get("/api/v1/scheduler/jobs/non_existent_job")
|
|
74
|
+
|
|
75
|
+
# Should return 404 for non-existent job, 503 if scheduler unavailable,
|
|
76
|
+
# or 500 for dependency issues
|
|
77
|
+
assert response.status_code in [404, 500, 503]
|
|
78
|
+
data = response.json()
|
|
79
|
+
|
|
80
|
+
assert "detail" in data
|
|
81
|
+
if response.status_code == 404:
|
|
82
|
+
assert "job_not_found" in data["detail"].get("error", "")
|
|
83
|
+
elif response.status_code == 503:
|
|
84
|
+
assert "scheduler_unavailable" in data["detail"].get("error", "")
|
|
85
|
+
elif response.status_code == 500:
|
|
86
|
+
assert "internal_error" in data["detail"].get("error", "")
|
|
87
|
+
|
|
88
|
+
@pytest.mark.asyncio
|
|
89
|
+
async def test_scheduler_statistics_endpoint_accessible(
|
|
90
|
+
self, async_client: AsyncClient
|
|
91
|
+
) -> None:
|
|
92
|
+
"""Test /api/v1/scheduler/statistics endpoint is accessible."""
|
|
93
|
+
response = await async_client.get("/api/v1/scheduler/statistics")
|
|
94
|
+
|
|
95
|
+
# Should return a valid response (200 for success, 503 for unavailable,
|
|
96
|
+
# 500 for dependency issues)
|
|
97
|
+
assert response.status_code in [200, 500, 503]
|
|
98
|
+
data = response.json()
|
|
99
|
+
|
|
100
|
+
if response.status_code == 200:
|
|
101
|
+
# Verify response structure matches ScheduledTaskStatisticsResponse
|
|
102
|
+
assert "statistics" in data
|
|
103
|
+
stats = data["statistics"]
|
|
104
|
+
assert "total_tasks" in stats
|
|
105
|
+
assert "active_tasks" in stats
|
|
106
|
+
assert "paused_tasks" in stats
|
|
107
|
+
assert isinstance(stats["total_tasks"], int)
|
|
108
|
+
assert isinstance(stats["active_tasks"], int)
|
|
109
|
+
assert isinstance(stats["paused_tasks"], int)
|
|
110
|
+
elif response.status_code == 503:
|
|
111
|
+
# 503 when scheduler unavailable
|
|
112
|
+
assert "detail" in data
|
|
113
|
+
assert "scheduler_unavailable" in data["detail"].get("error", "")
|
|
114
|
+
elif response.status_code == 500:
|
|
115
|
+
# 500 for dependency/internal issues
|
|
116
|
+
assert "detail" in data
|
|
117
|
+
assert "internal_error" in data["detail"].get("error", "")
|
|
118
|
+
|
|
119
|
+
@pytest.mark.asyncio
|
|
120
|
+
async def test_scheduler_endpoints_return_proper_content_type(
|
|
121
|
+
self, async_client: AsyncClient
|
|
122
|
+
) -> None:
|
|
123
|
+
"""Test all scheduler endpoints return JSON content type."""
|
|
124
|
+
endpoints = [
|
|
125
|
+
"/api/v1/scheduler/jobs",
|
|
126
|
+
"/api/v1/scheduler/jobs/test_job",
|
|
127
|
+
"/api/v1/scheduler/statistics"
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
for endpoint in endpoints:
|
|
131
|
+
response = await async_client.get(endpoint)
|
|
132
|
+
assert "application/json" in response.headers.get("content-type", "")
|
|
133
|
+
|
|
134
|
+
@pytest.mark.asyncio
|
|
135
|
+
async def test_scheduler_job_detail_response_structure(
|
|
136
|
+
self, async_client: AsyncClient
|
|
137
|
+
) -> None:
|
|
138
|
+
"""Test job detail response has correct structure when job exists."""
|
|
139
|
+
# Try to get any existing job first
|
|
140
|
+
list_response = await async_client.get("/api/v1/scheduler/jobs")
|
|
141
|
+
|
|
142
|
+
if list_response.status_code == 200:
|
|
143
|
+
data = list_response.json()
|
|
144
|
+
tasks = data.get("tasks", [])
|
|
145
|
+
|
|
146
|
+
if tasks:
|
|
147
|
+
# Test detail endpoint with first available job
|
|
148
|
+
job_id = tasks[0]["job_id"]
|
|
149
|
+
url = f"/api/v1/scheduler/jobs/{job_id}"
|
|
150
|
+
detail_response = await async_client.get(url)
|
|
151
|
+
|
|
152
|
+
if detail_response.status_code == 200:
|
|
153
|
+
detail_data = detail_response.json()
|
|
154
|
+
assert "task" in detail_data
|
|
155
|
+
|
|
156
|
+
task = detail_data["task"]
|
|
157
|
+
# Verify task structure matches ScheduledTask model
|
|
158
|
+
required_fields = [
|
|
159
|
+
"job_id",
|
|
160
|
+
"name",
|
|
161
|
+
"function",
|
|
162
|
+
"schedule",
|
|
163
|
+
"trigger_type",
|
|
164
|
+
"status",
|
|
165
|
+
]
|
|
166
|
+
for field in required_fields:
|
|
167
|
+
assert field in task, f"Missing required field: {field}"
|
|
168
|
+
|
|
169
|
+
# Verify field types
|
|
170
|
+
assert isinstance(task["job_id"], str)
|
|
171
|
+
assert isinstance(task["name"], str)
|
|
172
|
+
assert isinstance(task["function"], str)
|
|
173
|
+
assert isinstance(task["schedule"], str)
|
|
174
|
+
valid_trigger_types = ["interval", "cron", "date", "unknown"]
|
|
175
|
+
assert task["trigger_type"] in valid_trigger_types
|
|
176
|
+
assert task["status"] in ["active", "paused"]
|
|
177
|
+
|
|
178
|
+
@pytest.mark.asyncio
|
|
179
|
+
async def test_scheduler_dependency_injection_works(
|
|
180
|
+
self, async_client: AsyncClient
|
|
181
|
+
) -> None:
|
|
182
|
+
"""Test that dependency injection properly provides ScheduledTaskManager."""
|
|
183
|
+
# Mock the service layer to return predictable data
|
|
184
|
+
mock_tasks = [
|
|
185
|
+
ScheduledTask(
|
|
186
|
+
job_id="test_job",
|
|
187
|
+
name="Test Job",
|
|
188
|
+
function="test.module.function",
|
|
189
|
+
schedule="Every 5m",
|
|
190
|
+
trigger_type="interval",
|
|
191
|
+
status="active",
|
|
192
|
+
max_instances=1,
|
|
193
|
+
coalesce=True
|
|
194
|
+
)
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
manager_path = "app.components.backend.api.scheduler.ScheduledTaskManager"
|
|
198
|
+
with patch(manager_path) as mock_manager_class:
|
|
199
|
+
mock_manager = AsyncMock()
|
|
200
|
+
mock_manager.list_tasks.return_value = mock_tasks
|
|
201
|
+
mock_manager_class.return_value = mock_manager
|
|
202
|
+
|
|
203
|
+
response = await async_client.get("/api/v1/scheduler/jobs")
|
|
204
|
+
|
|
205
|
+
# Verify the dependency was called
|
|
206
|
+
mock_manager_class.assert_called_once()
|
|
207
|
+
mock_manager.list_tasks.assert_called_once()
|
|
208
|
+
|
|
209
|
+
# Verify response structure
|
|
210
|
+
assert response.status_code == 200
|
|
211
|
+
data = response.json()
|
|
212
|
+
assert len(data["tasks"]) == 1
|
|
213
|
+
assert data["tasks"][0]["job_id"] == "test_job"
|
|
214
|
+
{%- endif %}
|
aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_worker_endpoints.py.jinja
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
{%- if include_worker %}
|
|
2
|
+
"""
|
|
3
|
+
Tests for worker API endpoints.
|
|
4
|
+
|
|
5
|
+
These tests focus on the worker HTTP endpoints that handle task enqueuing,
|
|
6
|
+
ensuring TaskRequest model works correctly with task_kwargs field and
|
|
7
|
+
validates the API contract.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from collections.abc import AsyncIterator
|
|
11
|
+
from unittest.mock import AsyncMock, MagicMock, patch
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
from httpx import AsyncClient, ASGITransport
|
|
15
|
+
|
|
16
|
+
from app.integrations.main import create_integrated_app
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestWorkerEndpoints:
|
|
20
|
+
"""Test worker API endpoints for task enqueuing."""
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
async def async_client(self) -> AsyncIterator[AsyncClient]:
|
|
24
|
+
"""Async HTTP client for testing."""
|
|
25
|
+
app = create_integrated_app()
|
|
26
|
+
|
|
27
|
+
# Manually trigger startup for health check registration
|
|
28
|
+
from app.components.backend.hooks import backend_hooks
|
|
29
|
+
await backend_hooks.discover_lifespan_hooks()
|
|
30
|
+
await backend_hooks.execute_startup_hooks()
|
|
31
|
+
|
|
32
|
+
transport = ASGITransport(app=app)
|
|
33
|
+
async with AsyncClient(
|
|
34
|
+
transport=transport, base_url="http://test"
|
|
35
|
+
) as client:
|
|
36
|
+
yield client
|
|
37
|
+
|
|
38
|
+
@patch("app.components.worker.pools.create_pool")
|
|
39
|
+
async def test_enqueue_task_with_kwargs(self, mock_create_pool, async_client):
|
|
40
|
+
"""Test task enqueueing with task_kwargs field."""
|
|
41
|
+
# Clear cache to ensure fresh mock
|
|
42
|
+
from app.components.worker.pools import clear_pool_cache
|
|
43
|
+
await clear_pool_cache()
|
|
44
|
+
|
|
45
|
+
# Mock pool and job
|
|
46
|
+
mock_pool = AsyncMock()
|
|
47
|
+
mock_job = MagicMock()
|
|
48
|
+
mock_job.job_id = "test-job-123"
|
|
49
|
+
mock_pool.enqueue_job.return_value = mock_job
|
|
50
|
+
|
|
51
|
+
# Mock the get_queue_pool function to return our mocked pool
|
|
52
|
+
mock_create_pool.return_value = mock_pool
|
|
53
|
+
|
|
54
|
+
# Test data with task_kwargs
|
|
55
|
+
task_request = {
|
|
56
|
+
"task_name": "cpu_intensive_task",
|
|
57
|
+
"queue_type": "system",
|
|
58
|
+
"args": ["arg1", "arg2"],
|
|
59
|
+
"task_kwargs": {
|
|
60
|
+
"keyword_arg": "value",
|
|
61
|
+
"another_kwarg": 123
|
|
62
|
+
},
|
|
63
|
+
"delay_seconds": None
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Make the API call
|
|
67
|
+
response = await async_client.post(
|
|
68
|
+
"/api/v1/tasks/enqueue",
|
|
69
|
+
json=task_request
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Verify response
|
|
73
|
+
assert response.status_code == 200
|
|
74
|
+
response_data = response.json()
|
|
75
|
+
assert response_data["task_id"] == "test-job-123"
|
|
76
|
+
assert response_data["task_name"] == "cpu_intensive_task"
|
|
77
|
+
assert response_data["queue_type"] == "system"
|
|
78
|
+
|
|
79
|
+
# Verify that enqueue_job was called with task_kwargs unpacked
|
|
80
|
+
mock_pool.enqueue_job.assert_called_once_with(
|
|
81
|
+
"cpu_intensive_task",
|
|
82
|
+
"arg1",
|
|
83
|
+
"arg2",
|
|
84
|
+
_queue_name="arq:queue:system",
|
|
85
|
+
_defer_by=None,
|
|
86
|
+
keyword_arg="value",
|
|
87
|
+
another_kwarg=123
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
@patch("app.components.worker.pools.create_pool")
|
|
91
|
+
async def test_enqueue_task_without_kwargs(self, mock_create_pool, async_client):
|
|
92
|
+
"""Test task enqueueing without task_kwargs (empty dict)."""
|
|
93
|
+
# Clear cache to ensure fresh mock
|
|
94
|
+
from app.components.worker.pools import clear_pool_cache
|
|
95
|
+
await clear_pool_cache()
|
|
96
|
+
|
|
97
|
+
# Mock pool and job
|
|
98
|
+
mock_pool = AsyncMock()
|
|
99
|
+
mock_job = MagicMock()
|
|
100
|
+
mock_job.job_id = "test-job-456"
|
|
101
|
+
mock_pool.enqueue_job.return_value = mock_job
|
|
102
|
+
mock_create_pool.return_value = mock_pool
|
|
103
|
+
|
|
104
|
+
# Test data without task_kwargs (should default to empty dict)
|
|
105
|
+
task_request = {
|
|
106
|
+
"task_name": "io_simulation_task",
|
|
107
|
+
"queue_type": "system",
|
|
108
|
+
"args": [],
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# Make the API call
|
|
112
|
+
response = await async_client.post(
|
|
113
|
+
"/api/v1/tasks/enqueue",
|
|
114
|
+
json=task_request
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Verify response
|
|
118
|
+
assert response.status_code == 200
|
|
119
|
+
response_data = response.json()
|
|
120
|
+
assert response_data["task_id"] == "test-job-456"
|
|
121
|
+
|
|
122
|
+
# Verify enqueue_job called with no extra kwargs
|
|
123
|
+
mock_pool.enqueue_job.assert_called_once_with(
|
|
124
|
+
"io_simulation_task",
|
|
125
|
+
_queue_name="arq:queue:system",
|
|
126
|
+
_defer_by=None
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
async def test_enqueue_task_invalid_queue_type(self, async_client):
|
|
130
|
+
"""Test error handling for invalid queue type."""
|
|
131
|
+
task_request = {
|
|
132
|
+
"task_name": "cpu_intensive_task",
|
|
133
|
+
"queue_type": "invalid_queue",
|
|
134
|
+
"args": [],
|
|
135
|
+
"task_kwargs": {}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
response = await async_client.post(
|
|
139
|
+
"/api/v1/tasks/enqueue",
|
|
140
|
+
json=task_request
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Should return 400 for invalid queue type
|
|
144
|
+
assert response.status_code == 400
|
|
145
|
+
response_data = response.json()
|
|
146
|
+
assert "detail" in response_data
|
|
147
|
+
assert response_data["detail"]["error"] == "invalid_queue_type"
|
|
148
|
+
|
|
149
|
+
async def test_task_request_model_validation(self, async_client):
|
|
150
|
+
"""Test TaskRequest model validation."""
|
|
151
|
+
# Missing required task_name field
|
|
152
|
+
invalid_request = {
|
|
153
|
+
"queue_type": "system",
|
|
154
|
+
"args": [],
|
|
155
|
+
"task_kwargs": {}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
response = await async_client.post(
|
|
159
|
+
"/api/v1/tasks/enqueue",
|
|
160
|
+
json=invalid_request
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Should return 422 for validation error
|
|
164
|
+
assert response.status_code == 422
|
|
165
|
+
{% endif %}
|