Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions src/linuxmuster_base7/functions/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# network interface helpers
# Signed-off by: thomas@linuxmuster.net
# Assisted by : Claude
# Date : 20260921
# Date : 20261001
#

import csv
Expand All @@ -19,6 +19,10 @@
import environment
import netifaces

# reserved in hardware group names: separates school and group in the
# school-qualified LINBO group id (<school>+<group>)
LINBO_GROUP_SEPARATOR = '+'


# test if ip matches subnet
def ipMatchSubnet(ip, subnet):
Expand Down Expand Up @@ -111,7 +115,9 @@ def validateDeviceRow(row, school='default-school'):
Validate and parse a device row from devices.csv.

Extracts device fields, applies hostname transformation for non-default
schools, and validates hostname, MAC address, and IP address.
schools, and validates group, hostname, MAC address, and IP address.
A group containing '+' is invalid, because '+' separates school and
group in the school-qualified LINBO group id.

Args:
row: CSV row as list of fields
Expand All @@ -136,6 +142,11 @@ def validateDeviceRow(row, school='default-school'):
ip = row[4]
pxe = row[10]

# '+' is reserved for the school-qualified LINBO group id
# (<school>+<group>), so a group must not contain it
if LINBO_GROUP_SEPARATOR in group:
return False, None

# Validate hostname and MAC address
if not isValidHostname(hostname) or not isValidMac(mac):
return False, None
Expand Down
27 changes: 25 additions & 2 deletions tests/test_functions_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# validation.
# Signed-off by: thomas@linuxmuster.net
# Assisted by : Claude
# Date : 20260921
# Date : 20261001
#
"""
Regression tests for linuxmuster_base7.functions.network.isValidHostIpv4().
Expand All @@ -23,7 +23,7 @@

pytest.importorskip('environment', reason='requires linuxmuster-common (environment.py) on sys.path')

from linuxmuster_base7.functions import isValidHostIpv4 # noqa: E402
from linuxmuster_base7.functions import isValidHostIpv4, validateDeviceRow # noqa: E402


@pytest.mark.parametrize('ip, expected', [
Expand Down Expand Up @@ -77,3 +77,26 @@ def test_subnet_aware_network_broadcast_check(ip, subnet, expected):

def test_malformed_subnet_rejects():
assert isValidHostIpv4('10.0.0.1', 'not-a-subnet') is False


def _device_row(group='raum101', hostname='pc01', mac='AA:BB:CC:DD:EE:01', ip='10.0.0.11'):
return ['room', hostname, group, mac, ip, '', '', '', '', '', '1']


@pytest.mark.parametrize('school', ['default-school', 'abc'])
def test_valid_group_is_accepted(school):
is_valid, device = validateDeviceRow(_device_row('raum101'), school)
assert is_valid is True
assert device['group'] == 'raum101'


@pytest.mark.parametrize('group', ['abc+raum101', 'raum+101', '+raum101', 'raum101+'])
@pytest.mark.parametrize('school', ['default-school', 'abc'])
def test_group_with_plus_is_rejected(group, school):
assert validateDeviceRow(_device_row(group), school) == (False, None)


def test_plus_in_group_does_not_modify_the_row():
row = _device_row('abc+raum101')
validateDeviceRow(row, 'abc')
assert row == _device_row('abc+raum101')