Skip to content

Commit f62310f

Browse files
committed
initial commit
1 parent 598b74b commit f62310f

30 files changed

Lines changed: 2258 additions & 0 deletions

.idea/.gitignore

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/vcs.xml

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import pytest
2+
from fastapi.testclient import TestClient
3+
from unittest.mock import patch, MagicMock
4+
from app.main import app
5+
import json
6+
7+
client = TestClient(app)
8+
9+
def test_api_versioning():
10+
"""Test API versioning and backward compatibility."""
11+
# Test current API version
12+
response = client.get("/api/v1/utils/health-check/")
13+
assert response.status_code == 200
14+
15+
# Test that v1 endpoints are available
16+
assert "/api/v1/" in str(response.url) or response.status_code == 200
17+
18+
def test_openapi_schema_validation():
19+
"""Test OpenAPI schema is valid and complete."""
20+
response = client.get("/openapi.json")
21+
assert response.status_code == 200
22+
23+
schema = response.json()
24+
assert "openapi" in schema
25+
assert "info" in schema
26+
assert "paths" in schema
27+
28+
def test_api_documentation_accessibility():
29+
"""Test that API documentation is accessible."""
30+
docs_response = client.get("/docs")
31+
redoc_response = client.get("/redoc")
32+
33+
# At least one documentation endpoint should be available
34+
assert docs_response.status_code == 200 or redoc_response.status_code == 200
35+
36+
def test_health_monitoring_endpoints():
37+
"""Test health monitoring and status endpoints."""
38+
health_response = client.get("/api/v1/utils/health-check/")
39+
assert health_response.status_code == 200
40+
41+
# Verify health check response format
42+
data = health_response.json()
43+
assert isinstance(data, dict)
44+
45+
def test_api_error_response_format():
46+
"""Test that API errors follow consistent format."""
47+
# Make request to non-existent endpoint
48+
response = client.get("/api/v1/nonexistent")
49+
assert response.status_code == 404
50+
51+
# Verify error response is JSON
52+
try:
53+
error_data = response.json()
54+
assert isinstance(error_data, dict)
55+
except json.JSONDecodeError:
56+
# Some APIs might return non-JSON 404s, which is also acceptable
57+
pass
58+
59+
def test_request_id_tracking():
60+
"""Test request ID tracking for debugging."""
61+
response = client.get("/api/v1/utils/health-check/")
62+
63+
# Check if request ID is present in headers (implementation dependent)
64+
request_id_headers = [
65+
"x-request-id", "request-id", "x-trace-id"
66+
]
67+
68+
has_request_id = any(header in response.headers for header in request_id_headers)
69+
# This test is optional - not all APIs implement request ID tracking
70+
assert response.status_code == 200 # Main assertion is that the endpoint works
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import pytest
2+
from fastapi.testclient import TestClient
3+
from app.main import app
4+
5+
client = TestClient(app)
6+
7+
def test_cors_headers():
8+
"""Test CORS headers are properly set."""
9+
response = client.options("/api/v1/users/me")
10+
assert response.status_code in [200, 405]
11+
# Check if CORS headers might be present
12+
if "access-control-allow-origin" in response.headers:
13+
assert response.headers["access-control-allow-origin"]
14+
15+
def test_api_version_consistency():
16+
"""Test that all API endpoints use consistent versioning."""
17+
# Test health check endpoint version
18+
response = client.get("/api/v1/utils/health-check/")
19+
assert response.status_code == 200
20+
21+
def test_content_type_headers():
22+
"""Test that API returns proper content-type headers."""
23+
response = client.get("/api/v1/utils/health-check/")
24+
assert "application/json" in response.headers.get("content-type", "")
25+
26+
def test_response_time_performance():
27+
"""Test basic response time performance."""
28+
import time
29+
start_time = time.time()
30+
response = client.get("/api/v1/utils/health-check/")
31+
end_time = time.time()
32+
33+
assert response.status_code == 200
34+
# Response should be under 1 second for health check
35+
assert (end_time - start_time) < 1.0
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import pytest
2+
from fastapi.testclient import TestClient
3+
from app.main import app
4+
5+
client = TestClient(app)
6+
7+
def test_login_valid_credentials():
8+
"""Test login with valid credentials."""
9+
response = client.post(
10+
"/api/v1/login/access-token",
11+
data={"username": "test@example.com", "password": "testpassword"}
12+
)
13+
assert response.status_code == 200
14+
data = response.json()
15+
assert "access_token" in data
16+
17+
def test_login_invalid_credentials():
18+
"""Test login with invalid credentials."""
19+
response = client.post(
20+
"/api/v1/login/access-token",
21+
data={"username": "invalid@example.com", "password": "wrongpassword"}
22+
)
23+
assert response.status_code == 400
24+
25+
def test_protected_route_without_token():
26+
"""Test accessing protected route without token."""
27+
response = client.get("/api/v1/users/me")
28+
assert response.status_code == 401
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import pytest
2+
from fastapi.testclient import TestClient
3+
from app.main import app
4+
5+
client = TestClient(app)
6+
7+
def test_unauthorized_access():
8+
"""Test accessing protected endpoints without authentication."""
9+
response = client.get("/api/v1/users/me")
10+
assert response.status_code == 401
11+
12+
def test_invalid_token_format():
13+
"""Test using malformed authorization token."""
14+
headers = {"Authorization": "InvalidTokenFormat"}
15+
response = client.get("/api/v1/users/me", headers=headers)
16+
assert response.status_code == 401
17+
18+
def test_expired_token():
19+
"""Test using expired token."""
20+
# This would require creating an expired token
21+
headers = {"Authorization": "Bearer expired_token_here"}
22+
response = client.get("/api/v1/users/me", headers=headers)
23+
assert response.status_code == 401
24+
25+
def test_insufficient_permissions():
26+
"""Test accessing admin endpoints with regular user token."""
27+
headers = {"Authorization": "Bearer regular_user_token"}
28+
response = client.get("/api/v1/admin/users/", headers=headers)
29+
assert response.status_code in [401, 403, 404]

