diff --git a/exceptions.py b/blog_exceptions.py similarity index 100% rename from exceptions.py rename to blog_exceptions.py diff --git a/config/env_config.py b/config/env_config.py index 93c29f8..69a3a70 100644 --- a/config/env_config.py +++ b/config/env_config.py @@ -28,9 +28,9 @@ def _get_env(self, name: str) -> str: Raises: RuntimeError: If the mandatory environment variable is missing. """ - # Intentionally NOT in exceptions.py: startup-only crash path. + # Intentionally NOT in blog_exceptions.py: startup-only crash path. # Never caught by application code. Builtin RuntimeError sufficient. - # Do not move to exceptions.py. + # Do not move to blog_exceptions.py. value = os.getenv(name) if not value: raise RuntimeError( diff --git a/src/application/services/article_service.py b/src/application/services/article_service.py index 43dd41c..cf5c1cb 100644 --- a/src/application/services/article_service.py +++ b/src/application/services/article_service.py @@ -2,7 +2,7 @@ import re from datetime import UTC, datetime -from exceptions import ( +from blog_exceptions import ( AccountBannedError, AccountNotFoundError, ArticleNotFoundError, @@ -40,7 +40,7 @@ def _extract_image_uuids(content: str) -> set[str]: try: data = json.loads(content) # Python builtin — safety net for json.loads on non-string input. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except (json.JSONDecodeError, TypeError): return set() uuids: set[str] = set() diff --git a/src/application/services/comment_service.py b/src/application/services/comment_service.py index e17f259..2858650 100644 --- a/src/application/services/comment_service.py +++ b/src/application/services/comment_service.py @@ -2,7 +2,7 @@ import nh3 -from exceptions import ( +from blog_exceptions import ( AccountBannedError, AccountNotFoundError, ArticleNotFoundError, diff --git a/src/application/services/file_service.py b/src/application/services/file_service.py index 668f17b..17a93e0 100644 --- a/src/application/services/file_service.py +++ b/src/application/services/file_service.py @@ -1,7 +1,7 @@ from datetime import datetime from uuid import uuid4 -from exceptions import FileTooLargeError, FileTypeError +from blog_exceptions import FileTooLargeError, FileTypeError from src.application.domain.file_record import FileRecord from src.application.input_ports.file_management import FileManagementPort from src.application.output_ports.file_storage_repository import FileStorageRepository diff --git a/src/application/services/login_service.py b/src/application/services/login_service.py index b43b75b..90b7f6a 100644 --- a/src/application/services/login_service.py +++ b/src/application/services/login_service.py @@ -1,4 +1,4 @@ -from exceptions import ( +from blog_exceptions import ( AccountBannedError, AccountNotFoundError, AuthenticationError, diff --git a/src/application/services/registration_service.py b/src/application/services/registration_service.py index 6b09764..2d42870 100644 --- a/src/application/services/registration_service.py +++ b/src/application/services/registration_service.py @@ -1,4 +1,4 @@ -from exceptions import AccountAlreadyExistsError, EmailAlreadyTakenError, UsernameAlreadyTakenError +from blog_exceptions import AccountAlreadyExistsError, EmailAlreadyTakenError, UsernameAlreadyTakenError from src.application.domain.account import Account, AccountRole from src.application.input_ports.registration_management import RegistrationManagementPort from src.application.output_ports.account_repository import AccountRepository diff --git a/src/infrastructure/input_adapters/dto/comment_request.py b/src/infrastructure/input_adapters/dto/comment_request.py index a5b1f4c..ffa2214 100644 --- a/src/infrastructure/input_adapters/dto/comment_request.py +++ b/src/infrastructure/input_adapters/dto/comment_request.py @@ -2,7 +2,7 @@ from pydantic import BaseModel, Field, field_validator -from exceptions import CommentEmptyError, CommentTooLongError +from blog_exceptions import CommentEmptyError, CommentTooLongError class CommentRequest(BaseModel): diff --git a/src/infrastructure/input_adapters/dto/registration_request.py b/src/infrastructure/input_adapters/dto/registration_request.py index 775e3d9..b6bd791 100644 --- a/src/infrastructure/input_adapters/dto/registration_request.py +++ b/src/infrastructure/input_adapters/dto/registration_request.py @@ -1,6 +1,6 @@ from pydantic import BaseModel, EmailStr, Field, model_validator -from exceptions import PasswordsDoNotMatchError +from blog_exceptions import PasswordsDoNotMatchError class RegistrationRequest(BaseModel): diff --git a/src/infrastructure/input_adapters/flask/flask_account_session_adapter.py b/src/infrastructure/input_adapters/flask/flask_account_session_adapter.py index afa258c..f37e80b 100644 --- a/src/infrastructure/input_adapters/flask/flask_account_session_adapter.py +++ b/src/infrastructure/input_adapters/flask/flask_account_session_adapter.py @@ -3,7 +3,7 @@ from flask_babel import gettext as _ -from exceptions import BlogCommentError, FileTooLargeError, FileTypeError +from blog_exceptions import BlogCommentError, FileTooLargeError, FileTypeError from flask import abort, flash, jsonify, redirect, render_template, request, session, url_for from flask import g as global_request_context from flask.views import MethodView @@ -185,7 +185,7 @@ def upload_profile_photo(self): # Intentionally broad: non-critical cleanup (delete old avatar # file from DB). Broad catch ensures request never fails due to # cleanup failure, even from unexpected bugs. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except Exception: logging.getLogger(__name__).warning( "Failed to delete old avatar %s for account %s", @@ -226,7 +226,7 @@ def remove_profile_photo(self): self.session_service.update_avatar(None) # Intentionally broad: non-critical cleanup. Broad catch ensures # request never fails; if cleanup fails, user gets a flash error. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except Exception: flash(_("Failed to remove profile photo."), "error") return redirect(url_for("auth.profile")) @@ -392,7 +392,7 @@ def delete_account(self): # Intentionally broad: non-critical cleanup (delete avatar # file from DB before account deletion). Broad catch ensures # account deletion proceeds even if avatar cleanup fails. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except Exception: logging.getLogger(__name__).warning( "Failed to delete avatar %s for account %s", diff --git a/src/infrastructure/input_adapters/flask/flask_article_adapter.py b/src/infrastructure/input_adapters/flask/flask_article_adapter.py index 75467f2..888d640 100644 --- a/src/infrastructure/input_adapters/flask/flask_article_adapter.py +++ b/src/infrastructure/input_adapters/flask/flask_article_adapter.py @@ -5,7 +5,7 @@ from pydantic import ValidationError from werkzeug.wrappers.response import Response -from exceptions import BlogCommentError +from blog_exceptions import BlogCommentError from flask import flash, jsonify, redirect, render_template, request, url_for from flask import g as global_request_context from src.application.domain.comment import CommentNode @@ -125,7 +125,7 @@ def read_article(self, article_id: int) -> str | Response: try: json.loads(content) # Python builtin — safety net for json.loads on non-string input. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except (json.JSONDecodeError, TypeError): content = json.dumps([{ "type": "paragraph", @@ -186,7 +186,7 @@ def api_get_article(self, article_id: int) -> Response | tuple[Response, int]: try: json.loads(content) # Python builtin — safety net for json.loads on non-string input. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except (json.JSONDecodeError, TypeError): content = json.dumps([{ "type": "paragraph", @@ -233,7 +233,7 @@ def api_create_article(self) -> Response | tuple[Response, int]: description=data.get("description", ""), ) # Pydantic library exception — caught at web boundary for 400 response. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except ValidationError as e: for error in e.errors(): return jsonify({"error": f"({error['loc'][0]}): {error['msg']}"}), 400 @@ -281,7 +281,7 @@ def api_update_article(self, article_id: int) -> Response | tuple[Response, int] description=data.get("description", ""), ) # Pydantic library exception — caught at web boundary for 400 response. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except ValidationError as e: for error in e.errors(): return jsonify({"error": f"({error['loc'][0]}): {error['msg']}"}), 400 diff --git a/src/infrastructure/input_adapters/flask/flask_comment_adapter.py b/src/infrastructure/input_adapters/flask/flask_comment_adapter.py index e19b50f..bcee988 100644 --- a/src/infrastructure/input_adapters/flask/flask_comment_adapter.py +++ b/src/infrastructure/input_adapters/flask/flask_comment_adapter.py @@ -4,7 +4,7 @@ from pydantic import ValidationError from werkzeug.wrappers.response import Response -from exceptions import BlogCommentError +from blog_exceptions import BlogCommentError from flask import flash, redirect, request, url_for from flask import g as global_request_context from src.application.input_ports.comment_management import CommentManagementPort @@ -71,7 +71,7 @@ def create_comment(self, article_id: int) -> Response: try: req_data = CommentRequest(content=request.form.get("content", "")) # Pydantic library exception — caught at web boundary for flash + redirect. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except ValidationError as e: for error in e.errors(): msg = error["msg"].removeprefix("Value error, ") @@ -118,7 +118,7 @@ def reply_to_comment(self, article_id: int, parent_comment_id: int) -> Response: try: req_data = CommentRequest(content=request.form.get("content", "")) # Pydantic library exception — caught at web boundary for flash + redirect. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except ValidationError as e: for error in e.errors(): msg = error["msg"].removeprefix("Value error, ") diff --git a/src/infrastructure/input_adapters/flask/flask_file_adapter.py b/src/infrastructure/input_adapters/flask/flask_file_adapter.py index 25a31ba..06114c6 100644 --- a/src/infrastructure/input_adapters/flask/flask_file_adapter.py +++ b/src/infrastructure/input_adapters/flask/flask_file_adapter.py @@ -2,7 +2,7 @@ from flask_babel import gettext as _ -from exceptions import FileTooLargeError, FileTypeError +from blog_exceptions import FileTooLargeError, FileTypeError from flask import jsonify, request, send_file from src.application.input_ports.file_management import FileManagementPort from src.infrastructure.input_adapters.dto.file_upload_request import FileUploadRequest @@ -41,7 +41,7 @@ def upload_image(self): mime_type=uploaded_file.content_type or "application/octet-stream", ) # Intentionally broad: catches Pydantic ValidationError or unexpected errors - # from file upload request parsing. Not in exceptions.py. Do not move it there. + # from file upload request parsing. Not in blog_exceptions.py. Do not move it there. except Exception as e: return jsonify({"error": str(e)}), 400 diff --git a/src/infrastructure/input_adapters/flask/flask_login_adapter.py b/src/infrastructure/input_adapters/flask/flask_login_adapter.py index 096ae36..f20b2d0 100644 --- a/src/infrastructure/input_adapters/flask/flask_login_adapter.py +++ b/src/infrastructure/input_adapters/flask/flask_login_adapter.py @@ -1,7 +1,7 @@ from flask_babel import gettext as _ from pydantic import ValidationError -from exceptions import AccountBannedError, AuthenticationError +from blog_exceptions import AccountBannedError, AuthenticationError from flask import flash, redirect, render_template, request, url_for from flask import g as global_request_context from flask.views import MethodView @@ -51,7 +51,7 @@ def authenticate(self): password=request.form.get("password", "") ) # Pydantic library exception — caught at web boundary for flash + redirect. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except ValidationError as e: for error in e.errors(): location = str(error["loc"][0]) if error["loc"] else "Request" diff --git a/src/infrastructure/input_adapters/flask/flask_registration_adapter.py b/src/infrastructure/input_adapters/flask/flask_registration_adapter.py index 5354f10..cbfcc06 100644 --- a/src/infrastructure/input_adapters/flask/flask_registration_adapter.py +++ b/src/infrastructure/input_adapters/flask/flask_registration_adapter.py @@ -1,7 +1,7 @@ from flask_babel import gettext as _ from pydantic import ValidationError -from exceptions import BlogCommentError +from blog_exceptions import BlogCommentError from flask import flash, redirect, render_template, request, url_for from flask import g as global_request_context from flask.views import MethodView @@ -54,7 +54,7 @@ def register(self): confirm_password=request.form.get("confirm_password", "") ) # Pydantic library exception — caught at web boundary for flash + redirect. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except ValidationError as e: for error in e.errors(): location = str(error["loc"][0]) if error["loc"] else "Request" diff --git a/src/infrastructure/output_adapters/in_memory/account_repository.py b/src/infrastructure/output_adapters/in_memory/account_repository.py index 3fc387f..319c700 100644 --- a/src/infrastructure/output_adapters/in_memory/account_repository.py +++ b/src/infrastructure/output_adapters/in_memory/account_repository.py @@ -1,6 +1,6 @@ from datetime import datetime -from exceptions import AccountNotFoundError +from blog_exceptions import AccountNotFoundError from src.application.domain.account import Account, AccountRole from src.application.output_ports.account_repository import AccountRepository diff --git a/src/infrastructure/output_adapters/security/argon2_password_hasher_adapter.py b/src/infrastructure/output_adapters/security/argon2_password_hasher_adapter.py index 7a20906..4082543 100644 --- a/src/infrastructure/output_adapters/security/argon2_password_hasher_adapter.py +++ b/src/infrastructure/output_adapters/security/argon2_password_hasher_adapter.py @@ -53,11 +53,11 @@ def verify(self, password: str, hashed_password: str) -> bool: try: return self._hasher.verify(hashed_password, password) # Argon2 library exception — caught, returns False. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except VerifyMismatchError: return False # Argon2 library exception — caught for legacy hash fallback. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except InvalidHashError: is_correct = secrets.compare_digest(password, hashed_password) self._hasher.hash("dummy_password_for_timing_consistency") @@ -76,6 +76,6 @@ def check_needs_rehash(self, hashed_password: str) -> bool: try: return self._hasher.check_needs_rehash(hashed_password) # Argon2 library exception — caught for legacy hash fallback. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except InvalidHashError: return True diff --git a/src/infrastructure/output_adapters/sqlalchemy/sqlalchemy_account_adapter.py b/src/infrastructure/output_adapters/sqlalchemy/sqlalchemy_account_adapter.py index d35db3b..3faa647 100644 --- a/src/infrastructure/output_adapters/sqlalchemy/sqlalchemy_account_adapter.py +++ b/src/infrastructure/output_adapters/sqlalchemy/sqlalchemy_account_adapter.py @@ -6,7 +6,7 @@ from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import Session -from exceptions import AccountAlreadyExistsError, AccountNotFoundError, DatabaseError +from blog_exceptions import AccountAlreadyExistsError, AccountNotFoundError, DatabaseError from src.application.domain.account import Account from src.application.output_ports.account_repository import AccountRepository from src.infrastructure.output_adapters.dto.account_record import AccountRecord @@ -143,7 +143,7 @@ def save(self, account: Account) -> None: try: self._db_commit() # SQLAlchemy library exception — caught to translate to domain exception. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except IntegrityError as e: constraint_name = cast(UniqueViolation, e.orig).diag.constraint_name if e.orig else None @@ -202,7 +202,7 @@ def update_email(self, account_id: int, new_email: str) -> None: try: self._db_commit() # SQLAlchemy library exception — caught to translate to domain exception. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except IntegrityError as e: constraint_name = cast(UniqueViolation, e.orig).diag.constraint_name if e.orig else None if constraint_name == "accounts_account_email_key": diff --git a/src/infrastructure/output_adapters/sqlalchemy/sqlalchemy_base_adapter.py b/src/infrastructure/output_adapters/sqlalchemy/sqlalchemy_base_adapter.py index dd8c9bd..615293c 100644 --- a/src/infrastructure/output_adapters/sqlalchemy/sqlalchemy_base_adapter.py +++ b/src/infrastructure/output_adapters/sqlalchemy/sqlalchemy_base_adapter.py @@ -1,7 +1,7 @@ from sqlalchemy.exc import IntegrityError, SQLAlchemyError from sqlalchemy.orm import Session -from exceptions import DatabaseError +from blog_exceptions import DatabaseError class SqlAlchemyBaseAdapter: @@ -32,7 +32,7 @@ def _db_get(self, model_class, pk): try: return self._session.get(model_class, pk) # SQLAlchemy library exception — caught and translated to DatabaseError. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except SQLAlchemyError as e: raise DatabaseError("Database read failed.") from e @@ -48,7 +48,7 @@ def _db_add(self, model): try: self._session.add(model) # SQLAlchemy library exception — caught and translated to DatabaseError. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except SQLAlchemyError as e: raise DatabaseError("Database insert failed.") from e @@ -64,7 +64,7 @@ def _db_delete(self, model): try: self._session.delete(model) # SQLAlchemy library exception — caught and translated to DatabaseError. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except SQLAlchemyError as e: raise DatabaseError("Database delete failed.") from e @@ -81,12 +81,12 @@ def _db_commit(self): try: self._session.commit() # SQLAlchemy library exception — re-raised for constraint handling by callers. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except IntegrityError: self._session.rollback() raise # SQLAlchemy library exception — caught and translated to DatabaseError. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except SQLAlchemyError as e: self._session.rollback() raise DatabaseError("Database commit failed.") from e @@ -131,6 +131,6 @@ def _db_query_raw(self, query_fn): try: return query_fn() # SQLAlchemy library exception — caught and translated to DatabaseError. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except SQLAlchemyError as e: raise DatabaseError("Database query failed.") from e diff --git a/tests/tests_infrastructure/tests_input_adapters/tests_flask/test_flask_account_session_adapter.py b/tests/tests_infrastructure/tests_input_adapters/tests_flask/test_flask_account_session_adapter.py index be4af80..26294fd 100644 --- a/tests/tests_infrastructure/tests_input_adapters/tests_flask/test_flask_account_session_adapter.py +++ b/tests/tests_infrastructure/tests_input_adapters/tests_flask/test_flask_account_session_adapter.py @@ -444,7 +444,7 @@ def test_update_email_unauthenticated(self): def test_update_email_error(self): fake_user = create_test_account(account_id=1, account_email="old@test.com") self.mock_session_service.get_current_account.return_value = fake_user - from exceptions import EmailAlreadyTakenError + from blog_exceptions import EmailAlreadyTakenError self.mock_session_service.update_email.side_effect = EmailAlreadyTakenError("This email is already taken.") response = self.client.post( "/profile/email", @@ -535,7 +535,7 @@ def test_admin_change_role_nonexistent_target(self): admin = create_test_account(account_id=1, account_role=AccountRole.ADMIN) self.set_current_user(admin) self.mock_session_service.get_current_account.return_value = admin - from exceptions import AccountNotFoundError + from blog_exceptions import AccountNotFoundError self.mock_session_service.update_account_role.side_effect = AccountNotFoundError("Account not found.") self.mock_session_service.get_account_by_id.return_value = None response = self.client.post( @@ -614,7 +614,7 @@ def test_admin_ban_another_admin_returns_302_with_flash(self): admin = create_test_account(account_id=1, account_role=AccountRole.ADMIN) self.set_current_user(admin) self.mock_session_service.get_current_account.return_value = admin - from exceptions import AuthorizationError + from blog_exceptions import AuthorizationError self.mock_session_service.ban_account.side_effect = AuthorizationError("Cannot ban another admin.") response = self.client.post("/admin/users/2/ban", data={"ban_reason": "Spam"}, follow_redirects=True) assert response.status_code == 200 @@ -625,7 +625,7 @@ def test_admin_ban_nonexistent_user_redirects_with_flash(self): admin = create_test_account(account_id=1, account_role=AccountRole.ADMIN) self.set_current_user(admin) self.mock_session_service.get_current_account.return_value = admin - from exceptions import AccountNotFoundError + from blog_exceptions import AccountNotFoundError self.mock_session_service.ban_account.side_effect = AccountNotFoundError("Account not found.") response = self.client.post("/admin/users/999/ban", data={"ban_reason": "Spam"}, follow_redirects=True) assert response.status_code == 200 diff --git a/tests/tests_infrastructure/tests_input_adapters/tests_flask/test_flask_article_adapter.py b/tests/tests_infrastructure/tests_input_adapters/tests_flask/test_flask_article_adapter.py index 0279a64..480570c 100644 --- a/tests/tests_infrastructure/tests_input_adapters/tests_flask/test_flask_article_adapter.py +++ b/tests/tests_infrastructure/tests_input_adapters/tests_flask/test_flask_article_adapter.py @@ -305,7 +305,7 @@ def _save_side_effect(article): def test_author_create_article_service_error(self): author = create_test_account(account_id=10, account_role=AccountRole.AUTHOR) self._prepare_user_context(author) - from exceptions import BlogCommentError + from blog_exceptions import BlogCommentError mock_service = Mock(spec=ArticleService) self.adapter.article_service = mock_service mock_service.create_article.side_effect = BlogCommentError("Service Error Message") @@ -408,7 +408,7 @@ def test_update_article_validation_error(self): def test_delete_article_service_error(self): author = create_test_account(account_id=10, account_role=AccountRole.AUTHOR) self._prepare_user_context(author) - from exceptions import BlogCommentError + from blog_exceptions import BlogCommentError self.adapter.article_service.delete_article = Mock(side_effect=BlogCommentError("Delete Error")) response = self.client.delete("/api/articles/1") assert response.status_code == 403 diff --git a/tests/tests_infrastructure/tests_input_adapters/tests_flask/test_flask_comment_adapter.py b/tests/tests_infrastructure/tests_input_adapters/tests_flask/test_flask_comment_adapter.py index 2293819..846224e 100644 --- a/tests/tests_infrastructure/tests_input_adapters/tests_flask/test_flask_comment_adapter.py +++ b/tests/tests_infrastructure/tests_input_adapters/tests_flask/test_flask_comment_adapter.py @@ -83,7 +83,7 @@ def test_create_comment_validation_error(self): def test_create_comment_service_error_string(self): user = create_test_account(account_id=123) self.set_current_user(user) - from exceptions import ArticleNotFoundError + from blog_exceptions import ArticleNotFoundError self.mock_comment_service.create_comment.side_effect = ArticleNotFoundError("Article not found") response = self.client.post("/articles/1/comments", data={"content": "Valid"}, follow_redirects=True) assert b"Article not found" in response.data @@ -134,7 +134,7 @@ def test_reply_validation_error(self): def test_reply_service_error_string(self): user = create_test_account(account_id=123) self.set_current_user(user) - from exceptions import CommentNotFoundError + from blog_exceptions import CommentNotFoundError self.mock_comment_service.create_reply.side_effect = CommentNotFoundError("Parent not found") response = self.client.post("/articles/1/comments/10/reply", data={"content": "Valid"}, follow_redirects=True) assert b"Parent not found" in response.data @@ -178,7 +178,7 @@ def test_delete_comment_requires_login(self): def test_delete_comment_service_error_string(self): user = create_test_account(account_id=1, account_role=AccountRole.USER) self.set_current_user(user) - from exceptions import CommentNotFoundError + from blog_exceptions import CommentNotFoundError self.mock_comment_service.delete_comment.side_effect = CommentNotFoundError("Comment not found") response = self.client.post("/articles/1/comments/99/delete", follow_redirects=True) assert b"Comment not found" in response.data @@ -187,7 +187,7 @@ def test_delete_comment_service_error_string(self): def test_delete_comment_unauthorized_raises(self): user = create_test_account(account_id=1, account_role=AccountRole.USER) self.set_current_user(user) - from exceptions import OwnershipError + from blog_exceptions import OwnershipError self.mock_comment_service.delete_comment.side_effect = OwnershipError( "Unauthorized: You can only delete your own comments." ) @@ -247,7 +247,7 @@ def test_hard_delete_comment_not_authenticated(self): def test_hard_delete_comment_service_error_string(self): user = create_test_account(account_id=1, account_role=AccountRole.USER) self.set_current_user(user) - from exceptions import CommentNotFoundError + from blog_exceptions import CommentNotFoundError self.mock_comment_service.hard_delete_comment.side_effect = CommentNotFoundError("Comment not found") response = self.client.post("/articles/1/comments/99/delete-permanent", follow_redirects=True) assert b"Comment not found" in response.data diff --git a/tests/tests_infrastructure/tests_output_adapters/test_flask_session_adapter.py b/tests/tests_infrastructure/tests_output_adapters/test_flask_session_adapter.py index 73c798c..96bf1e1 100644 --- a/tests/tests_infrastructure/tests_output_adapters/test_flask_session_adapter.py +++ b/tests/tests_infrastructure/tests_output_adapters/test_flask_session_adapter.py @@ -109,8 +109,8 @@ def test_get_account_with_corrupted_structure_returns_none(self): def test_get_account_repository_timeout_resilience(self): from flask import session as flask_session - # Intentionally NOT in exceptions.py: test-only. Exception sufficient. - # Do not move to exceptions.py. + # Intentionally NOT in blog_exceptions.py: test-only. Exception sufficient. + # Do not move to blog_exceptions.py. self.mock_repo.get_by_id.side_effect = Exception("DB Timeout") with self.app.test_request_context(): flask_session[self.adapter._KEY_USER_ID] = 123 diff --git a/tests/tests_infrastructure/tests_output_adapters/tests_sqlalchemy/test_sqlalchemy_account_adapter.py b/tests/tests_infrastructure/tests_output_adapters/tests_sqlalchemy/test_sqlalchemy_account_adapter.py index ad367ef..17c7d42 100644 --- a/tests/tests_infrastructure/tests_output_adapters/tests_sqlalchemy/test_sqlalchemy_account_adapter.py +++ b/tests/tests_infrastructure/tests_output_adapters/tests_sqlalchemy/test_sqlalchemy_account_adapter.py @@ -1,6 +1,6 @@ import pytest -from exceptions import AccountAlreadyExistsError +from blog_exceptions import AccountAlreadyExistsError from src.application.domain.account import AccountRole from src.infrastructure.output_adapters.sqlalchemy.models.sqlalchemy_account_model import AccountModel from src.infrastructure.output_adapters.sqlalchemy.sqlalchemy_account_adapter import SqlAlchemyAccountAdapter diff --git a/tests/tests_services/test_article_service.py b/tests/tests_services/test_article_service.py index af3751f..2eb64e0 100644 --- a/tests/tests_services/test_article_service.py +++ b/tests/tests_services/test_article_service.py @@ -4,7 +4,7 @@ import pytest -from exceptions import ( +from blog_exceptions import ( AccountNotFoundError, ArticleNotFoundError, InsufficientPermissionsError, diff --git a/tests/tests_services/test_comment_service.py b/tests/tests_services/test_comment_service.py index 3feed7e..0e6bd5c 100644 --- a/tests/tests_services/test_comment_service.py +++ b/tests/tests_services/test_comment_service.py @@ -2,7 +2,7 @@ import pytest -from exceptions import ( +from blog_exceptions import ( AccountNotFoundError, ArticleNotFoundError, CommentAuthorizationError, diff --git a/tests/tests_services/test_file_service.py b/tests/tests_services/test_file_service.py index 3b8b848..7974e61 100644 --- a/tests/tests_services/test_file_service.py +++ b/tests/tests_services/test_file_service.py @@ -1,6 +1,6 @@ from unittest.mock import MagicMock -from exceptions import FileTooLargeError, FileTypeError +from blog_exceptions import FileTooLargeError, FileTypeError from src.application.domain.file_record import FileRecord from src.application.output_ports.file_storage_repository import FileStorageRepository from src.application.services.file_service import FileService diff --git a/tests/tests_services/test_login_service.py b/tests/tests_services/test_login_service.py index 3b94e4a..96ec4ec 100644 --- a/tests/tests_services/test_login_service.py +++ b/tests/tests_services/test_login_service.py @@ -2,7 +2,7 @@ import pytest -from exceptions import ( +from blog_exceptions import ( AccountBannedError, AccountNotFoundError, AuthenticationError, @@ -101,8 +101,8 @@ def test_terminate_session(self): def test_authenticate_user_session_repo_failure(self): fake_account = create_test_account() self.mock_repo.find_by_username.return_value = fake_account - # Intentionally NOT in exceptions.py: test-only. Exception sufficient. - # Do not move to exceptions.py. + # Intentionally NOT in blog_exceptions.py: test-only. Exception sufficient. + # Do not move to blog_exceptions.py. self.mock_session_repo.save_account.side_effect = Exception("Storage failure") with pytest.raises(Exception, match="Storage failure"): self.service.authenticate_user("leia", "password123") diff --git a/tests/tests_services/test_registration_service.py b/tests/tests_services/test_registration_service.py index 87abc7e..16e9d9b 100644 --- a/tests/tests_services/test_registration_service.py +++ b/tests/tests_services/test_registration_service.py @@ -2,7 +2,7 @@ import pytest -from exceptions import EmailAlreadyTakenError, UsernameAlreadyTakenError +from blog_exceptions import EmailAlreadyTakenError, UsernameAlreadyTakenError from src.application.domain.account import Account, AccountRole from src.application.output_ports.account_repository import AccountRepository from src.application.output_ports.password_hasher_repository import PasswordHasherRepository diff --git a/utils/prosemirror_to_html.py b/utils/prosemirror_to_html.py index 0ce2d69..a6e8925 100644 --- a/utils/prosemirror_to_html.py +++ b/utils/prosemirror_to_html.py @@ -9,7 +9,7 @@ def prosemirror_to_html(content_json: str | None, base_url: str = "") -> Markup: try: blocks = json.loads(content_json) # Python builtin — safety net for json.loads on non-string input. - # Not in exceptions.py. Do not move it there. + # Not in blog_exceptions.py. Do not move it there. except (json.JSONDecodeError, TypeError): return Markup(f"
{escape(content_json)}
") if not isinstance(blocks, list): diff --git a/utils/template_helpers.py b/utils/template_helpers.py index e974848..2bb1bba 100644 --- a/utils/template_helpers.py +++ b/utils/template_helpers.py @@ -25,9 +25,9 @@ def init(cls, static_dir: str | None) -> None: Raises: RuntimeError: If Flask's static_folder is None. """ - # Intentionally NOT in exceptions.py: startup-only crash path. + # Intentionally NOT in blog_exceptions.py: startup-only crash path. # Never caught by application code. Builtin RuntimeError sufficient. - # Do not move to exceptions.py. + # Do not move to blog_exceptions.py. if static_dir is None: raise RuntimeError("Flask static_folder is None; cannot locate Vite manifest.") cls._manifest_path = os.path.join(static_dir, ".vite", "manifest.json")