From 2f81e4ff06a57a03c66be171c3988a11ab174e1a Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Mon, 21 Jul 2025 02:28:04 -0300 Subject: [PATCH 1/6] Add the tests for the hash methods and the create methods --- tests/service/test_photo_service.py | 56 +++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tests/service/test_photo_service.py diff --git a/tests/service/test_photo_service.py b/tests/service/test_photo_service.py new file mode 100644 index 0000000..a3f595f --- /dev/null +++ b/tests/service/test_photo_service.py @@ -0,0 +1,56 @@ +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 + +@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() From 5f20bd3624d2c9e4c7f8a2d05ff63b6ec8f817ec Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Mon, 21 Jul 2025 02:48:04 -0300 Subject: [PATCH 2/6] Add the tests for the read methods for the photo service --- tests/conftest.py | 24 +++++++++++++ tests/service/test_photo_service.py | 52 +++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 0c1ce17..7232c5a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,7 @@ import pytest from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker +from pilgrim import Photo from pilgrim.database import Base from pilgrim.models.travel_diary import TravelDiary @@ -34,3 +35,26 @@ 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): + """ + Fixture que usa a session_with_one_diary e adiciona duas fotos a ela. + """ + session, diary = session_with_one_diary + + photo1 = Photo( + filepath=f"/fake/{diary.directory_name}/p1.jpg", name="Foto 1", + photo_hash="hash1", fk_travel_diary_id=diary.id + ) + photo2 = Photo( + filepath=f"/fake/{diary.directory_name}/p2.jpg", name="Foto 2", + photo_hash="hash2", fk_travel_diary_id=diary.id + ) + + session.add_all([photo1, photo2]) + session.commit() + + # Retornamos a sessão e os objetos criados para que os testes possam usá-los + 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 index a3f595f..4f4ec0a 100644 --- a/tests/service/test_photo_service.py +++ b/tests/service/test_photo_service.py @@ -54,3 +54,55 @@ def test_create_photo_returns_none_if_hash_exists(mock_hash, mock_copy, session_ ) 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 \ No newline at end of file From 5d0d4fc5acb04dd45ef658df7747a8115ea3ece0 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Mon, 21 Jul 2025 02:28:04 -0300 Subject: [PATCH 3/6] Add the tests for the hash methods and the create methods --- tests/service/test_photo_service.py | 56 +++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tests/service/test_photo_service.py diff --git a/tests/service/test_photo_service.py b/tests/service/test_photo_service.py new file mode 100644 index 0000000..a3f595f --- /dev/null +++ b/tests/service/test_photo_service.py @@ -0,0 +1,56 @@ +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 + +@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() From 32b1c24846c6c619a9c3b456ef34cf828ba89a7b Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Mon, 21 Jul 2025 02:48:04 -0300 Subject: [PATCH 4/6] Add the tests for the read methods for the photo service --- tests/conftest.py | 24 +++++++++++++ tests/service/test_photo_service.py | 52 +++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 0c1ce17..7232c5a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,7 @@ import pytest from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker +from pilgrim import Photo from pilgrim.database import Base from pilgrim.models.travel_diary import TravelDiary @@ -34,3 +35,26 @@ 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): + """ + Fixture que usa a session_with_one_diary e adiciona duas fotos a ela. + """ + session, diary = session_with_one_diary + + photo1 = Photo( + filepath=f"/fake/{diary.directory_name}/p1.jpg", name="Foto 1", + photo_hash="hash1", fk_travel_diary_id=diary.id + ) + photo2 = Photo( + filepath=f"/fake/{diary.directory_name}/p2.jpg", name="Foto 2", + photo_hash="hash2", fk_travel_diary_id=diary.id + ) + + session.add_all([photo1, photo2]) + session.commit() + + # Retornamos a sessão e os objetos criados para que os testes possam usá-los + 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 index a3f595f..4f4ec0a 100644 --- a/tests/service/test_photo_service.py +++ b/tests/service/test_photo_service.py @@ -54,3 +54,55 @@ def test_create_photo_returns_none_if_hash_exists(mock_hash, mock_copy, session_ ) 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 \ No newline at end of file From 44824cd6901a2d94af50a875b7f54ce7d3370af1 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Mon, 21 Jul 2025 16:30:54 -0300 Subject: [PATCH 5/6] Add the tests for the update photo service and also improved the fixture --- tests/conftest.py | 20 ++++++----- tests/service/test_photo_service.py | 51 ++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 7232c5a..66b44a1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -39,22 +39,26 @@ def session_with_one_diary(db_session): @pytest.fixture def session_with_photos(session_with_one_diary): - """ - Fixture que usa a session_with_one_diary e adiciona duas fotos a ela. - """ 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( - filepath=f"/fake/{diary.directory_name}/p1.jpg", name="Foto 1", - photo_hash="hash1", fk_travel_diary_id=diary.id + # 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"/fake/{diary.directory_name}/p2.jpg", name="Foto 2", - photo_hash="hash2", fk_travel_diary_id=diary.id + 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() - # Retornamos a sessão e os objetos criados para que os testes possam usá-los 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 index 4f4ec0a..636dd36 100644 --- a/tests/service/test_photo_service.py +++ b/tests/service/test_photo_service.py @@ -4,6 +4,8 @@ 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") @@ -105,4 +107,51 @@ def test_check_photo_by_hash_returns_none_when_not_found(session_with_photos): assert result1 is None invalid_diary_id = 999 result2 = service.check_photo_by_hash(existing_hash, invalid_diary_id) - assert result2 is None \ No newline at end of file + assert result2 is None + +def test_update_photo_metadata_successfully(session_with_photos): + session, photos = session_with_photos + service = PhotoService(session) + photo_to_update = photos[0] + photo_with_new_data = Photo( + filepath=photo_to_update.filepath, + name="Novo Nome da Foto", + caption="Nova legenda.", + photo_hash=photo_to_update.photo_hash, # Hash não muda + addition_date=photo_to_update.addition_date, + fk_travel_diary_id=photo_to_update.fk_travel_diary_id + ) + updated_photo = service.update(photo_to_update, photo_with_new_data) + assert updated_photo is not None + assert updated_photo.name == "Novo Nome da Foto" + assert updated_photo.caption == "Nova legenda." + assert updated_photo.photo_hash == "hash1" + +@patch.object(PhotoService, 'hash_file') +@patch('pathlib.Path.unlink') +@patch('pathlib.Path.exists') +@patch.object(PhotoService, '_copy_photo_to_diary') +@patch.object(DirectoryManager, 'get_diaries_root', return_value="/fake/diaries_root") +def test_update_photo_with_new_file_successfully( + mock_get_root, mock_copy, mock_exists, mock_unlink, mock_hash, session_with_photos +): + session, photos = session_with_photos + service = PhotoService(session) + photo_to_update = photos[0] + new_source_path = Path("/path/para/nova_imagem.jpg") + new_copied_path = Path(f"/fake/diaries_root/{photo_to_update.travel_diary.directory_name}/images/nova_imagem.jpg") + mock_copy.return_value = new_copied_path + mock_exists.return_value = True + mock_hash.return_value = "novo_hash_calculado" + photo_with_new_file = Photo( + filepath=new_source_path, + name=photo_to_update.name, + photo_hash="hash_antigo", + fk_travel_diary_id=photo_to_update.fk_travel_diary_id + ) + updated_photo = service.update(photo_to_update, photo_with_new_file) + mock_copy.assert_called_once_with(new_source_path, photo_to_update.travel_diary) + mock_unlink.assert_called_once() + mock_hash.assert_called_once_with(new_copied_path) + assert updated_photo.filepath == str(new_copied_path) + assert updated_photo.photo_hash == "novo_hash_calculado" \ No newline at end of file From 32f363f15ad517c612b5daade59883c16bf0e513 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Mon, 21 Jul 2025 16:48:09 -0300 Subject: [PATCH 6/6] Add the tests for the delete photo service and also fixed the missing import --- tests/conftest.py | 1 + tests/service/test_photo_service.py | 31 ++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 57452bd..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 # ... diff --git a/tests/service/test_photo_service.py b/tests/service/test_photo_service.py index 20facbd..faceacd 100644 --- a/tests/service/test_photo_service.py +++ b/tests/service/test_photo_service.py @@ -107,4 +107,33 @@ def test_check_photo_by_hash_returns_none_when_not_found(session_with_photos): assert result1 is None invalid_diary_id = 999 result2 = service.check_photo_by_hash(existing_hash, invalid_diary_id) - assert result2 is None \ No newline at end of file + 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() +