"""The tests for the Sonarr platform.""" from datetime import datetime import time import unittest import pytest import homeassistant.components.sonarr.sensor as sonarr from tests.common import get_test_home_assistant def mocked_exception(*args, **kwargs): """Mock exception thrown by requests.get.""" raise OSError def mocked_requests_get(*args, **kwargs): """Mock requests.get invocations.""" class MockResponse: """Class to represent a mocked response.""" def __init__(self, json_data, status_code): """Initialize the mock response class.""" self.json_data = json_data self.status_code = status_code def json(self): """Return the json of the response.""" return self.json_data today = datetime.date(datetime.fromtimestamp(time.time())) url = str(args[0]) if "api/calendar" in url: return MockResponse( [ { "seriesId": 3, "episodeFileId": 0, "seasonNumber": 4, "episodeNumber": 11, "title": "Easy Com-mercial, Easy Go-mercial", "airDate": str(today), "airDateUtc": "2014-01-27T01:30:00Z", "overview": "To compete with fellow “restaurateur,” Ji...", "hasFile": "false", "monitored": "true", "sceneEpisodeNumber": 0, "sceneSeasonNumber": 0, "tvDbEpisodeId": 0, "series": { "tvdbId": 194031, "tvRageId": 24607, "imdbId": "tt1561755", "title": "Bob's Burgers", "cleanTitle": "bobsburgers", "status": "continuing", "overview": "Bob's Burgers follows a third-generation ...", "airTime": "5:30pm", "monitored": "true", "qualityProfileId": 1, "seasonFolder": "true", "lastInfoSync": "2014-01-26T19:25:55.4555946Z", "runtime": 30, "images": [ { "coverType": "banner", "url": "http://slurm.trakt.us/images/bann.jpg", }, { "coverType": "poster", "url": "http://slurm.trakt.us/images/poster00.jpg", }, { "coverType": "fanart", "url": "http://slurm.trakt.us/images/fan6.jpg", }, ], "seriesType": "standard", "network": "FOX", "useSceneNumbering": "false", "titleSlug": "bobs-burgers", "path": "T:\\Bob's Burgers", "year": 0, "firstAired": "2011-01-10T01:30:00Z", "qualityProfile": { "value": { "name": "SD", "allowed": [ {"id": 1, "name": "SDTV", "weight": 1}, {"id": 8, "name": "WEBDL-480p", "weight": 2}, {"id": 2, "name": "DVD", "weight": 3}, ], "cutoff": {"id": 1, "name": "SDTV", "weight": 1}, "id": 1, }, "isLoaded": "true", }, "seasons": [ {"seasonNumber": 4, "monitored": "true"}, {"seasonNumber": 3, "monitored": "true"}, {"seasonNumber": 2, "monitored": "true"}, {"seasonNumber": 1, "monitored": "true"}, {"seasonNumber": 0, "monitored": "false"}, ], "id": 66, }, "downloading": "false", "id": 14402, } ], 200, ) if "api/command" in url: return MockResponse( [ { "name": "RescanSeries", "startedOn": "0001-01-01T00:00:00Z", "stateChangeTime": "2014-02-05T05:09:09.2366139Z", "sendUpdatesToClient": "true", "state": "pending", "id": 24, } ], 200, ) if "api/wanted/missing" in url or "totalRecords" in url: return MockResponse( { "page": 1, "pageSize": 15, "sortKey": "airDateUtc", "sortDirection": "descending", "totalRecords": 1, "records": [ { "seriesId": 1, "episodeFileId": 0, "seasonNumber": 5, "episodeNumber": 4, "title": "Archer Vice: House Call", "airDate": "2014-02-03", "airDateUtc": "2014-02-04T03:00:00Z", "overview": "Archer has to stage an that ... ", "hasFile": "false", "monitored": "true", "sceneEpisodeNumber": 0, "sceneSeasonNumber": 0, "tvDbEpisodeId": 0, "absoluteEpisodeNumber": 50, "series": { "tvdbId": 110381, "tvRageId": 23354, "imdbId": "tt1486217", "title": "Archer (2009)", "cleanTitle": "archer2009", "status": "continuing", "overview": "At ISIS, an international spy ...", "airTime": "7:00pm", "monitored": "true", "qualityProfileId": 1, "seasonFolder": "true", "lastInfoSync": "2014-02-05T04:39:28.550495Z", "runtime": 30, "images": [ { "coverType": "banner", "url": "http://slurm.trakt.us//57.12.jpg", }, { "coverType": "poster", "url": "http://slurm.trakt.u/57.12-300.jpg", }, { "coverType": "fanart", "url": "http://slurm.trakt.us/image.12.jpg", }, ], "seriesType": "standard", "network": "FX", "useSceneNumbering": "false", "titleSlug": "archer-2009", "path": "E:\\Test\\TV\\Archer (2009)", "year": 2009, "firstAired": "2009-09-18T02:00:00Z", "qualityProfile": { "value": { "name": "SD", "cutoff": {"id": 1, "name": "SDTV"}, "items": [ { "quality": {"id": 1, "name": "SDTV"}, "allowed": "true", }, { "quality": {"id": 8, "name": "WEBDL-480p"}, "allowed": "true", }, { "quality": {"id": 2, "name": "DVD"}, "allowed": "true", }, { "quality": {"id": 4, "name": "HDTV-720p"}, "allowed": "false", }, { "quality": {"id": 9, "name": "HDTV-1080p"}, "allowed": "false", }, { "quality": {"id": 10, "name": "Raw-HD"}, "allowed": "false", }, { "quality": {"id": 5, "name": "WEBDL-720p"}, "allowed": "false", }, { "quality": {"id": 6, "name": "Bluray-720p"}, "allowed": "false", }, { "quality": {"id": 3, "name": "WEBDL-1080p"}, "allowed": "false", }, { "quality": { "id": 7, "name": "Bluray-1080p", }, "allowed": "false", }, ], "id": 1, }, "isLoaded": "true", }, "seasons": [ {"seasonNumber": 5, "monitored": "true"}, {"seasonNumber": 4, "monitored": "true"}, {"seasonNumber": 3, "monitored": "true"}, {"seasonNumber": 2, "monitored": "true"}, {"seasonNumber": 1, "monitored": "true"}, {"seasonNumber": 0, "monitored": "false"}, ], "id": 1, }, "downloading": "false", "id": 55, } ], }, 200, ) if "api/queue" in url: return MockResponse( [ { "series": { "title": "Game of Thrones", "sortTitle": "game thrones", "seasonCount": 6, "status": "continuing", "overview": "Seven noble families fight for land ...", "network": "HBO", "airTime": "21:00", "images": [ { "coverType": "fanart", "url": "http://thetvdb.com/banners/fanart/-83.jpg", }, { "coverType": "banner", "url": "http://thetvdb.com/banners/-g19.jpg", }, { "coverType": "poster", "url": "http://thetvdb.com/banners/posters-34.jpg", }, ], "seasons": [ {"seasonNumber": 0, "monitored": "false"}, {"seasonNumber": 1, "monitored": "false"}, {"seasonNumber": 2, "monitored": "true"}, {"seasonNumber": 3, "monitored": "false"}, {"seasonNumber": 4, "monitored": "false"}, {"seasonNumber": 5, "monitored": "true"}, {"seasonNumber": 6, "monitored": "true"}, ], "year": 2011, "path": "/Volumes/Media/Shows/Game of Thrones", "profileId": 5, "seasonFolder": "true", "monitored": "true", "useSceneNumbering": "false", "runtime": 60, "tvdbId": 121361, "tvRageId": 24493, "tvMazeId": 82, "firstAired": "2011-04-16T23:00:00Z", "lastInfoSync": "2016-02-05T16:40:11.614176Z", "seriesType": "standard", "cleanTitle": "gamethrones", "imdbId": "tt0944947", "titleSlug": "game-of-thrones", "certification": "TV-MA", "genres": ["Adventure", "Drama", "Fantasy"], "tags": [], "added": "2015-12-28T13:44:24.204583Z", "ratings": {"votes": 1128, "value": 9.4}, "qualityProfileId": 5, "id": 17, }, "episode": { "seriesId": 17, "episodeFileId": 0, "seasonNumber": 3, "episodeNumber": 8, "title": "Second Sons", "airDate": "2013-05-19", "airDateUtc": "2013-05-20T01:00:00Z", "overview": "King’s Landing hosts a wedding, and ...", "hasFile": "false", "monitored": "false", "absoluteEpisodeNumber": 28, "unverifiedSceneNumbering": "false", "id": 889, }, "quality": { "quality": {"id": 7, "name": "Bluray-1080p"}, "revision": {"version": 1, "real": 0}, }, "size": 4472186820, "title": "Game.of.Thrones.S03E08.Second.Sons.2013.1080p.", "sizeleft": 0, "timeleft": "00:00:00", "estimatedCompletionTime": "2016-02-05T22:46:52.440104Z", "status": "Downloading", "trackedDownloadStatus": "Ok", "statusMessages": [], "downloadId": "SABnzbd_nzo_Mq2f_b", "protocol": "usenet", "id": 1503378561, } ], 200, ) if "api/series" in url: return MockResponse( [ { "title": "Marvel's Daredevil", "alternateTitles": [{"title": "Daredevil", "seasonNumber": -1}], "sortTitle": "marvels daredevil", "seasonCount": 2, "totalEpisodeCount": 26, "episodeCount": 26, "episodeFileCount": 26, "sizeOnDisk": 79282273693, "status": "continuing", "overview": "Matt Murdock was blinded in a tragic accident...", "previousAiring": "2016-03-18T04:01:00Z", "network": "Netflix", "airTime": "00:01", "images": [ { "coverType": "fanart", "url": "/sonarr/MediaCover/7/fanart.jpg?lastWrite=", }, { "coverType": "banner", "url": "/sonarr/MediaCover/7/banner.jpg?lastWrite=", }, { "coverType": "poster", "url": "/sonarr/MediaCover/7/poster.jpg?lastWrite=", }, ], "seasons": [ { "seasonNumber": 1, "monitored": "false", "statistics": { "previousAiring": "2015-04-10T04:01:00Z", "episodeFileCount": 13, "episodeCount": 13, "totalEpisodeCount": 13, "sizeOnDisk": 22738179333, "percentOfEpisodes": 100, }, }, { "seasonNumber": 2, "monitored": "false", "statistics": { "previousAiring": "2016-03-18T04:01:00Z", "episodeFileCount": 13, "episodeCount": 13, "totalEpisodeCount": 13, "sizeOnDisk": 56544094360, "percentOfEpisodes": 100, }, }, ], "year": 2015, "path": "F:\\TV_Shows\\Marvels Daredevil", "profileId": 6, "seasonFolder": "true", "monitored": "true", "useSceneNumbering": "false", "runtime": 55, "tvdbId": 281662, "tvRageId": 38796, "tvMazeId": 1369, "firstAired": "2015-04-10T04:00:00Z", "lastInfoSync": "2016-09-09T09:02:49.4402575Z", "seriesType": "standard", "cleanTitle": "marvelsdaredevil", "imdbId": "tt3322312", "titleSlug": "marvels-daredevil", "certification": "TV-MA", "genres": ["Action", "Crime", "Drama"], "tags": [], "added": "2015-05-15T00:20:32.7892744Z", "ratings": {"votes": 461, "value": 8.9}, "qualityProfileId": 6, "id": 7, } ], 200, ) if "api/diskspace" in url: return MockResponse( [ { "path": "/data", "label": "", "freeSpace": 282500067328, "totalSpace": 499738734592, } ], 200, ) if "api/system/status" in url: return MockResponse( { "version": "2.0.0.1121", "buildTime": "2014-02-08T20:49:36.5560392Z", "isDebug": "false", "isProduction": "true", "isAdmin": "true", "isUserInteractive": "false", "startupPath": "C:\\ProgramData\\NzbDrone\\bin", "appData": "C:\\ProgramData\\NzbDrone", "osVersion": "6.2.9200.0", "isMono": "false", "isLinux": "false", "isWindows": "true", "branch": "develop", "authentication": "false", "startOfWeek": 0, "urlBase": "", }, 200, ) return MockResponse({"error": "Unauthorized"}, 401) class TestSonarrSetup(unittest.TestCase): """Test the Sonarr platform.""" # pylint: disable=invalid-name DEVICES = [] def add_entities(self, devices, update): """Mock add devices.""" for device in devices: self.DEVICES.append(device) def setUp(self): """Initialize values for this testcase class.""" self.DEVICES = [] self.hass = get_test_home_assistant() self.hass.config.time_zone = "America/Los_Angeles" def tearDown(self): # pylint: disable=invalid-name """Stop everything that was started.""" self.hass.stop() @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) def test_diskspace_no_paths(self, req_mock): """Test getting all disk space.""" config = { "platform": "sonarr", "api_key": "foo", "days": "2", "unit": "GB", "include_paths": [], "monitored_conditions": ["diskspace"], } sonarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() assert "263.10" == device.state assert "mdi:harddisk" == device.icon assert "GB" == device.unit_of_measurement assert "Sonarr Disk Space" == device.name assert "263.10/465.42GB (56.53%)" == device.device_state_attributes["/data"] @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) def test_diskspace_paths(self, req_mock): """Test getting diskspace for included paths.""" config = { "platform": "sonarr", "api_key": "foo", "days": "2", "unit": "GB", "include_paths": ["/data"], "monitored_conditions": ["diskspace"], } sonarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() assert "263.10" == device.state assert "mdi:harddisk" == device.icon assert "GB" == device.unit_of_measurement assert "Sonarr Disk Space" == device.name assert "263.10/465.42GB (56.53%)" == device.device_state_attributes["/data"] @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) def test_commands(self, req_mock): """Test getting running commands.""" config = { "platform": "sonarr", "api_key": "foo", "days": "2", "unit": "GB", "include_paths": ["/data"], "monitored_conditions": ["commands"], } sonarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() assert 1 == device.state assert "mdi:code-braces" == device.icon assert "Commands" == device.unit_of_measurement assert "Sonarr Commands" == device.name assert "pending" == device.device_state_attributes["RescanSeries"] @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) def test_queue(self, req_mock): """Test getting downloads in the queue.""" config = { "platform": "sonarr", "api_key": "foo", "days": "2", "unit": "GB", "include_paths": ["/data"], "monitored_conditions": ["queue"], } sonarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() assert 1 == device.state assert "mdi:download" == device.icon assert "Episodes" == device.unit_of_measurement assert "Sonarr Queue" == device.name assert "100.00%" == device.device_state_attributes["Game of Thrones S03E08"] @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) def test_series(self, req_mock): """Test getting the number of series.""" config = { "platform": "sonarr", "api_key": "foo", "days": "2", "unit": "GB", "include_paths": ["/data"], "monitored_conditions": ["series"], } sonarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() assert 1 == device.state assert "mdi:television" == device.icon assert "Shows" == device.unit_of_measurement assert "Sonarr Series" == device.name assert ( "26/26 Episodes" == device.device_state_attributes["Marvel's Daredevil"] ) @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) def test_wanted(self, req_mock): """Test getting wanted episodes.""" config = { "platform": "sonarr", "api_key": "foo", "days": "2", "unit": "GB", "include_paths": ["/data"], "monitored_conditions": ["wanted"], } sonarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() assert 1 == device.state assert "mdi:television" == device.icon assert "Episodes" == device.unit_of_measurement assert "Sonarr Wanted" == device.name assert ( "2014-02-03" == device.device_state_attributes["Archer (2009) S05E04"] ) @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) def test_upcoming_multiple_days(self, req_mock): """Test the upcoming episodes for multiple days.""" config = { "platform": "sonarr", "api_key": "foo", "days": "2", "unit": "GB", "include_paths": ["/data"], "monitored_conditions": ["upcoming"], } sonarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() assert 1 == device.state assert "mdi:television" == device.icon assert "Episodes" == device.unit_of_measurement assert "Sonarr Upcoming" == device.name assert "S04E11" == device.device_state_attributes["Bob's Burgers"] @pytest.mark.skip @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) def test_upcoming_today(self, req_mock): """Test filtering for a single day. Sonarr needs to respond with at least 2 days """ config = { "platform": "sonarr", "api_key": "foo", "days": "1", "unit": "GB", "include_paths": ["/data"], "monitored_conditions": ["upcoming"], } sonarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() assert 1 == device.state assert "mdi:television" == device.icon assert "Episodes" == device.unit_of_measurement assert "Sonarr Upcoming" == device.name assert "S04E11" == device.device_state_attributes["Bob's Burgers"] @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) def test_system_status(self, req_mock): """Test getting system status.""" config = { "platform": "sonarr", "api_key": "foo", "days": "2", "unit": "GB", "include_paths": ["/data"], "monitored_conditions": ["status"], } sonarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() assert "2.0.0.1121" == device.state assert "mdi:information" == device.icon assert "Sonarr Status" == device.name assert "6.2.9200.0" == device.device_state_attributes["osVersion"] @pytest.mark.skip @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) def test_ssl(self, req_mock): """Test SSL being enabled.""" config = { "platform": "sonarr", "api_key": "foo", "days": "1", "unit": "GB", "include_paths": ["/data"], "monitored_conditions": ["upcoming"], "ssl": "true", } sonarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() assert 1 == device.state assert "s" == device.ssl assert "mdi:television" == device.icon assert "Episodes" == device.unit_of_measurement assert "Sonarr Upcoming" == device.name assert "S04E11" == device.device_state_attributes["Bob's Burgers"] @unittest.mock.patch("requests.get", side_effect=mocked_exception) def test_exception_handling(self, req_mock): """Test exception being handled.""" config = { "platform": "sonarr", "api_key": "foo", "days": "1", "unit": "GB", "include_paths": ["/data"], "monitored_conditions": ["upcoming"], } sonarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() assert device.state is None