fastapi-fullstack 0.1.7__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.
- fastapi_fullstack-0.1.7.dist-info/METADATA +739 -0
- fastapi_fullstack-0.1.7.dist-info/RECORD +241 -0
- fastapi_fullstack-0.1.7.dist-info/WHEEL +4 -0
- fastapi_fullstack-0.1.7.dist-info/entry_points.txt +2 -0
- fastapi_fullstack-0.1.7.dist-info/licenses/LICENSE +21 -0
- fastapi_gen/__init__.py +3 -0
- fastapi_gen/cli.py +442 -0
- fastapi_gen/config.py +356 -0
- fastapi_gen/generator.py +207 -0
- fastapi_gen/prompts.py +874 -0
- fastapi_gen/template/VARIABLES.md +276 -0
- fastapi_gen/template/cookiecutter.json +93 -0
- fastapi_gen/template/hooks/post_gen_project.py +355 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/.env.prod.example +56 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +150 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/.gitignore +109 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/AGENTS.md +55 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/CLAUDE.md +99 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/Makefile +315 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/README.md +768 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.dockerignore +60 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.env.example +155 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.pre-commit-config.yaml +32 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/Dockerfile +56 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/env.py +76 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/script.py.mako +30 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/versions/.gitkeep +0 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic.ini +48 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/__init__.py +3 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/admin.py +447 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/__init__.py +23 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/assistant.py +226 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/langchain_assistant.py +226 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/prompts.py +10 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/__init__.py +13 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/datetime_tool.py +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/deps.py +541 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/exception_handlers.py +98 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/router.py +10 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/__init__.py +9 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/__init__.py +87 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/agent.py +902 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/auth.py +395 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/conversations.py +498 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/health.py +227 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/items.py +275 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/oauth.py +205 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/sessions.py +168 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/users.py +333 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/webhooks.py +477 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/ws.py +46 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/versioning.py +221 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/__init__.py +14 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/redis.py +88 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/__init__.py +117 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/cleanup.py +75 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/example.py +28 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/seed.py +266 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/__init__.py +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/cache.py +23 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/config.py +267 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/csrf.py +153 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/exceptions.py +122 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/logfire_setup.py +101 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/middleware.py +99 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/oauth.py +23 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/rate_limit.py +58 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/sanitize.py +271 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/security.py +102 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/__init__.py +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/base.py +41 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/__init__.py +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/conversation.py +319 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/item.py +96 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/session.py +126 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/user.py +218 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/webhook.py +244 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/session.py +130 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/main.py +334 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/__init__.py +9 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/base.py +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/__init__.py +49 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/base.py +154 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/conversation.py +838 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/item.py +222 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/session.py +318 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/user.py +322 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/webhook.py +358 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/__init__.py +50 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/base.py +57 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/conversation.py +192 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/item.py +52 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/session.py +42 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/token.py +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/user.py +64 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/webhook.py +89 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/__init__.py +38 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/conversation.py +850 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/item.py +246 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/session.py +333 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/user.py +432 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/webhook.py +561 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/__init__.py +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/celery_app.py +64 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/taskiq_app.py +38 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/__init__.py +25 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/examples.py +106 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/schedules.py +29 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/taskiq_examples.py +92 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/commands.py +438 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/pyproject.toml +180 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/scripts/.gitkeep +0 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_auth.py +242 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_exceptions.py +151 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_health.py +113 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_items.py +310 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_users.py +253 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/conftest.py +151 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_admin.py +890 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_agents.py +261 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_clients.py +183 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_commands.py +173 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_core.py +143 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_pipelines.py +118 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_repositories.py +181 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_security.py +124 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_services.py +363 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_worker.py +85 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.dev.yml +242 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.frontend.yml +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.prod.yml +435 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.yml +241 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docs/adding_features.md +132 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docs/architecture.md +63 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docs/patterns.md +161 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docs/testing.md +120 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.dockerignore +40 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.env.example +12 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.gitignore +45 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierignore +19 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierrc +11 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/Dockerfile +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/README.md +648 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.setup.ts +49 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.spec.ts +134 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/chat.spec.ts +207 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/home.spec.ts +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/instrumentation.ts +14 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/en.json +84 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/pl.json +84 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/next.config.ts +76 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/package.json +69 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/playwright.config.ts +101 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/postcss.config.mjs +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/layout.tsx +11 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/login/page.tsx +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/register/page.tsx +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/chat/page.tsx +48 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/dashboard/page.tsx +99 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/layout.tsx +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/profile/page.tsx +152 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/auth/callback/page.tsx +113 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/layout.tsx +46 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/page.tsx +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/login/route.ts +58 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/logout/route.ts +24 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/me/route.ts +39 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/oauth-callback/route.ts +50 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/refresh/route.ts +54 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/register/route.ts +26 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/messages/route.ts +41 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/route.ts +108 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/route.ts +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/health/route.ts +21 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/globals.css +323 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/layout.tsx +22 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/providers.tsx +29 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/index.ts +2 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/login-form.tsx +120 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/register-form.tsx +153 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-container.tsx +234 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-input.tsx +72 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/conversation-sidebar.tsx +328 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/copy-button.tsx +46 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/index.ts +11 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/local-conversation-sidebar.tsx +295 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/markdown-content.tsx +167 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-item.tsx +79 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-list.tsx +18 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/tool-call-card.tsx +79 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/google-icon.tsx +32 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/index.ts +3 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/language-switcher.tsx +97 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/header.tsx +65 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/index.ts +2 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/sidebar.tsx +82 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/index.ts +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-provider.tsx +53 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-toggle.tsx +105 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/badge.tsx +35 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.test.tsx +75 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.tsx +56 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/card.tsx +82 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/index.ts +13 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/input.tsx +21 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/label.tsx +21 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/sheet.tsx +109 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/index.ts +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-auth.ts +97 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-chat.ts +203 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-conversations.ts +181 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-local-chat.ts +165 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-websocket.ts +105 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/i18n.ts +37 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/api-client.ts +90 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/constants.ts +39 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/server-api.ts +78 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.test.ts +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.ts +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/middleware.ts +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.test.ts +72 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.ts +64 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-sidebar-store.ts +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-store.ts +65 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/conversation-store.ts +76 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/index.ts +9 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/local-chat-store.ts +255 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/sidebar-store.ts +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/theme-store.ts +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/api.ts +27 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/auth.ts +52 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/chat.ts +83 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/conversation.ts +49 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/index.ts +10 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/tsconfig.json +28 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.config.ts +36 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.setup.ts +56 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
{%- if cookiecutter.use_jwt %}
|
|
2
|
+
"""Tests for service layer."""
|
|
3
|
+
{%- if cookiecutter.use_postgresql or cookiecutter.use_mongodb %}
|
|
4
|
+
|
|
5
|
+
from unittest.mock import AsyncMock, patch
|
|
6
|
+
{%- elif cookiecutter.use_sqlite %}
|
|
7
|
+
|
|
8
|
+
from unittest.mock import MagicMock, patch
|
|
9
|
+
{%- endif %}
|
|
10
|
+
from uuid import uuid4
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from app.core.exceptions import AlreadyExistsError, AuthenticationError, NotFoundError
|
|
15
|
+
from app.schemas.user import UserCreate, UserUpdate
|
|
16
|
+
from app.services.user import UserService
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MockUser:
|
|
20
|
+
"""Mock user for testing."""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
id=None,
|
|
25
|
+
email="test@example.com",
|
|
26
|
+
full_name="Test User",
|
|
27
|
+
hashed_password="$2b$12$hashedpassword",
|
|
28
|
+
is_active=True,
|
|
29
|
+
is_superuser=False,
|
|
30
|
+
):
|
|
31
|
+
{%- if cookiecutter.use_postgresql %}
|
|
32
|
+
self.id = id or uuid4()
|
|
33
|
+
{%- else %}
|
|
34
|
+
self.id = id or str(uuid4())
|
|
35
|
+
{%- endif %}
|
|
36
|
+
self.email = email
|
|
37
|
+
self.full_name = full_name
|
|
38
|
+
self.hashed_password = hashed_password
|
|
39
|
+
self.is_active = is_active
|
|
40
|
+
self.is_superuser = is_superuser
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
{%- if cookiecutter.use_postgresql %}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class TestUserServicePostgresql:
|
|
47
|
+
"""Tests for UserService with PostgreSQL."""
|
|
48
|
+
|
|
49
|
+
@pytest.fixture
|
|
50
|
+
def mock_db(self) -> AsyncMock:
|
|
51
|
+
"""Create mock database session."""
|
|
52
|
+
return AsyncMock()
|
|
53
|
+
|
|
54
|
+
@pytest.fixture
|
|
55
|
+
def user_service(self, mock_db: AsyncMock) -> UserService:
|
|
56
|
+
"""Create UserService instance with mock db."""
|
|
57
|
+
return UserService(mock_db)
|
|
58
|
+
|
|
59
|
+
@pytest.fixture
|
|
60
|
+
def mock_user(self) -> MockUser:
|
|
61
|
+
"""Create a mock user."""
|
|
62
|
+
return MockUser()
|
|
63
|
+
|
|
64
|
+
@pytest.mark.anyio
|
|
65
|
+
async def test_get_by_id_success(self, user_service: UserService, mock_user: MockUser):
|
|
66
|
+
"""Test getting user by ID successfully."""
|
|
67
|
+
with patch("app.services.user.user_repo") as mock_repo:
|
|
68
|
+
mock_repo.get_by_id = AsyncMock(return_value=mock_user)
|
|
69
|
+
|
|
70
|
+
result = await user_service.get_by_id(mock_user.id)
|
|
71
|
+
|
|
72
|
+
assert result == mock_user
|
|
73
|
+
mock_repo.get_by_id.assert_called_once()
|
|
74
|
+
|
|
75
|
+
@pytest.mark.anyio
|
|
76
|
+
async def test_get_by_id_not_found(self, user_service: UserService):
|
|
77
|
+
"""Test getting non-existent user raises NotFoundError."""
|
|
78
|
+
with patch("app.services.user.user_repo") as mock_repo:
|
|
79
|
+
mock_repo.get_by_id = AsyncMock(return_value=None)
|
|
80
|
+
|
|
81
|
+
with pytest.raises(NotFoundError):
|
|
82
|
+
await user_service.get_by_id(uuid4())
|
|
83
|
+
|
|
84
|
+
@pytest.mark.anyio
|
|
85
|
+
async def test_get_by_email(self, user_service: UserService, mock_user: MockUser):
|
|
86
|
+
"""Test getting user by email."""
|
|
87
|
+
with patch("app.services.user.user_repo") as mock_repo:
|
|
88
|
+
mock_repo.get_by_email = AsyncMock(return_value=mock_user)
|
|
89
|
+
|
|
90
|
+
result = await user_service.get_by_email("test@example.com")
|
|
91
|
+
|
|
92
|
+
assert result == mock_user
|
|
93
|
+
|
|
94
|
+
@pytest.mark.anyio
|
|
95
|
+
async def test_get_multi(self, user_service: UserService, mock_user: MockUser):
|
|
96
|
+
"""Test getting multiple users."""
|
|
97
|
+
with patch("app.services.user.user_repo") as mock_repo:
|
|
98
|
+
mock_repo.get_multi = AsyncMock(return_value=[mock_user])
|
|
99
|
+
|
|
100
|
+
result = await user_service.get_multi(skip=0, limit=10)
|
|
101
|
+
|
|
102
|
+
assert len(result) == 1
|
|
103
|
+
assert result[0] == mock_user
|
|
104
|
+
|
|
105
|
+
@pytest.mark.anyio
|
|
106
|
+
async def test_register_success(self, user_service: UserService, mock_user: MockUser):
|
|
107
|
+
"""Test registering a new user."""
|
|
108
|
+
with patch("app.services.user.user_repo") as mock_repo:
|
|
109
|
+
mock_repo.get_by_email = AsyncMock(return_value=None)
|
|
110
|
+
mock_repo.create = AsyncMock(return_value=mock_user)
|
|
111
|
+
|
|
112
|
+
user_in = UserCreate(
|
|
113
|
+
email="new@example.com",
|
|
114
|
+
password="password123",
|
|
115
|
+
full_name="New User",
|
|
116
|
+
)
|
|
117
|
+
result = await user_service.register(user_in)
|
|
118
|
+
|
|
119
|
+
assert result == mock_user
|
|
120
|
+
mock_repo.create.assert_called_once()
|
|
121
|
+
|
|
122
|
+
@pytest.mark.anyio
|
|
123
|
+
async def test_register_duplicate_email(self, user_service: UserService, mock_user: MockUser):
|
|
124
|
+
"""Test registering with existing email raises AlreadyExistsError."""
|
|
125
|
+
with patch("app.services.user.user_repo") as mock_repo:
|
|
126
|
+
mock_repo.get_by_email = AsyncMock(return_value=mock_user)
|
|
127
|
+
|
|
128
|
+
user_in = UserCreate(
|
|
129
|
+
email="existing@example.com",
|
|
130
|
+
password="password123",
|
|
131
|
+
full_name="Test",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
with pytest.raises(AlreadyExistsError):
|
|
135
|
+
await user_service.register(user_in)
|
|
136
|
+
|
|
137
|
+
@pytest.mark.anyio
|
|
138
|
+
async def test_authenticate_success(self, user_service: UserService, mock_user: MockUser):
|
|
139
|
+
"""Test successful authentication."""
|
|
140
|
+
with (
|
|
141
|
+
patch("app.services.user.user_repo") as mock_repo,
|
|
142
|
+
patch("app.services.user.verify_password", return_value=True),
|
|
143
|
+
):
|
|
144
|
+
mock_repo.get_by_email = AsyncMock(return_value=mock_user)
|
|
145
|
+
|
|
146
|
+
result = await user_service.authenticate("test@example.com", "password123")
|
|
147
|
+
|
|
148
|
+
assert result == mock_user
|
|
149
|
+
|
|
150
|
+
@pytest.mark.anyio
|
|
151
|
+
async def test_authenticate_invalid_password(self, user_service: UserService, mock_user: MockUser):
|
|
152
|
+
"""Test authentication with wrong password."""
|
|
153
|
+
with (
|
|
154
|
+
patch("app.services.user.user_repo") as mock_repo,
|
|
155
|
+
patch("app.services.user.verify_password", return_value=False),
|
|
156
|
+
):
|
|
157
|
+
mock_repo.get_by_email = AsyncMock(return_value=mock_user)
|
|
158
|
+
|
|
159
|
+
with pytest.raises(AuthenticationError):
|
|
160
|
+
await user_service.authenticate("test@example.com", "wrongpassword")
|
|
161
|
+
|
|
162
|
+
@pytest.mark.anyio
|
|
163
|
+
async def test_authenticate_user_not_found(self, user_service: UserService):
|
|
164
|
+
"""Test authentication with non-existent user."""
|
|
165
|
+
with patch("app.services.user.user_repo") as mock_repo:
|
|
166
|
+
mock_repo.get_by_email = AsyncMock(return_value=None)
|
|
167
|
+
|
|
168
|
+
with pytest.raises(AuthenticationError):
|
|
169
|
+
await user_service.authenticate("unknown@example.com", "password")
|
|
170
|
+
|
|
171
|
+
@pytest.mark.anyio
|
|
172
|
+
async def test_authenticate_inactive_user(self, user_service: UserService):
|
|
173
|
+
"""Test authentication with inactive user."""
|
|
174
|
+
inactive_user = MockUser(is_active=False)
|
|
175
|
+
with (
|
|
176
|
+
patch("app.services.user.user_repo") as mock_repo,
|
|
177
|
+
patch("app.services.user.verify_password", return_value=True),
|
|
178
|
+
):
|
|
179
|
+
mock_repo.get_by_email = AsyncMock(return_value=inactive_user)
|
|
180
|
+
|
|
181
|
+
with pytest.raises(AuthenticationError):
|
|
182
|
+
await user_service.authenticate("test@example.com", "password")
|
|
183
|
+
|
|
184
|
+
@pytest.mark.anyio
|
|
185
|
+
async def test_update_success(self, user_service: UserService, mock_user: MockUser):
|
|
186
|
+
"""Test updating user."""
|
|
187
|
+
with patch("app.services.user.user_repo") as mock_repo:
|
|
188
|
+
mock_repo.get_by_id = AsyncMock(return_value=mock_user)
|
|
189
|
+
mock_repo.update = AsyncMock(return_value=mock_user)
|
|
190
|
+
|
|
191
|
+
user_update = UserUpdate(full_name="Updated Name")
|
|
192
|
+
result = await user_service.update(mock_user.id, user_update)
|
|
193
|
+
|
|
194
|
+
assert result == mock_user
|
|
195
|
+
|
|
196
|
+
@pytest.mark.anyio
|
|
197
|
+
async def test_update_with_password(self, user_service: UserService, mock_user: MockUser):
|
|
198
|
+
"""Test updating user with password change."""
|
|
199
|
+
with patch("app.services.user.user_repo") as mock_repo:
|
|
200
|
+
mock_repo.get_by_id = AsyncMock(return_value=mock_user)
|
|
201
|
+
mock_repo.update = AsyncMock(return_value=mock_user)
|
|
202
|
+
|
|
203
|
+
user_update = UserUpdate(password="newpassword123")
|
|
204
|
+
result = await user_service.update(mock_user.id, user_update)
|
|
205
|
+
|
|
206
|
+
assert result == mock_user
|
|
207
|
+
# Verify hashed_password was passed to update
|
|
208
|
+
call_args = mock_repo.update.call_args
|
|
209
|
+
assert "hashed_password" in call_args[1]["update_data"]
|
|
210
|
+
|
|
211
|
+
@pytest.mark.anyio
|
|
212
|
+
async def test_delete_success(self, user_service: UserService, mock_user: MockUser):
|
|
213
|
+
"""Test deleting user."""
|
|
214
|
+
with patch("app.services.user.user_repo") as mock_repo:
|
|
215
|
+
mock_repo.delete = AsyncMock(return_value=mock_user)
|
|
216
|
+
|
|
217
|
+
result = await user_service.delete(mock_user.id)
|
|
218
|
+
|
|
219
|
+
assert result == mock_user
|
|
220
|
+
|
|
221
|
+
@pytest.mark.anyio
|
|
222
|
+
async def test_delete_not_found(self, user_service: UserService):
|
|
223
|
+
"""Test deleting non-existent user."""
|
|
224
|
+
with patch("app.services.user.user_repo") as mock_repo:
|
|
225
|
+
mock_repo.delete = AsyncMock(return_value=None)
|
|
226
|
+
|
|
227
|
+
with pytest.raises(NotFoundError):
|
|
228
|
+
await user_service.delete(uuid4())
|
|
229
|
+
{%- endif %}
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
{%- if cookiecutter.use_sqlite %}
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class TestUserServiceSQLite:
|
|
236
|
+
"""Tests for UserService with SQLite."""
|
|
237
|
+
|
|
238
|
+
@pytest.fixture
|
|
239
|
+
def mock_db(self) -> MagicMock:
|
|
240
|
+
"""Create mock database session."""
|
|
241
|
+
return MagicMock()
|
|
242
|
+
|
|
243
|
+
@pytest.fixture
|
|
244
|
+
def user_service(self, mock_db: MagicMock) -> UserService:
|
|
245
|
+
"""Create UserService instance with mock db."""
|
|
246
|
+
return UserService(mock_db)
|
|
247
|
+
|
|
248
|
+
@pytest.fixture
|
|
249
|
+
def mock_user(self) -> MockUser:
|
|
250
|
+
"""Create a mock user."""
|
|
251
|
+
return MockUser()
|
|
252
|
+
|
|
253
|
+
def test_get_by_id_success(self, user_service: UserService, mock_user: MockUser):
|
|
254
|
+
"""Test getting user by ID successfully."""
|
|
255
|
+
with patch("app.services.user.user_repo") as mock_repo:
|
|
256
|
+
mock_repo.get_by_id = MagicMock(return_value=mock_user)
|
|
257
|
+
|
|
258
|
+
result = user_service.get_by_id(mock_user.id)
|
|
259
|
+
|
|
260
|
+
assert result == mock_user
|
|
261
|
+
|
|
262
|
+
def test_get_by_id_not_found(self, user_service: UserService):
|
|
263
|
+
"""Test getting non-existent user raises NotFoundError."""
|
|
264
|
+
with patch("app.services.user.user_repo") as mock_repo:
|
|
265
|
+
mock_repo.get_by_id = MagicMock(return_value=None)
|
|
266
|
+
|
|
267
|
+
with pytest.raises(NotFoundError):
|
|
268
|
+
user_service.get_by_id("nonexistent")
|
|
269
|
+
|
|
270
|
+
def test_authenticate_success(self, user_service: UserService, mock_user: MockUser):
|
|
271
|
+
"""Test successful authentication."""
|
|
272
|
+
with (
|
|
273
|
+
patch("app.services.user.user_repo") as mock_repo,
|
|
274
|
+
patch("app.services.user.verify_password", return_value=True),
|
|
275
|
+
):
|
|
276
|
+
mock_repo.get_by_email = MagicMock(return_value=mock_user)
|
|
277
|
+
|
|
278
|
+
result = user_service.authenticate("test@example.com", "password123")
|
|
279
|
+
|
|
280
|
+
assert result == mock_user
|
|
281
|
+
|
|
282
|
+
def test_register_success(self, user_service: UserService, mock_user: MockUser):
|
|
283
|
+
"""Test registering a new user."""
|
|
284
|
+
with patch("app.services.user.user_repo") as mock_repo:
|
|
285
|
+
mock_repo.get_by_email = MagicMock(return_value=None)
|
|
286
|
+
mock_repo.create = MagicMock(return_value=mock_user)
|
|
287
|
+
|
|
288
|
+
user_in = UserCreate(
|
|
289
|
+
email="new@example.com",
|
|
290
|
+
password="password123",
|
|
291
|
+
full_name="New User",
|
|
292
|
+
)
|
|
293
|
+
result = user_service.register(user_in)
|
|
294
|
+
|
|
295
|
+
assert result == mock_user
|
|
296
|
+
{%- endif %}
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
{%- if cookiecutter.use_mongodb %}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
class TestUserServiceMongoDB:
|
|
303
|
+
"""Tests for UserService with MongoDB."""
|
|
304
|
+
|
|
305
|
+
@pytest.fixture
|
|
306
|
+
def user_service(self) -> UserService:
|
|
307
|
+
"""Create UserService instance."""
|
|
308
|
+
return UserService()
|
|
309
|
+
|
|
310
|
+
@pytest.fixture
|
|
311
|
+
def mock_user(self) -> MockUser:
|
|
312
|
+
"""Create a mock user."""
|
|
313
|
+
return MockUser()
|
|
314
|
+
|
|
315
|
+
@pytest.mark.anyio
|
|
316
|
+
async def test_get_by_id_success(self, user_service: UserService, mock_user: MockUser):
|
|
317
|
+
"""Test getting user by ID successfully."""
|
|
318
|
+
with patch("app.services.user.user_repo") as mock_repo:
|
|
319
|
+
mock_repo.get_by_id = AsyncMock(return_value=mock_user)
|
|
320
|
+
|
|
321
|
+
result = await user_service.get_by_id(mock_user.id)
|
|
322
|
+
|
|
323
|
+
assert result == mock_user
|
|
324
|
+
|
|
325
|
+
@pytest.mark.anyio
|
|
326
|
+
async def test_get_by_id_not_found(self, user_service: UserService):
|
|
327
|
+
"""Test getting non-existent user raises NotFoundError."""
|
|
328
|
+
with patch("app.services.user.user_repo") as mock_repo:
|
|
329
|
+
mock_repo.get_by_id = AsyncMock(return_value=None)
|
|
330
|
+
|
|
331
|
+
with pytest.raises(NotFoundError):
|
|
332
|
+
await user_service.get_by_id("nonexistent")
|
|
333
|
+
|
|
334
|
+
@pytest.mark.anyio
|
|
335
|
+
async def test_authenticate_success(self, user_service: UserService, mock_user: MockUser):
|
|
336
|
+
"""Test successful authentication."""
|
|
337
|
+
with (
|
|
338
|
+
patch("app.services.user.user_repo") as mock_repo,
|
|
339
|
+
patch("app.services.user.verify_password", return_value=True),
|
|
340
|
+
):
|
|
341
|
+
mock_repo.get_by_email = AsyncMock(return_value=mock_user)
|
|
342
|
+
|
|
343
|
+
result = await user_service.authenticate("test@example.com", "password123")
|
|
344
|
+
|
|
345
|
+
assert result == mock_user
|
|
346
|
+
|
|
347
|
+
@pytest.mark.anyio
|
|
348
|
+
async def test_register_success(self, user_service: UserService, mock_user: MockUser):
|
|
349
|
+
"""Test registering a new user."""
|
|
350
|
+
with patch("app.services.user.user_repo") as mock_repo:
|
|
351
|
+
mock_repo.get_by_email = AsyncMock(return_value=None)
|
|
352
|
+
mock_repo.create = AsyncMock(return_value=mock_user)
|
|
353
|
+
|
|
354
|
+
user_in = UserCreate(
|
|
355
|
+
email="new@example.com",
|
|
356
|
+
password="password123",
|
|
357
|
+
full_name="New User",
|
|
358
|
+
)
|
|
359
|
+
result = await user_service.register(user_in)
|
|
360
|
+
|
|
361
|
+
assert result == mock_user
|
|
362
|
+
{%- endif %}
|
|
363
|
+
{%- endif %}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{%- if cookiecutter.use_celery %}
|
|
2
|
+
"""Tests for Celery worker tasks."""
|
|
3
|
+
|
|
4
|
+
from unittest.mock import MagicMock, patch
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestExampleTask:
|
|
10
|
+
"""Tests for example_task."""
|
|
11
|
+
|
|
12
|
+
def test_example_task_success(self):
|
|
13
|
+
"""Test example_task completes successfully."""
|
|
14
|
+
from app.worker.tasks.examples import example_task
|
|
15
|
+
|
|
16
|
+
# Create a mock for self (the task)
|
|
17
|
+
mock_self = MagicMock()
|
|
18
|
+
mock_self.request.id = "test-task-id"
|
|
19
|
+
|
|
20
|
+
with patch("app.worker.tasks.examples.time.sleep"):
|
|
21
|
+
result = example_task.run(mock_self, "test message")
|
|
22
|
+
|
|
23
|
+
assert result["status"] == "completed"
|
|
24
|
+
assert "test message" in result["message"]
|
|
25
|
+
assert result["task_id"] == "test-task-id"
|
|
26
|
+
|
|
27
|
+
def test_example_task_retry_on_error(self):
|
|
28
|
+
"""Test example_task retries on error."""
|
|
29
|
+
from app.worker.tasks.examples import example_task
|
|
30
|
+
|
|
31
|
+
mock_self = MagicMock()
|
|
32
|
+
mock_self.request.id = "test-task-id"
|
|
33
|
+
mock_self.request.retries = 0
|
|
34
|
+
|
|
35
|
+
with patch("app.worker.tasks.examples.time.sleep", side_effect=Exception("Test error")):
|
|
36
|
+
mock_self.retry = MagicMock(side_effect=Exception("Retry"))
|
|
37
|
+
with pytest.raises(Exception, match="Retry"):
|
|
38
|
+
example_task.run(mock_self, "test message")
|
|
39
|
+
mock_self.retry.assert_called_once()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TestLongRunningTask:
|
|
43
|
+
"""Tests for long_running_task."""
|
|
44
|
+
|
|
45
|
+
def test_long_running_task_completes(self):
|
|
46
|
+
"""Test long_running_task completes with progress."""
|
|
47
|
+
from app.worker.tasks.examples import long_running_task
|
|
48
|
+
|
|
49
|
+
mock_self = MagicMock()
|
|
50
|
+
mock_self.request.id = "test-task-id"
|
|
51
|
+
|
|
52
|
+
with patch("app.worker.tasks.examples.time.sleep"):
|
|
53
|
+
result = long_running_task.run(mock_self, duration=3)
|
|
54
|
+
|
|
55
|
+
assert result["status"] == "completed"
|
|
56
|
+
assert result["duration"] == 3
|
|
57
|
+
# Check progress updates were made
|
|
58
|
+
assert mock_self.update_state.call_count == 3
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class TestSendEmailTask:
|
|
62
|
+
"""Tests for send_email_task."""
|
|
63
|
+
|
|
64
|
+
def test_send_email_task_success(self):
|
|
65
|
+
"""Test send_email_task sends email."""
|
|
66
|
+
from app.worker.tasks.examples import send_email_task
|
|
67
|
+
|
|
68
|
+
with patch("app.worker.tasks.examples.time.sleep"):
|
|
69
|
+
result = send_email_task("test@example.com", "Subject", "Body")
|
|
70
|
+
|
|
71
|
+
assert result["status"] == "sent"
|
|
72
|
+
assert result["to"] == "test@example.com"
|
|
73
|
+
assert result["subject"] == "Subject"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class TestCeleryAppConfiguration:
|
|
77
|
+
"""Tests for Celery app configuration."""
|
|
78
|
+
|
|
79
|
+
def test_celery_app_exists(self):
|
|
80
|
+
"""Test Celery app is configured."""
|
|
81
|
+
from app.worker.celery_app import celery_app
|
|
82
|
+
|
|
83
|
+
assert celery_app is not None
|
|
84
|
+
assert celery_app.main == "{{ cookiecutter.project_slug }}"
|
|
85
|
+
{%- endif %}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
{%- if cookiecutter.enable_docker %}
|
|
2
|
+
# Development configuration (alias for docker-compose.yml)
|
|
3
|
+
# Usage: docker-compose -f docker-compose.dev.yml up -d
|
|
4
|
+
#
|
|
5
|
+
# This file is identical to docker-compose.yml for explicit naming preference.
|
|
6
|
+
# Use docker-compose.yml for default development.
|
|
7
|
+
|
|
8
|
+
services:
|
|
9
|
+
app:
|
|
10
|
+
build:
|
|
11
|
+
context: ./backend
|
|
12
|
+
dockerfile: Dockerfile
|
|
13
|
+
container_name: {{ cookiecutter.project_slug }}_backend
|
|
14
|
+
ports:
|
|
15
|
+
- "{{ cookiecutter.backend_port }}:{{ cookiecutter.backend_port }}"
|
|
16
|
+
volumes:
|
|
17
|
+
- ./backend/app:/app/app:ro
|
|
18
|
+
- ./backend/cli:/app/cli:ro
|
|
19
|
+
env_file:
|
|
20
|
+
- ./backend/.env
|
|
21
|
+
environment:
|
|
22
|
+
- DEBUG=true
|
|
23
|
+
- ENVIRONMENT=local
|
|
24
|
+
{%- if cookiecutter.use_postgresql %}
|
|
25
|
+
- POSTGRES_HOST=db
|
|
26
|
+
{%- endif %}
|
|
27
|
+
{%- if cookiecutter.enable_redis %}
|
|
28
|
+
- REDIS_HOST=redis
|
|
29
|
+
{%- endif %}
|
|
30
|
+
{%- if cookiecutter.use_celery %}
|
|
31
|
+
- CELERY_BROKER_URL=redis://redis:6379/0
|
|
32
|
+
- CELERY_RESULT_BACKEND=redis://redis:6379/0
|
|
33
|
+
{%- endif %}
|
|
34
|
+
{%- if cookiecutter.use_taskiq %}
|
|
35
|
+
- TASKIQ_BROKER_URL=redis://redis:6379/1
|
|
36
|
+
- TASKIQ_RESULT_BACKEND=redis://redis:6379/1
|
|
37
|
+
{%- endif %}
|
|
38
|
+
command: uvicorn app.main:app --host 0.0.0.0 --port {{ cookiecutter.backend_port }} --reload
|
|
39
|
+
networks:
|
|
40
|
+
- backend
|
|
41
|
+
{%- if cookiecutter.use_postgresql or cookiecutter.enable_redis %}
|
|
42
|
+
depends_on:
|
|
43
|
+
{%- if cookiecutter.use_postgresql %}
|
|
44
|
+
db:
|
|
45
|
+
condition: service_healthy
|
|
46
|
+
{%- endif %}
|
|
47
|
+
{%- if cookiecutter.enable_redis %}
|
|
48
|
+
redis:
|
|
49
|
+
condition: service_healthy
|
|
50
|
+
{%- endif %}
|
|
51
|
+
{%- endif %}
|
|
52
|
+
restart: unless-stopped
|
|
53
|
+
|
|
54
|
+
{%- if cookiecutter.use_postgresql %}
|
|
55
|
+
|
|
56
|
+
db:
|
|
57
|
+
image: postgres:16-alpine
|
|
58
|
+
container_name: {{ cookiecutter.project_slug }}_db
|
|
59
|
+
environment:
|
|
60
|
+
- POSTGRES_USER=${POSTGRES_USER:-postgres}
|
|
61
|
+
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres}
|
|
62
|
+
- POSTGRES_DB=${POSTGRES_DB:-{{ cookiecutter.project_slug }}}
|
|
63
|
+
volumes:
|
|
64
|
+
- postgres_data:/var/lib/postgresql/data
|
|
65
|
+
ports:
|
|
66
|
+
- "5432:5432"
|
|
67
|
+
networks:
|
|
68
|
+
- backend
|
|
69
|
+
healthcheck:
|
|
70
|
+
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"]
|
|
71
|
+
interval: 10s
|
|
72
|
+
timeout: 5s
|
|
73
|
+
retries: 5
|
|
74
|
+
restart: unless-stopped
|
|
75
|
+
{%- endif %}
|
|
76
|
+
|
|
77
|
+
{%- if cookiecutter.enable_redis %}
|
|
78
|
+
|
|
79
|
+
redis:
|
|
80
|
+
image: redis:7-alpine
|
|
81
|
+
container_name: {{ cookiecutter.project_slug }}_redis
|
|
82
|
+
ports:
|
|
83
|
+
- "6379:6379"
|
|
84
|
+
volumes:
|
|
85
|
+
- redis_data:/data
|
|
86
|
+
networks:
|
|
87
|
+
- backend
|
|
88
|
+
healthcheck:
|
|
89
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
90
|
+
interval: 10s
|
|
91
|
+
timeout: 5s
|
|
92
|
+
retries: 5
|
|
93
|
+
restart: unless-stopped
|
|
94
|
+
{%- endif %}
|
|
95
|
+
|
|
96
|
+
{%- if cookiecutter.use_celery %}
|
|
97
|
+
|
|
98
|
+
celery_worker:
|
|
99
|
+
build:
|
|
100
|
+
context: ./backend
|
|
101
|
+
dockerfile: Dockerfile
|
|
102
|
+
container_name: {{ cookiecutter.project_slug }}_celery_worker
|
|
103
|
+
volumes:
|
|
104
|
+
- ./backend/app:/app/app:ro
|
|
105
|
+
command: celery -A app.worker.celery_app worker --loglevel=debug
|
|
106
|
+
env_file:
|
|
107
|
+
- ./backend/.env
|
|
108
|
+
environment:
|
|
109
|
+
- DEBUG=true
|
|
110
|
+
{%- if cookiecutter.use_postgresql %}
|
|
111
|
+
- POSTGRES_HOST=db
|
|
112
|
+
{%- endif %}
|
|
113
|
+
- REDIS_HOST=redis
|
|
114
|
+
- CELERY_BROKER_URL=redis://redis:6379/0
|
|
115
|
+
- CELERY_RESULT_BACKEND=redis://redis:6379/0
|
|
116
|
+
networks:
|
|
117
|
+
- backend
|
|
118
|
+
depends_on:
|
|
119
|
+
redis:
|
|
120
|
+
condition: service_healthy
|
|
121
|
+
{%- if cookiecutter.use_postgresql %}
|
|
122
|
+
db:
|
|
123
|
+
condition: service_healthy
|
|
124
|
+
{%- endif %}
|
|
125
|
+
restart: unless-stopped
|
|
126
|
+
|
|
127
|
+
celery_beat:
|
|
128
|
+
build:
|
|
129
|
+
context: ./backend
|
|
130
|
+
dockerfile: Dockerfile
|
|
131
|
+
container_name: {{ cookiecutter.project_slug }}_celery_beat
|
|
132
|
+
volumes:
|
|
133
|
+
- ./backend/app:/app/app:ro
|
|
134
|
+
command: celery -A app.worker.celery_app beat --loglevel=debug
|
|
135
|
+
env_file:
|
|
136
|
+
- ./backend/.env
|
|
137
|
+
environment:
|
|
138
|
+
- DEBUG=true
|
|
139
|
+
- REDIS_HOST=redis
|
|
140
|
+
- CELERY_BROKER_URL=redis://redis:6379/0
|
|
141
|
+
- CELERY_RESULT_BACKEND=redis://redis:6379/0
|
|
142
|
+
networks:
|
|
143
|
+
- backend
|
|
144
|
+
depends_on:
|
|
145
|
+
redis:
|
|
146
|
+
condition: service_healthy
|
|
147
|
+
restart: unless-stopped
|
|
148
|
+
|
|
149
|
+
flower:
|
|
150
|
+
build:
|
|
151
|
+
context: ./backend
|
|
152
|
+
dockerfile: Dockerfile
|
|
153
|
+
container_name: {{ cookiecutter.project_slug }}_flower
|
|
154
|
+
command: celery -A app.worker.celery_app flower --port=5555
|
|
155
|
+
ports:
|
|
156
|
+
- "5555:5555"
|
|
157
|
+
env_file:
|
|
158
|
+
- ./backend/.env
|
|
159
|
+
environment:
|
|
160
|
+
- DEBUG=true
|
|
161
|
+
- REDIS_HOST=redis
|
|
162
|
+
- CELERY_BROKER_URL=redis://redis:6379/0
|
|
163
|
+
- CELERY_RESULT_BACKEND=redis://redis:6379/0
|
|
164
|
+
networks:
|
|
165
|
+
- backend
|
|
166
|
+
depends_on:
|
|
167
|
+
redis:
|
|
168
|
+
condition: service_healthy
|
|
169
|
+
restart: unless-stopped
|
|
170
|
+
{%- endif %}
|
|
171
|
+
|
|
172
|
+
{%- if cookiecutter.use_taskiq %}
|
|
173
|
+
|
|
174
|
+
taskiq_worker:
|
|
175
|
+
build:
|
|
176
|
+
context: ./backend
|
|
177
|
+
dockerfile: Dockerfile
|
|
178
|
+
container_name: {{ cookiecutter.project_slug }}_taskiq_worker
|
|
179
|
+
volumes:
|
|
180
|
+
- ./backend/app:/app/app:ro
|
|
181
|
+
command: taskiq worker app.worker.taskiq_app:broker --workers 1 --reload
|
|
182
|
+
env_file:
|
|
183
|
+
- ./backend/.env
|
|
184
|
+
environment:
|
|
185
|
+
- DEBUG=true
|
|
186
|
+
{%- if cookiecutter.use_postgresql %}
|
|
187
|
+
- POSTGRES_HOST=db
|
|
188
|
+
{%- endif %}
|
|
189
|
+
- REDIS_HOST=redis
|
|
190
|
+
- TASKIQ_BROKER_URL=redis://redis:6379/1
|
|
191
|
+
- TASKIQ_RESULT_BACKEND=redis://redis:6379/1
|
|
192
|
+
networks:
|
|
193
|
+
- backend
|
|
194
|
+
depends_on:
|
|
195
|
+
redis:
|
|
196
|
+
condition: service_healthy
|
|
197
|
+
{%- if cookiecutter.use_postgresql %}
|
|
198
|
+
db:
|
|
199
|
+
condition: service_healthy
|
|
200
|
+
{%- endif %}
|
|
201
|
+
restart: unless-stopped
|
|
202
|
+
|
|
203
|
+
taskiq_scheduler:
|
|
204
|
+
build:
|
|
205
|
+
context: ./backend
|
|
206
|
+
dockerfile: Dockerfile
|
|
207
|
+
container_name: {{ cookiecutter.project_slug }}_taskiq_scheduler
|
|
208
|
+
volumes:
|
|
209
|
+
- ./backend/app:/app/app:ro
|
|
210
|
+
command: taskiq scheduler app.worker.taskiq_app:scheduler
|
|
211
|
+
env_file:
|
|
212
|
+
- ./backend/.env
|
|
213
|
+
environment:
|
|
214
|
+
- DEBUG=true
|
|
215
|
+
- REDIS_HOST=redis
|
|
216
|
+
- TASKIQ_BROKER_URL=redis://redis:6379/1
|
|
217
|
+
- TASKIQ_RESULT_BACKEND=redis://redis:6379/1
|
|
218
|
+
networks:
|
|
219
|
+
- backend
|
|
220
|
+
depends_on:
|
|
221
|
+
redis:
|
|
222
|
+
condition: service_healthy
|
|
223
|
+
restart: unless-stopped
|
|
224
|
+
{%- endif %}
|
|
225
|
+
|
|
226
|
+
networks:
|
|
227
|
+
backend:
|
|
228
|
+
driver: bridge
|
|
229
|
+
|
|
230
|
+
{%- if cookiecutter.use_postgresql or cookiecutter.enable_redis %}
|
|
231
|
+
|
|
232
|
+
volumes:
|
|
233
|
+
{%- if cookiecutter.use_postgresql %}
|
|
234
|
+
postgres_data:
|
|
235
|
+
{%- endif %}
|
|
236
|
+
{%- if cookiecutter.enable_redis %}
|
|
237
|
+
redis_data:
|
|
238
|
+
{%- endif %}
|
|
239
|
+
{%- endif %}
|
|
240
|
+
{%- else %}
|
|
241
|
+
# Docker is disabled for this project
|
|
242
|
+
{%- endif %}
|