def test_get_hacs(): SHARE["hacs"] = None os.environ["GITHUB_ACTION"] = "value" if "PYTEST" in os.environ: del os.environ["PYTEST"] get_hacs() SHARE["hacs"] = None del os.environ["GITHUB_ACTION"] os.environ["PYTEST"] = "value"
async def async_download_file(url: str) -> bytes | None: """Download files, and return the content.""" if url is None: return None hacs = get_hacs() tries_left = 5 if "tags/" in url: url = url.replace("tags/", "") _LOGGER.debug("Downloading %s", url) while tries_left > 0: try: with async_timeout.timeout(60): request = await hacs.session.get(url) # Make sure that we got a valid result if request.status == 200: return await request.read() raise HacsException( f"Got status code {request.status} when trying to download {url}" ) except Exception as exception: _LOGGER.debug("Download failed - %s", exception) tries_left -= 1 await asyncio.sleep(1) continue return None
def read_hacs_manifest(): """Reads the HACS manifest file and returns the contents.""" hacs = get_hacs() content = {} with open(f"{hacs.core.config_path}/custom_components/hacs/manifest.json") as manifest: content = json.loads(manifest.read()) return content
async def test_common_base_exception_does_not_exsist(aresponses, event_loop): aresponses.add( "api.github.com", "/rate_limit", "get", aresponses.Response(headers=response_rate_limit_header_with_limit, status=500), ) aresponses.add( "api.github.com", "/repos/test/test", "get", aresponses.Response( body=json.dumps({"message": "X"}), headers=response_rate_limit_header_with_limit, status=500, ), ) async with aiohttp.ClientSession(loop=event_loop) as session: hacs = get_hacs() hacs.session = session hacs.configuration = Configuration() hacs.configuration.token = TOKEN hacs.system.status.startup = False repository = dummy_repository_base() with pytest.raises(HacsException): await common_validate(repository)
async def test_update_repository(): hacs = get_hacs() hacs.hass = HomeAssistant() repository = HacsNetdaemon("test/test") repository.hacs = hacs with pytest.raises(HacsException): await repository.update_repository(True)
async def register_repository(full_name, category, check=True, ref=None): """Register a repository.""" hacs = get_hacs() from custom_components.hacs.repositories import ( RERPOSITORY_CLASSES, ) # To handle import error if full_name in hacs.common.skip: if full_name != "hacs/integration": raise HacsExpectedException(f"Skipping {full_name}") if category not in RERPOSITORY_CLASSES: raise HacsException(f"{category} is not a valid repository category.") repository = RERPOSITORY_CLASSES[category](full_name) if check: try: await repository.async_registration(ref) if hacs.status.new: repository.data.new = False if repository.validate.errors: hacs.common.skip.append(repository.data.full_name) if not hacs.status.startup: hacs.log.error(f"Validation for {full_name} failed.") if hacs.system.action: raise HacsException(f"::error:: Validation for {full_name} failed.") return repository.validate.errors if hacs.system.action: repository.logger.info("Validation completed") else: repository.logger.info("Registration completed") except AIOGitHubAPIException as exception: hacs.common.skip.append(repository.data.full_name) raise HacsException( f"Validation for {full_name} failed with {exception}." ) from None exists = ( False if str(repository.data.id) == "0" else [x for x in hacs.repositories if str(x.data.id) == str(repository.data.id)] ) if exists: if exists[0] in hacs.repositories: hacs.repositories.remove(exists[0]) else: if hacs.hass is not None and ( (check and repository.data.new) or hacs.status.new ): hacs.hass.bus.async_fire( "hacs/repository", { "action": "registration", "repository": repository.data.full_name, "repository_id": repository.data.id, }, ) hacs.repositories.append(repository)
async def test_download_content(aresponses, tmp_path, event_loop): aresponses.add( "raw.githubusercontent.com", "/test/test/master/test/path/file.file", "get", aresponses.Response(body="test", headers=response_rate_limit_header), ) repository = dummy_repository_base() repository.content.path.remote = "" repository.content.path.local = tmp_path repository.tree = [ AIOGitHubAPIRepositoryTreeContent( { "path": "test/path/file.file", "type": "blob" }, "test/test", "master") ] async with aiohttp.ClientSession(loop=event_loop) as session: hacs = get_hacs() hacs.hass.loop = event_loop hacs.session = session await download_content(repository) assert os.path.exists( f"{repository.content.path.local}/test/path/file.file")
async def test_reload_custom_components(): hacs = get_hacs() hacs.hass = HomeAssistant() hacs.hass.data["custom_components"] = [] repository = HacsIntegration("test/test") repository.hacs = hacs await repository.reload_custom_components()
async def update_resources() -> None: """Check Lovelace resources and and resources that are missing if in storage mode.""" base = get_base() hacs = get_hacs() # Reset missing resources missing_resources = base.configuration.missing_resources = [] # Add the HACS plugins to the list for hacs_plugin in HACS_PLUGINS: try: repo = hacs.get_by_name(hacs_plugin) url = f"/hacsfiles/{hacs_plugin.split('/')[-1]}/{repo.data.file_name}" try: await add_resource_module(url) except: base.log.error( f"Unable to add {hacs_plugin} the the Lovelace resource list.", hacs_plugin, ) missing_resources.append(url) except: base.log.warning( f"Could not connect to HACS to check repository for '{hacs_plugin}', will assume everything is okay." ) # Add custom cards to the list for card in LOVELACE_CUSTOM_CARDS: url = ( f"/local/{LOVELACE_DASHBOARD_URL_PATH}/{card['dirname']}/{card['filename']}" ) await add_resource_module(url)
async def async_download_file(url): """Download files, and return the content.""" hacs = get_hacs() if url is None: return if "tags/" in url: url = url.replace("tags/", "") _LOGGER.debug("Downloading %s", url) result = None with async_timeout.timeout(60, loop=hacs.hass.loop): request = await hacs.session.get(url) # Make sure that we got a valid result if request.status == 200: result = await request.read() else: raise HacsException( "Got status code {} when trying to download {}".format( request.status, url)) return result
async def async_serve_category_file(requested_file): hacs = get_hacs() logger = getLogger("web.category") try: if requested_file.startswith("themes/"): servefile = f"{hacs.system.config_path}/{requested_file}" else: servefile = f"{hacs.system.config_path}/www/community/{requested_file}" # Serve .gz if it exist if await async_path_exsist(f"{servefile}.gz"): servefile += ".gz" if await async_path_exsist(servefile): logger.debug(f"Serving {requested_file} from {servefile}") response = web.FileResponse(servefile) response.headers["Cache-Control"] = "no-store, max-age=0" response.headers["Pragma"] = "no-store" return response else: logger.error( f"Tried to serve up '{servefile}' but it does not exist") except (Exception, BaseException) as error: logger.debug( f"there was an issue trying to serve {requested_file} - {error}") return web.Response(status=404)
async def async_setup_frontend(): """Configure the HACS frontend elements.""" hacs = get_hacs() # Custom view hacs.hass.http.register_view(HacsFrontend()) # Custom iconset if "frontend_extra_module_url" not in hacs.hass.data: hacs.hass.data["frontend_extra_module_url"] = set() hacs.hass.data["frontend_extra_module_url"].add("/hacsfiles/iconset.js") hacs.frontend.version_running = FE_VERSION hacs.frontend.version_expected = await hacs.hass.async_add_executor_job( get_frontend_version) # Add to sidepanel custom_panel_config = { "name": "hacs-frontend", "embed_iframe": True, "trust_external": False, "js_url": f"/hacsfiles/frontend-{hacs.frontend.version_running}.js", } config = {} config["_panel_custom"] = custom_panel_config hacs.hass.components.frontend.async_register_built_in_panel( component_name="custom", sidebar_title=hacs.configuration.sidepanel_title, sidebar_icon=hacs.configuration.sidepanel_icon, frontend_url_path="hacs", config=config, require_admin=True, )
async def async_serve_category_file(request, requested_file): hacs = get_hacs() try: if requested_file.startswith("themes/"): servefile = f"{hacs.core.config_path}/{requested_file}" else: servefile = f"{hacs.core.config_path}/www/community/{requested_file}" if await async_path_exsist(servefile): _LOGGER.debug("Serving %s from %s", requested_file, servefile) response = web.FileResponse(servefile) if requested_file.startswith("themes/"): response.headers["Cache-Control"] = "public, max-age=2678400" else: response.headers["Cache-Control"] = "no-store, max-age=0" response.headers["Pragma"] = "no-store" return response else: _LOGGER.error( "%s tried to request '%s' but the file does not exist", request.remote, servefile, ) except (Exception, BaseException) as exception: _LOGGER.debug( "there was an issue trying to serve %s - %s", requested_file, exception ) return web.Response(status=404)
async def async_load_hacs_repository(): """Load HACS repositroy.""" hacs = get_hacs() hacs.log.info("Setup task %s", HacsSetupTask.HACS_REPO) try: repository = hacs.get_by_name("hacs/integration") if repository is None: await register_repository("hacs/integration", "integration") repository = hacs.get_by_name("hacs/integration") if repository is None: raise HacsException("Unknown error") repository.data.installed = True repository.data.installed_version = VERSION repository.data.new = False hacs.repo = repository.repository_object hacs.data_repo = await get_repository(hacs.session, hacs.configuration.token, "hacs/default") except HacsException as exception: if "403" in f"{exception}": hacs.log.critical( "GitHub API is ratelimited, or the token is wrong.") else: hacs.log.critical(f"[{exception}] - Could not load HACS!") return False return True
async def test_frontend_debug(): hacs = get_hacs() hacs.hass = HomeAssistant() hacs.configuration = Configuration() hacs.configuration.debug = True await async_serve_frontend() hacs.configuration = Configuration()
async def test_common_blacklist(aresponses, event_loop): aresponses.add( "api.github.com", "/rate_limit", "get", aresponses.Response(body=b"{}", headers=response_rate_limit_header, status=200), ) aresponses.add( "api.github.com", "/repos/test/test", "get", aresponses.Response(body=json.dumps(repository_data), headers=response_rate_limit_header), ) async with aiohttp.ClientSession(loop=event_loop) as session: hacs = get_hacs() hacs.session = session hacs.configuration = Configuration() hacs.configuration.token = TOKEN removed = get_removed("test/test") assert removed.repository == "test/test" repository = dummy_repository_base() with pytest.raises(HacsException): await common_validate(repository)
async def update_hacs() -> None: """Install or update hacs integrations and frontend plugins.""" base = get_base() hass = base.hass hacs = get_hacs() # Add custom repositories to HACS for repo in HACS_CUSTOM_REPOSITORIES: hacs_repository_data(hass, None, { "repository": repo["url"], "action": "add", "data": "plugin" }) # Install needed HACS integrations and plugins for hacs_plugin in HACS_INTEGRATIONS + HACS_PLUGINS: try: repo = hacs.get_by_name(hacs_plugin) try: hacs_repository(hass, None, { "repository": repo.data.id, "action": "install" }) except: base.log.error("Unable to install HACS repository: %s", hacs_plugin) except: base.log.warning( f"Could not connect to HACS install '{hacs_plugin}', will assume everything is okay." )
async def async_hacs_startup(): """HACS startup tasks.""" hacs = get_hacs() await hacs.async_set_stage(HacsStage.SETUP) if hacs.system.disabled: return False await hacs.async_set_stage(HacsStage.STARTUP) if hacs.system.disabled: return False # Setup startup tasks if hacs.hass.state == CoreState.running: async_call_later(hacs.hass, 5, hacs.startup_tasks) else: hacs.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, hacs.startup_tasks) # Mischief managed! await hacs.async_set_stage(HacsStage.WAITING) hacs.log.info( "Setup complete, waiting for Home Assistant before startup tasks starts" ) return not hacs.system.disabled
async def hacs_settings(hass, connection, msg): """Handle get media player cover command.""" hacs = get_hacs() logger = getLogger("api.settings") action = msg["action"] logger.debug(f"WS action '{action}'") if action == "set_fe_grid": hacs.configuration.frontend_mode = "Grid" elif action == "onboarding_done": hacs.configuration.onboarding_done = True elif action == "set_fe_table": hacs.configuration.frontend_mode = "Table" elif action == "set_fe_compact_true": hacs.configuration.frontend_compact = False elif action == "set_fe_compact_false": hacs.configuration.frontend_compact = True elif action == "clear_new": for repo in hacs.repositories: if repo.data.new and repo.data.category in msg.get("categories", []): logger.debug(f"Clearing new flag from '{repo.data.full_name}'") repo.data.new = False else: logger.error(f"WS action '{action}' is not valid") hass.bus.async_fire("hacs/config", {}) await hacs.data.async_write() connection.send_message(websocket_api.result_message(msg["id"], {}))
async def async_run_repository_checks(repository): hacs = get_hacs() if not SHARE["rules"]: await async_initialize_rules() if not hacs.system.running: return checks = [] for check in SHARE["rules"].get("common", []): checks.append(check(repository)) for check in SHARE["rules"].get(repository.data.category, []): checks.append(check(repository)) await asyncio.gather(*[ check._async_run_check() for check in checks or [] if hacs.action or not check.action_only ]) total = len([x for x in checks if hacs.action or not x.action_only]) failed = len([x for x in checks if x.failed]) if failed != 0: repository.logger.error(f"{failed}/{total} checks failed") if hacs.action: exit(1) else: repository.logger.debug(f"All ({total}) checks passed")
async def test_validate_repository(): hacs = get_hacs() hacs.hass = HomeAssistant() repository = HacsTheme("test/test") repository.hacs = hacs with pytest.raises(HacsException): await repository.validate_repository()
def _clear_storage(): """Clear old files from storage.""" hacs = get_hacs() storagefiles = ["hacs"] for s_f in storagefiles: path = f"{hacs.core.config_path}/.storage/{s_f}" if os.path.isfile(path): hacs.log.info(f"Cleaning up old storage file {path}") os.remove(path)
async def test_frontend_view_class(): hacs = get_hacs() hacs.hass = HomeAssistant() hacs.configuration = Configuration() frontend = HacsFrontend() await frontend.get({}, "test") await frontend.get({}, "class-map.js.map") await frontend.get({}, "frontend-test") await frontend.get({}, "iconset.js")
async def test_hacs_data_async_write2(tmpdir): data = HacsData() hacs = get_hacs() hacs.hass = HomeAssistant() hacs.hass.config.config_dir = tmpdir hacs.configuration = Configuration() hacs.system.status.background_task = False hacs.system.disabled = False await data.async_write()
async def test_get_package_content(): hacs = get_hacs() hacs.hass = HomeAssistant() repository = HacsPlugin("test/test") repository.hacs = hacs await repository.get_package_content() repository.repository_object = MockRepositoryObject() await repository.get_package_content()
async def test_common_base_exception_tree_issues(aresponses, event_loop): aresponses.add( "api.github.com", "/rate_limit", "get", aresponses.Response(body=b"{}", headers=response_rate_limit_header, status=200), ) aresponses.add( "api.github.com", "/repos/test/test", "get", aresponses.Response(body=json.dumps(repository_data), headers=response_rate_limit_header), ) aresponses.add( "api.github.com", "/rate_limit", "get", aresponses.Response(body=b"{}", headers=response_rate_limit_header, status=200), ) aresponses.add( "api.github.com", "/repos/test/test/releases", "get", aresponses.Response(body=json.dumps(release_data), headers=response_rate_limit_header), ) aresponses.add( "api.github.com", "/rate_limit", "get", aresponses.Response(body=b"{}", headers=response_rate_limit_header, status=200), ) aresponses.add( "api.github.com", "/repos/test/test/git/trees/3", "get", aresponses.Response(body=json.dumps({"message": "X"}), headers=response_rate_limit_header), ) async with aiohttp.ClientSession(loop=event_loop) as session: hacs = get_hacs() hacs.session = session hacs.configuration = Configuration() hacs.configuration.token = TOKEN repository = dummy_repository_base() hacs.system.status.startup = False with pytest.raises(HacsException): await common_validate(repository)
def constrain_custom_updater(): """Check if custom_updater exist.""" hacs = get_hacs() for location in CUSTOM_UPDATER_LOCATIONS: if os.path.exists(location.format(hacs.core.config_path)): msg = CUSTOM_UPDATER_WARNING.format( location.format(hacs.core.config_path)) hacs.log.critical(msg) return False return True
async def test_clear_storage(tmpdir): hacs = get_hacs() hacs.hass = HomeAssistant() hacs.system.config_path = tmpdir.dirname os.makedirs(f"{hacs.system.config_path}/.storage") with open(f"{hacs.system.config_path}/.storage/hacs", "w") as h_f: h_f.write("") await async_clear_storage() os.makedirs(f"{hacs.system.config_path}/.storage/hacs") await async_clear_storage()
def _clear_storage(): """Clear old files from storage.""" hacs = get_hacs() logger = getLogger("startup.clear_storage") storagefiles = ["hacs"] for s_f in storagefiles: path = f"{hacs.system.config_path}/.storage/{s_f}" if os.path.isfile(path): logger.info(f"Cleaning up old storage file {path}") os.remove(path)
def constrain_version(): """Check if the version is valid.""" hacs = get_hacs() if not version_left_higher_then_right(hacs.system.ha_version, MINIMUM_HA_VERSION): hacs.log.critical( f"You need HA version {MINIMUM_HA_VERSION} or newer to use this integration." ) return False return True