fastapi-fullstack 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastapi_fullstack-0.1.2.dist-info/METADATA +545 -0
- fastapi_fullstack-0.1.2.dist-info/RECORD +221 -0
- fastapi_fullstack-0.1.2.dist-info/WHEEL +4 -0
- fastapi_fullstack-0.1.2.dist-info/entry_points.txt +2 -0
- fastapi_fullstack-0.1.2.dist-info/licenses/LICENSE +21 -0
- fastapi_gen/__init__.py +3 -0
- fastapi_gen/cli.py +256 -0
- fastapi_gen/config.py +255 -0
- fastapi_gen/generator.py +181 -0
- fastapi_gen/prompts.py +648 -0
- fastapi_gen/template/cookiecutter.json +76 -0
- fastapi_gen/template/hooks/post_gen_project.py +111 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/.env.example +136 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +150 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/.gitignore +108 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/CLAUDE.md +357 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/Makefile +298 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/README.md +723 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.dockerignore +60 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.pre-commit-config.yaml +32 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/Dockerfile +56 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/env.py +76 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/script.py.mako +30 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/versions/.gitkeep +0 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic.ini +48 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/__init__.py +3 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/admin.py +115 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/__init__.py +13 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/assistant.py +202 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/__init__.py +13 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/datetime_tool.py +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/deps.py +528 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/exception_handlers.py +85 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/router.py +10 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/__init__.py +9 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/__init__.py +87 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/agent.py +448 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/auth.py +395 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/conversations.py +490 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/health.py +227 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/items.py +275 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/oauth.py +205 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/sessions.py +168 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/users.py +333 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/webhooks.py +477 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/ws.py +46 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/versioning.py +221 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/__init__.py +14 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/redis.py +88 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/__init__.py +117 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/cleanup.py +75 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/example.py +28 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/seed.py +266 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/__init__.py +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/cache.py +23 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/config.py +247 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/csrf.py +153 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/exceptions.py +122 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/logfire_setup.py +101 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/middleware.py +99 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/oauth.py +23 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/rate_limit.py +58 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/sanitize.py +271 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/security.py +102 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/__init__.py +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/base.py +41 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/__init__.py +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/conversation.py +319 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/item.py +96 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/session.py +126 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/user.py +218 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/webhook.py +244 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/session.py +113 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/main.py +326 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/__init__.py +9 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/base.py +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/__init__.py +49 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/base.py +154 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/conversation.py +760 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/item.py +222 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/session.py +318 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/user.py +322 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/webhook.py +358 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/__init__.py +50 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/base.py +57 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/conversation.py +195 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/item.py +52 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/session.py +42 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/token.py +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/user.py +64 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/webhook.py +89 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/__init__.py +38 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/conversation.py +797 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/item.py +246 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/session.py +333 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/user.py +432 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/webhook.py +561 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/__init__.py +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/celery_app.py +64 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/taskiq_app.py +38 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/__init__.py +25 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/examples.py +106 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/schedules.py +29 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/taskiq_examples.py +92 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/commands.py +438 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/pyproject.toml +158 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/scripts/.gitkeep +0 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_auth.py +242 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_exceptions.py +151 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_health.py +113 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_items.py +310 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_users.py +253 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/conftest.py +151 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_agents.py +121 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_clients.py +183 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_commands.py +173 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_core.py +143 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_pipelines.py +118 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_repositories.py +181 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_security.py +124 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_services.py +363 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_worker.py +85 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.dev.yml +242 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.frontend.yml +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.prod.yml +382 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.yml +241 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.env.example +12 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.gitignore +45 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierignore +19 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierrc +11 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/Dockerfile +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/README.md +693 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.setup.ts +49 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.spec.ts +134 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/chat.spec.ts +207 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/home.spec.ts +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/instrumentation.ts +14 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/en.json +84 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/pl.json +84 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/next.config.ts +76 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/package.json +66 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/playwright.config.ts +101 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/postcss.config.mjs +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(auth)/layout.tsx +11 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(auth)/login/page.tsx +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(auth)/register/page.tsx +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(dashboard)/chat/page.tsx +20 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(dashboard)/dashboard/page.tsx +99 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(dashboard)/layout.tsx +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(dashboard)/profile/page.tsx +156 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/login/route.ts +58 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/logout/route.ts +24 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/me/route.ts +39 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/oauth-callback/route.ts +50 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/refresh/route.ts +54 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/register/route.ts +26 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/messages/route.ts +41 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/route.ts +108 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/route.ts +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/health/route.ts +21 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/auth/callback/page.tsx +96 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/globals.css +108 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/layout.tsx +25 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/page.tsx +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/providers.tsx +29 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/index.ts +2 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/login-form.tsx +120 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/register-form.tsx +153 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-container.tsx +135 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-input.tsx +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/conversation-sidebar.tsx +261 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/index.ts +8 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-item.tsx +63 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-list.tsx +18 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/tool-call-card.tsx +60 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/google-icon.tsx +32 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/index.ts +3 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/language-switcher.tsx +97 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/header.tsx +45 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/index.ts +2 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/sidebar.tsx +48 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/index.ts +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-provider.tsx +53 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-toggle.tsx +83 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/badge.tsx +35 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.test.tsx +75 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.tsx +54 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/card.tsx +82 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/index.ts +12 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/input.tsx +21 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/label.tsx +21 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/index.ts +6 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-auth.ts +97 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-chat.ts +203 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-conversations.ts +175 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-websocket.ts +105 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/i18n.ts +32 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/api-client.ts +90 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/constants.ts +39 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/server-api.ts +78 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.test.ts +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.ts +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/middleware.ts +33 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.test.ts +72 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.ts +48 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-store.ts +65 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/conversation-store.ts +76 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/index.ts +6 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/theme-store.ts +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/api.ts +27 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/auth.ts +52 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/chat.ts +81 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/conversation.ts +49 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/index.ts +10 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/tsconfig.json +28 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.config.ts +36 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.setup.ts +56 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Tests for pipeline infrastructure."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from app.pipelines.base import BasePipeline, PipelineResult
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestPipelineResult:
|
|
9
|
+
"""Tests for PipelineResult dataclass."""
|
|
10
|
+
|
|
11
|
+
def test_success_rate_all_processed(self):
|
|
12
|
+
"""Test success rate when all items processed."""
|
|
13
|
+
result = PipelineResult(processed=10, failed=0)
|
|
14
|
+
assert result.success_rate == 100.0
|
|
15
|
+
|
|
16
|
+
def test_success_rate_with_failures(self):
|
|
17
|
+
"""Test success rate with some failures."""
|
|
18
|
+
result = PipelineResult(processed=8, failed=2)
|
|
19
|
+
assert result.success_rate == 80.0
|
|
20
|
+
|
|
21
|
+
def test_success_rate_all_failed(self):
|
|
22
|
+
"""Test success rate when all items failed."""
|
|
23
|
+
result = PipelineResult(processed=0, failed=10)
|
|
24
|
+
assert result.success_rate == 0.0
|
|
25
|
+
|
|
26
|
+
def test_success_rate_empty(self):
|
|
27
|
+
"""Test success rate with no items."""
|
|
28
|
+
result = PipelineResult(processed=0, failed=0)
|
|
29
|
+
assert result.success_rate == 100.0
|
|
30
|
+
|
|
31
|
+
def test_has_errors_with_failures(self):
|
|
32
|
+
"""Test has_errors returns True when failures exist."""
|
|
33
|
+
result = PipelineResult(processed=5, failed=1)
|
|
34
|
+
assert result.has_errors is True
|
|
35
|
+
|
|
36
|
+
def test_has_errors_with_error_messages(self):
|
|
37
|
+
"""Test has_errors returns True when error messages exist."""
|
|
38
|
+
result = PipelineResult(processed=5, failed=0, errors=["Error 1"])
|
|
39
|
+
assert result.has_errors is True
|
|
40
|
+
|
|
41
|
+
def test_has_errors_no_errors(self):
|
|
42
|
+
"""Test has_errors returns False when no errors."""
|
|
43
|
+
result = PipelineResult(processed=5, failed=0)
|
|
44
|
+
assert result.has_errors is False
|
|
45
|
+
|
|
46
|
+
def test_default_values(self):
|
|
47
|
+
"""Test default values are set correctly."""
|
|
48
|
+
result = PipelineResult(processed=5)
|
|
49
|
+
assert result.failed == 0
|
|
50
|
+
assert result.errors == []
|
|
51
|
+
assert result.metadata == {}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class TestBasePipeline:
|
|
55
|
+
"""Tests for BasePipeline abstract class."""
|
|
56
|
+
|
|
57
|
+
@pytest.mark.anyio
|
|
58
|
+
async def test_validate_returns_true_by_default(self):
|
|
59
|
+
"""Test validate method returns True by default."""
|
|
60
|
+
|
|
61
|
+
class TestPipeline(BasePipeline):
|
|
62
|
+
async def run(self) -> PipelineResult:
|
|
63
|
+
return PipelineResult(processed=0)
|
|
64
|
+
|
|
65
|
+
pipeline = TestPipeline()
|
|
66
|
+
assert await pipeline.validate() is True
|
|
67
|
+
|
|
68
|
+
@pytest.mark.anyio
|
|
69
|
+
async def test_cleanup_does_nothing_by_default(self):
|
|
70
|
+
"""Test cleanup method does nothing by default."""
|
|
71
|
+
|
|
72
|
+
class TestPipeline(BasePipeline):
|
|
73
|
+
async def run(self) -> PipelineResult:
|
|
74
|
+
return PipelineResult(processed=0)
|
|
75
|
+
|
|
76
|
+
pipeline = TestPipeline()
|
|
77
|
+
await pipeline.cleanup() # Should not raise
|
|
78
|
+
|
|
79
|
+
@pytest.mark.anyio
|
|
80
|
+
async def test_run_must_be_implemented(self):
|
|
81
|
+
"""Test that run method must be implemented by subclasses."""
|
|
82
|
+
# This test verifies the abstract method requirement
|
|
83
|
+
with pytest.raises(TypeError, match="Can't instantiate abstract class"):
|
|
84
|
+
BasePipeline()
|
|
85
|
+
|
|
86
|
+
@pytest.mark.anyio
|
|
87
|
+
async def test_custom_pipeline_implementation(self):
|
|
88
|
+
"""Test a custom pipeline implementation."""
|
|
89
|
+
|
|
90
|
+
class MyPipeline(BasePipeline):
|
|
91
|
+
def __init__(self, items: list):
|
|
92
|
+
self.items = items
|
|
93
|
+
|
|
94
|
+
async def run(self) -> PipelineResult:
|
|
95
|
+
processed = 0
|
|
96
|
+
failed = 0
|
|
97
|
+
errors = []
|
|
98
|
+
|
|
99
|
+
for item in self.items:
|
|
100
|
+
if item > 0:
|
|
101
|
+
processed += 1
|
|
102
|
+
else:
|
|
103
|
+
failed += 1
|
|
104
|
+
errors.append(f"Invalid item: {item}")
|
|
105
|
+
|
|
106
|
+
return PipelineResult(
|
|
107
|
+
processed=processed,
|
|
108
|
+
failed=failed,
|
|
109
|
+
errors=errors,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
pipeline = MyPipeline([1, 2, 3, -1, 5])
|
|
113
|
+
result = await pipeline.run()
|
|
114
|
+
|
|
115
|
+
assert result.processed == 4
|
|
116
|
+
assert result.failed == 1
|
|
117
|
+
assert len(result.errors) == 1
|
|
118
|
+
assert result.success_rate == 80.0
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
{%- if cookiecutter.use_database %}
|
|
2
|
+
"""Tests for repository layer."""
|
|
3
|
+
|
|
4
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
5
|
+
from uuid import uuid4
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
from app.repositories.base import BaseRepository
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MockModel:
|
|
14
|
+
"""Mock SQLAlchemy model for testing."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, **kwargs):
|
|
17
|
+
self.id = kwargs.get("id", uuid4())
|
|
18
|
+
for key, value in kwargs.items():
|
|
19
|
+
setattr(self, key, value)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class MockCreateSchema(BaseModel):
|
|
23
|
+
"""Mock create schema."""
|
|
24
|
+
|
|
25
|
+
name: str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class MockUpdateSchema(BaseModel):
|
|
29
|
+
"""Mock update schema."""
|
|
30
|
+
|
|
31
|
+
name: str | None = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TestBaseRepository:
|
|
35
|
+
"""Tests for BaseRepository."""
|
|
36
|
+
|
|
37
|
+
@pytest.fixture
|
|
38
|
+
def repository(self):
|
|
39
|
+
"""Create a test repository."""
|
|
40
|
+
return BaseRepository[MockModel, MockCreateSchema, MockUpdateSchema](MockModel)
|
|
41
|
+
|
|
42
|
+
@pytest.fixture
|
|
43
|
+
def mock_session(self):
|
|
44
|
+
"""Create a mock async session."""
|
|
45
|
+
session = MagicMock()
|
|
46
|
+
session.get = AsyncMock()
|
|
47
|
+
session.execute = AsyncMock()
|
|
48
|
+
session.add = MagicMock()
|
|
49
|
+
session.flush = AsyncMock()
|
|
50
|
+
session.refresh = AsyncMock()
|
|
51
|
+
session.delete = AsyncMock()
|
|
52
|
+
return session
|
|
53
|
+
|
|
54
|
+
@pytest.mark.anyio
|
|
55
|
+
async def test_get_returns_model(self, repository, mock_session):
|
|
56
|
+
"""Test get returns a model by ID."""
|
|
57
|
+
mock_obj = MockModel(name="test")
|
|
58
|
+
mock_session.get.return_value = mock_obj
|
|
59
|
+
|
|
60
|
+
result = await repository.get(mock_session, mock_obj.id)
|
|
61
|
+
|
|
62
|
+
assert result == mock_obj
|
|
63
|
+
mock_session.get.assert_called_once_with(MockModel, mock_obj.id)
|
|
64
|
+
|
|
65
|
+
@pytest.mark.anyio
|
|
66
|
+
async def test_get_returns_none_when_not_found(self, repository, mock_session):
|
|
67
|
+
"""Test get returns None when not found."""
|
|
68
|
+
mock_session.get.return_value = None
|
|
69
|
+
|
|
70
|
+
result = await repository.get(mock_session, uuid4())
|
|
71
|
+
|
|
72
|
+
assert result is None
|
|
73
|
+
|
|
74
|
+
# Note: test_get_multi_returns_list is skipped because it requires a real
|
|
75
|
+
# SQLAlchemy model. The select() function cannot work with a mock class.
|
|
76
|
+
# For proper integration testing, use actual SQLAlchemy models with a test DB.
|
|
77
|
+
|
|
78
|
+
@pytest.mark.anyio
|
|
79
|
+
async def test_create_adds_and_returns_model(self, repository, mock_session):
|
|
80
|
+
"""Test create adds a new model."""
|
|
81
|
+
create_data = MockCreateSchema(name="new item")
|
|
82
|
+
|
|
83
|
+
# Mock the model creation
|
|
84
|
+
async def refresh_side_effect(obj):
|
|
85
|
+
obj.id = uuid4()
|
|
86
|
+
|
|
87
|
+
mock_session.refresh.side_effect = refresh_side_effect
|
|
88
|
+
|
|
89
|
+
result = await repository.create(mock_session, obj_in=create_data)
|
|
90
|
+
|
|
91
|
+
assert result.name == "new item"
|
|
92
|
+
mock_session.add.assert_called_once()
|
|
93
|
+
mock_session.flush.assert_called_once()
|
|
94
|
+
mock_session.refresh.assert_called_once()
|
|
95
|
+
|
|
96
|
+
@pytest.mark.anyio
|
|
97
|
+
async def test_update_with_schema(self, repository, mock_session):
|
|
98
|
+
"""Test update with Pydantic schema."""
|
|
99
|
+
db_obj = MockModel(name="old name")
|
|
100
|
+
update_data = MockUpdateSchema(name="new name")
|
|
101
|
+
|
|
102
|
+
result = await repository.update(mock_session, db_obj=db_obj, obj_in=update_data)
|
|
103
|
+
|
|
104
|
+
assert result.name == "new name"
|
|
105
|
+
mock_session.add.assert_called_once()
|
|
106
|
+
mock_session.flush.assert_called_once()
|
|
107
|
+
|
|
108
|
+
@pytest.mark.anyio
|
|
109
|
+
async def test_update_with_dict(self, repository, mock_session):
|
|
110
|
+
"""Test update with dictionary."""
|
|
111
|
+
db_obj = MockModel(name="old name")
|
|
112
|
+
update_data = {"name": "new name"}
|
|
113
|
+
|
|
114
|
+
result = await repository.update(mock_session, db_obj=db_obj, obj_in=update_data)
|
|
115
|
+
|
|
116
|
+
assert result.name == "new name"
|
|
117
|
+
|
|
118
|
+
@pytest.mark.anyio
|
|
119
|
+
async def test_delete_removes_and_returns_model(self, repository, mock_session):
|
|
120
|
+
"""Test delete removes and returns model."""
|
|
121
|
+
mock_obj = MockModel(name="to delete")
|
|
122
|
+
mock_session.get.return_value = mock_obj
|
|
123
|
+
|
|
124
|
+
result = await repository.delete(mock_session, id=mock_obj.id)
|
|
125
|
+
|
|
126
|
+
assert result == mock_obj
|
|
127
|
+
mock_session.delete.assert_called_once_with(mock_obj)
|
|
128
|
+
mock_session.flush.assert_called_once()
|
|
129
|
+
|
|
130
|
+
@pytest.mark.anyio
|
|
131
|
+
async def test_delete_returns_none_when_not_found(self, repository, mock_session):
|
|
132
|
+
"""Test delete returns None when not found."""
|
|
133
|
+
mock_session.get.return_value = None
|
|
134
|
+
|
|
135
|
+
result = await repository.delete(mock_session, id=uuid4())
|
|
136
|
+
|
|
137
|
+
assert result is None
|
|
138
|
+
mock_session.delete.assert_not_called()
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
{%- if cookiecutter.use_jwt %}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class TestUserRepository:
|
|
145
|
+
"""Tests for user repository functions."""
|
|
146
|
+
|
|
147
|
+
@pytest.fixture
|
|
148
|
+
def mock_session(self):
|
|
149
|
+
"""Create a mock async session."""
|
|
150
|
+
session = MagicMock()
|
|
151
|
+
session.execute = AsyncMock()
|
|
152
|
+
return session
|
|
153
|
+
|
|
154
|
+
@pytest.mark.anyio
|
|
155
|
+
async def test_get_by_email(self, mock_session):
|
|
156
|
+
"""Test get_by_email returns user."""
|
|
157
|
+
from app.repositories import user as user_repo
|
|
158
|
+
|
|
159
|
+
mock_user = MagicMock()
|
|
160
|
+
mock_result = MagicMock()
|
|
161
|
+
mock_result.scalar_one_or_none.return_value = mock_user
|
|
162
|
+
mock_session.execute.return_value = mock_result
|
|
163
|
+
|
|
164
|
+
result = await user_repo.get_by_email(mock_session, "test@example.com")
|
|
165
|
+
|
|
166
|
+
assert result == mock_user
|
|
167
|
+
|
|
168
|
+
@pytest.mark.anyio
|
|
169
|
+
async def test_get_by_email_not_found(self, mock_session):
|
|
170
|
+
"""Test get_by_email returns None when not found."""
|
|
171
|
+
from app.repositories import user as user_repo
|
|
172
|
+
|
|
173
|
+
mock_result = MagicMock()
|
|
174
|
+
mock_result.scalar_one_or_none.return_value = None
|
|
175
|
+
mock_session.execute.return_value = mock_result
|
|
176
|
+
|
|
177
|
+
result = await user_repo.get_by_email(mock_session, "notfound@example.com")
|
|
178
|
+
|
|
179
|
+
assert result is None
|
|
180
|
+
{%- endif %}
|
|
181
|
+
{%- endif %}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
{%- if cookiecutter.use_jwt %}
|
|
2
|
+
"""Tests for security module."""
|
|
3
|
+
|
|
4
|
+
from datetime import timedelta
|
|
5
|
+
|
|
6
|
+
from app.core.security import (
|
|
7
|
+
create_access_token,
|
|
8
|
+
create_refresh_token,
|
|
9
|
+
get_password_hash,
|
|
10
|
+
verify_password,
|
|
11
|
+
verify_token,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestPasswordHashing:
|
|
16
|
+
"""Tests for password hashing functions."""
|
|
17
|
+
|
|
18
|
+
def test_hash_password(self):
|
|
19
|
+
"""Test password hashing."""
|
|
20
|
+
password = "mysecretpassword"
|
|
21
|
+
hashed = get_password_hash(password)
|
|
22
|
+
|
|
23
|
+
assert hashed != password
|
|
24
|
+
assert len(hashed) > 0
|
|
25
|
+
assert hashed.startswith("$2") # bcrypt prefix
|
|
26
|
+
|
|
27
|
+
def test_verify_password_correct(self):
|
|
28
|
+
"""Test verifying correct password."""
|
|
29
|
+
password = "mysecretpassword"
|
|
30
|
+
hashed = get_password_hash(password)
|
|
31
|
+
|
|
32
|
+
assert verify_password(password, hashed) is True
|
|
33
|
+
|
|
34
|
+
def test_verify_password_incorrect(self):
|
|
35
|
+
"""Test verifying incorrect password."""
|
|
36
|
+
password = "mysecretpassword"
|
|
37
|
+
wrong_password = "wrongpassword"
|
|
38
|
+
hashed = get_password_hash(password)
|
|
39
|
+
|
|
40
|
+
assert verify_password(wrong_password, hashed) is False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TestAccessToken:
|
|
44
|
+
"""Tests for access token functions."""
|
|
45
|
+
|
|
46
|
+
def test_create_access_token(self):
|
|
47
|
+
"""Test creating access token."""
|
|
48
|
+
subject = "user123"
|
|
49
|
+
token = create_access_token(subject)
|
|
50
|
+
|
|
51
|
+
assert isinstance(token, str)
|
|
52
|
+
assert len(token) > 0
|
|
53
|
+
|
|
54
|
+
def test_create_access_token_with_expires_delta(self):
|
|
55
|
+
"""Test creating access token with custom expiration."""
|
|
56
|
+
subject = "user123"
|
|
57
|
+
expires = timedelta(hours=2)
|
|
58
|
+
token = create_access_token(subject, expires_delta=expires)
|
|
59
|
+
|
|
60
|
+
assert isinstance(token, str)
|
|
61
|
+
payload = verify_token(token)
|
|
62
|
+
assert payload is not None
|
|
63
|
+
assert payload["sub"] == subject
|
|
64
|
+
assert payload["type"] == "access"
|
|
65
|
+
|
|
66
|
+
def test_verify_access_token(self):
|
|
67
|
+
"""Test verifying access token."""
|
|
68
|
+
subject = "user123"
|
|
69
|
+
token = create_access_token(subject)
|
|
70
|
+
payload = verify_token(token)
|
|
71
|
+
|
|
72
|
+
assert payload is not None
|
|
73
|
+
assert payload["sub"] == subject
|
|
74
|
+
assert payload["type"] == "access"
|
|
75
|
+
|
|
76
|
+
def test_verify_invalid_token(self):
|
|
77
|
+
"""Test verifying invalid token."""
|
|
78
|
+
payload = verify_token("invalid.token.here")
|
|
79
|
+
|
|
80
|
+
assert payload is None
|
|
81
|
+
|
|
82
|
+
def test_verify_expired_token(self):
|
|
83
|
+
"""Test verifying expired token."""
|
|
84
|
+
subject = "user123"
|
|
85
|
+
# Create token that expires immediately
|
|
86
|
+
token = create_access_token(subject, expires_delta=timedelta(seconds=-1))
|
|
87
|
+
payload = verify_token(token)
|
|
88
|
+
|
|
89
|
+
assert payload is None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class TestRefreshToken:
|
|
93
|
+
"""Tests for refresh token functions."""
|
|
94
|
+
|
|
95
|
+
def test_create_refresh_token(self):
|
|
96
|
+
"""Test creating refresh token."""
|
|
97
|
+
subject = "user123"
|
|
98
|
+
token = create_refresh_token(subject)
|
|
99
|
+
|
|
100
|
+
assert isinstance(token, str)
|
|
101
|
+
assert len(token) > 0
|
|
102
|
+
|
|
103
|
+
def test_create_refresh_token_with_expires_delta(self):
|
|
104
|
+
"""Test creating refresh token with custom expiration."""
|
|
105
|
+
subject = "user123"
|
|
106
|
+
expires = timedelta(days=7)
|
|
107
|
+
token = create_refresh_token(subject, expires_delta=expires)
|
|
108
|
+
|
|
109
|
+
assert isinstance(token, str)
|
|
110
|
+
payload = verify_token(token)
|
|
111
|
+
assert payload is not None
|
|
112
|
+
assert payload["sub"] == subject
|
|
113
|
+
assert payload["type"] == "refresh"
|
|
114
|
+
|
|
115
|
+
def test_verify_refresh_token(self):
|
|
116
|
+
"""Test verifying refresh token."""
|
|
117
|
+
subject = "user123"
|
|
118
|
+
token = create_refresh_token(subject)
|
|
119
|
+
payload = verify_token(token)
|
|
120
|
+
|
|
121
|
+
assert payload is not None
|
|
122
|
+
assert payload["sub"] == subject
|
|
123
|
+
assert payload["type"] == "refresh"
|
|
124
|
+
{%- endif %}
|