From a85d18c43a9a9e34176f32a83a4f7f93fab5092f Mon Sep 17 00:00:00 2001 From: Denis Bezykornov Date: Sun, 1 Mar 2026 19:29:13 +0300 Subject: [PATCH 1/6] Remove default project and force doc uploading to specific project --- ...48_make_project_id_nullable_in_document.py | 32 +++ backend/app/documents/models.py | 4 +- backend/app/documents/query.py | 2 +- backend/app/documents/schema.py | 4 +- backend/app/routers/document.py | 3 +- backend/app/services/document_service.py | 17 +- backend/app/services/project_service.py | 28 +-- backend/tests/routers/test_routes_comments.py | 26 +++ .../tests/routers/test_routes_doc_records.py | 38 ++++ .../tests/routers/test_routes_documents.py | 194 +++++++++++++----- backend/tests/routers/test_routes_projects.py | 73 +------ .../routers/test_routes_segment_history.py | 26 +++ backend/tests/routers/test_tm_search.py | 12 ++ backend/tests/test_worker.py | 7 + 14 files changed, 311 insertions(+), 155 deletions(-) create mode 100644 backend/alembic/versions/35020a30cb48_make_project_id_nullable_in_document.py diff --git a/backend/alembic/versions/35020a30cb48_make_project_id_nullable_in_document.py b/backend/alembic/versions/35020a30cb48_make_project_id_nullable_in_document.py new file mode 100644 index 0000000..487d130 --- /dev/null +++ b/backend/alembic/versions/35020a30cb48_make_project_id_nullable_in_document.py @@ -0,0 +1,32 @@ +"""Make project_id nullable in document + +Revision ID: 35020a30cb48 +Revises: dcd734dfe9c4 +Create Date: 2026-03-01 15:05:44.684815 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# pylint: disable=E1101 + +# revision identifiers, used by Alembic. +revision: str = '35020a30cb48' +down_revision: Union[str, None] = 'dcd734dfe9c4' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.alter_column('document', 'project_id', + existing_type=sa.INTEGER(), + nullable=False) + + +def downgrade() -> None: + op.alter_column('document', 'project_id', + existing_type=sa.INTEGER(), + nullable=True) diff --git a/backend/app/documents/models.py b/backend/app/documents/models.py index 21d9afc..f324447 100644 --- a/backend/app/documents/models.py +++ b/backend/app/documents/models.py @@ -77,9 +77,7 @@ class Document(Base): created_by: Mapped[int] = mapped_column(ForeignKey("user.id")) processing_status: Mapped[str] = mapped_column() upload_time: Mapped[datetime] = mapped_column(default=utc_time) - project_id: Mapped[int | None] = mapped_column( - ForeignKey("projects.id"), nullable=True - ) + project_id: Mapped[int] = mapped_column(ForeignKey("projects.id")) records: Mapped[list["DocumentRecord"]] = relationship( back_populates="document", diff --git a/backend/app/documents/query.py b/backend/app/documents/query.py index f7b4a1e..603c183 100644 --- a/backend/app/documents/query.py +++ b/backend/app/documents/query.py @@ -212,7 +212,7 @@ def update_document( if name is not None: document.name = name if project_id is not None: - document.project_id = project_id if project_id != -1 else None + document.project_id = project_id self.__db.commit() self.__db.refresh(document) return document diff --git a/backend/app/documents/schema.py b/backend/app/documents/schema.py index 306ec55..4126b30 100644 --- a/backend/app/documents/schema.py +++ b/backend/app/documents/schema.py @@ -112,7 +112,7 @@ class DocumentUpdate(BaseModel): ) project_id: int | None = Field( default=None, - description="ID of project to assign document to. Set to -1 to unassign.", + description="ID of project to assign document to.", ) model_config = ConfigDict(from_attributes=True) @@ -121,7 +121,7 @@ class DocumentUpdate(BaseModel): class DocumentUpdateResponse(BaseModel): id: int name: str - project_id: int | None + project_id: int model_config = ConfigDict(from_attributes=True) diff --git a/backend/app/routers/document.py b/backend/app/routers/document.py index 24f64dc..e7b7d88 100644 --- a/backend/app/routers/document.py +++ b/backend/app/routers/document.py @@ -172,9 +172,10 @@ async def create_doc( file: Annotated[UploadFile, File()], service: Annotated[DocumentService, Depends(get_service)], current_user: Annotated[int, Depends(get_current_user_id)], + project_id: Annotated[int, Form()], ) -> doc_schema.Document: try: - return await service.create_document(file, current_user) + return await service.create_document(file, current_user, project_id) except EntityNotFound as e: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) except BusinessLogicError as e: diff --git a/backend/app/services/document_service.py b/backend/app/services/document_service.py index 265489e..94c0f4d 100644 --- a/backend/app/services/document_service.py +++ b/backend/app/services/document_service.py @@ -93,7 +93,7 @@ def get_document(self, doc_id: int) -> doc_schema.DocumentWithRecordsCount: ) async def create_document( - self, file: UploadFile, user_id: int + self, file: UploadFile, user_id: int, project_id: int ) -> doc_schema.Document: """ Create a new document from uploaded file. @@ -101,12 +101,13 @@ async def create_document( Args: file: Uploaded file user_id: ID of user creating the document + project_id: Optional ID of project to assign document to Returns: Created Document object Raises: - EntityNotFound: If file type is unsupported + EntityNotFound: If file type is unsupported or project not found """ cutoff_date = datetime.now() - timedelta(days=1) @@ -127,12 +128,20 @@ async def create_document( else: raise BusinessLogicError("Unsupported file type") + # Validate project_id if provided + try: + pq = ProjectQuery(self.__db) + pq._get_project(project_id) + except NotFoundProjectExc: + raise EntityNotFound("Project", project_id) + doc = Document( name=name, type=doc_type, processing_status=models.DocumentStatus.UPLOADED.value, upload_time=datetime.now(), created_by=user_id, + project_id=project_id, ) self.__query.add_document(doc, original_document) return doc_schema.Document( @@ -141,7 +150,7 @@ async def create_document( status=models.DocumentStatus(doc.processing_status), created_by=doc.created_by, type=doc.type.value, - project_id=None, + project_id=doc.project_id, ) def delete_document(self, doc_id: int) -> models.StatusMessage: @@ -638,7 +647,7 @@ def update_document( """ self._get_document_by_id(doc_id) try: - if update_data.project_id is not None and update_data.project_id != -1: + if update_data.project_id is not None: pq = ProjectQuery(self.__db) # verify project exists pq._get_project(update_data.project_id) diff --git a/backend/app/services/project_service.py b/backend/app/services/project_service.py index 57711a0..38a5650 100644 --- a/backend/app/services/project_service.py +++ b/backend/app/services/project_service.py @@ -35,14 +35,6 @@ def list_projects(self, user_id: int) -> list[ProjectResponse]: """ projects = self.__query.list_projects(user_id) return [ - ProjectResponse( - id=-1, - name="Unnamed project", - created_by=-1, - created_at=datetime.now(UTC), - updated_at=datetime.now(UTC), - ) - ] + [ ProjectResponse( id=project.id, name=project.name, @@ -69,24 +61,10 @@ def get_project(self, project_id: int, user_id: int) -> DetailedProjectResponse: UnauthorizedAccess: If user doesn't own the project """ try: - project = ( - self.__query._get_project(project_id) - if project_id != -1 - else Project( - id=-1, - name="Unnamed project", - created_by=-1, - created_at=datetime.now(UTC), - updated_at=datetime.now(UTC), - ) - ) + project = self.__query._get_project(project_id) self._check_ownership(project, user_id) - aggregates = self.__query.get_project_aggregates( - project_id if project_id != -1 else None - ) - documents = self.__query.get_project_documents( - project_id if project_id != -1 else None - ) + aggregates = self.__query.get_project_aggregates(project_id) + documents = self.__query.get_project_documents(project_id) def find_doc(doc_id: int): for aggregate in aggregates: diff --git a/backend/tests/routers/test_routes_comments.py b/backend/tests/routers/test_routes_comments.py index b86c3e4..bd0d27a 100644 --- a/backend/tests/routers/test_routes_comments.py +++ b/backend/tests/routers/test_routes_comments.py @@ -5,6 +5,8 @@ from app.comments.models import Comment from app.documents.models import Document, DocumentRecord, DocumentType +from app.projects.query import ProjectQuery +from app.projects.schema import ProjectCreate # pylint: disable=C0116 @@ -12,6 +14,7 @@ def test_can_get_comments_for_record(user_logged_client: TestClient, session: Session): """Test getting all comments for a document record""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) # Create document with records records = [ DocumentRecord(source="Hello World", target="Привет Мир"), @@ -24,6 +27,7 @@ def test_can_get_comments_for_record(user_logged_client: TestClient, session: Se records=records, processing_status="done", created_by=1, + project_id=p.id, ) ) @@ -63,6 +67,7 @@ def test_get_comments_returns_empty_for_no_comments( ): """Test getting comments for record with no comments""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [DocumentRecord(source="Hello World", target="Привет Мир")] s.add( Document( @@ -71,6 +76,7 @@ def test_get_comments_returns_empty_for_no_comments( records=records, processing_status="done", created_by=1, + project_id=p.id, ) ) s.commit() @@ -93,6 +99,7 @@ def test_get_comments_returns_404_for_nonexistent_record( def test_can_create_comment(user_logged_client: TestClient, session: Session): """Test creating a new comment""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [DocumentRecord(source="Hello World", target="Привет Мир")] s.add( Document( @@ -101,6 +108,7 @@ def test_can_create_comment(user_logged_client: TestClient, session: Session): records=records, processing_status="done", created_by=1, + project_id=p.id, ) ) s.commit() @@ -129,6 +137,7 @@ def test_create_comment_returns_404_for_nonexistent_record( def test_create_comment_requires_text(user_logged_client: TestClient, session: Session): """Test that creating comment requires text field""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [DocumentRecord(source="Hello World", target="Привет Мир")] s.add( Document( @@ -137,6 +146,7 @@ def test_create_comment_requires_text(user_logged_client: TestClient, session: S records=records, processing_status="done", created_by=1, + project_id=p.id, ) ) s.commit() @@ -150,6 +160,7 @@ def test_create_comment_requires_min_length_text( ): """Test that creating comment requires minimum length text""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [DocumentRecord(source="Hello World", target="Привет Мир")] s.add( Document( @@ -158,6 +169,7 @@ def test_create_comment_requires_min_length_text( records=records, processing_status="done", created_by=1, + project_id=p.id, ) ) s.commit() @@ -171,6 +183,7 @@ def test_create_comment_requires_authentication( ): """Test that creating comment requires authentication""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [DocumentRecord(source="Hello World", target="Привет Мир")] s.add( Document( @@ -179,6 +192,7 @@ def test_create_comment_requires_authentication( records=records, processing_status="done", created_by=1, + project_id=p.id, ) ) s.commit() @@ -191,6 +205,7 @@ def test_create_comment_requires_authentication( def test_can_update_own_comment(user_logged_client: TestClient, session: Session): """Test that user can update their own comment""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [DocumentRecord(source="Hello World", target="Привет Мир")] s.add( Document( @@ -199,6 +214,7 @@ def test_can_update_own_comment(user_logged_client: TestClient, session: Session records=records, processing_status="done", created_by=1, + project_id=p.id, ) ) @@ -223,6 +239,7 @@ def test_can_update_own_comment(user_logged_client: TestClient, session: Session def test_cannot_update_others_comment(user_logged_client: TestClient, session: Session): """Test that user cannot update another user's comment""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [DocumentRecord(source="Hello World", target="Привет Мир")] s.add( Document( @@ -231,6 +248,7 @@ def test_cannot_update_others_comment(user_logged_client: TestClient, session: S records=records, processing_status="done", created_by=1, + project_id=p.id, ) ) @@ -259,6 +277,7 @@ def test_cannot_update_others_comment(user_logged_client: TestClient, session: S def test_can_delete_own_comment(user_logged_client: TestClient, session: Session): """Test that user can delete their own comment""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [DocumentRecord(source="Hello World", target="Привет Мир")] s.add( Document( @@ -267,6 +286,7 @@ def test_can_delete_own_comment(user_logged_client: TestClient, session: Session records=records, processing_status="done", created_by=1, + project_id=p.id, ) ) @@ -294,6 +314,7 @@ def test_can_delete_own_comment(user_logged_client: TestClient, session: Session def test_cannot_delete_others_comment(user_logged_client: TestClient, session: Session): """Test that user cannot delete another user's comment""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [DocumentRecord(source="Hello World", target="Привет Мир")] s.add( Document( @@ -302,6 +323,7 @@ def test_cannot_delete_others_comment(user_logged_client: TestClient, session: S records=records, processing_status="done", created_by=1, + project_id=p.id, ) ) @@ -377,6 +399,7 @@ def test_admin_can_update_any_comment( ): """Test that admin can update any comment""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [DocumentRecord(source="Hello World", target="Привет Мир")] s.add( Document( @@ -385,6 +408,7 @@ def test_admin_can_update_any_comment( records=records, processing_status="done", created_by=1, + project_id=p.id, ) ) @@ -411,6 +435,7 @@ def test_admin_can_delete_any_comment( ): """Test that admin can delete any comment""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [DocumentRecord(source="Hello World", target="Привет Мир")] s.add( Document( @@ -419,6 +444,7 @@ def test_admin_can_delete_any_comment( records=records, processing_status="done", created_by=1, + project_id=p.id, ) ) diff --git a/backend/tests/routers/test_routes_doc_records.py b/backend/tests/routers/test_routes_doc_records.py index d20f01f..ba42d07 100644 --- a/backend/tests/routers/test_routes_doc_records.py +++ b/backend/tests/routers/test_routes_doc_records.py @@ -11,6 +11,8 @@ DocumentRecord, DocumentType, ) +from app.projects.query import ProjectQuery +from app.projects.schema import ProjectCreate from app.translation_memory.models import TranslationMemory, TranslationMemoryRecord # pylint: disable=C0116 @@ -18,6 +20,7 @@ def test_can_get_doc_records(user_logged_client: TestClient, session: Session): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord( source="Regional Effects", @@ -32,6 +35,7 @@ def test_can_get_doc_records(user_logged_client: TestClient, session: Session): records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -65,6 +69,7 @@ def test_doc_records_returns_second_page( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord( source=f"line{i}", @@ -80,6 +85,7 @@ def test_doc_records_returns_second_page( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -104,6 +110,7 @@ def test_doc_records_returns_empty_for_too_large_page( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord( source=f"line{i}", @@ -119,6 +126,7 @@ def test_doc_records_returns_empty_for_too_large_page( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -149,6 +157,7 @@ def test_can_update_doc_record( user_logged_client: TestClient, session: Session, arguments: dict[str, str] ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord( source="Regional Effects", @@ -166,6 +175,7 @@ def test_can_update_doc_record( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -193,6 +203,7 @@ def test_record_approving_creates_memory( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord(source="Some source", target=""), ] @@ -203,6 +214,7 @@ def test_record_approving_creates_memory( records=records, processing_status="done", created_by=1, + project_id=p.id, ) ) s.add( @@ -245,6 +257,7 @@ def test_record_approving_updates_memory( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord(source="Some source", target=""), ] @@ -255,6 +268,7 @@ def test_record_approving_updates_memory( records=records, processing_status="done", created_by=1, + project_id=p.id, ) ) tm_records = [ @@ -314,6 +328,7 @@ def test_returns_404_for_nonexistent_record( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) s.add( Document( name="test_doc.txt", @@ -321,6 +336,7 @@ def test_returns_404_for_nonexistent_record( records=[], processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -336,6 +352,7 @@ def test_can_update_doc_record_with_repetitions( ): """Test updating all records with the same source text""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord(source="Hello World", target="Привет Мир"), DocumentRecord(source="Hello World", target="Здравствуйте Мир"), @@ -348,6 +365,7 @@ def test_can_update_doc_record_with_repetitions( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -380,6 +398,7 @@ def test_update_repetitions_default_behavior( ): """Test that update_repetitions defaults to False when not specified""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord(source="Hello World", target="Привет Мир"), DocumentRecord(source="Hello World", target="Здравствуйте Мир"), @@ -391,6 +410,7 @@ def test_update_repetitions_default_behavior( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -421,6 +441,7 @@ def test_update_repetitions_default_behavior( def test_doc_records_source_filter(user_logged_client: TestClient, session: Session): """Test filtering document records by source text""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord(source="Hello World", target="Привет Мир"), DocumentRecord(source="Goodbye", target="Пока"), @@ -433,6 +454,7 @@ def test_doc_records_source_filter(user_logged_client: TestClient, session: Sess records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -456,6 +478,7 @@ def test_doc_records_source_filter(user_logged_client: TestClient, session: Sess def test_doc_records_target_filter(user_logged_client: TestClient, session: Session): """Test filtering document records by target text""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord(source="Hello World", target="Привет Мир"), DocumentRecord(source="Goodbye", target="Пока"), @@ -468,6 +491,7 @@ def test_doc_records_target_filter(user_logged_client: TestClient, session: Sess records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -493,6 +517,7 @@ def test_doc_records_target_filter(user_logged_client: TestClient, session: Sess def test_doc_records_both_filters(user_logged_client: TestClient, session: Session): """Test filtering document records by both source and target text""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord(source="Hello World", target="Привет Мир"), DocumentRecord(source="Goodbye", target="Пока"), @@ -505,6 +530,7 @@ def test_doc_records_both_filters(user_logged_client: TestClient, session: Sessi records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -534,6 +560,7 @@ def test_doc_records_no_filter_matches( ): """Test filtering with no matching results""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord(source="Hello World", target="Привет Мир"), DocumentRecord(source="Goodbye", target="Пока"), @@ -545,6 +572,7 @@ def test_doc_records_no_filter_matches( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -566,6 +594,7 @@ def test_doc_records_case_insensitive_filter( ): """Test that filtering is case insensitive""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord(source="Hello World", target="Привет Мир"), DocumentRecord(source="hello universe", target="Привет Вселенная"), @@ -577,6 +606,7 @@ def test_doc_records_case_insensitive_filter( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -600,6 +630,7 @@ def test_doc_records_filter_with_pagination( ): """Test that filtering works with pagination""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord(source=f"Hello {i}", target=f"Привет {i}") for i in range(10) ] @@ -610,6 +641,7 @@ def test_doc_records_filter_with_pagination( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -648,6 +680,7 @@ def test_update_repetitions_only_when_approved( ): """Test that repetitions are only updated when segment is approved, not just updated""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord(source="Hello World", target="Привет Мир"), DocumentRecord(source="Hello World", target="Здравствуйте Мир"), @@ -660,6 +693,7 @@ def test_update_repetitions_only_when_approved( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -710,6 +744,7 @@ def test_update_repetitions_only_when_approved( def test_has_comments_field(user_logged_client: TestClient, session: Session): """Test that has_comments field correctly shows if document record has comments""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) # Create document records records = [ DocumentRecord(source="Hello World", target="Привет мир"), @@ -723,6 +758,7 @@ def test_has_comments_field(user_logged_client: TestClient, session: Session): records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -772,6 +808,7 @@ def test_has_comments_with_multiple_comments( ): """Test that has_comments is True when record has multiple comments""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) # Create document record record = DocumentRecord(source="Test", target="Тест") s.add( @@ -781,6 +818,7 @@ def test_has_comments_with_multiple_comments( records=[record], processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() diff --git a/backend/tests/routers/test_routes_documents.py b/backend/tests/routers/test_routes_documents.py index 9b83681..18505dd 100644 --- a/backend/tests/routers/test_routes_documents.py +++ b/backend/tests/routers/test_routes_documents.py @@ -26,6 +26,8 @@ ) from app.models import DocumentStatus from app.projects.models import Project +from app.projects.query import ProjectQuery +from app.projects.schema import ProjectCreate from app.schema import DocumentTask from app.translation_memory.models import TranslationMemory @@ -34,6 +36,7 @@ def test_can_get_document(user_logged_client: TestClient, session: Session): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord( source="Regional Effects", @@ -53,6 +56,7 @@ def test_can_get_document(user_logged_client: TestClient, session: Session): records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -69,7 +73,7 @@ def test_can_get_document(user_logged_client: TestClient, session: Session): "type": "txt", "approved_word_count": 0, "total_word_count": 4, - "project_id": None, + "project_id": 1, } @@ -80,6 +84,7 @@ def test_returns_404_when_doc_not_found(user_logged_client: TestClient): def test_can_delete_xliff_doc(user_logged_client: TestClient, session: Session): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) s.add( XliffDocument( parent_id=1, @@ -92,6 +97,7 @@ def test_can_delete_xliff_doc(user_logged_client: TestClient, session: Session): type=DocumentType.txt, processing_status="waiting", created_by=1, + project_id=p.id, ) ) s.commit() @@ -107,6 +113,7 @@ def test_can_delete_xliff_doc(user_logged_client: TestClient, session: Session): def test_can_delete_txt_doc(user_logged_client: TestClient, session: Session): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) s.add( TxtDocument( parent_id=1, @@ -119,6 +126,7 @@ def test_can_delete_txt_doc(user_logged_client: TestClient, session: Session): type=DocumentType.txt, processing_status="waiting", created_by=1, + project_id=p.id, ) ) s.commit() @@ -140,8 +148,13 @@ def test_returns_404_when_deleting_nonexistent_doc( def test_upload_xliff(user_logged_client: TestClient, session: Session): + with session as s: + ProjectQuery(s).create_project(1, ProjectCreate(name="test")) + with open("tests/fixtures/small.xliff", "rb") as fp: - response = user_logged_client.post("/document/", files={"file": fp}) + response = user_logged_client.post( + "/document/", files={"file": fp}, data={"project_id": "1"} + ) assert response.status_code == 200 with session as s: @@ -161,8 +174,13 @@ def test_upload_xliff(user_logged_client: TestClient, session: Session): def test_upload_txt(user_logged_client: TestClient, session: Session): + with session as s: + ProjectQuery(s).create_project(1, ProjectCreate(name="test")) + with open("tests/fixtures/small.txt", "rb") as fp: - response = user_logged_client.post("/document/", files={"file": fp}) + response = user_logged_client.post( + "/document/", files={"file": fp}, data={"project_id": "1"} + ) assert response.status_code == 200 with session as s: @@ -187,14 +205,22 @@ def test_upload_no_file(user_logged_client: TestClient): assert response.status_code == 422 -def test_upload_fails_with_unknown_type(user_logged_client: TestClient): +def test_upload_fails_with_unknown_type( + user_logged_client: TestClient, session: Session +): + with session as s: + ProjectQuery(s).create_project(1, ProjectCreate(name="test")) + with open("tests/fixtures/small.tmx", "rb") as fp: - response = user_logged_client.post("/document/", files={"file": fp}) + response = user_logged_client.post( + "/document/", files={"file": fp}, data={"project_id": "1"} + ) assert response.status_code == 400 def test_upload_removes_old_files(user_logged_client: TestClient, session: Session): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) s.add( Document( name="some_doc.txt", @@ -202,12 +228,15 @@ def test_upload_removes_old_files(user_logged_client: TestClient, session: Sessi processing_status=DocumentStatus.UPLOADED.value, upload_time=(datetime.now() - timedelta(days=2)), created_by=1, + project_id=p.id, ) ) s.commit() with open("tests/fixtures/small.txt", "rb") as fp: - response = user_logged_client.post("/document/", files={"file": fp}) + response = user_logged_client.post( + "/document/", files={"file": fp}, data={"project_id": "1"} + ) assert response.status_code == 200 with session as s: @@ -218,6 +247,7 @@ def test_upload_removes_only_uploaded_documents( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) s.add( Document( name="uploaded_doc.txt", @@ -225,6 +255,7 @@ def test_upload_removes_only_uploaded_documents( processing_status=DocumentStatus.UPLOADED.value, upload_time=(datetime.now() - timedelta(days=2)), created_by=1, + project_id=p.id, ) ) s.add( @@ -234,12 +265,15 @@ def test_upload_removes_only_uploaded_documents( processing_status=DocumentStatus.DONE.value, upload_time=(datetime.now() - timedelta(days=2)), created_by=1, + project_id=p.id, ) ) s.commit() with open("tests/fixtures/small.txt", "rb") as fp: - response = user_logged_client.post("/document/", files={"file": fp}) + response = user_logged_client.post( + "/document/", files={"file": fp}, data={"project_id": "1"} + ) assert response.status_code == 200 with session as s: @@ -250,8 +284,13 @@ def test_upload_removes_only_uploaded_documents( def test_process_sets_document_in_pending_stage_and_creates_task_xliff( user_logged_client: TestClient, session: Session ): + with session as s: + ProjectQuery(s).create_project(1, ProjectCreate(name="test")) + with open("tests/fixtures/small.xliff", "rb") as fp: - user_logged_client.post("/document/", files={"file": fp}) + user_logged_client.post( + "/document/", files={"file": fp}, data={"project_id": "1"} + ) response = user_logged_client.post( "/document/1/process", @@ -270,8 +309,13 @@ def test_process_sets_document_in_pending_stage_and_creates_task_xliff( def test_process_sets_document_in_pending_stage_and_creates_task_txt( user_logged_client: TestClient, session: Session ): + with session as s: + ProjectQuery(s).create_project(1, ProjectCreate(name="test")) + with open("tests/fixtures/small.txt", "rb") as fp: - user_logged_client.post("/document/", files={"file": fp}) + user_logged_client.post( + "/document/", files={"file": fp}, data={"project_id": "1"} + ) response = user_logged_client.post( "/document/1/process", @@ -290,8 +334,13 @@ def test_process_sets_document_in_pending_stage_and_creates_task_txt( def test_process_creates_task_for_xliff( user_logged_client: TestClient, session: Session ): + with session as s: + ProjectQuery(s).create_project(1, ProjectCreate(name="test")) + with open("tests/fixtures/small.xliff", "rb") as fp: - user_logged_client.post("/document/", files={"file": fp}) + user_logged_client.post( + "/document/", files={"file": fp}, data={"project_id": "1"} + ) response = user_logged_client.post( "/document/1/process", @@ -317,8 +366,13 @@ def test_process_creates_task_for_xliff( def test_process_creates_task_for_txt(user_logged_client: TestClient, session: Session): + with session as s: + ProjectQuery(s).create_project(1, ProjectCreate(name="test")) + with open("tests/fixtures/small.txt", "rb") as fp: - user_logged_client.post("/document/", files={"file": fp}) + user_logged_client.post( + "/document/", files={"file": fp}, data={"project_id": "1"} + ) response = user_logged_client.post( "/document/1/process", @@ -356,8 +410,13 @@ def test_returns_404_when_processing_nonexistent_doc( def test_download_xliff_doc(user_logged_client: TestClient, session: Session): + with session as s: + ProjectQuery(s).create_project(1, ProjectCreate(name="test")) + with open("tests/fixtures/small.xliff", "rb") as fp: - user_logged_client.post("/document/", files={"file": fp}) + user_logged_client.post( + "/document/", files={"file": fp}, data={"project_id": "1"} + ) with session as s: records = [ @@ -416,8 +475,13 @@ def test_download_xliff_doc(user_logged_client: TestClient, session: Session): def test_download_txt_doc(user_logged_client: TestClient, session: Session): + with session as s: + ProjectQuery(s).create_project(1, ProjectCreate(name="test")) + with open("tests/fixtures/small.txt", "rb") as fp: - user_logged_client.post("/document/", files={"file": fp}) + user_logged_client.post( + "/document/", files={"file": fp}, data={"project_id": "1"} + ) with session as s: txt_records = [ @@ -477,12 +541,14 @@ def test_download_shows_404_for_unknown_doc(user_logged_client: TestClient): def test_returns_linked_tms(user_logged_client: TestClient, session: Session): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) s.add( Document( name="small.xliff", type=DocumentType.xliff, created_by=1, processing_status="UPLOADED", + project_id=p.id, ) ) s.add(TranslationMemory(name="first_doc.tmx", created_by=1)) @@ -509,12 +575,14 @@ def test_returns_linked_tms(user_logged_client: TestClient, session: Session): def test_sets_new_linked_tms(user_logged_client: TestClient, session: Session): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) s.add( Document( name="small.xliff", type=DocumentType.xliff, created_by=1, processing_status="UPLOADED", + project_id=p.id, ) ) s.add(TranslationMemory(name="first_doc.tmx", created_by=1)) @@ -544,12 +612,14 @@ def test_new_linked_tms_work_with_duplicated_ids( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) s.add( Document( name="small.xliff", type=DocumentType.xliff, created_by=1, processing_status="UPLOADED", + project_id=p.id, ) ) s.add(TranslationMemory(name="first_doc.tmx", created_by=1)) @@ -589,12 +659,14 @@ def test_new_linked_tms_replaces_old_ones( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) s.add( Document( name="small.xliff", type=DocumentType.xliff, created_by=1, processing_status="UPLOADED", + project_id=p.id, ) ) s.add(TranslationMemory(name="first_doc.tmx", created_by=1)) @@ -621,12 +693,14 @@ def test_set_linked_tms_fail_if_not_exists( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) s.add( Document( name="small.xliff", type=DocumentType.xliff, created_by=1, processing_status="UPLOADED", + project_id=p.id, ) ) s.commit() @@ -642,6 +716,7 @@ def test_set_linked_tms_fail_with_multiple_writes( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) s.add_all( [ Document( @@ -649,6 +724,7 @@ def test_set_linked_tms_fail_with_multiple_writes( type=DocumentType.xliff, created_by=1, processing_status="UPLOADED", + project_id=p.id, ), TranslationMemory(name="first_doc.tmx", created_by=1), TranslationMemory(name="another_doc.tmx", created_by=1), @@ -668,6 +744,7 @@ def test_can_get_glossaries_substitutions( ): dq = GenericDocsQuery(session) with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord( source="Regional Effects", @@ -685,6 +762,7 @@ def test_can_get_glossaries_substitutions( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -715,6 +793,7 @@ def test_glossary_substitution_returns_404_for_non_existent_record( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord( source="Regional Effects", @@ -732,6 +811,7 @@ def test_glossary_substitution_returns_404_for_non_existent_record( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -742,6 +822,7 @@ def test_glossary_substitution_returns_404_for_non_existent_record( def test_can_get_linked_glossaries(user_logged_client: TestClient, session: Session): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord( source="Regional Effects", @@ -759,6 +840,7 @@ def test_can_get_linked_glossaries(user_logged_client: TestClient, session: Sess records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -795,12 +877,14 @@ def test_can_set_glossaries_for_document( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) s.add( Document( name="small.xliff", type=DocumentType.xliff, created_by=1, processing_status="UPLOADED", + project_id=p.id, ) ) s.commit() @@ -836,12 +920,14 @@ def test_setting_glossaries_returns_404_for_non_existing_glossaries( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) s.add( Document( name="small.xliff", type=DocumentType.xliff, created_by=1, processing_status="UPLOADED", + project_id=p.id, ) ) s.commit() @@ -861,6 +947,7 @@ def test_get_doc_records_with_repetitions( ): """Test that document records endpoint returns repetition counts""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord(source="Hello World", target="Привет Мир"), DocumentRecord(source="Goodbye", target="Пока"), @@ -875,6 +962,7 @@ def test_get_doc_records_with_repetitions( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -901,6 +989,7 @@ def test_doc_glossary_search_with_matching_records( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord(source="Regional Effects", target=""), DocumentRecord(source="User Interface", target=""), @@ -912,6 +1001,7 @@ def test_doc_glossary_search_with_matching_records( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -953,6 +1043,7 @@ def test_doc_glossary_search_returns_empty_when_no_glossaries( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord(source="Regional Effects", target=""), ] @@ -963,6 +1054,7 @@ def test_doc_glossary_search_returns_empty_when_no_glossaries( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -978,6 +1070,7 @@ def test_doc_glossary_search_returns_empty_when_no_matches( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord(source="Regional Effects", target=""), ] @@ -988,6 +1081,7 @@ def test_doc_glossary_search_returns_empty_when_no_matches( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -1021,11 +1115,13 @@ def test_doc_glossary_search_returns_404_for_nonexistent_document( def test_update_document_name_only(user_logged_client: TestClient, session: Session): """Test successful update of document name only.""" + p = ProjectQuery(session).create_project(1, ProjectCreate(name="test")) doc = Document( name="original.txt", type=DocumentType.txt, processing_status="done", created_by=1, + project_id=p.id, ) session.add(doc) session.commit() @@ -1037,22 +1133,24 @@ def test_update_document_name_only(user_logged_client: TestClient, session: Sess response_json = response.json() assert response_json["id"] == doc.id assert response_json["name"] == "updated.txt" - assert response_json["project_id"] is None + assert response_json["project_id"] == 1 with session as s: updated_doc = s.query(Document).filter_by(id=doc.id).first() assert updated_doc is not None assert updated_doc.name == "updated.txt" - assert updated_doc.project_id is None + assert updated_doc.project_id == 1 def test_update_document_project_only(user_logged_client: TestClient, session: Session): """Test successful update of document project_id only.""" + p = ProjectQuery(session).create_project(1, ProjectCreate(name="test")) doc = Document( name="document.txt", type=DocumentType.txt, processing_status="done", created_by=1, + project_id=p.id, ) project = Project(created_by=1, name="Test Project") session.add(doc) @@ -1079,11 +1177,13 @@ def test_update_document_name_and_project( user_logged_client: TestClient, session: Session ): """Test successful update of both name and project_id.""" + p = ProjectQuery(session).create_project(1, ProjectCreate(name="test")) doc = Document( name="original.txt", type=DocumentType.txt, processing_status="done", created_by=1, + project_id=p.id, ) project = Project(created_by=1, name="Test Project") session.add(doc) @@ -1107,36 +1207,7 @@ def test_update_document_name_and_project( assert updated_doc.project_id == project_id -def test_unassign_document_from_project( - user_logged_client: TestClient, session: Session -): - """Test successful unassignment of document from project.""" - doc = Document( - name="document.txt", - type=DocumentType.txt, - processing_status="done", - created_by=1, - project_id=1, - ) - project = Project(created_by=1, name="Test Project") - session.add(doc) - session.add(project) - session.commit() - - response = user_logged_client.put(f"/document/{doc.id}", json={"project_id": -1}) - assert response.status_code == 200 - response_json = response.json() - assert response_json["id"] == doc.id - assert response_json["name"] == "document.txt" - assert response_json["project_id"] is None - - with session as s: - updated_doc = s.query(Document).filter_by(id=doc.id).first() - assert updated_doc is not None - assert updated_doc.project_id is None - - -def test_update_document_not_found(user_logged_client: TestClient, session: Session): +def test_update_document_not_found(user_logged_client: TestClient): """Test 404 when document doesn't exist.""" response = user_logged_client.put("/document/999", json={"name": "updated.txt"}) assert response.status_code == 404 @@ -1145,11 +1216,13 @@ def test_update_document_not_found(user_logged_client: TestClient, session: Sess def test_update_project_not_found(user_logged_client: TestClient, session: Session): """Test 404 when project doesn't exist.""" + p = ProjectQuery(session).create_project(1, ProjectCreate(name="test")) doc = Document( name="document.txt", type=DocumentType.txt, processing_status="done", created_by=1, + project_id=p.id, ) session.add(doc) session.commit() @@ -1162,12 +1235,14 @@ def test_update_project_not_found(user_logged_client: TestClient, session: Sessi def test_update_document_validation_error( user_logged_client: TestClient, session: Session ): - """Test 422 for invalid project_id (negative, zero) or invalid name.""" + """Test 422 for invalid project_id (zero) or invalid name.""" + p = ProjectQuery(session).create_project(1, ProjectCreate(name="test")) doc = Document( name="document.txt", type=DocumentType.txt, processing_status="done", created_by=1, + project_id=p.id, ) session.add(doc) session.commit() @@ -1192,11 +1267,13 @@ def test_update_document_validation_error( def test_update_document_unauthenticated(fastapi_client: TestClient, session: Session): """Test 401 when user is not authenticated.""" + p = ProjectQuery(session).create_project(1, ProjectCreate(name="test")) doc = Document( name="document.txt", type=DocumentType.txt, processing_status="done", created_by=1, + project_id=p.id, ) session.add(doc) session.commit() @@ -1209,12 +1286,13 @@ def test_update_document_to_same_project( user_logged_client: TestClient, session: Session ): """Test idempotent update to same project.""" + p = ProjectQuery(session).create_project(1, ProjectCreate(name="test")) doc = Document( name="document.txt", type=DocumentType.txt, processing_status="done", created_by=1, - project_id=1, + project_id=p.id, ) project = Project(created_by=1, name="Test Project") session.add(doc) @@ -1239,8 +1317,13 @@ def test_update_document_to_same_project( def test_download_original_xliff_doc(user_logged_client: TestClient, session: Session): """Test downloading original XLIFF document.""" + with session as s: + ProjectQuery(s).create_project(1, ProjectCreate(name="test")) + with open("tests/fixtures/small.xliff", "rb") as fp: - user_logged_client.post("/document/", files={"file": fp}) + user_logged_client.post( + "/document/", files={"file": fp}, data={"project_id": "1"} + ) response = user_logged_client.get("/document/1/download_original") assert response.status_code == 200 @@ -1252,8 +1335,13 @@ def test_download_original_xliff_doc(user_logged_client: TestClient, session: Se def test_download_original_txt_doc(user_logged_client: TestClient, session: Session): """Test downloading original TXT document.""" + with session as s: + ProjectQuery(s).create_project(1, ProjectCreate(name="test")) + with open("tests/fixtures/small.txt", "rb") as fp: - user_logged_client.post("/document/", files={"file": fp}) + user_logged_client.post( + "/document/", files={"file": fp}, data={"project_id": "1"} + ) response = user_logged_client.get("/document/1/download_original") assert response.status_code == 200 @@ -1272,6 +1360,7 @@ def test_download_original_shows_404_for_unknown_doc(user_logged_client: TestCli def test_download_xliff(user_logged_client: TestClient, session: Session): """Test downloading document as XLIFF.""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord( source="Regional Effects", @@ -1291,6 +1380,7 @@ def test_download_xliff(user_logged_client: TestClient, session: Session): records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -1315,6 +1405,7 @@ def test_download_xliff_shows_404_for_unknown_doc(user_logged_client: TestClient def test_upload_xliff_success(user_logged_client: TestClient, session: Session): """Test successful XLIFF upload with record updates.""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord( source="Regional Effects", @@ -1349,6 +1440,7 @@ def test_upload_xliff_success(user_logged_client: TestClient, session: Session): records=records, processing_status="done", created_by=1, + project_id=p.id, ) ) s.commit() @@ -1406,6 +1498,7 @@ def test_upload_xliff_with_update_approved( ): """Test XLIFF upload with update_approved=True to update approved records.""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord( source="Regional Effects", @@ -1425,6 +1518,7 @@ def test_upload_xliff_with_update_approved( records=records, processing_status="done", created_by=1, + project_id=p.id, ) ) s.commit() @@ -1472,6 +1566,7 @@ def test_upload_xliff_history_tracking( ): """Test that history entries are created for XLIFF upload.""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord( id=1, @@ -1493,6 +1588,7 @@ def test_upload_xliff_history_tracking( records=records, processing_status="done", created_by=1, + project_id=p.id, ) ) s.commit() diff --git a/backend/tests/routers/test_routes_projects.py b/backend/tests/routers/test_routes_projects.py index 462abdb..e23c44d 100644 --- a/backend/tests/routers/test_routes_projects.py +++ b/backend/tests/routers/test_routes_projects.py @@ -46,10 +46,9 @@ def test_list_projects(user_logged_client: TestClient, session: Session): response_json = response.json() assert response.status_code == status.HTTP_200_OK - assert len(response_json) == 3 - assert response_json[0]["name"] == "Unnamed project" - assert response_json[1]["name"] == project_1.name - assert response_json[2]["name"] == project_2.name + assert len(response_json) == 2 + assert response_json[0]["name"] == project_1.name + assert response_json[1]["name"] == project_2.name def test_retrieve_project(user_logged_client: TestClient, session: Session): @@ -242,69 +241,3 @@ def test_retrieve_project_project_with_documents_no_records( assert project_data["total_records_count"] == 0 assert project_data["approved_words_count"] == 0 assert project_data["total_words_count"] == 0 - - -def test_retrieve_unnamed_project(user_logged_client: TestClient, session: Session): - with session as s: - project = Project(created_by=1, name="Test Project") - s.add(project) - s.flush() - project_id = project.id - - doc1 = Document( - name="doc.txt", - type=DocumentType.txt, - processing_status="done", - created_by=1, - project_id=project_id, - records=[ - DocumentRecord( - source="Hello", target="Привет", approved=True, word_count=10 - ), - DocumentRecord( - source="World", target="Мир", approved=False, word_count=10 - ), - DocumentRecord( - source="Test", target="Тест", approved=True, word_count=20 - ), - ], - ) - doc2 = Document( - name="doc.txt", - type=DocumentType.txt, - processing_status="done", - created_by=1, - project_id=None, - records=[ - DocumentRecord( - source="Hello", target="Привет", approved=True, word_count=1 - ), - DocumentRecord( - source="World", target="Мир", approved=False, word_count=1 - ), - DocumentRecord( - source="Test", target="Тест", approved=True, word_count=2 - ), - ], - ) - - s.add(doc1) - s.add(doc2) - s.commit() - - response = user_logged_client.get("/projects/-1") - assert response.status_code == status.HTTP_200_OK - response_json = response.json() - - assert response_json["id"] == -1 - assert response_json["name"] == "Unnamed project" - # 2 approved records (Hello, Test) - assert response_json["approved_records_count"] == 2 - # 3 total records (Hello, World, Test) - assert response_json["total_records_count"] == 3 - # 3 approved words (1 + 2) - assert response_json["approved_words_count"] == 3 - # 4 total words (1 + 1 + 2) - assert response_json["total_words_count"] == 4 - assert len(response_json["documents"]) == 1 - assert response_json["documents"][0]["id"] == 2 diff --git a/backend/tests/routers/test_routes_segment_history.py b/backend/tests/routers/test_routes_segment_history.py index ee8fc64..6711ec0 100644 --- a/backend/tests/routers/test_routes_segment_history.py +++ b/backend/tests/routers/test_routes_segment_history.py @@ -15,10 +15,13 @@ DocumentType, ) from app.documents.utils import apply_diff, compute_diff +from app.projects.query import ProjectQuery +from app.projects.schema import ProjectCreate def test_get_segment_history_empty(user_logged_client: TestClient, session: Session): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord(source="Hello World", target="Test translation"), ] @@ -29,6 +32,7 @@ def test_get_segment_history_empty(user_logged_client: TestClient, session: Sess records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -43,6 +47,7 @@ def test_get_segment_history_with_entries( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord(source="Hello World", target="Test translation"), ] @@ -53,6 +58,7 @@ def test_get_segment_history_with_entries( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.flush() @@ -119,6 +125,7 @@ def test_update_record_creates_history( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) s.add( Document( name="test_doc.txt", @@ -128,6 +135,7 @@ def test_update_record_creates_history( ], processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -159,6 +167,7 @@ def test_update_record_with_repetitions_creates_history( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) s.add( Document( name="test_doc.txt", @@ -170,6 +179,7 @@ def test_update_record_with_repetitions_creates_history( ], processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -215,6 +225,7 @@ def test_update_same_type_updates_in_place( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord( source="Hello World", target="Test translation", approved=False @@ -227,6 +238,7 @@ def test_update_same_type_updates_in_place( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.flush() @@ -274,6 +286,7 @@ def test_update_different_type_creates_new_history( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord( source="Hello World", target="Test translation", approved=True @@ -286,6 +299,7 @@ def test_update_different_type_creates_new_history( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.flush() @@ -330,6 +344,7 @@ def test_history_cascade_delete_on_record_delete( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord(source="Hello World", target="Test translation"), ] @@ -339,6 +354,7 @@ def test_history_cascade_delete_on_record_delete( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) s.add(doc) s.flush() @@ -373,6 +389,7 @@ def test_history_cascade_delete_on_record_delete( def test_no_history_for_same_text(user_logged_client: TestClient, session: Session): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord(source="Hello World", target="Same text"), ] @@ -383,6 +400,7 @@ def test_no_history_for_same_text(user_logged_client: TestClient, session: Sessi records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -412,6 +430,7 @@ def test_history_ordering_by_timestamp( user_logged_client: TestClient, session: Session ): with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord(source="Hello World", target="Test translation"), ] @@ -422,6 +441,7 @@ def test_history_ordering_by_timestamp( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.flush() @@ -476,6 +496,7 @@ def test_merge_diffs_correctly_merges_consecutive_changes( ): """Test that multiple consecutive changes by the same author are properly merged.""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord( source="Hello World", target="Test translation", approved=False @@ -488,6 +509,7 @@ def test_merge_diffs_correctly_merges_consecutive_changes( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -556,6 +578,7 @@ def test_merge_diffs_with_insert_only_operations( ): """Test that merging works correctly when only insert operations are involved.""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord(source="Hello", target="Hi", approved=False), ] @@ -566,6 +589,7 @@ def test_merge_diffs_with_insert_only_operations( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -612,6 +636,7 @@ def test_merge_diffs_with_multiple_history_records( ): """Test that multiple consecutive changes by the same author are properly merged.""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) records = [ DocumentRecord(source="Hello World", target="Replacement", approved=False), ] @@ -622,6 +647,7 @@ def test_merge_diffs_with_multiple_history_records( records=records, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() diff --git a/backend/tests/routers/test_tm_search.py b/backend/tests/routers/test_tm_search.py index 3c04eb0..2180b8e 100644 --- a/backend/tests/routers/test_tm_search.py +++ b/backend/tests/routers/test_tm_search.py @@ -7,6 +7,8 @@ DocumentType, TmMode, ) +from app.projects.query import ProjectQuery +from app.projects.schema import ProjectCreate from app.translation_memory.models import TranslationMemory, TranslationMemoryRecord @@ -15,12 +17,14 @@ def test_search_tm_exact_with_no_linked_memories( ): """Test exact search returns empty response when document has no linked TMs""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) s.add( Document( name="test_doc.txt", type=DocumentType.txt, processing_status="pending", created_by=1, + project_id=p.id, ) ) s.commit() @@ -38,6 +42,7 @@ def test_search_tm_exact_with_linked_memories( ): """Test exact search finds matches in linked translation memories""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) # Create document s.add( Document( @@ -45,6 +50,7 @@ def test_search_tm_exact_with_linked_memories( type=DocumentType.txt, processing_status="pending", created_by=1, + project_id=p.id, ) ) @@ -99,6 +105,7 @@ def test_search_tm_exact_with_multiple_linked_memories( ): """Test exact search across multiple linked translation memories""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) # Create document s.add( Document( @@ -106,6 +113,7 @@ def test_search_tm_exact_with_multiple_linked_memories( type=DocumentType.txt, processing_status="pending", created_by=1, + project_id=p.id, ) ) @@ -161,6 +169,7 @@ def test_search_tm_exact_returns_404_for_nonexistent_document( def test_search_tm_exact_no_results(user_logged_client: TestClient, session: Session): """Test exact search returns no results when no matches found""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) # Create document s.add( Document( @@ -168,6 +177,7 @@ def test_search_tm_exact_no_results(user_logged_client: TestClient, session: Ses type=DocumentType.txt, processing_status="pending", created_by=1, + project_id=p.id, ) ) @@ -200,6 +210,7 @@ def test_search_tm_exact_no_results(user_logged_client: TestClient, session: Ses def test_search_tm_limit_20_results(user_logged_client: TestClient, session: Session): """Test that search endpoints limit results to 20 records""" with session as s: + p = ProjectQuery(s).create_project(1, ProjectCreate(name="test")) # Create document s.add( Document( @@ -207,6 +218,7 @@ def test_search_tm_limit_20_results(user_logged_client: TestClient, session: Ses type=DocumentType.txt, processing_status="pending", created_by=1, + project_id=p.id, ) ) diff --git a/backend/tests/test_worker.py b/backend/tests/test_worker.py index 777f8ce..10781ce 100644 --- a/backend/tests/test_worker.py +++ b/backend/tests/test_worker.py @@ -26,6 +26,7 @@ DocumentStatus, YandexTranslatorSettings, ) +from app.projects.models import Project from app.schema import DocumentTask from app.translation_memory.models import TranslationMemory, TranslationMemoryRecord from worker import process_task @@ -42,6 +43,7 @@ def create_doc(name: str, type_: DocumentType): name=name, type=type_, created_by=1, + project_id=1, processing_status=DocumentStatus.PENDING.value, upload_time=datetime.now(), ) @@ -97,6 +99,7 @@ def test_process_task_sets_xliff_records(session: Session): ], created_by=1, ), + Project(name="test", created_by=1), create_doc(name="small.xliff", type_=DocumentType.xliff), create_xliff_doc(file_data), DocMemoryAssociation(doc_id=1, tm_id=1, mode="read"), @@ -222,6 +225,7 @@ def test_process_task_sets_txt_records(session: Session): ], created_by=1, ), + Project(name="test", created_by=1), create_doc(name="small.txt", type_=DocumentType.txt), TxtDocument(parent_id=1, original_document=file_data), DocMemoryAssociation(doc_id=1, tm_id=1, mode="read"), @@ -359,6 +363,7 @@ def test_process_task_uses_correct_tm_ids(session: Session): [ TranslationMemory(name="test1", records=tm_records_1, created_by=1), TranslationMemory(name="test2", records=tm_records_2, created_by=1), + Project(name="test", created_by=1), create_doc(name="small.xliff", type_=DocumentType.xliff), create_xliff_doc(file_data), create_task(), @@ -433,6 +438,7 @@ def test_process_task_puts_doc_in_error_state(monkeypatch, session: Session): with session as s: s.add_all( [ + Project(name="test", created_by=1), create_doc(name="small.xliff", type_=DocumentType.xliff), create_xliff_doc(file_data), create_task( @@ -484,6 +490,7 @@ def test_process_task_uses_correct_glossary_ids(session: Session): [ Glossary(name="test1", created_by=1, records=glossary_records_1), Glossary(name="test2", created_by=1, records=glossary_records_2), + Project(name="test", created_by=1), create_doc(name="small.xliff", type_=DocumentType.xliff), create_xliff_doc(file_data), create_task(), From 5de17d0bfd0b9d70988c93b065b25cba0d642b78 Mon Sep 17 00:00:00 2001 From: Denis Bezykornov Date: Sun, 1 Mar 2026 19:29:20 +0300 Subject: [PATCH 2/6] Generate client --- frontend/src/client/schemas/Body_create_doc_document__post.ts | 1 + frontend/src/client/schemas/DocumentUpdateResponse.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/client/schemas/Body_create_doc_document__post.ts b/frontend/src/client/schemas/Body_create_doc_document__post.ts index 0896cb9..957463a 100644 --- a/frontend/src/client/schemas/Body_create_doc_document__post.ts +++ b/frontend/src/client/schemas/Body_create_doc_document__post.ts @@ -2,4 +2,5 @@ export interface Body_create_doc_document__post { file: Blob + project_id: number } diff --git a/frontend/src/client/schemas/DocumentUpdateResponse.ts b/frontend/src/client/schemas/DocumentUpdateResponse.ts index e843b3d..6b3b35b 100644 --- a/frontend/src/client/schemas/DocumentUpdateResponse.ts +++ b/frontend/src/client/schemas/DocumentUpdateResponse.ts @@ -3,5 +3,5 @@ export interface DocumentUpdateResponse { id: number name: string - project_id: number | null + project_id: number } From 918d228111765304ad614ff8cddde482495d12cc Mon Sep 17 00:00:00 2001 From: Denis Bezykornov Date: Sun, 1 Mar 2026 19:30:33 +0300 Subject: [PATCH 3/6] Upload document to the specific project --- frontend/src/components/AddDocumentModal.vue | 5 ++++ .../src/components/DocUploadingDialog.vue | 16 ++++++++----- frontend/src/components/ProjectList.vue | 2 ++ frontend/src/components/ProjectListItem.vue | 12 +++++++++- frontend/src/views/IndexView.vue | 23 +++++++++++-------- 5 files changed, 42 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/AddDocumentModal.vue b/frontend/src/components/AddDocumentModal.vue index 547738a..672f8d8 100644 --- a/frontend/src/components/AddDocumentModal.vue +++ b/frontend/src/components/AddDocumentModal.vue @@ -3,6 +3,10 @@ import {Dialog} from 'primevue' import {useRouter} from 'vue-router' import DocUploadingDialog from './DocUploadingDialog.vue' +defineProps<{ + projectId: number +}>() + const visible = defineModel({required: true}) const router = useRouter() @@ -17,6 +21,7 @@ const router = useRouter() diff --git a/frontend/src/components/ProjectList.vue b/frontend/src/components/ProjectList.vue index 3783908..f61beb7 100644 --- a/frontend/src/components/ProjectList.vue +++ b/frontend/src/components/ProjectList.vue @@ -7,6 +7,7 @@ const {data: projects, status: projectsStatus} = useProjects() defineEmits<{ openSettings: [number] + uploadDocument: [number] }>() @@ -19,6 +20,7 @@ defineEmits<{ :key="project.name" :project="project" @open-settings="(docId) => $emit('openSettings', docId)" + @upload-document="(projectId) => $emit('uploadDocument', projectId)" /> diff --git a/frontend/src/components/ProjectListItem.vue b/frontend/src/components/ProjectListItem.vue index 4012fac..bcb7928 100644 --- a/frontend/src/components/ProjectListItem.vue +++ b/frontend/src/components/ProjectListItem.vue @@ -1,7 +1,7 @@