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,261 @@
1
+ {%- if cookiecutter.enable_ai_agent and cookiecutter.use_pydantic_ai %}
2
+ """Tests for AI agent module (PydanticAI)."""
3
+
4
+ from unittest.mock import MagicMock, patch
5
+
6
+ import pytest
7
+
8
+ from app.agents.assistant import AssistantAgent, Deps, get_agent, run_agent
9
+ from app.agents.tools.datetime_tool import get_current_datetime
10
+
11
+
12
+ class TestDeps:
13
+ """Tests for Deps dataclass."""
14
+
15
+ def test_deps_default_values(self):
16
+ """Test Deps has correct default values."""
17
+ deps = Deps()
18
+ assert deps.user_id is None
19
+ assert deps.user_name is None
20
+ assert deps.metadata == {}
21
+
22
+ def test_deps_with_values(self):
23
+ """Test Deps with custom values."""
24
+ deps = Deps(user_id="123", user_name="Test User", metadata={"key": "value"})
25
+ assert deps.user_id == "123"
26
+ assert deps.user_name == "Test User"
27
+ assert deps.metadata == {"key": "value"}
28
+
29
+
30
+ class TestGetCurrentDatetime:
31
+ """Tests for get_current_datetime tool."""
32
+
33
+ def test_returns_formatted_string(self):
34
+ """Test get_current_datetime returns formatted string."""
35
+ result = get_current_datetime()
36
+ assert isinstance(result, str)
37
+ # Should contain year, month, day
38
+ assert len(result) > 10
39
+
40
+
41
+ class TestAssistantAgent:
42
+ """Tests for AssistantAgent class."""
43
+
44
+ def test_init_with_defaults(self):
45
+ """Test AssistantAgent initializes with defaults."""
46
+ agent = AssistantAgent()
47
+ assert agent.system_prompt == "You are a helpful assistant."
48
+ assert agent._agent is None
49
+
50
+ def test_init_with_custom_values(self):
51
+ """Test AssistantAgent with custom configuration."""
52
+ agent = AssistantAgent(
53
+ model_name="gpt-4",
54
+ temperature=0.5,
55
+ system_prompt="Custom prompt",
56
+ )
57
+ assert agent.model_name == "gpt-4"
58
+ assert agent.temperature == 0.5
59
+ assert agent.system_prompt == "Custom prompt"
60
+
61
+ @patch("app.agents.assistant.OpenAIProvider")
62
+ @patch("app.agents.assistant.OpenAIChatModel")
63
+ def test_agent_property_creates_agent(self, mock_model, mock_provider):
64
+ """Test agent property creates agent on first access."""
65
+ agent = AssistantAgent()
66
+ _ = agent.agent
67
+ assert agent._agent is not None
68
+ mock_model.assert_called_once()
69
+
70
+ @patch("app.agents.assistant.OpenAIProvider")
71
+ @patch("app.agents.assistant.OpenAIChatModel")
72
+ def test_agent_property_caches_agent(self, mock_model, mock_provider):
73
+ """Test agent property caches the agent instance."""
74
+ agent = AssistantAgent()
75
+ agent1 = agent.agent
76
+ agent2 = agent.agent
77
+ assert agent1 is agent2
78
+ mock_model.assert_called_once()
79
+
80
+
81
+ class TestGetAgent:
82
+ """Tests for get_agent factory function."""
83
+
84
+ def test_returns_assistant_agent(self):
85
+ """Test get_agent returns AssistantAgent."""
86
+ agent = get_agent()
87
+ assert isinstance(agent, AssistantAgent)
88
+
89
+
90
+ class TestAgentRoutes:
91
+ """Tests for agent WebSocket routes."""
92
+
93
+ @pytest.mark.anyio
94
+ async def test_agent_websocket_connection(self, client):
95
+ """Test WebSocket connection to agent endpoint."""
96
+ # This test verifies the WebSocket endpoint is accessible
97
+ # Actual agent testing would require mocking OpenAI
98
+ pass
99
+
100
+
101
+ class TestHistoryConversion:
102
+ """Tests for conversation history conversion."""
103
+
104
+ def test_empty_history(self):
105
+ """Test with empty history."""
106
+ _agent = AssistantAgent() # noqa: F841
107
+ # History conversion happens inside run/iter methods
108
+ # We test the structure here
109
+ history = []
110
+ assert len(history) == 0
111
+
112
+ def test_history_roles(self):
113
+ """Test history with different roles."""
114
+ history = [
115
+ {"role": "user", "content": "Hello"},
116
+ {"role": "assistant", "content": "Hi there!"},
117
+ {"role": "system", "content": "You are helpful"},
118
+ ]
119
+ assert len(history) == 3
120
+ assert all("role" in msg and "content" in msg for msg in history)
121
+ {%- elif cookiecutter.enable_ai_agent and cookiecutter.use_langchain %}
122
+ """Tests for AI agent module (LangChain)."""
123
+
124
+ from unittest.mock import MagicMock, patch
125
+
126
+ import pytest
127
+
128
+ from app.agents.langchain_assistant import AgentContext, LangChainAssistant, get_agent, run_agent
129
+ from app.agents.tools.datetime_tool import get_current_datetime
130
+
131
+
132
+ class TestAgentContext:
133
+ """Tests for AgentContext TypedDict."""
134
+
135
+ def test_context_empty(self):
136
+ """Test AgentContext can be empty."""
137
+ context: AgentContext = {}
138
+ assert "user_id" not in context
139
+ assert "user_name" not in context
140
+
141
+ def test_context_with_values(self):
142
+ """Test AgentContext with values."""
143
+ context: AgentContext = {
144
+ "user_id": "123",
145
+ "user_name": "Test User",
146
+ "metadata": {"key": "value"},
147
+ }
148
+ assert context["user_id"] == "123"
149
+ assert context["user_name"] == "Test User"
150
+ assert context["metadata"] == {"key": "value"}
151
+
152
+
153
+ class TestGetCurrentDatetime:
154
+ """Tests for get_current_datetime tool."""
155
+
156
+ def test_returns_formatted_string(self):
157
+ """Test get_current_datetime returns formatted string."""
158
+ result = get_current_datetime()
159
+ assert isinstance(result, str)
160
+ # Should contain year, month, day
161
+ assert len(result) > 10
162
+
163
+
164
+ class TestLangChainAssistant:
165
+ """Tests for LangChainAssistant class."""
166
+
167
+ def test_init_with_defaults(self):
168
+ """Test LangChainAssistant initializes with defaults."""
169
+ agent = LangChainAssistant()
170
+ assert agent.system_prompt == "You are a helpful assistant."
171
+ assert agent._agent is None
172
+
173
+ def test_init_with_custom_values(self):
174
+ """Test LangChainAssistant with custom configuration."""
175
+ agent = LangChainAssistant(
176
+ model_name="gpt-4",
177
+ temperature=0.5,
178
+ system_prompt="Custom prompt",
179
+ )
180
+ assert agent.model_name == "gpt-4"
181
+ assert agent.temperature == 0.5
182
+ assert agent.system_prompt == "Custom prompt"
183
+
184
+ @patch("app.agents.langchain_assistant.ChatOpenAI")
185
+ @patch("app.agents.langchain_assistant.create_agent")
186
+ def test_agent_property_creates_agent(self, mock_create_agent, mock_chat):
187
+ """Test agent property creates agent on first access."""
188
+ mock_create_agent.return_value = MagicMock()
189
+ agent = LangChainAssistant()
190
+ _ = agent.agent
191
+ assert agent._agent is not None
192
+ mock_create_agent.assert_called_once()
193
+
194
+ @patch("app.agents.langchain_assistant.ChatOpenAI")
195
+ @patch("app.agents.langchain_assistant.create_agent")
196
+ def test_agent_property_caches_agent(self, mock_create_agent, mock_chat):
197
+ """Test agent property caches the agent instance."""
198
+ mock_create_agent.return_value = MagicMock()
199
+ agent = LangChainAssistant()
200
+ agent1 = agent.agent
201
+ agent2 = agent.agent
202
+ assert agent1 is agent2
203
+ mock_create_agent.assert_called_once()
204
+
205
+
206
+ class TestGetAgent:
207
+ """Tests for get_agent factory function."""
208
+
209
+ def test_returns_langchain_assistant(self):
210
+ """Test get_agent returns LangChainAssistant."""
211
+ agent = get_agent()
212
+ assert isinstance(agent, LangChainAssistant)
213
+
214
+
215
+ class TestAgentRoutes:
216
+ """Tests for agent WebSocket routes."""
217
+
218
+ @pytest.mark.anyio
219
+ async def test_agent_websocket_connection(self, client):
220
+ """Test WebSocket connection to agent endpoint."""
221
+ # This test verifies the WebSocket endpoint is accessible
222
+ # Actual agent testing would require mocking OpenAI
223
+ pass
224
+
225
+
226
+ class TestHistoryConversion:
227
+ """Tests for conversation history conversion."""
228
+
229
+ def test_empty_history(self):
230
+ """Test with empty history."""
231
+ _agent = LangChainAssistant() # noqa: F841
232
+ # History conversion happens inside run/stream methods
233
+ # We test the structure here
234
+ history = []
235
+ assert len(history) == 0
236
+
237
+ def test_history_roles(self):
238
+ """Test history with different roles."""
239
+ history = [
240
+ {"role": "user", "content": "Hello"},
241
+ {"role": "assistant", "content": "Hi there!"},
242
+ {"role": "system", "content": "You are helpful"},
243
+ ]
244
+ assert len(history) == 3
245
+ assert all("role" in msg and "content" in msg for msg in history)
246
+
247
+ def test_convert_history(self):
248
+ """Test _convert_history method."""
249
+ agent = LangChainAssistant()
250
+ history = [
251
+ {"role": "user", "content": "Hello"},
252
+ {"role": "assistant", "content": "Hi there!"},
253
+ {"role": "system", "content": "You are helpful"},
254
+ ]
255
+ messages = agent._convert_history(history)
256
+ assert len(messages) == 3
257
+ from langchain.messages import AIMessage, HumanMessage, SystemMessage
258
+ assert isinstance(messages[0], HumanMessage)
259
+ assert isinstance(messages[1], AIMessage)
260
+ assert isinstance(messages[2], SystemMessage)
261
+ {%- endif %}
@@ -0,0 +1,183 @@
1
+ {%- if cookiecutter.enable_redis %}
2
+ """Tests for client modules."""
3
+
4
+ from unittest.mock import AsyncMock, MagicMock, patch
5
+
6
+ import pytest
7
+
8
+ from app.clients.redis import RedisClient
9
+
10
+
11
+ class TestRedisClient:
12
+ """Tests for RedisClient."""
13
+
14
+ @pytest.fixture
15
+ def redis_client(self) -> RedisClient:
16
+ """Create a RedisClient instance."""
17
+ return RedisClient(url="redis://localhost:6379")
18
+
19
+ @pytest.fixture
20
+ def mock_aioredis(self) -> MagicMock:
21
+ """Create a mock aioredis client."""
22
+ mock = MagicMock()
23
+ mock.get = AsyncMock(return_value="value")
24
+ mock.set = AsyncMock()
25
+ mock.delete = AsyncMock(return_value=1)
26
+ mock.exists = AsyncMock(return_value=1)
27
+ mock.ping = AsyncMock()
28
+ mock.close = AsyncMock()
29
+ return mock
30
+
31
+ @pytest.mark.anyio
32
+ async def test_connect(self, redis_client: RedisClient):
33
+ """Test Redis connection."""
34
+ with patch("app.clients.redis.aioredis") as mock_aioredis:
35
+ mock_client = MagicMock()
36
+ mock_aioredis.from_url.return_value = mock_client
37
+
38
+ await redis_client.connect()
39
+
40
+ assert redis_client.client is not None
41
+ mock_aioredis.from_url.assert_called_once()
42
+
43
+ @pytest.mark.anyio
44
+ async def test_close(self, redis_client: RedisClient, mock_aioredis: MagicMock):
45
+ """Test closing Redis connection."""
46
+ redis_client.client = mock_aioredis
47
+
48
+ await redis_client.close()
49
+
50
+ mock_aioredis.close.assert_called_once()
51
+ assert redis_client.client is None
52
+
53
+ @pytest.mark.anyio
54
+ async def test_close_when_not_connected(self, redis_client: RedisClient):
55
+ """Test closing when not connected does nothing."""
56
+ redis_client.client = None
57
+
58
+ await redis_client.close() # Should not raise
59
+
60
+ @pytest.mark.anyio
61
+ async def test_get(self, redis_client: RedisClient, mock_aioredis: MagicMock):
62
+ """Test getting a value."""
63
+ redis_client.client = mock_aioredis
64
+
65
+ result = await redis_client.get("test_key")
66
+
67
+ assert result == "value"
68
+ mock_aioredis.get.assert_called_once_with("test_key")
69
+
70
+ @pytest.mark.anyio
71
+ async def test_get_not_connected(self, redis_client: RedisClient):
72
+ """Test getting when not connected raises error."""
73
+ redis_client.client = None
74
+
75
+ with pytest.raises(RuntimeError, match="not connected"):
76
+ await redis_client.get("test_key")
77
+
78
+ @pytest.mark.anyio
79
+ async def test_set(self, redis_client: RedisClient, mock_aioredis: MagicMock):
80
+ """Test setting a value."""
81
+ redis_client.client = mock_aioredis
82
+
83
+ await redis_client.set("test_key", "test_value")
84
+
85
+ mock_aioredis.set.assert_called_once_with("test_key", "test_value", ex=None)
86
+
87
+ @pytest.mark.anyio
88
+ async def test_set_with_ttl(self, redis_client: RedisClient, mock_aioredis: MagicMock):
89
+ """Test setting a value with TTL."""
90
+ redis_client.client = mock_aioredis
91
+
92
+ await redis_client.set("test_key", "test_value", ttl=60)
93
+
94
+ mock_aioredis.set.assert_called_once_with("test_key", "test_value", ex=60)
95
+
96
+ @pytest.mark.anyio
97
+ async def test_set_not_connected(self, redis_client: RedisClient):
98
+ """Test setting when not connected raises error."""
99
+ redis_client.client = None
100
+
101
+ with pytest.raises(RuntimeError, match="not connected"):
102
+ await redis_client.set("test_key", "test_value")
103
+
104
+ @pytest.mark.anyio
105
+ async def test_delete(self, redis_client: RedisClient, mock_aioredis: MagicMock):
106
+ """Test deleting a key."""
107
+ redis_client.client = mock_aioredis
108
+
109
+ result = await redis_client.delete("test_key")
110
+
111
+ assert result == 1
112
+ mock_aioredis.delete.assert_called_once_with("test_key")
113
+
114
+ @pytest.mark.anyio
115
+ async def test_delete_not_connected(self, redis_client: RedisClient):
116
+ """Test deleting when not connected raises error."""
117
+ redis_client.client = None
118
+
119
+ with pytest.raises(RuntimeError, match="not connected"):
120
+ await redis_client.delete("test_key")
121
+
122
+ @pytest.mark.anyio
123
+ async def test_exists(self, redis_client: RedisClient, mock_aioredis: MagicMock):
124
+ """Test checking if key exists."""
125
+ redis_client.client = mock_aioredis
126
+
127
+ result = await redis_client.exists("test_key")
128
+
129
+ assert result is True
130
+ mock_aioredis.exists.assert_called_once_with("test_key")
131
+
132
+ @pytest.mark.anyio
133
+ async def test_exists_not_connected(self, redis_client: RedisClient):
134
+ """Test exists when not connected raises error."""
135
+ redis_client.client = None
136
+
137
+ with pytest.raises(RuntimeError, match="not connected"):
138
+ await redis_client.exists("test_key")
139
+
140
+ @pytest.mark.anyio
141
+ async def test_ping_connected(self, redis_client: RedisClient, mock_aioredis: MagicMock):
142
+ """Test ping when connected."""
143
+ redis_client.client = mock_aioredis
144
+
145
+ result = await redis_client.ping()
146
+
147
+ assert result is True
148
+ mock_aioredis.ping.assert_called_once()
149
+
150
+ @pytest.mark.anyio
151
+ async def test_ping_not_connected(self, redis_client: RedisClient):
152
+ """Test ping when not connected returns False."""
153
+ redis_client.client = None
154
+
155
+ result = await redis_client.ping()
156
+
157
+ assert result is False
158
+
159
+ @pytest.mark.anyio
160
+ async def test_ping_exception(self, redis_client: RedisClient, mock_aioredis: MagicMock):
161
+ """Test ping when exception occurs returns False."""
162
+ redis_client.client = mock_aioredis
163
+ mock_aioredis.ping = AsyncMock(side_effect=Exception("Connection error"))
164
+
165
+ result = await redis_client.ping()
166
+
167
+ assert result is False
168
+
169
+ def test_raw_property(self, redis_client: RedisClient, mock_aioredis: MagicMock):
170
+ """Test accessing raw client."""
171
+ redis_client.client = mock_aioredis
172
+
173
+ result = redis_client.raw
174
+
175
+ assert result == mock_aioredis
176
+
177
+ def test_raw_property_not_connected(self, redis_client: RedisClient):
178
+ """Test accessing raw client when not connected raises error."""
179
+ redis_client.client = None
180
+
181
+ with pytest.raises(RuntimeError, match="not connected"):
182
+ _ = redis_client.raw
183
+ {%- endif %}
@@ -0,0 +1,173 @@
1
+ """Tests for CLI commands module."""
2
+
3
+ import click
4
+ from click.testing import CliRunner
5
+
6
+ from app.commands import (
7
+ command,
8
+ discover_commands,
9
+ error,
10
+ info,
11
+ register_commands,
12
+ success,
13
+ warning,
14
+ )
15
+
16
+
17
+ class TestCommandDecorator:
18
+ """Tests for the command decorator."""
19
+
20
+ def test_command_registers_function(self):
21
+ """Test that @command decorator registers a click command."""
22
+ from app.commands import _commands
23
+
24
+ initial_count = len(_commands)
25
+
26
+ @command("test-cmd", help="Test command")
27
+ def test_func():
28
+ pass
29
+
30
+ assert len(_commands) == initial_count + 1
31
+ assert _commands[-1].name == "test-cmd"
32
+
33
+ def test_command_uses_function_name_as_default(self):
34
+ """Test that command name defaults to function name."""
35
+ from app.commands import _commands
36
+
37
+ @command()
38
+ def my_test_command():
39
+ pass
40
+
41
+ assert _commands[-1].name == "my-test-command"
42
+
43
+
44
+ class TestHelperFunctions:
45
+ """Tests for helper output functions."""
46
+
47
+ def test_success_prints_green(self, capsys):
48
+ """Test success prints in green."""
49
+ success("Test message")
50
+ # Click uses escape codes for colors
51
+ captured = capsys.readouterr()
52
+ assert "Test message" in captured.out
53
+
54
+ def test_error_prints_red(self, capsys):
55
+ """Test error prints in red."""
56
+ error("Error message")
57
+ captured = capsys.readouterr()
58
+ assert "Error message" in captured.out
59
+
60
+ def test_warning_prints_yellow(self, capsys):
61
+ """Test warning prints in yellow."""
62
+ warning("Warning message")
63
+ captured = capsys.readouterr()
64
+ assert "Warning message" in captured.out
65
+
66
+ def test_info_prints_plain(self, capsys):
67
+ """Test info prints plain text."""
68
+ info("Info message")
69
+ captured = capsys.readouterr()
70
+ assert "Info message" in captured.out
71
+
72
+
73
+ class TestDiscoverCommands:
74
+ """Tests for command discovery."""
75
+
76
+ def test_discover_commands_returns_list(self):
77
+ """Test that discover_commands returns a list."""
78
+ commands = discover_commands()
79
+ assert isinstance(commands, list)
80
+
81
+ def test_discover_commands_caches_results(self):
82
+ """Test that discover_commands caches on second call."""
83
+ commands1 = discover_commands()
84
+ commands2 = discover_commands()
85
+ assert commands1 is commands2
86
+
87
+
88
+ class TestRegisterCommands:
89
+ """Tests for registering commands."""
90
+
91
+ def test_register_commands_adds_to_group(self):
92
+ """Test that register_commands adds discovered commands to CLI group."""
93
+ @click.group()
94
+ def cli():
95
+ pass
96
+
97
+ register_commands(cli)
98
+ # After registration, cli should have commands
99
+ # We can't assert exact count since it depends on what's discovered
100
+
101
+
102
+ {%- if cookiecutter.use_database %}
103
+
104
+
105
+ class TestSeedCommand:
106
+ """Tests for the seed command."""
107
+
108
+ def test_seed_dry_run(self):
109
+ """Test seed command with --dry-run."""
110
+ from app.commands.seed import seed
111
+
112
+ runner = CliRunner()
113
+ result = runner.invoke(seed, ["--dry-run", "--count", "5"])
114
+ assert result.exit_code == 0
115
+ assert "[DRY RUN]" in result.output
116
+ assert "5" in result.output
117
+
118
+ def test_seed_dry_run_with_clear(self):
119
+ """Test seed command with --dry-run and --clear."""
120
+ from app.commands.seed import seed
121
+
122
+ runner = CliRunner()
123
+ result = runner.invoke(seed, ["--dry-run", "--clear"])
124
+ assert result.exit_code == 0
125
+ assert "Would clear existing data" in result.output
126
+ {%- endif %}
127
+
128
+
129
+ class TestHelloCommand:
130
+ """Tests for the hello command."""
131
+
132
+ def test_hello_command_runs(self):
133
+ """Test hello command executes."""
134
+ from app.commands.example import hello
135
+
136
+ runner = CliRunner()
137
+ result = runner.invoke(hello)
138
+ assert result.exit_code == 0
139
+ assert "Hello" in result.output
140
+
141
+ def test_hello_command_with_name(self):
142
+ """Test hello command with --name option."""
143
+ from app.commands.example import hello
144
+
145
+ runner = CliRunner()
146
+ result = runner.invoke(hello, ["--name", "Alice"])
147
+ assert result.exit_code == 0
148
+ assert "Alice" in result.output
149
+
150
+
151
+ {%- if cookiecutter.use_database %}
152
+
153
+
154
+ class TestCleanupCommand:
155
+ """Tests for the cleanup command."""
156
+
157
+ def test_cleanup_dry_run(self):
158
+ """Test cleanup command with --dry-run."""
159
+ from app.commands.cleanup import cleanup
160
+
161
+ runner = CliRunner()
162
+ result = runner.invoke(cleanup, ["--dry-run"])
163
+ assert result.exit_code == 0
164
+ assert "[DRY RUN]" in result.output
165
+
166
+ def test_cleanup_with_days_option(self):
167
+ """Test cleanup command with --days option."""
168
+ from app.commands.cleanup import cleanup
169
+
170
+ runner = CliRunner()
171
+ result = runner.invoke(cleanup, ["--dry-run", "--days", "7"])
172
+ assert result.exit_code == 0
173
+ {%- endif %}