aegis-stack 0.2.0rc2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aegis/__init__.py +5 -0
- aegis/__main__.py +51 -0
- aegis/cli/__init__.py +6 -0
- aegis/cli/callbacks.py +114 -0
- aegis/cli/interactive.py +611 -0
- aegis/cli/utils.py +70 -0
- aegis/cli/validators.py +34 -0
- aegis/commands/__init__.py +6 -0
- aegis/commands/add.py +353 -0
- aegis/commands/add_service.py +332 -0
- aegis/commands/components.py +35 -0
- aegis/commands/init.py +370 -0
- aegis/commands/remove.py +227 -0
- aegis/commands/services.py +52 -0
- aegis/commands/update.py +252 -0
- aegis/commands/version.py +12 -0
- aegis/config/__init__.py +1 -0
- aegis/config/shared_files.py +136 -0
- aegis/core/CLAUDE.md +377 -0
- aegis/core/__init__.py +6 -0
- aegis/core/component_files.py +228 -0
- aegis/core/component_utils.py +220 -0
- aegis/core/components.py +127 -0
- aegis/core/copier_manager.py +315 -0
- aegis/core/copier_updater.py +475 -0
- aegis/core/dependency_resolver.py +119 -0
- aegis/core/manual_updater.py +554 -0
- aegis/core/post_gen_tasks.py +547 -0
- aegis/core/service_resolver.py +261 -0
- aegis/core/services.py +157 -0
- aegis/core/template_generator.py +266 -0
- aegis/core/version_compatibility.py +259 -0
- aegis/templates/CLAUDE.md +591 -0
- aegis/templates/cookiecutter-aegis-project/cookiecutter.json +39 -0
- aegis/templates/cookiecutter-aegis-project/hooks/post_gen_project.py +214 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.dockerignore +71 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.env.example.j2 +130 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.gitignore +131 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Dockerfile +53 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Makefile +236 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/README.md.j2 +196 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/alembic.ini.j2 +111 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/env.py.j2 +91 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/script.py.mako +25 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/versions/001_initial_auth.py.j2 +51 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/__init__.py +5 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/__init__.py +6 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/ai.py.j2 +700 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/ai_rendering.py +361 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/auth.py.j2 +253 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/health.py.j2 +419 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/load_test.py.j2 +656 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/main.py.j2 +65 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/marko_terminal_renderer.py +489 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/tasks.py.j2 +328 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/{% if cookiecutter.include_scheduler == /"yes/" %}tasks.py{% endif %}" +340 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/ai/__init__.py +8 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/ai/router.py +329 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/auth/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/auth/router.py +64 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/deps.py +58 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/health.py +163 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/models.py.j2 +280 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/routing.py.j2 +32 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/scheduler.py.j2 +121 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/worker.py.j2 +478 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/hooks.py +144 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/main.py +31 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/cors.py +20 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/cleanup.py +14 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/component_health.py.j2 +418 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/database_init.py.j2 +83 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/__init__.py +5 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/__init__.py +27 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/table.py +78 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/text.py +142 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/__init__.py.j2 +47 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/ai_card.py +287 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/auth_card.py +198 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/base_card.py +256 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/card_factory.py +227 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/card_utils.py +333 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/database_card.py +420 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/fastapi_card.py +328 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/flet_card.py +267 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/redis_card.py +322 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/scheduler_card.py +352 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/services_card.py +233 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/worker_card.py +684 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/main.py.j2 +653 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/theme.py +48 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/main.py.j2 +156 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/CLAUDE.md.j2 +213 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/__init__.py +6 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/constants.py.j2 +30 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/pools.py +97 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/load_test.py +55 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/media.py +49 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/system.py +44 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/registry.py +139 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/__init__.py +120 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/load_tasks.py +507 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/simple_system_tasks.py +33 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/system_tasks.py +281 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/config.py.j2 +178 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/constants.py +58 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/db.py.j2 +176 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/log.py +92 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/security.py +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/webserver.py +40 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/{% if cookiecutter.include_scheduler == /"yes/" %}scheduler.py{% endif %}" +21 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/main.py +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/models/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/models/user.py +44 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/py.typed +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/__init__.py +8 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/config.py +130 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/conversation.py +213 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/health.py +96 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/models.py +229 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/providers.py +370 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/service.py +388 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/auth_service.py +41 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/health.py +164 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/user_service.py +83 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/middleware_inspector.py.j2 +223 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/models.py.j2 +70 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/route_inspector.py.j2 +155 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test.py +679 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test_models.py +266 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/__init__.py.j2 +21 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/models.py.j2 +119 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/scheduled_task_manager.py.j2 +273 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/task_monitor.py.j2 +189 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/__init__.py +15 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/models.py +26 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/__init__.py +52 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/alerts.py +94 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/backup.py.j2 +119 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/health.py.j2 +1333 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/models.py +243 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/ui.py +52 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto-dark.png +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto-square-backup.png +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto.png +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.dockerignore +71 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.env.example.j2 +64 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.gitignore +131 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/Dockerfile +53 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/Makefile +211 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/README.md.j2 +172 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/docker-compose.yml.j2 +78 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/mkdocs.yml.j2 +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/pyproject.toml.j2 +120 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/uv.lock +1673 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docker-compose.yml.j2 +200 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/api.md +191 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md.j2 +621 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/development.md +215 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/health.md +240 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/javascripts/mermaid-config.js +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/stylesheets/mermaid.css +95 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/mkdocs.yml.j2 +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/pyproject.toml.j2 +131 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh +87 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh.j2 +93 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/gen_docs.py +16 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_auth_endpoints.py.j2 +307 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_health_endpoints.py.j2 +262 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_scheduler_endpoints.py.j2 +214 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_worker_endpoints.py.j2 +165 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/cli/test_ai_rendering.py +427 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/cli/test_conversation_memory.py +465 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/components/test_scheduler.py +43 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/conftest.py.j2 +195 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/conftest.py +78 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_health.py +157 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_models.py +164 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_service.py +198 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_auth_integration.py.j2 +528 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_component_integration.py.j2 +387 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_conversation_persistence.py +342 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_health_logic.py.j2 +663 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_models.py +619 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_service.py +603 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_middleware_inspector.py.j2 +248 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_scheduled_task_manager.py.j2 +292 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_system_service.py +98 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_worker_health_registration.py.j2 +257 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/test_core.py +49 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/uv.lock +1673 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/.copier-answers.yml.jinja +21 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/.dockerignore +71 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/.env.example.jinja +130 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/.gitignore +131 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/Dockerfile +53 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/Makefile.jinja +236 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/README.md.jinja +196 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/alembic.ini.jinja +111 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/env.py.jinja +91 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/script.py.mako +25 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/versions/001_initial_auth.py.jinja +51 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/__init__.py.jinja +5 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/__init__.py.jinja +6 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/ai.py.jinja +700 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/ai_rendering.py +360 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/auth.py.jinja +253 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/health.py.jinja +419 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/load_test.py.jinja +656 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/main.py.jinja +65 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/marko_terminal_renderer.py +489 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/tasks.py.jinja +328 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/__init__.py +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/__init__.py +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/ai/__init__.py +8 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/ai/router.py +329 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/auth/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/auth/router.py +64 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/deps.py +58 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/health.py.jinja +163 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/models.py.jinja +280 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/routing.py.jinja +32 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/scheduler.py.jinja +121 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/worker.py.jinja +478 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/hooks.py +144 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/main.py +31 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/middleware/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/middleware/cors.py +20 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/shutdown/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/shutdown/cleanup.py +14 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/component_health.py.jinja +418 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/database_init.py.jinja +83 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/__init__.py +5 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/__init__.py +27 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/table.py +78 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/text.py +142 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/__init__.py.jinja +47 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/ai_card.py +287 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/auth_card.py +198 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/base_card.py +256 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/card_factory.py +227 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/card_utils.py +333 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/database_card.py +420 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/fastapi_card.py +328 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/flet_card.py +267 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/redis_card.py +322 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/scheduler_card.py +352 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/services_card.py +233 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/worker_card.py +684 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/main.py.jinja +653 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/theme.py +48 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/scheduler/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/scheduler/main.py.jinja +156 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/CLAUDE.md.jinja +213 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/__init__.py +6 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/constants.py.jinja +30 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/pools.py +97 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/load_test.py +55 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/media.py +49 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/system.py +44 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/registry.py +139 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/__init__.py +120 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/load_tasks.py +507 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/simple_system_tasks.py +33 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/system_tasks.py +281 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/config.py.jinja +178 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/constants.py +58 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/db.py.jinja +176 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/log.py +92 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/security.py +62 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/scheduler.py.jinja +21 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/webserver.py +39 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/integrations/__init__.py +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/integrations/main.py +61 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/models/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/models/user.py +44 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/py.typed +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/__init__.py +8 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/config.py +130 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/conversation.py +213 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/health.py +96 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/models.py +229 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/providers.py.jinja +370 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/service.py +387 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/auth_service.py +40 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/health.py +162 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/user_service.py +82 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/middleware_inspector.py.jinja +223 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/models.py.jinja +70 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/route_inspector.py.jinja +155 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/load_test.py +678 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/load_test_models.py +265 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/__init__.py.jinja +21 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/models.py.jinja +119 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/scheduled_task_manager.py.jinja +273 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/task_monitor.py.jinja +189 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/shared/__init__.py +15 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/shared/models.py +26 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/__init__.py +52 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/alerts.py +94 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/backup.py.jinja +119 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/health.py.jinja +1333 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/models.py +243 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/ui.py +52 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57223!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57224!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57225!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57533!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57534!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57538!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57897!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57898!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57904!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58315!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58316!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58324!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58837!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58838!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58849!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/.env.example.jinja +64 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/README.md.jinja +172 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/docker-compose.yml.jinja +78 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/mkdocs.yml.jinja +62 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/pyproject.toml.jinja +120 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docker-compose.yml.jinja +200 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/api.md.jinja +191 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/components/scheduler.md +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/components/scheduler.md.jinja +621 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/development.md.jinja +215 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/health.md.jinja +240 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/javascripts/mermaid-config.js +62 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/stylesheets/mermaid.css +95 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/mkdocs.yml.jinja +62 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/pyproject.toml.jinja +131 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/entrypoint.sh +87 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/entrypoint.sh.jinja +93 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/gen_docs.py +16 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_auth_endpoints.py.jinja +307 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_health_endpoints.py.jinja +262 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_scheduler_endpoints.py.jinja +214 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_worker_endpoints.py.jinja +165 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/cli/test_ai_rendering.py +427 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/cli/test_conversation_memory.py +465 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/components/test_scheduler.py +43 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/conftest.py.jinja +195 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/conftest.py +78 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_health.py +157 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_models.py +164 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_service.py +198 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_auth_integration.py.jinja +528 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_component_integration.py.jinja +387 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_conversation_persistence.py +342 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_health_logic.py.jinja +663 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_load_test_models.py +619 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_load_test_service.py +603 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_middleware_inspector.py.jinja +248 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_scheduled_task_manager.py.jinja +292 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_system_service.py +98 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_worker_health_registration.py.jinja +257 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/test_core.py +49 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/uv.lock +1673 -0
- aegis_stack-0.2.0rc2.dist-info/METADATA +165 -0
- aegis_stack-0.2.0rc2.dist-info/RECORD +392 -0
- aegis_stack-0.2.0rc2.dist-info/WHEEL +4 -0
- aegis_stack-0.2.0rc2.dist-info/entry_points.txt +3 -0
- aegis_stack-0.2.0rc2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Integration tests for authentication services.
|
|
3
|
+
|
|
4
|
+
This module tests the integration between auth services, database,
|
|
5
|
+
and other components in realistic scenarios.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
from app.core.security import create_access_token, verify_password, verify_token
|
|
10
|
+
from app.models.user import User, UserCreate
|
|
11
|
+
from app.services.auth.auth_service import get_current_user_from_token
|
|
12
|
+
from app.services.auth.user_service import UserService
|
|
13
|
+
from fastapi import HTTPException
|
|
14
|
+
from sqlmodel import Session, select
|
|
15
|
+
from sqlmodel.ext.asyncio.session import AsyncSession
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestAuthServiceIntegration:
|
|
19
|
+
"""Test auth service integration with database and security."""
|
|
20
|
+
|
|
21
|
+
@pytest.mark.asyncio
|
|
22
|
+
async def test_user_creation_and_retrieval(self, async_db_session: AsyncSession):
|
|
23
|
+
"""Test complete user creation and retrieval flow."""
|
|
24
|
+
user_service = UserService(async_db_session)
|
|
25
|
+
|
|
26
|
+
# Create user data
|
|
27
|
+
user_data = UserCreate(
|
|
28
|
+
email="integration@example.com",
|
|
29
|
+
full_name="Integration Test User",
|
|
30
|
+
password="integrationpassword123"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Create user
|
|
34
|
+
created_user = await user_service.create_user(user_data)
|
|
35
|
+
|
|
36
|
+
# Verify user was created correctly
|
|
37
|
+
assert created_user.id is not None
|
|
38
|
+
assert created_user.email == user_data.email
|
|
39
|
+
assert created_user.full_name == user_data.full_name
|
|
40
|
+
assert created_user.is_active is True
|
|
41
|
+
# Password should be hashed
|
|
42
|
+
assert created_user.hashed_password != user_data.password
|
|
43
|
+
assert verify_password(user_data.password, created_user.hashed_password)
|
|
44
|
+
|
|
45
|
+
# Retrieve user from database
|
|
46
|
+
retrieved_user = await user_service.get_user_by_email(user_data.email)
|
|
47
|
+
assert retrieved_user is not None
|
|
48
|
+
assert retrieved_user.id == created_user.id
|
|
49
|
+
assert retrieved_user.email == created_user.email
|
|
50
|
+
assert retrieved_user.hashed_password == created_user.hashed_password
|
|
51
|
+
|
|
52
|
+
@pytest.mark.asyncio
|
|
53
|
+
async def test_user_authentication_flow(self, async_db_session: AsyncSession):
|
|
54
|
+
"""Test complete user authentication flow."""
|
|
55
|
+
user_service = UserService(async_db_session)
|
|
56
|
+
|
|
57
|
+
# Create user
|
|
58
|
+
user_data = UserCreate(
|
|
59
|
+
email="authflow@example.com",
|
|
60
|
+
full_name="Auth Flow User",
|
|
61
|
+
password="authflowpassword"
|
|
62
|
+
)
|
|
63
|
+
created_user = await user_service.create_user(user_data)
|
|
64
|
+
|
|
65
|
+
# Simulate login: verify credentials and create token
|
|
66
|
+
retrieved_user = await user_service.get_user_by_email(user_data.email)
|
|
67
|
+
assert retrieved_user is not None
|
|
68
|
+
|
|
69
|
+
# Verify password
|
|
70
|
+
is_valid_password = verify_password(
|
|
71
|
+
user_data.password, retrieved_user.hashed_password
|
|
72
|
+
)
|
|
73
|
+
assert is_valid_password
|
|
74
|
+
|
|
75
|
+
# Create access token
|
|
76
|
+
access_token = create_access_token(data={"sub": retrieved_user.email})
|
|
77
|
+
assert access_token is not None
|
|
78
|
+
assert len(access_token) > 0
|
|
79
|
+
|
|
80
|
+
# Verify token
|
|
81
|
+
payload = verify_token(access_token)
|
|
82
|
+
assert payload is not None
|
|
83
|
+
assert payload["sub"] == retrieved_user.email
|
|
84
|
+
|
|
85
|
+
# Get user from token (simulates protected endpoint)
|
|
86
|
+
token_user = await get_current_user_from_token(access_token, async_db_session)
|
|
87
|
+
assert token_user.id == created_user.id
|
|
88
|
+
assert token_user.email == created_user.email
|
|
89
|
+
|
|
90
|
+
@pytest.mark.asyncio
|
|
91
|
+
async def test_multiple_users_isolation(self, async_db_session: AsyncSession):
|
|
92
|
+
"""Test that multiple users are properly isolated."""
|
|
93
|
+
user_service = UserService(async_db_session)
|
|
94
|
+
|
|
95
|
+
# Create multiple users
|
|
96
|
+
users_data = [
|
|
97
|
+
UserCreate(
|
|
98
|
+
email="user1@example.com", full_name="User One", password="password1"
|
|
99
|
+
),
|
|
100
|
+
UserCreate(
|
|
101
|
+
email="user2@example.com", full_name="User Two", password="password2"
|
|
102
|
+
),
|
|
103
|
+
UserCreate(
|
|
104
|
+
email="user3@example.com", full_name="User Three", password="password3"
|
|
105
|
+
),
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
created_users = []
|
|
109
|
+
for user_data in users_data:
|
|
110
|
+
created_user = await user_service.create_user(user_data)
|
|
111
|
+
created_users.append(created_user)
|
|
112
|
+
|
|
113
|
+
# Verify each user can be retrieved independently
|
|
114
|
+
for i, user_data in enumerate(users_data):
|
|
115
|
+
retrieved_user = await user_service.get_user_by_email(user_data.email)
|
|
116
|
+
assert retrieved_user is not None
|
|
117
|
+
assert retrieved_user.id == created_users[i].id
|
|
118
|
+
assert retrieved_user.email == user_data.email
|
|
119
|
+
assert verify_password(user_data.password, retrieved_user.hashed_password)
|
|
120
|
+
|
|
121
|
+
# Verify passwords are different
|
|
122
|
+
for i in range(len(created_users)):
|
|
123
|
+
for j in range(i + 1, len(created_users)):
|
|
124
|
+
assert (
|
|
125
|
+
created_users[i].hashed_password != created_users[j].hashed_password
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
@pytest.mark.asyncio
|
|
129
|
+
async def test_invalid_token_handling(self, async_db_session: AsyncSession):
|
|
130
|
+
"""Test handling of invalid tokens."""
|
|
131
|
+
# Test with completely invalid token
|
|
132
|
+
with pytest.raises(HTTPException) as exc_info:
|
|
133
|
+
await get_current_user_from_token("invalid_token", async_db_session)
|
|
134
|
+
assert exc_info.value.status_code == 401
|
|
135
|
+
|
|
136
|
+
# Test with malformed token
|
|
137
|
+
with pytest.raises(HTTPException) as exc_info:
|
|
138
|
+
await get_current_user_from_token("Bearer invalid_token", async_db_session)
|
|
139
|
+
assert exc_info.value.status_code == 401
|
|
140
|
+
|
|
141
|
+
# Test with token for non-existent user
|
|
142
|
+
fake_token = create_access_token(data={"sub": "nonexistent@example.com"})
|
|
143
|
+
with pytest.raises(HTTPException) as exc_info:
|
|
144
|
+
await get_current_user_from_token(fake_token, async_db_session)
|
|
145
|
+
assert exc_info.value.status_code == 401
|
|
146
|
+
|
|
147
|
+
@pytest.mark.asyncio
|
|
148
|
+
async def test_user_deactivation_flow(self, async_db_session: AsyncSession):
|
|
149
|
+
"""Test user deactivation prevents authentication."""
|
|
150
|
+
user_service = UserService(async_db_session)
|
|
151
|
+
|
|
152
|
+
# Create active user
|
|
153
|
+
user_data = UserCreate(
|
|
154
|
+
email="deactivate@example.com",
|
|
155
|
+
full_name="Deactivate User",
|
|
156
|
+
password="deactivatepassword"
|
|
157
|
+
)
|
|
158
|
+
created_user = await user_service.create_user(user_data)
|
|
159
|
+
assert created_user.is_active is True
|
|
160
|
+
|
|
161
|
+
# Create token for active user
|
|
162
|
+
token = create_access_token(data={"sub": created_user.email})
|
|
163
|
+
|
|
164
|
+
# Verify token works for active user
|
|
165
|
+
active_user = await get_current_user_from_token(token, async_db_session)
|
|
166
|
+
assert active_user.id == created_user.id
|
|
167
|
+
|
|
168
|
+
# Deactivate user using async service
|
|
169
|
+
await user_service.deactivate_user(created_user.id)
|
|
170
|
+
|
|
171
|
+
# Verify deactivated user cannot authenticate
|
|
172
|
+
with pytest.raises(HTTPException) as exc_info:
|
|
173
|
+
await get_current_user_from_token(token, async_db_session)
|
|
174
|
+
assert exc_info.value.status_code == 401
|
|
175
|
+
|
|
176
|
+
@pytest.mark.asyncio
|
|
177
|
+
async def test_email_uniqueness_constraint(self, async_db_session: AsyncSession):
|
|
178
|
+
"""Test that email uniqueness is enforced."""
|
|
179
|
+
user_service = UserService(async_db_session)
|
|
180
|
+
|
|
181
|
+
# Create first user
|
|
182
|
+
user_data1 = UserCreate(
|
|
183
|
+
email="unique@example.com",
|
|
184
|
+
full_name="First User",
|
|
185
|
+
password="password1"
|
|
186
|
+
)
|
|
187
|
+
created_user1 = await user_service.create_user(user_data1)
|
|
188
|
+
assert created_user1.id is not None
|
|
189
|
+
|
|
190
|
+
# Try to create second user with same email
|
|
191
|
+
user_data2 = UserCreate(
|
|
192
|
+
email="unique@example.com", # Same email
|
|
193
|
+
full_name="Second User",
|
|
194
|
+
password="password2"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# This should raise an exception or return None depending on implementation
|
|
198
|
+
try:
|
|
199
|
+
created_user2 = await user_service.create_user(user_data2)
|
|
200
|
+
# If it doesn't raise an exception, it should return None
|
|
201
|
+
assert created_user2 is None
|
|
202
|
+
except Exception:
|
|
203
|
+
# Exception is acceptable for duplicate email
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
@pytest.mark.asyncio
|
|
207
|
+
async def test_password_hashing_security(self, async_db_session: AsyncSession):
|
|
208
|
+
"""Test password hashing security properties."""
|
|
209
|
+
user_service = UserService(async_db_session)
|
|
210
|
+
|
|
211
|
+
password = "securepassword123"
|
|
212
|
+
user_data = UserCreate(
|
|
213
|
+
email="security@example.com",
|
|
214
|
+
full_name="Security User",
|
|
215
|
+
password=password
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
created_user = await user_service.create_user(user_data)
|
|
219
|
+
|
|
220
|
+
# Store hash before session expires
|
|
221
|
+
hash1 = created_user.hashed_password
|
|
222
|
+
|
|
223
|
+
# Verify password is not stored in plain text
|
|
224
|
+
assert hash1 != password
|
|
225
|
+
|
|
226
|
+
# Verify password hash is different each time (salt)
|
|
227
|
+
user_data2 = UserCreate(
|
|
228
|
+
email="security2@example.com",
|
|
229
|
+
full_name="Security User 2",
|
|
230
|
+
password=password # Same password
|
|
231
|
+
)
|
|
232
|
+
created_user2 = await user_service.create_user(user_data2)
|
|
233
|
+
|
|
234
|
+
# Store hash before session expires
|
|
235
|
+
hash2 = created_user2.hashed_password
|
|
236
|
+
|
|
237
|
+
# Hashes should be different due to salt
|
|
238
|
+
assert hash1 != hash2
|
|
239
|
+
|
|
240
|
+
# But both should verify correctly
|
|
241
|
+
assert verify_password(password, hash1)
|
|
242
|
+
assert verify_password(password, hash2)
|
|
243
|
+
|
|
244
|
+
# Wrong password should not verify
|
|
245
|
+
assert not verify_password("wrongpassword", hash1)
|
|
246
|
+
|
|
247
|
+
@pytest.mark.asyncio
|
|
248
|
+
async def test_database_transaction_rollback(self, async_db_session: AsyncSession):
|
|
249
|
+
"""Test that database operations work correctly with transactions."""
|
|
250
|
+
user_service = UserService(async_db_session)
|
|
251
|
+
|
|
252
|
+
# Create user
|
|
253
|
+
user_data = UserCreate(
|
|
254
|
+
email="transaction@example.com",
|
|
255
|
+
full_name="Transaction User",
|
|
256
|
+
password="transactionpassword"
|
|
257
|
+
)
|
|
258
|
+
await user_service.create_user(user_data)
|
|
259
|
+
|
|
260
|
+
# Verify user exists before committing
|
|
261
|
+
retrieved_user = await user_service.get_user_by_email(user_data.email)
|
|
262
|
+
assert retrieved_user is not None
|
|
263
|
+
assert retrieved_user.email == user_data.email
|
|
264
|
+
|
|
265
|
+
# Note: In test context, transactions are typically rolled back
|
|
266
|
+
# This test ensures the user service works within transaction boundaries
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class TestAuthMigrationIntegration:
|
|
270
|
+
"""Test auth service migration and database initialization."""
|
|
271
|
+
|
|
272
|
+
@pytest.mark.asyncio
|
|
273
|
+
async def test_user_table_exists_after_migrations(
|
|
274
|
+
self, async_db_session: AsyncSession
|
|
275
|
+
):
|
|
276
|
+
"""Test that user table exists and has correct structure after migrations."""
|
|
277
|
+
# This test verifies migrations ran successfully
|
|
278
|
+
# Try to query user table - should not raise "no such table" error
|
|
279
|
+
stmt = select(User)
|
|
280
|
+
try:
|
|
281
|
+
result = await async_db_session.exec(stmt)
|
|
282
|
+
users = result.all()
|
|
283
|
+
# If we get here, table exists (even if empty)
|
|
284
|
+
assert isinstance(users, list)
|
|
285
|
+
except Exception as e:
|
|
286
|
+
pytest.fail(f"User table does not exist or has wrong structure: {e}")
|
|
287
|
+
|
|
288
|
+
@pytest.mark.asyncio
|
|
289
|
+
async def test_user_table_has_required_columns(
|
|
290
|
+
self, async_db_session: AsyncSession
|
|
291
|
+
):
|
|
292
|
+
"""Test that user table has all required columns from migration."""
|
|
293
|
+
user_service = UserService(async_db_session)
|
|
294
|
+
|
|
295
|
+
# Create a user to test all columns are available
|
|
296
|
+
user_data = UserCreate(
|
|
297
|
+
email="column_test@example.com",
|
|
298
|
+
full_name="Column Test User",
|
|
299
|
+
password="columntest123"
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
try:
|
|
303
|
+
created_user = await user_service.create_user(user_data)
|
|
304
|
+
|
|
305
|
+
# Verify all expected columns are accessible
|
|
306
|
+
assert created_user.id is not None
|
|
307
|
+
assert created_user.email == user_data.email
|
|
308
|
+
assert created_user.full_name == user_data.full_name
|
|
309
|
+
assert created_user.hashed_password is not None
|
|
310
|
+
assert created_user.is_active is not None
|
|
311
|
+
assert created_user.created_at is not None
|
|
312
|
+
# updated_at may be None for new users
|
|
313
|
+
assert hasattr(created_user, 'updated_at')
|
|
314
|
+
|
|
315
|
+
except Exception as e:
|
|
316
|
+
pytest.fail(f"User table missing required columns: {e}")
|
|
317
|
+
|
|
318
|
+
@pytest.mark.asyncio
|
|
319
|
+
async def test_email_index_exists_and_enforces_uniqueness(
|
|
320
|
+
self, async_db_session: AsyncSession
|
|
321
|
+
):
|
|
322
|
+
"""Test that email index from migration enforces uniqueness."""
|
|
323
|
+
user_service = UserService(async_db_session)
|
|
324
|
+
|
|
325
|
+
# Create first user
|
|
326
|
+
user_data = UserCreate(
|
|
327
|
+
email="index_test@example.com",
|
|
328
|
+
full_name="Index Test User",
|
|
329
|
+
password="indextest123"
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
created_user = await user_service.create_user(user_data)
|
|
333
|
+
assert created_user is not None
|
|
334
|
+
|
|
335
|
+
# Try to create user with same email
|
|
336
|
+
duplicate_user_data = UserCreate(
|
|
337
|
+
email="index_test@example.com", # Same email
|
|
338
|
+
full_name="Duplicate User",
|
|
339
|
+
password="duplicate123"
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
# Should either return None or raise exception due to unique constraint
|
|
343
|
+
try:
|
|
344
|
+
duplicate_user = await user_service.create_user(duplicate_user_data)
|
|
345
|
+
# If no exception, should return None
|
|
346
|
+
assert duplicate_user is None
|
|
347
|
+
except Exception:
|
|
348
|
+
# Exception is expected for unique constraint violation
|
|
349
|
+
pass
|
|
350
|
+
|
|
351
|
+
@pytest.mark.asyncio
|
|
352
|
+
async def test_auth_endpoints_work_without_table_errors(
|
|
353
|
+
self, async_db_session: AsyncSession
|
|
354
|
+
):
|
|
355
|
+
"""Test that auth operations don't produce 'no such table' errors."""
|
|
356
|
+
user_service = UserService(async_db_session)
|
|
357
|
+
|
|
358
|
+
# Test user creation (tests INSERT)
|
|
359
|
+
user_data = UserCreate(
|
|
360
|
+
email="endpoint_test@example.com",
|
|
361
|
+
full_name="Endpoint Test User",
|
|
362
|
+
password="endpointtest123"
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
try:
|
|
366
|
+
# Should not raise "no such table: user" error
|
|
367
|
+
created_user = await user_service.create_user(user_data)
|
|
368
|
+
assert created_user is not None
|
|
369
|
+
|
|
370
|
+
# Test user retrieval (tests SELECT)
|
|
371
|
+
retrieved_user = await user_service.get_user_by_email(user_data.email)
|
|
372
|
+
assert retrieved_user is not None
|
|
373
|
+
assert retrieved_user.email == user_data.email
|
|
374
|
+
|
|
375
|
+
except Exception as e:
|
|
376
|
+
if "no such table" in str(e).lower():
|
|
377
|
+
pytest.fail(
|
|
378
|
+
"Auth endpoints failed with 'no such table' error - "
|
|
379
|
+
"migrations not run properly"
|
|
380
|
+
)
|
|
381
|
+
else:
|
|
382
|
+
# Re-raise other exceptions
|
|
383
|
+
raise
|
|
384
|
+
|
|
385
|
+
@pytest.mark.asyncio
|
|
386
|
+
async def test_auth_service_works_end_to_end(self, async_db_session: AsyncSession):
|
|
387
|
+
"""Test complete auth flow works without database errors."""
|
|
388
|
+
user_service = UserService(async_db_session)
|
|
389
|
+
|
|
390
|
+
# Complete flow: create user -> login -> authenticate
|
|
391
|
+
user_data = UserCreate(
|
|
392
|
+
email="e2e_test@example.com",
|
|
393
|
+
full_name="End to End User",
|
|
394
|
+
password="e2etest123"
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
try:
|
|
398
|
+
# Create user
|
|
399
|
+
created_user = await user_service.create_user(user_data)
|
|
400
|
+
assert created_user is not None
|
|
401
|
+
|
|
402
|
+
# Verify login credentials
|
|
403
|
+
retrieved_user = await user_service.get_user_by_email(user_data.email)
|
|
404
|
+
assert retrieved_user is not None
|
|
405
|
+
assert verify_password(user_data.password, retrieved_user.hashed_password)
|
|
406
|
+
|
|
407
|
+
# Create and verify token
|
|
408
|
+
token = create_access_token(data={"sub": retrieved_user.email})
|
|
409
|
+
assert token is not None
|
|
410
|
+
|
|
411
|
+
# Authenticate with token
|
|
412
|
+
authenticated_user = await get_current_user_from_token(
|
|
413
|
+
token, async_db_session
|
|
414
|
+
)
|
|
415
|
+
assert authenticated_user.id == created_user.id
|
|
416
|
+
|
|
417
|
+
except Exception as e:
|
|
418
|
+
if "no such table" in str(e).lower() or "database" in str(e).lower():
|
|
419
|
+
pytest.fail(f"End-to-end auth flow failed with database error: {e}")
|
|
420
|
+
else:
|
|
421
|
+
# Re-raise other exceptions
|
|
422
|
+
raise
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
class TestAuthDatabaseInitialization:
|
|
426
|
+
"""Test auth database initialization and setup."""
|
|
427
|
+
|
|
428
|
+
@pytest.mark.asyncio
|
|
429
|
+
async def test_database_session_works_with_auth_models(
|
|
430
|
+
self, async_db_session: AsyncSession
|
|
431
|
+
):
|
|
432
|
+
"""Test that database session properly handles auth models."""
|
|
433
|
+
# Verify we can create, read, update, delete with User model
|
|
434
|
+
user_data = UserCreate(
|
|
435
|
+
email="crud_test@example.com",
|
|
436
|
+
full_name="CRUD Test User",
|
|
437
|
+
password="crudtest123"
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
user_service = UserService(async_db_session)
|
|
441
|
+
|
|
442
|
+
# CREATE
|
|
443
|
+
created_user = await user_service.create_user(user_data)
|
|
444
|
+
assert created_user is not None
|
|
445
|
+
original_id = created_user.id
|
|
446
|
+
|
|
447
|
+
# READ
|
|
448
|
+
read_user = await user_service.get_user_by_email(user_data.email)
|
|
449
|
+
assert read_user is not None
|
|
450
|
+
assert read_user.id == original_id
|
|
451
|
+
|
|
452
|
+
# UPDATE (via direct SQLModel operations)
|
|
453
|
+
stmt = select(User).where(User.id == original_id)
|
|
454
|
+
result = await async_db_session.exec(stmt)
|
|
455
|
+
user_to_update = result.first()
|
|
456
|
+
assert user_to_update is not None
|
|
457
|
+
|
|
458
|
+
user_to_update.full_name = "Updated Name"
|
|
459
|
+
async_db_session.add(user_to_update)
|
|
460
|
+
await async_db_session.commit()
|
|
461
|
+
|
|
462
|
+
# Verify update
|
|
463
|
+
updated_user = await user_service.get_user_by_email(user_data.email)
|
|
464
|
+
assert updated_user.full_name == "Updated Name"
|
|
465
|
+
|
|
466
|
+
# DELETE (via direct SQLModel operations)
|
|
467
|
+
await async_db_session.delete(updated_user)
|
|
468
|
+
await async_db_session.commit()
|
|
469
|
+
|
|
470
|
+
# Verify deletion
|
|
471
|
+
deleted_user = await user_service.get_user_by_email(user_data.email)
|
|
472
|
+
assert deleted_user is None
|
|
473
|
+
|
|
474
|
+
@pytest.mark.asyncio
|
|
475
|
+
async def test_database_connection_pool_works(self, async_db_session: AsyncSession):
|
|
476
|
+
"""Test that database connection works reliably for auth operations."""
|
|
477
|
+
user_service = UserService(async_db_session)
|
|
478
|
+
|
|
479
|
+
# Perform multiple operations to test connection stability
|
|
480
|
+
users_created = []
|
|
481
|
+
for i in range(5):
|
|
482
|
+
user_data = UserCreate(
|
|
483
|
+
email=f"pool_test_{i}@example.com",
|
|
484
|
+
full_name=f"Pool Test User {i}",
|
|
485
|
+
password=f"pooltest{i}123"
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
created_user = await user_service.create_user(user_data)
|
|
489
|
+
assert created_user is not None
|
|
490
|
+
users_created.append(created_user)
|
|
491
|
+
|
|
492
|
+
# Verify all users can be retrieved
|
|
493
|
+
for i, user in enumerate(users_created):
|
|
494
|
+
email = f"pool_test_{i}@example.com"
|
|
495
|
+
retrieved_user = await user_service.get_user_by_email(email)
|
|
496
|
+
assert retrieved_user is not None
|
|
497
|
+
assert retrieved_user.id == user.id
|
|
498
|
+
|
|
499
|
+
@pytest.mark.asyncio
|
|
500
|
+
async def test_migration_schema_matches_model_definitions(
|
|
501
|
+
self, async_db_session: AsyncSession
|
|
502
|
+
):
|
|
503
|
+
"""Test that migration schema matches current User model."""
|
|
504
|
+
# Create user to test all model fields work with database schema
|
|
505
|
+
user_data = UserCreate(
|
|
506
|
+
email="schema_test@example.com",
|
|
507
|
+
full_name="Schema Test User",
|
|
508
|
+
password="schematest123"
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
user_service = UserService(async_db_session)
|
|
512
|
+
created_user = await user_service.create_user(user_data)
|
|
513
|
+
|
|
514
|
+
# Test that all User model fields are supported by current schema
|
|
515
|
+
# This catches cases where model was updated but migrations weren't
|
|
516
|
+
try:
|
|
517
|
+
# Access all fields to ensure they exist in database
|
|
518
|
+
assert created_user.id is not None
|
|
519
|
+
assert created_user.email is not None
|
|
520
|
+
assert created_user.full_name is not None
|
|
521
|
+
assert created_user.hashed_password is not None
|
|
522
|
+
assert created_user.is_active is not None
|
|
523
|
+
assert created_user.created_at is not None
|
|
524
|
+
# updated_at may be None initially
|
|
525
|
+
assert hasattr(created_user, 'updated_at')
|
|
526
|
+
|
|
527
|
+
except Exception as e:
|
|
528
|
+
pytest.fail(f"User model fields don't match database schema: {e}")
|