diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d8f9270..e168376 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,22 +3,26 @@ on: [ push, pull_request ] name: CI jobs: - ruff: - name: Ruff + checks: + name: Checks runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: chartboost/ruff-action@v1 - with: - version: "0.1.3" + strategy: + matrix: + python-version: [ 3.12 ] + check: [ ruff, blue, isort, pydocstyle, radon, mypy, bandit ] - isort: - name: Isort - runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: isort/isort-action@v1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 with: - sort-paths: "netmikro" - requirements-files: "requirements.txt" - configuration: "--profile black -l 79" + python-version: ${{ matrix.python-version }} + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install poetry + poetry config virtualenvs.create false + poetry install + - name: Run checks + run: | + task ${{ matrix.check }} diff --git a/netmikro/exceptions.py b/netmikro/exceptions.py index 976e28c..10a078c 100644 --- a/netmikro/exceptions.py +++ b/netmikro/exceptions.py @@ -1,10 +1,10 @@ -class InvalidIpAddress(Exception): +class InvalidIpAddress(Exception): # noqa: D101 pass -class InvalidNtpMode(Exception): +class InvalidNtpMode(Exception): # noqa: D101 pass -class UndefinedBooleanValue(Exception): +class UndefinedBooleanValue(Exception): # noqa: D101 pass diff --git a/netmikro/modules/base.py b/netmikro/modules/base.py index a07cfa0..fd6e72a 100644 --- a/netmikro/modules/base.py +++ b/netmikro/modules/base.py @@ -1,11 +1,26 @@ -from typing import Optional - from netmiko.mikrotik.mikrotik_ssh import MikrotikRouterOsSSH from ..utils.common import IpAddress class Base: + """Class that generates the connection with a MikroTik router. + + Args: + host (str): IP address of the router you want to connect to. + username (str): Username to be used in the connection. + password (str): Password to be used in the connection. + ssh_port (int): SSH port to be used in the connection. + delay (float): Time delay between command executions on the router. + + Attributes: + host (IpAddress): IP address of the router you want to connect to. + username (str): Username to be used in the connection. + password (str): Password to be used in the connection. + ssh_port (int): SSH port to be used in the connection. + delay (float): Time delay between command executions on the router. + """ + def __init__( self, host: str, @@ -14,17 +29,7 @@ def __init__( ssh_port: int = 22, delay: float = 0, ): - """ - Class that generates the connection with a MikroTik router. - - Parameters: - host (str): IP address of the router you want to connect to. - username (str): Username to be used in the connection. - password (str): Password to be used in the connection. - ssh_port (int): SSH port to be used in the connection. - delay (float): Time delay between command executions on the router. - """ - self.host = IpAddress(host) + self.host = host self.username = username self.password = password self.ssh_port = ssh_port @@ -39,28 +44,43 @@ def __init__( } self._connection = MikrotikRouterOsSSH(**_auth) - def _get(self, command: str) -> Optional[str]: - output = self._connection.send_command( - command_string=f'return [{command}]' - ) - if output == '': - return None + def _get(self, command: str) -> str: + output = self._connection.send_command(f'return [{command}]').strip() return output + def _get_number(self, command: str) -> int: + output = self._connection.send_command(f'return [{command}]').strip() + if output == '': + return 0 + return int(output) + + def _get_bool(self, command: str) -> bool: + output = self._connection.send_command(f'return [{command}]').strip() + if output == 'true': + return True + return False + + def _get_list_ips(self, command: str) -> list[IpAddress]: + output = ( + self._connection.send_command(f'return [{command}]') + .strip() + .split(';') + ) + return [IpAddress(ip) for ip in output] + def disconnect(self): + """Disconnects the connection with the router.""" return self._connection.disconnect() def cmd(self, command: str) -> str: - """ - Runs a command in the router's terminal. + """Runs a command in the router's terminal. - Parameters: - command: Command to be executed + Args: + command (str): Command to be executed Returns: - Output of the command + str: Output of the command """ - # The `expect_string` parameter is a regex (format: [admin@mikrotik]) # necessary in case the router's identity is changed, # there is no ReadTimeout error due to the output format changing, diff --git a/netmikro/modules/ip.py b/netmikro/modules/ip.py index 22f7f64..6b65994 100644 --- a/netmikro/modules/ip.py +++ b/netmikro/modules/ip.py @@ -2,17 +2,25 @@ from netmikro.modules.base import Base -from ..utils import boolean, validate_port +from ..utils import validate_port @dataclass class IpService: + """Class for representing ip service on a MikroTik router.""" + port: int disabled: bool - available_from: None | list[str] + available_from: str class Ip(Base): + """Class that generates the connection with a MikroTik router. + + Attributes: + service (dict): Dictionary with the services available on the router. + """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -30,17 +38,14 @@ def __init__(self, *args, **kwargs): self.service = { service: IpService( port=int(self._get(f'/ip service get {service} port')), - disabled=boolean( - self._get(f'/ip service get {service} disabled') - ), + disabled=self._get_bool(f'/ip service get {service} disabled'), available_from=self._get(f'/ip service get {service} address'), ) for service in _service_names } def ip_port_set(self, service_name: str, port: int) -> None: - """ - Set the API port number. + """Set the API port number. Args: service_name: The service to be changed. diff --git a/netmikro/modules/system.py b/netmikro/modules/system.py index 9133a36..8bc3c02 100644 --- a/netmikro/modules/system.py +++ b/netmikro/modules/system.py @@ -8,88 +8,98 @@ # noinspection PyUnresolvedReferences class System(Base): + """Gets system-related information from the router. + + Attributes: + identity: Name of the router (format: 'HS-A (192.168.88.1) on RB912UAG-5HPnD (mipsbe)') + routerboard: If the router is a RouterBoard, returns a dictionary with the following keys: + - model: RouterBoard model + - revision: Revision version + - serial-number: Serial number of the router + license: A dictionary with router license information, with the following keys: + - software-id: Software ID + - level: License level + - features: License features + note: Notes of about the router + resources: A dictionary with router hardware information, with the following keys: + - cpu: CPU model + - cpu-frequency: CPU frequency (MHz) + - memory: Total RAM (Bytes) + - storage: Total storage (Bytes) + - architecture: Router architecture + - board-name: Name of the board Routerboard + - version: Router version + """ + def __init__(self, *args, **kwargs): - """ - Gets system-related information from the router - - Attributes: - identity: Name of the router (format: 'HS-A (192.168.88.1) on RB912UAG-5HPnD (mipsbe)') - routerboard: If the router is a RouterBoard, returns a dictionary with the following keys: - - model: RouterBoard model - - revision: Revision version - - serial-number: Serial number of the router - license: A dictionary with router license information, with the following keys: - - software-id: Software ID - - level: License level - - features: License features - note: Notes of about the router - resources: A dictionary with router hardware information, with the following keys: - - cpu: CPU model - - cpu-frequency: CPU frequency (MHz) - - memory: Total RAM (Bytes) - - storage: Total storage (Bytes) - - architecture: Router architecture - - board-name: Name of the board Routerboard - - version: Router version - """ super().__init__(*args, **kwargs) self.identity = self._get('/system identity get name') + # ROUTERBOARD if self.is_routerboard(): + _model = self._get('/system routerboard get model') + _revision = self._get('/system routerboard get revision') + _serial_number = self._get('/system routerboard get serial-number') + _firmware_type = self._get('/system routerboard get firmware-type') + _factory_firmware = self._get( + '/system routerboard get factory-firmware' + ) + _current_firmware = self._get( + '/system routerboard get current-firmware' + ) + _upgrade_firmware = self._get( + '/system routerboard get upgrade-firmware' + ) + self.routerboard: dict[str, str] = { - 'model': self._get('/system routerboard get model'), - 'revision': self._get('/system routerboard get revision'), - 'serial-number': self._get( - '/system routerboard get serial-number' - ), - 'firmware-type': self._get( - '/system routerboard get firmware-type' - ), - 'factory-firmware': self._get( - '/system routerboard get factory-firmware' - ), - 'current-firmware': self._get( - '/system routerboard get current-firmware' - ), - 'upgrade-firmware': self._get( - '/system routerboard get upgrade-firmware' - ), + 'model': _model, + 'revision': _revision, + 'serial-number': _serial_number, + 'firmware-type': _firmware_type, + 'factory-firmware': _factory_firmware, + 'current-firmware': _current_firmware, + 'upgrade-firmware': _upgrade_firmware, } - self.license: dict[str, Union[str, None]] = { - 'software-id': self._get('/system license get software-id'), - 'level': self._get('/system license get nlevel'), - 'features': self._get('/system license get features'), - } - else: - self.license: dict[str, Union[str, None]] = { - 'system-id': self._get('/system license get system-id'), - 'level': self._get('/system license get level'), + # LICENSE + _software_id = self._get('/system license get software-id') + _level = self._get_number('/system license get nlevel') + _features = self._get('/system license get features') + + self.license: dict[str, str | int] = { + 'software-id': _software_id, + 'level': _level, + 'features': _features, } + # NOTE self.note = self._get('/system note get note') - self.resources: dict[str, Union[str | int]] = { - 'cpu': self._get('/system resources get cpu'), - 'cpu-frequency': int( - self._get('/system resource get cpu-frequency') - ), - 'memory': int(self._get('/system resource get total-memory')), - 'storage': int(self._get('/system resource get total-hdd-space')), - 'architecture': self._get( - '/system resource get architecture-name' - ), - 'board-name': self._get('/system resource get board-name'), - 'version': self._get('/system resource get version'), + # RESOURCES + _cpu = self._get('/system resources get cpu') + _cpu_frequency = self._get_number('/system resource get cpu-frequency') + _memory = self._get_number('/system resource get total-memory') + _storage = self._get_number('/system resource get total-hdd-space') + _architecture = self._get('/system resource get architecture-name') + _board_name = self._get('/system resource get board-name') + _version = self._get('/system resource get version') + + self.resources: dict[str, str | int] = { + 'cpu': _cpu, + 'cpu-frequency': _cpu_frequency, + 'memory': _memory, + 'storage': _storage, + 'architecture': _architecture, + 'board-name': _board_name, + 'version': _version, } def __str__(self) -> str: return f'{self.identity} ({self.host}) on {self.resources["board-name"]} ({self.resources["architecture"]})' def clock_time_get(self) -> time: - """ - Returns the router's system time. + """Returns the router's system time. Returns: Time with time zone, an instance of the `time` class. @@ -101,11 +111,13 @@ def clock_time_get(self) -> time: '15:30:00' """ clock_time = self._get('/system clock get time').split(':') - return time(*[int(i) for i in clock_time]) + time_list = [int(i) for i in clock_time] + return time( + hour=time_list[0], minute=time_list[1], second=time_list[2] + ) def clock_date_get(self) -> date: - """ - Returns the router's system date. + """Returns the router's system date. Returns: An instance of the `date` class. @@ -120,8 +132,7 @@ def clock_date_get(self) -> date: return date(*[int(i) for i in clock_date]) def clock_time_zone_get(self) -> str: - """ - Returns the router's time zone. + """Returns the router's time zone. Returns: Time zone in the format Continent/City @@ -133,8 +144,7 @@ def clock_time_zone_get(self) -> str: return self._get('/system clock get time-zone-name') def clock_gmt_offset_get(self) -> str: - """ - Returns the router's GMT offset. + """Returns the router's GMT offset. Returns: GMT offset in the format +/-HH:MM @@ -146,28 +156,24 @@ def clock_gmt_offset_get(self) -> str: return self._get('/system clock get gmt-offset as-string') def clock_dst_active_get(self) -> Optional[bool]: - """ - Returns True if DST is enabled, if not enabled, returns False. + """Returns True if DST is enabled, if not enabled, returns False. Returns: True if DST is enabled, if not enabled, returns False. """ - return boolean(self._get('/system clock get dst-active')) + return self._get_bool('/system clock get dst-active') def clock_time_zone_autodetect_get(self) -> Optional[bool]: - """ - Returns True if time-zone-autodetect is enabled, - if not enabled, it returns False. + """Returns True if time-zone-autodetect is enabled, if not enabled, it returns False. Returns: True if time-zone-autodetect is enabled, if not enabled, returns False. """ - return boolean(self._get('/system clock get time-zone-autodetect')) + return self._get_bool('/system clock get time-zone-autodetect') def health_voltage(self) -> float: - """ - Returns the current voltage at the router + """Returns the current voltage at the router. Returns: Voltage in Volts @@ -180,8 +186,7 @@ def health_voltage(self) -> float: return float(output) def health_temperature(self) -> float: - """ - Returns the current temperature at the router + """Returns the current temperature at the router. Returns: Temperature in Celsius @@ -194,9 +199,7 @@ def health_temperature(self) -> float: return float(output) def history_system_get(self) -> str: - """ - Returns the history of changes made to the router's - system settings during the time it has been running uninterrupted. + """Returns the history of changes made to the router's system settings. Returns: History of changes made to the router's system settings @@ -215,10 +218,9 @@ def history_system_get(self) -> str: return output def identity_set(self, new_identity: str): - """ - Sets the router's identity. + """Sets the router's identity. - Parameters: + Args: new_identity: New identity to be set Examples: @@ -229,10 +231,9 @@ def identity_set(self, new_identity: str): self.identity = new_identity def note_set(self, note: str, show_at_login: bool = False): - """ - Sets the router's note. + """Sets the router's note. - Parameters: + Args: note: New note to be set show_at_login: Specifies whether a new note should be displayed every time a user logs into the router. @@ -240,17 +241,18 @@ def note_set(self, note: str, show_at_login: bool = False): Examples: >>> router.note_set('new_note', True) """ - show_at_login = 'yes' if show_at_login else 'no' + show_at_login_command = 'yes' if show_at_login else 'no' self.cmd( - f'/system note set note="{note}" show-at-login={show_at_login}' + f'/system note set note="{note}" show-at-login={show_at_login_command}' ) - def ntp_client_get(self) -> Dict[str, Union[bool, str, int, List[str]]]: - """ - Returns the NTP client configuration. + def ntp_client_get( + self, + ) -> Dict[str, Union[bool, str, int, List[IpAddress], IpAddress]]: + """Returns the NTP client configuration. Returns: - Dictionary with the NTP client configuration + dict: Dictionary with the NTP client configuration Examples: >>> router.ntp_client_get() @@ -266,22 +268,32 @@ def ntp_client_get(self) -> Dict[str, Union[bool, str, int, List[str]]]: 'vrf': 'main' } """ - - ntp_command = '/system ntp client get' - servers = self._get(f'{ntp_command} servers').split(';') + _enabled = self._get_bool('/system ntp client get enabled') + _mode = self._get('/system ntp client get mode') + _servers = self._get_list_ips('/system ntp client get servers') + _vrf = self._get('/system ntp client get vrf') + _freq_dift = self._get_number('/system ntp client get freq-drift') + _status = self._get('/system ntp client get status') + _synced_server = IpAddress( + self._get('/system ntp client get synced-server') + ) + _synced_stratum = self._get_number( + '/system ntp client get synced-stratum' + ) + _system_offset = self._get_number( + '/system ntp client get system-offset' + ) return { - 'enabled': boolean(self._get(f'{ntp_command} enabled')), - 'mode': self._get(f'{ntp_command} mode'), - 'servers': servers, - 'vrf': self._get(f'{ntp_command} vrf'), - 'freq-dift': self._get(f'{ntp_command} freq-drift as-string'), - 'status': self._get(f'{ntp_command} status'), - 'synced-server': self._get(f'{ntp_command} synced-server'), - 'synced-stratum': int(self._get(f'{ntp_command} synced-stratum')), - 'system-offset': self._get( - f'{ntp_command} system-offset as-string' - ), + 'enabled': _enabled, + 'mode': _mode, + 'servers': _servers, + 'vrf': _vrf, + 'freq-diff': _freq_dift, + 'status': _status, + 'synced-server': _synced_server, + 'synced-stratum': _synced_stratum, + 'system-offset': _system_offset, } def ntp_client_set( @@ -291,14 +303,13 @@ def ntp_client_set( mode: str = 'unicast', vrf: str = 'main', ): - """ - Sets the NTP client configuration. + """Sets the NTP client configuration. - Parameters: - servers: Comma separated list of NTP servers - enabled: Specifies whether the NTP client should be enabled - mode: Specifies the NTP client mode - vrf: Specifies the VRF to be used by the NTP client + Args: + servers (list): List of NTP servers + enabled (bool): Specifies whether the NTP client should be enabled + mode (str): Specifies the NTP client mode + vrf (str): Specifies the VRF to be used by the NTP client Examples: >>> router.ntp_client_set( @@ -308,10 +319,9 @@ def ntp_client_set( 'main' ) """ + servers_command: str = ','.join([str(server) for server in servers]) - servers: str = ','.join([str(server) for server in servers]) - - enabled = 'yes' if enabled else 'no' + enabled_command = 'yes' if enabled else 'no' mode = mode.lower().strip() if mode not in ['unicast', 'broadcast', 'multicast', 'manycast']: raise InvalidNtpMode(f'Invalid mode: {mode}') @@ -319,15 +329,14 @@ def ntp_client_set( self.cmd( f'/system ntp client set ' - f'enabled={enabled} mode={mode} servers={servers} vrf={vrf}' + f'enabled={enabled_command} mode={mode} servers={servers_command} vrf={vrf}' ) def ntp_server_get(self) -> dict[str, Union[bool, str, None]]: - """ - Returns the NTP server configuration. + """Returns the NTP server configuration. Returns: - Dictionary with the NTP server configuration + dict: Dictionary with the NTP server configuration Examples: >>> router.ntp_server_get() @@ -351,6 +360,11 @@ def ntp_server_get(self) -> dict[str, Union[bool, str, None]]: } def is_routerboard(self) -> bool: + """Returns True if the router is a RouterBoard, if not, returns False. + + Returns: + bool: True if the router is a RouterBoard, if not, returns False + """ get_routerboard = self._get('/system routerboard get routerboard') if get_routerboard != 'true': return False diff --git a/netmikro/routeros.py b/netmikro/routeros.py index ac60de8..26b4200 100644 --- a/netmikro/routeros.py +++ b/netmikro/routeros.py @@ -5,8 +5,7 @@ # noinspection PyUnresolvedReferences class RouterOS(Ip, System): - """ - Class that generates the connection with a MikroTik router. + """Class that generates the connection with a MikroTik router. Examples: >>> from netmikro import RouterOS @@ -28,14 +27,22 @@ def __init__( ssh_port: int = 22, delay: float = 0, ): + """Class that generates the connection with a MikroTik router. + + Args: + host (str): IP address of the router you want to connect to. + username (str): Username to be used in the connection. + password (str): Password to be used in the connection. + ssh_port (int): SSH port to be used in the connection. + delay (float): Time delay between command executions on the router. + """ super().__init__(host, username, password, ssh_port, delay) def cmd_multiline(self, commands: List[str]) -> str: - """ - Runs multiple commands in the router's terminal. + """Runs multiple commands in the router's terminal. - Parameters - commands: List of commands to be executed + Args: + commands (List[str]): List of commands to be executed. Returns: str: Output of the commands diff --git a/netmikro/utils/common.py b/netmikro/utils/common.py index 4317be9..a010d86 100644 --- a/netmikro/utils/common.py +++ b/netmikro/utils/common.py @@ -4,8 +4,7 @@ class IpAddress: - """ - Class that represents an IP address. + """Class that represents an IP address. Args: address (str): The IP address to be represented. @@ -50,7 +49,7 @@ def __init__(self, address: str): self.addresses_on_network = addresses_on_network break - self.address_type = self.address_type(octets) + self.address_type = self.address_type_check(octets) def __str__(self) -> str: return self.address @@ -62,9 +61,8 @@ def __eq__(self, other): return self.address == other.address @classmethod - def address_type(cls, octets: list[int]) -> str: - """ - Get the type of IP address. + def address_type_check(cls, octets: list[int]) -> str: + """Get the type of IP address. Args: octets (list): List of integers representing the IP address. diff --git a/netmikro/utils/converter.py b/netmikro/utils/converter.py index 5d3427a..eaeafee 100644 --- a/netmikro/utils/converter.py +++ b/netmikro/utils/converter.py @@ -1,7 +1,17 @@ +from typing import Union + from netmikro.exceptions import UndefinedBooleanValue -def boolean(string: str) -> bool or None or str: +def boolean(string: str) -> Union[bool, None, str]: + """Convert a string to a boolean value. + + Args: + string (str): String to be converted. + + Returns: + Union[bool, None]: Boolean value of the string or None if the string is empty. + """ string = string.strip() if string == 'true': return True diff --git a/netmikro/utils/validators.py b/netmikro/utils/validators.py index d1a32ab..118e40d 100644 --- a/netmikro/utils/validators.py +++ b/netmikro/utils/validators.py @@ -1,10 +1,9 @@ def validate_port(port: int): - """ - Validate if port is valid + """Validate if port is valid. + + Args: + port (int): Port to be validated. - :param port: Port to be validated - :return: True if port is valid, False otherwise """ if port < 1 or port > 65535: raise ValueError(f'Invalid port: {port}') - return diff --git a/pyproject.toml b/pyproject.toml index a037369..55b7929 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,10 @@ taskipy = "^1.12.0" coverage-badge = "^1.1.1" setuptools = "^69.5.1" pytest = "^8.2.0" +bandit = {extras = ["toml"], version = "^1.7.8"} +radon = {extras = ["toml"], version = "^6.0.1"} +mypy = "^1.10.0" +pydocstyle = "^6.3.0" [tool.poetry.group.doc.dependencies] mkdocs-material = "^9.2.8" @@ -56,6 +60,10 @@ mkdocs-git-revision-date-localized-plugin = "^1.2.2" requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" +[tool.mypy] +ignore_missing_imports = true +check_untyped_defs = true + [tool.pytest.ini_options] pythonpath = "." python_files = "test.py tests.py test_*.py tests_*.py *_test.py *_tests.py" @@ -81,10 +89,21 @@ indent-width = 4 "tests/*" = ["F401", "F811"] [tool.taskipy.tasks] -lint = "ruff check . && blue --check --diff . && isort --check --diff ." +ruff = "ruff check ." +blue = "blue --check . --diff" +isort = "isort --check --diff ." +mypy = "mypy -p netmikro" +radon = "radon cc ./netmikro -a -na" +bandit = "bandit -r ./netmikro" +pydocstyle = "pydocstyle ./netmikro --count --convention=google --add-ignore=D100,D104,D105,D107" +lint = "task ruff && task blue && task isort" format = 'blue . && isort .' docs = "mkdocs serve" +quality = "task mypy && task radon && task pydocstyle" +badge = "coverage-badge -o docs/assets/coverage.svg -f" pre_test = "task lint" test = "pytest -s -x --cov=netmikro -vv" -post_test = "coverage html && coverage-badge -o docs/assets/coverage.svg -f && rm requirements.txt && poetry export -f requirements.txt --output requirements.txt --without-hashes" +post_test = "coverage html" export-requirements = "rm requirements.txt && poetry export -f requirements.txt --output requirements.txt --without-hashes" +export-requirements-doc = "poetry export -f requirements.txt --output docs/requirements.txt --without-hashes --only doc" +ready = "task lint && task quality && task bandit && pytest -s -x --cov=netmikro -vv && coverage html && task export-requirements && task export-requirements-doc && task badge" diff --git a/tests/_utils.py b/tests/_utils.py index b09b974..8ab62da 100644 --- a/tests/_utils.py +++ b/tests/_utils.py @@ -5,6 +5,7 @@ from pytest import fixture from netmikro.routeros import RouterOS +from netmikro.utils import IpAddress load_dotenv() @@ -23,6 +24,11 @@ def router(): connection.disconnect() +@fixture(scope='function') +def ip(): + yield IpAddress('1.1.1.1') + + @fixture(scope='session') def chr_router(): """Fixture to create a RouterOS connection to be used in tests.""" diff --git a/tests/test_base.py b/tests/test_base.py new file mode 100644 index 0000000..bcc9950 --- /dev/null +++ b/tests/test_base.py @@ -0,0 +1,9 @@ +from dotenv import load_dotenv + +from tests._utils import router + +load_dotenv() + + +def test_base_get_number_return_0_if_output_is_empty(router): + assert router._get_number('/system/license/get features') == 0 diff --git a/tests/test_system.py b/tests/test_system.py index cd0c4d7..c0718b0 100644 --- a/tests/test_system.py +++ b/tests/test_system.py @@ -69,18 +69,16 @@ def test_system_identity_set(router): router.identity_set(os.getenv('IDENTITY')) -def test_system_license(chr_router): - assert isinstance(chr_router.license, dict) - assert chr_router.license['system-id'] == os.getenv('CHR_SYSTEM_ID') - assert chr_router.license['level'] == os.getenv('CHR_LEVEL') +def test_system_license(router): + assert isinstance(router.license, dict) + assert router.license['software-id'] == os.getenv('SYSTEM_ID_ROUTER') + assert router.license['level'] == int(os.getenv('LEVEL_ROUTER')) def test_system_ntp_client_get(router): ntp_client_output = router.ntp_client_get() assert isinstance(ntp_client_output, dict) assert isinstance(ntp_client_output['enabled'], bool) - for server in ntp_client_output['servers']: - assert validate_ip(server) assert ntp_client_output['vrf'] == 'main' @@ -114,3 +112,7 @@ def test_system_note_set(router): assert router._get('/system note get note') == test_string assert boolean(router._get('/system note get show-at-login')) router.note_set(note=os.getenv('NOTE'), show_at_login=False) + + +def test_system_is_routerboard_false(chr_router): + assert not chr_router.is_routerboard() diff --git a/tests/test_utils.py b/tests/test_utils.py index f2d7605..f3c1766 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -3,6 +3,7 @@ from netmikro.exceptions import UndefinedBooleanValue from netmikro.utils import boolean from netmikro.utils.common import InvalidIpAddress, IpAddress +from tests._utils import ip def test_ip_address_class_invalid_ip(): @@ -12,16 +13,12 @@ def test_ip_address_class_invalid_ip(): IpAddress('999.999.999.999') -def test_ip_address_class_representation(): - ip = '1.1.1.1' - ip_instance = IpAddress(ip) - assert str(ip_instance) == ip +def test_ip_address_class_representation(ip): + assert str(ip) == '1.1.1.1' -def test_ip_address_repr(): - ip = '1.1.1.1' - ip_instance = IpAddress(ip) - assert repr(ip_instance) == f'IpAddress(address="{ip}")' +def test_ip_address_repr(ip): + assert repr(ip) == f'IpAddress(address="{ip}")' def test_ip_address_eq(): @@ -30,6 +27,10 @@ def test_ip_address_eq(): assert ip_instance == IpAddress(ip) +def test_ip_address_class_type_checker(): + assert IpAddress.address_type_check([10, 0, 0, 0]) == 'PRIVATE' + + def test_convert_to_boolean(): assert boolean('true') assert boolean('false') is False