lanscape 2.0.0a1__tar.gz → 2.0.0a2__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.
Potentially problematic release.
This version of lanscape might be problematic. Click here for more details.
- {lanscape-2.0.0a1/lanscape.egg-info → lanscape-2.0.0a2}/PKG-INFO +4 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/__init__.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/__main__.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/core/app_scope.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/core/decorators.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/core/device_alive.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/core/errors.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/core/ip_parser.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/core/logger.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/core/mac_lookup.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/core/net_tools.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/core/port_manager.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/core/runtime_args.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/core/scan_config.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/core/service_scan.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/core/subnet_scan.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/core/version_manager.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/core/web_browser.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/resources/mac_addresses/convert_csv.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/resources/ports/convert_csv.py +0 -1
- lanscape-2.0.0a2/lanscape/resources/ports/test_port_list_scan.json +4 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/app.py +13 -2
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/blueprints/__init__.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/blueprints/api/__init__.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/blueprints/api/port.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/blueprints/api/scan.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/blueprints/api/tools.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/blueprints/web/__init__.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/blueprints/web/routes.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/main.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/shutdown_handler.py +0 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2/lanscape.egg-info}/PKG-INFO +4 -1
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape.egg-info/SOURCES.txt +1 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape.egg-info/requires.txt +4 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/pyproject.toml +7 -1
- lanscape-2.0.0a2/tests/test_api.py +274 -0
- lanscape-2.0.0a2/tests/test_decorators.py +332 -0
- lanscape-2.0.0a2/tests/test_env.py +147 -0
- lanscape-2.0.0a2/tests/test_library.py +107 -0
- lanscape-2.0.0a2/tests/test_logging.py +101 -0
- lanscape-2.0.0a2/tests/test_port_scan.py +271 -0
- lanscape-2.0.0a2/tests/test_service_scan.py +283 -0
- lanscape-2.0.0a2/tests/test_utils.py +159 -0
- lanscape-2.0.0a1/tests/test_api.py +0 -244
- lanscape-2.0.0a1/tests/test_decorators.py +0 -283
- lanscape-2.0.0a1/tests/test_env.py +0 -46
- lanscape-2.0.0a1/tests/test_library.py +0 -105
- lanscape-2.0.0a1/tests/test_logging.py +0 -79
- lanscape-2.0.0a1/tests/test_port_scan.py +0 -251
- lanscape-2.0.0a1/tests/test_service_scan.py +0 -253
- lanscape-2.0.0a1/tests/test_utils.py +0 -110
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/LICENSE +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/MANIFEST.in +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/README.md +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/core/__init__.py +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/resources/mac_addresses/mac_db.json +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/resources/ports/full.json +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/resources/ports/large.json +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/resources/ports/medium.json +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/resources/ports/small.json +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/resources/services/definitions.jsonc +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/__init__.py +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/static/css/style.css +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/static/img/ico/android-chrome-192x192.png +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/static/img/ico/android-chrome-512x512.png +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/static/img/ico/apple-touch-icon.png +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/static/img/ico/favicon-16x16.png +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/static/img/ico/favicon-32x32.png +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/static/img/ico/favicon.ico +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/static/img/ico/site.webmanifest +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/static/js/core.js +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/static/js/layout-sizing.js +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/static/js/main.js +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/static/js/on-tab-close.js +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/static/js/quietReload.js +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/static/js/scan-config.js +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/static/js/shutdown-server.js +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/static/js/subnet-info.js +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/static/js/subnet-selector.js +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/static/lanscape.webmanifest +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/templates/base.html +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/templates/core/head.html +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/templates/core/scripts.html +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/templates/error.html +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/templates/info.html +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/templates/main.html +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/templates/scan/config.html +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/templates/scan/device-detail.html +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/templates/scan/export.html +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/templates/scan/ip-table-row.html +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/templates/scan/ip-table.html +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/templates/scan/overview.html +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/templates/scan/scan-error.html +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/templates/scan.html +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape/ui/templates/shutdown.html +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape.egg-info/dependency_links.txt +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape.egg-info/entry_points.txt +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/lanscape.egg-info/top_level.txt +0 -0
- {lanscape-2.0.0a1 → lanscape-2.0.0a2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lanscape
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.0a2
|
|
4
4
|
Summary: A python based local network scanner
|
|
5
5
|
Author-email: Michael Dennis <michael@dipduo.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -25,6 +25,9 @@ Requires-Dist: scapy<3.0,>=2.3.2
|
|
|
25
25
|
Requires-Dist: tabulate==0.9.0
|
|
26
26
|
Requires-Dist: pydantic
|
|
27
27
|
Requires-Dist: icmplib
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
28
31
|
Dynamic: license-file
|
|
29
32
|
|
|
30
33
|
# LANscape
|
|
@@ -51,6 +51,18 @@ app.jinja_env.filters['is_substring_in_values'] = is_substring_in_values
|
|
|
51
51
|
################################
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
def get_runtime_args_safe():
|
|
55
|
+
"""
|
|
56
|
+
Safely get runtime args, returning empty dict if parsing fails.
|
|
57
|
+
This prevents conflicts when the module is imported during testing.
|
|
58
|
+
"""
|
|
59
|
+
try:
|
|
60
|
+
return vars(parse_args())
|
|
61
|
+
except SystemExit:
|
|
62
|
+
# This happens when pytest tries to import the module
|
|
63
|
+
return {}
|
|
64
|
+
|
|
65
|
+
|
|
54
66
|
def set_global_safe(key: str, value):
|
|
55
67
|
""" Safely set global vars without worrying about an exception """
|
|
56
68
|
app_globals = app.jinja_env.globals
|
|
@@ -73,7 +85,7 @@ def set_global_safe(key: str, value):
|
|
|
73
85
|
set_global_safe('app_version', get_installed_version)
|
|
74
86
|
set_global_safe('update_available', is_update_available)
|
|
75
87
|
set_global_safe('latest_version', lookup_latest_version)
|
|
76
|
-
set_global_safe('runtime_args',
|
|
88
|
+
set_global_safe('runtime_args', get_runtime_args_safe)
|
|
77
89
|
set_global_safe('is_local', is_local_run)
|
|
78
90
|
set_global_safe('is_arp_supported', is_arp_supported)
|
|
79
91
|
|
|
@@ -122,4 +134,3 @@ def start_webserver(args: RuntimeArgs) -> int:
|
|
|
122
134
|
'use_reloader': args.reloader
|
|
123
135
|
}
|
|
124
136
|
app.run(**run_args)
|
|
125
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lanscape
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.0a2
|
|
4
4
|
Summary: A python based local network scanner
|
|
5
5
|
Author-email: Michael Dennis <michael@dipduo.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -25,6 +25,9 @@ Requires-Dist: scapy<3.0,>=2.3.2
|
|
|
25
25
|
Requires-Dist: tabulate==0.9.0
|
|
26
26
|
Requires-Dist: pydantic
|
|
27
27
|
Requires-Dist: icmplib
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
28
31
|
Dynamic: license-file
|
|
29
32
|
|
|
30
33
|
# LANscape
|
|
@@ -33,6 +33,7 @@ lanscape/resources/ports/full.json
|
|
|
33
33
|
lanscape/resources/ports/large.json
|
|
34
34
|
lanscape/resources/ports/medium.json
|
|
35
35
|
lanscape/resources/ports/small.json
|
|
36
|
+
lanscape/resources/ports/test_port_list_scan.json
|
|
36
37
|
lanscape/resources/services/definitions.jsonc
|
|
37
38
|
lanscape/ui/__init__.py
|
|
38
39
|
lanscape/ui/app.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "lanscape"
|
|
3
|
-
version = "2.0.
|
|
3
|
+
version = "2.0.0a2"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name="Michael Dennis", email="michael@dipduo.com" },
|
|
6
6
|
]
|
|
@@ -29,6 +29,12 @@ dependencies = [
|
|
|
29
29
|
"pydantic",
|
|
30
30
|
"icmplib"
|
|
31
31
|
]
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
dev = [
|
|
35
|
+
"pytest>=8.0",
|
|
36
|
+
"pytest-cov>=5.0"
|
|
37
|
+
]
|
|
32
38
|
[project.urls]
|
|
33
39
|
Homepage = "https://github.com/mdennis281/py-lanscape"
|
|
34
40
|
Issues = "https://github.com/mdennis281/py-lanscape/issues"
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"""
|
|
2
|
+
API integration tests for the LANscape application.
|
|
3
|
+
Tests REST API endpoints for port management, subnet validation, and scan operations.
|
|
4
|
+
"""
|
|
5
|
+
import json
|
|
6
|
+
import time
|
|
7
|
+
import pytest
|
|
8
|
+
from unittest.mock import patch
|
|
9
|
+
|
|
10
|
+
from lanscape.ui.app import app
|
|
11
|
+
from lanscape.core.net_tools import get_network_subnet
|
|
12
|
+
|
|
13
|
+
from tests._helpers import right_size_subnet
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture
|
|
17
|
+
def api_client():
|
|
18
|
+
"""Create a test client for the Flask application."""
|
|
19
|
+
return app.test_client()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
def sample_port_list():
|
|
24
|
+
"""Create a sample port list for testing."""
|
|
25
|
+
return {'80': 'http', '443': 'https'}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.fixture
|
|
29
|
+
def updated_port_list():
|
|
30
|
+
"""Create an updated port list for testing."""
|
|
31
|
+
return {'22': 'ssh', '8080': 'http-alt'}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.fixture
|
|
35
|
+
def test_scan_config():
|
|
36
|
+
"""Create a test scan configuration."""
|
|
37
|
+
return {
|
|
38
|
+
'subnet': right_size_subnet(get_network_subnet()),
|
|
39
|
+
'port_list': 'test_port_list_scan',
|
|
40
|
+
'lookup_type': ['POKE_THEN_ARP']
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# API Port Management Tests
|
|
44
|
+
###########################
|
|
45
|
+
|
|
46
|
+
def test_port_lifecycle(api_client, sample_port_list, updated_port_list):
|
|
47
|
+
"""
|
|
48
|
+
Test the complete lifecycle of port list management through the API.
|
|
49
|
+
Creates, retrieves, updates, and deletes a port list through API endpoints.
|
|
50
|
+
"""
|
|
51
|
+
test_list_name = 'test_port_list_lifecycle'
|
|
52
|
+
|
|
53
|
+
# Delete the new port list if it exists
|
|
54
|
+
api_client.delete(f'/api/port/list/{test_list_name}')
|
|
55
|
+
|
|
56
|
+
# Get the list of port lists
|
|
57
|
+
response = api_client.get('/api/port/list')
|
|
58
|
+
assert response.status_code == 200
|
|
59
|
+
port_list_start = json.loads(response.data)
|
|
60
|
+
|
|
61
|
+
# Create a new port list
|
|
62
|
+
response = api_client.post(f'/api/port/list/{test_list_name}', json=sample_port_list)
|
|
63
|
+
assert response.status_code == 200
|
|
64
|
+
|
|
65
|
+
# Get the list of port lists again
|
|
66
|
+
response = api_client.get('/api/port/list')
|
|
67
|
+
assert response.status_code == 200
|
|
68
|
+
port_list_new = json.loads(response.data)
|
|
69
|
+
# Verify that the new port list is in the list of port lists
|
|
70
|
+
assert len(port_list_new) == len(port_list_start) + 1
|
|
71
|
+
|
|
72
|
+
# Get the new port list
|
|
73
|
+
response = api_client.get(f'/api/port/list/{test_list_name}')
|
|
74
|
+
assert response.status_code == 200
|
|
75
|
+
port_list = json.loads(response.data)
|
|
76
|
+
assert port_list == sample_port_list
|
|
77
|
+
|
|
78
|
+
# Update the new port list
|
|
79
|
+
response = api_client.put(f'/api/port/list/{test_list_name}', json=updated_port_list)
|
|
80
|
+
assert response.status_code == 200
|
|
81
|
+
|
|
82
|
+
# Get the new port list again
|
|
83
|
+
response = api_client.get(f'/api/port/list/{test_list_name}')
|
|
84
|
+
assert response.status_code == 200
|
|
85
|
+
port_list = json.loads(response.data)
|
|
86
|
+
|
|
87
|
+
# Verify that the new port list has been updated
|
|
88
|
+
assert port_list == updated_port_list
|
|
89
|
+
|
|
90
|
+
# Delete the new port list
|
|
91
|
+
response = api_client.delete(f'/api/port/list/{test_list_name}')
|
|
92
|
+
assert response.status_code == 200
|
|
93
|
+
|
|
94
|
+
# API Scan Tests
|
|
95
|
+
################
|
|
96
|
+
|
|
97
|
+
@pytest.mark.integration
|
|
98
|
+
def test_scan(api_client, sample_port_list, test_scan_config):
|
|
99
|
+
"""
|
|
100
|
+
Test the scan API functionality by creating and monitoring a network scan.
|
|
101
|
+
Verifies scan creation, status retrieval, and UI rendering for scan results.
|
|
102
|
+
"""
|
|
103
|
+
test_list_name = 'test_port_list_scan'
|
|
104
|
+
|
|
105
|
+
# Delete the new port list if it exists
|
|
106
|
+
api_client.delete(f'/api/port/list/{test_list_name}')
|
|
107
|
+
|
|
108
|
+
# Create a new port list
|
|
109
|
+
response = api_client.post(f'/api/port/list/{test_list_name}', json=sample_port_list)
|
|
110
|
+
assert response.status_code == 200
|
|
111
|
+
|
|
112
|
+
# Create a new scan, wait for completion
|
|
113
|
+
response = api_client.post('/api/scan/async', json=test_scan_config)
|
|
114
|
+
assert response.status_code == 200
|
|
115
|
+
scan_info = json.loads(response.data)
|
|
116
|
+
assert scan_info['status'] == 'complete'
|
|
117
|
+
scanid = scan_info['scan_id']
|
|
118
|
+
assert scanid is not None
|
|
119
|
+
|
|
120
|
+
# Validate the scan worked without error
|
|
121
|
+
response = api_client.get(f"/api/scan/{scanid}")
|
|
122
|
+
assert response.status_code == 200
|
|
123
|
+
scan_data = json.loads(response.data)
|
|
124
|
+
assert scan_data['errors'] == []
|
|
125
|
+
assert scan_data['stage'] == 'complete'
|
|
126
|
+
|
|
127
|
+
# Test scan UI rendering (if method exists)
|
|
128
|
+
_render_scan_ui_if_available(api_client, scanid)
|
|
129
|
+
|
|
130
|
+
# Delete the new port list
|
|
131
|
+
response = api_client.delete(f'/api/port/list/{test_list_name}')
|
|
132
|
+
assert response.status_code == 200
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _render_scan_ui_if_available(api_client, scanid):
|
|
136
|
+
"""Helper function to render scan UI if the method is available."""
|
|
137
|
+
try:
|
|
138
|
+
# This would be the equivalent of the original _render_scan_ui method
|
|
139
|
+
response = api_client.get(f"/scan/{scanid}")
|
|
140
|
+
# We don't assert here since this is an optional UI test
|
|
141
|
+
except Exception:
|
|
142
|
+
# Silently pass if UI rendering is not available
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
def test_subnet_detection(api_client):
|
|
146
|
+
"""
|
|
147
|
+
Test to ensure multi-subnet detection is working
|
|
148
|
+
"""
|
|
149
|
+
response = api_client.get('/api/tools/subnet/list')
|
|
150
|
+
assert response.status_code == 200
|
|
151
|
+
|
|
152
|
+
subnets = json.loads(response.data)
|
|
153
|
+
assert len(subnets) != 0
|
|
154
|
+
assert isinstance(subnets[0], dict)
|
|
155
|
+
subnet: dict = subnets[0]
|
|
156
|
+
assert subnet.get('address_cnt') is not None
|
|
157
|
+
|
|
158
|
+
# Subnet Validation Tests
|
|
159
|
+
##########################
|
|
160
|
+
|
|
161
|
+
@pytest.mark.parametrize("subnet,expected_count", [
|
|
162
|
+
# Valid subnets
|
|
163
|
+
('10.0.0.0/24', 254),
|
|
164
|
+
('10.0.0.2/24', 254),
|
|
165
|
+
('10.0.0.1-100', 100),
|
|
166
|
+
('192.168.1.1/25', 126),
|
|
167
|
+
('10.0.0.1/24, 192.168.1.1-100', 354),
|
|
168
|
+
('10.0.0.1/20', 4094),
|
|
169
|
+
('10.0.0.1/19', 8190),
|
|
170
|
+
('10.0.0.1/19, 192.168.1.1/20', 12284),
|
|
171
|
+
('10.0.0.1/17, 192.168.0.1/16', 98300),
|
|
172
|
+
('10.0.0.1/20, 192.168.0.1/20, 10.100.0.1/20', 12282),
|
|
173
|
+
# Invalid subnets
|
|
174
|
+
('', -1), # blank
|
|
175
|
+
('10.0.1/24', -1), # invalid
|
|
176
|
+
('10.0.0.1/2', -1), # too big
|
|
177
|
+
('10.0.0.1/17, 192.168.0.1/16, 10.100.0.1/20', -1), # combined too big
|
|
178
|
+
])
|
|
179
|
+
def test_subnet_validation(api_client, subnet, expected_count):
|
|
180
|
+
"""Test subnet validation and parsing works as expected."""
|
|
181
|
+
uri = f'/api/tools/subnet/test?subnet={subnet}'
|
|
182
|
+
response = api_client.get(uri)
|
|
183
|
+
assert response.status_code == 200
|
|
184
|
+
|
|
185
|
+
data: dict = json.loads(response.data)
|
|
186
|
+
assert data.get('count') == expected_count
|
|
187
|
+
assert data.get('msg') is not None
|
|
188
|
+
|
|
189
|
+
if expected_count == -1:
|
|
190
|
+
assert not data.get('valid')
|
|
191
|
+
|
|
192
|
+
@pytest.mark.parametrize("arp_supported,expected_in,expected_not_in", [
|
|
193
|
+
(False, 'POKE_THEN_ARP', 'ARP_LOOKUP'),
|
|
194
|
+
(True, 'ARP_LOOKUP', None)
|
|
195
|
+
])
|
|
196
|
+
def test_default_scan_configs_arp_handling(api_client, arp_supported, expected_in, expected_not_in):
|
|
197
|
+
"""Test ARP lookup configuration based on system support."""
|
|
198
|
+
with patch('lanscape.ui.blueprints.api.tools.is_arp_supported', return_value=arp_supported):
|
|
199
|
+
response = api_client.get('/api/tools/config/defaults')
|
|
200
|
+
|
|
201
|
+
assert response.status_code == 200
|
|
202
|
+
configs = json.loads(response.data)
|
|
203
|
+
accurate_lookup = configs['accurate']['lookup_type']
|
|
204
|
+
|
|
205
|
+
assert expected_in in accurate_lookup
|
|
206
|
+
if expected_not_in:
|
|
207
|
+
assert expected_not_in not in accurate_lookup
|
|
208
|
+
|
|
209
|
+
# UI Rendering Helper
|
|
210
|
+
def _render_scan_ui_comprehensive(api_client, scanid):
|
|
211
|
+
"""Test comprehensive UI rendering for a scan."""
|
|
212
|
+
uris = [
|
|
213
|
+
'/info',
|
|
214
|
+
f'/?scan_id={scanid}',
|
|
215
|
+
f'/scan/{scanid}/overview',
|
|
216
|
+
f'/scan/{scanid}/table',
|
|
217
|
+
f'/scan/{scanid}/table?filter=test',
|
|
218
|
+
f'/export/{scanid}'
|
|
219
|
+
]
|
|
220
|
+
for uri in uris:
|
|
221
|
+
response = api_client.get(uri)
|
|
222
|
+
assert response.status_code == 200
|
|
223
|
+
|
|
224
|
+
@pytest.mark.integration
|
|
225
|
+
@pytest.mark.slow
|
|
226
|
+
def test_scan_api_async(api_client, test_scan_config):
|
|
227
|
+
"""
|
|
228
|
+
Test the full scan API lifecycle with progress monitoring
|
|
229
|
+
"""
|
|
230
|
+
# Create the port list first (since test_scan_config references it)
|
|
231
|
+
sample_port_list = {'80': 'http', '443': 'https'}
|
|
232
|
+
api_client.post('/api/port/list/test_port_list_scan', json=sample_port_list)
|
|
233
|
+
|
|
234
|
+
# Create a new scan
|
|
235
|
+
response = api_client.post('/api/scan', json=test_scan_config)
|
|
236
|
+
assert response.status_code == 200
|
|
237
|
+
scan_info = json.loads(response.data)
|
|
238
|
+
assert scan_info['status'] == 'running'
|
|
239
|
+
scan_id = scan_info['scan_id']
|
|
240
|
+
assert scan_id is not None
|
|
241
|
+
|
|
242
|
+
# Monitor scan progress
|
|
243
|
+
percent_complete = 0
|
|
244
|
+
max_iterations = 30 # Safety limit
|
|
245
|
+
iteration = 0
|
|
246
|
+
|
|
247
|
+
while percent_complete < 100 and iteration < max_iterations:
|
|
248
|
+
# Get scan summary
|
|
249
|
+
response = api_client.get(f'/api/scan/{scan_id}/summary')
|
|
250
|
+
assert response.status_code == 200
|
|
251
|
+
summary = json.loads(response.data)
|
|
252
|
+
assert summary['running'] or summary['stage'] == 'complete'
|
|
253
|
+
|
|
254
|
+
percent_complete = summary['percent_complete']
|
|
255
|
+
assert 0 <= percent_complete <= 100
|
|
256
|
+
|
|
257
|
+
# Test UI rendering during scan
|
|
258
|
+
_render_scan_ui_if_available(api_client, scan_id)
|
|
259
|
+
|
|
260
|
+
if percent_complete < 100:
|
|
261
|
+
time.sleep(2)
|
|
262
|
+
iteration += 1
|
|
263
|
+
|
|
264
|
+
# Verify final scan state
|
|
265
|
+
assert not summary['running']
|
|
266
|
+
assert summary['stage'] == 'complete'
|
|
267
|
+
assert summary['runtime'] > 0
|
|
268
|
+
|
|
269
|
+
# Validate device counts
|
|
270
|
+
devices = summary['devices']
|
|
271
|
+
assert devices['scanned'] == devices['total']
|
|
272
|
+
assert devices['alive'] > 0
|
|
273
|
+
|
|
274
|
+
|