Improve test coverance and fixes according to tests

This commit is contained in:
2026-03-05 11:46:48 +02:00
parent 2dfd625b5e
commit 03ba3fb3c2
17 changed files with 256 additions and 44 deletions
+4 -7
View File
@@ -2,6 +2,7 @@
import pytest
from sqlalchemy import create_engine
from sqlalchemy.pool import StaticPool
from sqlalchemy.orm import sessionmaker
from fastapi.testclient import TestClient
@@ -9,21 +10,18 @@ from app.main import app
from app.db.base import Base
from app.db.session import get_db
import os
os.environ["TESTING"] = "1"
# Use in-memory SQLite for tests
TEST_DATABASE_URL = "sqlite+pysqlite:///:memory:"
@pytest.fixture(scope="function")
def db_session():
"""
Creates a new database session for a test.
Rolls back everything after test finishes.
"""
engine = create_engine(
TEST_DATABASE_URL,
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
TestingSessionLocal = sessionmaker(
@@ -32,7 +30,6 @@ def db_session():
bind=engine,
)
# Create tables
Base.metadata.create_all(bind=engine)
db = TestingSessionLocal()
+16
View File
@@ -0,0 +1,16 @@
from app.core.config import get_settings
def test_get_settings_returns_same_instance():
s1 = get_settings()
s2 = get_settings()
assert s1 is s2 # lru_cache should return same instance
def test_default_settings_values():
settings = get_settings()
assert settings.app_name == "URL Shortener"
assert settings.host == "0.0.0.0"
assert settings.port == 8000
assert settings.debug is True
assert isinstance(settings.allowed_hosts, list)
+32
View File
@@ -0,0 +1,32 @@
from app.db.models import URL
def test_url_model_defaults(db_session):
url = URL(
short_code="abc123",
original_url="https://example.com",
)
db_session.add(url)
db_session.commit()
db_session.refresh(url)
assert url.id is not None
assert url.clicks == 0
assert url.created_at is not None
assert url.last_accessed is None
def test_url_repr(db_session):
url = URL(
short_code="xyz789",
original_url="https://repr-test.com",
)
db_session.add(url)
db_session.commit()
db_session.refresh(url)
repr_output = repr(url)
assert "URL" in repr_output
assert "xyz789" in repr_output
assert "https://repr-test.com" in repr_output
-3
View File
@@ -1,8 +1,5 @@
# app/tests/test_routes.py
import pytest
def test_home(client):
response = client.get("/")
assert response.status_code == 200
+106
View File
@@ -0,0 +1,106 @@
import pytest
from unittest.mock import patch
from app.services import url_service
from app.db.models import URL
def test_create_short_url_creates_new(db_session):
short_url = url_service.create_short_url(
db_session,
"https://new-url.com",
)
assert short_url.startswith("http://")
entry = db_session.query(URL).first()
assert entry is not None
assert entry.original_url == "https://new-url.com"
def test_create_short_url_returns_existing(db_session):
first = url_service.create_short_url(
db_session,
"https://duplicate.com",
)
second = url_service.create_short_url(
db_session,
"https://duplicate.com",
)
assert first == second
assert db_session.query(URL).count() == 1
def test_create_short_url_collision_failure(db_session):
with patch(
"app.services.url_service.generate_short_code",
return_value="fixedcode",
):
# First insert works
url_service.create_short_url(db_session, "https://a.com")
# Force collision repeatedly
with pytest.raises(RuntimeError):
url_service.create_short_url(db_session, "https://b.com")
def test_get_original_url_success(db_session):
short_url = url_service.create_short_url(
db_session,
"https://lookup.com",
)
code = short_url.split("/")[-1]
original = url_service.get_original_url(db_session, code)
assert original == "https://lookup.com"
def test_get_original_url_not_found(db_session):
result = url_service.get_original_url(db_session, "missing")
assert result is None
def test_increment_clicks_success(db_session):
short_url = url_service.create_short_url(
db_session,
"https://click-test.com",
)
code = short_url.split("/")[-1]
success = url_service.increment_clicks(db_session, code)
assert success is True
entry = db_session.query(URL).first()
assert entry.clicks == 1
assert entry.last_accessed is not None
def test_increment_clicks_not_found(db_session):
result = url_service.increment_clicks(db_session, "missing")
assert result is False
def test_get_stats_success(db_session):
short_url = url_service.create_short_url(
db_session,
"https://stats.com",
)
code = short_url.split("/")[-1]
url_service.increment_clicks(db_session, code)
stats = url_service.get_stats(db_session, code)
assert stats is not None
assert stats["original_url"] == "https://stats.com"
assert stats["clicks"] == 1
assert stats["created_at"] is not None
def test_get_stats_not_found(db_session):
stats = url_service.get_stats(db_session, "missing")
assert stats is None
+19
View File
@@ -0,0 +1,19 @@
import string
from app.utils.short_code import generate_short_code
def test_generate_short_code_length():
code = generate_short_code(8)
assert len(code) == 8
def test_generate_short_code_is_alphanumeric():
code = generate_short_code(12)
allowed = string.ascii_letters + string.digits
for char in code:
assert char in allowed
def test_generate_short_code_randomness():
code1 = generate_short_code()
code2 = generate_short_code()
assert code1 != code2 # extremely unlikely to fail