From 8138ce12ddc2f84f3e7add1d673db9568bdd9ca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mat=C4=9Bjek?= Date: Tue, 5 Nov 2019 18:04:52 +0100 Subject: [PATCH 1/2] updater: handle pkglists options from svupdater --- foris_controller_backends/updater/__init__.py | 22 +- .../updater/handlers/mock.py | 114 +++++++---- .../updater/handlers/openwrt.py | 2 +- .../updater/schema/updater.json | 59 +++++- setup.py | 2 +- tests/blackbox/test_updater.py | 193 +++++++++++++----- 6 files changed, 290 insertions(+), 102 deletions(-) diff --git a/foris_controller_backends/updater/__init__.py b/foris_controller_backends/updater/__init__.py index 5d247b57..bbf56621 100644 --- a/foris_controller_backends/updater/__init__.py +++ b/foris_controller_backends/updater/__init__.py @@ -73,7 +73,7 @@ class UpdaterUci(object): raise NotImplementedError() if user_lists is not None: - svupdater_lists.update_pkglists(user_lists) + svupdater_lists.update_pkglists(self._jsonschema_to_svupdater(user_lists)) if languages is not None: svupdater_l10n.update_languages(languages) @@ -94,6 +94,14 @@ class UpdaterUci(object): return True + def _jsonschema_to_svupdater(self, user_lists): + """Restructure data from jsonschema format into structure that svupdater expects""" + res = {} + for lst in user_lists: + res[lst["name"]] = {opt["name"]: opt["enabled"] for opt in lst.get("options", {})} + + return res + class Updater(object): def updater_running(self): @@ -139,11 +147,21 @@ class Updater(object): "enabled": v["enabled"], "hidden": v["hidden"], "title": v["title"], - "msg": v["message"], + "description": v["description"], + "options": [] if "options" not in v else [ + { + "name": name, + "title": data["title"], + "description": data["description"], + "enabled": data.get("enabled", data.get("default", False)), + } + for name, data in v["options"].items() + ], } for k, v in user_lists.items() ] + def get_languages(self): logger.debug("Getting languages") languages = svupdater_l10n.languages() diff --git a/foris_controller_modules/updater/handlers/mock.py b/foris_controller_modules/updater/handlers/mock.py index 5fcbc5ef..46cd8e6d 100644 --- a/foris_controller_modules/updater/handlers/mock.py +++ b/foris_controller_modules/updater/handlers/mock.py @@ -19,6 +19,7 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # +import copy import logging import random import uuid @@ -36,10 +37,12 @@ logger = logging.getLogger(__name__) class MockUpdaterHandler(Handler, BaseMockHandler): guide_set = BaseMockHandler._manager.Value(bool, False) - user_lists = [ - { - "name": "api-token", - "msg": { + + # Enabled flag in option is there only for testing purposes + # On real system enabled/disabled status will be written in uci config file + DEFAULT_USERLISTS = { + "api-token": { + "description": { "en": u"A Foris plugin allowing to manage remote API access tokens" " (for example for use in Spectator or Android application).", "cs": "Správa tokenů pro vzdálený API přístup" @@ -52,9 +55,8 @@ class MockUpdaterHandler(Handler, BaseMockHandler): "enabled": False, "hidden": False, }, - { - "name": "automation", - "msg": { + "automation": { + "description": { "cs": "Software pro ovládání domácí automatizace, včetně Turris Gadgets.", "de": "Steuerungssoftware für die Hausautomation, einschließlich Turris " "Gadgets.", @@ -64,9 +66,8 @@ class MockUpdaterHandler(Handler, BaseMockHandler): "enabled": False, "hidden": False, }, - { - "name": "dev-detect", - "msg": { + "dev-detect": { + "description": { "cs": "Software pro detekci nově připojených zařízení na lokální síti" " (EXPERIMENTÁLNÍ).", "de": "Software für die Erkennung neuer Geräte im lokalen Netzwerk" @@ -81,9 +82,8 @@ class MockUpdaterHandler(Handler, BaseMockHandler): "enabled": False, "hidden": False, }, - { - "name": "dvb", - "msg": { + "dvb": { + "description": { "cs": "Software na sdílení televizního vysílání přijímaného Turrisem." " Neobsahuje ovladače pro zařízení.", "de": "Software für die Weiterleitung von Fernsehsignal, welcher mittels" @@ -95,9 +95,8 @@ class MockUpdaterHandler(Handler, BaseMockHandler): "enabled": False, "hidden": False, }, - { - "name": "i_agree_honeypot", - "msg": { + "i_agree_honeypot": { + "description": { "cs": "Past na roboty zkoušející hesla na SSH.", "de": "Falle für Roboter, die das Kennwort für den SSH-Zugriff zu erraten" " versuchen.", @@ -106,15 +105,29 @@ class MockUpdaterHandler(Handler, BaseMockHandler): "title": {"cs": "SSH Honeypot", "de": "SSH-Honigtopf", "en": "SSH Honeypot"}, "enabled": False, "hidden": False, + "options": { + "minipot": { + "title": "Minipots", + "description": "Minimal honeypots to catch attackers for various protocols.", + "default": True, + }, + "haas": { + "title": "SSH Honeypot", + "description": "SSH honeypot using Honeypot as a Service (haas.nic.cz).", + } + } }, - { - "name": "i_agree_datacollect", - "msg": {"cs": "", "de": "", "en": ""}, + "i_agree_datacollect": { + "description": {"cs": "", "de": "", "en": ""}, "title": {"cs": "", "de": "", "en": ""}, "enabled": False, - "hidden": True, + "hidden": False, }, - ] + } + + # actual stored user lists + USER_LISTS = {} + languages = [ {"code": "cs", "enabled": True}, {"code": "de", "enabled": True}, @@ -145,24 +158,39 @@ class MockUpdaterHandler(Handler, BaseMockHandler): result["approval_settings"]["delay"] = self.approvals_delay return result + @staticmethod @logger_wrapper(logger) - def update_settings(self, user_lists, languages, approvals_settings, enabled): + def update_settings(user_lists, languages, approvals_settings, enabled): """ Mocks update updater settings :param user_lists: new user-list set - :type user_lists: list + :type user_lists: list of dictionaries :param languages: languages which will be installed :type languages: list :param approvals_settings: new approval settings :type approvals_settings: dict - :param enable: is updater enabled indicator - :type enable: bool + :param enabled: is updater enabled indicator + :type enabled: bool :returns: True on success False otherwise :rtype: bool """ + if user_lists is not None: - for record in MockUpdaterHandler.user_lists: - record["enabled"] = record["name"] in user_lists + MockUpdaterHandler.USER_LISTS = {} + for lst in user_lists: + list_name = lst["name"] + MockUpdaterHandler.USER_LISTS[list_name] = copy.deepcopy(MockUpdaterHandler.DEFAULT_USERLISTS[list_name]) + MockUpdaterHandler.USER_LISTS[list_name]["enabled"] = True + + default_list_options = MockUpdaterHandler.USER_LISTS[list_name].get("options", {}) + opts = {} + for opt in lst.get("options", {}): + if opt["name"] in default_list_options: + opts[opt["name"]] = default_list_options[opt["name"]] + opts[opt["name"]]["enabled"] = opt["enabled"] + + MockUpdaterHandler.USER_LISTS[list_name]["options"] = opts + if languages is not None: for record in MockUpdaterHandler.languages: record["enabled"] = record["code"] in languages @@ -199,24 +227,40 @@ class MockUpdaterHandler(Handler, BaseMockHandler): ] ) + @staticmethod @logger_wrapper(logger) - def get_user_lists(self, lang): + def get_user_lists(lang): """ Mocks getting user lists :param lang: language en/cs/de - :returns: [{"name": "..", "enabled": True, "title": "..", "msg": "..", "hidden": True}, ...] + :returns: [{"name": "..", "enabled": True, "title": "..", "description": "..", "hidden": True}, ...] :rtype: dict """ exported = [] - for record in MockUpdaterHandler.user_lists: + for list_name, lst in MockUpdaterHandler.DEFAULT_USERLISTS.items(): + opts = [ + { + "name": opt_name, + "title": data["title"], + "description": data["description"], + "enabled": (MockUpdaterHandler.USER_LISTS[list_name]["options"][opt_name]["enabled"] + if (list_name in MockUpdaterHandler.USER_LISTS and + opt_name in MockUpdaterHandler.USER_LISTS[list_name]["options"]) + else data.get("default", False)), + } + for opt_name, data in lst.get("options", {}).items() + ] exported.append( { - "name": record["name"], - "hidden": record["hidden"], - "enabled": record["enabled"], - "msg": record["msg"].get(lang, record["msg"]["en"]), - "title": record["title"].get(lang, record["title"]["en"]), + "name": list_name, + "hidden": lst["hidden"], + "enabled": (MockUpdaterHandler.USER_LISTS[list_name]["enabled"] + if list_name in MockUpdaterHandler.USER_LISTS else lst["enabled"]), + "description": lst["description"].get(lang, lst["description"]["en"]), + "title": lst["title"].get(lang, lst["title"]["en"]), + "options": opts, } ) + return exported @logger_wrapper(logger) diff --git a/foris_controller_modules/updater/handlers/openwrt.py b/foris_controller_modules/updater/handlers/openwrt.py index 1bb8958b..c8045960 100644 --- a/foris_controller_modules/updater/handlers/openwrt.py +++ b/foris_controller_modules/updater/handlers/openwrt.py @@ -46,7 +46,7 @@ class OpenwrtUpdaterHandler(Handler, BaseOpenwrtHandler): """ update updater settings :param user_lists: new user-list set - :type user_lists: list + :type user_lists: dictionary :param languages: languages which will be installed :type languages: list :param approvals_settings: new approval settings diff --git a/foris_controller_modules/updater/schema/updater.json b/foris_controller_modules/updater/schema/updater.json index 72468744..a2f41b89 100644 --- a/foris_controller_modules/updater/schema/updater.json +++ b/foris_controller_modules/updater/schema/updater.json @@ -10,20 +10,29 @@ "type": "array", "items": { "type": "object", - "properties": { - "name": {"$ref": "#/definitions/updater_user_list_name"}, - "enabled": {"type": "boolean"}, - "hidden": {"type": "boolean"}, - "title": {"type": "string"}, - "msg": {"type": "string"} - }, - "required": ["enabled", "name", "title", "msg", "hidden"], - "additionalProperties": false + "properties": { + "name": {"$ref": "#/definitions/updater_user_list_name"}, + "enabled": {"type": "boolean"}, + "hidden": {"type": "boolean"}, + "title": {"type": "string"}, + "description": {"type": "string"}, + "options": {"$ref": "#/definitions/updater_user_list_options_get"} + }, + "required": ["enabled", "name", "title", "description", "hidden", "options"], + "additionalProperties": false } }, "updater_user_lists_set": { "type": "array", - "items": {"$ref": "#/definitions/updater_user_list_name"} + "items": { + "type": "object", + "properties": { + "name": {"$ref": "#/definitions/updater_user_list_name"}, + "options": {"$ref": "#/definitions/updater_user_list_options_set"} + }, + "required": ["name"], + "additionalProperties": false + } }, "updater_languages_get": { "type": "array", @@ -159,6 +168,36 @@ "required": ["present"] } ] + }, + "updater_user_list_option_get": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "title": {"type": "string"}, + "description": {"type": "string"}, + "enabled": {"type": "boolean"} + }, + "additionalProperties": false, + "required": ["name", "title", "description", "enabled"] + }, + "updater_user_list_options_get": { + "type": "array", + "items": {"$ref": "#/definitions/updater_user_list_option_get"} + }, + "updater_user_list_option_set": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "enabled": {"type": "boolean"} + }, + "additionalProperties": false, + "required": ["name", "enabled"] + }, + "updater_user_list_options_set": { + "type": "array", + "items": { + "$ref": "#/definitions/updater_user_list_option_set" + } } }, "oneOf": [ diff --git a/setup.py b/setup.py index 102b6b12..4e6ca5f5 100644 --- a/setup.py +++ b/setup.py @@ -118,7 +118,7 @@ setup( "python-prctl", "pbkdf2", "python-slugify", - "svupdater @ git+https://gitlab.labs.nic.cz/turris/updater/supervisor.git", + "svupdater @ git+https://gitlab.labs.nic.cz/turris/updater/supervisor.git#egg=supervisor", "turrishw @ git+https://gitlab.labs.nic.cz/turris/turrishw.git", ], setup_requires=[ diff --git a/tests/blackbox/test_updater.py b/tests/blackbox/test_updater.py index 36b02540..44038b55 100644 --- a/tests/blackbox/test_updater.py +++ b/tests/blackbox/test_updater.py @@ -70,38 +70,86 @@ def wait_for_updater_run_finished(notifications, infrastructure): exit_count = notification_status_count(notifications, "exit") +@pytest.mark.parametrize("lang", ["en", "cs", "de", "nb_NO", "xx"]) def test_get_settings( - updater_languages, updater_userlists, uci_configs_init, infrastructure, start_buses + updater_languages, updater_userlists, uci_configs_init, infrastructure, start_buses, lang ): - def get(lang): - res = infrastructure.process_message( - { - "module": "updater", - "action": "get_settings", - "kind": "request", - "data": {"lang": lang}, - } - ) - assert set(res.keys()) == {"action", "kind", "data", "module"} - assert "enabled" in res["data"].keys() - assert "languages" in res["data"].keys() - assert {"enabled", "code"} == set(res["data"]["languages"][0].keys()) - assert "user_lists" in res["data"].keys() - assert {"enabled", "name", "title", "msg", "hidden"} == set( - res["data"]["user_lists"][0].keys() - ) - assert "approval_settings" in res["data"].keys() - assert "status" in res["data"]["approval_settings"].keys() - assert "approval" in res["data"].keys() + res = infrastructure.process_message( + { + "module": "updater", + "action": "get_settings", + "kind": "request", + "data": {"lang": lang}, + } + ) - get("en") - get("cs") - get("de") - get("nb_NO") - get("xx") + assert set(res.keys()) == {"action", "kind", "data", "module"} + assert "enabled" in res["data"].keys() + assert "languages" in res["data"].keys() + assert {"enabled", "code"} == set(res["data"]["languages"][0].keys()) + assert "user_lists" in res["data"].keys() + assert {"enabled", "name", "title", "description", "hidden", "options"} == set( + res["data"]["user_lists"][0].keys() + ) + assert "approval_settings" in res["data"].keys() + assert "status" in res["data"]["approval_settings"].keys() + assert "approval" in res["data"].keys() @pytest.mark.parametrize("device,turris_os_version", [("mox", "4.0")], indirect=True) +@pytest.mark.parametrize( + "settings,default_opts", + [ + ( + {"enabled": True, "approval_settings": {"status": "off"}, "user_lists": [], "languages": []}, + None + ), + ( + { + "enabled": True, + "approval_settings": {"status": "on"}, + "user_lists": [ + {"name": "i_agree_honeypot", "options": [{"name": "minipot", "enabled": True}]} + ], + "languages": ["cs"], + }, + None + ), + ( + { + "enabled": True, + "approval_settings": {"status": "on"}, + "user_lists": [ + {"name": "i_agree_honeypot", "options": [{"name": "minipot", "enabled": False}]} + ], + "languages": ["cs"], + }, + None + ), + ( + { + "enabled": True, + "approval_settings": {"status": "on"}, + "user_lists": [ + {"name": "i_agree_honeypot", "options": [{"name": "haas", "enabled": True}]} + ], + "languages": ["cs"], + }, + {"i_agree_honeypot": {"minipot"}} + ), + ( + { + "enabled": True, + "approval_settings": {"status": "delayed", "delay": 24}, + "user_lists": [ + {"name": "dvb"} + ], + "languages": ["cs", "de", "nb_NO"], + }, + None + ), + ] +) def test_update_settings( updater_languages, updater_userlists, @@ -110,6 +158,70 @@ def test_update_settings( start_buses, device, turris_os_version, + settings, + default_opts +): + defaults = default_opts if default_opts else {} + + res = infrastructure.process_message( + { + "module": "updater", + "action": "update_settings", + "kind": "request", + "data": settings, + } + ) + assert "result" in res["data"] and res["data"]["result"] is True + res = infrastructure.process_message( + { + "module": "updater", + "action": "get_settings", + "kind": "request", + "data": {"lang": "en"}, + } + ) + + new_settings_options = { + ulist["name"]: { + opt["name"] for opt in ulist.get("options", []) if opt["enabled"] + } + for ulist in settings["user_lists"] + } + + del res["data"]["approval"] + list_data = res["data"].pop("user_lists") + list_options = { + ulist["name"]: { + opt["name"] for opt in ulist.get("options", []) if opt["enabled"] + } + for ulist in list_data if ulist["enabled"] + } + assert new_settings_options.keys() == {e["name"] for e in list_data if e["enabled"]} + + # compare user list options minus the expected defaults + # to confirm that everything was written succesfully + for lst, opts in list_options.items(): + if lst in defaults: + list_options[lst] = opts.difference(defaults[lst]) + + assert new_settings_options == list_options + lang_data = res["data"].pop("languages") + assert set(settings["languages"]) == {e["code"] for e in lang_data if e["enabled"]} + + del settings["user_lists"] + del settings["languages"] + assert match_subdict(settings, res["data"]) + + +@pytest.mark.parametrize("device,turris_os_version", [("mox", "4.0")], indirect=True) +def test_update_settings_expected( + updater_languages, + updater_userlists, + uci_configs_init, + infrastructure, + start_buses, + device, + turris_os_version, ): def update_settings(new_settings, expected=None): res = infrastructure.process_message( @@ -129,44 +241,19 @@ def test_update_settings( "data": {"lang": "en"}, } ) - new_settings = expected if expected else new_settings del res["data"]["approval"] list_data = res["data"].pop("user_lists") assert set(new_settings["user_lists"]) == {e["name"] for e in list_data if e["enabled"]} lang_data = res["data"].pop("languages") assert set(new_settings["languages"]) == {e["code"] for e in lang_data if e["enabled"]} - del new_settings["user_lists"] del new_settings["languages"] assert match_subdict(new_settings, res["data"]) - + update_settings( - {"enabled": True, "approval_settings": {"status": "off"}, "user_lists": [], "languages": []} - ) - - update_settings( - { - "enabled": True, - "approval_settings": {"status": "on"}, - "user_lists": ["api-token"], - "languages": ["cs"], - } + {"enabled": True, "approval_settings": {"status": "off"}, "user_lists": [], "languages": []}, ) - - update_settings( - { - "enabled": True, - "approval_settings": {"status": "delayed", "delay": 24}, - "user_lists": ["dvb"], - "languages": ["cs", "de", "nb_NO"], - } - ) - - update_settings( - {"enabled": True, "approval_settings": {"status": "off"}, "user_lists": [], "languages": []} - ) - update_settings( {"enabled": False}, { -- GitLab From e011eec56bae450a647d2e5a311ae704f43664ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mat=C4=9Bjek?= Date: Tue, 18 Feb 2020 13:50:07 +0100 Subject: [PATCH 2/2] updater: expand tests and cleanup --- foris_controller_backends/updater/__init__.py | 49 +-- foris_controller_modules/updater/__init__.py | 5 +- .../updater/handlers/mock.py | 34 +- .../updater/handlers/openwrt.py | 10 +- tests/blackbox/test_updater.py | 304 ++++++++++++------ 5 files changed, 278 insertions(+), 124 deletions(-) diff --git a/foris_controller_backends/updater/__init__.py b/foris_controller_backends/updater/__init__.py index bbf56621..fd328f59 100644 --- a/foris_controller_backends/updater/__init__.py +++ b/foris_controller_backends/updater/__init__.py @@ -1,6 +1,6 @@ # # foris-controller -# Copyright (C) 2019 CZ.NIC, z.s.p.o. (http://www.nic.cz/) +# Copyright (C) 2019-20 CZ.NIC, z.s.p.o. (http://www.nic.cz/) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -37,11 +37,11 @@ logger = logging.getLogger(__name__) class UpdaterUci(object): - def get_settings(self): + def get_settings(self, lang="en"): res = { "enabled": svupdater_autorun.enabled(), - "user_lists": [k for k, v in svupdater_lists.pkglists("en").items() if v["enabled"]], + "user_lists": Updater.get_user_lists(lang), "languages": svupdater_l10n.languages(), "approval_settings": {"status": "on" if svupdater_autorun.approvals() else "off"}, } @@ -137,28 +137,39 @@ class Updater(object): else: return {"present": False} - def get_user_lists(self, lang): + @staticmethod + def _get_userlist_options(lst): + if "options" not in lst: + return [] + + res = [] + for name, data in lst["options"].items(): + res.append( + { + "name": name, + "title": data["title"], + "description": data["description"], + "enabled": data.get("enabled", data.get("default", False)), + } + ) + + return res + + @staticmethod + def get_user_lists(lang): logger.debug("Getting user lists for '%s'", lang) user_lists = svupdater_lists.pkglists(lang) logger.debug("Userlists obtained: %s", user_lists) return [ { - "name": k, - "enabled": v["enabled"], - "hidden": v["hidden"], - "title": v["title"], - "description": v["description"], - "options": [] if "options" not in v else [ - { - "name": name, - "title": data["title"], - "description": data["description"], - "enabled": data.get("enabled", data.get("default", False)), - } - for name, data in v["options"].items() - ], + "name": lst_name, + "enabled": lst["enabled"], + "hidden": lst["hidden"], + "title": lst["title"], + "description": lst["description"], + "options": Updater._get_userlist_options(lst) } - for k, v in user_lists.items() + for lst_name, lst in user_lists.items() ] diff --git a/foris_controller_modules/updater/__init__.py b/foris_controller_modules/updater/__init__.py index 38f791e1..e217d0ab 100644 --- a/foris_controller_modules/updater/__init__.py +++ b/foris_controller_modules/updater/__init__.py @@ -1,6 +1,6 @@ # # foris-controller -# Copyright (C) 2017 CZ.NIC, z.s.p.o. (http://www.nic.cz/) +# Copyright (C) 2017-20 CZ.NIC, z.s.p.o. (http://www.nic.cz/) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -33,9 +33,8 @@ class UpdaterModule(BaseModule): :returns: current updater settings :rtype: dict """ - res = self.handler.get_settings() + res = self.handler.get_settings(data["lang"]) res["approval"] = self.handler.get_approval() - res["user_lists"] = self.handler.get_user_lists(data["lang"]) res["languages"] = self.handler.get_languages() return res diff --git a/foris_controller_modules/updater/handlers/mock.py b/foris_controller_modules/updater/handlers/mock.py index 46cd8e6d..dcc80b3e 100644 --- a/foris_controller_modules/updater/handlers/mock.py +++ b/foris_controller_modules/updater/handlers/mock.py @@ -2,7 +2,7 @@ # # foris-controller -# Copyright (C) 2019 CZ.NIC, z.s.p.o. (http://www.nic.cz/) +# Copyright (C) 2019-20 CZ.NIC, z.s.p.o. (http://www.nic.cz/) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -122,6 +122,17 @@ class MockUpdaterHandler(Handler, BaseMockHandler): "title": {"cs": "", "de": "", "en": ""}, "enabled": False, "hidden": False, + "options": { + "survey": { + "title": "Usage Survey", + "description": "Collect data about router usage (installed packages, Internet connection type and etc.).", + }, + "dynfw": { + "title": "Dynamic Firewall", + "description": "Add firewall rules to block attackers detected by Turris collection network.", + "default": True, + } + }, }, } @@ -147,13 +158,17 @@ class MockUpdaterHandler(Handler, BaseMockHandler): updater_running = False @logger_wrapper(logger) - def get_settings(self): + def get_settings(self, lang): """ Mocks get updater settings :returns: current updater settings :rtype: dict """ - result = {"approval_settings": {"status": self.approvals_status}, "enabled": self.enabled} + result = { + "approval_settings": {"status": self.approvals_status}, + "enabled": self.enabled, + "user_lists": self.get_user_lists(lang), + } if self.approvals_delay: result["approval_settings"]["delay"] = self.approvals_delay return result @@ -227,6 +242,14 @@ class MockUpdaterHandler(Handler, BaseMockHandler): ] ) + @staticmethod + def _is_option_enabled(list_name, opt_name, opt_data): + if (list_name in MockUpdaterHandler.USER_LISTS and + opt_name in MockUpdaterHandler.USER_LISTS[list_name]["options"]): + return MockUpdaterHandler.USER_LISTS[list_name]["options"][opt_name]["enabled"] + + return opt_data.get("default", False) + @staticmethod @logger_wrapper(logger) def get_user_lists(lang): @@ -242,10 +265,7 @@ class MockUpdaterHandler(Handler, BaseMockHandler): "name": opt_name, "title": data["title"], "description": data["description"], - "enabled": (MockUpdaterHandler.USER_LISTS[list_name]["options"][opt_name]["enabled"] - if (list_name in MockUpdaterHandler.USER_LISTS and - opt_name in MockUpdaterHandler.USER_LISTS[list_name]["options"]) - else data.get("default", False)), + "enabled": MockUpdaterHandler._is_option_enabled(list_name, opt_name, data), } for opt_name, data in lst.get("options", {}).items() ] diff --git a/foris_controller_modules/updater/handlers/openwrt.py b/foris_controller_modules/updater/handlers/openwrt.py index c8045960..afd0835a 100644 --- a/foris_controller_modules/updater/handlers/openwrt.py +++ b/foris_controller_modules/updater/handlers/openwrt.py @@ -1,6 +1,6 @@ # # foris-controller -# Copyright (C) 2017 CZ.NIC, z.s.p.o. (http://www.nic.cz/) +# Copyright (C) 2017-20 CZ.NIC, z.s.p.o. (http://www.nic.cz/) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -33,13 +33,13 @@ class OpenwrtUpdaterHandler(Handler, BaseOpenwrtHandler): updater = Updater() @logger_wrapper(logger) - def get_settings(self): + def get_settings(self, lang="en"): """ get updater settings :returns: current updater settings :rtype: dict """ - return OpenwrtUpdaterHandler.uci.get_settings() + return OpenwrtUpdaterHandler.uci.get_settings(lang) @logger_wrapper(logger) def update_settings(self, user_lists, languages, approvals_settings, enabled): @@ -51,8 +51,8 @@ class OpenwrtUpdaterHandler(Handler, BaseOpenwrtHandler): :type languages: list :param approvals_settings: new approval settings :type approvals_settings: dict - :param enable: is updater enabled indicator - :type enable: bool + :param enabled: is updater enabled indicator + :type enabled: bool :returns: True on success False otherwise :rtype: bool """ diff --git a/tests/blackbox/test_updater.py b/tests/blackbox/test_updater.py index 44038b55..e9a04e87 100644 --- a/tests/blackbox/test_updater.py +++ b/tests/blackbox/test_updater.py @@ -1,6 +1,6 @@ # # foris-controller -# Copyright (C) 2018 CZ.NIC, z.s.p.o. (http://www.nic.cz/) +# Copyright (C) 2018-20 CZ.NIC, z.s.p.o. (http://www.nic.cz/) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -70,6 +70,47 @@ def wait_for_updater_run_finished(notifications, infrastructure): exit_count = notification_status_count(notifications, "exit") +def updated_match_expected(result, new_settings): + """Check if returned data match expected settings""" + del result["data"]["approval"] + + list_data = result["data"].pop("user_lists") + assert {e["name"] for e in new_settings["user_lists"]} == {e["name"] for e in list_data if e["enabled"]} + + lang_data = result["data"].pop("languages") + assert set(new_settings["languages"]) == {e["code"] for e in lang_data if e["enabled"]} + + del new_settings["user_lists"] + del new_settings["languages"] + assert match_subdict(new_settings, result["data"]) + + +def match_userlist_options(result, new_settings, defaults): + """Check if returned user lists options match expected settings""" + list_data = result["data"].pop("user_lists") + + new_settings_options = { + ulist["name"]: { + opt["name"] for opt in ulist.get("options", []) if opt["enabled"] + } + for ulist in new_settings["user_lists"] + } + list_options = { + ulist["name"]: { + opt["name"] for opt in ulist.get("options", []) if opt["enabled"] + } + for ulist in list_data if ulist["enabled"] + } + + # compare user list options minus the expected defaults + # to confirm that everything was written succesfully + for lst, opts in list_options.items(): + if lst in defaults: + list_options[lst] = opts.difference(defaults[lst]) + + assert new_settings_options == list_options + + @pytest.mark.parametrize("lang", ["en", "cs", "de", "nb_NO", "xx"]) def test_get_settings( updater_languages, updater_userlists, uci_configs_init, infrastructure, start_buses, lang @@ -97,60 +138,7 @@ def test_get_settings( @pytest.mark.parametrize("device,turris_os_version", [("mox", "4.0")], indirect=True) -@pytest.mark.parametrize( - "settings,default_opts", - [ - ( - {"enabled": True, "approval_settings": {"status": "off"}, "user_lists": [], "languages": []}, - None - ), - ( - { - "enabled": True, - "approval_settings": {"status": "on"}, - "user_lists": [ - {"name": "i_agree_honeypot", "options": [{"name": "minipot", "enabled": True}]} - ], - "languages": ["cs"], - }, - None - ), - ( - { - "enabled": True, - "approval_settings": {"status": "on"}, - "user_lists": [ - {"name": "i_agree_honeypot", "options": [{"name": "minipot", "enabled": False}]} - ], - "languages": ["cs"], - }, - None - ), - ( - { - "enabled": True, - "approval_settings": {"status": "on"}, - "user_lists": [ - {"name": "i_agree_honeypot", "options": [{"name": "haas", "enabled": True}]} - ], - "languages": ["cs"], - }, - {"i_agree_honeypot": {"minipot"}} - ), - ( - { - "enabled": True, - "approval_settings": {"status": "delayed", "delay": 24}, - "user_lists": [ - {"name": "dvb"} - ], - "languages": ["cs", "de", "nb_NO"], - }, - None - ), - ] -) -def test_update_settings( +def test_update_settings_clear( updater_languages, updater_userlists, uci_configs_init, @@ -158,10 +146,13 @@ def test_update_settings( start_buses, device, turris_os_version, - settings, - default_opts ): - defaults = default_opts if default_opts else {} + settings = { + "enabled": True, + "approval_settings": {"status": "off"}, + "user_lists": [], + "languages": [] + } res = infrastructure.process_message( { @@ -181,40 +172,180 @@ def test_update_settings( } ) - new_settings_options = { - ulist["name"]: { - opt["name"] for opt in ulist.get("options", []) if opt["enabled"] - } - for ulist in settings["user_lists"] - } + updated_match_expected(res, settings) - del res["data"]["approval"] - list_data = res["data"].pop("user_lists") - list_options = { - ulist["name"]: { - opt["name"] for opt in ulist.get("options", []) if opt["enabled"] + +@pytest.mark.parametrize("device,turris_os_version", [("mox", "4.0")], indirect=True) +def test_update_settings_clear_and_write( + updater_languages, + updater_userlists, + uci_configs_init, + infrastructure, + start_buses, + device, + turris_os_version, +): + """Test that clearing of settings won't leave some setting enabled""" + + def update_settings(new_settings): + res = infrastructure.process_message( + { + "module": "updater", + "action": "update_settings", + "kind": "request", + "data": new_settings, + } + ) + assert "result" in res["data"] and res["data"]["result"] is True + res = infrastructure.process_message( + { + "module": "updater", + "action": "get_settings", + "kind": "request", + "data": {"lang": "en"}, + } + ) + updated_match_expected(res, new_settings) + + update_settings( + {"enabled": True, "approval_settings": {"status": "off"}, "user_lists": [], "languages": []} + ) + update_settings( + { + "enabled": True, + "approval_settings": {"status": "on"}, + "user_lists": [ + {"name": "dvb"} + ], + "languages": ["cs", "de", "nb_NO"], } - for ulist in list_data if ulist["enabled"] + ) + + +@pytest.mark.parametrize("device,turris_os_version", [("mox", "4.0")], indirect=True) +def test_update_settings_languages( + updater_languages, + updater_userlists, + uci_configs_init, + infrastructure, + start_buses, + device, + turris_os_version, +): + settings = { + "enabled": True, + "approval_settings": {"status": "on"}, + "user_lists": [], + "languages": ["cs", "de", "nb_NO"], } - assert new_settings_options.keys() == {e["name"] for e in list_data if e["enabled"]} - # compare user list options minus the expected defaults - # to confirm that everything was written succesfully - for lst, opts in list_options.items(): - if lst in defaults: - list_options[lst] = opts.difference(defaults[lst]) + res = infrastructure.process_message( + { + "module": "updater", + "action": "update_settings", + "kind": "request", + "data": settings, + } + ) + assert "result" in res["data"] and res["data"]["result"] is True + res = infrastructure.process_message( + { + "module": "updater", + "action": "get_settings", + "kind": "request", + "data": {"lang": "en"}, + } + ) - assert new_settings_options == list_options lang_data = res["data"].pop("languages") assert set(settings["languages"]) == {e["code"] for e in lang_data if e["enabled"]} - del settings["user_lists"] - del settings["languages"] - assert match_subdict(settings, res["data"]) + +@pytest.mark.parametrize("device,turris_os_version", [("mox", "4.0")], indirect=True) +def test_update_settings_with_defaults( + updater_languages, + updater_userlists, + uci_configs_init, + infrastructure, + start_buses, + device, + turris_os_version, +): + settings = { + "enabled": True, + "approval_settings": {"status": "on"}, + "user_lists": [ + {"name": "i_agree_honeypot", "options": [{"name": "haas", "enabled": True}]} + ], + "languages": ["cs"], + } + defaults = {"i_agree_honeypot": {"minipot"}} + + res = infrastructure.process_message( + { + "module": "updater", + "action": "update_settings", + "kind": "request", + "data": settings, + } + ) + assert "result" in res["data"] and res["data"]["result"] is True + res = infrastructure.process_message( + { + "module": "updater", + "action": "get_settings", + "kind": "request", + "data": {"lang": "en"}, + } + ) + + match_userlist_options(res, settings, defaults) @pytest.mark.parametrize("device,turris_os_version", [("mox", "4.0")], indirect=True) -def test_update_settings_expected( +def test_update_settings_overrride_defaults( + updater_languages, + updater_userlists, + uci_configs_init, + infrastructure, + start_buses, + device, + turris_os_version, +): + settings = { + "enabled": True, + "approval_settings": {"status": "on"}, + "user_lists": [ + {"name": "i_agree_honeypot", "options": [{"name": "minipot", "enabled": False}]}, + {"name": "i_agree_datacollect", "options": [{"name": "survey", "enabled": True}]}, + ], + "languages": ["cs"], + } + defaults = {"i_agree_datacollect": {"dynfw"}} + + res = infrastructure.process_message( + { + "module": "updater", + "action": "update_settings", + "kind": "request", + "data": settings, + } + ) + assert "result" in res["data"] and res["data"]["result"] is True + res = infrastructure.process_message( + { + "module": "updater", + "action": "get_settings", + "kind": "request", + "data": {"lang": "en"}, + } + ) + + match_userlist_options(res, settings, defaults) + + +@pytest.mark.parametrize("device,turris_os_version", [("mox", "4.0")], indirect=True) +def test_update_settings_disable_updater_keep_settings( updater_languages, updater_userlists, uci_configs_init, @@ -242,15 +373,8 @@ def test_update_settings_expected( } ) new_settings = expected if expected else new_settings - del res["data"]["approval"] - list_data = res["data"].pop("user_lists") - assert set(new_settings["user_lists"]) == {e["name"] for e in list_data if e["enabled"]} - lang_data = res["data"].pop("languages") - assert set(new_settings["languages"]) == {e["code"] for e in lang_data if e["enabled"]} - del new_settings["user_lists"] - del new_settings["languages"] - assert match_subdict(new_settings, res["data"]) - + updated_match_expected(res, new_settings) + update_settings( {"enabled": True, "approval_settings": {"status": "off"}, "user_lists": [], "languages": []}, ) -- GitLab