diff --git a/.github/linters/.flake8 b/.github/linters/.flake8 new file mode 100644 index 0000000..2050bd5 --- /dev/null +++ b/.github/linters/.flake8 @@ -0,0 +1,5 @@ +[flake8] +max-line-length = 120 +exclude = + # Exclude generated code. + *_pb2.py diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index f117026..8bf8e38 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -45,10 +45,10 @@ jobs: # Run Linter against code base # ################################ - name: Lint Code Base - uses: github/super-linter@v3 + uses: github/super-linter/slim@v4 env: VALIDATE_ALL_CODEBASE: true - # VALIDATE_PYTHON_FLAKE8: true + VALIDATE_PYTHON_FLAKE8: true VALIDATE_BASH: true VALIDATE_JSON: true VALIDATE_DOCKERFILE_HADOLINT: true diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 993dfea..84cc5a6 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -19,4 +19,4 @@ jobs: pip install -r test-requirements.txt pip install -r requirements.txt export PYTHONPATH=`pwd` - pytest --cov=minerconfig --cov=lib --cov-fail-under=1 \ No newline at end of file + pytest --cov=gatewayconfig --cov=lib --cov-fail-under=70 diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml index 1157c1a..dd99b41 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -16,4 +16,4 @@ jobs: - name: Run bandit against code base run: | pip install bandit - bandit -r config-python + bandit -r . diff --git a/gatewayconfig/__main__.py b/gatewayconfig/__main__.py index b4780c5..0db1aa8 100644 --- a/gatewayconfig/__main__.py +++ b/gatewayconfig/__main__.py @@ -6,7 +6,7 @@ logger = get_logger(__name__) VARIANT = os.getenv('VARIANT') # SENTRY_CONFIG currently being used in production -SENTRY_DSN = os.getenv('SENTRY_CONFIG') # https://docs.sentry.io/product/sentry-basics/dsn-explainer/ +SENTRY_DSN = os.getenv('SENTRY_CONFIG') # https://docs.sentry.io/product/sentry-basics/dsn-explainer/ BALENA_DEVICE_UUID = os.getenv('BALENA_DEVICE_UUID') BALENA_APP_NAME = os.getenv('BALENA_APP_NAME') FIRMWARE_VERSION = os.getenv('FIRMWARE_VERSION') @@ -24,10 +24,12 @@ ) ETHERNET_IS_ONLINE_FILEPATH = os.getenv('ETHERNET_IS_ONLINE_FILEPATH', '/sys/class/net/eth0/carrier') + def main(): validate_env() start() + def validate_env(): logger.debug("Starting with the following ENV:\n\ SENTRY_DSN=%s\n\ @@ -50,6 +52,7 @@ def validate_env(): FIRMWARE_VERSION )) + def start(): config_app = GatewayconfigApp( SENTRY_DSN, diff --git a/gatewayconfig/bluetooth/advertisements/bluetooth_connection_advertisement.py b/gatewayconfig/bluetooth/advertisements/bluetooth_connection_advertisement.py index f88428a..a207424 100644 --- a/gatewayconfig/bluetooth/advertisements/bluetooth_connection_advertisement.py +++ b/gatewayconfig/bluetooth/advertisements/bluetooth_connection_advertisement.py @@ -22,4 +22,4 @@ def __init__(self, index, eth0_mac_address, advertisement_type, variant_details) self.add_local_name(advertisement_name) self.include_tx_power = True - self.service_uuids = [constants.HELIUM_SERVICE_UUID] \ No newline at end of file + self.service_uuids = [constants.HELIUM_SERVICE_UUID] diff --git a/gatewayconfig/bluetooth/characteristics/assert_location_characteristic.py b/gatewayconfig/bluetooth/characteristics/assert_location_characteristic.py index ac6f965..8c20fa2 100644 --- a/gatewayconfig/bluetooth/characteristics/assert_location_characteristic.py +++ b/gatewayconfig/bluetooth/characteristics/assert_location_characteristic.py @@ -71,8 +71,9 @@ def WriteValue(self, value, options): fee = assert_location_details.fee payer = assert_location_details.payer - logger.debug("Going to assert location for h3 %s, owner %s, nonce %s, amount %s, fee %s, payer %s" % - (h3_string, owner, nonce, amount, fee, payer)) + logger.debug("Going to assert location for h3 %s, owner %s, nonce %s, amount %s, fee %s, payer %s" % + (h3_string, owner, nonce, amount, fee, payer)) + miner_assert_request = miner_interface.AssertLocation(h3_string, owner, nonce, amount, fee, payer) logger.debug("Asserted location") diff --git a/gatewayconfig/bluetooth/characteristics/diagnostics_characteristic.py b/gatewayconfig/bluetooth/characteristics/diagnostics_characteristic.py index 955c070..d76968b 100644 --- a/gatewayconfig/bluetooth/characteristics/diagnostics_characteristic.py +++ b/gatewayconfig/bluetooth/characteristics/diagnostics_characteristic.py @@ -1,5 +1,4 @@ import dbus -import os from time import sleep from lib.cputemp.service import Characteristic diff --git a/gatewayconfig/bluetooth/characteristics/ethernet_online_characteristic.py b/gatewayconfig/bluetooth/characteristics/ethernet_online_characteristic.py index b00393b..7088fb8 100644 --- a/gatewayconfig/bluetooth/characteristics/ethernet_online_characteristic.py +++ b/gatewayconfig/bluetooth/characteristics/ethernet_online_characteristic.py @@ -26,4 +26,4 @@ def ReadValue(self, options): is_ethernet_online = read_ethernet_is_online(self.ethernet_is_online_filepath) logger.debug("Ethernet is online: %s" % is_ethernet_online) - return string_to_dbus_encoded_byte_array(is_ethernet_online) \ No newline at end of file + return string_to_dbus_encoded_byte_array(is_ethernet_online) diff --git a/gatewayconfig/bluetooth/characteristics/lights_characteristic.py b/gatewayconfig/bluetooth/characteristics/lights_characteristic.py index d44a3a4..d274681 100644 --- a/gatewayconfig/bluetooth/characteristics/lights_characteristic.py +++ b/gatewayconfig/bluetooth/characteristics/lights_characteristic.py @@ -1,6 +1,3 @@ -import logging -import dbus - from lib.cputemp.service import Characteristic from gatewayconfig.helpers import string_to_dbus_encoded_byte_array @@ -24,4 +21,4 @@ def __init__(self, service): def ReadValue(self, options): logger.debug('Read Lights') - return string_to_dbus_encoded_byte_array(DEFAULT_LIGHTS_VALUE) \ No newline at end of file + return string_to_dbus_encoded_byte_array(DEFAULT_LIGHTS_VALUE) diff --git a/gatewayconfig/bluetooth/characteristics/mac_address_characteristic.py b/gatewayconfig/bluetooth/characteristics/mac_address_characteristic.py index c1d8e95..ad31316 100644 --- a/gatewayconfig/bluetooth/characteristics/mac_address_characteristic.py +++ b/gatewayconfig/bluetooth/characteristics/mac_address_characteristic.py @@ -1,6 +1,3 @@ -import logging -import dbus - from lib.cputemp.service import Characteristic from gatewayconfig.helpers import string_to_dbus_encoded_byte_array @@ -24,4 +21,4 @@ def __init__(self, service, eth0_mac_address): def ReadValue(self, options): logger.debug('Read Mac Address') - return string_to_dbus_encoded_byte_array(self.eth0_mac_address) \ No newline at end of file + return string_to_dbus_encoded_byte_array(self.eth0_mac_address) diff --git a/gatewayconfig/bluetooth/characteristics/manufacturer_name_characteristic.py b/gatewayconfig/bluetooth/characteristics/manufacturer_name_characteristic.py index 790789d..93911d1 100644 --- a/gatewayconfig/bluetooth/characteristics/manufacturer_name_characteristic.py +++ b/gatewayconfig/bluetooth/characteristics/manufacturer_name_characteristic.py @@ -7,6 +7,7 @@ logger = get_logger(__name__) MANUFACTURER_NAME = "Nebra LTD." + class ManufacturerNameCharacteristic(Characteristic): def __init__(self, service): Characteristic.__init__( @@ -15,4 +16,4 @@ def __init__(self, service): def ReadValue(self, options): logger.debug('Read Manufacturer') - return string_to_dbus_encoded_byte_array(MANUFACTURER_NAME) \ No newline at end of file + return string_to_dbus_encoded_byte_array(MANUFACTURER_NAME) diff --git a/gatewayconfig/bluetooth/characteristics/public_key_characteristic.py b/gatewayconfig/bluetooth/characteristics/public_key_characteristic.py index 5e5c46f..3dfade0 100644 --- a/gatewayconfig/bluetooth/characteristics/public_key_characteristic.py +++ b/gatewayconfig/bluetooth/characteristics/public_key_characteristic.py @@ -1,7 +1,3 @@ - -import logging -import dbus - from lib.cputemp.service import Characteristic from gatewayconfig.helpers import string_to_dbus_encoded_byte_array @@ -27,4 +23,4 @@ def ReadValue(self, options): logger.debug("Read Public Key") self.shared_state.load_public_key() logger.debug("Read Public Key: %s", self.shared_state.public_key) - return string_to_dbus_encoded_byte_array(self.shared_state.public_key) \ No newline at end of file + return string_to_dbus_encoded_byte_array(self.shared_state.public_key) diff --git a/gatewayconfig/bluetooth/characteristics/wifi_configured_services_characteristic.py b/gatewayconfig/bluetooth/characteristics/wifi_configured_services_characteristic.py index d1661a9..ccf9340 100644 --- a/gatewayconfig/bluetooth/characteristics/wifi_configured_services_characteristic.py +++ b/gatewayconfig/bluetooth/characteristics/wifi_configured_services_characteristic.py @@ -22,7 +22,6 @@ def __init__(self, service, shared_state): self.shared_state = shared_state # logger.debug("Constructed WifiConfiguredServicesCharacteristic %s" % self.shared_state.to_s()) - def ReadValue(self, options): logger.debug("Read Wifi CONFIGURED Services") try: diff --git a/gatewayconfig/bluetooth/characteristics/wifi_connect_characteristic.py b/gatewayconfig/bluetooth/characteristics/wifi_connect_characteristic.py index c1167fe..fd3a88d 100644 --- a/gatewayconfig/bluetooth/characteristics/wifi_connect_characteristic.py +++ b/gatewayconfig/bluetooth/characteristics/wifi_connect_characteristic.py @@ -1,5 +1,3 @@ -import dbus - from lib.cputemp.service import Characteristic from gatewayconfig.logger import get_logger @@ -74,7 +72,7 @@ def StartNotify(self): try: self.connect_to_wifi(self.wifi_service, self.wifi_password) except Exception: - pass + logger.exception("Wifi connect failed for unknown reason") # Notify wifi status wifi_status = self.check_wifi_status() diff --git a/gatewayconfig/bluetooth/characteristics/wifi_remove_characteristic.py b/gatewayconfig/bluetooth/characteristics/wifi_remove_characteristic.py index 3759a2e..2b47dbe 100644 --- a/gatewayconfig/bluetooth/characteristics/wifi_remove_characteristic.py +++ b/gatewayconfig/bluetooth/characteristics/wifi_remove_characteristic.py @@ -52,7 +52,7 @@ def WriteValue(self, value, options): wifi_remove_ssid.ParseFromString(bytes(value)) nmcli_custom.connection.delete(wifi_remove_ssid.service) logger.debug('Connection %s should be deleted' - % wifi_remove_ssid.service) + % wifi_remove_ssid.service) def ReadValue(self, options): logger.debug('Read WiFi Remove') diff --git a/gatewayconfig/bluetooth/characteristics/wifi_services_characteristic.py b/gatewayconfig/bluetooth/characteristics/wifi_services_characteristic.py index e0c1197..4520c70 100644 --- a/gatewayconfig/bluetooth/characteristics/wifi_services_characteristic.py +++ b/gatewayconfig/bluetooth/characteristics/wifi_services_characteristic.py @@ -35,7 +35,7 @@ def ReadValue(self, options): if(is_valid_ssid(ssid_str)): logger.debug("%s is a valid ssid" % ssid_str) ssid_unknown = ssid_str not in known_wifi_services.services - + if(ssid_unknown): logger.debug("%s ssid is unknown" % ssid_str) known_wifi_services.services.append(ssid_str) @@ -54,4 +54,3 @@ def ReadValue(self, options): except Exception: logger.exception('WifiServicesCharacteristic failed for unknown reason') - diff --git a/gatewayconfig/bluetooth/characteristics/wifi_ssid_characteristic.py b/gatewayconfig/bluetooth/characteristics/wifi_ssid_characteristic.py index cf0a768..28cf3ea 100644 --- a/gatewayconfig/bluetooth/characteristics/wifi_ssid_characteristic.py +++ b/gatewayconfig/bluetooth/characteristics/wifi_ssid_characteristic.py @@ -36,4 +36,4 @@ def ReadValue(self, options): else: logger.debug("Ignoring SSID: %s" % ssid_str) - return string_to_dbus_encoded_byte_array(active_connection) \ No newline at end of file + return string_to_dbus_encoded_byte_array(active_connection) diff --git a/gatewayconfig/bluetooth/descriptors/add_gateway_descriptor.py b/gatewayconfig/bluetooth/descriptors/add_gateway_descriptor.py index 2656def..8b18257 100644 --- a/gatewayconfig/bluetooth/descriptors/add_gateway_descriptor.py +++ b/gatewayconfig/bluetooth/descriptors/add_gateway_descriptor.py @@ -2,6 +2,7 @@ from gatewayconfig.helpers import string_to_dbus_encoded_byte_array import gatewayconfig.constants as constants + class AddGatewayDescriptor(Descriptor): def __init__(self, characteristic): diff --git a/gatewayconfig/bluetooth/descriptors/assert_location_descriptor.py b/gatewayconfig/bluetooth/descriptors/assert_location_descriptor.py index 0abd44f..0c9c367 100644 --- a/gatewayconfig/bluetooth/descriptors/assert_location_descriptor.py +++ b/gatewayconfig/bluetooth/descriptors/assert_location_descriptor.py @@ -2,6 +2,7 @@ from gatewayconfig.helpers import string_to_dbus_encoded_byte_array import gatewayconfig.constants as constants + class AssertLocationDescriptor(Descriptor): def __init__(self, characteristic): @@ -12,4 +13,3 @@ def __init__(self, characteristic): def ReadValue(self, options): return string_to_dbus_encoded_byte_array(constants.ASSERT_LOCATION_LABEL) - diff --git a/gatewayconfig/bluetooth/descriptors/lights_descriptor.py b/gatewayconfig/bluetooth/descriptors/lights_descriptor.py index 938e116..ba28060 100644 --- a/gatewayconfig/bluetooth/descriptors/lights_descriptor.py +++ b/gatewayconfig/bluetooth/descriptors/lights_descriptor.py @@ -2,6 +2,7 @@ from gatewayconfig.helpers import string_to_dbus_encoded_byte_array import gatewayconfig.constants as constants + class LightsDescriptor(Descriptor): def __init__(self, characteristic): @@ -12,4 +13,3 @@ def __init__(self, characteristic): def ReadValue(self, options): return string_to_dbus_encoded_byte_array(constants.LIGHTS_LABEL) - diff --git a/gatewayconfig/bluetooth/descriptors/mac_address_descriptor.py b/gatewayconfig/bluetooth/descriptors/mac_address_descriptor.py index 1934183..0d2ab47 100644 --- a/gatewayconfig/bluetooth/descriptors/mac_address_descriptor.py +++ b/gatewayconfig/bluetooth/descriptors/mac_address_descriptor.py @@ -2,6 +2,7 @@ from gatewayconfig.helpers import string_to_dbus_encoded_byte_array import gatewayconfig.constants as constants + class MacAddressDescriptor(Descriptor): def __init__(self, characteristic): diff --git a/gatewayconfig/bluetooth/descriptors/onboarding_key_descriptor.py b/gatewayconfig/bluetooth/descriptors/onboarding_key_descriptor.py index 6ef6bfb..da217eb 100644 --- a/gatewayconfig/bluetooth/descriptors/onboarding_key_descriptor.py +++ b/gatewayconfig/bluetooth/descriptors/onboarding_key_descriptor.py @@ -2,6 +2,7 @@ from gatewayconfig.helpers import string_to_dbus_encoded_byte_array import gatewayconfig.constants as constants + class OnboardingKeyDescriptor(Descriptor): def __init__(self, characteristic): diff --git a/gatewayconfig/bluetooth/descriptors/opaque_structure_descriptor.py b/gatewayconfig/bluetooth/descriptors/opaque_structure_descriptor.py index bd9cf80..a75480d 100644 --- a/gatewayconfig/bluetooth/descriptors/opaque_structure_descriptor.py +++ b/gatewayconfig/bluetooth/descriptors/opaque_structure_descriptor.py @@ -2,6 +2,7 @@ from lib.cputemp.service import Descriptor import gatewayconfig.constants as constants + class OpaqueStructureDescriptor(Descriptor): def __init__(self, characteristic): @@ -20,4 +21,4 @@ def ReadValue(self, options): value.append(dbus.Byte(0x00)) value.append(dbus.Byte(0x00)) - return value \ No newline at end of file + return value diff --git a/gatewayconfig/bluetooth/descriptors/public_key_descriptor.py b/gatewayconfig/bluetooth/descriptors/public_key_descriptor.py index c2a63f0..1d74630 100644 --- a/gatewayconfig/bluetooth/descriptors/public_key_descriptor.py +++ b/gatewayconfig/bluetooth/descriptors/public_key_descriptor.py @@ -2,6 +2,7 @@ from gatewayconfig.helpers import string_to_dbus_encoded_byte_array import gatewayconfig.constants as constants + class PublicKeyDescriptor(Descriptor): def __init__(self, characteristic): @@ -11,4 +12,4 @@ def __init__(self, characteristic): characteristic) def ReadValue(self, options): - return string_to_dbus_encoded_byte_array(constants.PUBLIC_KEY_LABEL) \ No newline at end of file + return string_to_dbus_encoded_byte_array(constants.PUBLIC_KEY_LABEL) diff --git a/gatewayconfig/bluetooth/descriptors/utf8_format_descriptor.py b/gatewayconfig/bluetooth/descriptors/utf8_format_descriptor.py index c38bb65..2c09a4a 100644 --- a/gatewayconfig/bluetooth/descriptors/utf8_format_descriptor.py +++ b/gatewayconfig/bluetooth/descriptors/utf8_format_descriptor.py @@ -2,6 +2,7 @@ from lib.cputemp.service import Descriptor import gatewayconfig.constants as constants + class UTF8FormatDescriptor(Descriptor): def __init__(self, characteristic): @@ -20,4 +21,4 @@ def ReadValue(self, options): value.append(dbus.Byte(0x00)) value.append(dbus.Byte(0x00)) - return value \ No newline at end of file + return value diff --git a/gatewayconfig/bluetooth/descriptors/wifi_configured_services_descriptor.py b/gatewayconfig/bluetooth/descriptors/wifi_configured_services_descriptor.py index aa63a66..d030a84 100644 --- a/gatewayconfig/bluetooth/descriptors/wifi_configured_services_descriptor.py +++ b/gatewayconfig/bluetooth/descriptors/wifi_configured_services_descriptor.py @@ -2,6 +2,7 @@ from gatewayconfig.helpers import string_to_dbus_encoded_byte_array import gatewayconfig.constants as constants + class WifiConfiguredServicesDescriptor(Descriptor): def __init__(self, characteristic): diff --git a/gatewayconfig/bluetooth/descriptors/wifi_connect_descriptor.py b/gatewayconfig/bluetooth/descriptors/wifi_connect_descriptor.py index 8dd010f..e387288 100644 --- a/gatewayconfig/bluetooth/descriptors/wifi_connect_descriptor.py +++ b/gatewayconfig/bluetooth/descriptors/wifi_connect_descriptor.py @@ -2,6 +2,7 @@ from gatewayconfig.helpers import string_to_dbus_encoded_byte_array import gatewayconfig.constants as constants + class WifiConnectDescriptor(Descriptor): def __init__(self, characteristic): diff --git a/gatewayconfig/bluetooth/descriptors/wifi_remove_descriptor.py b/gatewayconfig/bluetooth/descriptors/wifi_remove_descriptor.py index 3accd64..90ab909 100644 --- a/gatewayconfig/bluetooth/descriptors/wifi_remove_descriptor.py +++ b/gatewayconfig/bluetooth/descriptors/wifi_remove_descriptor.py @@ -2,6 +2,7 @@ from gatewayconfig.helpers import string_to_dbus_encoded_byte_array import gatewayconfig.constants as constants + class WifiRemoveDescriptor(Descriptor): def __init__(self, characteristic): @@ -11,4 +12,4 @@ def __init__(self, characteristic): characteristic) def ReadValue(self, options): - return string_to_dbus_encoded_byte_array(constants.WIFI_REMOVE_VALUE) \ No newline at end of file + return string_to_dbus_encoded_byte_array(constants.WIFI_REMOVE_VALUE) diff --git a/gatewayconfig/bluetooth/descriptors/wifi_services_descriptor.py b/gatewayconfig/bluetooth/descriptors/wifi_services_descriptor.py index 457932a..f35e388 100644 --- a/gatewayconfig/bluetooth/descriptors/wifi_services_descriptor.py +++ b/gatewayconfig/bluetooth/descriptors/wifi_services_descriptor.py @@ -2,6 +2,7 @@ from gatewayconfig.helpers import string_to_dbus_encoded_byte_array import gatewayconfig.constants as constants + class WifiServicesDescriptor(Descriptor): def __init__(self, characteristic): @@ -11,4 +12,4 @@ def __init__(self, characteristic): characteristic) def ReadValue(self, options): - return string_to_dbus_encoded_byte_array(constants.WIFI_SERVICES_LABEL) \ No newline at end of file + return string_to_dbus_encoded_byte_array(constants.WIFI_SERVICES_LABEL) diff --git a/gatewayconfig/bluetooth/descriptors/wifi_ssid_descriptor.py b/gatewayconfig/bluetooth/descriptors/wifi_ssid_descriptor.py index 7246d1d..c62e762 100644 --- a/gatewayconfig/bluetooth/descriptors/wifi_ssid_descriptor.py +++ b/gatewayconfig/bluetooth/descriptors/wifi_ssid_descriptor.py @@ -2,6 +2,7 @@ from gatewayconfig.helpers import string_to_dbus_encoded_byte_array import gatewayconfig.constants as constants + class WifiSSIDDescriptor(Descriptor): def __init__(self, characteristic): @@ -11,4 +12,4 @@ def __init__(self, characteristic): characteristic) def ReadValue(self, options): - return string_to_dbus_encoded_byte_array(constants.WIFI_SSID_LABEL) \ No newline at end of file + return string_to_dbus_encoded_byte_array(constants.WIFI_SSID_LABEL) diff --git a/gatewayconfig/bluetooth/services/device_information_service.py b/gatewayconfig/bluetooth/services/device_information_service.py index c780435..7e60ac9 100644 --- a/gatewayconfig/bluetooth/services/device_information_service.py +++ b/gatewayconfig/bluetooth/services/device_information_service.py @@ -4,10 +4,11 @@ from gatewayconfig.bluetooth.characteristics.serial_number_characteristic import SerialNumberCharacteristic import gatewayconfig.constants as constants + class DeviceInformationService(Service): # Service that provides basic information def __init__(self, index, eth0_mac_address): Service.__init__(self, index, constants.DEVINFO_SVC_UUID, True) self.add_characteristic(ManufacturerNameCharacteristic(self)) self.add_characteristic(FirmwareRevisionCharacteristic(self)) - self.add_characteristic(SerialNumberCharacteristic(self, eth0_mac_address)) \ No newline at end of file + self.add_characteristic(SerialNumberCharacteristic(self, eth0_mac_address)) diff --git a/gatewayconfig/bluetooth/services/helium_service.py b/gatewayconfig/bluetooth/services/helium_service.py index eeb8bbd..dc318d8 100644 --- a/gatewayconfig/bluetooth/services/helium_service.py +++ b/gatewayconfig/bluetooth/services/helium_service.py @@ -3,7 +3,6 @@ from gatewayconfig.bluetooth.characteristics.onboarding_key_characteristic import OnboardingKeyCharacteristic from gatewayconfig.bluetooth.characteristics.public_key_characteristic import PublicKeyCharacteristic from gatewayconfig.bluetooth.characteristics.wifi_services_characteristic import WifiServicesCharacteristic -from gatewayconfig.bluetooth.characteristics.wifi_configured_services_characteristic import WifiConfiguredServicesCharacteristic from gatewayconfig.bluetooth.characteristics.diagnostics_characteristic import DiagnosticsCharacteristic from gatewayconfig.bluetooth.characteristics.mac_address_characteristic import MacAddressCharacteristic from gatewayconfig.bluetooth.characteristics.lights_characteristic import LightsCharacteristic @@ -14,11 +13,16 @@ from gatewayconfig.bluetooth.characteristics.ethernet_online_characteristic import EthernetOnlineCharacteristic from gatewayconfig.bluetooth.characteristics.software_version_characteristic import SoftwareVersionCharacteristic from gatewayconfig.bluetooth.characteristics.wifi_remove_characteristic import WifiRemoveCharacteristic +from gatewayconfig.bluetooth.characteristics.wifi_configured_services_characteristic import ( + WifiConfiguredServicesCharacteristic +) import gatewayconfig.constants as constants + class HeliumService(Service): - def __init__(self, index, eth0_mac_address, wlan0_mac_address, firmware_version, ethernet_is_online_filepath, shared_state): + def __init__(self, index, eth0_mac_address, wlan0_mac_address, firmware_version, ethernet_is_online_filepath, + shared_state): Service.__init__(self, index, constants.HELIUM_SERVICE_UUID, True) self.add_characteristic(OnboardingKeyCharacteristic(self, shared_state)) diff --git a/gatewayconfig/constants.py b/gatewayconfig/constants.py index 475877d..d027138 100644 --- a/gatewayconfig/constants.py +++ b/gatewayconfig/constants.py @@ -93,7 +93,7 @@ # https://github.com/helium/hotspot-app/blob/75df9970088606b0cc919929edc86764cf814668/src/features/hotspots/settings/WifiSetup.tsx#L26 WIFI_CONNECTED = "connected" WIFI_ERROR = "error" -WIFI_INVALID_PASSWORD = "invalid" +WIFI_INVALID_PASSWORD = "invalid" # nosec B105:hardcoded_password_string false positive # WiFi Status Mapping WIFI_STATUSES = { diff --git a/gatewayconfig/file_loader.py b/gatewayconfig/file_loader.py index b2586f4..f5f4cb4 100644 --- a/gatewayconfig/file_loader.py +++ b/gatewayconfig/file_loader.py @@ -1,17 +1,17 @@ -import json -from retry import retry from gatewayconfig.logger import get_logger from gatewayconfig.constants import ETHERNET_IS_ONLINE_CARRIER_VAL logger = get_logger(__name__) RETRY_SLEEP_SECONDS = 60 + def read_eth0_mac_address(eth0_mac_address_filepath): return open(eth0_mac_address_filepath) \ .readline() \ .strip() \ .upper() + def read_wlan0_mac_address(wlan0_mac_address_filepath): try: return open(wlan0_mac_address_filepath) \ @@ -21,6 +21,7 @@ def read_wlan0_mac_address(wlan0_mac_address_filepath): except FileNotFoundError: return "FF:FF:FF:FF:FF:FF" + def read_ethernet_is_online(ethernet_is_online_filepath): is_ethernet_online = "false" diff --git a/gatewayconfig/gatewayconfig_app.py b/gatewayconfig/gatewayconfig_app.py index 82eac0b..a715aec 100644 --- a/gatewayconfig/gatewayconfig_app.py +++ b/gatewayconfig/gatewayconfig_app.py @@ -1,11 +1,11 @@ import sentry_sdk import threading from gpiozero import Button, LED -import requests + try: # checks if you have access to RPi.GPIO, which is available inside RPi import RPi.GPIO as GPIO -except: +except Exception: # In case of exception, you are executing your script outside of RPi, so import Mock.GPIO import Mock.GPIO as GPIO @@ -27,9 +27,10 @@ class GatewayconfigApp: - def __init__(self, sentry_dsn, balena_app_name, balena_device_uuid, variant, eth0_mac_address_filepath, wlan0_mac_address_filepath, - diagnostics_json_url, ethernet_is_online_filepath, firmware_version - ): + def __init__(self, sentry_dsn, balena_app_name, balena_device_uuid, variant, eth0_mac_address_filepath, + wlan0_mac_address_filepath, diagnostics_json_url, ethernet_is_online_filepath, + firmware_version + ): self.variant = variant self.variant_details = variant_definitions[variant] @@ -43,15 +44,22 @@ def __init__(self, sentry_dsn, balena_app_name, balena_device_uuid, variant, eth logger.debug("Read eth0 mac address %s and wlan0 %s" % (eth0_mac_address, wlan0_mac_address)) self.shared_state.load_public_key() - self.bluetooth_services_processor = BluetoothServicesProcessor(eth0_mac_address, wlan0_mac_address, firmware_version, ethernet_is_online_filepath, self.shared_state) + self.bluetooth_services_processor = BluetoothServicesProcessor( + eth0_mac_address, wlan0_mac_address, + firmware_version, ethernet_is_online_filepath, + self.shared_state + ) self.led_processor = LEDProcessor(self.status_led, self.shared_state) self.diagnostics_processor = DiagnosticsProcessor( diagnostics_json_url, self.shared_state ) self.wifi_processor = WifiProcessor(self.shared_state) - self.bluetooth_advertisement_processor = BluetoothAdvertisementProcessor(eth0_mac_address, self.shared_state, self.variant_details) - + self.bluetooth_advertisement_processor = BluetoothAdvertisementProcessor( + eth0_mac_address, + self.shared_state, + self.variant_details + ) def start(self): logger.debug("Starting ConfigApp") @@ -74,8 +82,8 @@ def stop(self): def init_sentry(self, sentry_dsn, balena_app_name, balena_device_uuid, variant): sentry_sdk.init(sentry_dsn, environment=balena_app_name) - sentry_sdk.set_user({ "id": balena_device_uuid }) - sentry_sdk.set_context("variant", { variant }) + sentry_sdk.set_user({"id": balena_device_uuid}) + sentry_sdk.set_context("variant", {variant}) def init_nmcli(self): nmcli_custom.disable_use_sudo() @@ -83,7 +91,7 @@ def init_nmcli(self): def init_gpio(self): if is_raspberry_pi(): self.user_button = Button(self.get_button_pin(), hold_time=USER_BUTTON_HOLD_SECONDS) - self.user_button.when_held= self.start_bluetooth_advertisement + self.user_button.when_held = self.start_bluetooth_advertisement self.status_led = LED(self.get_status_led_pin()) else: logger.warn("LEDs and buttons are disabled. GPIO not yet supported on this device.") @@ -122,4 +130,3 @@ def get_button_pin(self): def get_status_led_pin(self): return self.variant_details['STATUS'] - diff --git a/gatewayconfig/gatewayconfig_shared_state.py b/gatewayconfig/gatewayconfig_shared_state.py index fee6749..9a243b5 100644 --- a/gatewayconfig/gatewayconfig_shared_state.py +++ b/gatewayconfig/gatewayconfig_shared_state.py @@ -5,6 +5,7 @@ LOGGER = get_logger(__name__) PUBLIC_KEY_UNAVAILABLE = 'Unavailable' + # Context is shared between multiple threads/processes. # This class simplifies and centralizes read/write of the state: class GatewayconfigSharedState: @@ -32,4 +33,4 @@ def load_public_key(self): public_keys = get_public_keys_rust() self.public_key = public_keys['key'] except Exception: - LOGGER.exception("Unable to read public key.") \ No newline at end of file + LOGGER.exception("Unable to read public key.") diff --git a/gatewayconfig/helpers.py b/gatewayconfig/helpers.py index 1fcaad7..2be558a 100644 --- a/gatewayconfig/helpers.py +++ b/gatewayconfig/helpers.py @@ -1,5 +1,6 @@ import dbus + # There is probably a less verbose way of doing this involving builtin functions # and/or maps. But this is how the working code functions. Maintaining the logic # for now. @@ -11,6 +12,7 @@ def string_to_dbus_encoded_byte_array(str): return byte_array + def string_to_dbus_byte_array(str): byte_array = [] @@ -19,5 +21,6 @@ def string_to_dbus_byte_array(str): return byte_array + def is_valid_ssid(ssid_str): - return ssid_str != "--" and ssid_str != "" + return ssid_str != "--" and ssid_str != "" diff --git a/gatewayconfig/logger.py b/gatewayconfig/logger.py index b6c5d1c..4f21e9c 100644 --- a/gatewayconfig/logger.py +++ b/gatewayconfig/logger.py @@ -2,7 +2,8 @@ import logging LOGLEVEL = os.environ.get("LOGLEVEL", "DEBUG") -_log_format = f"%(asctime)s - [%(levelname)s] - %(name)s - (%(filename)s).%(funcName)s -- %(pathname)s:(%(lineno)d) - %(message)s" +_log_format = ("%(asctime)s - [%(levelname)s] - %(name)s - (%(filename)s).%(funcName)s " + "-- %(pathname)s:(%(lineno)d) - %(message)s") def get_stream_handler(): diff --git a/gatewayconfig/nmcli_custom.py b/gatewayconfig/nmcli_custom.py index 32326a2..6505d32 100644 --- a/gatewayconfig/nmcli_custom.py +++ b/gatewayconfig/nmcli_custom.py @@ -85,7 +85,9 @@ def nmcli(self, parameters: CommandParameter) -> str: def disable_use_sudo(): _syscmd.disable_use_sudo() + def enable_use_sudo(): _syscmd._use_sudo = True -disable_use_sudo() \ No newline at end of file + +disable_use_sudo() diff --git a/gatewayconfig/processors/bluetooth_advertisement_processor.py b/gatewayconfig/processors/bluetooth_advertisement_processor.py index 0b635db..24993cd 100644 --- a/gatewayconfig/processors/bluetooth_advertisement_processor.py +++ b/gatewayconfig/processors/bluetooth_advertisement_processor.py @@ -15,11 +15,12 @@ class BluetoothAdvertisementProcessor: def __init__(self, eth0_mac_address, shared_state, variant_details): self.shared_state = shared_state - self.connection_advertisement = BluetoothConnectionAdvertisement(ADVERTISEMENT_INDEX, eth0_mac_address, ADVERTISEMENT_TYPE, variant_details) + self.connection_advertisement = BluetoothConnectionAdvertisement(ADVERTISEMENT_INDEX, eth0_mac_address, + ADVERTISEMENT_TYPE, variant_details) def run(self): logger.debug("Running BluetoothAdvertisementProcessor") - + while True: if(self.shared_state.should_advertise_bluetooth is True): # Prepare to advertise by first scanning wifi and stopping any existing connections @@ -43,4 +44,4 @@ def run(self): self.shared_state.is_advertising_bluetooth = False self.shared_state.should_scan_wifi = False else: - sleep(ADVERTISEMENT_OFF_SLEEP_SECONDS) \ No newline at end of file + sleep(ADVERTISEMENT_OFF_SLEEP_SECONDS) diff --git a/gatewayconfig/processors/bluetooth_services_processor.py b/gatewayconfig/processors/bluetooth_services_processor.py index 63c2296..97049a0 100644 --- a/gatewayconfig/processors/bluetooth_services_processor.py +++ b/gatewayconfig/processors/bluetooth_services_processor.py @@ -7,10 +7,12 @@ class BluetoothServicesProcessor(Application): - def __init__(self, eth0_mac_address, wlan0_mac_address, firmware_version, ethernet_is_online_filepath, shared_state): + def __init__(self, eth0_mac_address, wlan0_mac_address, firmware_version, + ethernet_is_online_filepath, shared_state): super().__init__() self.add_service(DeviceInformationService(0, eth0_mac_address)) - self.add_service(HeliumService(1, eth0_mac_address, wlan0_mac_address, firmware_version, ethernet_is_online_filepath, shared_state)) + self.add_service(HeliumService(1, eth0_mac_address, wlan0_mac_address, firmware_version, + ethernet_is_online_filepath, shared_state)) self.register() # Unlike the other processors, #run is not defined here. Instead, Application#run is used diff --git a/lib/cputemp/bletools.py b/lib/cputemp/bletools.py index 3baa89a..d3b414a 100755 --- a/lib/cputemp/bletools.py +++ b/lib/cputemp/bletools.py @@ -24,7 +24,7 @@ from gi.repository import GObject except ImportError: import gobject as GObject # noqa: F401 - + BLUEZ_SERVICE_NAME = "org.bluez" LE_ADVERTISING_MANAGER_IFACE = "org.bluez.LEAdvertisingManager1" DBUS_OM_IFACE = "org.freedesktop.DBus.ObjectManager" diff --git a/lib/cputemp/service.py b/lib/cputemp/service.py index b179129..438d6ec 100755 --- a/lib/cputemp/service.py +++ b/lib/cputemp/service.py @@ -53,6 +53,7 @@ class NotSupportedException(dbus.exceptions.DBusException): class NotPermittedException(dbus.exceptions.DBusException): _dbus_error_name = "org.bluez.Error.NotPermitted" + class Application(dbus.service.Object): def __init__(self): dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) @@ -328,4 +329,4 @@ def ReadValue(self, options): def WriteValue(self, value, options): if not self.writable: raise NotPermittedException() - self.value = value \ No newline at end of file + self.value = value diff --git a/tests/gatewayconfig/bluetooth/test_bluetooth_connection_advertisement.py b/tests/gatewayconfig/bluetooth/test_bluetooth_connection_advertisement.py index ea7ad13..c425878 100644 --- a/tests/gatewayconfig/bluetooth/test_bluetooth_connection_advertisement.py +++ b/tests/gatewayconfig/bluetooth/test_bluetooth_connection_advertisement.py @@ -16,6 +16,7 @@ VALID_LE_ADVERTISEMENT_IFACE = 'org.bluez.LEAdvertisement1' INVALID_LE_ADVERTISEMENT_IFACE = 'org.fake.iface' + class TestBluetoothConnectionAdvertisement(TestCase): # Prevent error log diff from being trimmed @@ -42,7 +43,7 @@ def test_instantiation(self): ) def test_name_no_friendly(self): - variant_details = { 'FRIENDLY': 'WALRUS' } + variant_details = {'FRIENDLY': 'WALRUS'} advertisement = BluetoothConnectionAdvertisement(105, 'A1:B2:C3:DD:E5:F6', 'peripheral_', variant_details) self.assertEqual( advertisement.local_name, diff --git a/tests/gatewayconfig/processors/test_diagnostics_processor.py b/tests/gatewayconfig/processors/test_diagnostics_processor.py index c8ee7d8..9af76d7 100644 --- a/tests/gatewayconfig/processors/test_diagnostics_processor.py +++ b/tests/gatewayconfig/processors/test_diagnostics_processor.py @@ -1,23 +1,25 @@ from unittest import TestCase from unittest.mock import patch -from time import sleep - from gatewayconfig.gatewayconfig_shared_state import GatewayconfigSharedState from gatewayconfig.processors.diagnostics_processor import DiagnosticsProcessor + class TestDiagnosticsProcessor(TestCase): -# This method will be used by the mock to replace requests.get + # This method will be used by the mock to replace requests.get def mocked_requests_get(*args, **kwargs): - DIAGNOSTICS_JSON_MOCK = {"AN":"acrobatic-neon-caribou","APPNAME":"Indoor","BA":"dev-marvin","BCH":976046, - "BN":"snowy-river","BSP":98.337,"BT":True,"BUTTON":26,"CELLULAR":False,"E0":"00:BD:27:78:06:EF", - "ECC":True,"ECCOB":True,"FR":"915","FRIENDLY":"Nebra Indoor Hotspot Gen 1","FW":"2021.08.16.1", - "ID":"851af059f032f4708fc6b77d07e9bc15","LOR":False,"LTE":False,"MAC":"eth0","MC":"yes","MD":"yes", - "MH":"959816","MN":"symmetric","MR":True,"MS":False,"OK":"11cdcEkzjvbGRwpNkihsqoM4yCsrw66jCznJkVuJDcyTf5xKJP", - "PF":True,"PK":"11cecEkzjvbGdwpNkihsqoM4yCsrw66jCznJkVuJDcyTf5xKJP","RE":"US915","RESET":38, - "RPI":"0000000057a920e3","SPIBUS":"spidev1.2","STATUS":25,"TYPE":"Full","VA":"NEBHNT-IN1", - "W0":"C8:FE:30:FF:F1:72","last_updated":"21:38 UTC 21 Aug 2021"} + DIAGNOSTICS_JSON_MOCK = { + "AN": "acrobatic-neon-caribou", "APPNAME": "Indoor", "BA": "dev-marvin", "BCH": 976046, "BN": "snowy-river", + "BSP": 98.337, "BT": True, "BUTTON": 26, "CELLULAR": False, "E0": "00:BD:27:78:06:EF", "ECC": True, + "ECCOB": True, "FR": "915", "FRIENDLY": "Nebra Indoor Hotspot Gen 1", "FW": "2021.08.16.1", + "ID": "851af059f032f4708fc6b77d07e9bc15", "LOR": False, "LTE": False, "MAC": "eth0", "MC": "yes", + "MD": "yes", "MH": "959816", "MN": "symmetric", "MR": True, "MS": False, + "OK": "11cdcEkzjvbGRwpNkihsqoM4yCsrw66jCznJkVuJDcyTf5xKJP", "PF": True, + "PK": "11cecEkzjvbGdwpNkihsqoM4yCsrw66jCznJkVuJDcyTf5xKJP", "RE": "US915", "RESET": 38, + "RPI": "0000000057a920e3", "SPIBUS": "spidev1.2", "STATUS": 25, "TYPE": "Full", "VA": "NEBHNT-IN1", + "W0": "C8:FE:30:FF:F1:72", "last_updated": "21:38 UTC 21 Aug 2021" + } class MockResponse: def __init__(self, json_data, status_code): @@ -34,7 +36,7 @@ def test_read_diagnostics(self, diagnostics_json_mock): shared_state = GatewayconfigSharedState() self.assertEqual(shared_state.are_diagnostics_ok, False) diagnostics_processor = DiagnosticsProcessor('url/ignored', shared_state) - + diagnostics_processor.read_diagnostics() # sleep(1) self.assertEqual(shared_state.are_diagnostics_ok, True) diff --git a/tests/gatewayconfig/test_file_loader.py b/tests/gatewayconfig/test_file_loader.py index c7681de..1e3bb37 100644 --- a/tests/gatewayconfig/test_file_loader.py +++ b/tests/gatewayconfig/test_file_loader.py @@ -3,6 +3,7 @@ from gatewayconfig.file_loader import read_eth0_mac_address, read_wlan0_mac_address, read_ethernet_is_online + class TestFileLoader(TestCase): @patch( diff --git a/tests/gatewayconfig/test_gatewayconfig_app.py b/tests/gatewayconfig/test_gatewayconfig_app.py index 726cc98..03d07ec 100644 --- a/tests/gatewayconfig/test_gatewayconfig_app.py +++ b/tests/gatewayconfig/test_gatewayconfig_app.py @@ -10,7 +10,16 @@ Device.pin_factory = MockFactory() ETHO_FILE_MOCK = 'A1:B2:C3:DD:E5:F6' -DIAGNOSTICS_RESPONSE_MOCK = '{"AN":"big-apple-animal","APPNAME":"Outdoor","BA":"HELIUM-OUTDOOR-915","BCH":0005594,"BN":"warm-night","BSP":0.0,"BT":true,"BUTTON":24,"CELLULAR":true,"E0":"00:00:00:EF:7F:F7","ECC":true,"ECCOB":true,"FR":"915","FRIENDLY":"Nebra Outdoor Hotspot Gen 1","FW":"2021.10.07.1","ID":"111111111111439dbbdb408ca51a93f6","LOR":true,"LTE":false,"MAC":"eth0","MC":true,"MD":true,"MH":1,"MN":"none","MR":false,"MS":false,"OK":"11111111111111zFs11TjTSb2hPBZMwq527XdQv2L7ibh4Ma4f4D","PF":true,"PK":"11111111111111zFs11TjTSb2hPBZMwq527XdQv2L7ibh4Ma4f4D","RE":"UN123","RESET":38,"RPI":"00000000000092f3","SPIBUS":"spidev1.2","STATUS":25,"TYPE":"Full","VA":"NEBHNT-OUT1","W0":"00:00:6B:20:00:42","last_updated":"17:40 UTC 15 Oct 2021"}' +DIAGNOSTICS_RESPONSE_MOCK = ( + '{"AN":"big-apple-animal","APPNAME":"Outdoor","BA":"HELIUM-OUTDOOR-915","BCH":0005594,"BN":"warm-night",' + '"BSP":0.0,"BT":true,"BUTTON":24,"CELLULAR":true,"E0":"00:00:00:EF:7F:F7","ECC":true,"ECCOB":true,"FR":"915",' + '"FRIENDLY":"Nebra Outdoor Hotspot Gen 1","FW":"2021.10.07.1","ID":"111111111111439dbbdb408ca51a93f6",' + '"LOR":true,"LTE":false,"MAC":"eth0","MC":true,"MD":true,"MH":1,"MN":"none","MR":false,"MS":false,' + '"OK":"11111111111111zFs11TjTSb2hPBZMwq527XdQv2L7ibh4Ma4f4D","PF":true,' + '"PK":"11111111111111zFs11TjTSb2hPBZMwq527XdQv2L7ibh4Ma4f4D","RE":"UN123","RESET":38,"RPI":"00000000000092f3",' + '"SPIBUS":"spidev1.2","STATUS":25,"TYPE":"Full","VA":"NEBHNT-OUT1","W0":"00:00:6B:20:00:42",' + '"last_updated":"17:40 UTC 15 Oct 2021"}') + class TestGatewayconfigSha(TestCase): @@ -21,8 +30,11 @@ class TestGatewayconfigSha(TestCase): @patch('requests.get', response=DIAGNOSTICS_RESPONSE_MOCK) def test_gpio_pins(self, mock_dbus_interface, mock_findadapter, mock_getbus, mock_file, mock_diagnostics): os.environ['BALENA_DEVICE_TYPE'] = 'TEST' - app = GatewayconfigApp('https://11111111111111119f8b0c9b118c415a@o111111.ingest.sentry.io/1111111', 'BALENA_APP_NAME', 'BALENA_DEVICE_UUID', 'NEBHNT-IN1', - 'ETH0_MOCK_USED', 'WLAN0_MAC_ADDRESS_FILEPATH', 'DIAGNOSTICS_JSON_URL', 'ETHERNET_IS_ONLINE_FILEPATH', 'FIRMWARE_VERSION') + app = GatewayconfigApp( + 'https://11111111111111119f8b0c9b118c415a@o111111.ingest.sentry.io/1111111', + 'BALENA_APP_NAME', 'BALENA_DEVICE_UUID', 'NEBHNT-IN1', 'ETH0_MOCK_USED', 'WLAN0_MAC_ADDRESS_FILEPATH', + 'DIAGNOSTICS_JSON_URL', 'ETHERNET_IS_ONLINE_FILEPATH', 'FIRMWARE_VERSION' + ) self.assertEqual(app.variant, 'NEBHNT-IN1') self.assertEqual(app.get_button_pin(), 26) diff --git a/tests/gatewayconfig/test_gatewayconfig_shared_state.py b/tests/gatewayconfig/test_gatewayconfig_shared_state.py index c416ec0..54aac59 100644 --- a/tests/gatewayconfig/test_gatewayconfig_shared_state.py +++ b/tests/gatewayconfig/test_gatewayconfig_shared_state.py @@ -3,6 +3,7 @@ from gatewayconfig.gatewayconfig_shared_state import GatewayconfigSharedState + class TestGatewayconfigSha(TestCase): def test_init(self): @@ -16,9 +17,11 @@ def test_init(self): def test_to_s(self): shared_state = GatewayconfigSharedState() - self.assertEqual(shared_state.to_s(), - '{"wifi_list_cache": [], "should_scan_wifi": false, "should_advertise_bluetooth": true, "is_advertising_bluetooth": false, "are_diagnostics_ok": false, "public_key": "Unavailable"}') - + self.assertEqual(shared_state.to_s(), + '{"wifi_list_cache": [], "should_scan_wifi": false, "should_advertise_bluetooth": true, ' + '"is_advertising_bluetooth": false, "are_diagnostics_ok": false, "public_key": "Unavailable"}' + ) + @patch('gatewayconfig.gatewayconfig_shared_state.get_public_keys_rust', return_value={'key': 'foo'}) def test_load_public_key(self, _): shared_state = GatewayconfigSharedState() diff --git a/tests/lib/cputemp/test_nmcli_custom.py b/tests/lib/cputemp/test_nmcli_custom.py index 4317fda..e8c03d3 100644 --- a/tests/lib/cputemp/test_nmcli_custom.py +++ b/tests/lib/cputemp/test_nmcli_custom.py @@ -177,4 +177,4 @@ def test_handle_called_process_error_exception(self): # We shouldn't get a result as we expect an exception to be # thrown and the handle_error method to be called. self.assertEqual(result, None) - self.sys_command._handle_error.assert_called() \ No newline at end of file + self.sys_command._handle_error.assert_called()