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,121 @@
|
|
|
1
|
+
{%- if cookiecutter.enable_ai_agent %}
|
|
2
|
+
"""Tests for AI agent module."""
|
|
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
|
+
{%- 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 %}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Tests for core modules."""
|
|
2
|
+
|
|
3
|
+
from app.core.config import settings
|
|
4
|
+
from app.core.exceptions import (
|
|
5
|
+
AlreadyExistsError,
|
|
6
|
+
AppException,
|
|
7
|
+
AuthenticationError,
|
|
8
|
+
AuthorizationError,
|
|
9
|
+
NotFoundError,
|
|
10
|
+
ValidationError,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestSettings:
|
|
15
|
+
"""Tests for settings configuration."""
|
|
16
|
+
|
|
17
|
+
def test_project_name_is_set(self):
|
|
18
|
+
"""Test project name is configured."""
|
|
19
|
+
assert settings.PROJECT_NAME == "{{ cookiecutter.project_name }}"
|
|
20
|
+
|
|
21
|
+
def test_api_v1_str_is_set(self):
|
|
22
|
+
"""Test API version string is set."""
|
|
23
|
+
assert settings.API_V1_STR == "/api/v1"
|
|
24
|
+
|
|
25
|
+
def test_debug_mode_default(self):
|
|
26
|
+
"""Test debug mode has default value."""
|
|
27
|
+
assert isinstance(settings.DEBUG, bool)
|
|
28
|
+
|
|
29
|
+
{%- if cookiecutter.enable_cors %}
|
|
30
|
+
def test_cors_origins_is_list(self):
|
|
31
|
+
"""Test CORS origins is a list."""
|
|
32
|
+
assert isinstance(settings.CORS_ORIGINS, list)
|
|
33
|
+
{%- endif %}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TestExceptions:
|
|
37
|
+
"""Tests for custom exceptions."""
|
|
38
|
+
|
|
39
|
+
def test_app_exception(self):
|
|
40
|
+
"""Test AppException initialization."""
|
|
41
|
+
error = AppException(message="Test error", code="TEST_ERROR")
|
|
42
|
+
assert error.message == "Test error"
|
|
43
|
+
assert error.code == "TEST_ERROR"
|
|
44
|
+
assert str(error) == "Test error"
|
|
45
|
+
|
|
46
|
+
def test_not_found_error(self):
|
|
47
|
+
"""Test NotFoundError."""
|
|
48
|
+
error = NotFoundError(message="Item not found")
|
|
49
|
+
assert error.status_code == 404
|
|
50
|
+
assert error.code == "NOT_FOUND"
|
|
51
|
+
|
|
52
|
+
def test_already_exists_error(self):
|
|
53
|
+
"""Test AlreadyExistsError."""
|
|
54
|
+
error = AlreadyExistsError(message="Item already exists")
|
|
55
|
+
assert error.status_code == 409
|
|
56
|
+
assert error.code == "ALREADY_EXISTS"
|
|
57
|
+
|
|
58
|
+
def test_authentication_error(self):
|
|
59
|
+
"""Test AuthenticationError."""
|
|
60
|
+
error = AuthenticationError(message="Invalid credentials")
|
|
61
|
+
assert error.status_code == 401
|
|
62
|
+
assert error.code == "AUTHENTICATION_ERROR"
|
|
63
|
+
|
|
64
|
+
def test_authorization_error(self):
|
|
65
|
+
"""Test AuthorizationError."""
|
|
66
|
+
error = AuthorizationError(message="Not authorized")
|
|
67
|
+
assert error.status_code == 403
|
|
68
|
+
assert error.code == "AUTHORIZATION_ERROR"
|
|
69
|
+
|
|
70
|
+
def test_validation_error(self):
|
|
71
|
+
"""Test ValidationError."""
|
|
72
|
+
error = ValidationError(message="Invalid input")
|
|
73
|
+
assert error.status_code == 422
|
|
74
|
+
assert error.code == "VALIDATION_ERROR"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
{%- if cookiecutter.enable_redis %}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TestCacheSetup:
|
|
81
|
+
"""Tests for cache setup."""
|
|
82
|
+
|
|
83
|
+
def test_setup_cache_function_exists(self):
|
|
84
|
+
"""Test setup_cache function exists."""
|
|
85
|
+
from app.core.cache import setup_cache
|
|
86
|
+
|
|
87
|
+
assert setup_cache is not None
|
|
88
|
+
assert callable(setup_cache)
|
|
89
|
+
{%- endif %}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class TestMiddleware:
|
|
93
|
+
"""Tests for middleware."""
|
|
94
|
+
|
|
95
|
+
def test_request_id_middleware_exists(self):
|
|
96
|
+
"""Test request ID middleware is configured."""
|
|
97
|
+
from app.core.middleware import RequestIDMiddleware
|
|
98
|
+
|
|
99
|
+
assert RequestIDMiddleware is not None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
{%- if cookiecutter.enable_rate_limiting %}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class TestRateLimit:
|
|
106
|
+
"""Tests for rate limiting."""
|
|
107
|
+
|
|
108
|
+
def test_limiter_exists(self):
|
|
109
|
+
"""Test rate limiter is configured."""
|
|
110
|
+
from app.core.rate_limit import limiter
|
|
111
|
+
|
|
112
|
+
assert limiter is not None
|
|
113
|
+
{%- endif %}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
{%- if cookiecutter.enable_logfire %}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
from unittest.mock import patch # noqa: E402
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class TestLogfireSetup:
|
|
123
|
+
"""Tests for Logfire setup."""
|
|
124
|
+
|
|
125
|
+
@patch("app.core.logfire_setup.logfire")
|
|
126
|
+
def test_setup_logfire_configures(self, mock_logfire):
|
|
127
|
+
"""Test setup_logfire calls configure."""
|
|
128
|
+
from app.core.logfire_setup import setup_logfire
|
|
129
|
+
|
|
130
|
+
setup_logfire()
|
|
131
|
+
mock_logfire.configure.assert_called_once()
|
|
132
|
+
|
|
133
|
+
@patch("app.core.logfire_setup.logfire")
|
|
134
|
+
def test_instrument_app_instruments_fastapi(self, mock_logfire):
|
|
135
|
+
"""Test instrument_app instruments FastAPI."""
|
|
136
|
+
from fastapi import FastAPI
|
|
137
|
+
|
|
138
|
+
from app.core.logfire_setup import instrument_app
|
|
139
|
+
|
|
140
|
+
app = FastAPI()
|
|
141
|
+
instrument_app(app)
|
|
142
|
+
mock_logfire.instrument_fastapi.assert_called()
|
|
143
|
+
{%- endif %}
|