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,310 @@
|
|
|
1
|
+
{%- if cookiecutter.include_example_crud and cookiecutter.use_database %}
|
|
2
|
+
"""Tests for items CRUD routes.
|
|
3
|
+
|
|
4
|
+
This module demonstrates testing patterns for CRUD endpoints.
|
|
5
|
+
You can use it as a template for testing your own endpoints.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import UTC, datetime
|
|
9
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
10
|
+
from uuid import uuid4
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
from httpx import AsyncClient
|
|
14
|
+
|
|
15
|
+
from app.core.config import settings
|
|
16
|
+
from app.main import app
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MockItem:
|
|
20
|
+
"""Mock item for testing."""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
id=None,
|
|
25
|
+
title="Test Item",
|
|
26
|
+
description="Test Description",
|
|
27
|
+
is_active=True,
|
|
28
|
+
):
|
|
29
|
+
{%- if cookiecutter.use_postgresql %}
|
|
30
|
+
self.id = id or uuid4()
|
|
31
|
+
{%- else %}
|
|
32
|
+
self.id = id or str(uuid4())
|
|
33
|
+
{%- endif %}
|
|
34
|
+
self.title = title
|
|
35
|
+
self.description = description
|
|
36
|
+
self.is_active = is_active
|
|
37
|
+
self.created_at = datetime.now(UTC)
|
|
38
|
+
self.updated_at = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.fixture
|
|
42
|
+
def mock_item() -> MockItem:
|
|
43
|
+
"""Create a mock item."""
|
|
44
|
+
return MockItem()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.fixture
|
|
48
|
+
def mock_items() -> list[MockItem]:
|
|
49
|
+
"""Create multiple mock items."""
|
|
50
|
+
return [
|
|
51
|
+
MockItem(title="Item 1", description="First item"),
|
|
52
|
+
MockItem(title="Item 2", description="Second item"),
|
|
53
|
+
MockItem(title="Item 3", description="Third item"),
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@pytest.fixture
|
|
58
|
+
def mock_item_service(mock_item: MockItem, mock_items: list[MockItem]) -> MagicMock:
|
|
59
|
+
"""Create a mock item service."""
|
|
60
|
+
service = MagicMock()
|
|
61
|
+
{%- if cookiecutter.use_postgresql or cookiecutter.use_mongodb %}
|
|
62
|
+
service.get_by_id = AsyncMock(return_value=mock_item)
|
|
63
|
+
service.get_multi = AsyncMock(return_value=mock_items)
|
|
64
|
+
service.create = AsyncMock(return_value=mock_item)
|
|
65
|
+
service.update = AsyncMock(return_value=mock_item)
|
|
66
|
+
service.delete = AsyncMock(return_value=mock_item)
|
|
67
|
+
{%- elif cookiecutter.use_sqlite %}
|
|
68
|
+
service.get_by_id = MagicMock(return_value=mock_item)
|
|
69
|
+
service.get_multi = MagicMock(return_value=mock_items)
|
|
70
|
+
service.create = MagicMock(return_value=mock_item)
|
|
71
|
+
service.update = MagicMock(return_value=mock_item)
|
|
72
|
+
service.delete = MagicMock(return_value=mock_item)
|
|
73
|
+
{%- endif %}
|
|
74
|
+
return service
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@pytest.fixture
|
|
78
|
+
async def client_with_mock_service(
|
|
79
|
+
mock_item_service: MagicMock,
|
|
80
|
+
{%- if cookiecutter.use_database %}
|
|
81
|
+
mock_db_session,
|
|
82
|
+
{%- endif %}
|
|
83
|
+
) -> AsyncClient:
|
|
84
|
+
"""Client with mocked item service."""
|
|
85
|
+
from httpx import ASGITransport
|
|
86
|
+
|
|
87
|
+
from app.api.deps import get_item_service
|
|
88
|
+
{%- if cookiecutter.use_database %}
|
|
89
|
+
from app.db.session import get_db_session
|
|
90
|
+
{%- endif %}
|
|
91
|
+
|
|
92
|
+
app.dependency_overrides[get_item_service] = lambda db=None: mock_item_service
|
|
93
|
+
{%- if cookiecutter.use_database %}
|
|
94
|
+
app.dependency_overrides[get_db_session] = lambda: mock_db_session
|
|
95
|
+
{%- endif %}
|
|
96
|
+
|
|
97
|
+
async with AsyncClient(
|
|
98
|
+
transport=ASGITransport(app=app),
|
|
99
|
+
base_url="http://test",
|
|
100
|
+
) as ac:
|
|
101
|
+
yield ac
|
|
102
|
+
|
|
103
|
+
app.dependency_overrides.clear()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@pytest.mark.anyio
|
|
107
|
+
async def test_create_item_success(client_with_mock_service: AsyncClient):
|
|
108
|
+
"""Test successful item creation."""
|
|
109
|
+
response = await client_with_mock_service.post(
|
|
110
|
+
f"{settings.API_V1_STR}/items",
|
|
111
|
+
json={"title": "New Item", "description": "A new item"},
|
|
112
|
+
)
|
|
113
|
+
assert response.status_code == 201
|
|
114
|
+
data = response.json()
|
|
115
|
+
assert data["title"] == "Test Item" # From mock
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@pytest.mark.anyio
|
|
119
|
+
async def test_create_item_minimal(client_with_mock_service: AsyncClient):
|
|
120
|
+
"""Test item creation with minimal data (only required fields)."""
|
|
121
|
+
response = await client_with_mock_service.post(
|
|
122
|
+
f"{settings.API_V1_STR}/items",
|
|
123
|
+
json={"title": "Minimal Item"},
|
|
124
|
+
)
|
|
125
|
+
assert response.status_code == 201
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@pytest.mark.anyio
|
|
129
|
+
async def test_create_item_validation_error(client_with_mock_service: AsyncClient):
|
|
130
|
+
"""Test item creation with invalid data."""
|
|
131
|
+
response = await client_with_mock_service.post(
|
|
132
|
+
f"{settings.API_V1_STR}/items",
|
|
133
|
+
json={}, # Missing required 'title' field
|
|
134
|
+
)
|
|
135
|
+
assert response.status_code == 422
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@pytest.mark.anyio
|
|
139
|
+
async def test_get_item_success(
|
|
140
|
+
client_with_mock_service: AsyncClient,
|
|
141
|
+
mock_item: MockItem,
|
|
142
|
+
):
|
|
143
|
+
"""Test successful item retrieval."""
|
|
144
|
+
response = await client_with_mock_service.get(
|
|
145
|
+
f"{settings.API_V1_STR}/items/{mock_item.id}",
|
|
146
|
+
)
|
|
147
|
+
assert response.status_code == 200
|
|
148
|
+
data = response.json()
|
|
149
|
+
assert data["title"] == mock_item.title
|
|
150
|
+
assert data["description"] == mock_item.description
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@pytest.mark.anyio
|
|
154
|
+
async def test_get_item_not_found(
|
|
155
|
+
client_with_mock_service: AsyncClient,
|
|
156
|
+
mock_item_service: MagicMock,
|
|
157
|
+
):
|
|
158
|
+
"""Test item retrieval when item doesn't exist."""
|
|
159
|
+
from app.core.exceptions import NotFoundError
|
|
160
|
+
|
|
161
|
+
{%- if cookiecutter.use_postgresql or cookiecutter.use_mongodb %}
|
|
162
|
+
mock_item_service.get_by_id = AsyncMock(
|
|
163
|
+
side_effect=NotFoundError(message="Item not found")
|
|
164
|
+
)
|
|
165
|
+
{%- elif cookiecutter.use_sqlite %}
|
|
166
|
+
mock_item_service.get_by_id = MagicMock(
|
|
167
|
+
side_effect=NotFoundError(message="Item not found")
|
|
168
|
+
)
|
|
169
|
+
{%- endif %}
|
|
170
|
+
|
|
171
|
+
{%- if cookiecutter.use_postgresql %}
|
|
172
|
+
response = await client_with_mock_service.get(
|
|
173
|
+
f"{settings.API_V1_STR}/items/{uuid4()}",
|
|
174
|
+
)
|
|
175
|
+
{%- else %}
|
|
176
|
+
response = await client_with_mock_service.get(
|
|
177
|
+
f"{settings.API_V1_STR}/items/nonexistent-id",
|
|
178
|
+
)
|
|
179
|
+
{%- endif %}
|
|
180
|
+
assert response.status_code == 404
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
{%- if not cookiecutter.enable_pagination %}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@pytest.mark.anyio
|
|
187
|
+
async def test_list_items_success(
|
|
188
|
+
client_with_mock_service: AsyncClient,
|
|
189
|
+
mock_items: list[MockItem],
|
|
190
|
+
):
|
|
191
|
+
"""Test successful item listing."""
|
|
192
|
+
response = await client_with_mock_service.get(
|
|
193
|
+
f"{settings.API_V1_STR}/items",
|
|
194
|
+
)
|
|
195
|
+
assert response.status_code == 200
|
|
196
|
+
data = response.json()
|
|
197
|
+
assert len(data) == len(mock_items)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@pytest.mark.anyio
|
|
201
|
+
async def test_list_items_pagination(client_with_mock_service: AsyncClient):
|
|
202
|
+
"""Test item listing with pagination parameters."""
|
|
203
|
+
response = await client_with_mock_service.get(
|
|
204
|
+
f"{settings.API_V1_STR}/items?skip=0&limit=10",
|
|
205
|
+
)
|
|
206
|
+
assert response.status_code == 200
|
|
207
|
+
{%- endif %}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@pytest.mark.anyio
|
|
211
|
+
async def test_update_item_success(
|
|
212
|
+
client_with_mock_service: AsyncClient,
|
|
213
|
+
mock_item: MockItem,
|
|
214
|
+
):
|
|
215
|
+
"""Test successful item update."""
|
|
216
|
+
response = await client_with_mock_service.patch(
|
|
217
|
+
f"{settings.API_V1_STR}/items/{mock_item.id}",
|
|
218
|
+
json={"title": "Updated Title"},
|
|
219
|
+
)
|
|
220
|
+
assert response.status_code == 200
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@pytest.mark.anyio
|
|
224
|
+
async def test_update_item_partial(
|
|
225
|
+
client_with_mock_service: AsyncClient,
|
|
226
|
+
mock_item: MockItem,
|
|
227
|
+
):
|
|
228
|
+
"""Test partial item update (only some fields)."""
|
|
229
|
+
response = await client_with_mock_service.patch(
|
|
230
|
+
f"{settings.API_V1_STR}/items/{mock_item.id}",
|
|
231
|
+
json={"is_active": False},
|
|
232
|
+
)
|
|
233
|
+
assert response.status_code == 200
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@pytest.mark.anyio
|
|
237
|
+
async def test_update_item_not_found(
|
|
238
|
+
client_with_mock_service: AsyncClient,
|
|
239
|
+
mock_item_service: MagicMock,
|
|
240
|
+
):
|
|
241
|
+
"""Test item update when item doesn't exist."""
|
|
242
|
+
from app.core.exceptions import NotFoundError
|
|
243
|
+
|
|
244
|
+
{%- if cookiecutter.use_postgresql or cookiecutter.use_mongodb %}
|
|
245
|
+
mock_item_service.update = AsyncMock(
|
|
246
|
+
side_effect=NotFoundError(message="Item not found")
|
|
247
|
+
)
|
|
248
|
+
{%- elif cookiecutter.use_sqlite %}
|
|
249
|
+
mock_item_service.update = MagicMock(
|
|
250
|
+
side_effect=NotFoundError(message="Item not found")
|
|
251
|
+
)
|
|
252
|
+
{%- endif %}
|
|
253
|
+
|
|
254
|
+
{%- if cookiecutter.use_postgresql %}
|
|
255
|
+
response = await client_with_mock_service.patch(
|
|
256
|
+
f"{settings.API_V1_STR}/items/{uuid4()}",
|
|
257
|
+
json={"title": "Updated"},
|
|
258
|
+
)
|
|
259
|
+
{%- else %}
|
|
260
|
+
response = await client_with_mock_service.patch(
|
|
261
|
+
f"{settings.API_V1_STR}/items/nonexistent-id",
|
|
262
|
+
json={"title": "Updated"},
|
|
263
|
+
)
|
|
264
|
+
{%- endif %}
|
|
265
|
+
assert response.status_code == 404
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@pytest.mark.anyio
|
|
269
|
+
async def test_delete_item_success(
|
|
270
|
+
client_with_mock_service: AsyncClient,
|
|
271
|
+
mock_item: MockItem,
|
|
272
|
+
):
|
|
273
|
+
"""Test successful item deletion."""
|
|
274
|
+
response = await client_with_mock_service.delete(
|
|
275
|
+
f"{settings.API_V1_STR}/items/{mock_item.id}",
|
|
276
|
+
)
|
|
277
|
+
assert response.status_code == 204
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@pytest.mark.anyio
|
|
281
|
+
async def test_delete_item_not_found(
|
|
282
|
+
client_with_mock_service: AsyncClient,
|
|
283
|
+
mock_item_service: MagicMock,
|
|
284
|
+
):
|
|
285
|
+
"""Test item deletion when item doesn't exist."""
|
|
286
|
+
from app.core.exceptions import NotFoundError
|
|
287
|
+
|
|
288
|
+
{%- if cookiecutter.use_postgresql or cookiecutter.use_mongodb %}
|
|
289
|
+
mock_item_service.delete = AsyncMock(
|
|
290
|
+
side_effect=NotFoundError(message="Item not found")
|
|
291
|
+
)
|
|
292
|
+
{%- elif cookiecutter.use_sqlite %}
|
|
293
|
+
mock_item_service.delete = MagicMock(
|
|
294
|
+
side_effect=NotFoundError(message="Item not found")
|
|
295
|
+
)
|
|
296
|
+
{%- endif %}
|
|
297
|
+
|
|
298
|
+
{%- if cookiecutter.use_postgresql %}
|
|
299
|
+
response = await client_with_mock_service.delete(
|
|
300
|
+
f"{settings.API_V1_STR}/items/{uuid4()}",
|
|
301
|
+
)
|
|
302
|
+
{%- else %}
|
|
303
|
+
response = await client_with_mock_service.delete(
|
|
304
|
+
f"{settings.API_V1_STR}/items/nonexistent-id",
|
|
305
|
+
)
|
|
306
|
+
{%- endif %}
|
|
307
|
+
assert response.status_code == 404
|
|
308
|
+
{%- else %}
|
|
309
|
+
"""Item tests - not configured (example CRUD disabled or no database)."""
|
|
310
|
+
{%- endif %}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
{%- if cookiecutter.use_jwt %}
|
|
2
|
+
"""Tests for user routes."""
|
|
3
|
+
{%- if cookiecutter.use_database or cookiecutter.enable_redis %}
|
|
4
|
+
# ruff: noqa: I001 - Imports structured for Jinja2 template conditionals
|
|
5
|
+
{%- endif %}
|
|
6
|
+
|
|
7
|
+
from datetime import UTC, datetime
|
|
8
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
9
|
+
from uuid import uuid4
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
from httpx import ASGITransport, AsyncClient
|
|
13
|
+
|
|
14
|
+
from app.api.deps import get_current_active_superuser, get_current_user, get_user_service
|
|
15
|
+
{%- if cookiecutter.use_database %}
|
|
16
|
+
from app.api.deps import get_db_session
|
|
17
|
+
{%- endif %}
|
|
18
|
+
{%- if cookiecutter.enable_redis %}
|
|
19
|
+
from app.api.deps import get_redis
|
|
20
|
+
{%- endif %}
|
|
21
|
+
from app.core.config import settings
|
|
22
|
+
from app.main import app
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MockUser:
|
|
26
|
+
"""Mock user for testing."""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
id=None,
|
|
31
|
+
email="test@example.com",
|
|
32
|
+
full_name="Test User",
|
|
33
|
+
is_active=True,
|
|
34
|
+
is_superuser=False,
|
|
35
|
+
role="admin",
|
|
36
|
+
):
|
|
37
|
+
{%- if cookiecutter.use_postgresql %}
|
|
38
|
+
self.id = id or uuid4()
|
|
39
|
+
{%- else %}
|
|
40
|
+
self.id = id or str(uuid4())
|
|
41
|
+
{%- endif %}
|
|
42
|
+
self.email = email
|
|
43
|
+
self.full_name = full_name
|
|
44
|
+
self.is_active = is_active
|
|
45
|
+
self.is_superuser = is_superuser
|
|
46
|
+
self.role = role
|
|
47
|
+
self.hashed_password = "hashed"
|
|
48
|
+
self.created_at = datetime.now(UTC)
|
|
49
|
+
self.updated_at = datetime.now(UTC)
|
|
50
|
+
|
|
51
|
+
def has_role(self, role) -> bool:
|
|
52
|
+
"""Check if user has the specified role."""
|
|
53
|
+
if hasattr(role, "value"):
|
|
54
|
+
return self.role == role.value
|
|
55
|
+
return self.role == role
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@pytest.fixture
|
|
59
|
+
def mock_user() -> MockUser:
|
|
60
|
+
"""Create a mock regular user."""
|
|
61
|
+
return MockUser()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.fixture
|
|
65
|
+
def mock_superuser() -> MockUser:
|
|
66
|
+
"""Create a mock superuser."""
|
|
67
|
+
return MockUser(is_superuser=True, email="admin@example.com")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.fixture
|
|
71
|
+
def mock_user_service(mock_user: MockUser) -> MagicMock:
|
|
72
|
+
"""Create a mock user service."""
|
|
73
|
+
service = MagicMock()
|
|
74
|
+
service.get_by_id = AsyncMock(return_value=mock_user)
|
|
75
|
+
service.get_multi = AsyncMock(return_value=[mock_user])
|
|
76
|
+
service.update = AsyncMock(return_value=mock_user)
|
|
77
|
+
service.delete = AsyncMock(return_value=mock_user)
|
|
78
|
+
return service
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@pytest.fixture
|
|
82
|
+
async def auth_client(
|
|
83
|
+
mock_user: MockUser,
|
|
84
|
+
mock_user_service: MagicMock,
|
|
85
|
+
{%- if cookiecutter.enable_redis %}
|
|
86
|
+
mock_redis: MagicMock,
|
|
87
|
+
{%- endif %}
|
|
88
|
+
{%- if cookiecutter.use_database %}
|
|
89
|
+
mock_db_session,
|
|
90
|
+
{%- endif %}
|
|
91
|
+
) -> AsyncClient:
|
|
92
|
+
"""Client with authenticated regular user."""
|
|
93
|
+
app.dependency_overrides[get_current_user] = lambda: mock_user
|
|
94
|
+
app.dependency_overrides[get_user_service] = lambda: mock_user_service
|
|
95
|
+
{%- if cookiecutter.enable_redis %}
|
|
96
|
+
app.dependency_overrides[get_redis] = lambda: mock_redis
|
|
97
|
+
{%- endif %}
|
|
98
|
+
{%- if cookiecutter.use_database %}
|
|
99
|
+
app.dependency_overrides[get_db_session] = lambda: mock_db_session
|
|
100
|
+
{%- endif %}
|
|
101
|
+
|
|
102
|
+
async with AsyncClient(
|
|
103
|
+
transport=ASGITransport(app=app),
|
|
104
|
+
base_url="http://test",
|
|
105
|
+
) as ac:
|
|
106
|
+
yield ac
|
|
107
|
+
|
|
108
|
+
app.dependency_overrides.clear()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@pytest.fixture
|
|
112
|
+
async def superuser_client(
|
|
113
|
+
mock_superuser: MockUser,
|
|
114
|
+
mock_user_service: MagicMock,
|
|
115
|
+
{%- if cookiecutter.enable_redis %}
|
|
116
|
+
mock_redis: MagicMock,
|
|
117
|
+
{%- endif %}
|
|
118
|
+
{%- if cookiecutter.use_database %}
|
|
119
|
+
mock_db_session,
|
|
120
|
+
{%- endif %}
|
|
121
|
+
) -> AsyncClient:
|
|
122
|
+
"""Client with authenticated superuser."""
|
|
123
|
+
app.dependency_overrides[get_current_user] = lambda: mock_superuser
|
|
124
|
+
app.dependency_overrides[get_current_active_superuser] = lambda: mock_superuser
|
|
125
|
+
app.dependency_overrides[get_user_service] = lambda: mock_user_service
|
|
126
|
+
{%- if cookiecutter.enable_redis %}
|
|
127
|
+
app.dependency_overrides[get_redis] = lambda: mock_redis
|
|
128
|
+
{%- endif %}
|
|
129
|
+
{%- if cookiecutter.use_database %}
|
|
130
|
+
app.dependency_overrides[get_db_session] = lambda: mock_db_session
|
|
131
|
+
{%- endif %}
|
|
132
|
+
|
|
133
|
+
async with AsyncClient(
|
|
134
|
+
transport=ASGITransport(app=app),
|
|
135
|
+
base_url="http://test",
|
|
136
|
+
) as ac:
|
|
137
|
+
yield ac
|
|
138
|
+
|
|
139
|
+
app.dependency_overrides.clear()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@pytest.mark.anyio
|
|
143
|
+
async def test_read_current_user(auth_client: AsyncClient, mock_user: MockUser):
|
|
144
|
+
"""Test getting current user."""
|
|
145
|
+
response = await auth_client.get(f"{settings.API_V1_STR}/users/me")
|
|
146
|
+
assert response.status_code == 200
|
|
147
|
+
data = response.json()
|
|
148
|
+
assert data["email"] == mock_user.email
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@pytest.mark.anyio
|
|
152
|
+
async def test_update_current_user(auth_client: AsyncClient, mock_user_service: MagicMock):
|
|
153
|
+
"""Test updating current user."""
|
|
154
|
+
response = await auth_client.patch(
|
|
155
|
+
f"{settings.API_V1_STR}/users/me",
|
|
156
|
+
json={"full_name": "Updated Name"},
|
|
157
|
+
)
|
|
158
|
+
assert response.status_code == 200
|
|
159
|
+
mock_user_service.update.assert_called_once()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
{%- if not cookiecutter.enable_pagination %}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@pytest.mark.anyio
|
|
166
|
+
async def test_read_users_superuser(superuser_client: AsyncClient, mock_user_service: MagicMock):
|
|
167
|
+
"""Test getting all users as superuser."""
|
|
168
|
+
response = await superuser_client.get(f"{settings.API_V1_STR}/users")
|
|
169
|
+
assert response.status_code == 200
|
|
170
|
+
data = response.json()
|
|
171
|
+
assert isinstance(data, list)
|
|
172
|
+
{%- endif %}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@pytest.mark.anyio
|
|
176
|
+
async def test_read_user_by_id(
|
|
177
|
+
superuser_client: AsyncClient,
|
|
178
|
+
mock_user: MockUser,
|
|
179
|
+
mock_user_service: MagicMock,
|
|
180
|
+
):
|
|
181
|
+
"""Test getting user by ID as superuser."""
|
|
182
|
+
response = await superuser_client.get(
|
|
183
|
+
f"{settings.API_V1_STR}/users/{mock_user.id}"
|
|
184
|
+
)
|
|
185
|
+
assert response.status_code == 200
|
|
186
|
+
data = response.json()
|
|
187
|
+
assert data["email"] == mock_user.email
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@pytest.mark.anyio
|
|
191
|
+
async def test_read_user_by_id_not_found(
|
|
192
|
+
superuser_client: AsyncClient,
|
|
193
|
+
mock_user_service: MagicMock,
|
|
194
|
+
):
|
|
195
|
+
"""Test getting non-existent user."""
|
|
196
|
+
from app.core.exceptions import NotFoundError
|
|
197
|
+
|
|
198
|
+
mock_user_service.get_by_id = AsyncMock(
|
|
199
|
+
side_effect=NotFoundError(message="User not found")
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
response = await superuser_client.get(
|
|
203
|
+
f"{settings.API_V1_STR}/users/{uuid4()}"
|
|
204
|
+
)
|
|
205
|
+
assert response.status_code == 404
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@pytest.mark.anyio
|
|
209
|
+
async def test_update_user_by_id(
|
|
210
|
+
superuser_client: AsyncClient,
|
|
211
|
+
mock_user: MockUser,
|
|
212
|
+
mock_user_service: MagicMock,
|
|
213
|
+
):
|
|
214
|
+
"""Test updating user by ID as superuser."""
|
|
215
|
+
response = await superuser_client.patch(
|
|
216
|
+
f"{settings.API_V1_STR}/users/{mock_user.id}",
|
|
217
|
+
json={"full_name": "Admin Updated"},
|
|
218
|
+
)
|
|
219
|
+
assert response.status_code == 200
|
|
220
|
+
mock_user_service.update.assert_called_once()
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@pytest.mark.anyio
|
|
224
|
+
async def test_delete_user_by_id(
|
|
225
|
+
superuser_client: AsyncClient,
|
|
226
|
+
mock_user: MockUser,
|
|
227
|
+
mock_user_service: MagicMock,
|
|
228
|
+
):
|
|
229
|
+
"""Test deleting user by ID as superuser."""
|
|
230
|
+
response = await superuser_client.delete(
|
|
231
|
+
f"{settings.API_V1_STR}/users/{mock_user.id}"
|
|
232
|
+
)
|
|
233
|
+
assert response.status_code == 204
|
|
234
|
+
mock_user_service.delete.assert_called_once()
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@pytest.mark.anyio
|
|
238
|
+
async def test_delete_user_by_id_not_found(
|
|
239
|
+
superuser_client: AsyncClient,
|
|
240
|
+
mock_user_service: MagicMock,
|
|
241
|
+
):
|
|
242
|
+
"""Test deleting non-existent user."""
|
|
243
|
+
from app.core.exceptions import NotFoundError
|
|
244
|
+
|
|
245
|
+
mock_user_service.delete = AsyncMock(
|
|
246
|
+
side_effect=NotFoundError(message="User not found")
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
response = await superuser_client.delete(
|
|
250
|
+
f"{settings.API_V1_STR}/users/{uuid4()}"
|
|
251
|
+
)
|
|
252
|
+
assert response.status_code == 404
|
|
253
|
+
{%- endif %}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Test configuration and fixtures.
|
|
2
|
+
|
|
3
|
+
Uses anyio for async testing instead of pytest-asyncio.
|
|
4
|
+
This allows using the same async primitives that Starlette uses internally.
|
|
5
|
+
See: https://anyio.readthedocs.io/en/stable/testing.html
|
|
6
|
+
"""
|
|
7
|
+
{%- if cookiecutter.enable_redis or cookiecutter.use_database or cookiecutter.use_api_key %}
|
|
8
|
+
# ruff: noqa: I001 - Imports structured for Jinja2 template conditionals
|
|
9
|
+
{%- endif %}
|
|
10
|
+
|
|
11
|
+
from collections.abc import AsyncGenerator
|
|
12
|
+
{%- if cookiecutter.enable_redis %}
|
|
13
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
14
|
+
{%- elif cookiecutter.use_postgresql or cookiecutter.use_mongodb %}
|
|
15
|
+
from unittest.mock import AsyncMock
|
|
16
|
+
{%- elif cookiecutter.use_sqlite %}
|
|
17
|
+
from unittest.mock import MagicMock
|
|
18
|
+
{%- endif %}
|
|
19
|
+
|
|
20
|
+
import pytest
|
|
21
|
+
from httpx import ASGITransport, AsyncClient
|
|
22
|
+
|
|
23
|
+
from app.main import app
|
|
24
|
+
{%- if cookiecutter.use_api_key %}
|
|
25
|
+
from app.core.config import settings
|
|
26
|
+
{%- endif %}
|
|
27
|
+
{%- if cookiecutter.enable_redis %}
|
|
28
|
+
from app.api.deps import get_redis
|
|
29
|
+
from app.clients.redis import RedisClient
|
|
30
|
+
{%- endif %}
|
|
31
|
+
{%- if cookiecutter.use_database %}
|
|
32
|
+
from app.api.deps import get_db_session
|
|
33
|
+
{%- endif %}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.fixture
|
|
37
|
+
def anyio_backend() -> str:
|
|
38
|
+
"""Specify the async backend for anyio tests.
|
|
39
|
+
|
|
40
|
+
Options: "asyncio" or "trio". We use asyncio since that's what uvicorn uses.
|
|
41
|
+
"""
|
|
42
|
+
return "asyncio"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
{%- if cookiecutter.enable_redis %}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@pytest.fixture
|
|
49
|
+
def mock_redis() -> MagicMock:
|
|
50
|
+
"""Create a mock Redis client for testing."""
|
|
51
|
+
mock = MagicMock(spec=RedisClient)
|
|
52
|
+
mock.ping = AsyncMock(return_value=True)
|
|
53
|
+
mock.get = AsyncMock(return_value=None)
|
|
54
|
+
mock.set = AsyncMock(return_value=True)
|
|
55
|
+
mock.delete = AsyncMock(return_value=1)
|
|
56
|
+
mock.exists = AsyncMock(return_value=0)
|
|
57
|
+
mock.incr = AsyncMock(return_value=1)
|
|
58
|
+
mock.expire = AsyncMock(return_value=True)
|
|
59
|
+
return mock
|
|
60
|
+
{%- endif %}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
{%- if cookiecutter.use_postgresql %}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@pytest.fixture
|
|
67
|
+
async def mock_db_session() -> AsyncGenerator[AsyncMock, None]:
|
|
68
|
+
"""Create a mock database session for testing."""
|
|
69
|
+
mock = AsyncMock()
|
|
70
|
+
mock.execute = AsyncMock()
|
|
71
|
+
mock.commit = AsyncMock()
|
|
72
|
+
mock.rollback = AsyncMock()
|
|
73
|
+
mock.close = AsyncMock()
|
|
74
|
+
yield mock
|
|
75
|
+
{%- endif %}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
{%- if cookiecutter.use_sqlite %}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@pytest.fixture
|
|
82
|
+
def mock_db_session() -> MagicMock:
|
|
83
|
+
"""Create a mock database session for testing."""
|
|
84
|
+
mock = MagicMock()
|
|
85
|
+
mock.execute = MagicMock()
|
|
86
|
+
mock.commit = MagicMock()
|
|
87
|
+
mock.rollback = MagicMock()
|
|
88
|
+
mock.close = MagicMock()
|
|
89
|
+
return mock
|
|
90
|
+
{%- endif %}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
{%- if cookiecutter.use_mongodb %}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@pytest.fixture
|
|
97
|
+
async def mock_db_session() -> AsyncMock:
|
|
98
|
+
"""Create a mock MongoDB session for testing."""
|
|
99
|
+
mock = AsyncMock()
|
|
100
|
+
mock.command = AsyncMock(return_value={"ok": 1})
|
|
101
|
+
return mock
|
|
102
|
+
{%- endif %}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@pytest.fixture
|
|
106
|
+
async def client(
|
|
107
|
+
{%- if cookiecutter.enable_redis %}
|
|
108
|
+
mock_redis: MagicMock,
|
|
109
|
+
{%- endif %}
|
|
110
|
+
{%- if cookiecutter.use_database %}
|
|
111
|
+
mock_db_session,
|
|
112
|
+
{%- endif %}
|
|
113
|
+
) -> AsyncGenerator[AsyncClient, None]:
|
|
114
|
+
"""Async HTTP client for testing.
|
|
115
|
+
|
|
116
|
+
Uses HTTPX AsyncClient with ASGITransport instead of Starlette's TestClient.
|
|
117
|
+
This allows proper async testing without thread pool overhead.
|
|
118
|
+
"""
|
|
119
|
+
# Override dependencies for testing
|
|
120
|
+
{%- if cookiecutter.enable_redis %}
|
|
121
|
+
app.dependency_overrides[get_redis] = lambda: mock_redis
|
|
122
|
+
{%- endif %}
|
|
123
|
+
{%- if cookiecutter.use_database %}
|
|
124
|
+
app.dependency_overrides[get_db_session] = lambda: mock_db_session
|
|
125
|
+
{%- endif %}
|
|
126
|
+
|
|
127
|
+
async with AsyncClient(
|
|
128
|
+
transport=ASGITransport(app=app),
|
|
129
|
+
base_url="http://test",
|
|
130
|
+
) as ac:
|
|
131
|
+
yield ac
|
|
132
|
+
|
|
133
|
+
# Clear overrides after test
|
|
134
|
+
app.dependency_overrides.clear()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
{%- if cookiecutter.use_api_key %}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@pytest.fixture
|
|
141
|
+
def api_key_headers() -> dict[str, str]:
|
|
142
|
+
"""Headers with valid API key."""
|
|
143
|
+
return {settings.API_KEY_HEADER: settings.API_KEY}
|
|
144
|
+
{%- endif %}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
{%- if cookiecutter.use_jwt %}
|
|
148
|
+
# Note: For integration tests requiring authenticated users,
|
|
149
|
+
# use dependency overrides with mock users instead of test_user fixture.
|
|
150
|
+
# See tests/api/test_auth.py and tests/api/test_users.py for examples.
|
|
151
|
+
{%- endif %}
|