diff --git a/custom_components/mail_and_packages/__init__.py b/custom_components/mail_and_packages/__init__.py index b07cc7ad..bb5b1677 100644 --- a/custom_components/mail_and_packages/__init__.py +++ b/custom_components/mail_and_packages/__init__.py @@ -20,6 +20,7 @@ CONF_SCAN_INTERVAL, COORDINATOR, DEFAULT_AMAZON_DAYS, + DEFAULT_AMAZON_FWDS, DEFAULT_IMAP_TIMEOUT, DOMAIN, ISSUE_URL, @@ -69,16 +70,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b # Sort the resources updated_config[CONF_RESOURCES] = sorted(updated_config[CONF_RESOURCES]) - # Make sure amazon forwarding emails are not a string - if isinstance(updated_config[CONF_AMAZON_FWDS], str): - tmp = updated_config[CONF_AMAZON_FWDS] - tmp_list = [] - if "," in tmp: - tmp_list = tmp.split(",") - else: - tmp_list.append(tmp) - updated_config[CONF_AMAZON_FWDS] = tmp_list - if updated_config != config_entry.data: hass.config_entries.async_update_entry(config_entry, data=updated_config) @@ -156,6 +147,7 @@ async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> Non async def async_migrate_entry(hass, config_entry): """Migrate an old config entry.""" version = config_entry.version + new_version = 5 # 1 -> 4: Migrate format if version == 1: @@ -182,11 +174,6 @@ async def async_migrate_entry(hass, config_entry): # Add default Amazon Days configuration updated_config[CONF_AMAZON_DAYS] = DEFAULT_AMAZON_DAYS - if updated_config != config_entry.data: - hass.config_entries.async_update_entry( - config_entry, data=updated_config, version=4 - ) - # 2 -> 4 if version == 2: _LOGGER.debug("Migrating from version %s", version) @@ -202,11 +189,6 @@ async def async_migrate_entry(hass, config_entry): # Add default Amazon Days configuration updated_config[CONF_AMAZON_DAYS] = DEFAULT_AMAZON_DAYS - if updated_config != config_entry.data: - hass.config_entries.async_update_entry( - config_entry, data=updated_config, version=4 - ) - if version == 3: _LOGGER.debug("Migrating from version %s", version) updated_config = config_entry.data.copy() @@ -214,12 +196,19 @@ async def async_migrate_entry(hass, config_entry): # Add default Amazon Days configuration updated_config[CONF_AMAZON_DAYS] = DEFAULT_AMAZON_DAYS - if updated_config != config_entry.data: - hass.config_entries.async_update_entry( - config_entry, data=updated_config, version=4 - ) + if version == 4: + _LOGGER.debug("Migrating from version %s", version) + updated_config = config_entry.data.copy() - _LOGGER.debug("Migration complete") + if updated_config[CONF_AMAZON_FWDS] == ['""']: + updated_config[CONF_AMAZON_FWDS] = DEFAULT_AMAZON_FWDS + + if updated_config != config_entry.data: + hass.config_entries.async_update_entry( + config_entry, data=updated_config, version=new_version + ) + + _LOGGER.debug("Migration complete to version %s", new_version) return True @@ -234,6 +223,7 @@ def __init__(self, hass, host, the_timeout, interval, config): self.timeout = the_timeout self.config = config self.hass = hass + self._data = {} _LOGGER.debug("Data will be update every %s", self.interval) @@ -249,4 +239,7 @@ async def _async_update_data(self): except Exception as error: _LOGGER.error("Problem updating sensors: %s", error) raise UpdateFailed(error) from error - return data + + if data: + self._data = data + return self._data diff --git a/custom_components/mail_and_packages/config_flow.py b/custom_components/mail_and_packages/config_flow.py index 10243dd3..9290745d 100644 --- a/custom_components/mail_and_packages/config_flow.py +++ b/custom_components/mail_and_packages/config_flow.py @@ -65,7 +65,7 @@ async def _check_amazon_forwards(forwards: str) -> tuple: amazon_forwards_list = forwards.split(",") # No forwards - elif forwards in ["", "(none)", ""]: + elif forwards in ["", "(none)", '""']: amazon_forwards_list = [] # If only one address append it to the list @@ -159,10 +159,10 @@ def _get_default(key: str, fallback_default: Any = None) -> None: return vol.Schema( { - vol.Required(CONF_HOST, default=_get_default(CONF_HOST)): str, - vol.Required(CONF_PORT, default=_get_default(CONF_PORT)): vol.Coerce(int), - vol.Required(CONF_USERNAME, default=_get_default(CONF_USERNAME)): str, - vol.Required(CONF_PASSWORD, default=_get_default(CONF_PASSWORD)): str, + vol.Required(CONF_HOST, default=_get_default(CONF_HOST)): cv.string, + vol.Required(CONF_PORT, default=_get_default(CONF_PORT, 993)): cv.port, + vol.Required(CONF_USERNAME, default=_get_default(CONF_USERNAME)): cv.string, + vol.Required(CONF_PASSWORD, default=_get_default(CONF_PASSWORD)): cv.string, } ) @@ -189,7 +189,9 @@ def _get_default(key: str, fallback_default: Any = None) -> None: vol.Required( CONF_RESOURCES, default=_get_default(CONF_RESOURCES) ): cv.multi_select(get_resources()), - vol.Optional(CONF_AMAZON_FWDS, default=_get_default(CONF_AMAZON_FWDS)): str, + vol.Optional( + CONF_AMAZON_FWDS, default=_get_default(CONF_AMAZON_FWDS) + ): cv.string, vol.Optional(CONF_AMAZON_DAYS, default=_get_default(CONF_AMAZON_DAYS)): int, vol.Optional( CONF_SCAN_INTERVAL, default=_get_default(CONF_SCAN_INTERVAL) @@ -202,11 +204,13 @@ def _get_default(key: str, fallback_default: Any = None) -> None: ): vol.Coerce(int), vol.Optional( CONF_GENERATE_MP4, default=_get_default(CONF_GENERATE_MP4) - ): bool, + ): cv.boolean, vol.Optional( CONF_ALLOW_EXTERNAL, default=_get_default(CONF_ALLOW_EXTERNAL) - ): bool, - vol.Optional(CONF_CUSTOM_IMG, default=_get_default(CONF_CUSTOM_IMG)): bool, + ): cv.boolean, + vol.Optional( + CONF_CUSTOM_IMG, default=_get_default(CONF_CUSTOM_IMG) + ): cv.boolean, } ) @@ -225,7 +229,7 @@ def _get_default(key: str, fallback_default: Any = None) -> None: vol.Optional( CONF_CUSTOM_IMG_FILE, default=_get_default(CONF_CUSTOM_IMG_FILE, DEFAULT_CUSTOM_IMG_FILE), - ): str, + ): cv.string, } ) @@ -234,7 +238,7 @@ def _get_default(key: str, fallback_default: Any = None) -> None: class MailAndPackagesFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Config flow for Mail and Packages.""" - VERSION = 4 + VERSION = 5 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL def __init__(self): diff --git a/custom_components/mail_and_packages/const.py b/custom_components/mail_and_packages/const.py index 04b2ba5e..bdaa2623 100644 --- a/custom_components/mail_and_packages/const.py +++ b/custom_components/mail_and_packages/const.py @@ -62,11 +62,11 @@ DEFAULT_FOLDER = '"INBOX"' DEFAULT_PATH = "custom_components/mail_and_packages/images/" DEFAULT_IMAGE_SECURITY = True -DEFAULT_IMAP_TIMEOUT = 30 +DEFAULT_IMAP_TIMEOUT = 60 DEFAULT_GIF_DURATION = 5 -DEFAULT_SCAN_INTERVAL = 5 +DEFAULT_SCAN_INTERVAL = 30 DEFAULT_GIF_FILE_NAME = "mail_today.gif" -DEFAULT_AMAZON_FWDS = "(none)" +DEFAULT_AMAZON_FWDS = [] DEFAULT_ALLOW_EXTERNAL = False DEFAULT_CUSTOM_IMG = False DEFAULT_CUSTOM_IMG_FILE = "custom_components/mail_and_packages/images/mail_none.gif" diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index a7477286..52bebf96 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -20,6 +20,7 @@ import aiohttp import dateparser +import homeassistant.helpers.config_validation as cv from bs4 import BeautifulSoup from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -332,7 +333,7 @@ def fetch( img_out_path = f"{hass.config.path()}/{config.get(CONF_PATH)}" gif_duration = config.get(CONF_DURATION) generate_mp4 = config.get(CONF_GENERATE_MP4) - amazon_fwds = config.get(CONF_AMAZON_FWDS) + amazon_fwds = cv.ensure_list_csv(config.get(CONF_AMAZON_FWDS)) image_name = data[ATTR_IMAGE_NAME] amazon_image_name = data[ATTR_AMAZON_IMAGE] amazon_days = config.get(CONF_AMAZON_DAYS) diff --git a/custom_components/mail_and_packages/strings.json b/custom_components/mail_and_packages/strings.json index 9cec9117..1f4b53f9 100644 --- a/custom_components/mail_and_packages/strings.json +++ b/custom_components/mail_and_packages/strings.json @@ -38,7 +38,7 @@ "allow_external": "Create image for notification apps", "custom_img": "Use custom 'no image' image?" }, - "description": "Finish the configuration by customizing the following based on your email structure and Home Assistant installation.\n\nFor details on the [Mail and Packages integration](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) options review the [configuration, templates, and automations section](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) on GitHub.\n\nIf using Amazon forwarded emails please seperate each address with a comma.", + "description": "Finish the configuration by customizing the following based on your email structure and Home Assistant installation.\n\nFor details on the [Mail and Packages integration](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) options review the [configuration, templates, and automations section](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) on GitHub.\n\nIf using Amazon forwarded emails please seperate each address with a comma or enter (none) to clear this setting.", "title": "Mail and Packages (Step 2 of 2)" }, "options_3": { @@ -86,7 +86,7 @@ "allow_external": "Create image for notification apps", "custom_img": "Use custom 'no image' image?" }, - "description": "Finish the configuration by customizing the following based on your email structure and Home Assistant installation.\n\nFor details on the [Mail and Packages integration](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) options review the [configuration, templates, and automations section](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) on GitHub.\n\nIf using Amazon forwarded emails please seperate each address with a comma.", + "description": "Finish the configuration by customizing the following based on your email structure and Home Assistant installation.\n\nFor details on the [Mail and Packages integration](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) options review the [configuration, templates, and automations section](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) on GitHub.\n\nIf using Amazon forwarded emails please seperate each address with a comma or enter (none) to clear this setting.", "title": "Mail and Packages (Step 2 of 2)" }, "options_3": { diff --git a/custom_components/mail_and_packages/translations/en.json b/custom_components/mail_and_packages/translations/en.json index 44e793e9..8e6ccf7e 100644 --- a/custom_components/mail_and_packages/translations/en.json +++ b/custom_components/mail_and_packages/translations/en.json @@ -38,7 +38,7 @@ "amazon_days": "Days back to check for Amazon emails", "custom_img": "Use custom 'no image' image?" }, - "description": "Finish the configuration by customizing the following based on your email structure and Home Assistant installation.\n\nFor details on the [Mail and Packages integration](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) options review the [configuration, templates, and automations section](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) on GitHub.", + "description": "Finish the configuration by customizing the following based on your email structure and Home Assistant installation.\n\nFor details on the [Mail and Packages integration](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) options review the [configuration, templates, and automations section](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) on GitHub.\n\nIf using Amazon forwarded emails please seperate each address with a comma or enter (none) to clear this setting.", "title": "Mail and Packages (Step 2 of 2)" }, "options_3": { @@ -87,7 +87,7 @@ "amazon_days": "Days back to check for Amazon emails", "custom_img": "Use custom 'no image' image?" }, - "description": "Finish the configuration by customizing the following based on your email structure and Home Assistant installation.\n\nFor details on the [Mail and Packages integration](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) options review the [configuration, templates, and automations section](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) on GitHub.", + "description": "Finish the configuration by customizing the following based on your email structure and Home Assistant installation.\n\nFor details on the [Mail and Packages integration](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) options review the [configuration, templates, and automations section](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) on GitHub.\n\nIf using Amazon forwarded emails please seperate each address with a comma or enter (none) to clear this setting.", "title": "Mail and Packages (Step 2 of 2)" }, "options_3": { diff --git a/tests/conftest.py b/tests/conftest.py index 54eb4588..1eae9153 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,6 +22,7 @@ FAKE_CONFIG_DATA_MISSING_TIMEOUT, FAKE_CONFIG_DATA_AMAZON_FWD_STRING, FAKE_CONFIG_DATA_EXTERNAL, + FAKE_CONFIG_DATA_V4_MIGRATE, ) from pytest_homeassistant_custom_component.common import MockConfigEntry @@ -135,6 +136,19 @@ async def integration_fixture_6(hass): return entry +@pytest.fixture(name="integration_v4_migration") +async def integration_fixture_7(hass): + """Set up the mail_and_packages integration.""" + entry = MockConfigEntry( + domain=DOMAIN, + title="imap.test.email", + data=FAKE_CONFIG_DATA_V4_MIGRATE, + ) + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + return entry @pytest.fixture() def mock_imap(): diff --git a/tests/const.py b/tests/const.py index be5a616a..e76bff6d 100644 --- a/tests/const.py +++ b/tests/const.py @@ -803,3 +803,62 @@ "gls_packages": 3, "gls_tracking": ["51687952111"], } +FAKE_CONFIG_DATA_V4_MIGRATE = { + "allow_external": True, + "amazon_fwds": ['""'], + "custom_img": False, + "folder": '"INBOX"', + "generate_mp4": False, + "gif_duration": 5, + "host": "imap.test.email", + "image_name": "mail_today.gif", + "image_path": "custom_components/mail_and_packages/images/", + "image_security": True, + "imap_timeout": 30, + "password": "suchfakemuchpassword", + "port": 993, + "resources": [ + "zpackages_delivered", + "zpackages_transit", + "amazon_delivered", + "amazon_hub", + "amazon_packages", + "capost_delivered", + "capost_delivering", + "capost_packages", + "dhl_delivered", + "dhl_delivering", + "dhl_packages", + "fedex_delivered", + "fedex_delivering", + "fedex_packages", + "hermes_delivered", + "hermes_delivering", + "mail_updated", + "royal_delivered", + "royal_delivering", + "ups_delivered", + "ups_delivering", + "ups_packages", + "usps_delivered", + "usps_delivering", + "usps_mail", + "usps_packages", + "auspost_delivered", + "auspost_delivering", + "auspost_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", + "dpd_com_pl_delivered", + "dpd_com_pl_delivering", + "dpd_com_pl_packages", + "gls_delivered", + "gls_delivering", + "gls_packages", + ], + "scan_interval": 20, + "username": "user@fake.email", +} \ No newline at end of file diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index 0a8f727f..a340809c 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -139,7 +139,167 @@ async def test_form( ) assert result["type"] == "form" assert result["errors"] == {} - # assert result["title"] == title_1 + + with patch( + "custom_components.mail_and_packages.config_flow._test_login", return_value=True + ), patch( + "custom_components.mail_and_packages.config_flow._check_ffmpeg", + return_value=True, + ), patch( + "custom_components.mail_and_packages.config_flow.path", + return_value=True, + ), patch( + "custom_components.mail_and_packages.async_setup", return_value=True + ) as mock_setup, patch( + "custom_components.mail_and_packages.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], input_1 + ) + assert result2["type"] == "form" + assert result2["step_id"] == step_id_2 + + result3 = await hass.config_entries.flow.async_configure( + result["flow_id"], input_2 + ) + + assert result3["type"] == "form" + assert result3["step_id"] == step_id_3 + result4 = await hass.config_entries.flow.async_configure( + result["flow_id"], input_3 + ) + + assert result4["type"] == "create_entry" + assert result4["title"] == title + assert result4["data"] == data + + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +@pytest.mark.parametrize( + "input_1,step_id_2,input_2,step_id_3,input_3,title,data", + [ + ( + { + "host": "imap.test.email", + "port": "993", + "username": "test@test.email", + "password": "notarealpassword", + }, + "config_2", + { + "allow_external": False, + "amazon_days": 3, + "amazon_fwds": "(none)", + "custom_img": True, + "folder": '"INBOX"', + "generate_mp4": False, + "gif_duration": 5, + "imap_timeout": 30, + "scan_interval": 20, + "resources": [ + "amazon_packages", + "fedex_delivered", + "fedex_delivering", + "fedex_packages", + "mail_updated", + "ups_delivered", + "ups_delivering", + "ups_packages", + "usps_delivered", + "usps_delivering", + "usps_mail", + "usps_packages", + "zpackages_delivered", + "zpackages_transit", + "dhl_delivered", + "dhl_delivering", + "dhl_packages", + "amazon_delivered", + "auspost_delivered", + "auspost_delivering", + "auspost_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", + ], + }, + "config_3", + { + "custom_img_file": "images/test.gif", + }, + "imap.test.email", + { + "allow_external": False, + "amazon_days": 3, + "amazon_fwds": [], + "custom_img": True, + "custom_img_file": "images/test.gif", + "host": "imap.test.email", + "port": 993, + "username": "test@test.email", + "password": "notarealpassword", + "folder": '"INBOX"', + "generate_mp4": False, + "gif_duration": 5, + "imap_timeout": 30, + "scan_interval": 20, + "resources": [ + "amazon_packages", + "fedex_delivered", + "fedex_delivering", + "fedex_packages", + "mail_updated", + "ups_delivered", + "ups_delivering", + "ups_packages", + "usps_delivered", + "usps_delivering", + "usps_mail", + "usps_packages", + "zpackages_delivered", + "zpackages_transit", + "dhl_delivered", + "dhl_delivering", + "dhl_packages", + "amazon_delivered", + "auspost_delivered", + "auspost_delivering", + "auspost_packages", + "poczta_polska_delivering", + "poczta_polska_packages", + "inpost_pl_delivered", + "inpost_pl_delivering", + "inpost_pl_packages", + ], + }, + ), + ], +) +@pytest.mark.asyncio +async def test_form_no_fwds( + input_1, + step_id_2, + input_2, + step_id_3, + input_3, + title, + data, + hass, + mock_imap, +): + """Test we get the form.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} with patch( "custom_components.mail_and_packages.config_flow._test_login", return_value=True @@ -1996,6 +2156,7 @@ async def test_options_flow_mailbox_format2( "options_2", { "allow_external": False, + "amazon_fwds": "(none)", "custom_img": False, "folder": '"INBOX"', "generate_mp4": False, diff --git a/tests/test_helpers.py b/tests/test_helpers.py index ea8ecc3f..188afa9c 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -101,10 +101,7 @@ async def test_process_emails( assert config == FAKE_CONFIG_DATA_CORRECTED state = hass.states.get(MAIL_IMAGE_SYSTEM_PATH) assert state is not None - assert ( - "/testing_config/custom_components/mail_and_packages/images/" - in state.state - ) + assert "/testing_config/custom_components/mail_and_packages/images/" in state.state state = hass.states.get(MAIL_IMAGE_URL_ENTITY) assert state.state == "unknown" result = process_emails(hass, config) @@ -133,22 +130,16 @@ async def test_process_emails_external( ): hass.config.internal_url = "http://127.0.0.1:8123/" hass.config.external_url = "http://really.fake.host.net:8123/" - + entry = integration_fake_external config = entry.data.copy() assert config == FAKE_CONFIG_DATA_CORRECTED_EXTERNAL state = hass.states.get(MAIL_IMAGE_SYSTEM_PATH) assert state is not None - assert ( - "/testing_config/custom_components/mail_and_packages/images/" - in state.state - ) + assert "/testing_config/custom_components/mail_and_packages/images/" in state.state state = hass.states.get(MAIL_IMAGE_URL_ENTITY) - assert ( - state.state - == "unknown" - ) + assert state.state == "unknown" result = process_emails(hass, config) assert isinstance(result["mail_updated"], datetime.datetime) assert result["zpackages_delivered"] == 0 @@ -888,7 +879,7 @@ async def test_generate_mp4( "yuv420p", "./testfile.mp4", ], - stdout=-3, + stdout=-3, stderr=-3, ) diff --git a/tests/test_init.py b/tests/test_init.py index be562053..1c4d309e 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -143,3 +143,24 @@ async def test_custom_img( assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 43 entries = hass.config_entries.async_entries(DOMAIN) assert len(entries) == 1 + +@pytest.mark.asyncio +async def test_v4_migration( + hass, + integration_v4_migration, + mock_imap_no_email, + mock_osremove, + mock_osmakedir, + mock_listdir, + mock_update_time, + mock_copy_overlays, + mock_hash_file, + mock_getctime_today, + mock_update, +): + """Test settting up entities.""" + entry = integration_v4_migration + + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 42 + entries = hass.config_entries.async_entries(DOMAIN) + assert len(entries) == 1 \ No newline at end of file