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,180 @@
1
+ [project]
2
+ name = "{{ cookiecutter.project_slug }}"
3
+ version = "0.1.0"
4
+ description = "{{ cookiecutter.project_description }}"
5
+ requires-python = ">={{ cookiecutter.python_version }}"
6
+ license = { text = "MIT" }
7
+ authors = [{ name = "{{ cookiecutter.author_name }}", email = "{{ cookiecutter.author_email }}" }]
8
+
9
+ dependencies = [
10
+ "fastapi>=0.115.0",
11
+ "uvicorn[standard]>=0.32.0",
12
+ "pydantic[email]>=2.10.0",
13
+ "pydantic-settings>=2.6.0",
14
+ {%- if cookiecutter.enable_orjson %}
15
+ "orjson>=3.10.0",
16
+ {%- endif %}
17
+ {%- if cookiecutter.enable_logfire %}
18
+ {%- set logfire_extras = ['fastapi'] %}
19
+ {%- if cookiecutter.use_postgresql and cookiecutter.logfire_database %}
20
+ {%- set _ = logfire_extras.append('asyncpg') %}
21
+ {%- endif %}
22
+ {%- if cookiecutter.enable_redis and cookiecutter.logfire_redis %}
23
+ {%- set _ = logfire_extras.append('redis') %}
24
+ {%- endif %}
25
+ {%- if cookiecutter.use_celery and cookiecutter.logfire_celery %}
26
+ {%- set _ = logfire_extras.append('celery') %}
27
+ {%- endif %}
28
+ {%- if cookiecutter.logfire_httpx %}
29
+ {%- set _ = logfire_extras.append('httpx') %}
30
+ {%- endif %}
31
+ "logfire[{{ logfire_extras | join(',') }}]>=2.0.0",
32
+ {%- endif %}
33
+ {%- if cookiecutter.use_postgresql %}
34
+ "sqlalchemy[asyncio]>=2.0.0",
35
+ "asyncpg>=0.30.0",
36
+ "psycopg2-binary>=2.9.0",
37
+ "alembic>=1.14.0",
38
+ "greenlet>=3.0.0",
39
+ {%- endif %}
40
+ {%- if cookiecutter.use_mongodb %}
41
+ "motor>=3.6.0",
42
+ "beanie>=1.27.0",
43
+ {%- endif %}
44
+ {%- if cookiecutter.use_sqlite %}
45
+ "sqlalchemy>=2.0.0",
46
+ "alembic>=1.14.0",
47
+ {%- endif %}
48
+ {%- if cookiecutter.use_jwt %}
49
+ "pyjwt>=2.9.0",
50
+ "bcrypt>=4.0.0",
51
+ "python-multipart>=0.0.12",
52
+ {%- endif %}
53
+ {%- if cookiecutter.enable_oauth %}
54
+ "authlib>=1.3.0",
55
+ "httpx>=0.27.0",
56
+ {%- endif %}
57
+ {%- if cookiecutter.enable_redis %}
58
+ "redis>=5.2.0",
59
+ {%- endif %}
60
+ {%- if cookiecutter.enable_caching %}
61
+ "fastapi-cache2>=0.2.2",
62
+ {%- endif %}
63
+ {%- if cookiecutter.enable_rate_limiting %}
64
+ "slowapi>=0.1.9",
65
+ {%- endif %}
66
+ {%- if cookiecutter.enable_pagination %}
67
+ "fastapi-pagination>=0.12.31",
68
+ {%- endif %}
69
+ {%- if cookiecutter.enable_sentry %}
70
+ "sentry-sdk[fastapi]>=2.18.0",
71
+ {%- endif %}
72
+ {%- if cookiecutter.enable_admin_panel %}
73
+ "sqladmin>=0.19.0",
74
+ {%- if cookiecutter.admin_require_auth %}
75
+ "itsdangerous>=2.2.0",
76
+ {%- endif %}
77
+ {%- endif %}
78
+ {%- if cookiecutter.use_celery %}
79
+ "celery[redis]>=5.4.0",
80
+ "flower>=2.0.0",
81
+ {%- endif %}
82
+ {%- if cookiecutter.use_taskiq %}
83
+ "taskiq>=0.11.0",
84
+ "taskiq-redis>=1.0.0",
85
+ "taskiq-dependencies>=0.1.0",
86
+ {%- endif %}
87
+ {%- if cookiecutter.use_arq %}
88
+ "arq>=0.26.0",
89
+ {%- endif %}
90
+ {%- if cookiecutter.enable_file_storage %}
91
+ "boto3>=1.35.0",
92
+ {%- endif %}
93
+ {%- if cookiecutter.enable_ai_agent and cookiecutter.use_pydantic_ai %}
94
+ {%- if cookiecutter.use_openai %}
95
+ "pydantic-ai>=0.0.39",
96
+ {%- endif %}
97
+ {%- if cookiecutter.use_anthropic %}
98
+ "pydantic-ai-slim[anthropic]>=0.0.39",
99
+ {%- endif %}
100
+ {%- if cookiecutter.use_openrouter %}
101
+ "pydantic-ai-slim[openrouter]>=0.0.39",
102
+ {%- endif %}
103
+ {%- endif %}
104
+ {%- if cookiecutter.enable_ai_agent and cookiecutter.use_langchain %}
105
+ "langchain>=0.3.0",
106
+ {%- if cookiecutter.use_openai %}
107
+ "langchain-openai>=0.3.0",
108
+ {%- endif %}
109
+ {%- if cookiecutter.use_anthropic %}
110
+ "langchain-anthropic>=0.3.0",
111
+ {%- endif %}
112
+ "langgraph>=0.2.0",
113
+ {%- endif %}
114
+ {%- if cookiecutter.logfire_httpx or cookiecutter.enable_file_storage %}
115
+ "httpx>=0.27.0",
116
+ {%- endif %}
117
+ "click>=8.1.0",
118
+ "tabulate>=0.9.0",
119
+ ]
120
+
121
+ [project.optional-dependencies]
122
+ dev = [
123
+ "pytest>=8.3.0",
124
+ "anyio[trio]>=4.0.0",
125
+ "pytest-cov>=6.0.0",
126
+ "httpx>=0.27.0",
127
+ "ruff>=0.8.0",
128
+ "mypy>=1.13.0",
129
+ {%- if cookiecutter.enable_precommit %}
130
+ "pre-commit>=4.0.0",
131
+ {%- endif %}
132
+ ]
133
+
134
+ [project.scripts]
135
+ {{ cookiecutter.project_slug }} = "cli.commands:main"
136
+
137
+ [build-system]
138
+ requires = ["hatchling"]
139
+ build-backend = "hatchling.build"
140
+
141
+ [tool.hatch.build.targets.wheel]
142
+ packages = ["app", "cli"]
143
+
144
+ [tool.ruff]
145
+ target-version = "py311"
146
+ line-length = 100
147
+
148
+ [tool.ruff.lint]
149
+ select = [
150
+ "E", # pycodestyle errors
151
+ "W", # pycodestyle warnings
152
+ "F", # pyflakes
153
+ "I", # isort
154
+ "B", # flake8-bugbear
155
+ "C4", # flake8-comprehensions
156
+ "UP", # pyupgrade
157
+ "SIM", # flake8-simplify
158
+ "RUF", # ruff-specific rules
159
+ ]
160
+ ignore = [
161
+ "E501", # line too long (handled by formatter)
162
+ "B008", # function call in default argument (needed for FastAPI Depends)
163
+ ]
164
+
165
+ [tool.ruff.lint.isort]
166
+ known-first-party = ["app", "cli"]
167
+
168
+ [tool.mypy]
169
+ python_version = "3.11"
170
+ strict = true
171
+ warn_return_any = true
172
+ warn_unused_configs = true
173
+ ignore_missing_imports = true
174
+
175
+ [tool.pytest.ini_options]
176
+ testpaths = ["tests"]
177
+
178
+ [tool.{{ cookiecutter.generator_name }}]
179
+ generator_version = "{{ cookiecutter.generator_version }}"
180
+ generated_at = "{{ cookiecutter.generated_at }}"
@@ -0,0 +1,242 @@
1
+ {%- if cookiecutter.use_jwt %}
2
+ # ruff: noqa: I001 - Imports structured for Jinja2 template conditionals
3
+ """Tests for authentication routes."""
4
+
5
+ from datetime import UTC, datetime
6
+ from unittest.mock import AsyncMock, MagicMock
7
+ from uuid import uuid4
8
+
9
+ import pytest
10
+ from httpx import AsyncClient
11
+
12
+ from app.api.deps import get_user_service
13
+ from app.core.config import settings
14
+ from app.core.security import create_access_token, create_refresh_token
15
+ from app.main import app
16
+
17
+
18
+ class MockUser:
19
+ """Mock user for testing."""
20
+
21
+ def __init__(
22
+ self,
23
+ id=None,
24
+ email="test@example.com",
25
+ full_name="Test User",
26
+ is_active=True,
27
+ is_superuser=False,
28
+ ):
29
+ self.id = id or uuid4()
30
+ self.email = email
31
+ self.full_name = full_name
32
+ self.is_active = is_active
33
+ self.is_superuser = is_superuser
34
+ self.hashed_password = "hashed"
35
+ self.created_at = datetime.now(UTC)
36
+ self.updated_at = datetime.now(UTC)
37
+
38
+
39
+ @pytest.fixture
40
+ def mock_user() -> MockUser:
41
+ """Create a mock user."""
42
+ return MockUser()
43
+
44
+
45
+ @pytest.fixture
46
+ def mock_user_service(mock_user: MockUser) -> MagicMock:
47
+ """Create a mock user service."""
48
+ service = MagicMock()
49
+ service.authenticate = AsyncMock(return_value=mock_user)
50
+ service.register = AsyncMock(return_value=mock_user)
51
+ service.get_by_id = AsyncMock(return_value=mock_user)
52
+ service.get_by_email = AsyncMock(return_value=mock_user)
53
+ return service
54
+
55
+
56
+ @pytest.fixture
57
+ async def client_with_mock_service(
58
+ mock_user_service: MagicMock,
59
+ {%- if cookiecutter.enable_redis %}
60
+ mock_redis: MagicMock,
61
+ {%- endif %}
62
+ {%- if cookiecutter.use_database %}
63
+ mock_db_session,
64
+ {%- endif %}
65
+ ) -> AsyncClient:
66
+ """Client with mocked user service."""
67
+ {%- if cookiecutter.enable_redis %}
68
+ from app.api.deps import get_redis
69
+ {%- endif %}
70
+ {%- if cookiecutter.use_database %}
71
+ from app.api.deps import get_db_session
72
+ {%- endif %}
73
+ from httpx import ASGITransport
74
+
75
+ app.dependency_overrides[get_user_service] = lambda: mock_user_service
76
+ {%- if cookiecutter.enable_redis %}
77
+ app.dependency_overrides[get_redis] = lambda: mock_redis
78
+ {%- endif %}
79
+ {%- if cookiecutter.use_database %}
80
+ app.dependency_overrides[get_db_session] = lambda: mock_db_session
81
+ {%- endif %}
82
+
83
+ async with AsyncClient(
84
+ transport=ASGITransport(app=app),
85
+ base_url="http://test",
86
+ ) as ac:
87
+ yield ac
88
+
89
+ app.dependency_overrides.clear()
90
+
91
+
92
+ @pytest.mark.anyio
93
+ async def test_login_success(client_with_mock_service: AsyncClient):
94
+ """Test successful login."""
95
+ response = await client_with_mock_service.post(
96
+ f"{settings.API_V1_STR}/auth/login",
97
+ data={"username": "test@example.com", "password": "password123"},
98
+ )
99
+ assert response.status_code == 200
100
+ data = response.json()
101
+ assert "access_token" in data
102
+ assert "refresh_token" in data
103
+ assert data["token_type"] == "bearer"
104
+
105
+
106
+ @pytest.mark.anyio
107
+ async def test_login_invalid_credentials(
108
+ client_with_mock_service: AsyncClient,
109
+ mock_user_service: MagicMock,
110
+ ):
111
+ """Test login with invalid credentials."""
112
+ from app.core.exceptions import AuthenticationError
113
+
114
+ mock_user_service.authenticate = AsyncMock(
115
+ side_effect=AuthenticationError(message="Invalid credentials")
116
+ )
117
+
118
+ response = await client_with_mock_service.post(
119
+ f"{settings.API_V1_STR}/auth/login",
120
+ data={"username": "test@example.com", "password": "wrongpassword"},
121
+ )
122
+ assert response.status_code == 401
123
+
124
+
125
+ @pytest.mark.anyio
126
+ async def test_register_success(client_with_mock_service: AsyncClient):
127
+ """Test successful registration."""
128
+ response = await client_with_mock_service.post(
129
+ f"{settings.API_V1_STR}/auth/register",
130
+ json={
131
+ "email": "new@example.com",
132
+ "password": "password123",
133
+ "full_name": "New User",
134
+ },
135
+ )
136
+ assert response.status_code == 201
137
+ data = response.json()
138
+ assert data["email"] == "test@example.com" # From mock
139
+
140
+
141
+ @pytest.mark.anyio
142
+ async def test_register_duplicate_email(
143
+ client_with_mock_service: AsyncClient,
144
+ mock_user_service: MagicMock,
145
+ ):
146
+ """Test registration with duplicate email."""
147
+ from app.core.exceptions import AlreadyExistsError
148
+
149
+ mock_user_service.register = AsyncMock(
150
+ side_effect=AlreadyExistsError(message="Email already registered")
151
+ )
152
+
153
+ response = await client_with_mock_service.post(
154
+ f"{settings.API_V1_STR}/auth/register",
155
+ json={
156
+ "email": "existing@example.com",
157
+ "password": "password123",
158
+ "full_name": "Test User",
159
+ },
160
+ )
161
+ assert response.status_code == 409
162
+
163
+
164
+ @pytest.mark.anyio
165
+ async def test_refresh_token_success(
166
+ client_with_mock_service: AsyncClient,
167
+ mock_user: MockUser,
168
+ ):
169
+ """Test successful token refresh."""
170
+ refresh_token = create_refresh_token(subject=str(mock_user.id))
171
+
172
+ response = await client_with_mock_service.post(
173
+ f"{settings.API_V1_STR}/auth/refresh",
174
+ json={"refresh_token": refresh_token},
175
+ )
176
+ assert response.status_code == 200
177
+ data = response.json()
178
+ assert "access_token" in data
179
+ assert "refresh_token" in data
180
+
181
+
182
+ @pytest.mark.anyio
183
+ async def test_refresh_token_invalid(client_with_mock_service: AsyncClient):
184
+ """Test refresh with invalid token."""
185
+ response = await client_with_mock_service.post(
186
+ f"{settings.API_V1_STR}/auth/refresh",
187
+ json={"refresh_token": "invalid.token.here"},
188
+ )
189
+ assert response.status_code == 401
190
+
191
+
192
+ @pytest.mark.anyio
193
+ async def test_refresh_token_wrong_type(
194
+ client_with_mock_service: AsyncClient,
195
+ mock_user: MockUser,
196
+ ):
197
+ """Test refresh with access token instead of refresh token."""
198
+ access_token = create_access_token(subject=str(mock_user.id))
199
+
200
+ response = await client_with_mock_service.post(
201
+ f"{settings.API_V1_STR}/auth/refresh",
202
+ json={"refresh_token": access_token},
203
+ )
204
+ assert response.status_code == 401
205
+
206
+
207
+ @pytest.mark.anyio
208
+ async def test_refresh_token_inactive_user(
209
+ client_with_mock_service: AsyncClient,
210
+ mock_user_service: MagicMock,
211
+ ):
212
+ """Test refresh token for inactive user."""
213
+ inactive_user = MockUser(is_active=False)
214
+ mock_user_service.get_by_id = AsyncMock(return_value=inactive_user)
215
+ refresh_token = create_refresh_token(subject=str(inactive_user.id))
216
+
217
+ response = await client_with_mock_service.post(
218
+ f"{settings.API_V1_STR}/auth/refresh",
219
+ json={"refresh_token": refresh_token},
220
+ )
221
+ assert response.status_code == 401
222
+
223
+
224
+ @pytest.mark.anyio
225
+ async def test_get_current_user(
226
+ client_with_mock_service: AsyncClient,
227
+ mock_user: MockUser,
228
+ mock_user_service: MagicMock,
229
+ ):
230
+ """Test getting current user info."""
231
+ from app.api.deps import get_current_user
232
+
233
+ # Override get_current_user to return mock user
234
+ app.dependency_overrides[get_current_user] = lambda: mock_user
235
+
236
+ response = await client_with_mock_service.get(
237
+ f"{settings.API_V1_STR}/auth/me",
238
+ )
239
+ assert response.status_code == 200
240
+ data = response.json()
241
+ assert data["email"] == mock_user.email
242
+ {%- endif %}
@@ -0,0 +1,151 @@
1
+ """Exception handler tests."""
2
+ {%- if cookiecutter.use_jwt or cookiecutter.use_api_key %}
3
+ # ruff: noqa: I001, E402 - Imports structured for Jinja2 template conditionals
4
+ {%- endif %}
5
+
6
+ import pytest
7
+ from httpx import AsyncClient
8
+
9
+ from app.core.config import settings
10
+
11
+
12
+ @pytest.mark.anyio
13
+ async def test_not_found_error_format(client: AsyncClient):
14
+ """Test that 404 errors return proper JSON format."""
15
+ response = await client.get(f"{settings.API_V1_STR}/nonexistent-endpoint")
16
+ assert response.status_code == 404
17
+ # FastAPI returns 404 for unknown routes
18
+
19
+
20
+ {%- if cookiecutter.use_jwt %}
21
+
22
+ from unittest.mock import AsyncMock, MagicMock
23
+
24
+ from httpx import ASGITransport
25
+
26
+ {%- if cookiecutter.enable_redis %}
27
+ from app.api.deps import get_redis
28
+ {%- endif %}
29
+ {%- if cookiecutter.use_database %}
30
+ from app.api.deps import get_db_session
31
+ {%- endif %}
32
+ from app.main import app
33
+
34
+
35
+ @pytest.mark.anyio
36
+ async def test_authentication_error_returns_401(client: AsyncClient):
37
+ """Test that authentication errors return 401 with proper headers."""
38
+ response = await client.get(
39
+ f"{settings.API_V1_STR}/users/me",
40
+ headers={"Authorization": "Bearer invalid-token"},
41
+ )
42
+ assert response.status_code == 401
43
+ assert "WWW-Authenticate" in response.headers
44
+
45
+
46
+ @pytest.mark.anyio
47
+ async def test_missing_auth_returns_401(client: AsyncClient):
48
+ """Test that missing authentication returns 401."""
49
+ response = await client.get(f"{settings.API_V1_STR}/users/me")
50
+ assert response.status_code == 401
51
+
52
+
53
+ @pytest.fixture
54
+ def mock_user_service_with_errors() -> MagicMock:
55
+ """Create mock user service that raises errors."""
56
+ from app.core.exceptions import AlreadyExistsError, AuthenticationError
57
+
58
+ service = MagicMock()
59
+ service.register = AsyncMock(
60
+ side_effect=AlreadyExistsError(message="Email already registered")
61
+ )
62
+ service.authenticate = AsyncMock(
63
+ side_effect=AuthenticationError(message="Invalid credentials")
64
+ )
65
+ return service
66
+
67
+
68
+ @pytest.fixture
69
+ async def error_client(
70
+ mock_user_service_with_errors: MagicMock,
71
+ {%- if cookiecutter.enable_redis %}
72
+ mock_redis: MagicMock,
73
+ {%- endif %}
74
+ {%- if cookiecutter.use_database %}
75
+ mock_db_session,
76
+ {%- endif %}
77
+ ) -> AsyncClient:
78
+ """Client with mocked services that raise errors."""
79
+ from app.api.deps import get_user_service
80
+
81
+ app.dependency_overrides[get_user_service] = lambda: mock_user_service_with_errors
82
+ {%- if cookiecutter.enable_redis %}
83
+ app.dependency_overrides[get_redis] = lambda: mock_redis
84
+ {%- endif %}
85
+ {%- if cookiecutter.use_database %}
86
+ app.dependency_overrides[get_db_session] = lambda: mock_db_session
87
+ {%- endif %}
88
+
89
+ async with AsyncClient(
90
+ transport=ASGITransport(app=app),
91
+ base_url="http://test",
92
+ ) as ac:
93
+ yield ac
94
+
95
+ app.dependency_overrides.clear()
96
+
97
+
98
+ @pytest.mark.anyio
99
+ async def test_register_duplicate_email_returns_409(error_client: AsyncClient):
100
+ """Test that registering with existing email returns 409."""
101
+ response = await error_client.post(
102
+ f"{settings.API_V1_STR}/auth/register",
103
+ json={
104
+ "email": "existing@example.com",
105
+ "password": "password123",
106
+ "full_name": "Test User",
107
+ },
108
+ )
109
+ assert response.status_code == 409
110
+ data = response.json()
111
+ assert "error" in data
112
+ assert data["error"]["code"] == "ALREADY_EXISTS"
113
+
114
+
115
+ @pytest.mark.anyio
116
+ async def test_invalid_login_returns_401(error_client: AsyncClient):
117
+ """Test that invalid login credentials return 401."""
118
+ response = await error_client.post(
119
+ f"{settings.API_V1_STR}/auth/login",
120
+ data={
121
+ "username": "nonexistent@example.com",
122
+ "password": "wrongpassword",
123
+ },
124
+ )
125
+ assert response.status_code == 401
126
+ data = response.json()
127
+ assert "error" in data
128
+ assert data["error"]["code"] == "AUTHENTICATION_ERROR"
129
+ {%- endif %}
130
+
131
+
132
+ {%- if cookiecutter.use_api_key %}
133
+
134
+
135
+ @pytest.mark.anyio
136
+ async def test_missing_api_key_returns_401(client: AsyncClient):
137
+ """Test that missing API key returns 401."""
138
+ # Health endpoint might not require auth, but we test the middleware
139
+ # For endpoints that require API key, they should return 401
140
+ await client.get(f"{settings.API_V1_STR}/health")
141
+
142
+
143
+ @pytest.mark.anyio
144
+ async def test_invalid_api_key_returns_403(client: AsyncClient):
145
+ """Test that invalid API key returns 403."""
146
+ # Health endpoint might not require auth
147
+ await client.get(
148
+ f"{settings.API_V1_STR}/health",
149
+ headers={settings.API_KEY_HEADER: "invalid-key"},
150
+ )
151
+ {%- endif %}
@@ -0,0 +1,113 @@
1
+ """Health endpoint tests."""
2
+ {%- if cookiecutter.enable_redis or cookiecutter.use_postgresql or cookiecutter.use_mongodb %}
3
+
4
+ from unittest.mock import AsyncMock
5
+ {%- endif %}
6
+
7
+ import pytest
8
+ from httpx import AsyncClient
9
+
10
+ from app.core.config import settings
11
+
12
+
13
+ @pytest.mark.anyio
14
+ async def test_health_check(client: AsyncClient):
15
+ """Test liveness probe."""
16
+ response = await client.get(f"{settings.API_V1_STR}/health")
17
+ assert response.status_code == 200
18
+ data = response.json()
19
+ assert data["status"] == "healthy"
20
+
21
+
22
+ @pytest.mark.anyio
23
+ async def test_readiness_check(client: AsyncClient):
24
+ """Test readiness probe with mocked dependencies."""
25
+ response = await client.get(f"{settings.API_V1_STR}/ready")
26
+ assert response.status_code == 200
27
+ data = response.json()
28
+ assert data["status"] in ["ready", "degraded"]
29
+ assert "checks" in data
30
+
31
+
32
+ {%- if cookiecutter.enable_redis %}
33
+
34
+
35
+ @pytest.mark.anyio
36
+ async def test_readiness_check_redis_healthy(client: AsyncClient, mock_redis):
37
+ """Test readiness when Redis is healthy."""
38
+ mock_redis.ping = AsyncMock(return_value=True)
39
+
40
+ response = await client.get(f"{settings.API_V1_STR}/ready")
41
+ assert response.status_code == 200
42
+ data = response.json()
43
+ assert data["checks"]["redis"] is True
44
+
45
+
46
+ @pytest.mark.anyio
47
+ async def test_readiness_check_redis_unhealthy(client: AsyncClient, mock_redis):
48
+ """Test readiness when Redis is unhealthy."""
49
+ mock_redis.ping = AsyncMock(side_effect=Exception("Connection failed"))
50
+
51
+ response = await client.get(f"{settings.API_V1_STR}/ready")
52
+ # Should return 503 when Redis is down
53
+ assert response.status_code == 503
54
+ data = response.json()
55
+ assert data["status"] == "degraded"
56
+ assert data["checks"]["redis"] is False
57
+ {%- endif %}
58
+
59
+
60
+ {%- if cookiecutter.use_postgresql %}
61
+
62
+
63
+ @pytest.mark.anyio
64
+ async def test_readiness_check_db_healthy(client: AsyncClient, mock_db_session):
65
+ """Test readiness when database is healthy."""
66
+ # Mock successful DB query
67
+ mock_db_session.execute = AsyncMock()
68
+
69
+ response = await client.get(f"{settings.API_V1_STR}/ready")
70
+ assert response.status_code == 200
71
+ data = response.json()
72
+ assert data["checks"]["database"]["status"] == "healthy"
73
+
74
+
75
+ @pytest.mark.anyio
76
+ async def test_readiness_check_db_unhealthy(client: AsyncClient, mock_db_session):
77
+ """Test readiness when database is unhealthy."""
78
+ mock_db_session.execute = AsyncMock(side_effect=Exception("DB connection failed"))
79
+
80
+ response = await client.get(f"{settings.API_V1_STR}/ready")
81
+ # Should return 503 when DB is down
82
+ assert response.status_code == 503
83
+ data = response.json()
84
+ assert data["status"] == "not_ready"
85
+ assert data["checks"]["database"]["status"] == "unhealthy"
86
+ {%- endif %}
87
+
88
+
89
+ {%- if cookiecutter.use_mongodb %}
90
+
91
+
92
+ @pytest.mark.anyio
93
+ async def test_readiness_check_db_healthy(client: AsyncClient, mock_db_session):
94
+ """Test readiness when MongoDB is healthy."""
95
+ mock_db_session.command = AsyncMock(return_value={"ok": 1})
96
+
97
+ response = await client.get(f"{settings.API_V1_STR}/ready")
98
+ assert response.status_code == 200
99
+ data = response.json()
100
+ assert data["checks"]["database"] is True
101
+
102
+
103
+ @pytest.mark.anyio
104
+ async def test_readiness_check_db_unhealthy(client: AsyncClient, mock_db_session):
105
+ """Test readiness when MongoDB is unhealthy."""
106
+ mock_db_session.command = AsyncMock(side_effect=Exception("MongoDB connection failed"))
107
+
108
+ response = await client.get(f"{settings.API_V1_STR}/ready")
109
+ assert response.status_code == 503
110
+ data = response.json()
111
+ assert data["status"] == "degraded"
112
+ assert data["checks"]["database"] is False
113
+ {%- endif %}