diff --git a/tests/conftest.py b/tests/conftest.py index 0c1ce17..59d0e26 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,6 +4,7 @@ from sqlalchemy.orm import sessionmaker from pilgrim.database import Base from pilgrim.models.travel_diary import TravelDiary +from pilgrim.models.photo import Photo # Todos os imports necessários para as fixtures devem estar aqui # ... @@ -34,3 +35,30 @@ def session_with_one_diary(db_session): db_session.commit() db_session.refresh(diary) return db_session, diary + + +@pytest.fixture +def session_with_photos(session_with_one_diary): + session, diary = session_with_one_diary + + # Usamos a mesma raiz de diretório que o mock do teste espera + diaries_root = "/fake/diaries_root" + + photo1 = Photo( + # CORREÇÃO: O caminho agora inclui a raiz e a subpasta 'images' + filepath=f"{diaries_root}/{diary.directory_name}/images/p1.jpg", + name="Foto 1", + photo_hash="hash1", + fk_travel_diary_id=diary.id + ) + photo2 = Photo( + filepath=f"{diaries_root}/{diary.directory_name}/images/p2.jpg", + name="Foto 2", + photo_hash="hash2", + fk_travel_diary_id=diary.id + ) + + session.add_all([photo1, photo2]) + session.commit() + + return session, [photo1, photo2] \ No newline at end of file diff --git a/tests/service/test_photo_service.py b/tests/service/test_photo_service.py new file mode 100644 index 0000000..faceacd --- /dev/null +++ b/tests/service/test_photo_service.py @@ -0,0 +1,139 @@ +import pytest +from pathlib import Path +from pilgrim.service.photo_service import PhotoService +import hashlib +from unittest.mock import patch +from pilgrim.models.photo import Photo +from pilgrim.utils import DirectoryManager + + +@patch.object(PhotoService, '_copy_photo_to_diary') +@patch.object(PhotoService, 'hash_file', return_value="fake_hash_123") +def test_create_photo_successfully(mock_hash, mock_copy, session_with_one_diary): + session, diary = session_with_one_diary + service = PhotoService(session) + fake_source_path = Path("/path/original/imagem.jpg") + fake_copied_path = Path(f"~/.pilgrim/diaries/{diary.directory_name}/images/imagem.jpg") + mock_copy.return_value = fake_copied_path + new_photo = service.create( + filepath=fake_source_path, + name="Foto da Praia", + travel_diary_id=diary.id, + caption="Pôr do sol") + mock_hash.assert_called_once_with(fake_source_path) + mock_copy.assert_called_once_with(fake_source_path, diary) + assert new_photo is not None + assert new_photo.name == "Foto da Praia" + assert new_photo.photo_hash == "fake_hash_123" + assert new_photo.filepath == str(fake_copied_path) + +def test_hash_file_generates_correct_hash(tmp_path: Path): + original_content_bytes = b"um conteudo de teste para o hash" + file_on_disk = tmp_path / "test.jpg" + file_on_disk.write_bytes(original_content_bytes) + hash_from_file = PhotoService.hash_file(file_on_disk) + expected_hash_func = hashlib.new('sha3_384') + expected_hash_func.update(original_content_bytes) + hash_from_memory = expected_hash_func.hexdigest() + assert hash_from_file == hash_from_memory + +@patch.object(PhotoService, '_copy_photo_to_diary') +@patch.object(PhotoService, 'hash_file', return_value="hash_ja_existente") +def test_create_photo_returns_none_if_hash_exists(mock_hash, mock_copy, session_with_one_diary): + session, diary = session_with_one_diary + existing_photo = Photo( + filepath="/path/existente.jpg", name="Foto Antiga", + photo_hash="hash_ja_existente", fk_travel_diary_id=diary.id + ) + session.add(existing_photo) + session.commit() + + service = PhotoService(session) + new_photo = service.create( + filepath=Path("/path/novo/arquivo.jpg"), + name="Foto Nova", + travel_diary_id=diary.id + ) + assert new_photo is None + mock_copy.assert_not_called() + +def test_read_by_id_successfully(session_with_photos): + session, photos = session_with_photos + service = PhotoService(session) + photo_to_find_id = photos[0].id + found_photo = service.read_by_id(photo_to_find_id) + assert found_photo is not None + assert found_photo.id == photo_to_find_id + assert found_photo.name == "Foto 1" + +def test_read_by_id_returns_none_for_invalid_id(db_session): + service = PhotoService(db_session) + result = service.read_by_id(999) + assert result is None + +def test_read_all_returns_all_photos(session_with_photos): + session, _ = session_with_photos + service = PhotoService(session) + all_photos = service.read_all() + + assert isinstance(all_photos, list) + assert len(all_photos) == 2 + assert all_photos[0].name == "Foto 1" + assert all_photos[1].name == "Foto 2" + +def test_read_all_returns_empty_list_for_empty_db(db_session): + service = PhotoService(db_session) + all_photos = service.read_all() + assert isinstance(all_photos, list) + assert len(all_photos) == 0 + +def test_check_photo_by_hash_finds_existing_photo(session_with_photos): + session, photos = session_with_photos + service = PhotoService(session) + existing_photo = photos[0] + hash_to_find = existing_photo.photo_hash # "hash1" + diary_id = existing_photo.fk_travel_diary_id # 1 + found_photo = service.check_photo_by_hash(hash_to_find, diary_id) + assert found_photo is not None + assert found_photo.id == existing_photo.id + assert found_photo.photo_hash == hash_to_find + +def test_check_photo_by_hash_returns_none_when_not_found(session_with_photos): + session, photos = session_with_photos + service = PhotoService(session) + existing_hash = photos[0].photo_hash # "hash1" + existing_diary_id = photos[0].fk_travel_diary_id # 1 + result1 = service.check_photo_by_hash("hash_inexistente", existing_diary_id) + assert result1 is None + invalid_diary_id = 999 + result2 = service.check_photo_by_hash(existing_hash, invalid_diary_id) + assert result2 is None + +@patch('pathlib.Path.unlink') +@patch('pathlib.Path.exists') +@patch.object(DirectoryManager, 'get_diaries_root', return_value="/fake/diaries_root") +def test_delete_photo_successfully(mock_get_root, mock_exists, mock_unlink, session_with_photos): + session, photos = session_with_photos + service = PhotoService(session) + photo_to_delete = photos[0] + photo_id = photo_to_delete.id + mock_exists.return_value = True + deleted_photo_data = service.delete(photo_to_delete) + mock_unlink.assert_called_once() + assert deleted_photo_data is not None + assert deleted_photo_data.id == photo_id + photo_in_db = service.read_by_id(photo_id) + assert photo_in_db is None + +@patch('pathlib.Path.unlink') +def test_delete_returns_none_for_non_existent_photo(mock_unlink, db_session): + service = PhotoService(db_session) + non_existent_photo = Photo( + filepath="/fake/path.jpg", name="dummy", + photo_hash="dummy_hash", fk_travel_diary_id=1 + ) + non_existent_photo.id = 999 + result = service.delete(non_existent_photo) + assert result is None + mock_unlink.assert_not_called() +