backend/tests/test_config.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import pytest
2+
from unittest.mock import patch
3+
from app.core.config import Settings
4+
5+
def test_settings_initialization():
6+
"""Test settings class initialization."""
7+
settings = Settings()
8+
assert hasattr(settings, 'SECRET_KEY')
9+
assert hasattr(settings, 'PROJECT_NAME')
10+
11+
def test_database_url_construction():
12+
"""Test database URL construction."""
13+
settings = Settings()
14+
# Should have postgres in the URL
15+
assert 'postgresql' in str(settings.SQLALCHEMY_DATABASE_URI)
16+
17+
def test_cors_origins_parsing():
18+
"""Test CORS origins parsing."""
19+
with patch.dict('os.environ', {'BACKEND_CORS_ORIGINS': '["http://localhost:3000", "http://localhost:8000"]'}):
20+
settings = Settings()
21+
assert isinstance(settings.BACKEND_CORS_ORIGINS, list)
22+
23+
def test_environment_validation():
24+
"""Test environment variable validation."""
25+
settings = Settings()
26+
assert settings.ENVIRONMENT in ['local', 'staging', 'production']
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import pytest
2+
from sqlalchemy.orm import Session
3+
from app.models import User, Item
4+
from app.crud import create_user, get_user, update_user, delete_user
5+
6+
def test_create_user_crud(db: Session):
7+
"""Test user creation through CRUD operations."""
8+
user_data = {
9+
"email": "crud@example.com",
10+
"password": "testpassword",
11+
"full_name": "CRUD User"
12+
}
13+
user = create_user(db, user_data)
14+
assert user.email == "crud@example.com"
15+
assert user.full_name == "CRUD User"
16+
17+
def test_get_user_by_email(db: Session):
18+
"""Test retrieving user by email."""
19+
# First create a user
20+
user_data = {
21+
"email": "getuser@example.com",
22+
"password": "testpassword"
23+
}
24+
created_user = create_user(db, user_data)
25+
26+
# Then retrieve it
27+
retrieved_user = get_user(db, email="getuser@example.com")
28+
assert retrieved_user is not None
29+
assert retrieved_user.email == "getuser@example.com"
30+
31+
def test_update_user_crud(db: Session):
32+
"""Test user update through CRUD operations."""
33+
user_data = {"email": "update@example.com", "password": "test"}
34+
user = create_user(db, user_data)
35+
36+
update_data = {"full_name": "Updated Name"}
37+
updated_user = update_user(db, user.id, update_data)
38+
assert updated_user.full_name == "Updated Name"

backend/tests/test_database.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import pytest
2+
from sqlalchemy.orm import Session
3+
from app.core.db import get_db
4+
from app.models import User
5+
from app.utils import generate_password_reset_token, verify_password_reset_token
6+
7+
def test_database_connection(db: Session):
8+
"""Test database connection is working."""
9+
result = db.execute("SELECT 1")
10+
assert result.fetchone()[0] == 1
11+
12+
def test_user_model_creation(db: Session):
13+
"""Test creating a user in the database."""
14+
user_data = {
15+
"email": "test@example.com",
16+
"hashed_password": "hashedpassword123",
17+
"full_name": "Test User"
18+
}
19+
user = User(**user_data)
20+
db.add(user)
21+
db.commit()
22+
db.refresh(user)
23+
24+
assert user.id is not None
25+
assert user.email == "test@example.com"
26+
assert user.full_name == "Test User"
27+
28+
def test_database_rollback(db: Session):
29+
"""Test database rollback functionality."""
30+
user = User(email="rollback@example.com", hashed_password="test123")
31+
db.add(user)
32+
db.rollback()
33+
34+
# User should not exist after rollback
35+
user_check = db.query(User).filter(User.email == "rollback@example.com").first()
36+
assert user_check is None
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import pytest
2+
from unittest.mock import patch, MagicMock
3+
from app.utils import send_email
4+
5+
def test_email_service_integration():
6+
"""Test email service integration."""
7+
with patch('smtplib.SMTP') as mock_smtp:
8+
mock_server = MagicMock()
9+
mock_smtp.return_value = mock_server
10+
11+
result = send_email(
12+
email_to="test@example.com",
13+
subject="Test Subject",
14+
html_content="<p>Test email</p>"
15+
)
16+
17+
mock_smtp.assert_called_once()
18+
19+
@patch('app.core.config.settings.SMTP_HOST', 'localhost')
20+
def test_email_configuration():
21+
"""Test email configuration settings."""
22+
from app.core.config import settings
23+
assert settings.SMTP_HOST == 'localhost'
24+
25+
def test_email_template_rendering():
26+
"""Test email template rendering with variables."""
27+
template_data = {
28+
"username": "Test User",
29+
"reset_link": "https://example.com/reset"
30+
}
31+
32+
# Mock template rendering
33+
with patch('app.email_templates.render_template') as mock_render:
34+
mock_render.return_value = "<p>Hello Test User</p>"
35+
result = mock_render(template_data)
36+
assert "Test User" in result
37+
38+
def test_email_sending_failure():
39+
"""Test handling email sending failures."""
40+
with patch('smtplib.SMTP') as mock_smtp:
41+
mock_smtp.side_effect = Exception("SMTP connection failed")
42+
43+
result = send_email("test@example.com", "Test", "<p>Test</p>")
44+
# Should handle the error gracefully
45+
assert result is False or result is None

0 commit comments

Comments
 (0)