lanscape 2.0.0a2__tar.gz → 2.0.1a1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. {lanscape-2.0.0a2/lanscape.egg-info → lanscape-2.0.1a1}/PKG-INFO +2 -2
  2. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/README.md +1 -1
  3. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/core/device_alive.py +1 -1
  4. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/core/ip_parser.py +1 -25
  5. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/core/service_scan.py +3 -20
  6. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/templates/scan/config.html +1 -1
  7. {lanscape-2.0.0a2 → lanscape-2.0.1a1/lanscape.egg-info}/PKG-INFO +2 -2
  8. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/pyproject.toml +1 -1
  9. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/tests/test_api.py +23 -16
  10. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/tests/test_decorators.py +7 -6
  11. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/tests/test_env.py +20 -20
  12. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/tests/test_library.py +2 -1
  13. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/tests/test_logging.py +15 -14
  14. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/tests/test_port_scan.py +9 -3
  15. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/tests/test_service_scan.py +6 -19
  16. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/tests/test_utils.py +13 -12
  17. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/LICENSE +0 -0
  18. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/MANIFEST.in +0 -0
  19. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/__init__.py +0 -0
  20. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/__main__.py +0 -0
  21. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/core/__init__.py +0 -0
  22. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/core/app_scope.py +0 -0
  23. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/core/decorators.py +0 -0
  24. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/core/errors.py +0 -0
  25. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/core/logger.py +0 -0
  26. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/core/mac_lookup.py +0 -0
  27. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/core/net_tools.py +0 -0
  28. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/core/port_manager.py +0 -0
  29. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/core/runtime_args.py +0 -0
  30. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/core/scan_config.py +0 -0
  31. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/core/subnet_scan.py +0 -0
  32. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/core/version_manager.py +0 -0
  33. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/core/web_browser.py +0 -0
  34. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/resources/mac_addresses/convert_csv.py +0 -0
  35. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/resources/mac_addresses/mac_db.json +0 -0
  36. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/resources/ports/convert_csv.py +0 -0
  37. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/resources/ports/full.json +0 -0
  38. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/resources/ports/large.json +0 -0
  39. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/resources/ports/medium.json +0 -0
  40. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/resources/ports/small.json +0 -0
  41. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/resources/ports/test_port_list_scan.json +0 -0
  42. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/resources/services/definitions.jsonc +0 -0
  43. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/__init__.py +0 -0
  44. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/app.py +0 -0
  45. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/blueprints/__init__.py +0 -0
  46. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/blueprints/api/__init__.py +0 -0
  47. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/blueprints/api/port.py +0 -0
  48. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/blueprints/api/scan.py +0 -0
  49. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/blueprints/api/tools.py +0 -0
  50. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/blueprints/web/__init__.py +0 -0
  51. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/blueprints/web/routes.py +0 -0
  52. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/main.py +0 -0
  53. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/shutdown_handler.py +0 -0
  54. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/static/css/style.css +0 -0
  55. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/static/img/ico/android-chrome-192x192.png +0 -0
  56. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/static/img/ico/android-chrome-512x512.png +0 -0
  57. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/static/img/ico/apple-touch-icon.png +0 -0
  58. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/static/img/ico/favicon-16x16.png +0 -0
  59. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/static/img/ico/favicon-32x32.png +0 -0
  60. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/static/img/ico/favicon.ico +0 -0
  61. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/static/img/ico/site.webmanifest +0 -0
  62. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/static/js/core.js +0 -0
  63. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/static/js/layout-sizing.js +0 -0
  64. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/static/js/main.js +0 -0
  65. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/static/js/on-tab-close.js +0 -0
  66. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/static/js/quietReload.js +0 -0
  67. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/static/js/scan-config.js +0 -0
  68. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/static/js/shutdown-server.js +0 -0
  69. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/static/js/subnet-info.js +0 -0
  70. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/static/js/subnet-selector.js +0 -0
  71. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/static/lanscape.webmanifest +0 -0
  72. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/templates/base.html +0 -0
  73. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/templates/core/head.html +0 -0
  74. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/templates/core/scripts.html +0 -0
  75. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/templates/error.html +0 -0
  76. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/templates/info.html +0 -0
  77. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/templates/main.html +0 -0
  78. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/templates/scan/device-detail.html +0 -0
  79. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/templates/scan/export.html +0 -0
  80. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/templates/scan/ip-table-row.html +0 -0
  81. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/templates/scan/ip-table.html +0 -0
  82. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/templates/scan/overview.html +0 -0
  83. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/templates/scan/scan-error.html +0 -0
  84. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/templates/scan.html +0 -0
  85. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape/ui/templates/shutdown.html +0 -0
  86. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape.egg-info/SOURCES.txt +0 -0
  87. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape.egg-info/dependency_links.txt +0 -0
  88. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape.egg-info/entry_points.txt +0 -0
  89. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape.egg-info/requires.txt +0 -0
  90. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/lanscape.egg-info/top_level.txt +0 -0
  91. {lanscape-2.0.0a2 → lanscape-2.0.1a1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lanscape
3
- Version: 2.0.0a2
3
+ Version: 2.0.1a1
4
4
  Summary: A python based local network scanner
5
5
  Author-email: Michael Dennis <michael@dipduo.com>
6
6
  License-Expression: MIT
@@ -88,7 +88,7 @@ I use a combination of ARP, ICMP & port testing to determine if a device is onli
88
88
  Recommendations:
89
89
 
90
90
  - Adjust scan configuration
91
- - Configure ARP lookup [ARP lookup setup](./support/arp-issues.md)
91
+ - Configure ARP lookup [ARP lookup setup](./docs/arp-issues.md)
92
92
  - Create a bug
93
93
 
94
94
 
@@ -56,7 +56,7 @@ I use a combination of ARP, ICMP & port testing to determine if a device is onli
56
56
  Recommendations:
57
57
 
58
58
  - Adjust scan configuration
59
- - Configure ARP lookup [ARP lookup setup](./support/arp-issues.md)
59
+ - Configure ARP lookup [ARP lookup setup](./docs/arp-issues.md)
60
60
  - Create a bug
61
61
 
62
62
 
@@ -156,7 +156,7 @@ class ArpLookup():
156
156
  NOTE: This lookup method requires elevated privileges to access the ARP cache.
157
157
 
158
158
 
159
- [Arp Lookup Requirements](/support/arp-issues.md)
159
+ [Arp Lookup Requirements](/docs/arp-issues.md)
160
160
  """
161
161
 
162
162
  @classmethod
@@ -10,7 +10,6 @@ This module provides utilities for parsing various IP address formats including:
10
10
  It also includes validation to prevent processing excessively large IP ranges.
11
11
  """
12
12
  import ipaddress
13
- import re
14
13
 
15
14
  from lanscape.core.errors import SubnetTooLargeError
16
15
 
@@ -50,14 +49,10 @@ def parse_ip_input(ip_input):
50
49
  for ip in net.hosts():
51
50
  ip_ranges.append(ip)
52
51
 
53
- # Handle IP range (e.g., 10.0.0.15-10.0.0.25)
52
+ # Handle IP range (e.g., 10.0.0.15-10.0.0.25) and (e.g., 10.0.9.1-253)
54
53
  elif '-' in entry:
55
54
  ip_ranges += parse_ip_range(entry)
56
55
 
57
- # Handle shorthand IP range (e.g., 10.0.9.1-253)
58
- elif re.search(r'\d+\-\d+', entry):
59
- ip_ranges += parse_shorthand_ip_range(entry)
60
-
61
56
  # If no CIDR or range, assume a single IP
62
57
  else:
63
58
  ip_ranges.append(ipaddress.IPv4Address(entry))
@@ -106,25 +101,6 @@ def parse_ip_range(entry):
106
101
  return list(ip_range_to_list(start_ip, end_ip))
107
102
 
108
103
 
109
- def parse_shorthand_ip_range(entry):
110
- """
111
- Parse a shorthand IP range (e.g., 192.168.1.1-10).
112
-
113
- In this format, only the last octet of the end IP is specified.
114
-
115
- Args:
116
- entry (str): String containing a shorthand IP range
117
-
118
- Returns:
119
- list: List of IPv4Address objects in the range (inclusive)
120
- """
121
- start_ip, end_part = entry.split('-')
122
- start_ip = ipaddress.IPv4Address(start_ip.strip())
123
- end_ip = start_ip.exploded.rsplit('.', 1)[0] + '.' + end_part.strip()
124
-
125
- return list(ip_range_to_list(start_ip, ipaddress.IPv4Address(end_ip)))
126
-
127
-
128
104
  def ip_range_to_list(start_ip, end_ip):
129
105
  """
130
106
  Convert an IP range defined by start and end addresses to a list of addresses.
@@ -2,7 +2,6 @@
2
2
  """
3
3
 
4
4
  from typing import Optional, Union
5
- import sys
6
5
  import asyncio
7
6
  import logging
8
7
  import traceback
@@ -10,6 +9,9 @@ import traceback
10
9
  from lanscape.core.app_scope import ResourceManager
11
10
  from lanscape.core.scan_config import ServiceScanConfig, ServiceScanStrategy
12
11
 
12
+ # asyncio complains more than it needs to
13
+ logging.getLogger("asyncio").setLevel(logging.WARNING)
14
+
13
15
  log = logging.getLogger('ServiceScan')
14
16
  SERVICES = ResourceManager('services').get_jsonc('definitions.jsonc')
15
17
 
@@ -183,22 +185,3 @@ def scan_service(ip: str, port: int, cfg: ServiceScanConfig) -> str:
183
185
 
184
186
  # Use asyncio.run to execute the asynchronous logic synchronously
185
187
  return asyncio.run(_async_scan_service(ip, port, cfg=cfg))
186
-
187
-
188
- def asyncio_logger_suppression():
189
- """Suppress the noisy asyncio transport errors since they are expected in service scanning."""
190
-
191
- # Reduce noisy asyncio transport errors on Windows by switching to Selector policy
192
- if sys.platform.startswith("win"):
193
- try:
194
- asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
195
- except Exception:
196
- pass
197
- # Also tone down asyncio logger noise from transport callbacks
198
- try:
199
- logging.getLogger("asyncio").setLevel(logging.WARNING)
200
- except Exception:
201
- pass
202
-
203
-
204
- asyncio_logger_suppression()
@@ -56,7 +56,7 @@
56
56
  data-bs-toggle="tooltip"
57
57
  data-bs-placement="top"
58
58
  title="ARP lookup is not supported on this system, click for more info"
59
- onclick="window.open('https://github.com/mdennis281/LANscape/blob/main/support/arp-issues.md', '_blank')"
59
+ onclick="window.open('https://github.com/mdennis281/LANscape/blob/main/docs/arp-issues.md', '_blank')"
60
60
  >
61
61
  help
62
62
  </span>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lanscape
3
- Version: 2.0.0a2
3
+ Version: 2.0.1a1
4
4
  Summary: A python based local network scanner
5
5
  Author-email: Michael Dennis <michael@dipduo.com>
6
6
  License-Expression: MIT
@@ -88,7 +88,7 @@ I use a combination of ARP, ICMP & port testing to determine if a device is onli
88
88
  Recommendations:
89
89
 
90
90
  - Adjust scan configuration
91
- - Configure ARP lookup [ARP lookup setup](./support/arp-issues.md)
91
+ - Configure ARP lookup [ARP lookup setup](./docs/arp-issues.md)
92
92
  - Create a bug
93
93
 
94
94
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lanscape"
3
- version = "2.0.0a2"
3
+ version = "2.0.1a1"
4
4
  authors = [
5
5
  { name="Michael Dennis", email="michael@dipduo.com" },
6
6
  ]
@@ -4,9 +4,10 @@ Tests REST API endpoints for port management, subnet validation, and scan operat
4
4
  """
5
5
  import json
6
6
  import time
7
- import pytest
8
7
  from unittest.mock import patch
9
8
 
9
+ import pytest
10
+
10
11
  from lanscape.ui.app import app
11
12
  from lanscape.core.net_tools import get_network_subnet
12
13
 
@@ -43,13 +44,14 @@ def test_scan_config():
43
44
  # API Port Management Tests
44
45
  ###########################
45
46
 
47
+
46
48
  def test_port_lifecycle(api_client, sample_port_list, updated_port_list):
47
49
  """
48
50
  Test the complete lifecycle of port list management through the API.
49
51
  Creates, retrieves, updates, and deletes a port list through API endpoints.
50
52
  """
51
53
  test_list_name = 'test_port_list_lifecycle'
52
-
54
+
53
55
  # Delete the new port list if it exists
54
56
  api_client.delete(f'/api/port/list/{test_list_name}')
55
57
 
@@ -94,6 +96,7 @@ def test_port_lifecycle(api_client, sample_port_list, updated_port_list):
94
96
  # API Scan Tests
95
97
  ################
96
98
 
99
+
97
100
  @pytest.mark.integration
98
101
  def test_scan(api_client, sample_port_list, test_scan_config):
99
102
  """
@@ -101,7 +104,7 @@ def test_scan(api_client, sample_port_list, test_scan_config):
101
104
  Verifies scan creation, status retrieval, and UI rendering for scan results.
102
105
  """
103
106
  test_list_name = 'test_port_list_scan'
104
-
107
+
105
108
  # Delete the new port list if it exists
106
109
  api_client.delete(f'/api/port/list/{test_list_name}')
107
110
 
@@ -136,12 +139,13 @@ def _render_scan_ui_if_available(api_client, scanid):
136
139
  """Helper function to render scan UI if the method is available."""
137
140
  try:
138
141
  # This would be the equivalent of the original _render_scan_ui method
139
- response = api_client.get(f"/scan/{scanid}")
142
+ _ = api_client.get(f"/scan/{scanid}")
140
143
  # We don't assert here since this is an optional UI test
141
144
  except Exception:
142
145
  # Silently pass if UI rendering is not available
143
146
  pass
144
147
 
148
+
145
149
  def test_subnet_detection(api_client):
146
150
  """
147
151
  Test to ensure multi-subnet detection is working
@@ -155,9 +159,10 @@ def test_subnet_detection(api_client):
155
159
  subnet: dict = subnets[0]
156
160
  assert subnet.get('address_cnt') is not None
157
161
 
158
- # Subnet Validation Tests
162
+ # Subnet Validation Tests
159
163
  ##########################
160
164
 
165
+
161
166
  @pytest.mark.parametrize("subnet,expected_count", [
162
167
  # Valid subnets
163
168
  ('10.0.0.0/24', 254),
@@ -185,10 +190,11 @@ def test_subnet_validation(api_client, subnet, expected_count):
185
190
  data: dict = json.loads(response.data)
186
191
  assert data.get('count') == expected_count
187
192
  assert data.get('msg') is not None
188
-
193
+
189
194
  if expected_count == -1:
190
195
  assert not data.get('valid')
191
196
 
197
+
192
198
  @pytest.mark.parametrize("arp_supported,expected_in,expected_not_in", [
193
199
  (False, 'POKE_THEN_ARP', 'ARP_LOOKUP'),
194
200
  (True, 'ARP_LOOKUP', None)
@@ -201,12 +207,14 @@ def test_default_scan_configs_arp_handling(api_client, arp_supported, expected_i
201
207
  assert response.status_code == 200
202
208
  configs = json.loads(response.data)
203
209
  accurate_lookup = configs['accurate']['lookup_type']
204
-
210
+
205
211
  assert expected_in in accurate_lookup
206
212
  if expected_not_in:
207
213
  assert expected_not_in not in accurate_lookup
208
214
 
209
215
  # UI Rendering Helper
216
+
217
+
210
218
  def _render_scan_ui_comprehensive(api_client, scanid):
211
219
  """Test comprehensive UI rendering for a scan."""
212
220
  uris = [
@@ -221,8 +229,9 @@ def _render_scan_ui_comprehensive(api_client, scanid):
221
229
  response = api_client.get(uri)
222
230
  assert response.status_code == 200
223
231
 
232
+
224
233
  @pytest.mark.integration
225
- @pytest.mark.slow
234
+ @pytest.mark.slow
226
235
  def test_scan_api_async(api_client, test_scan_config):
227
236
  """
228
237
  Test the full scan API lifecycle with progress monitoring
@@ -230,8 +239,8 @@ def test_scan_api_async(api_client, test_scan_config):
230
239
  # Create the port list first (since test_scan_config references it)
231
240
  sample_port_list = {'80': 'http', '443': 'https'}
232
241
  api_client.post('/api/port/list/test_port_list_scan', json=sample_port_list)
233
-
234
- # Create a new scan
242
+
243
+ # Create a new scan
235
244
  response = api_client.post('/api/scan', json=test_scan_config)
236
245
  assert response.status_code == 200
237
246
  scan_info = json.loads(response.data)
@@ -243,20 +252,20 @@ def test_scan_api_async(api_client, test_scan_config):
243
252
  percent_complete = 0
244
253
  max_iterations = 30 # Safety limit
245
254
  iteration = 0
246
-
255
+
247
256
  while percent_complete < 100 and iteration < max_iterations:
248
257
  # Get scan summary
249
258
  response = api_client.get(f'/api/scan/{scan_id}/summary')
250
259
  assert response.status_code == 200
251
260
  summary = json.loads(response.data)
252
261
  assert summary['running'] or summary['stage'] == 'complete'
253
-
262
+
254
263
  percent_complete = summary['percent_complete']
255
264
  assert 0 <= percent_complete <= 100
256
-
265
+
257
266
  # Test UI rendering during scan
258
267
  _render_scan_ui_if_available(api_client, scan_id)
259
-
268
+
260
269
  if percent_complete < 100:
261
270
  time.sleep(2)
262
271
  iteration += 1
@@ -270,5 +279,3 @@ def test_scan_api_async(api_client, test_scan_config):
270
279
  devices = summary['devices']
271
280
  assert devices['scanned'] == devices['total']
272
281
  assert devices['alive'] > 0
273
-
274
-
@@ -3,9 +3,10 @@
3
3
  import logging
4
4
  import threading
5
5
  import time
6
- import pytest
7
6
  from concurrent.futures import ThreadPoolExecutor, as_completed
8
7
 
8
+ import pytest
9
+
9
10
  from lanscape.core.decorators import run_once, job_tracker, JobStats
10
11
 
11
12
 
@@ -311,15 +312,15 @@ def test_job_tracker_multiple_different_functions():
311
312
  ])
312
313
  def test_job_tracker_parametrized_function_calls(function_name, expected_calls):
313
314
  """Test parametrized validation of function call counts."""
314
-
315
+
315
316
  @job_tracker
316
317
  def function_a():
317
318
  return "a"
318
-
319
- @job_tracker
319
+
320
+ @job_tracker
320
321
  def function_b():
321
322
  return "b"
322
-
323
+
323
324
  # Execute functions based on expected calls
324
325
  if function_name == "function_a":
325
326
  for _ in range(expected_calls):
@@ -327,6 +328,6 @@ def test_job_tracker_parametrized_function_calls(function_name, expected_calls):
327
328
  elif function_name == "function_b":
328
329
  for _ in range(expected_calls):
329
330
  function_b()
330
-
331
+
331
332
  stats = JobStats()
332
333
  assert stats.finished[function_name] == expected_calls
@@ -4,9 +4,10 @@ Verifies functionality related to version checking, resource management,
4
4
  execution environment detection, and network support features.
5
5
  """
6
6
 
7
- import pytest
8
7
  from unittest.mock import patch, MagicMock
9
8
 
9
+ import pytest
10
+
10
11
  from lanscape.core.version_manager import lookup_latest_version
11
12
  from lanscape.core.app_scope import ResourceManager, is_local_run
12
13
  from lanscape.core.net_tools import is_arp_supported
@@ -27,15 +28,14 @@ def mock_resource_manager():
27
28
  def test_version_lookup_returns_valid_version():
28
29
  """Test that the version lookup functionality returns a valid version string."""
29
30
  version = lookup_latest_version()
30
-
31
+
31
32
  assert version is not None
32
33
  assert isinstance(version, str)
33
34
  # Basic version pattern check (should contain digits and dots)
34
35
  assert any(char.isdigit() for char in version)
35
36
 
36
37
 
37
- @pytest.mark.parametrize("expected_type", [str, type(None)])
38
- def test_version_lookup_return_type(expected_type):
38
+ def test_version_lookup_return_type():
39
39
  """Test version lookup returns expected type."""
40
40
  version = lookup_latest_version()
41
41
  if version is not None:
@@ -52,7 +52,7 @@ def test_resource_manager_lists_ports():
52
52
  """Test the ResourceManager can list port resources."""
53
53
  ports = ResourceManager('ports')
54
54
  port_list = ports.list()
55
-
55
+
56
56
  assert len(port_list) > 0
57
57
  assert isinstance(port_list, list)
58
58
 
@@ -61,7 +61,7 @@ def test_resource_manager_retrieves_mac_database():
61
61
  """Test ResourceManager can retrieve MAC address database."""
62
62
  mac = ResourceManager('mac_addresses')
63
63
  mac_list = mac.get('mac_db.json')
64
-
64
+
65
65
  assert mac_list is not None
66
66
 
67
67
 
@@ -72,7 +72,7 @@ def test_resource_manager_retrieves_mac_database():
72
72
  def test_resource_manager_methods(resource_type, expected_method):
73
73
  """Test ResourceManager has required methods for different resource types."""
74
74
  manager = ResourceManager(resource_type)
75
-
75
+
76
76
  assert hasattr(manager, expected_method)
77
77
  assert callable(getattr(manager, expected_method))
78
78
 
@@ -81,7 +81,7 @@ def test_resource_manager_with_mock(mock_resource_manager):
81
81
  """Test ResourceManager behavior with mocked dependencies."""
82
82
  mock_resource_manager.list.return_value = ['port1', 'port2']
83
83
  mock_resource_manager.get.return_value = {'test': 'data'}
84
-
84
+
85
85
  # Test the mock behavior
86
86
  assert mock_resource_manager.list() == ['port1', 'port2']
87
87
  assert mock_resource_manager.get('test') == {'test': 'data'}
@@ -93,7 +93,7 @@ def test_resource_manager_with_mock(mock_resource_manager):
93
93
  def test_local_run_detection():
94
94
  """Test that the app correctly identifies it's running in a local environment."""
95
95
  result = is_local_run()
96
-
96
+
97
97
  assert isinstance(result, bool)
98
98
  assert result is True # Should be True in test environment
99
99
 
@@ -101,17 +101,17 @@ def test_local_run_detection():
101
101
  def test_arp_support_detection():
102
102
  """Test that ARP support detection returns a valid boolean value."""
103
103
  arp_supported = is_arp_supported()
104
-
104
+
105
105
  assert isinstance(arp_supported, bool)
106
106
  assert arp_supported in [True, False]
107
107
 
108
108
 
109
109
  @pytest.mark.parametrize("platform,expected", [
110
110
  ("win32", True), # Windows typically supports ARP
111
- ("linux", True), # Linux typically supports ARP
111
+ ("linux", True), # Linux typically supports ARP
112
112
  ("darwin", True), # macOS typically supports ARP
113
113
  ])
114
- def test_arp_support_by_platform(platform, expected):
114
+ def test_arp_support_by_platform(platform, expected): # pylint: disable=unused-argument
115
115
  """Test ARP support expectations by platform (informational test)."""
116
116
  with patch('sys.platform', platform):
117
117
  # This is more of an informational test about expectations
@@ -128,20 +128,20 @@ def test_environment_integration():
128
128
  """Integration test verifying all environment components work together."""
129
129
  # Test version lookup
130
130
  version = lookup_latest_version()
131
-
132
- # Test resource access
131
+
132
+ # Test resource access
133
133
  ports_rm = ResourceManager('ports')
134
- mac_rm = ResourceManager('mac_addresses')
135
-
134
+ _ = ResourceManager('mac_addresses') # MAC database is optional
135
+
136
136
  # Test environment detection
137
137
  is_local = is_local_run()
138
138
  arp_support = is_arp_supported()
139
-
139
+
140
140
  # Basic assertions that everything returns reasonable values
141
141
  if version is not None:
142
142
  assert isinstance(version, str)
143
-
143
+
144
144
  assert len(ports_rm.list()) >= 0
145
- assert mac_rm.get('mac_db.json') is not None or True # Allow flexibility
146
- assert isinstance(is_local, bool)
145
+ # MAC DB is optional, don't assert presence
146
+ assert isinstance(is_local, bool)
147
147
  assert isinstance(arp_support, bool)
@@ -56,6 +56,7 @@ def test_scan_config():
56
56
  assert cfg2.arp_config.timeout == arp_timeout
57
57
  assert cfg2.lookup_type == [ScanType.POKE_THEN_ARP]
58
58
 
59
+
59
60
  @pytest.mark.integration
60
61
  @pytest.mark.slow
61
62
  def test_scan(scan_manager):
@@ -65,7 +66,7 @@ def test_scan(scan_manager):
65
66
  """
66
67
  subnet = smart_select_primary_subnet()
67
68
  assert subnet is not None
68
-
69
+
69
70
  cfg = ScanConfig(
70
71
  subnet=right_size_subnet(subnet),
71
72
  t_multiplier=1.0,
@@ -5,12 +5,12 @@ Tests include log file creation, CLI logging settings, and runtime arguments for
5
5
 
6
6
  import logging
7
7
  import os
8
+ import shutil
8
9
  import tempfile
9
- import pytest
10
10
  from logging.handlers import RotatingFileHandler
11
11
  from unittest.mock import patch
12
12
 
13
- # Third-party imports - ensure 'click' is installed with 'pip install click'
13
+ import pytest
14
14
  import click
15
15
 
16
16
  from lanscape.core.logger import configure_logging
@@ -21,7 +21,7 @@ from lanscape.core.runtime_args import parse_args
21
21
  def logging_cleanup():
22
22
  """Clean up logging after test (manual use)."""
23
23
  yield
24
-
24
+
25
25
  # Cleanup after test
26
26
  root = logging.getLogger()
27
27
  for handler in root.handlers[:]:
@@ -39,52 +39,53 @@ def test_configure_logging_writes_file():
39
39
  root_logger = logging.getLogger()
40
40
  original_handlers = root_logger.handlers[:]
41
41
  root_logger.handlers.clear()
42
-
42
+
43
43
  tmpdir = tempfile.mkdtemp()
44
44
  try:
45
45
  logfile = os.path.join(tmpdir, 'test.log')
46
46
  configure_logging('INFO', logfile, flask_logging=True)
47
47
  logging.getLogger('test').info('hello file')
48
-
48
+
49
49
  # Flush all handlers to ensure content is written
50
50
  for handler in logging.getLogger().handlers:
51
51
  handler.flush()
52
-
52
+
53
53
  # Read the file contents
54
54
  with open(logfile, 'r', encoding='utf-8') as fh:
55
55
  contents = fh.read()
56
-
56
+
57
57
  # Clean up handlers to release file locks
58
58
  for handler in logging.getLogger().handlers[:]:
59
59
  if hasattr(handler, 'close'):
60
60
  handler.close()
61
61
  logging.getLogger().handlers.clear()
62
-
62
+
63
63
  assert 'hello file' in contents
64
-
64
+
65
65
  finally:
66
66
  # Restore original handlers
67
67
  root_logger.handlers = original_handlers
68
-
68
+
69
69
  # Clean up the temp directory
70
- import shutil
71
70
  try:
72
71
  shutil.rmtree(tmpdir)
73
72
  except PermissionError:
74
73
  # On Windows, sometimes files are still locked, ignore cleanup errors
75
74
  pass
76
- def test_configure_logging_without_file(logging_cleanup):
75
+
76
+
77
+ def test_configure_logging_without_file(logging_cleanup): # pylint: disable=unused-argument
77
78
  """Test that no file handlers are created when no log file is specified."""
78
79
  configure_logging('INFO', None, flask_logging=True)
79
80
  root_handlers = logging.getLogger().handlers
80
81
  assert all(not isinstance(h, RotatingFileHandler) for h in root_handlers)
81
82
 
82
83
 
83
- def test_disable_flask_logging_overrides_click(logging_cleanup):
84
+ def test_disable_flask_logging_overrides_click(logging_cleanup): # pylint: disable=unused-argument
84
85
  """Test that disabling Flask logging properly overrides click echo functions."""
85
86
  original_click_echo = click.echo
86
87
  original_click_secho = click.secho
87
-
88
+
88
89
  configure_logging('INFO', None, flask_logging=False)
89
90
  assert click.echo != original_click_echo
90
91
  assert click.secho != original_click_secho
@@ -3,9 +3,10 @@ Tests for port scanning functionality including the new PortScanConfig
3
3
  retry logic and timeout enforcement.
4
4
  """
5
5
 
6
- import pytest
7
- from unittest.mock import patch, MagicMock
8
6
  from time import time
7
+ from unittest.mock import patch, MagicMock
8
+
9
+ import pytest
9
10
 
10
11
  from lanscape.core.net_tools import Device
11
12
  from lanscape.core.scan_config import PortScanConfig
@@ -35,6 +36,7 @@ def retry_port_config():
35
36
  # PortScanConfig Tests
36
37
  ######################
37
38
 
39
+
38
40
  def test_port_scan_config_defaults():
39
41
  """Test PortScanConfig default values."""
40
42
  config = PortScanConfig()
@@ -80,6 +82,7 @@ def test_port_scan_config_serialization():
80
82
  # Device Port Testing
81
83
  ####################
82
84
 
85
+
83
86
  def test_device_test_port_with_default_config(test_device, default_port_config):
84
87
  """Test Device.test_port with default PortScanConfig."""
85
88
  # Test with a port that should be closed
@@ -98,6 +101,7 @@ def test_device_test_port_without_config(test_device):
98
101
  assert isinstance(result, bool)
99
102
  assert test_device.ports_scanned == initial_count + 1
100
103
 
104
+
101
105
  @patch('socket.socket')
102
106
  def test_device_test_port_with_retries(mock_socket_class, test_device):
103
107
  """Test Device.test_port retry mechanism."""
@@ -167,6 +171,7 @@ def test_device_test_port_exception_handling(mock_socket_class, test_device):
167
171
  # Timeout and Configuration Tests
168
172
  ##################################
169
173
 
174
+
170
175
  @pytest.mark.parametrize("timeout,retries,expected_enforcer_timeout", [
171
176
  (1.0, 0, 1.5), # 1.0 * (0 + 1) * 1.5 = 1.5
172
177
  (2.0, 2, 9.0), # 2.0 * (2 + 1) * 1.5 = 9.0
@@ -176,11 +181,12 @@ def test_device_test_port_exception_handling(mock_socket_class, test_device):
176
181
  def test_timeout_enforcer_calculation(timeout, retries, expected_enforcer_timeout):
177
182
  """Test that timeout enforcer uses correct formula."""
178
183
  config = PortScanConfig(timeout=timeout, retries=retries, retry_delay=0.1)
179
-
184
+
180
185
  # Formula: timeout * (retries + 1) * 1.5
181
186
  calculated_timeout = config.timeout * (config.retries + 1) * 1.5
182
187
  assert calculated_timeout == expected_enforcer_timeout
183
188
 
189
+
184
190
  def test_device_ports_scanned_counter(test_device, default_port_config):
185
191
  """Test that ports_scanned counter is properly incremented."""
186
192
  initial_count = test_device.ports_scanned
@@ -3,18 +3,17 @@ Dedicated tests for service scanning functionality.
3
3
  Tests the service_scan module including async probing, service identification,
4
4
  and configuration handling.
5
5
  """
6
- import logging
7
6
  import asyncio
8
- import pytest
9
7
  from unittest.mock import patch, AsyncMock, MagicMock
10
8
 
9
+ import pytest
10
+
11
11
  from lanscape.core.service_scan import (
12
12
  scan_service,
13
13
  get_port_probes,
14
14
  _try_probe,
15
15
  _multi_probe_generic,
16
- PRINTER_PORTS,
17
- asyncio_logger_suppression
16
+ PRINTER_PORTS
18
17
  )
19
18
  from lanscape.core.scan_config import ServiceScanConfig, ServiceScanStrategy
20
19
 
@@ -50,6 +49,7 @@ def aggressive_config():
50
49
  # Strategy and Probe Generation Tests
51
50
  ####################################
52
51
 
52
+
53
53
  def test_service_scan_strategy_enum():
54
54
  """Test ServiceScanStrategy enum values."""
55
55
  assert ServiceScanStrategy.LAZY.value == 'LAZY'
@@ -73,6 +73,7 @@ def test_get_port_probes_lazy_strategy():
73
73
  http_probes = [p for p in probes if p and b"HTTP" in p]
74
74
  assert len(http_probes) > 0
75
75
 
76
+
76
77
  @pytest.mark.parametrize("port", [22, 80, 443])
77
78
  def test_get_port_probes_basic_strategy(port):
78
79
  """Test probe generation for BASIC strategy."""
@@ -152,7 +153,7 @@ def test_concurrent_probe_limits():
152
153
  assert isinstance(result2, str)
153
154
 
154
155
 
155
- # Async Probe Tests
156
+ # Async Probe Tests
156
157
  ##################
157
158
 
158
159
  def test_try_probe_success():
@@ -211,19 +212,6 @@ def test_multi_probe_generic_no_response():
211
212
  asyncio.run(run_test())
212
213
 
213
214
 
214
- # Logger and Integration Tests
215
- #############################
216
-
217
- def test_asyncio_logger_suppression():
218
- """Test that asyncio logger suppression works."""
219
- # This should not raise any exceptions
220
- asyncio_logger_suppression()
221
-
222
- # Verify that asyncio logger level was changed
223
- asyncio_logger = logging.getLogger("asyncio")
224
- assert asyncio_logger.level >= logging.WARNING
225
-
226
-
227
215
  @pytest.mark.integration
228
216
  def test_service_scan_integration():
229
217
  """Integration test for full service scanning workflow."""
@@ -280,4 +268,3 @@ def test_probe_payload_types():
280
268
 
281
269
  assert has_none, "Should include None for banner grab"
282
270
  assert has_bytes, "Should include bytes payloads"
283
-
@@ -3,9 +3,10 @@ Unit tests for various utility modules in the LANscape project.
3
3
  Tests include IP parsing, port management, and decorator functionality.
4
4
  """
5
5
 
6
+ import ipaddress
6
7
  import time
8
+
7
9
  import pytest
8
- import ipaddress
9
10
 
10
11
  from lanscape.core.ip_parser import parse_ip_input
11
12
  from lanscape.core.errors import SubnetTooLargeError
@@ -37,7 +38,7 @@ def test_parse_cidr_specific():
37
38
  def test_parse_range_length_and_bounds():
38
39
  """Test explicit IP range parsing validates length and boundaries."""
39
40
  ips = parse_ip_input('10.0.0.1-10.0.0.3')
40
-
41
+
41
42
  assert len(ips) == 3
42
43
  assert str(ips[0]) == '10.0.0.1'
43
44
  assert str(ips[-1]) == '10.0.0.3'
@@ -53,7 +54,7 @@ def test_parse_mixed_format_comprehensive():
53
54
  """Test parsing a comprehensive mix of CIDR, range, and individual IP formats."""
54
55
  ip_input = "10.0.0.1/30, 10.0.0.10-10.0.0.12, 10.0.0.20-22, 10.0.0.50"
55
56
  result = ip_parser.parse_ip_input(ip_input)
56
-
57
+
57
58
  expected = [
58
59
  ipaddress.IPv4Address("10.0.0.1"),
59
60
  ipaddress.IPv4Address("10.0.0.2"),
@@ -65,7 +66,7 @@ def test_parse_mixed_format_comprehensive():
65
66
  ipaddress.IPv4Address("10.0.0.22"),
66
67
  ipaddress.IPv4Address("10.0.0.50"),
67
68
  ]
68
-
69
+
69
70
  assert result == expected
70
71
 
71
72
 
@@ -106,35 +107,35 @@ def test_port_manager_allows_empty_service_name(port_manager):
106
107
 
107
108
  def test_timeout_enforcer_no_raise():
108
109
  """Test timeout_enforcer with raise_on_timeout=False returns None on timeout."""
109
-
110
+
110
111
  @timeout_enforcer(0.1, raise_on_timeout=False)
111
112
  def slow_function():
112
113
  time.sleep(0.5)
113
114
  return "should_not_return"
114
-
115
+
115
116
  result = slow_function()
116
117
  assert result is None
117
118
 
118
119
 
119
120
  def test_timeout_enforcer_with_raise():
120
121
  """Test timeout_enforcer with raise_on_timeout=True raises TimeoutError."""
121
-
122
+
122
123
  @timeout_enforcer(0.1, raise_on_timeout=True)
123
124
  def slow_function():
124
125
  time.sleep(0.5)
125
126
  return "should_not_return"
126
-
127
+
127
128
  with pytest.raises(TimeoutError):
128
129
  slow_function()
129
130
 
130
131
 
131
132
  def test_timeout_enforcer_fast_function():
132
133
  """Test timeout_enforcer allows fast functions to complete normally."""
133
-
134
+
134
135
  @timeout_enforcer(1.0, raise_on_timeout=True)
135
136
  def fast_function():
136
137
  return "completed"
137
-
138
+
138
139
  result = fast_function()
139
140
  assert result == "completed"
140
141
 
@@ -145,12 +146,12 @@ def test_timeout_enforcer_fast_function():
145
146
  ])
146
147
  def test_timeout_enforcer_parametrized(timeout, raise_flag, expected_exception):
147
148
  """Test timeout_enforcer with different timeout values and raise settings."""
148
-
149
+
149
150
  @timeout_enforcer(timeout, raise_on_timeout=raise_flag)
150
151
  def slow_function():
151
152
  time.sleep(0.2)
152
153
  return "done"
153
-
154
+
154
155
  if raise_flag:
155
156
  with pytest.raises(expected_exception):
156
157
  slow_function()
File without changes
File without changes
File without changes