Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
78ea83b
Show loader for stopping status (#1299)
sofyakurilova May 16, 2025
e661702
Fix settings update (#1300)
sofyakurilova May 16, 2025
48ae7bd
Highlight Required if possible and Required with Non-Compliant result…
sofyakurilova May 21, 2025
3ee8f13
409766621: (feat) add clear search button (#1307)
OlgaMardvilko May 22, 2025
d40a37c
Calculate tests count for first page (#1306)
sofyakurilova May 22, 2025
c03385a
418729718: (fix) change error message view for the same port selected…
OlgaMardvilko May 22, 2025
b1ada2b
Add details feild to the test case result
hitnik May 16, 2025
26290ed
pylint
hitnik May 19, 2025
bffbcf7
fix "details" key error
hitnik May 20, 2025
2cef013
416241072: (feat) provide further context in test results (#1309)
OlgaMardvilko May 23, 2025
664af3e
413638736: (fix) change design for discard popup (#1310)
OlgaMardvilko May 26, 2025
d302f62
410565507: (fix) changes to close form when creating via actions butt…
OlgaMardvilko May 29, 2025
e322e7f
410583302: (fix) changes for prevent RA disappeared after copy (#1315)
OlgaMardvilko Jun 2, 2025
eb3bbd5
421374390: (fix) change save risk profile popup design (#1316)
OlgaMardvilko Jun 2, 2025
53acae3
As create date does not contain time, the validator change to compare…
sofyakurilova Jun 2, 2025
c8bea81
Change menu for outdated device (#1318)
sofyakurilova Jun 5, 2025
3748b78
410583302: (fix) changes for prevent profile disappeared after copy (…
OlgaMardvilko Jun 5, 2025
47a358d
Align the details by center (#1320)
sofyakurilova Jun 5, 2025
6bc00f4
421375877: (fix) remove actions menu for copy profile (#1322)
OlgaMardvilko Jun 10, 2025
f421be0
Change order of inputs (#1324)
sofyakurilova Jun 10, 2025
ac5d328
Adds new status for easier profile management (#1325)
sofyakurilova Jun 11, 2025
9cd51e4
Change condition as value can be null (#1327)
sofyakurilova Jun 12, 2025
d89d812
Clear copy is profile status is not Copy (#1328)
sofyakurilova Jun 13, 2025
44ee403
refactor
hitnik Jun 6, 2025
3eca5b9
refactor _connection_dhcp_address
hitnik Jun 9, 2025
b44d0d5
refactor _connection_mac_address
hitnik Jun 9, 2025
94627c4
refactor _connection_mac_oui
hitnik Jun 9, 2025
36dc538
fix oui not found
hitnik Jun 9, 2025
e0669b0
refactor of _connection_target_ping
hitnik Jun 9, 2025
b1d3fbc
refactor _connection_ipaddr_dhcp_failover
hitnik Jun 9, 2025
2ea0140
refactor _connection_dhcp_disconnect
hitnik Jun 10, 2025
ae7414d
refactor _connection_ipv6_slaac
hitnik Jun 11, 2025
220a691
refactor _connection_ipv6_ping
hitnik Jun 11, 2025
7b11cf3
refactor setup_single_dhcp_server
hitnik Jun 11, 2025
e699a81
refactor _change_subnet
hitnik Jun 11, 2025
0815b91
pylint
hitnik Jun 11, 2025
8fcf8ed
added time to risk profile creation date
hitnik Jun 16, 2025
47e3280
device created_at, modified_at field
hitnik Jun 17, 2025
3349719
FE: Refactor RA date format - add time (#1331)
sofyakurilova Jun 18, 2025
950bcc6
change device creation approach
hitnik Jun 20, 2025
6a25e62
add details to report json
hitnik Jun 27, 2025
5930564
split details to list
hitnik Jun 30, 2025
64017a8
refactor split test results to pages approach
hitnik Jul 1, 2025
5156b7f
Adds styles for details
sofyakurilova Jul 2, 2025
c1230a1
qualification test details
hitnik Jul 4, 2025
5e3189f
fix report variables
hitnik Jul 7, 2025
76ccebb
Do not insert copy when the same copy is already exist (#1339)
sofyakurilova Jul 17, 2025
4dff5dc
Fix css (#1341)
sofyakurilova Jul 17, 2025
4e55a04
Hide scroll for items list (#1342)
sofyakurilova Jul 21, 2025
8f4f429
fix text aligment and add title
hitnik Jul 10, 2025
4df74ee
place details under the description field
hitnik Jul 16, 2025
7d96450
Service module timeout fix (#1333)
hitnik Jul 28, 2025
62cf804
description line break
hitnik Aug 1, 2025
63f9753
Do not highlight the error row (#1352)
sofyakurilova Aug 7, 2025
1e2ec5a
Get updated tip element after timeout to prevent focus previous (unde…
sofyakurilova Aug 8, 2025
b96971e
Fix tooltips (#1355)
sofyakurilova Aug 11, 2025
845370c
fix ip.dst keyerror
hitnik Aug 14, 2025
a4690a0
error messages
hitnik Aug 14, 2025
696f2b7
fix pyshark error
hitnik Aug 6, 2025
6a7e382
fix not_valid_before depracation warning
hitnik Aug 6, 2025
242ae8b
fix tests deprecation warning
hitnik Aug 6, 2025
19bb17c
pylint
hitnik Aug 6, 2025
050317b
fix inconsistent quotas
hitnik Aug 8, 2025
fae878a
refactor https_detect
hitnik Sep 19, 2025
bc6178e
set timeout to 5 seconds
hitnik Sep 19, 2025
16a4fd5
pylint
hitnik Sep 19, 2025
a4f51ba
Fix npm audit issues (#1366)
sofyakurilova Sep 29, 2025
3195ff6
docs: fix typo DEGUG → DEBUG in log level example
hitnik Oct 1, 2025
c4bec5b
update python dependancies
hitnik Oct 1, 2025
02f238f
update grcpio
hitnik Oct 2, 2025
939c2b1
update risk profiles
hitnik Oct 2, 2025
3ce4d6f
convert details to the list of strings
hitnik Oct 6, 2025
b7df181
unit tests
hitnik Oct 6, 2025
7a9fd05
pylint
hitnik Oct 7, 2025
7e9e12b
change dns module styles
hitnik Oct 10, 2025
491b79c
unit tests
hitnik Oct 10, 2025
2f55ae7
detecting https using requests module
hitnik Oct 7, 2025
6055ce6
pylint
hitnik Oct 8, 2025
f070e88
Change the method to count test results (#1373)
sofyakurilova Oct 17, 2025
abbf25b
Highlight row when test result non compliant and required (#1374)
sofyakurilova Oct 17, 2025
d8618a6
Change regular expression to satisfy the requirements (#1376)
sofyakurilova Oct 20, 2025
6666566
Show "Welcome modal" when the version is updated (#1377)
sofyakurilova Oct 20, 2025
52a2a7e
change details display method
hitnik Oct 17, 2025
b4736ea
Bump version
sofyakurilova Oct 20, 2025
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
2 changes: 1 addition & 1 deletion docs/additional_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ set the log_level property for:
```
"test_modules":{
"connection":{
"log_level": "DEGUG"
"log_level": "DEBUG"
}
}
```
Expand Down
28 changes: 15 additions & 13 deletions framework/python/src/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,17 +599,19 @@ async def save_device(self, request: Request, response: Response):
if device is None:

# Create new device
device = Device()
device.mac_addr = device_json.get(DEVICE_MAC_ADDR_KEY).lower()
device.manufacturer = device_json.get(DEVICE_MANUFACTURER_KEY)
device.model = device_json.get(DEVICE_MODEL_KEY)
device.test_pack = device_json.get(DEVICE_TEST_PACK_KEY)
device.type = device_json.get(DEVICE_TYPE_KEY)
device.technology = device_json.get(DEVICE_TECH_KEY)
device.additional_info = device_json.get(DEVICE_ADDITIONAL_INFO_KEY)

device.device_folder = device.manufacturer + " " + device.model
device.test_modules = device_json.get(DEVICE_TEST_MODULES_KEY)
device_manufacturer = device_json.get(DEVICE_MANUFACTURER_KEY)
device_model = device_json.get(DEVICE_MODEL_KEY)
additional_info=device_json.get(DEVICE_ADDITIONAL_INFO_KEY)
device = Device(mac_addr=device_json.get(DEVICE_MAC_ADDR_KEY).lower(),
manufacturer=device_manufacturer,
model=device_model,
test_pack=device_json.get(DEVICE_TEST_PACK_KEY),
type=device_json.get(DEVICE_TYPE_KEY),
technology=device_json.get(DEVICE_TECH_KEY),
additional_info=additional_info,
device_folder=f"{device_manufacturer} {device_model}",
test_modules=device_json.get(DEVICE_TEST_MODULES_KEY)
)

self._testrun.create_device(device)
response.status_code = status.HTTP_201_CREATED
Expand Down Expand Up @@ -1103,13 +1105,13 @@ async def upload_cert(self, file: UploadFile, response: Response):
response.status_code = status.HTTP_400_BAD_REQUEST
return self._generate_msg(
False,
"Failed to upload certificate. Is it in the correct format?")
"Failed to upload certificate. The file is corrupted.")

# Return error if something went wrong
if cert_obj is None:
response.status_code = 500
return self._generate_msg(
False, "Failed to upload certificate. Is it in the correct format?")
False, "Failed to upload certificate. An error occurred.")

response.status_code = status.HTTP_201_CREATED

Expand Down
23 changes: 23 additions & 0 deletions framework/python/src/common/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ class Device():
device_folder: str = None
reports: List[TestReport] = field(default_factory=list)
max_device_reports: int = None
created_at: datetime = field(default_factory=datetime.now)
modified_at: datetime = field(default_factory=datetime.now)

# Store the original values to detect changes
_initial_values: dict = field(init=False, repr=False, default_factory=dict)

def add_report(self, report):
self.reports.append(report)
Expand Down Expand Up @@ -66,6 +71,8 @@ def to_dict(self):
device_json['technology'] = self.technology
device_json['test_pack'] = self.test_pack
device_json['additional_info'] = self.additional_info
device_json['created_at'] = self.created_at.isoformat()
device_json['modified_at'] = self.modified_at.isoformat()

if self.firmware is not None:
device_json['firmware'] = self.firmware
Expand All @@ -85,4 +92,20 @@ def to_config_json(self):
device_json['test_pack'] = self.test_pack
device_json['test_modules'] = self.test_modules
device_json['additional_info'] = self.additional_info
device_json['created_at'] = self.created_at.isoformat()
device_json['modified_at'] = self.modified_at.isoformat()

return device_json

def __post_init__(self):
# Store initial values after creation
for f in self.__dataclass_fields__:
if f not in ['created_at', 'modified_at', '_initial_values']:
self._initial_values[f] = getattr(self, f)

def __setattr__(self, name: str, value: any) -> None:
if (name not in ['created_at', 'modified_at', '_initial_values'] and
hasattr(self, name) and getattr(self, name) != value):
# Update the last_updated timestamp
super().__setattr__('modified_at', datetime.now())
super().__setattr__(name, value)
5 changes: 2 additions & 3 deletions framework/python/src/common/risk_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ def __init__(self, profile_json=None, profile_format=None):
# but still validate the profile
def load(self, profile_json, profile_format):
self.name = profile_json['name']
self.created = datetime.strptime(
profile_json['created'], '%Y-%m-%d')
self.created = datetime.fromisoformat(profile_json['created'])
self.version = profile_json['version']
self.questions = profile_json['questions']
self.status = None
Expand Down Expand Up @@ -329,7 +328,7 @@ def to_json(self, pretty=False):
json_dict = {
'name': self.name,
'version': self.version,
'created': self.created.strftime('%Y-%m-%d'),
'created': self.created.isoformat(),
'status': self.status,
'risk': self.risk,
'questions': self.questions
Expand Down
96 changes: 73 additions & 23 deletions framework/python/src/common/testreport.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from jinja2 import Environment, FileSystemLoader, BaseLoader
from collections import OrderedDict
from bs4 import BeautifulSoup
import math


DATE_TIME_FORMAT = '%Y-%m-%d %H:%M:%S'
Expand All @@ -35,6 +36,8 @@
TEMPLATES_FOLDER = 'report_templates'
TEST_REPORT_TEMPLATE = 'report_template.html'
ICON = 'icon.png'
RESULTS_SPACE_FIRST_PAGE = 440
RESULTS_SPACE = 800


LOGGER = logger.get_logger('REPORT')
Expand Down Expand Up @@ -133,12 +136,21 @@ def to_json(self):

test_results = []
for test in self._results:
details = test.details
if isinstance(details, str):
details = ' '.join(list(filter(lambda s: s != '', details.split('\n'))))
if isinstance(details, list):
details = [str(d) for d in details]
details = ' '.join(details)
else:
details = str(details)
test_dict = {
'name': test.name,
'description': test.description,
'expected_behavior': test.expected_behavior,
'required_result': test.required_result,
'result': test.result
'result': test.result,
'details': details
}

if test.recommendations is not None and len(test.recommendations) > 0:
Expand Down Expand Up @@ -217,6 +229,8 @@ def from_json(self, json_file):
if 'optional_recommendations' in test_result:
test_case.optional_recommendations = test_result[
'optional_recommendations']
if 'details' in test_result:
test_case.details = test_result['details']

self.add_test(test_case)

Expand Down Expand Up @@ -284,9 +298,15 @@ def to_html(self):

module_reports = self._module_reports
env_module = Environment(loader=BaseLoader())
pages_num = self._pages_num(json_data)
manufacturer_length = len(json_data['device']['manufacturer'])
device_name_length = len(json_data['device']['model'])
title_length = manufacturer_length + device_name_length + 1
results = json_data['tests']['results']
results_pages = self._generate_result_pages(title_length, results)

module_templates = [
env_module.from_string(s).render(
title = 'Testrun report',
name=current_test_pack.name,
device=json_data['device'],
logo=logo,
Expand All @@ -302,19 +322,31 @@ def to_html(self):
json_data=json_data,
device=json_data['device'],
modules=self._device_modules(json_data['device']),
results_pages = results_pages,
test_status=json_data['status'],
duration=duration,
successful_tests=successful_tests,
total_tests=self._total_tests,
test_results=json_data['tests']['results'],
steps_to_resolve=steps_to_resolve_,
module_reports=module_reports,
pages_num=pages_num,
tests_first_page=TESTS_FIRST_PAGE,
tests_per_page=TESTS_PER_PAGE,
module_templates=module_templates
))

def _calculate_space_first_page(self, title_length):
# Calculation of test results lines at first page
# Average chars per line is 25
estimated_lines = title_length // 25
if title_length % 25 > 0:
estimated_lines += 1
if estimated_lines > 1:
# Line height is 60 px
title_px = (estimated_lines - 1) * 60
return RESULTS_SPACE_FIRST_PAGE - title_px
else:
return RESULTS_SPACE_FIRST_PAGE

def _add_page_counter(self, html):
# Add page nums and total page
soup = BeautifulSoup(html, features='html5lib')
Expand All @@ -324,25 +356,43 @@ def _add_page_counter(self, html):
div.string = f'Page {index+1}/{total_pages}'
return str(soup)

def _pages_num(self, json_data):

# Calculate pages
test_count = len(json_data['tests']['results'])

# Multiple pages required
if test_count > TESTS_FIRST_PAGE:
# First page
pages = 1

# Remaining testsgenerate
test_count -= TESTS_FIRST_PAGE
pages += (int)(test_count / TESTS_PER_PAGE)
pages = pages + 1 if test_count % TESTS_PER_PAGE > 0 else pages

# 1 page required
else:
pages = 1

def _calc_details_height(self, text):
# Calculate a details line height
lines = math.ceil(len(text) / 45)
return (lines * 14) + 12

def _calc_text_line_height(self, text):
# Calculate result lines count
lines = math.ceil(len(text) / 52)
return 40 + 15 * (lines -1)

def _gen_result_page(self, results, space):
# Build results page content
page = []
page_space = 0
while results:
result = results[0]
line_height = self._calc_text_line_height(result['description'])
if line_height > 40:
result['height'] = line_height
if result['details']:
line_height += self._calc_details_height(result['details'])
page_space += line_height
if page_space > space:
break
else:
page.append(results.pop(0))
return page

def _generate_result_pages(self, title_length, results):
# Generating pages with tests results
pages = []
pages.append(
self._gen_result_page(results,
self._calculate_space_first_page(title_length))
)
while results:
pages.append(self._gen_result_page(results, RESULTS_SPACE))
return pages

def _device_modules(self, device):
Expand Down
8 changes: 8 additions & 0 deletions framework/python/src/core/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,14 @@ def add_test_result(self, result):
if len(result.description) != 0:
test_result.description = result.description

# Add details to test result
details = result.details
if isinstance(details, str):
details = list(filter(lambda s: s!='', details.split('\n')))
if isinstance(details, list):
details = ' '.join(details)
test_result.details = details

# Add recommendations if provided
if result.recommendations is not None:
test_result.recommendations = result.recommendations
Expand Down
5 changes: 5 additions & 0 deletions framework/python/src/test_orc/test_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ class TestCase: # pylint: disable=too-few-public-methods,too-many-instance-attr
result: str = TestResult.NON_COMPLIANT
recommendations: list = field(default_factory=lambda: [])
optional_recommendations: list = field(default_factory=lambda: [])
details: str = ""

def to_dict(self):

test_dict = {
"name": self.name,
"description": self.description,
"details": self.details,
"expected_behavior": self.expected_behavior,
"required_result": self.required_result,
"result": self.result
Expand All @@ -47,3 +49,6 @@ def to_dict(self):
test_dict["optional_recommendations"] = self.optional_recommendations

return test_dict

def __post_init__(self):
self.details = self.details.replace("\n", "", 1)
6 changes: 5 additions & 1 deletion framework/python/src/test_orc/test_orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,14 +611,18 @@ def _run_test_module(self, module):
# Convert dict from json into TestCase object
test_case = TestCase(name=test_result["name"],
result=test_result["result"],
description=test_result["description"])
description=test_result["description"]
)

# Add steps to resolve if test is non-compliant
if (test_case.result == TestResult.NON_COMPLIANT
and "recommendations" in test_result):
test_case.recommendations = test_result["recommendations"]
else:
test_case.recommendations = []
# Add details to the test case if presented
if "details" in test_result:
test_case.details = test_result["details"]

self.get_session().add_test_result(test_case)

Expand Down
2 changes: 1 addition & 1 deletion framework/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Requirements for the core module
requests==2.32.3
requests==2.32.5

# Requirements for the net_orc module
docker==7.1.0
Expand Down
4 changes: 2 additions & 2 deletions make/DEBIAN/control
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Package: Testrun
Version: 2.2.1
Version: 2.2.2
Architecture: amd64
Maintainer: Google <boddey@google.com>
Maintainer: Google <ssm-orcas@google.com>
Homepage: https://github.com/google/testrun
Bugs: https://github.com/google/testrun/issues
Description: Automatically verify IoT device network behavior
Expand Down
6 changes: 3 additions & 3 deletions modules/network/base/python/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Dependencies to user defined packages
# Package dependencies should always be defined before the user defined
# packages to prevent auto-upgrades of stable dependencies
protobuf==5.28.3
protobuf==6.32.1

# User defined packages
grpcio==1.67.1
grpcio-tools==1.67.1
grpcio==1.75.1
grpcio-tools==1.75.1
netifaces==0.11.0

2 changes: 1 addition & 1 deletion modules/network/radius/python/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ greenlet==3.0.3
six==1.16.0

# User defined packages
eventlet==0.36.1
eventlet==0.40.3
pbr==6.1.0
transitions==0.9.2
6 changes: 3 additions & 3 deletions modules/test/base/python/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Dependencies to user defined packages
# Package dependencies should always be defined before the user defined
# packages to prevent auto-upgrades of stable dependencies
protobuf==5.28.0
protobuf==6.32.1

# User defined packages
grpcio==1.67.1
grpcio-tools==1.67.1
grpcio==1.75.1
grpcio-tools==1.75.1
netifaces==0.11.0

# Requirements for reports generation
Expand Down
Loading