diff --git a/comictaggerlib/comicvinetalker.py b/comictaggerlib/comicvinetalker.py index 8b845f1..c5cb236 100644 --- a/comictaggerlib/comicvinetalker.py +++ b/comictaggerlib/comicvinetalker.py @@ -399,7 +399,7 @@ class ComicVineTalker: "filter": flt, } - cv_response = self.get_cv_content(self.api_base_url + "/issues", params) + cv_response = self.get_cv_content(self.api_base_url + "/issues/", params) current_result_count = cv_response["number_of_page_results"] total_result_count = cv_response["number_of_total_results"] @@ -429,7 +429,7 @@ class ComicVineTalker: f_record = None for record in issues_list_results: - if IssueString(issue_number).as_string() is None: + if not IssueString(issue_number).as_string(): issue_number = "1" if ( IssueString(record["issue_number"]).as_string().casefold() diff --git a/comictaggerlib/filerenamer.py b/comictaggerlib/filerenamer.py index 7019f96..0fa8507 100644 --- a/comictaggerlib/filerenamer.py +++ b/comictaggerlib/filerenamer.py @@ -90,17 +90,9 @@ class MetadataFormatter(string.Formatter): field_name = field_name.casefold() # this is some markup, find the object and do the formatting - # handle arg indexing when empty field_names are given. - if field_name == "": - if auto_arg_index is False: - raise ValueError("cannot switch from manual field specification to automatic field numbering") - field_name = str(auto_arg_index) - auto_arg_index += 1 - elif field_name.isdigit(): - if auto_arg_index: - raise ValueError("cannot switch from manual field specification to automatic field numbering") - # disable auto arg incrementing, if it gets used later on, then an exception will be raised - auto_arg_index = False + # handle arg indexing when digit field_names are given. + if field_name.isdigit(): + raise ValueError("cannot use a number as a field name") # given the field_name, find the object it references # and the argument it came from @@ -111,8 +103,8 @@ class MetadataFormatter(string.Formatter): obj = self.convert_field(obj, conversion) # type: ignore # expand the format spec, if needed - format_spec, auto_arg_index = self._vformat( - cast(str, format_spec), args, kwargs, used_args, recursion_depth - 1, auto_arg_index=auto_arg_index + format_spec, _ = self._vformat( + cast(str, format_spec), args, kwargs, used_args, recursion_depth - 1, auto_arg_index=False ) # format the object and append to the result @@ -128,7 +120,7 @@ class MetadataFormatter(string.Formatter): fmt_obj = str(sanitize_filename(fmt_obj, platform=self.platform)) result.append(fmt_obj) - return "".join(result), auto_arg_index + return "".join(result), False class FileRenamer: diff --git a/comictaggerlib/volumeselectionwindow.py b/comictaggerlib/volumeselectionwindow.py index 328e2d0..df1be7b 100644 --- a/comictaggerlib/volumeselectionwindow.py +++ b/comictaggerlib/volumeselectionwindow.py @@ -19,7 +19,7 @@ import itertools import logging from collections import deque -from PyQt5 import QtCore, QtWidgets, uic +from PyQt5 import QtCore, QtGui, QtWidgets, uic from PyQt5.QtCore import pyqtSignal from comicapi import utils @@ -452,15 +452,16 @@ class VolumeSelectionWindow(QtWidgets.QDialog): self.twList.selectRow(0) self.twList.resizeColumnsToContents() - if not self.cv_search_results: - QtCore.QCoreApplication.processEvents() - QtWidgets.QMessageBox.information(self, "Search Result", "No matches found!") - QtCore.QTimer.singleShot(200, self.close_me) + def showEvent(self, event: QtGui.QShowEvent): + if not self.cv_search_results: + QtCore.QCoreApplication.processEvents() + QtWidgets.QMessageBox.information(self, "Search Result", "No matches found!") + QtCore.QTimer.singleShot(200, self.close_me) - if self.immediate_autoselect and self.cv_search_results: - # defer the immediate autoselect so this dialog has time to pop up - QtCore.QCoreApplication.processEvents() - QtCore.QTimer.singleShot(10, self.do_immediate_autoselect) + elif self.immediate_autoselect: + # defer the immediate autoselect so this dialog has time to pop up + QtCore.QCoreApplication.processEvents() + QtCore.QTimer.singleShot(10, self.do_immediate_autoselect) def do_immediate_autoselect(self) -> None: self.immediate_autoselect = False diff --git a/testing/comicvine.py b/testing/comicvine.py index d4532c9..32bf460 100644 --- a/testing/comicvine.py +++ b/testing/comicvine.py @@ -5,6 +5,14 @@ from typing import Any import comicapi.genericmetadata import comictaggerlib.comicvinetalker + +def filter_field_list(cv_result, kwargs): + if "field_list" in kwargs["params"]: + for key in list(cv_result.keys()): + if key not in kwargs["params"]["field_list"]: + del cv_result[key] + + cv_issue_result: dict[str, Any] = { "error": "OK", "limit": 1, @@ -99,6 +107,15 @@ cv_volume_result: dict[str, Any] = { }, "version": "1.0", } +cv_not_found = { + "error": "Object Not Found", + "limit": 0, + "offset": 0, + "number_of_page_results": 0, + "number_of_total_results": 0, + "status_code": 101, + "results": [], +} date = comictaggerlib.comicvinetalker.ComicVineTalker().parse_date_str(cv_issue_result["results"]["cover_date"]) cv_md = comicapi.genericmetadata.GenericMetadata( diff --git a/testing/filenames.py b/testing/filenames.py index 1e4c3ee..57902a6 100644 --- a/testing/filenames.py +++ b/testing/filenames.py @@ -11,6 +11,16 @@ format is """ from __future__ import annotations +import os +import os.path +import pathlib +from contextlib import nullcontext as does_not_raise + +import pytest + +datadir = pathlib.Path(__file__).parent / "data" +cbz_path = datadir / "Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game (2007).cbz" + fnames = [ ( "batman 3 title (DC).cbz", @@ -731,89 +741,125 @@ rnames = [ False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game (2007).cbz", + does_not_raise(), ), ( "{series} #{issue} - {title} ({year})({price})", # price should be none, test no space between ')(' False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game (2007).cbz", + does_not_raise(), ), ( "{series} #{issue} - {title} ({year}) ({price})", # price should be none, test double space ') (' False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game (2007).cbz", + does_not_raise(), ), ( "{series} #{issue} - {title} ({year})", False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game (2007).cbz", + does_not_raise(), ), ( "{series}: {title} #{issue} ({year})", # on windows the ':' is replaced False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now - Anda's Game #001 (2007).cbz", + does_not_raise(), ), ( "{series}: {title} #{issue} ({year})", # on linux the ':' is preserved False, "Linux", "Cory Doctorow's Futuristic Tales of the Here and Now: Anda's Game #001 (2007).cbz", + does_not_raise(), ), ( "{publisher}/ {series} #{issue} - {title} ({year})", # leading whitespace is removed when moving True, "universal", "IDW Publishing/Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game (2007).cbz", + does_not_raise(), ), ( "{publisher}/ {series} #{issue} - {title} ({year})", # leading whitespace is removed when only renaming False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game (2007).cbz", + does_not_raise(), ), ( r"{publisher}\ {series} #{issue} - {title} ({year})", # backslashes separate directories False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game (2007).cbz", + does_not_raise(), ), ( "{series} # {issue} - {title} ({year})", # double spaces are reduced to one False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now # 001 - Anda's Game (2007).cbz", + does_not_raise(), ), ( "{series} # {issue} - {locations} ({year})", False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now # 001 - lonely cottage (2007).cbz", + does_not_raise(), ), ( "{series} #{issue} - {title} - {WriteR}, {EDITOR} ({year})", # fields are case in-sensitive False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game - Dara Naraghi, Ted Adams (2007).cbz", + does_not_raise(), ), ( "{series} v{price} #{issue} ({year})", # Remove previous text if value is "" False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now #001 (2007).cbz", + does_not_raise(), ), ( "{series} {price} #{issue} ({year})", # Ensure that a single space remains False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now #001 (2007).cbz", + does_not_raise(), ), ( "{series} - {title}{price} #{issue} ({year})", # Ensure removal before None values only impacts literal text False, "universal", "Cory Doctorow's Futuristic Tales of the Here and Now - Anda's Game #001 (2007).cbz", + does_not_raise(), + ), + ( + "{series} - {title} {test} #{issue} ({year})", # Test non-existent key + False, + "universal", + "Cory Doctorow's Futuristic Tales of the Here and Now - Anda's Game {test} #001 (2007).cbz", + does_not_raise(), + ), + ( + "{series} - {title} {1} #{issue} ({year})", # Test numeric key + False, + "universal", + "Cory Doctorow's Futuristic Tales of the Here and Now - Anda's Game {test} #001 (2007).cbz", + pytest.raises(ValueError), ), ] + +rfnames = [ + (None, lambda x: x.path.parent.absolute()), + ("", lambda x: pathlib.Path(os.getcwd())), + ("test", lambda x: (pathlib.Path(os.getcwd()) / "test")), + (pathlib.Path(os.getcwd()) / "test", lambda x: pathlib.Path(os.getcwd()) / "test"), +] diff --git a/tests/comicarchive_test.py b/tests/comicarchive_test.py index dbd8e6d..9c132aa 100644 --- a/tests/comicarchive_test.py +++ b/tests/comicarchive_test.py @@ -1,16 +1,12 @@ from __future__ import annotations -import pathlib import shutil import pytest import comicapi.comicarchive import comicapi.genericmetadata -import testing - -datadir = pathlib.Path(testing.__file__).parent / "data" -cbz_path = datadir / "Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game (2007).cbz" +from testing.filenames import cbz_path, datadir @pytest.mark.xfail(not comicapi.comicarchive.rar_support, reason="rar support") @@ -28,25 +24,14 @@ def test_getPageNameList(): ] -def test_set_default_page_list(tmp_path): - md = comicapi.genericmetadata.GenericMetadata() - md.overlay(comicapi.genericmetadata.md_test) - md.pages = [] - md.set_default_page_list(len(comicapi.genericmetadata.md_test.pages)) - - assert isinstance(md.pages[0]["Image"], int) - - -def test_page_type_read(): - c = comicapi.comicarchive.ComicArchive(cbz_path) - md = c.read_cix() +def test_page_type_read(cbz): + md = cbz.read_cix() assert isinstance(md.pages[0]["Type"], str) -def test_metadata_read(): - c = comicapi.comicarchive.ComicArchive(cbz_path) - md = c.read_cix() +def test_metadata_read(cbz): + md = cbz.read_cix() assert md == comicapi.genericmetadata.md_test @@ -101,10 +86,9 @@ archivers = [ @pytest.mark.parametrize("archiver", archivers) -def test_copy_to_archive(archiver, tmp_path): - comic_path = tmp_path / cbz_path.with_suffix("").name +def test_copy_to_archive(archiver, tmp_path, cbz): + comic_path = tmp_path / cbz.path.with_suffix("").name - cbz = comicapi.comicarchive.ComicArchive(cbz_path) archive = archiver(comic_path) assert archive.copy_from_archive(cbz.archiver) diff --git a/tests/comicvinetalker_test.py b/tests/comicvinetalker_test.py index 56b0ce8..7b8f35f 100644 --- a/tests/comicvinetalker_test.py +++ b/tests/comicvinetalker_test.py @@ -2,6 +2,7 @@ from __future__ import annotations import pytest +import comicapi.genericmetadata import comictaggerlib.comicvinetalker import testing.comicvine from testing.comicdata import select_details @@ -16,12 +17,60 @@ def test_fetch_volume_data(comicvine_api, settings, mock_now, comic_cache): assert volume == comic_cache.get_volume_info(23437, ct.source_name) +def test_fetch_issues_by_volume(comicvine_api, settings, comic_cache): + ct = comictaggerlib.comicvinetalker.ComicVineTalker() + issues = ct.fetch_issues_by_volume(23437) + cache_issues = comic_cache.get_volume_issues_info(23437, ct.source_name) + + issues[0]["image"] = {"super_url": issues[0]["image"]["super_url"], "thumb_url": issues[0]["image"]["thumb_url"]} + del issues[0]["volume"] + assert issues == cache_issues + + def test_fetch_issue_data_by_issue_id(comicvine_api, settings, mock_now, mock_version): ct = comictaggerlib.comicvinetalker.ComicVineTalker() md = ct.fetch_issue_data_by_issue_id(311811, settings) assert md == testing.comicvine.cv_md +def test_fetch_issues_by_volume_issue_num_and_year(comicvine_api): + ct = comictaggerlib.comicvinetalker.ComicVineTalker() + cv = ct.fetch_issues_by_volume_issue_num_and_year([23437], "3", None) + cv_expected = testing.comicvine.cv_issue_result["results"].copy() + testing.comicvine.filter_field_list( + cv_expected, + {"params": {"field_list": "id,volume,issue_number,name,image,cover_date,site_detail_url,description"}}, + ) + assert cv[0] == cv_expected + + +cv_issue = [ + (23437, "3", testing.comicvine.cv_md), + (23437, "", comicapi.genericmetadata.GenericMetadata()), + (23437, "0", comicapi.genericmetadata.GenericMetadata()), +] + + +@pytest.mark.parametrize("volume_id, issue_number, result_md", cv_issue) +def test_fetch_issue_data(comicvine_api, settings, mock_now, mock_version, volume_id, issue_number, result_md): + ct = comictaggerlib.comicvinetalker.ComicVineTalker() + md = ct.fetch_issue_data(volume_id, issue_number, settings) + assert md == result_md + + +# @pytest.mark.parametrize("volume_id, issue_number, result_md", cv_issue) +def test_fetch_issue_select_details(comicvine_api, settings, mock_now, mock_version): + ct = comictaggerlib.comicvinetalker.ComicVineTalker() + md = ct.fetch_issue_select_details(311811) + res = { + "cover_date": testing.comicvine.cv_issue_result["results"]["cover_date"], + "site_detail_url": testing.comicvine.cv_issue_result["results"]["site_detail_url"], + "image_url": testing.comicvine.cv_issue_result["results"]["image"]["super_url"], + "thumb_image_url": testing.comicvine.cv_issue_result["results"]["image"]["thumb_url"], + } + assert md == res + + @pytest.mark.parametrize("details", select_details) def test_issue_select_details(comic_cache, details): ct = comictaggerlib.comicvinetalker.ComicVineTalker() diff --git a/tests/conftest.py b/tests/conftest.py index f3a9659..6c03d55 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,15 +8,21 @@ from typing import Any, Generator import pytest import requests +import comicapi.comicarchive import comicapi.genericmetadata import comictaggerlib.comiccacher import comictaggerlib.comicvinetalker import comictaggerlib.settings from comicapi import utils -from testing import comicvine +from testing import comicvine, filenames from testing.comicdata import all_seed_imprints, seed_imprints +@pytest.fixture +def cbz(): + yield comicapi.comicarchive.ComicArchive(filenames.cbz_path) + + @pytest.fixture(autouse=True) def no_requests(monkeypatch) -> None: """Remove requests.sessions.Session.request for all tests.""" @@ -35,7 +41,18 @@ def comicvine_api(monkeypatch) -> unittest.mock.Mock: return comicvine.MockResponse(comicvine.cv_volume_result) if args[0].startswith("https://comicvine.gamespot.com/api/issue/4000-311811"): return comicvine.MockResponse(comicvine.cv_issue_result) - return comicvine.MockResponse({}) + if ( + args[0].startswith("https://comicvine.gamespot.com/api/issues/") + and "params" in kwargs + and "filter" in kwargs["params"] + and "23437" in kwargs["params"]["filter"] + ): + cv_list = comicvine.cv_issue_result.copy() + cv_list["results"] = cv_list["results"].copy() + comicvine.filter_field_list(cv_list["results"], kwargs) + cv_list["results"] = [cv_list["results"]] + return comicvine.MockResponse(cv_list) + return comicvine.MockResponse(comicvine.cv_not_found) m_get = unittest.mock.Mock(side_effect=mock_get) diff --git a/tests/genericmetadata_test.py b/tests/genericmetadata_test.py index fac746f..e753447 100644 --- a/tests/genericmetadata_test.py +++ b/tests/genericmetadata_test.py @@ -6,6 +6,15 @@ import comicapi.genericmetadata from testing.comicdata import credits, metadata +def test_set_default_page_list(tmp_path): + md = comicapi.genericmetadata.GenericMetadata() + md.overlay(comicapi.genericmetadata.md_test) + md.pages = [] + md.set_default_page_list(len(comicapi.genericmetadata.md_test.pages)) + + assert isinstance(md.pages[0]["Image"], int) + + @pytest.mark.parametrize("replaced, expected", metadata) def test_metadata_overlay(md: comicapi.genericmetadata.GenericMetadata, replaced, expected): md.overlay(replaced) diff --git a/tests/rename_test.py b/tests/rename_test.py index 8f6ae5a..ab71c82 100644 --- a/tests/rename_test.py +++ b/tests/rename_test.py @@ -5,13 +5,19 @@ import pathlib import pytest from comicapi.genericmetadata import md_test -from comictaggerlib.filerenamer import FileRenamer -from testing.filenames import rnames +from comictaggerlib import filerenamer +from testing.filenames import rfnames, rnames -@pytest.mark.parametrize("template, move, platform, expected", rnames) -def test_rename(template, platform, move, expected): - fr = FileRenamer(md_test, platform=platform) +@pytest.mark.parametrize("template, move, platform, expected, exception", rnames) +def test_rename(template, platform, move, expected, exception): + fr = filerenamer.FileRenamer(md_test, platform=platform) fr.move = move fr.set_template(template) - assert str(pathlib.PureWindowsPath(fr.determine_name(".cbz"))) == str(pathlib.PureWindowsPath(expected)) + with exception: + assert str(pathlib.PureWindowsPath(fr.determine_name(".cbz"))) == str(pathlib.PureWindowsPath(expected)) + + +@pytest.mark.parametrize("inp, result", rfnames) +def test_get_rename_dir(inp, result, cbz): + assert result(cbz) == filerenamer.get_rename_dir(cbz, inp)