From 76377e24c14d4c8848b56a8e6a3398099dcd1a10 Mon Sep 17 00:00:00 2001 From: Alexandre Brito Date: Fri, 26 Jan 2024 17:07:11 -0300 Subject: [PATCH] tests: added nhentai doujin get testing --- .github/workflows/testing.yml | 31 ++++++ enma/application/use_cases/get_manga.py | 8 +- enma/domain/entities/manga.py | 2 +- enma/infra/adapters/repositories/nhentai.py | 9 +- requirements.txt | 3 +- setup.cfg | 1 + ..._nhentai_fetch_chapter_by_symbolic_link.py | 34 ++++++- tests/test_nhentai_get_manga_use_case.py | 94 +++++++++++++++++-- 8 files changed, 164 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/testing.yml diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml new file mode 100644 index 0000000..b12f4df --- /dev/null +++ b/.github/workflows/testing.yml @@ -0,0 +1,31 @@ +name: Development Pypi +on: + push: + branches: + - "dev" + - "master" + pull_request: + branches: + - "dev" + - "master" +jobs: + pypi: + name: Code Testing + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@master + with: + python-version: 3.10 + + - name: Preparing the environment + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then python -m pip install -r requirements.txt; else python -m pip install beautifulsoup4 requests pydantic; fi + + - name: Starting Testing Suites + run: | + python -m pytest ./tests \ No newline at end of file diff --git a/enma/application/use_cases/get_manga.py b/enma/application/use_cases/get_manga.py index 2fffe03..a0c0c19 100644 --- a/enma/application/use_cases/get_manga.py +++ b/enma/application/use_cases/get_manga.py @@ -1,7 +1,8 @@ from dataclasses import dataclass from typing import Union -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, validator +from enma.application.core.handlers.error import InvalidRequest from enma.application.core.interfaces.manga_repository import IMangaRepository from enma.application.core.interfaces.use_case import DTO, IUseCase from enma.application.core.utils.logger import logger @@ -15,15 +16,14 @@ class GetMangaRequestDTO(BaseModel): class GetMangaResponseDTO: found: bool manga: Union[Manga, None] - class GetMangaUseCase(IUseCase[GetMangaRequestDTO, GetMangaResponseDTO]): - + def __init__(self, manga_repository: IMangaRepository): self.__manga_repository = manga_repository def execute(self, dto: DTO[GetMangaRequestDTO]) -> GetMangaResponseDTO: logger.info(f'Fetching manga with identifier: {dto.data.identifier}.') - + manga = self.__manga_repository.get(identifier=dto.data.identifier, with_symbolic_links=dto.data.with_symbolic_links) diff --git a/enma/domain/entities/manga.py b/enma/domain/entities/manga.py index 236e2f7..e3fbb1e 100644 --- a/enma/domain/entities/manga.py +++ b/enma/domain/entities/manga.py @@ -38,7 +38,7 @@ class SymbolicLink: @dataclass class Chapter: - id: str | int + id: str | int = field(default=0) pages: list[Image] = field(default_factory=list) pages_count: int = field(default=0) link: SymbolicLink | None = field(default=None) diff --git a/enma/infra/adapters/repositories/nhentai.py b/enma/infra/adapters/repositories/nhentai.py index 3735c06..caa668f 100644 --- a/enma/infra/adapters/repositories/nhentai.py +++ b/enma/infra/adapters/repositories/nhentai.py @@ -103,16 +103,20 @@ def fetch_chapter_by_symbolic_link(self, if response.status_code != 200: self.__handle_request_error(msg=f'Could not fetch {link.link} because nhentai\'s request ends up with {response.status_code} status code.') + return Chapter() doujin: NHentaiResponse = response.json() + if doujin.get('media_id') is None or doujin.get('images') is None: + return Chapter() + return self.__create_chapter(media_id=doujin.get('media_id'), pages=doujin.get('images').get('pages')) def __create_chapter(self, media_id: str, pages: list[NHentaiImage]) -> Chapter: - chapter = Chapter(id=0) + chapter = Chapter() for index, page in enumerate(pages): mime = MIME[page.get('t').upper()] chapter.add_page(Image(uri=self.__make_page_uri(type='page', @@ -128,6 +132,7 @@ def __create_chapter(self, def get(self, identifier: str, with_symbolic_links: bool = False) -> Manga | None: + print(identifier, with_symbolic_links) url = f'{self.__API_URL}/gallery/{identifier}' response = self.__make_request(url=url) @@ -138,7 +143,7 @@ def get(self, media_id = doujin.get('media_id') if with_symbolic_links: - chapter = Chapter(id=0, link=SymbolicLink(link=url)) + chapter = Chapter(link=SymbolicLink(link=url)) else: chapter = self.__create_chapter(media_id=media_id, pages=doujin.get('images').get('pages')) diff --git a/requirements.txt b/requirements.txt index 891601b..11d71d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ requests==2.31.0 pytest==7.0.1 -beautifulsoup4==4.10.0 \ No newline at end of file +beautifulsoup4==4.10.0 +pydantic==2.5.3 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 5ec7dfe..c578b70 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,6 +19,7 @@ python_requires = >=3.9 install_requires = requests==2.31.0 beautifulsoup4==4.10.0 + pydantic==2.5.3 setup_requires = setuptools_scm diff --git a/tests/test_nhentai_fetch_chapter_by_symbolic_link.py b/tests/test_nhentai_fetch_chapter_by_symbolic_link.py index 77b25d3..2a8dba7 100644 --- a/tests/test_nhentai_fetch_chapter_by_symbolic_link.py +++ b/tests/test_nhentai_fetch_chapter_by_symbolic_link.py @@ -48,4 +48,36 @@ def test_fetch_chapter_by_symbolic_link(self): assert isinstance(response.chapter, Chapter) assert response.chapter.pages_count == 14 assert response.chapter.id == 0 - assert len(response.chapter.pages) == 14 \ No newline at end of file + assert len(response.chapter.pages) == 14 + + def test_should_return_empty_chapter_for_broken_link(self): + with patch('requests.get') as mock_method: + mock = Mock() + mock.status_code = 404 + mock.json.return_value = {} + mock_method.return_value = mock + + link = SymbolicLink(link='https://nhentai.net') + response = self.sut.execute(dto=DTO(data=FetchChapterBySymbolicLinkRequestDTO(link=link))) + + assert isinstance(response.chapter, Chapter) + assert response.chapter.link is None + assert response.chapter.pages_count == 0 + assert response.chapter.id == 0 + assert len(response.chapter.pages) == 0 + + def test_should_return_empty_chapter_for_broken_response(self): + with patch('requests.get') as mock_method: + mock = Mock() + mock.status_code = 200 + mock.json.return_value = {} + mock_method.return_value = mock + + link = SymbolicLink(link='https://nhentai.net') + response = self.sut.execute(dto=DTO(data=FetchChapterBySymbolicLinkRequestDTO(link=link))) + + assert isinstance(response.chapter, Chapter) + assert response.chapter.link is None + assert response.chapter.pages_count == 0 + assert response.chapter.id == 0 + assert len(response.chapter.pages) == 0 \ No newline at end of file diff --git a/tests/test_nhentai_get_manga_use_case.py b/tests/test_nhentai_get_manga_use_case.py index 4e38a2d..4a4f166 100644 --- a/tests/test_nhentai_get_manga_use_case.py +++ b/tests/test_nhentai_get_manga_use_case.py @@ -1,16 +1,19 @@ import datetime import json -from unittest.mock import Mock, patch +from unittest.mock import MagicMock, Mock, patch +from pydantic import ValidationError import pytest import sys import os +from enma.application.core.handlers.error import InvalidRequest sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) +from enma.infra.core.interfaces.nhentai_response import NHentaiResponse from enma.application.use_cases.get_manga import GetMangaRequestDTO, GetMangaUseCase from enma.application.core.interfaces.use_case import DTO from enma.infra.adapters.repositories.nhentai import CloudFlareConfig, NHentai -from enma.domain.entities.manga import MIME, Author, Chapter, Genre, Image, Manga, SymbolicLink, Title +from enma.domain.entities.manga import MIME, Author, Chapter, Genre, Image, Manga, Title class TestNHentaiGetDoujin: @@ -56,6 +59,30 @@ def test_success_doujin_retrieve(self): assert isinstance(chapter, Chapter) mock_method.assert_called_with(identifier='489504', with_symbolic_links=False) + + def test_must_return_other_titles_as_none_if_doesnt_exists(self): + with patch('requests.get') as mock_method: + mock = Mock() + mock.status_code = 200 + + with open('./tests/data/get.json', 'r') as get: + data: NHentaiResponse = json.loads(get.read()) + + data['title']['japanese'] = None + data['title']['pretty'] = None + + mock.json.return_value = data + + mock_method.return_value = mock + + res = self.sut.execute(dto=DTO(data=GetMangaRequestDTO(identifier='489504'))) + + assert res.found == True + assert res.manga is not None + assert res.manga.id == 1 + assert res.manga.title.english == "(C71) [Arisan-Antenna (Koari)] Eat The Rich! (Sukatto Golf Pangya)" + assert res.manga.title.japanese == None + assert res.manga.title.other == None def test_response_when_it_could_not_get_doujin(self): with patch('enma.infra.adapters.repositories.nhentai.NHentai.get') as mock_method: @@ -79,12 +106,12 @@ def test_return_none_when_not_receive_200_status_code(self): headers={'User-Agent': ''}, params={}, cookies={'cf_clearance': ''}) - + def test_return_empty_chapters(self): with patch('requests.get') as mock_method: mock = Mock() mock.status_code = 200 - + with open('./tests/data/get.json', 'r') as get: data = json.loads(get.read()) data['images']['pages'] = [] @@ -92,19 +119,19 @@ def test_return_empty_chapters(self): mock_method.return_value = mock - doujin = self.sut.execute(dto=DTO(data=GetMangaRequestDTO(identifier='2'))) + doujin = self.sut.execute(dto=DTO(data=GetMangaRequestDTO(identifier='1'))) assert doujin.found == True assert doujin.manga is not None assert isinstance(doujin.manga.chapters[0], Chapter) assert len(doujin.manga.chapters[0].pages) == 0 - assert doujin.manga.id == 1 - + assert doujin.manga.id == 1 + def test_return_right_mime(self): with patch('requests.get') as mock_method: mock = Mock() mock.status_code = 200 - + with open('./tests/data/get.json', 'r') as get: data = json.loads(get.read()) mock.json.return_value = data @@ -127,7 +154,7 @@ def test_get_with_symbolic_link(self): with patch('requests.get') as mock_method: mock = Mock() mock.status_code = 200 - + with open('./tests/data/get.json', 'r') as get: data = json.loads(get.read()) mock.json.return_value = data @@ -141,3 +168,52 @@ def test_get_with_symbolic_link(self): assert isinstance(doujin.manga.chapters[0], Chapter) assert doujin.manga.chapters[0].link is not None assert doujin.manga.chapters[0].link != "" + + def test_language_must_be_present(self): + with patch('requests.get') as mock_method: + mock = Mock() + mock.status_code = 200 + + with open('./tests/data/get.json', 'r') as get: + data = json.loads(get.read()) + mock.json.return_value = data + + mock_method.return_value = mock + + doujin = self.sut.execute(dto=DTO(data=GetMangaRequestDTO(identifier='420719', with_symbolic_links=True))) + + assert doujin.found == True + assert doujin.manga is not None + assert doujin.manga.language is not None + assert doujin.manga.language == 'japanese' + + def test_images_mime_types_must_be_correct(self): + with patch('requests.get') as mock_method: + mock = Mock() + mock.status_code = 200 + + with open('./tests/data/get.json', 'r') as get: + data: NHentaiResponse = json.loads(get.read()) + mock.json.return_value = data + + mock_method.return_value = mock + + doujin = self.sut.execute(dto=DTO(data=GetMangaRequestDTO(identifier='420719', with_symbolic_links=True))) + + assert doujin.found == True + assert doujin.manga is not None + + cover_mime = data['images']['cover']['t'] + thumb_mime = data['images']['thumbnail']['t'] + + assert cover_mime.upper() == doujin.manga.cover.mime.name + assert thumb_mime.upper() == doujin.manga.thumbnail.mime.name + + @patch('enma.application.use_cases.get_manga.GetMangaUseCase.execute') + def test_symbolic_links_must_be_disabled_by_default(self, use_case_mock: MagicMock): + self.sut.execute(dto=DTO(data=GetMangaRequestDTO(identifier='420719'))) + use_case_mock.assert_called_with(dto=DTO(data=GetMangaRequestDTO(identifier='420719', with_symbolic_links=False))) + + def test_must_raise_an_exception_case_user_has_provided_wrong_data_type(self): + with pytest.raises(ValidationError) as _: + self.sut.execute(dto=DTO(data=GetMangaRequestDTO(identifier='420719', with_symbolic_links='nao'))) \ No newline at end of file