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,603 @@
1
+ """
2
+ Unit tests for LoadTestService.
3
+
4
+ Tests business logic, data transformation, and analysis functions.
5
+ """
6
+
7
+ import pickle
8
+ from unittest.mock import AsyncMock, MagicMock, patch
9
+
10
+ import pytest
11
+ from app.components.worker.constants import LoadTestTypes
12
+ from app.services.load_test import LoadTestConfiguration, LoadTestService
13
+ from app.services.load_test_models import (
14
+ LoadTestResult,
15
+ PerformanceAnalysis,
16
+ )
17
+ from pydantic import ValidationError
18
+
19
+
20
+ class TestLoadTestConfiguration:
21
+ """Test LoadTestConfiguration class (legacy config handler)."""
22
+
23
+ def test_default_configuration(self):
24
+ """Test configuration with defaults."""
25
+ config = LoadTestConfiguration()
26
+
27
+ assert config.num_tasks >= 10
28
+ assert config.num_tasks <= 10000
29
+ assert config.task_type == LoadTestTypes.CPU_INTENSIVE
30
+ assert config.batch_size >= 1
31
+ assert config.delay_ms >= 0
32
+
33
+ def test_configuration_bounds(self):
34
+ """Test configuration value bounds enforcement."""
35
+ # Test upper bounds - should raise ValidationError
36
+ with pytest.raises(ValidationError):
37
+ LoadTestConfiguration(num_tasks=50000, batch_size=200, delay_ms=10000)
38
+
39
+ # Test lower bounds - should raise ValidationError
40
+ with pytest.raises(ValidationError):
41
+ LoadTestConfiguration(num_tasks=5, batch_size=0, delay_ms=-100)
42
+
43
+ def test_to_dict(self):
44
+ """Test configuration serialization."""
45
+ config = LoadTestConfiguration(
46
+ num_tasks=100,
47
+ task_type=LoadTestTypes.IO_SIMULATION,
48
+ batch_size=20,
49
+ delay_ms=50,
50
+ target_queue="test_queue",
51
+ )
52
+
53
+ result = config.model_dump()
54
+
55
+ assert result["num_tasks"] == 100
56
+ assert result["task_type"] == "io_simulation"
57
+ assert result["batch_size"] == 20
58
+ assert result["delay_ms"] == 50
59
+ assert result["target_queue"] == "test_queue"
60
+
61
+
62
+ class TestLoadTestServiceTestTypeInfo:
63
+ """Test LoadTestService.get_test_type_info method."""
64
+
65
+ def test_cpu_test_type_info(self):
66
+ """Test CPU test type information."""
67
+ info = LoadTestService.get_test_type_info(LoadTestTypes.CPU_INTENSIVE)
68
+
69
+ assert info["name"] == "CPU Intensive"
70
+ assert "fibonacci" in info["description"].lower()
71
+ assert "fibonacci_n" in info["expected_metrics"]
72
+ assert "cpu_operations" in info["expected_metrics"]
73
+ assert "cpu bound" in info["performance_signature"].lower()
74
+
75
+ def test_io_test_type_info(self):
76
+ """Test I/O test type information."""
77
+ info = LoadTestService.get_test_type_info(LoadTestTypes.IO_SIMULATION)
78
+
79
+ assert info["name"] == "I/O Simulation"
80
+ assert "async" in info["description"].lower()
81
+ assert "simulated_delay_ms" in info["expected_metrics"]
82
+ assert "io_operations" in info["expected_metrics"]
83
+ assert "i/o bound" in info["performance_signature"].lower()
84
+
85
+ def test_memory_test_type_info(self):
86
+ """Test memory test type information."""
87
+ info = LoadTestService.get_test_type_info(LoadTestTypes.MEMORY_OPERATIONS)
88
+
89
+ assert info["name"] == "Memory Operations"
90
+ assert "allocation" in info["description"].lower()
91
+ assert "allocation_size" in info["expected_metrics"]
92
+ assert "list_sum" in info["expected_metrics"]
93
+ assert "memory bound" in info["performance_signature"].lower()
94
+
95
+ def test_failure_test_type_info(self):
96
+ """Test failure test type information."""
97
+ info = LoadTestService.get_test_type_info(LoadTestTypes.FAILURE_TESTING)
98
+
99
+ assert info["name"] == "Failure Testing"
100
+ assert "error handling" in info["description"].lower()
101
+ assert "failure_rate" in info["expected_metrics"]
102
+ assert "resilience" in info["performance_signature"].lower()
103
+
104
+ def test_unknown_test_type(self):
105
+ """Test handling of unknown test types."""
106
+ info = LoadTestService.get_test_type_info("unknown_type")
107
+
108
+ assert info == {} # Should return empty dict for unknown types
109
+
110
+
111
+ class TestLoadTestServiceAnalysis:
112
+ """Test LoadTestService analysis methods."""
113
+
114
+ def test_analyze_performance_excellent_throughput(self):
115
+ """Test performance analysis with excellent throughput."""
116
+ result_data = {
117
+ "metrics": {
118
+ "overall_throughput": 60.0, # Excellent (>= 50)
119
+ "tasks_sent": 100,
120
+ "tasks_completed": 100,
121
+ "total_duration_seconds": 10.0,
122
+ }
123
+ }
124
+
125
+ analysis = LoadTestService._analyze_performance(result_data)
126
+
127
+ assert analysis["throughput_rating"] == "excellent"
128
+ assert analysis["efficiency_rating"] == "excellent" # 100% completion
129
+ assert analysis["queue_pressure"] == "low" # < 30s duration
130
+
131
+ def test_analyze_performance_poor_throughput(self):
132
+ """Test performance analysis with poor throughput."""
133
+ result_data = {
134
+ "metrics": {
135
+ "overall_throughput": 5.0, # Poor (< 10)
136
+ "tasks_sent": 100,
137
+ "tasks_completed": 50, # 50% completion
138
+ "total_duration_seconds": 80.0, # High queue pressure
139
+ }
140
+ }
141
+
142
+ analysis = LoadTestService._analyze_performance(result_data)
143
+
144
+ assert analysis["throughput_rating"] == "poor"
145
+ assert analysis["efficiency_rating"] == "poor" # 50% completion
146
+ assert analysis["queue_pressure"] == "high" # > 60s duration
147
+
148
+ def test_analyze_performance_pydantic_models(self):
149
+ """Test Pydantic-based performance analysis."""
150
+ # Create a proper LoadTestResult
151
+ from app.services.load_test_models import (
152
+ LoadTestConfiguration as ConfigModel,
153
+ )
154
+ from app.services.load_test_models import (
155
+ LoadTestMetrics,
156
+ )
157
+
158
+ config = ConfigModel(
159
+ task_type=LoadTestTypes.CPU_INTENSIVE,
160
+ num_tasks=100,
161
+ batch_size=10,
162
+ target_queue="load_test",
163
+ )
164
+
165
+ metrics = LoadTestMetrics(
166
+ tasks_sent=100,
167
+ tasks_completed=95,
168
+ tasks_failed=5,
169
+ total_duration_seconds=25.0,
170
+ overall_throughput=25.0, # Good throughput
171
+ failure_rate_percent=5.0,
172
+ )
173
+
174
+ result = LoadTestResult(
175
+ status="completed",
176
+ test_id="test-123",
177
+ configuration=config,
178
+ metrics=metrics,
179
+ )
180
+
181
+ analysis = LoadTestService._analyze_performance_pydantic(result)
182
+
183
+ assert isinstance(analysis, PerformanceAnalysis)
184
+ assert analysis.throughput_rating == "good" # 20 <= 25 < 50
185
+ assert analysis.efficiency_rating == "excellent" # 95% completion
186
+ assert analysis.queue_pressure == "low" # < 30s
187
+
188
+ def test_generate_recommendations_low_throughput(self):
189
+ """Test recommendations for low throughput."""
190
+ result_data = {
191
+ "metrics": {
192
+ "overall_throughput": 5.0, # Low
193
+ "failure_rate_percent": 2.0, # Acceptable
194
+ "total_duration_seconds": 20.0,
195
+ "tasks_sent": 100,
196
+ }
197
+ }
198
+
199
+ recommendations = LoadTestService._generate_recommendations(result_data)
200
+
201
+ assert len(recommendations) == 1
202
+ assert "low throughput" in recommendations[0].lower()
203
+ assert "worker concurrency" in recommendations[0].lower()
204
+
205
+ def test_generate_recommendations_high_failure_rate(self):
206
+ """Test recommendations for high failure rate."""
207
+ result_data = {
208
+ "metrics": {
209
+ "overall_throughput": 20.0, # Good
210
+ "failure_rate_percent": 15.0, # High
211
+ "total_duration_seconds": 25.0,
212
+ "tasks_sent": 100,
213
+ }
214
+ }
215
+
216
+ recommendations = LoadTestService._generate_recommendations(result_data)
217
+
218
+ assert len(recommendations) == 1
219
+ assert "high failure rate" in recommendations[0].lower()
220
+ assert "15.0%" in recommendations[0]
221
+ assert "worker logs" in recommendations[0].lower()
222
+
223
+ def test_generate_recommendations_queue_saturation(self):
224
+ """Test recommendations for queue saturation."""
225
+ result_data = {
226
+ "metrics": {
227
+ "overall_throughput": 15.0, # Fair
228
+ "failure_rate_percent": 2.0, # Good
229
+ "total_duration_seconds": 90.0, # Long
230
+ "tasks_sent": 50, # Few tasks for the duration
231
+ }
232
+ }
233
+
234
+ recommendations = LoadTestService._generate_recommendations(result_data)
235
+
236
+ assert len(recommendations) == 1
237
+ assert "queue saturation" in recommendations[0].lower()
238
+ assert "smaller batches" in recommendations[0].lower()
239
+
240
+
241
+ @pytest.mark.asyncio
242
+ class TestLoadTestServiceIntegration:
243
+ """Test LoadTestService integration with mocked dependencies."""
244
+
245
+ @patch("app.components.worker.pools.create_pool")
246
+ async def test_enqueue_load_test_success(self, mock_create_pool):
247
+ """Test successful load test enqueueing."""
248
+ # Mock pool and job
249
+ mock_pool = AsyncMock()
250
+ mock_job = MagicMock()
251
+ mock_job.job_id = "test-job-123"
252
+ mock_pool.enqueue_job.return_value = mock_job
253
+ mock_pool.ping.return_value = True # For cache validation
254
+ mock_create_pool.return_value = mock_pool
255
+
256
+ # Create configuration
257
+ config = LoadTestConfiguration(
258
+ num_tasks=50,
259
+ task_type=LoadTestTypes.CPU_INTENSIVE,
260
+ batch_size=10,
261
+ target_queue="load_test",
262
+ )
263
+
264
+ # Test enqueueing
265
+ task_id = await LoadTestService.enqueue_load_test(config)
266
+
267
+ # Verify results
268
+ assert task_id == "test-job-123"
269
+
270
+ mock_pool.enqueue_job.assert_called_once_with(
271
+ "load_test_orchestrator",
272
+ _queue_name="arq:queue:load_test",
273
+ num_tasks=50,
274
+ task_type=LoadTestTypes.CPU_INTENSIVE,
275
+ batch_size=10,
276
+ delay_ms=0,
277
+ target_queue="load_test",
278
+ )
279
+ mock_pool.aclose.assert_called_once()
280
+
281
+ @patch("app.components.worker.pools.create_pool")
282
+ async def test_enqueue_load_test_failure(self, mock_create_pool):
283
+ """Test load test enqueueing failure."""
284
+ # Clear cache to ensure fresh mock
285
+ from app.components.worker.pools import clear_pool_cache
286
+
287
+ await clear_pool_cache()
288
+
289
+ # Mock create_pool to raise an exception
290
+ mock_create_pool.side_effect = Exception("Redis connection failed")
291
+
292
+ config = LoadTestConfiguration()
293
+
294
+ # Should raise the exception since pool creation fails
295
+ with pytest.raises(Exception, match="Redis connection failed"):
296
+ await LoadTestService.enqueue_load_test(config)
297
+
298
+ # No pool cleanup needed since create_pool failed
299
+
300
+ @patch("app.components.worker.pools.create_pool")
301
+ async def test_get_load_test_result_success(self, mock_create_pool): # noqa
302
+ """Test successful result retrieval with Pydantic validation."""
303
+ # Clear cache to ensure fresh mock
304
+ from app.components.worker.pools import clear_pool_cache
305
+
306
+ await clear_pool_cache()
307
+
308
+ # Mock Redis data (realistic orchestrator result)
309
+ raw_result_data = {
310
+ "test_id": "test-123",
311
+ "task_type": "io_simulation",
312
+ "tasks_sent": 10,
313
+ "tasks_completed": 10,
314
+ "tasks_failed": 0,
315
+ "total_duration_seconds": 2.5,
316
+ "overall_throughput_per_second": 4.0,
317
+ "failure_rate_percent": 0.0,
318
+ "completion_percentage": 100.0,
319
+ "average_throughput_per_second": 4.0,
320
+ "monitor_duration_seconds": 2.5,
321
+ "batch_size": 10,
322
+ "delay_ms": 0,
323
+ "target_queue": "load_test",
324
+ "start_time": "2023-01-01T10:00:00",
325
+ "end_time": "2023-01-01T10:00:02.5",
326
+ }
327
+
328
+ # Mock arq result format: {"r": actual_result, "t": 1, "s": true, ...}
329
+ arq_result = {"r": raw_result_data, "t": 1, "s": True}
330
+ pickled_result = pickle.dumps(arq_result)
331
+
332
+ # Mock pool
333
+ mock_pool = AsyncMock()
334
+ mock_pool.exists.return_value = True
335
+ mock_pool.get.return_value = pickled_result
336
+ mock_pool.ping.return_value = True # For cache validation
337
+ mock_pool.aclose.return_value = None # Mock cleanup
338
+ mock_create_pool.return_value = mock_pool
339
+
340
+ # Test result retrieval
341
+ result = await LoadTestService.get_load_test_result("test-123", "load_test")
342
+
343
+ # Verify result structure
344
+ assert result is not None
345
+ assert result["status"] == "completed"
346
+ assert result["test_id"] == "test-123"
347
+ assert result["metrics"]["tasks_completed"] == 10
348
+ assert result["metrics"]["overall_throughput"] == 4.0
349
+
350
+ # Verify analysis was added
351
+ assert "analysis" in result
352
+ assert "performance_analysis" in result["analysis"]
353
+ assert "recommendations" in result["analysis"]
354
+
355
+ mock_pool.aclose.assert_called_once()
356
+
357
+ @patch("app.components.worker.pools.create_pool")
358
+ async def test_get_load_test_result_not_found(self, mock_create_pool): # noqa
359
+ """Test result retrieval when task doesn't exist."""
360
+ # Clear cache to ensure fresh mock
361
+ from app.components.worker.pools import clear_pool_cache
362
+
363
+ await clear_pool_cache()
364
+
365
+ # Mock pool with no results
366
+ mock_pool = AsyncMock()
367
+ mock_pool.exists.return_value = False
368
+ mock_pool.ping.return_value = True # For cache validation
369
+ mock_pool.aclose.return_value = None # Mock cleanup
370
+ mock_create_pool.return_value = mock_pool
371
+
372
+ result = await LoadTestService.get_load_test_result("nonexistent", "load_test")
373
+
374
+ assert result is None
375
+ mock_pool.aclose.assert_called_once()
376
+
377
+ @patch("app.components.worker.pools.create_pool")
378
+ async def test_get_load_test_result_validation_error_fallback(
379
+ self, mock_create_pool
380
+ ):
381
+ """Test fallback when Pydantic validation fails."""
382
+ # Clear cache to ensure fresh mock
383
+ from app.components.worker.pools import clear_pool_cache
384
+
385
+ await clear_pool_cache()
386
+
387
+ # Create invalid data that will fail validation
388
+ invalid_result_data = {
389
+ "test_id": "test-123",
390
+ "task_type": "io_simulation",
391
+ "tasks_sent": -10, # Invalid - negative value
392
+ "tasks_completed": 20, # Invalid - more than sent
393
+ "total_duration_seconds": -5.0, # Invalid - negative
394
+ "batch_size": 10,
395
+ "target_queue": "load_test",
396
+ }
397
+
398
+ arq_result = {"r": invalid_result_data}
399
+ pickled_result = pickle.dumps(arq_result)
400
+
401
+ mock_pool = AsyncMock()
402
+ mock_pool.exists.return_value = True
403
+ mock_pool.get.return_value = pickled_result
404
+ mock_pool.ping.return_value = True # For cache validation
405
+ mock_pool.aclose.return_value = None # Mock cleanup
406
+ mock_create_pool.return_value = mock_pool
407
+
408
+ # Should fall back to manual transformation when Pydantic validation fails
409
+ result = await LoadTestService.get_load_test_result("test-123", "load_test")
410
+
411
+ # Should still get a result (via fallback)
412
+ assert result is not None
413
+ mock_pool.aclose.assert_called_once()
414
+
415
+ @patch("app.components.worker.pools.create_pool")
416
+ async def test_get_load_test_result_exception_handling(self, mock_create_pool):
417
+ """Test exception handling during result retrieval."""
418
+ # Clear cache to ensure fresh mock
419
+ from app.components.worker.pools import clear_pool_cache
420
+
421
+ await clear_pool_cache()
422
+
423
+ # Mock create_pool to raise exception
424
+ mock_create_pool.side_effect = Exception("Redis connection lost")
425
+
426
+ result = await LoadTestService.get_load_test_result("test-123", "load_test")
427
+
428
+ assert result is None # Should return None on exception
429
+ # No aclose to assert since create_pool raised exception
430
+
431
+
432
+ class TestTransformOrchestratorResult:
433
+ """Test the orchestrator result transformation logic."""
434
+
435
+ def test_transform_complete_result(self):
436
+ """Test transformation with all fields present."""
437
+ orchestrator_result = {
438
+ "test_id": "transform-test",
439
+ "task_type": "memory_operations",
440
+ "tasks_sent": 100,
441
+ "tasks_completed": 95,
442
+ "tasks_failed": 5,
443
+ "total_duration_seconds": 45.5,
444
+ "overall_throughput_per_second": 2.1,
445
+ "failure_rate_percent": 5.0,
446
+ "completion_percentage": 95.0,
447
+ "average_throughput_per_second": 2.1,
448
+ "monitor_duration_seconds": 45.0,
449
+ "batch_size": 20,
450
+ "delay_ms": 100,
451
+ "target_queue": "system",
452
+ "start_time": "2023-01-01T12:00:00",
453
+ "end_time": "2023-01-01T12:00:45",
454
+ "task_ids": ["id1", "id2", "id3"],
455
+ }
456
+
457
+ transformed = LoadTestService._transform_orchestrator_result(
458
+ orchestrator_result
459
+ )
460
+
461
+ # Check basic structure
462
+ assert transformed["task"] == "load_test_orchestrator"
463
+ assert transformed["status"] == "completed"
464
+ assert transformed["test_id"] == "transform-test"
465
+
466
+ # Check configuration mapping
467
+ config = transformed["configuration"]
468
+ assert config["task_type"] == "memory_operations"
469
+ assert config["num_tasks"] == 100
470
+ assert config["batch_size"] == 20
471
+ assert config["delay_ms"] == 100
472
+ assert config["target_queue"] == "system"
473
+
474
+ # Check metrics mapping
475
+ metrics = transformed["metrics"]
476
+ assert metrics["tasks_sent"] == 100
477
+ assert metrics["tasks_completed"] == 95
478
+ assert metrics["tasks_failed"] == 5
479
+ assert metrics["total_duration_seconds"] == 45.5
480
+ assert metrics["overall_throughput"] == 2.1
481
+ assert metrics["failure_rate_percent"] == 5.0
482
+
483
+ # Check optional fields
484
+ assert transformed["start_time"] == "2023-01-01T12:00:00"
485
+ assert transformed["end_time"] == "2023-01-01T12:00:45"
486
+ assert transformed["task_ids"] == ["id1", "id2", "id3"]
487
+
488
+ def test_transform_minimal_result(self):
489
+ """Test transformation with minimal required fields."""
490
+ minimal_result = {
491
+ "test_id": "minimal",
492
+ "task_type": "cpu_intensive",
493
+ "tasks_sent": 10,
494
+ "tasks_completed": 10,
495
+ "total_duration_seconds": 5.0,
496
+ "batch_size": 10,
497
+ "target_queue": "load_test",
498
+ }
499
+
500
+ transformed = LoadTestService._transform_orchestrator_result(minimal_result)
501
+
502
+ # Should handle missing optional fields gracefully
503
+ assert transformed["test_id"] == "minimal"
504
+ assert transformed["configuration"]["task_type"] == "cpu_intensive"
505
+ assert transformed["metrics"]["tasks_sent"] == 10
506
+ assert transformed["metrics"]["tasks_failed"] == 0 # Default
507
+ assert transformed["metrics"]["overall_throughput"] == 0 # Default
508
+ assert transformed["start_time"] is None
509
+ assert transformed["task_ids"] == []
510
+
511
+
512
+ # Performance and stress tests
513
+ class TestLoadTestServicePerformance:
514
+ """Test performance characteristics of the service."""
515
+
516
+ def test_test_type_info_caching_behavior(self):
517
+ """Test that test type info doesn't have unexpected side effects."""
518
+ # Call multiple times to ensure no state leakage
519
+ info1 = LoadTestService.get_test_type_info(LoadTestTypes.CPU_INTENSIVE)
520
+ info2 = LoadTestService.get_test_type_info(LoadTestTypes.CPU_INTENSIVE)
521
+
522
+ # Should return same data
523
+ assert info1 == info2
524
+
525
+ # Modifying one shouldn't affect the other (defensive copy)
526
+ info1["name"] = "Modified"
527
+ info3 = LoadTestService.get_test_type_info(LoadTestTypes.CPU_INTENSIVE)
528
+ assert info3["name"] == "CPU Intensive" # Should be unmodified
529
+
530
+ def test_analysis_with_edge_case_values(self):
531
+ """Test analysis functions with edge case values."""
532
+ # Zero duration
533
+ result_data = {
534
+ "metrics": {
535
+ "overall_throughput": 0.0,
536
+ "tasks_sent": 0,
537
+ "tasks_completed": 0,
538
+ "total_duration_seconds": 0.0,
539
+ }
540
+ }
541
+
542
+ analysis = LoadTestService._analyze_performance(result_data)
543
+ assert analysis["throughput_rating"] == "poor"
544
+ assert analysis["queue_pressure"] == "low"
545
+
546
+ # Very high values
547
+ result_data = {
548
+ "metrics": {
549
+ "overall_throughput": 10000.0,
550
+ "tasks_sent": 100000,
551
+ "tasks_completed": 100000,
552
+ "total_duration_seconds": 10.0,
553
+ }
554
+ }
555
+
556
+ analysis = LoadTestService._analyze_performance(result_data)
557
+ assert analysis["throughput_rating"] == "excellent"
558
+ assert analysis["efficiency_rating"] == "excellent"
559
+
560
+
561
+ # Error conditions and boundary testing
562
+ class TestLoadTestServiceErrorHandling:
563
+ """Test error handling in LoadTestService."""
564
+
565
+ @patch("app.core.config.get_load_test_queue")
566
+ def test_analyze_load_test_result_missing_configuration(self, mock_get_queue):
567
+ """Test analysis with missing configuration."""
568
+ # Mock the queue function to return a valid default
569
+ mock_get_queue.return_value = "load_test"
570
+
571
+ incomplete_result = {
572
+ "test_id": "incomplete",
573
+ "status": "completed",
574
+ # Missing configuration and metrics
575
+ }
576
+
577
+ # Should return a fallback LoadTestResult when validation fails
578
+ result = LoadTestService._analyze_load_test_result(incomplete_result)
579
+ assert isinstance(result, LoadTestResult)
580
+ assert result.status == "failed"
581
+ assert result.test_id == "incomplete"
582
+
583
+ def test_validate_test_execution_with_edge_cases(self):
584
+ """Test validation with edge case conditions."""
585
+ result_data = {"status": "unknown"}
586
+ test_info = {"validation_keys": ["some_key"]}
587
+
588
+ validation = LoadTestService._validate_test_execution(result_data, test_info)
589
+
590
+ assert validation["test_type_verified"] is False
591
+ assert "unknown" in validation["issues"][0]
592
+
593
+ def test_recommendations_empty_metrics(self):
594
+ """Test recommendations generation with empty metrics."""
595
+ empty_result = {"metrics": {}}
596
+
597
+ recommendations = LoadTestService._generate_recommendations(empty_result)
598
+
599
+ # Should handle missing metrics gracefully
600
+ assert isinstance(recommendations, list)
601
+ # Should still generate relevant recommendations based on defaults
602
+ # (likely low throughput)
603
+ assert len(recommendations) >= 1