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.
Files changed (241) hide show
  1. fastapi_fullstack-0.1.7.dist-info/METADATA +739 -0
  2. fastapi_fullstack-0.1.7.dist-info/RECORD +241 -0
  3. fastapi_fullstack-0.1.7.dist-info/WHEEL +4 -0
  4. fastapi_fullstack-0.1.7.dist-info/entry_points.txt +2 -0
  5. fastapi_fullstack-0.1.7.dist-info/licenses/LICENSE +21 -0
  6. fastapi_gen/__init__.py +3 -0
  7. fastapi_gen/cli.py +442 -0
  8. fastapi_gen/config.py +356 -0
  9. fastapi_gen/generator.py +207 -0
  10. fastapi_gen/prompts.py +874 -0
  11. fastapi_gen/template/VARIABLES.md +276 -0
  12. fastapi_gen/template/cookiecutter.json +93 -0
  13. fastapi_gen/template/hooks/post_gen_project.py +355 -0
  14. fastapi_gen/template/{{cookiecutter.project_slug}}/.env.prod.example +56 -0
  15. fastapi_gen/template/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +150 -0
  16. fastapi_gen/template/{{cookiecutter.project_slug}}/.gitignore +109 -0
  17. fastapi_gen/template/{{cookiecutter.project_slug}}/AGENTS.md +55 -0
  18. fastapi_gen/template/{{cookiecutter.project_slug}}/CLAUDE.md +99 -0
  19. fastapi_gen/template/{{cookiecutter.project_slug}}/Makefile +315 -0
  20. fastapi_gen/template/{{cookiecutter.project_slug}}/README.md +768 -0
  21. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.dockerignore +60 -0
  22. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.env.example +155 -0
  23. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.pre-commit-config.yaml +32 -0
  24. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/Dockerfile +56 -0
  25. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/env.py +76 -0
  26. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/script.py.mako +30 -0
  27. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/versions/.gitkeep +0 -0
  28. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic.ini +48 -0
  29. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/__init__.py +3 -0
  30. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/admin.py +447 -0
  31. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/__init__.py +23 -0
  32. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/assistant.py +226 -0
  33. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/langchain_assistant.py +226 -0
  34. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/prompts.py +10 -0
  35. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/__init__.py +13 -0
  36. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/datetime_tool.py +17 -0
  37. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/__init__.py +1 -0
  38. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/deps.py +541 -0
  39. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/exception_handlers.py +98 -0
  40. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/router.py +10 -0
  41. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/__init__.py +9 -0
  42. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/__init__.py +87 -0
  43. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/agent.py +902 -0
  44. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/auth.py +395 -0
  45. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/conversations.py +498 -0
  46. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/health.py +227 -0
  47. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/items.py +275 -0
  48. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/oauth.py +205 -0
  49. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/sessions.py +168 -0
  50. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/users.py +333 -0
  51. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/webhooks.py +477 -0
  52. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/ws.py +46 -0
  53. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/versioning.py +221 -0
  54. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/__init__.py +14 -0
  55. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/redis.py +88 -0
  56. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/__init__.py +117 -0
  57. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/cleanup.py +75 -0
  58. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/example.py +28 -0
  59. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/seed.py +266 -0
  60. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/__init__.py +5 -0
  61. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/cache.py +23 -0
  62. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/config.py +267 -0
  63. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/csrf.py +153 -0
  64. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/exceptions.py +122 -0
  65. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/logfire_setup.py +101 -0
  66. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/middleware.py +99 -0
  67. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/oauth.py +23 -0
  68. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/rate_limit.py +58 -0
  69. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/sanitize.py +271 -0
  70. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/security.py +102 -0
  71. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/__init__.py +7 -0
  72. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/base.py +41 -0
  73. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/__init__.py +31 -0
  74. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/conversation.py +319 -0
  75. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/item.py +96 -0
  76. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/session.py +126 -0
  77. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/user.py +218 -0
  78. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/webhook.py +244 -0
  79. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/session.py +130 -0
  80. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/main.py +334 -0
  81. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/__init__.py +9 -0
  82. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/base.py +73 -0
  83. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/__init__.py +49 -0
  84. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/base.py +154 -0
  85. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/conversation.py +838 -0
  86. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/item.py +222 -0
  87. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/session.py +318 -0
  88. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/user.py +322 -0
  89. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/webhook.py +358 -0
  90. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/__init__.py +50 -0
  91. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/base.py +57 -0
  92. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/conversation.py +192 -0
  93. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/item.py +52 -0
  94. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/session.py +42 -0
  95. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/token.py +31 -0
  96. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/user.py +64 -0
  97. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/webhook.py +89 -0
  98. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/__init__.py +38 -0
  99. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/conversation.py +850 -0
  100. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/item.py +246 -0
  101. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/session.py +333 -0
  102. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/user.py +432 -0
  103. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/webhook.py +561 -0
  104. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/__init__.py +5 -0
  105. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/celery_app.py +64 -0
  106. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/taskiq_app.py +38 -0
  107. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/__init__.py +25 -0
  108. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/examples.py +106 -0
  109. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/schedules.py +29 -0
  110. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/taskiq_examples.py +92 -0
  111. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/__init__.py +1 -0
  112. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/commands.py +438 -0
  113. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/pyproject.toml +180 -0
  114. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/scripts/.gitkeep +0 -0
  115. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/__init__.py +1 -0
  116. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/__init__.py +1 -0
  117. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_auth.py +242 -0
  118. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_exceptions.py +151 -0
  119. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_health.py +113 -0
  120. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_items.py +310 -0
  121. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_users.py +253 -0
  122. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/conftest.py +151 -0
  123. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_admin.py +890 -0
  124. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_agents.py +261 -0
  125. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_clients.py +183 -0
  126. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_commands.py +173 -0
  127. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_core.py +143 -0
  128. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_pipelines.py +118 -0
  129. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_repositories.py +181 -0
  130. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_security.py +124 -0
  131. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_services.py +363 -0
  132. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_worker.py +85 -0
  133. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.dev.yml +242 -0
  134. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.frontend.yml +31 -0
  135. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.prod.yml +435 -0
  136. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.yml +241 -0
  137. fastapi_gen/template/{{cookiecutter.project_slug}}/docs/adding_features.md +132 -0
  138. fastapi_gen/template/{{cookiecutter.project_slug}}/docs/architecture.md +63 -0
  139. fastapi_gen/template/{{cookiecutter.project_slug}}/docs/patterns.md +161 -0
  140. fastapi_gen/template/{{cookiecutter.project_slug}}/docs/testing.md +120 -0
  141. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.dockerignore +40 -0
  142. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.env.example +12 -0
  143. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.gitignore +45 -0
  144. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierignore +19 -0
  145. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierrc +11 -0
  146. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/Dockerfile +44 -0
  147. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/README.md +648 -0
  148. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.setup.ts +49 -0
  149. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.spec.ts +134 -0
  150. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/chat.spec.ts +207 -0
  151. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/home.spec.ts +73 -0
  152. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/instrumentation.ts +14 -0
  153. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/en.json +84 -0
  154. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/pl.json +84 -0
  155. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/next.config.ts +76 -0
  156. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/package.json +69 -0
  157. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/playwright.config.ts +101 -0
  158. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/postcss.config.mjs +7 -0
  159. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/layout.tsx +11 -0
  160. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/login/page.tsx +5 -0
  161. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/register/page.tsx +5 -0
  162. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/chat/page.tsx +48 -0
  163. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/dashboard/page.tsx +99 -0
  164. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/layout.tsx +17 -0
  165. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/profile/page.tsx +152 -0
  166. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/auth/callback/page.tsx +113 -0
  167. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/layout.tsx +46 -0
  168. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/page.tsx +73 -0
  169. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/login/route.ts +58 -0
  170. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/logout/route.ts +24 -0
  171. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/me/route.ts +39 -0
  172. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/oauth-callback/route.ts +50 -0
  173. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/refresh/route.ts +54 -0
  174. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/register/route.ts +26 -0
  175. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/messages/route.ts +41 -0
  176. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/route.ts +108 -0
  177. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/route.ts +73 -0
  178. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/health/route.ts +21 -0
  179. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/globals.css +323 -0
  180. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/layout.tsx +22 -0
  181. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/providers.tsx +29 -0
  182. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/index.ts +2 -0
  183. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/login-form.tsx +120 -0
  184. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/register-form.tsx +153 -0
  185. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-container.tsx +234 -0
  186. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-input.tsx +72 -0
  187. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/conversation-sidebar.tsx +328 -0
  188. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/copy-button.tsx +46 -0
  189. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/index.ts +11 -0
  190. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/local-conversation-sidebar.tsx +295 -0
  191. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/markdown-content.tsx +167 -0
  192. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-item.tsx +79 -0
  193. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-list.tsx +18 -0
  194. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/tool-call-card.tsx +79 -0
  195. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/google-icon.tsx +32 -0
  196. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/index.ts +3 -0
  197. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/language-switcher.tsx +97 -0
  198. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/header.tsx +65 -0
  199. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/index.ts +2 -0
  200. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/sidebar.tsx +82 -0
  201. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/index.ts +7 -0
  202. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-provider.tsx +53 -0
  203. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-toggle.tsx +105 -0
  204. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/badge.tsx +35 -0
  205. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.test.tsx +75 -0
  206. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.tsx +56 -0
  207. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/card.tsx +82 -0
  208. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/index.ts +13 -0
  209. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/input.tsx +21 -0
  210. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/label.tsx +21 -0
  211. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/sheet.tsx +109 -0
  212. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/index.ts +7 -0
  213. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-auth.ts +97 -0
  214. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-chat.ts +203 -0
  215. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-conversations.ts +181 -0
  216. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-local-chat.ts +165 -0
  217. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-websocket.ts +105 -0
  218. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/i18n.ts +37 -0
  219. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/api-client.ts +90 -0
  220. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/constants.ts +39 -0
  221. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/server-api.ts +78 -0
  222. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.test.ts +44 -0
  223. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.ts +44 -0
  224. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/middleware.ts +31 -0
  225. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.test.ts +72 -0
  226. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.ts +64 -0
  227. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-sidebar-store.ts +17 -0
  228. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-store.ts +65 -0
  229. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/conversation-store.ts +76 -0
  230. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/index.ts +9 -0
  231. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/local-chat-store.ts +255 -0
  232. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/sidebar-store.ts +17 -0
  233. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/theme-store.ts +44 -0
  234. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/api.ts +27 -0
  235. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/auth.ts +52 -0
  236. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/chat.ts +83 -0
  237. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/conversation.ts +49 -0
  238. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/index.ts +10 -0
  239. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/tsconfig.json +28 -0
  240. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.config.ts +36 -0
  241. 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 %}