diff --git a/comictaggerlib/comiccacher.py b/comictaggerlib/comiccacher.py index f5ec4d0..e9abf41 100644 --- a/comictaggerlib/comiccacher.py +++ b/comictaggerlib/comiccacher.py @@ -133,7 +133,10 @@ class ComicCacher: cur = con.cursor() # remove all previous entries with this search term - cur.execute("DELETE FROM VolumeSearchCache WHERE search_term = ?", [search_term.casefold()]) + cur.execute( + "DELETE FROM VolumeSearchCache WHERE search_term = ? AND source_name = ?", + [search_term.casefold(), source_name], + ) # now add in new results for record in cv_search_results: @@ -212,7 +215,7 @@ class ComicCacher: # remove all previous entries with this search term cur.execute("DELETE FROM AltCovers WHERE issue_id=? AND source_name=?", [issue_id, source_name]) - url_list_str = ", ".join(url_list) + url_list_str = ",".join(url_list) # now add in new record cur.execute( "INSERT INTO AltCovers (source_name, issue_id, url_list) VALUES(?, ?, ?)", @@ -237,12 +240,9 @@ class ComicCacher: return [] url_list_str = row[0] - if len(url_list_str) == 0: + if not url_list_str: return [] - raw_list = url_list_str.split(",") - url_list = [] - for item in raw_list: - url_list.append(str(item).strip()) + url_list = url_list_str.split(",") return url_list def add_volume_info(self, source_name: str, cv_volume_record: CVVolumeResults) -> None: diff --git a/testing/comicdata.py b/testing/comicdata.py new file mode 100644 index 0000000..5033dfa --- /dev/null +++ b/testing/comicdata.py @@ -0,0 +1,107 @@ +from __future__ import annotations + +import dataclasses + +import comicapi.genericmetadata +import comictaggerlib.resulttypes +from comicapi import utils + +search_results = [ + comictaggerlib.resulttypes.CVVolumeResults( + count_of_issues=1, + description="this is a description", + id=1, + image={"super_url": "https://test.org/image/1"}, + name="test", + publisher=comictaggerlib.resulttypes.CVPublisher(name="test"), + start_year="", # This is currently submitted as a string and returned as an int + ), + comictaggerlib.resulttypes.CVVolumeResults( + count_of_issues=1, + description="this is a description", + id=1, + image={"super_url": "https://test.org/image/2"}, + name="test 2", + publisher=comictaggerlib.resulttypes.CVPublisher(name="test"), + start_year="", # This is currently submitted as a string and returned as an int + ), +] + +alt_covers = [ + {"issue_id": 1, "url_list": ["https://test.org/image/1"]}, + {"issue_id": 2, "url_list": ["https://test.org/image/2"]}, +] + +select_details = [ + { + "issue_id": 1, + "image_url": "https://test.org/image/1", + "thumb_image_url": "https://test.org/thumb/1", + "cover_date": "1998", + "site_detail_url": "https://test.org/1", + }, + { + "issue_id": 2, + "image_url": "https://test.org/image/2", + "thumb_image_url": "https://test.org/thumb/2", + "cover_date": "1998", + "site_detail_url": "https://test.org/2", + }, +] + +# Used to test GenericMetadata.overlay +metadata = [ + ( + comicapi.genericmetadata.GenericMetadata(series="test", issue="2", title="never"), + dataclasses.replace(comicapi.genericmetadata.md_test, series="test", issue="2", title="never"), + ), + ( + comicapi.genericmetadata.GenericMetadata(series="", issue="2", title="never"), + dataclasses.replace(comicapi.genericmetadata.md_test, series=None, issue="2", title="never"), + ), + ( + comicapi.genericmetadata.GenericMetadata(), + dataclasses.replace(comicapi.genericmetadata.md_test), + ), +] + +credits = [ + ("writer", "Dara Naraghi"), + ("writeR", "Dara Naraghi"), +] + +imprints = [ + ("marvel", ("", "Marvel")), + ("marvel comics", ("", "Marvel")), + ("aircel", ("Aircel Comics", "Marvel")), +] + +additional_imprints = [ + ("test", ("Test", "Marvel")), + ("temp", ("Temp", "DC Comics")), +] + +all_imprints = imprints + additional_imprints + +seed_imprints = { + "Marvel": utils.ImprintDict( + "Marvel", + { + "marvel comics": "", + "aircel": "Aircel Comics", + }, + ) +} + +additional_seed_imprints = { + "Marvel": utils.ImprintDict("Marvel", {"test": "Test"}), + "DC Comics": utils.ImprintDict("DC Comics", {"temp": "Temp"}), +} + +all_seed_imprints = { + "Marvel": seed_imprints["Marvel"].copy(), + "DC Comics": additional_seed_imprints["DC Comics"].copy(), +} +all_seed_imprints["Marvel"].update(additional_seed_imprints["Marvel"]) + +conflicting_seed_imprints = {"Marvel": {"test": "Never"}} diff --git a/testing/comicvine.py b/testing/comicvine.py new file mode 100644 index 0000000..d4532c9 --- /dev/null +++ b/testing/comicvine.py @@ -0,0 +1,173 @@ +from __future__ import annotations + +from typing import Any + +import comicapi.genericmetadata +import comictaggerlib.comicvinetalker + +cv_issue_result: dict[str, Any] = { + "error": "OK", + "limit": 1, + "offset": 0, + "number_of_page_results": 1, + "number_of_total_results": 1, + "status_code": 1, + "results": { + "aliases": None, + "api_detail_url": "https://comicvine.gamespot.com/api/issue/4000-311811/", + "associated_images": [], + "character_credits": [], + "character_died_in": [], + "concept_credits": [], + "cover_date": "2007-12-01", + "date_added": "2012-01-18 06:48:56", + "date_last_updated": "2012-01-18 17:27:48", + "deck": None, + "description": '
IDW Publishing continues to bring the stories of acclaimed science-fiction author Cory Doctorow to comics, this time offering up "Craphound." Despite the exoskeleton and mouth full of poisonous suckers, Jerry got along with the alien better than with most humans. In fact, Jerry had nicknamed him "Craphound", after their shared love of hunting for unique treasures at garage sales and thrift stores. They were buddies. That is, until Craphound found the old cowboy trunk. Adapted by Dara Naraghi (whose Lifelike debuts this month, too. See previous page for info), with a cover by Eisner-winning artist Paul Pope!
', + "first_appearance_characters": None, + "first_appearance_concepts": None, + "first_appearance_locations": None, + "first_appearance_objects": None, + "first_appearance_storyarcs": None, + "first_appearance_teams": None, + "has_staff_review": False, + "id": 311811, + "image": { + "icon_url": "https://comicvine.gamespot.com/a/uploads/square_avatar/11/115179/2165551-cd3.jpg", + "medium_url": "https://comicvine.gamespot.com/a/uploads/scale_medium/11/115179/2165551-cd3.jpg", + "screen_url": "https://comicvine.gamespot.com/a/uploads/screen_medium/11/115179/2165551-cd3.jpg", + "screen_large_url": "https://comicvine.gamespot.com/a/uploads/screen_kubrick/11/115179/2165551-cd3.jpg", + "small_url": "https://comicvine.gamespot.com/a/uploads/scale_small/11/115179/2165551-cd3.jpg", + "super_url": "https://comicvine.gamespot.com/a/uploads/scale_large/11/115179/2165551-cd3.jpg", + "thumb_url": "https://comicvine.gamespot.com/a/uploads/scale_avatar/11/115179/2165551-cd3.jpg", + "tiny_url": "https://comicvine.gamespot.com/a/uploads/square_mini/11/115179/2165551-cd3.jpg", + "original_url": "https://comicvine.gamespot.com/a/uploads/original/11/115179/2165551-cd3.jpg", + "image_tags": "All Images", + }, + "issue_number": "3", + "location_credits": [], + "name": "Craphound", + "object_credits": [], + "person_credits": [ + { + "api_detail_url": "https://comicvine.gamespot.com/api/person/4040-56410/", + "id": 56410, + "name": "Dara Naraghi", + "site_detail_url": "https://comicvine.gamespot.com/dara-naraghi/4040-56410/", + "role": "writer", + }, + { + "api_detail_url": "https://comicvine.gamespot.com/api/person/4040-4306/", + "id": 4306, + "name": "Paul Pope", + "site_detail_url": "https://comicvine.gamespot.com/paul-pope/4040-4306/", + "role": "cover", + }, + ], + "site_detail_url": "https://comicvine.gamespot.com/cory-doctorows-futuristic-tales-of-the-here-and-no/4000-311811/", + "store_date": None, + "story_arc_credits": [], + "team_credits": [], + "team_disbanded_in": [], + "volume": { + "api_detail_url": "https://comicvine.gamespot.com/api/volume/4050-23437/", + "id": 23437, + "name": "Cory Doctorow's Futuristic Tales of the Here and Now", + "site_detail_url": "https://comicvine.gamespot.com/cory-doctorows-futuristic-tales-of-the-here-and-no/4050-23437/", + }, + }, + "version": "1.0", +} + +cv_volume_result: dict[str, Any] = { + "error": "OK", + "limit": 1, + "offset": 0, + "number_of_page_results": 1, + "number_of_total_results": 1, + "status_code": 1, + "results": { + "count_of_issues": 6, + "id": 23437, + "name": "Cory Doctorow's Futuristic Tales of the Here and Now", + "publisher": { + "api_detail_url": "https://comicvine.gamespot.com/api/publisher/4010-1190/", + "id": 1190, + "name": "IDW Publishing", + }, + "start_year": "2007", + }, + "version": "1.0", +} +date = comictaggerlib.comicvinetalker.ComicVineTalker().parse_date_str(cv_issue_result["results"]["cover_date"]) + +cv_md = comicapi.genericmetadata.GenericMetadata( + is_empty=False, + tag_origin=None, + series=cv_issue_result["results"]["volume"]["name"], + issue=cv_issue_result["results"]["issue_number"], + title=cv_issue_result["results"]["name"], + publisher=cv_volume_result["results"]["publisher"]["name"], + month=date[1], + year=date[2], + day=date[0], + issue_count=None, + volume=None, + genre=None, + language=None, + comments=comictaggerlib.comicvinetalker.ComicVineTalker().cleanup_html( + cv_issue_result["results"]["description"], False + ), + volume_count=None, + critical_rating=None, + country=None, + alternate_series=None, + alternate_number=None, + alternate_count=None, + imprint=None, + notes="Tagged with ComicTagger 1.4.4a9.dev20 using info from Comic Vine on 2022-07-11 17:42:41. [Issue ID 311811]", + web_link=cv_issue_result["results"]["site_detail_url"], + format=None, + manga=None, + black_and_white=None, + page_count=None, + maturity_rating=None, + story_arc=None, + series_group=None, + scan_info=None, + characters="", + teams="", + locations="", + credits=[ + { + "person": cv_issue_result["results"]["person_credits"][0]["name"], + "role": cv_issue_result["results"]["person_credits"][0]["role"].title(), + "primary": False, + }, + { + "person": cv_issue_result["results"]["person_credits"][1]["name"], + "role": cv_issue_result["results"]["person_credits"][1]["role"].title(), + "primary": False, + }, + ], + tags=[], + pages=[], + price=None, + is_version_of=None, + rights=None, + identifier=None, + last_mark=None, + cover_image=None, +) + + +class MockResponse: + """Mocks the response object from requests""" + + def __init__(self, result: dict[str, Any]) -> None: + self.status_code = 200 + self.result = result + + # mock json() method always returns a specific testing dictionary + def json(self) -> dict[str, list]: + return self.result diff --git a/tests/data/Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game (2007).cbz b/testing/data/Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game (2007).cbz similarity index 100% rename from tests/data/Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game (2007).cbz rename to testing/data/Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game (2007).cbz diff --git a/tests/data/fake_cbr.cbr b/testing/data/fake_cbr.cbr similarity index 100% rename from tests/data/fake_cbr.cbr rename to testing/data/fake_cbr.cbr diff --git a/tests/autoimprint_test.py b/tests/autoimprint_test.py index 6fae3b6..bd76702 100644 --- a/tests/autoimprint_test.py +++ b/tests/autoimprint_test.py @@ -3,59 +3,7 @@ from __future__ import annotations import pytest from comicapi import utils - -imprints = [ - ("marvel", ("", "Marvel")), - ("marvel comics", ("", "Marvel")), - ("aircel", ("Aircel Comics", "Marvel")), -] - -additional_imprints = [ - ("test", ("Test", "Marvel")), - ("temp", ("Temp", "DC Comics")), -] - -all_imprints = imprints + additional_imprints - -seed = { - "Marvel": utils.ImprintDict( - "Marvel", - { - "marvel comics": "", - "aircel": "Aircel Comics", - }, - ) -} - -additional_seed = { - "Marvel": utils.ImprintDict("Marvel", {"test": "Test"}), - "DC Comics": utils.ImprintDict("DC Comics", {"temp": "Temp"}), -} - -all_seed = { - "Marvel": seed["Marvel"].copy(), - "DC Comics": additional_seed["DC Comics"].copy(), -} -all_seed["Marvel"].update(additional_seed["Marvel"]) - -conflicting_seed = {"Marvel": {"test": "Never"}} - - -# manually seeds publishers -@pytest.fixture -def seed_publishers(monkeypatch): - publisher_seed = {} - for publisher, imprint in seed.items(): - publisher_seed[publisher] = imprint - monkeypatch.setattr(utils, "publishers", publisher_seed) - - -@pytest.fixture -def seed_all_publishers(monkeypatch): - publisher_seed = {} - for publisher, imprint in all_seed.items(): - publisher_seed[publisher] = imprint - monkeypatch.setattr(utils, "publishers", publisher_seed) +from testing.comicdata import additional_seed_imprints, all_imprints, conflicting_seed_imprints, imprints, seed_imprints # test that that an empty list returns the input unchanged @@ -73,14 +21,14 @@ def test_get_publisher(publisher: str, expected: tuple[str, str], seed_publisher # tests that update_publishers will initially set values @pytest.mark.parametrize("publisher, expected", imprints) def test_set_publisher(publisher: str, expected: tuple[str, str]): - utils.update_publishers(seed) + utils.update_publishers(seed_imprints) assert expected == utils.get_publisher(publisher) # tests that update_publishers will add to existing values @pytest.mark.parametrize("publisher, expected", all_imprints) def test_update_publisher(publisher: str, expected: tuple[str, str], seed_publishers): - utils.update_publishers(additional_seed) + utils.update_publishers(additional_seed_imprints) assert expected == utils.get_publisher(publisher) @@ -88,6 +36,6 @@ def test_update_publisher(publisher: str, expected: tuple[str, str], seed_publis def test_conflict_publisher(seed_all_publishers): assert ("Test", "Marvel") == utils.get_publisher("test") - utils.update_publishers(conflicting_seed) + utils.update_publishers(conflicting_seed_imprints) assert ("Never", "Marvel") == utils.get_publisher("test") diff --git a/tests/comicarchive_test.py b/tests/comicarchive_test.py index 25618a8..dbd8e6d 100644 --- a/tests/comicarchive_test.py +++ b/tests/comicarchive_test.py @@ -7,14 +7,15 @@ import pytest import comicapi.comicarchive import comicapi.genericmetadata +import testing -thisdir = pathlib.Path(__file__).parent -cbz_path = thisdir / "data" / "Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game (2007).cbz" +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" @pytest.mark.xfail(not comicapi.comicarchive.rar_support, reason="rar support") def test_getPageNameList(): - c = comicapi.comicarchive.ComicArchive(thisdir / "data" / "fake_cbr.cbr") + c = comicapi.comicarchive.ComicArchive(datadir / "fake_cbr.cbr") pageNameList = c.get_page_name_list() assert pageNameList == [ @@ -44,9 +45,7 @@ def test_page_type_read(): def test_metadata_read(): - c = comicapi.comicarchive.ComicArchive( - thisdir / "data" / "Cory Doctorow's Futuristic Tales of the Here and Now #001 - Anda's Game (2007).cbz" - ) + c = comicapi.comicarchive.ComicArchive(cbz_path) md = c.read_cix() assert md == comicapi.genericmetadata.md_test diff --git a/tests/comiccacher_test.py b/tests/comiccacher_test.py new file mode 100644 index 0000000..be5d011 --- /dev/null +++ b/tests/comiccacher_test.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +import pytest + +import comictaggerlib.comiccacher +import comictaggerlib.resulttypes +from testing.comicdata import alt_covers, search_results, select_details + + +def test_create_cache(settings): + comictaggerlib.comiccacher.ComicCacher() + assert (settings.get_settings_folder() / "settings").exists() + + +def test_search_results(comic_cache): + comic_cache.add_search_results( + "test", + "test search", + search_results, + ) + assert search_results == comic_cache.get_search_results("test", "test search") + + +@pytest.mark.parametrize("alt_cover", alt_covers) +def test_alt_covers(comic_cache, alt_cover): + comic_cache.add_alt_covers(**alt_cover, source_name="test") + assert alt_cover["url_list"] == comic_cache.get_alt_covers(issue_id=alt_cover["issue_id"], source_name="test") + + +@pytest.mark.parametrize("volume_info", search_results) +def test_volume_info(comic_cache, volume_info): + comic_cache.add_volume_info(cv_volume_record=volume_info, source_name="test") + vi = volume_info.copy() + del vi["description"] + del vi["image"] + assert vi == comic_cache.get_volume_info(volume_id=volume_info["id"], source_name="test") + + +@pytest.mark.parametrize("details", select_details) +def test_issue_select_details(comic_cache, details): + comic_cache.add_issue_select_details(**details) + det = details.copy() + del det["issue_id"] + assert det == comic_cache.get_issue_select_details(details["issue_id"], "test") diff --git a/tests/comicvinetalker_test.py b/tests/comicvinetalker_test.py new file mode 100644 index 0000000..2d057f7 --- /dev/null +++ b/tests/comicvinetalker_test.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +import pytest + +import comictaggerlib.comicvinetalker +import testing.comicvine +from testing.comicdata import select_details + + +def test_fetch_volume_data(comicvine_api, settings, mock_now, comic_cache): + ct = comictaggerlib.comicvinetalker.ComicVineTalker() + volume = ct.fetch_volume_data(23437) + volume["start_year"] = int(volume["start_year"]) + del volume["publisher"]["id"] + del volume["publisher"]["api_detail_url"] + assert volume == comic_cache.get_volume_info(23437, ct.source_name) + + +def test_fetch_issue_data_by_issue_id(comicvine_api, settings, mock_now): + ct = comictaggerlib.comicvinetalker.ComicVineTalker() + md = ct.fetch_issue_data_by_issue_id(311811, settings) + assert md == testing.comicvine.cv_md + + +@pytest.mark.parametrize("details", select_details) +def test_issue_select_details(comic_cache, details): + ct = comictaggerlib.comicvinetalker.ComicVineTalker() + ct.cache_issue_select_details( + issue_id=details["issue_id"], + image_url=details["image_url"], + thumb_url=details["thumb_image_url"], + cover_date=details["cover_date"], + page_url=details["site_detail_url"], + ) + det = details.copy() + del det["issue_id"] + assert det == comic_cache.get_issue_select_details(details["issue_id"], ct.source_name) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..5f88470 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,88 @@ +from __future__ import annotations + +import dataclasses +import datetime +import unittest.mock +from typing import Any, Generator + +import pytest +import requests + +import comicapi.genericmetadata +import comictaggerlib.comiccacher +import comictaggerlib.comicvinetalker +import comictaggerlib.settings +from comicapi import utils +from testing import comicvine +from testing.comicdata import all_seed_imprints, seed_imprints + + +@pytest.fixture(autouse=True) +def no_requests(monkeypatch) -> None: + """Remove requests.sessions.Session.request for all tests.""" + monkeypatch.delattr("requests.sessions.Session.request") + + +@pytest.fixture +def comicvine_api(monkeypatch) -> unittest.mock.Mock: + # Any arguments may be passed and mock_get() will always return our + # mocked object, which only has the .json() method or None for invalid urls. + + def mock_get(*args, **kwargs): + + if args: + if args[0].startswith("https://comicvine.gamespot.com/api/volume/4050-23437"): + 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({}) + + m_get = unittest.mock.Mock(side_effect=mock_get) + + # apply the monkeypatch for requests.get to mock_get + monkeypatch.setattr(requests, "get", m_get) + return m_get + + +@pytest.fixture +def mock_now(monkeypatch): + class mydatetime: + time = datetime.datetime(2022, 7, 11, 17, 42, 41) + + @classmethod + def now(cls): + return cls.time + + monkeypatch.setattr(comictaggerlib.comicvinetalker, "datetime", mydatetime) + + +@pytest.fixture +def md(): + yield dataclasses.replace(comicapi.genericmetadata.md_test) + + +# manually seeds publishers +@pytest.fixture +def seed_publishers(monkeypatch): + publisher_seed = {} + for publisher, imprint in seed_imprints.items(): + publisher_seed[publisher] = imprint + monkeypatch.setattr(utils, "publishers", publisher_seed) + + +@pytest.fixture +def seed_all_publishers(monkeypatch): + publisher_seed = {} + for publisher, imprint in all_seed_imprints.items(): + publisher_seed[publisher] = imprint + monkeypatch.setattr(utils, "publishers", publisher_seed) + + +@pytest.fixture +def settings(tmp_path): + yield comictaggerlib.settings.ComicTaggerSettings(tmp_path / "settings") + + +@pytest.fixture +def comic_cache(settings) -> Generator[comictaggerlib.comiccacher.ComicCacher, Any, None]: + yield comictaggerlib.comiccacher.ComicCacher() diff --git a/tests/genericmetadata_test.py b/tests/genericmetadata_test.py index 16683fa..fac746f 100644 --- a/tests/genericmetadata_test.py +++ b/tests/genericmetadata_test.py @@ -1,37 +1,14 @@ from __future__ import annotations -import dataclasses - import pytest import comicapi.genericmetadata +from testing.comicdata import credits, metadata -@pytest.fixture -def md(): - yield dataclasses.replace(comicapi.genericmetadata.md_test) - - -stuff = [ - ( - {"series": "test", "issue": "2", "title": "never"}, - dataclasses.replace(comicapi.genericmetadata.md_test, series="test", issue="2", title="never"), - ), - ( - {"series": "", "issue": "2", "title": "never"}, - dataclasses.replace(comicapi.genericmetadata.md_test, series=None, issue="2", title="never"), - ), - ( - {}, - dataclasses.replace(comicapi.genericmetadata.md_test), - ), -] - - -@pytest.mark.parametrize("replaced, expected", stuff) +@pytest.mark.parametrize("replaced, expected", metadata) def test_metadata_overlay(md: comicapi.genericmetadata.GenericMetadata, replaced, expected): - md_overlay = comicapi.genericmetadata.GenericMetadata(**replaced) - md.overlay(md_overlay) + md.overlay(replaced) assert md == expected @@ -51,12 +28,6 @@ def test_add_credit_primary(): md.credits == [{"person": "test", "role": "writer", "primary": True}] -credits = [ - ("writer", "Dara Naraghi"), - ("writeR", "Dara Naraghi"), -] - - @pytest.mark.parametrize("role, expected", credits) def test_get_primary_credit(md, role, expected): assert md.get_primary_credit(role) == expected