def test_tag(api: Api, resp: RequestsMock) -> None: full_url = routes.tag(api.job_type, api.url) resp.add( responses.GET, url=full_url, json=[{ "label": "My Tag", "id": 0 }, { "label": "Other Tag", "id": 42 }], ) tags = api.tag() assert len(tags) == 2 assert tags[0].id == 0 resp.replace(responses.GET, url=full_url, json=[]) tags = api.tag() assert len(tags) == 0
def test_profile(api: Api, resp: RequestsMock) -> None: full_url = routes.profile(api.job_type, api.url) resp.add( responses.GET, url=full_url, json=[ { "name": "HD-1080p", "id": 0, "cuttoff": 1 }, { "name": "4K", "id": 42 }, ], ) profiles = api.profile() assert len(profiles) == 2 assert profiles[0].id == 0 resp.replace(responses.GET, url=full_url, json=[]) profiles = api.profile() assert len(profiles) == 0
def test_radarr_content(resp: RequestsMock, api: Api, file_body: str) -> None: url = "http://host/route" job_type = JobType.Radarr with Api(job_type=job_type, url=url, api_key="aaa") as api: full_url = routes.content(api.job_type, api.url) resp.add( responses.GET, url=full_url, body=file_body, content_type="application/json", ) content_items = api.content() assert len(content_items) > 0 item = content_items[0] assert isinstance(item, RadarrContent) assert isinstance(item.title, str) assert isinstance(item.tmdb_id, int) assert isinstance(item.year, int) assert len(item.images) > 0 assert hasattr(item.images[0], "remote_url") assert not hasattr(item.images[0], "url")
def test_api_post_fail(resp: RequestsMock, api: Api, body: Any, status: Any, expectation: Any) -> None: resp.add(responses.POST, url=api.url, body=body, status=status) with expectation: api.post(api.url, {})
def test_api_get_fail(resp: RequestsMock, api: Api, body: Union[str, None], status: int, expectation: Any) -> None: url = f"{api.url}test" resp.add(responses.GET, url=url, body=body, status=status) with expectation: api.get(url)
def start_sync_job(job: SyncJob, dry_run: bool = False) -> None: logger.debug("starting %s job", job.name) with Api( job_type=job.type, url=job.source_url, api_key=job.source_key, headers=job.source_headers, ) as source_api, Api( job_type=job.type, url=job.dest_url, api_key=job.dest_key, headers=job.dest_headers, ) as dest_api: source_status = source_api.status() dest_status = dest_api.status() if not source_status or not dest_status: logger.error("failed %s job", job.name) raise Exception("failed to check stauts") source_tags = source_api.tag() source_profiles = source_api.profile() dest_profiles = dest_api.profile() dest_metadata_profiles = dest_api.metadata() dest_languages = dest_api.language() source_content = source_api.content() dest_content = dest_api.content() content_diff = calculate_content_diff( job=job, source_content=source_content, source_tags=source_tags, source_profiles=source_profiles, dest_content=dest_content, ) content_payloads = get_content_payloads( job=job, content=content_diff, dest_profiles=dest_profiles, dest_metadata_profiles=dest_metadata_profiles, dest_languages=dest_languages, ) sync_content(content=content_payloads, dest_api=dest_api, dry_run=dry_run)
def test_metadata_profile(resp: RequestsMock) -> None: with Api(job_type=JobType.Sonarr, url="http://host/", api_key="aaa") as api: assert len(api.metadata()) == 0 with Api(job_type=JobType.Radarr, url="http://host/", api_key="aaa") as api: assert len(api.metadata()) == 0 with Api(job_type=JobType.Lidarr, url="http://host/", api_key="aaa") as api: full_url = routes.metadata(api.job_type, api.url) resp.add( responses.GET, url=full_url, json=[ { "name": "One", "id": 0, "releaseStatuses": [{ "allowed": True, "releaseStatus": { "id": 0, "name": "Unknown" } }], }, { "name": "Two", "id": 42 }, ], ) metadata = api.metadata() assert len(metadata) == 2 assert metadata[1].id == 42 assert not hasattr(metadata[0], "releaseStatuses") resp.replace(responses.GET, url=full_url, json=[]) metadata = api.metadata() assert len(metadata) == 0
def test_language(resp: RequestsMock) -> None: with Api(job_type=JobType.Lidarr, url="http://host/", api_key="aaa") as api: assert len(api.language()) == 0 with Api(job_type=JobType.Radarr, url="http://host/", api_key="aaa") as api: assert len(api.language()) == 0 with Api(job_type=JobType.Sonarr, url="http://host/", api_key="aaa") as api: full_url = routes.language(api.job_type, api.url) resp.add( responses.GET, url=full_url, json=[ { "name": "English", "id": 0, "languages": [{ "allowed": True, "language": { "id": 0, "name": "Unknown" } }], }, { "name": "Forign", "id": 42 }, ], ) languages = api.language() assert len(languages) == 2 assert languages[1].id == 42 assert not hasattr(languages[0], "languages") resp.replace(responses.GET, url=full_url, json=[]) languages = api.language() assert len(languages) == 0
def test_lidarr_content(resp: RequestsMock, file_body: str) -> None: url = "http://host/route" job_type = JobType.Lidarr with Api(job_type=job_type, url=url, api_key="aaa") as api: full_url = routes.content(api.job_type, api.url) resp.add( responses.GET, url=full_url, body=file_body, content_type="application/json", ) content_items = api.content() assert len(content_items) > 0 item = content_items[0] assert isinstance(item, LidarrContent) assert isinstance(item.artist_name, str) assert isinstance(item.foreign_artist_id, str) assert len(item.images) > 0 assert hasattr(item.images[0], "url")
def test_api_attaches_headers() -> None: with Api( job_type=JobType.Sonarr, url="http://host", api_key="aaa", headers={"X-My-Header": "yes"}, ) as api: assert api.session.headers.get("X-My-Other-Header") is None assert api.session.headers.get("X-My-Header") == "yes"
def test_status(api: Api, resp: RequestsMock) -> None: full_url = routes.status(api.job_type, api.url) resp.add(responses.GET, url=full_url, json={ "version": "3", "ignore": True }) status = api.status() assert status.version == "3" assert not hasattr(status, "ignore") resp.replace(responses.GET, url=full_url, json={}) with pytest.raises(ValidationError): api.status()
def test_api_get_success(resp: RequestsMock, api: Api, body: str) -> None: url = f"{api.url}test" resp.add( responses.GET, url=url, body=body, content_type="application/json", status=200, ) assert api.get(url) == json.loads(body)
def test_save_content(resp: RequestsMock, job_type: JobType, url: str, item: ContentItem) -> None: with Api(job_type=job_type, url=url, api_key="aaa") as api: full_url = routes.content(job_type=job_type, url=api.url) resp.add( responses.POST, url=full_url, json=item.dict(), match=[responses.json_params_matcher(item.dict(by_alias=True))], status=200, ) api.save(item)
def test_api_post_success(resp: RequestsMock, api: Api, json_dict: Any) -> None: url = f"{api.url}test" resp.add( responses.POST, url=url, json=json_dict, match=[responses.json_params_matcher(json_dict)], status=200, ) assert api.post(url, json_dict) == json_dict
def sync_content(content: ContentItems, dest_api: Api, dry_run: bool = False) -> None: for item in content: post_json = None if not dry_run: post_json = dest_api.save(content_item=item) if not post_json and not dry_run: logger.error("failed to sync %s", get_debug_title(item)) raise Exception(f"Failed to create {get_debug_title(item)}") else: logger.info("synced %s%s", get_debug_title(item), " (dry-run)" if dry_run else "")
def test_unknown_content(mocker: MockerFixture, resp: RequestsMock) -> None: mock_route = mocker.patch("arrsync.api.routes") url = "http://host" route = "v3/new/path" mock_route.content.return_value = f"{url}/{route}" unknown_job_type = cast(JobType, "unknown") with Api(job_type=unknown_job_type, url=url, api_key="aaa") as api: resp.add( responses.GET, json={}, url=f"{api.url}{route}", ) with pytest.raises(Exception): api.content()
def test_sync_content_dry_run( mocker: MockerFixture, job_type: JobType, create_content_item: CreateContentItem, ) -> None: item_one = create_content_item(job_type) content: ContentItems = [item_one] with Api(job_type=job_type, url="http://host", api_key="aaa") as api: dest_api = mocker.patch.object(target=api, attribute="save") dest_api.save.return_value = content[0].dict() sync_content(content=content, dest_api=dest_api, dry_run=True) dest_api.save.assert_not_called()
def test_sync_content( mocker: MockerFixture, job_type: JobType, create_content_item: CreateContentItem, ) -> None: item_one = create_content_item(job_type) content: ContentItems = [item_one] with Api(job_type=job_type, url="http://host", api_key="aaa") as api: dest_api = mocker.patch.object(target=api, attribute="save") dest_api.save.return_value = content[0].dict() sync_content(content=content, dest_api=dest_api) dest_api.save.assert_called_once_with(content_item=content[0]) dest_api.save.return_value = None with pytest.raises(Exception): sync_content(content=content, dest_api=dest_api)
def api(request: SubRequest) -> Iterator[Api]: url, job_type = request.param with Api(job_type=job_type, url=url, api_key="aaa") as api: yield api
def test_api_attaches_key() -> None: with Api(job_type=JobType.Sonarr, url="http://host", api_key="aaa") as api: assert api.session.headers.get("X-API-Key") == "aaa"
def tets_api_normalize_url(url: str) -> None: with Api(job_type=JobType.Sonarr, url=url, api_key="aaa") as api: assert api.url.endswith("/")