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.
Files changed (392) hide show
  1. aegis/__init__.py +5 -0
  2. aegis/__main__.py +51 -0
  3. aegis/cli/__init__.py +6 -0
  4. aegis/cli/callbacks.py +114 -0
  5. aegis/cli/interactive.py +611 -0
  6. aegis/cli/utils.py +70 -0
  7. aegis/cli/validators.py +34 -0
  8. aegis/commands/__init__.py +6 -0
  9. aegis/commands/add.py +353 -0
  10. aegis/commands/add_service.py +332 -0
  11. aegis/commands/components.py +35 -0
  12. aegis/commands/init.py +370 -0
  13. aegis/commands/remove.py +227 -0
  14. aegis/commands/services.py +52 -0
  15. aegis/commands/update.py +252 -0
  16. aegis/commands/version.py +12 -0
  17. aegis/config/__init__.py +1 -0
  18. aegis/config/shared_files.py +136 -0
  19. aegis/core/CLAUDE.md +377 -0
  20. aegis/core/__init__.py +6 -0
  21. aegis/core/component_files.py +228 -0
  22. aegis/core/component_utils.py +220 -0
  23. aegis/core/components.py +127 -0
  24. aegis/core/copier_manager.py +315 -0
  25. aegis/core/copier_updater.py +475 -0
  26. aegis/core/dependency_resolver.py +119 -0
  27. aegis/core/manual_updater.py +554 -0
  28. aegis/core/post_gen_tasks.py +547 -0
  29. aegis/core/service_resolver.py +261 -0
  30. aegis/core/services.py +157 -0
  31. aegis/core/template_generator.py +266 -0
  32. aegis/core/version_compatibility.py +259 -0
  33. aegis/templates/CLAUDE.md +591 -0
  34. aegis/templates/cookiecutter-aegis-project/cookiecutter.json +39 -0
  35. aegis/templates/cookiecutter-aegis-project/hooks/post_gen_project.py +214 -0
  36. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.dockerignore +71 -0
  37. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.env.example.j2 +130 -0
  38. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.gitignore +131 -0
  39. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Dockerfile +53 -0
  40. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Makefile +236 -0
  41. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/README.md.j2 +196 -0
  42. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/alembic.ini.j2 +111 -0
  43. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/env.py.j2 +91 -0
  44. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/script.py.mako +25 -0
  45. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/versions/001_initial_auth.py.j2 +51 -0
  46. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/__init__.py +5 -0
  47. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/__init__.py +6 -0
  48. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/ai.py.j2 +700 -0
  49. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/ai_rendering.py +361 -0
  50. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/auth.py.j2 +253 -0
  51. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/health.py.j2 +419 -0
  52. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/load_test.py.j2 +656 -0
  53. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/main.py.j2 +65 -0
  54. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/marko_terminal_renderer.py +489 -0
  55. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/tasks.py.j2 +328 -0
  56. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/{% if cookiecutter.include_scheduler == /"yes/" %}tasks.py{% endif %}" +340 -0
  57. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/__init__.py +0 -0
  58. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/__init__.py +0 -0
  59. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/ai/__init__.py +8 -0
  60. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/ai/router.py +329 -0
  61. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/auth/__init__.py +1 -0
  62. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/auth/router.py +64 -0
  63. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/deps.py +58 -0
  64. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/health.py +163 -0
  65. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/models.py.j2 +280 -0
  66. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/routing.py.j2 +32 -0
  67. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/scheduler.py.j2 +121 -0
  68. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/worker.py.j2 +478 -0
  69. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/hooks.py +144 -0
  70. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/main.py +31 -0
  71. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/__init__.py +1 -0
  72. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/cors.py +20 -0
  73. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/__init__.py +1 -0
  74. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/cleanup.py +14 -0
  75. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/__init__.py +1 -0
  76. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/component_health.py.j2 +418 -0
  77. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/database_init.py.j2 +83 -0
  78. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/__init__.py +5 -0
  79. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/__init__.py +27 -0
  80. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/table.py +78 -0
  81. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/text.py +142 -0
  82. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/__init__.py.j2 +47 -0
  83. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/ai_card.py +287 -0
  84. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/auth_card.py +198 -0
  85. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/base_card.py +256 -0
  86. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/card_factory.py +227 -0
  87. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/card_utils.py +333 -0
  88. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/database_card.py +420 -0
  89. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/fastapi_card.py +328 -0
  90. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/flet_card.py +267 -0
  91. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/redis_card.py +322 -0
  92. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/scheduler_card.py +352 -0
  93. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/services_card.py +233 -0
  94. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/worker_card.py +684 -0
  95. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/main.py.j2 +653 -0
  96. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/theme.py +48 -0
  97. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/__init__.py +1 -0
  98. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/main.py.j2 +156 -0
  99. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/CLAUDE.md.j2 +213 -0
  100. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/__init__.py +6 -0
  101. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/constants.py.j2 +30 -0
  102. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/pools.py +97 -0
  103. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/__init__.py +1 -0
  104. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/load_test.py +55 -0
  105. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/media.py +49 -0
  106. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/system.py +44 -0
  107. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/registry.py +139 -0
  108. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/__init__.py +120 -0
  109. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/load_tasks.py +507 -0
  110. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/simple_system_tasks.py +33 -0
  111. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/system_tasks.py +281 -0
  112. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/config.py.j2 +178 -0
  113. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/constants.py +58 -0
  114. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/db.py.j2 +176 -0
  115. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/log.py +92 -0
  116. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/security.py +62 -0
  117. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/__init__.py +1 -0
  118. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/webserver.py +40 -0
  119. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/{% if cookiecutter.include_scheduler == /"yes/" %}scheduler.py{% endif %}" +21 -0
  120. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/__init__.py +0 -0
  121. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/main.py +62 -0
  122. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/models/__init__.py +1 -0
  123. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/models/user.py +44 -0
  124. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/py.typed +0 -0
  125. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/__init__.py +1 -0
  126. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/__init__.py +8 -0
  127. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/config.py +130 -0
  128. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/conversation.py +213 -0
  129. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/health.py +96 -0
  130. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/models.py +229 -0
  131. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/providers.py +370 -0
  132. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/service.py +388 -0
  133. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/__init__.py +1 -0
  134. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/auth_service.py +41 -0
  135. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/health.py +164 -0
  136. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/user_service.py +83 -0
  137. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/middleware_inspector.py.j2 +223 -0
  138. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/models.py.j2 +70 -0
  139. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/route_inspector.py.j2 +155 -0
  140. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test.py +679 -0
  141. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test_models.py +266 -0
  142. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/__init__.py.j2 +21 -0
  143. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/models.py.j2 +119 -0
  144. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/scheduled_task_manager.py.j2 +273 -0
  145. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/task_monitor.py.j2 +189 -0
  146. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/__init__.py +15 -0
  147. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/models.py +26 -0
  148. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/__init__.py +52 -0
  149. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/alerts.py +94 -0
  150. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/backup.py.j2 +119 -0
  151. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/health.py.j2 +1333 -0
  152. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/models.py +243 -0
  153. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/ui.py +52 -0
  154. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto-dark.png +0 -0
  155. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto-square-backup.png +0 -0
  156. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto.png +0 -0
  157. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.dockerignore +71 -0
  158. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.env.example.j2 +64 -0
  159. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.gitignore +131 -0
  160. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/Dockerfile +53 -0
  161. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/Makefile +211 -0
  162. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/README.md.j2 +172 -0
  163. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/docker-compose.yml.j2 +78 -0
  164. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/mkdocs.yml.j2 +62 -0
  165. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/pyproject.toml.j2 +120 -0
  166. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/uv.lock +1673 -0
  167. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docker-compose.yml.j2 +200 -0
  168. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/api.md +191 -0
  169. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md +0 -0
  170. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md.j2 +621 -0
  171. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/development.md +215 -0
  172. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/health.md +240 -0
  173. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/javascripts/mermaid-config.js +62 -0
  174. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/stylesheets/mermaid.css +95 -0
  175. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/mkdocs.yml.j2 +62 -0
  176. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/pyproject.toml.j2 +131 -0
  177. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh +87 -0
  178. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh.j2 +93 -0
  179. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/gen_docs.py +16 -0
  180. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/__init__.py +1 -0
  181. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_auth_endpoints.py.j2 +307 -0
  182. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_health_endpoints.py.j2 +262 -0
  183. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_scheduler_endpoints.py.j2 +214 -0
  184. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_worker_endpoints.py.j2 +165 -0
  185. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/cli/test_ai_rendering.py +427 -0
  186. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/cli/test_conversation_memory.py +465 -0
  187. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/components/test_scheduler.py +43 -0
  188. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/conftest.py.j2 +195 -0
  189. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/__init__.py +1 -0
  190. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/__init__.py +1 -0
  191. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/conftest.py +78 -0
  192. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_health.py +157 -0
  193. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_models.py +164 -0
  194. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_service.py +198 -0
  195. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_auth_integration.py.j2 +528 -0
  196. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_component_integration.py.j2 +387 -0
  197. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_conversation_persistence.py +342 -0
  198. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_health_logic.py.j2 +663 -0
  199. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_models.py +619 -0
  200. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_service.py +603 -0
  201. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_middleware_inspector.py.j2 +248 -0
  202. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_scheduled_task_manager.py.j2 +292 -0
  203. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_system_service.py +98 -0
  204. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_worker_health_registration.py.j2 +257 -0
  205. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/test_core.py +49 -0
  206. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/uv.lock +1673 -0
  207. aegis/templates/copier-aegis-project/{{ project_slug }}/.copier-answers.yml.jinja +21 -0
  208. aegis/templates/copier-aegis-project/{{ project_slug }}/.dockerignore +71 -0
  209. aegis/templates/copier-aegis-project/{{ project_slug }}/.env.example.jinja +130 -0
  210. aegis/templates/copier-aegis-project/{{ project_slug }}/.gitignore +131 -0
  211. aegis/templates/copier-aegis-project/{{ project_slug }}/Dockerfile +53 -0
  212. aegis/templates/copier-aegis-project/{{ project_slug }}/Makefile.jinja +236 -0
  213. aegis/templates/copier-aegis-project/{{ project_slug }}/README.md.jinja +196 -0
  214. aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/alembic.ini.jinja +111 -0
  215. aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/env.py.jinja +91 -0
  216. aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/script.py.mako +25 -0
  217. aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/versions/001_initial_auth.py.jinja +51 -0
  218. aegis/templates/copier-aegis-project/{{ project_slug }}/app/__init__.py.jinja +5 -0
  219. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/__init__.py.jinja +6 -0
  220. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/ai.py.jinja +700 -0
  221. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/ai_rendering.py +360 -0
  222. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/auth.py.jinja +253 -0
  223. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/health.py.jinja +419 -0
  224. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/load_test.py.jinja +656 -0
  225. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/main.py.jinja +65 -0
  226. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/marko_terminal_renderer.py +489 -0
  227. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/tasks.py.jinja +328 -0
  228. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/__init__.py +0 -0
  229. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/__init__.py +0 -0
  230. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/ai/__init__.py +8 -0
  231. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/ai/router.py +329 -0
  232. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/auth/__init__.py +1 -0
  233. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/auth/router.py +64 -0
  234. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/deps.py +58 -0
  235. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/health.py.jinja +163 -0
  236. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/models.py.jinja +280 -0
  237. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/routing.py.jinja +32 -0
  238. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/scheduler.py.jinja +121 -0
  239. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/worker.py.jinja +478 -0
  240. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/hooks.py +144 -0
  241. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/main.py +31 -0
  242. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/middleware/__init__.py +1 -0
  243. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/middleware/cors.py +20 -0
  244. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/shutdown/__init__.py +1 -0
  245. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/shutdown/cleanup.py +14 -0
  246. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/__init__.py +1 -0
  247. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/component_health.py.jinja +418 -0
  248. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/database_init.py.jinja +83 -0
  249. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/__init__.py +5 -0
  250. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/__init__.py +27 -0
  251. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/table.py +78 -0
  252. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/text.py +142 -0
  253. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/__init__.py.jinja +47 -0
  254. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/ai_card.py +287 -0
  255. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/auth_card.py +198 -0
  256. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/base_card.py +256 -0
  257. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/card_factory.py +227 -0
  258. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/card_utils.py +333 -0
  259. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/database_card.py +420 -0
  260. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/fastapi_card.py +328 -0
  261. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/flet_card.py +267 -0
  262. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/redis_card.py +322 -0
  263. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/scheduler_card.py +352 -0
  264. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/services_card.py +233 -0
  265. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/worker_card.py +684 -0
  266. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/main.py.jinja +653 -0
  267. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/theme.py +48 -0
  268. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/scheduler/__init__.py +1 -0
  269. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/scheduler/main.py.jinja +156 -0
  270. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/CLAUDE.md.jinja +213 -0
  271. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/__init__.py +6 -0
  272. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/constants.py.jinja +30 -0
  273. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/pools.py +97 -0
  274. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/__init__.py +1 -0
  275. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/load_test.py +55 -0
  276. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/media.py +49 -0
  277. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/system.py +44 -0
  278. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/registry.py +139 -0
  279. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/__init__.py +120 -0
  280. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/load_tasks.py +507 -0
  281. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/simple_system_tasks.py +33 -0
  282. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/system_tasks.py +281 -0
  283. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/config.py.jinja +178 -0
  284. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/constants.py +58 -0
  285. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/db.py.jinja +176 -0
  286. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/log.py +92 -0
  287. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/security.py +62 -0
  288. aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/__init__.py +1 -0
  289. aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/scheduler.py.jinja +21 -0
  290. aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/webserver.py +39 -0
  291. aegis/templates/copier-aegis-project/{{ project_slug }}/app/integrations/__init__.py +0 -0
  292. aegis/templates/copier-aegis-project/{{ project_slug }}/app/integrations/main.py +61 -0
  293. aegis/templates/copier-aegis-project/{{ project_slug }}/app/models/__init__.py +1 -0
  294. aegis/templates/copier-aegis-project/{{ project_slug }}/app/models/user.py +44 -0
  295. aegis/templates/copier-aegis-project/{{ project_slug }}/app/py.typed +0 -0
  296. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/__init__.py +1 -0
  297. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/__init__.py +8 -0
  298. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/config.py +130 -0
  299. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/conversation.py +213 -0
  300. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/health.py +96 -0
  301. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/models.py +229 -0
  302. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/providers.py.jinja +370 -0
  303. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/service.py +387 -0
  304. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/__init__.py +1 -0
  305. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/auth_service.py +40 -0
  306. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/health.py +162 -0
  307. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/user_service.py +82 -0
  308. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/middleware_inspector.py.jinja +223 -0
  309. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/models.py.jinja +70 -0
  310. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/route_inspector.py.jinja +155 -0
  311. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/load_test.py +678 -0
  312. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/load_test_models.py +265 -0
  313. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/__init__.py.jinja +21 -0
  314. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/models.py.jinja +119 -0
  315. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/scheduled_task_manager.py.jinja +273 -0
  316. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/task_monitor.py.jinja +189 -0
  317. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/shared/__init__.py +15 -0
  318. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/shared/models.py +26 -0
  319. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/__init__.py +52 -0
  320. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/alerts.py +94 -0
  321. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/backup.py.jinja +119 -0
  322. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/health.py.jinja +1333 -0
  323. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/models.py +243 -0
  324. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/ui.py +52 -0
  325. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57223!aegis-manifesto.png +0 -0
  326. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57224!aegis-manifesto-dark.png +0 -0
  327. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57225!aegis-manifesto-square-backup.png +0 -0
  328. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57533!aegis-manifesto.png +0 -0
  329. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57534!aegis-manifesto-dark.png +0 -0
  330. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57538!aegis-manifesto-square-backup.png +0 -0
  331. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57897!aegis-manifesto.png +0 -0
  332. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57898!aegis-manifesto-dark.png +0 -0
  333. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57904!aegis-manifesto-square-backup.png +0 -0
  334. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58315!aegis-manifesto.png +0 -0
  335. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58316!aegis-manifesto-dark.png +0 -0
  336. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58324!aegis-manifesto-square-backup.png +0 -0
  337. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58837!aegis-manifesto.png +0 -0
  338. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58838!aegis-manifesto-dark.png +0 -0
  339. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58849!aegis-manifesto-square-backup.png +0 -0
  340. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto-dark.png +0 -0
  341. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto-square-backup.png +0 -0
  342. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto.png +0 -0
  343. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/.env.example.jinja +64 -0
  344. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/README.md.jinja +172 -0
  345. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/docker-compose.yml.jinja +78 -0
  346. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/mkdocs.yml.jinja +62 -0
  347. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/pyproject.toml.jinja +120 -0
  348. aegis/templates/copier-aegis-project/{{ project_slug }}/docker-compose.yml.jinja +200 -0
  349. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/api.md.jinja +191 -0
  350. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/components/scheduler.md +0 -0
  351. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/components/scheduler.md.jinja +621 -0
  352. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/development.md.jinja +215 -0
  353. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/health.md.jinja +240 -0
  354. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/javascripts/mermaid-config.js +62 -0
  355. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/stylesheets/mermaid.css +95 -0
  356. aegis/templates/copier-aegis-project/{{ project_slug }}/mkdocs.yml.jinja +62 -0
  357. aegis/templates/copier-aegis-project/{{ project_slug }}/pyproject.toml.jinja +131 -0
  358. aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/entrypoint.sh +87 -0
  359. aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/entrypoint.sh.jinja +93 -0
  360. aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/gen_docs.py +16 -0
  361. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/__init__.py +1 -0
  362. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_auth_endpoints.py.jinja +307 -0
  363. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_health_endpoints.py.jinja +262 -0
  364. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_scheduler_endpoints.py.jinja +214 -0
  365. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_worker_endpoints.py.jinja +165 -0
  366. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/cli/test_ai_rendering.py +427 -0
  367. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/cli/test_conversation_memory.py +465 -0
  368. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/components/test_scheduler.py +43 -0
  369. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/conftest.py.jinja +195 -0
  370. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/__init__.py +1 -0
  371. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/__init__.py +1 -0
  372. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/conftest.py +78 -0
  373. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_health.py +157 -0
  374. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_models.py +164 -0
  375. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_service.py +198 -0
  376. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_auth_integration.py.jinja +528 -0
  377. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_component_integration.py.jinja +387 -0
  378. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_conversation_persistence.py +342 -0
  379. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_health_logic.py.jinja +663 -0
  380. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_load_test_models.py +619 -0
  381. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_load_test_service.py +603 -0
  382. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_middleware_inspector.py.jinja +248 -0
  383. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_scheduled_task_manager.py.jinja +292 -0
  384. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_system_service.py +98 -0
  385. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_worker_health_registration.py.jinja +257 -0
  386. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/test_core.py +49 -0
  387. aegis/templates/copier-aegis-project/{{ project_slug }}/uv.lock +1673 -0
  388. aegis_stack-0.2.0rc2.dist-info/METADATA +165 -0
  389. aegis_stack-0.2.0rc2.dist-info/RECORD +392 -0
  390. aegis_stack-0.2.0rc2.dist-info/WHEEL +4 -0
  391. aegis_stack-0.2.0rc2.dist-info/entry_points.txt +3 -0
  392. aegis_stack-0.2.0rc2.dist-info/licenses/LICENSE +21 -0
@@ -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 cookiecutter.include_worker == "yes" %}
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 %}
@@ -0,0 +1,214 @@
1
+ {%- if cookiecutter.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 %}
@@ -0,0 +1,165 @@
1
+ {%- if cookiecutter.include_worker == "yes" %}
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 %}