feat(api): Pydantic schemas + Data Repositories
This commit is contained in:
133
tests/repositories/test_user.py
Normal file
133
tests/repositories/test_user.py
Normal file
@@ -0,0 +1,133 @@
|
||||
"""Tests for UserRepository."""
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
import asyncpg
|
||||
import pytest
|
||||
|
||||
from app.repositories.user import UserRepository
|
||||
|
||||
|
||||
class TestUserRepository:
|
||||
"""Tests for UserRepository conforming to SPECS.md."""
|
||||
|
||||
async def test_create_user_returns_user_data(self, db_conn: asyncpg.Connection) -> None:
|
||||
"""Creating a user returns the user data with id, email, created_at."""
|
||||
repo = UserRepository(db_conn)
|
||||
user_id = uuid4()
|
||||
email = "test@example.com"
|
||||
password_hash = "hashed_password_123"
|
||||
|
||||
result = await repo.create(user_id, email, password_hash)
|
||||
|
||||
assert result["id"] == user_id
|
||||
assert result["email"] == email
|
||||
assert result["created_at"] is not None
|
||||
|
||||
async def test_create_user_stores_password_hash(self, db_conn: asyncpg.Connection) -> None:
|
||||
"""Password hash is stored correctly in the database."""
|
||||
repo = UserRepository(db_conn)
|
||||
user_id = uuid4()
|
||||
email = "hash_test@example.com"
|
||||
password_hash = "bcrypt_hashed_value"
|
||||
|
||||
await repo.create(user_id, email, password_hash)
|
||||
user = await repo.get_by_id(user_id)
|
||||
|
||||
assert user["password_hash"] == password_hash
|
||||
|
||||
async def test_create_user_email_must_be_unique(self, db_conn: asyncpg.Connection) -> None:
|
||||
"""Email uniqueness constraint per SPECS.md users table."""
|
||||
repo = UserRepository(db_conn)
|
||||
email = "duplicate@example.com"
|
||||
|
||||
await repo.create(uuid4(), email, "hash1")
|
||||
|
||||
with pytest.raises(asyncpg.UniqueViolationError):
|
||||
await repo.create(uuid4(), email, "hash2")
|
||||
|
||||
async def test_get_by_id_returns_user(self, db_conn: asyncpg.Connection) -> None:
|
||||
"""get_by_id returns the correct user."""
|
||||
repo = UserRepository(db_conn)
|
||||
user_id = uuid4()
|
||||
email = "getbyid@example.com"
|
||||
|
||||
await repo.create(user_id, email, "hash")
|
||||
result = await repo.get_by_id(user_id)
|
||||
|
||||
assert result is not None
|
||||
assert result["id"] == user_id
|
||||
assert result["email"] == email
|
||||
|
||||
async def test_get_by_id_returns_none_for_nonexistent(self, db_conn: asyncpg.Connection) -> None:
|
||||
"""get_by_id returns None for non-existent user."""
|
||||
repo = UserRepository(db_conn)
|
||||
|
||||
result = await repo.get_by_id(uuid4())
|
||||
|
||||
assert result is None
|
||||
|
||||
async def test_get_by_email_returns_user(self, db_conn: asyncpg.Connection) -> None:
|
||||
"""get_by_email returns the correct user."""
|
||||
repo = UserRepository(db_conn)
|
||||
user_id = uuid4()
|
||||
email = "getbyemail@example.com"
|
||||
|
||||
await repo.create(user_id, email, "hash")
|
||||
result = await repo.get_by_email(email)
|
||||
|
||||
assert result is not None
|
||||
assert result["id"] == user_id
|
||||
assert result["email"] == email
|
||||
|
||||
async def test_get_by_email_returns_none_for_nonexistent(self, db_conn: asyncpg.Connection) -> None:
|
||||
"""get_by_email returns None for non-existent email."""
|
||||
repo = UserRepository(db_conn)
|
||||
|
||||
result = await repo.get_by_email("nonexistent@example.com")
|
||||
|
||||
assert result is None
|
||||
|
||||
async def test_get_by_email_is_case_sensitive(self, db_conn: asyncpg.Connection) -> None:
|
||||
"""Email lookup is case-sensitive (stored as provided)."""
|
||||
repo = UserRepository(db_conn)
|
||||
email = "CaseSensitive@Example.com"
|
||||
|
||||
await repo.create(uuid4(), email, "hash")
|
||||
|
||||
# Exact match works
|
||||
result = await repo.get_by_email(email)
|
||||
assert result is not None
|
||||
|
||||
# Different case returns None
|
||||
result = await repo.get_by_email(email.lower())
|
||||
assert result is None
|
||||
|
||||
async def test_exists_by_email_returns_true_when_exists(self, db_conn: asyncpg.Connection) -> None:
|
||||
"""exists_by_email returns True when email exists."""
|
||||
repo = UserRepository(db_conn)
|
||||
email = "exists@example.com"
|
||||
|
||||
await repo.create(uuid4(), email, "hash")
|
||||
result = await repo.exists_by_email(email)
|
||||
|
||||
assert result is True
|
||||
|
||||
async def test_exists_by_email_returns_false_when_not_exists(self, db_conn: asyncpg.Connection) -> None:
|
||||
"""exists_by_email returns False when email doesn't exist."""
|
||||
repo = UserRepository(db_conn)
|
||||
|
||||
result = await repo.exists_by_email("notexists@example.com")
|
||||
|
||||
assert result is False
|
||||
|
||||
async def test_user_id_is_uuid_primary_key(self, db_conn: asyncpg.Connection) -> None:
|
||||
"""User ID must be a valid UUID (primary key)."""
|
||||
repo = UserRepository(db_conn)
|
||||
user_id = uuid4()
|
||||
|
||||
await repo.create(user_id, "pk_test@example.com", "hash")
|
||||
|
||||
# Duplicate ID should fail
|
||||
with pytest.raises(asyncpg.UniqueViolationError):
|
||||
await repo.create(user_id, "other@example.com", "hash")
|
||||
Reference in New Issue
Block a user