Changes committed

This commit is contained in:
2026-06-01 21:49:53 +05:30
parent 8537c653c1
commit 3163bb213e
387 changed files with 21940 additions and 107 deletions

View File

View File

@@ -0,0 +1,278 @@
from __future__ import annotations
import uuid
import pytest
from sqlalchemy.orm import Session
from app.models.document import (
Document,
DocumentImage,
DocumentPage,
DocumentTable,
DocumentTextBlock,
TemplateMatch,
)
from app.repositories.document_repository import (
DocumentPageRepository,
DocumentRepository,
DocumentTextBlockRepository,
TemplateMatchRepository,
)
from tests.conftest import (
create_test_document,
create_test_fingerprint,
create_test_page,
create_test_template,
create_test_text_block,
create_test_user,
)
class TestDocumentRepository:
"""Tests for DocumentRepository CRUD."""
def test_create_document(self, db: Session) -> None:
user = create_test_user(db)
doc = create_test_document(db, user=user, filename="created.pdf")
assert doc.id is not None
assert doc.original_filename == "created.pdf"
assert doc.status == "pending"
def test_get_by_id(self, db: Session) -> None:
user = create_test_user(db)
doc = create_test_document(db, user=user)
repo = DocumentRepository(db)
found = repo.get_by_id(doc.id)
assert found is not None
assert found.id == doc.id
def test_get_by_id_not_found(self, db: Session) -> None:
repo = DocumentRepository(db)
assert repo.get_by_id(uuid.uuid4()) is None
def test_get_with_pages(self, db: Session) -> None:
user = create_test_user(db)
doc = create_test_document(db, user=user)
page1 = create_test_page(db, doc, page_number=1)
page2 = create_test_page(db, doc, page_number=2)
create_test_text_block(db, page1, text="Page 1 text")
repo = DocumentRepository(db)
result = repo.get_with_pages(doc.id)
assert result is not None
assert len(result.pages) == 2
def test_update_status(self, db: Session) -> None:
user = create_test_user(db)
doc = create_test_document(db, user=user, status="pending")
repo = DocumentRepository(db)
repo.update_status(doc.id, "processing")
db.flush()
updated = repo.get_by_id(doc.id)
assert updated is not None
assert updated.status == "processing"
def test_update_status_with_error(self, db: Session) -> None:
user = create_test_user(db)
doc = create_test_document(db, user=user, status="processing")
repo = DocumentRepository(db)
repo.update_status(doc.id, "failed", error_message="OCR engine crashed")
db.flush()
updated = repo.get_by_id(doc.id)
assert updated is not None
assert updated.status == "failed"
assert updated.error_message == "OCR engine crashed"
def test_get_all_with_pagination(self, db: Session) -> None:
user = create_test_user(db)
for i in range(5):
create_test_document(db, user=user, filename=f"pagdoc_{i}.pdf")
repo = DocumentRepository(db)
page1 = repo.get_all(offset=0, limit=3)
assert len(page1) == 3
page2 = repo.get_all(offset=3, limit=3)
assert len(page2) == 2
def test_get_all_with_status_filter(self, db: Session) -> None:
user = create_test_user(db)
create_test_document(db, user=user, filename="pend.pdf", status="pending")
create_test_document(db, user=user, filename="comp.pdf", status="completed")
create_test_document(db, user=user, filename="fail.pdf", status="failed")
repo = DocumentRepository(db)
pending = repo.get_all(offset=0, limit=100, filters={"status": "pending"})
assert all(d.status == "pending" for d in pending)
def test_count(self, db: Session) -> None:
user = create_test_user(db)
create_test_document(db, user=user, filename="cnt1.pdf")
create_test_document(db, user=user, filename="cnt2.pdf")
repo = DocumentRepository(db)
total = repo.count()
assert total >= 2
def test_count_with_filter(self, db: Session) -> None:
user = create_test_user(db)
create_test_document(db, user=user, filename="cnt_p.pdf", status="pending")
create_test_document(db, user=user, filename="cnt_c.pdf", status="completed")
repo = DocumentRepository(db)
pending_count = repo.count(filters={"status": "pending"})
assert pending_count >= 1
def test_delete(self, db: Session) -> None:
user = create_test_user(db)
doc = create_test_document(db, user=user)
repo = DocumentRepository(db)
repo.delete(doc)
db.flush()
assert repo.get_by_id(doc.id) is None
def test_get_by_checksum(self, db: Session) -> None:
user = create_test_user(db)
doc = create_test_document(db, user=user)
repo = DocumentRepository(db)
found = repo.get_by_checksum(doc.checksum)
assert found is not None
assert found.id == doc.id
class TestDocumentPageRepository:
"""Tests for DocumentPageRepository."""
def test_create_page(self, db: Session) -> None:
user = create_test_user(db)
doc = create_test_document(db, user=user)
page = create_test_page(db, doc, page_number=1)
assert page.id is not None
assert page.document_id == doc.id
assert page.page_number == 1
def test_get_pages_by_document(self, db: Session) -> None:
user = create_test_user(db)
doc = create_test_document(db, user=user)
create_test_page(db, doc, page_number=1)
create_test_page(db, doc, page_number=2)
create_test_page(db, doc, page_number=3)
repo = DocumentPageRepository(db)
pages = repo.get_document_pages(doc.id)
assert len(pages) == 3
assert [p.page_number for p in pages] == [1, 2, 3]
class TestDocumentTextBlockRepository:
"""Tests for DocumentTextBlockRepository."""
def test_create_text_block(self, db: Session) -> None:
user = create_test_user(db)
doc = create_test_document(db, user=user)
page = create_test_page(db, doc)
block = create_test_text_block(db, page, text="Hello World")
assert block.id is not None
assert block.text == "Hello World"
assert block.page_id == page.id
def test_get_blocks_by_page(self, db: Session) -> None:
user = create_test_user(db)
doc = create_test_document(db, user=user)
page = create_test_page(db, doc)
create_test_text_block(db, page, text="First", sequence=0)
create_test_text_block(db, page, text="Second", sequence=1, y=120.0)
create_test_text_block(db, page, text="Third", sequence=2, y=140.0)
repo = DocumentTextBlockRepository(db)
blocks = repo.get_page_text_blocks(page.id)
assert len(blocks) == 3
def test_get_blocks_by_type(self, db: Session) -> None:
user = create_test_user(db)
doc = create_test_document(db, user=user)
page = create_test_page(db, doc)
create_test_text_block(db, page, text="Header", block_type="header")
create_test_text_block(db, page, text="Body", block_type="text", y=200.0)
create_test_text_block(db, page, text="Footer", block_type="footer", y=700.0)
repo = DocumentTextBlockRepository(db)
headers = repo.get_by_block_type(page.id, "header")
assert len(headers) == 1
assert headers[0].text == "Header"
class TestTemplateMatchRepository:
"""Tests for TemplateMatchRepository."""
def test_create_match(self, db: Session) -> None:
user = create_test_user(db)
doc = create_test_document(db, user=user, status="completed")
template = create_test_template(db, created_by=user)
match = TemplateMatch(
id=uuid.uuid4(),
document_id=doc.id,
format_id=template.id,
confidence_score=0.85,
selected=True,
)
db.add(match)
db.flush()
repo = TemplateMatchRepository(db)
matches = repo.get_document_matches(doc.id)
assert len(matches) == 1
assert matches[0].confidence_score == 0.85
def test_get_selected_match(self, db: Session) -> None:
user = create_test_user(db)
doc = create_test_document(db, user=user, status="completed")
t1 = create_test_template(db, name="Low", created_by=user)
t2 = create_test_template(db, name="High", created_by=user)
db.add(TemplateMatch(
id=uuid.uuid4(), document_id=doc.id, format_id=t1.id,
confidence_score=0.5, selected=False,
))
db.add(TemplateMatch(
id=uuid.uuid4(), document_id=doc.id, format_id=t2.id,
confidence_score=0.95, selected=True,
))
db.flush()
repo = TemplateMatchRepository(db)
best = repo.get_selected_match(doc.id)
assert best is not None
assert best.format_id == t2.id
assert best.confidence_score == 0.95
def test_get_document_matches_ordered(self, db: Session) -> None:
user = create_test_user(db)
doc = create_test_document(db, user=user, status="completed")
t1 = create_test_template(db, name="T1", created_by=user)
t2 = create_test_template(db, name="T2", created_by=user)
t3 = create_test_template(db, name="T3", created_by=user)
db.add(TemplateMatch(
id=uuid.uuid4(), document_id=doc.id, format_id=t1.id,
confidence_score=0.3, selected=False,
))
db.add(TemplateMatch(
id=uuid.uuid4(), document_id=doc.id, format_id=t2.id,
confidence_score=0.9, selected=True,
))
db.add(TemplateMatch(
id=uuid.uuid4(), document_id=doc.id, format_id=t3.id,
confidence_score=0.6, selected=False,
))
db.flush()
repo = TemplateMatchRepository(db)
matches = repo.get_document_matches(doc.id)
scores = [m.confidence_score for m in matches]
assert scores == sorted(scores, reverse=True)

View File

@@ -0,0 +1,292 @@
from __future__ import annotations
import uuid
from datetime import UTC, datetime, timedelta
import pytest
from sqlalchemy.orm import Session
from app.models.user import RefreshToken, Role, User
from app.repositories.user_repository import (
AuditLogRepository,
RefreshTokenRepository,
RoleRepository,
UserRepository,
)
from tests.conftest import create_test_role, create_test_user
class TestUserRepository:
"""Tests for UserRepository CRUD operations."""
def test_create_user(self, db: Session) -> None:
repo = UserRepository(db)
user = repo.create_user(
username="repo_user",
email="repo@test.com",
hashed_password="$2b$12$fakehash",
full_name="Repo User",
)
assert user.id is not None
assert user.username == "repo_user"
assert user.email == "repo@test.com"
assert user.is_active is True
assert user.is_superuser is False
def test_get_by_username(self, db: Session) -> None:
user = create_test_user(db, username="findme")
repo = UserRepository(db)
found = repo.get_by_username("findme")
assert found is not None
assert found.id == user.id
def test_get_by_username_not_found(self, db: Session) -> None:
repo = UserRepository(db)
assert repo.get_by_username("nonexistent") is None
def test_get_by_email(self, db: Session) -> None:
user = create_test_user(db, email="email@test.com")
repo = UserRepository(db)
found = repo.get_by_email("email@test.com")
assert found is not None
assert found.id == user.id
def test_get_by_email_not_found(self, db: Session) -> None:
repo = UserRepository(db)
assert repo.get_by_email("nobody@test.com") is None
def test_get_by_id(self, db: Session) -> None:
user = create_test_user(db)
repo = UserRepository(db)
found = repo.get_by_id(user.id)
assert found is not None
assert found.username == user.username
def test_get_by_id_not_found(self, db: Session) -> None:
repo = UserRepository(db)
assert repo.get_by_id(uuid.uuid4()) is None
def test_update_last_login(self, db: Session) -> None:
user = create_test_user(db)
assert user.last_login is None
repo = UserRepository(db)
updated = repo.update_last_login(user)
assert updated.last_login is not None
def test_get_active_users(self, db: Session) -> None:
create_test_user(db, username="active1", is_active=True)
create_test_user(db, username="active2", is_active=True)
create_test_user(db, username="inactive1", is_active=False)
repo = UserRepository(db)
active = repo.get_active_users()
usernames = [u.username for u in active]
assert "active1" in usernames
assert "active2" in usernames
assert "inactive1" not in usernames
def test_create_user_with_roles(self, db: Session) -> None:
create_test_role(db, name="admin")
create_test_role(db, name="user")
repo = UserRepository(db)
user = repo.create_user(
username="roled_user",
email="roled@test.com",
hashed_password="$2b$12$fakehash",
role_names=["admin", "user"],
)
role_names = [r.name for r in user.roles]
assert "admin" in role_names
assert "user" in role_names
def test_assign_roles(self, db: Session) -> None:
create_test_role(db, name="viewer")
user = create_test_user(db)
repo = UserRepository(db)
updated = repo.assign_roles(user, ["viewer"])
role_names = [r.name for r in updated.roles]
assert "viewer" in role_names
def test_delete_user(self, db: Session) -> None:
user = create_test_user(db)
repo = UserRepository(db)
assert repo.delete_by_id(user.id) is True
assert repo.get_by_id(user.id) is None
def test_delete_nonexistent_user(self, db: Session) -> None:
repo = UserRepository(db)
assert repo.delete_by_id(uuid.uuid4()) is False
def test_exists(self, db: Session) -> None:
user = create_test_user(db)
repo = UserRepository(db)
assert repo.exists(user.id) is True
assert repo.exists(uuid.uuid4()) is False
def test_count(self, db: Session) -> None:
create_test_user(db, username="count1")
create_test_user(db, username="count2")
repo = UserRepository(db)
assert repo.count() >= 2
class TestRoleRepository:
"""Tests for RoleRepository."""
def test_create_role(self, db: Session) -> None:
repo = RoleRepository(db)
role = repo.create_role(name="editor", description="Can edit documents")
assert role.id is not None
assert role.name == "editor"
def test_get_by_name(self, db: Session) -> None:
create_test_role(db, name="tester")
repo = RoleRepository(db)
found = repo.get_by_name("tester")
assert found is not None
assert found.name == "tester"
def test_get_by_name_not_found(self, db: Session) -> None:
repo = RoleRepository(db)
assert repo.get_by_name("nonexistent_role") is None
def test_get_all_roles(self, db: Session) -> None:
create_test_role(db, name="role_a")
create_test_role(db, name="role_b")
repo = RoleRepository(db)
roles = repo.get_all_roles()
names = [r.name for r in roles]
assert "role_a" in names
assert "role_b" in names
class TestRefreshTokenRepository:
"""Tests for RefreshTokenRepository."""
def test_create_token(self, db: Session) -> None:
user = create_test_user(db)
repo = RefreshTokenRepository(db)
expires = datetime.now(UTC) + timedelta(days=7)
token = repo.create_token(
user_id=user.id,
token="test_refresh_token_abc",
expires_at=expires,
)
assert token.id is not None
assert token.user_id == user.id
assert token.revoked is False
def test_get_by_token(self, db: Session) -> None:
user = create_test_user(db)
repo = RefreshTokenRepository(db)
expires = datetime.now(UTC) + timedelta(days=7)
repo.create_token(user_id=user.id, token="findable_token", expires_at=expires)
found = repo.get_by_token("findable_token")
assert found is not None
assert found.user_id == user.id
def test_get_by_token_not_found(self, db: Session) -> None:
repo = RefreshTokenRepository(db)
assert repo.get_by_token("nonexistent_token") is None
def test_get_by_revoked_token_returns_none(self, db: Session) -> None:
user = create_test_user(db)
repo = RefreshTokenRepository(db)
expires = datetime.now(UTC) + timedelta(days=7)
repo.create_token(user_id=user.id, token="revoked_token", expires_at=expires)
repo.revoke_token("revoked_token")
db.flush()
assert repo.get_by_token("revoked_token") is None
def test_revoke_token(self, db: Session) -> None:
user = create_test_user(db)
repo = RefreshTokenRepository(db)
expires = datetime.now(UTC) + timedelta(days=7)
repo.create_token(user_id=user.id, token="to_revoke", expires_at=expires)
assert repo.revoke_token("to_revoke") is True
assert repo.get_by_token("to_revoke") is None
def test_revoke_nonexistent_token(self, db: Session) -> None:
repo = RefreshTokenRepository(db)
assert repo.revoke_token("does_not_exist") is False
def test_revoke_all_user_tokens(self, db: Session) -> None:
user = create_test_user(db)
repo = RefreshTokenRepository(db)
expires = datetime.now(UTC) + timedelta(days=7)
repo.create_token(user_id=user.id, token="token_1", expires_at=expires)
repo.create_token(user_id=user.id, token="token_2", expires_at=expires)
repo.create_token(user_id=user.id, token="token_3", expires_at=expires)
count = repo.revoke_all_user_tokens(user.id)
assert count == 3
assert repo.get_by_token("token_1") is None
assert repo.get_by_token("token_2") is None
assert repo.get_by_token("token_3") is None
def test_cleanup_expired_tokens(self, db: Session) -> None:
user = create_test_user(db)
repo = RefreshTokenRepository(db)
# Create expired token
expired = datetime.now(UTC) - timedelta(days=1)
repo.create_token(user_id=user.id, token="expired_tok", expires_at=expired)
# Create valid token
valid = datetime.now(UTC) + timedelta(days=7)
repo.create_token(user_id=user.id, token="valid_tok", expires_at=valid)
count = repo.cleanup_expired_tokens()
assert count >= 1
class TestAuditLogRepository:
"""Tests for AuditLogRepository."""
def test_log_action(self, db: Session) -> None:
user = create_test_user(db)
repo = AuditLogRepository(db)
log = repo.log_action(
action="login",
resource_type="auth",
user_id=user.id,
ip_address="127.0.0.1",
user_agent="TestAgent/1.0",
)
assert log.id is not None
assert log.action == "login"
assert log.resource_type == "auth"
def test_log_action_without_user(self, db: Session) -> None:
repo = AuditLogRepository(db)
log = repo.log_action(
action="anonymous_access",
resource_type="documents",
)
assert log.id is not None
assert log.user_id is None
def test_get_user_logs(self, db: Session) -> None:
user = create_test_user(db)
repo = AuditLogRepository(db)
repo.log_action(action="view", resource_type="documents", user_id=user.id)
repo.log_action(action="edit", resource_type="templates", user_id=user.id)
logs = repo.get_user_logs(user.id)
assert len(logs) >= 2
def test_get_resource_logs(self, db: Session) -> None:
repo = AuditLogRepository(db)
resource_id = str(uuid.uuid4())
repo.log_action(action="create", resource_type="documents", resource_id=resource_id)
repo.log_action(action="update", resource_type="documents", resource_id=resource_id)
logs = repo.get_resource_logs("documents", resource_id)
assert len(logs) >= 2
for log in logs:
assert log.resource_type == "documents"
assert log.resource_id == resource_id