Skip to content
Merged
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
39 changes: 3 additions & 36 deletions docs/dev/mockoon.json

Large diffs are not rendered by default.

35 changes: 32 additions & 3 deletions framework/python/src/core/testrun.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
DEVICE_TEST_PACK_KEY = 'test_pack'
DEVICE_ADDITIONAL_INFO_KEY = 'additional_info'
DEVICE_REPORT_NAME_FORMAT = '{mac_addr}_{timestamp}'
DEVICE_QUESTIONS_FILE_NAME = 'device_profile.json'

MAX_DEVICE_REPORTS_KEY = 'max_device_reports'

Expand Down Expand Up @@ -283,9 +284,37 @@ def _load_devices(self, device_dir):
device.additional_info = device_config_json.get(
DEVICE_ADDITIONAL_INFO_KEY)

if None in [device.type, device.technology, device.test_pack]:
LOGGER.warning(
'Device is outdated and requires further configuration')
format_file_path = os.path.join(self.get_root_dir(),
RESOURCE_DEVICES_DIR,
DEVICE_QUESTIONS_FILE_NAME)
with open(format_file_path, 'r', encoding='utf-8') as f:
format_data = json.load(f)

required_questions = [
item['question'] for item in format_data
if item.get('validation', {}).get('required') is True
]

current_answers = \
device.additional_info if device.additional_info else []
answered_questions = \
[entry.get('question') for entry in current_answers]

missing_answers = [q for q in required_questions if
q not in answered_questions]

if (None in [device.type, device.technology, device.test_pack] or
len(missing_answers) > 0):
if missing_answers:
LOGGER.warning(
f'Device : {device}'
)
LOGGER.warning(
f'Device is missing required additional info: {missing_answers}'
)
else:
LOGGER.warning(
'Device is outdated and requires further configuration')
device.status = 'Invalid'

if not device.get_reports():
Expand Down
2 changes: 1 addition & 1 deletion make/DEBIAN/control
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Package: Testrun
Version: 2.3.4
Version: 2.4.0-beta.3
Architecture: amd64
Maintainer: Google <ssm-orcas@google.com>
Homepage: https://github.com/google/testrun
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
import { RouterTestingModule } from '@angular/router/testing';
import { Component } from '@angular/core';
import { MOCK_PROGRESS_DATA_COMPLIANT } from '../../mocks/testrun.mock';
import { DeviceStatus } from '../../model/device';

describe('DownloadReportZipComponent', () => {
let component: DownloadReportZipComponent;
Expand Down Expand Up @@ -68,6 +69,14 @@ describe('DownloadReportZipComponent', () => {
report: 'localhost:8080',
export: 'localhost:8080',
isPilot: false,
device: {
status: DeviceStatus.VALID,
manufacturer: 'Delta',
model: '03-DIN-CPU',
mac_addr: '01:02:03:04:05:06',
firmware: '1.2.2',
},
started: '2023-06-22T09:20:00.123Z',
},
autoFocus: true,
hasBackdrop: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export class DownloadReportZipComponent
report: this.report,
export: this.export,
isPilot: this.data?.device.test_pack === TestingType.Pilot,
device: this.data?.device,
started: this.data?.started,
},
autoFocus: true,
hasBackdrop: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { FocusManagerService } from '../../services/focus-manager.service';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { RouterTestingModule } from '@angular/router/testing';
import { Component } from '@angular/core';
import { device } from '../../mocks/device.mock';

describe('DownloadZipModalComponent', () => {
// @ts-expect-error data layer should be defined
Expand Down Expand Up @@ -67,6 +68,8 @@ describe('DownloadZipModalComponent', () => {
profiles: [PROFILE_MOCK_2, PROFILE_MOCK],
report: 'localhost:8080',
export: 'localhost:8080',
device: device,
started: '2026-02-02 17:24:52',
},
},
{ provide: TestRunService, useValue: testRunServiceMock },
Expand All @@ -83,6 +86,8 @@ describe('DownloadZipModalComponent', () => {
report: 'localhost:8080',
export: 'localhost:8080',
isPilot: true,
device: device,
started: '2026-02-02 17:24:52',
},
});

Expand Down Expand Up @@ -225,6 +230,8 @@ describe('DownloadZipModalComponent', () => {
profiles: [],
report: 'localhost:8080',
export: 'localhost:8080',
device: device,
started: '2026-02-02 17:24:52',
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
import { DownloadReportComponent } from '../download-report/download-report.component';
import { Subject, takeUntil, timer } from 'rxjs';
import { FocusManagerService } from '../../services/focus-manager.service';
import { Device } from '../../model/device';

interface DialogData {
profiles: Profile[];
Expand All @@ -41,6 +42,8 @@ interface DialogData {
report: string | null;
export: string | null;
isPilot?: boolean;
device: Device;
started: string | null;
}

export enum DialogCloseAction {
Expand All @@ -56,7 +59,6 @@ export interface DialogCloseResult {

@Component({
selector: 'app-download-zip-modal',

imports: [
CommonModule,
MatDialogActions,
Expand Down Expand Up @@ -133,7 +135,8 @@ export class DownloadZipModalComponent
) {
this.testRunService.downloadZip(
this.getZipLink(this.data),
result.profile
result.profile,
this.getZipName(this.data)
);
if (this.data.isPilot) {
// @ts-expect-error data layer is not null
Expand Down Expand Up @@ -189,4 +192,8 @@ export class DownloadZipModalComponent
data.export || data.report!.replace('report', 'export')
);
}

private getZipName(data: DialogData) {
return `${data.device.manufacturer}_${data.device.model}_${data.device.firmware}_${data.started}`;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
DownloadZipModalComponent,
} from '../download-zip-modal/download-zip-modal.component';
import { FocusManagerService } from '../../services/focus-manager.service';
import { DeviceStatus } from '../../model/device';

describe('TestingCompleteComponent', () => {
let component: TestingCompleteComponent;
Expand Down Expand Up @@ -68,6 +69,14 @@ describe('TestingCompleteComponent', () => {
report: '/report/123',
export: '',
isPilot: false,
device: {
status: DeviceStatus.VALID,
manufacturer: 'Delta',
model: '03-DIN-CPU',
mac_addr: '01:02:03:04:05:06',
firmware: '1.2.2',
},
started: '2023-06-22T09:20:00.123Z',
},
autoFocus: 'first-tabbable',
ariaDescribedBy: 'testing-result-main-info',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ export class TestingCompleteComponent implements OnDestroy, OnInit {
report: this.data?.report,
export: this.data?.export,
isPilot: this.data?.device.test_pack === TestingType.Pilot,
device: this.data?.device,
started: this.data?.started,
},
autoFocus: 'first-tabbable',
ariaDescribedBy: 'testing-result-main-info',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ <h2 tabindex="-1">Welcome to Testrun!</h2>
>
to share you thoughts
</li>
<li>Risk Profile was lastly updated in 2026 V2.4.0</li>
</ul>
</section>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ type DialogData = {

@Component({
selector: 'app-consent-dialog',

imports: [
MatDialogModule,
MatButtonModule,
Expand Down
1 change: 0 additions & 1 deletion modules/ui/src/app/components/version/version.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ export const INSTALLED_VERSION = 'INSTALLED_VERSION';
declare const gtag: Function;
@Component({
selector: 'app-version',

imports: [CommonModule, MatButtonModule, MatDialogModule],
templateUrl: './version.component.html',
styleUrls: ['./version.component.scss'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<form [formGroup]="profileForm" class="profile-form">
<form
[formGroup]="profileForm"
class="profile-form"
[class.profile-form-outdated]="
selectedProfile?.status === ProfileStatus.EXPIRED
">
<div class="field-container">
<label class="field-label name-field-label" for="name-field"
>Profile name *</label
Expand Down Expand Up @@ -58,7 +63,7 @@
mat-flat-button
color="primary"
class="save-profile-button"
[disabled]="profileHasNoChanges() || !profileForm.valid"
[disabled]="isSaveDisabled"
(click)="onSaveClick(ProfileStatus.VALID)">
Save
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@
}
}

.profile-form-outdated .field-container {
opacity: 0.5;
pointer-events: none;
}

.profile-form-field ::ng-deep .mat-mdc-form-field-textarea-control {
display: inherit;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { ProfileFormComponent } from './profile-form.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {
COPY_PROFILE_MOCK,
EXPIRED_PROFILE_MOCK,
NEW_PROFILE_MOCK,
NEW_PROFILE_MOCK_DRAFT,
OUTDATED_DRAFT_PROFILE_MOCK,
Expand All @@ -35,6 +36,7 @@ import { of } from 'rxjs';
import { MatDialogRef } from '@angular/material/dialog';
import { SimpleDialogComponent } from '../../../components/simple-dialog/simple-dialog.component';
import SpyObj = jasmine.SpyObj;
import { By } from '@angular/platform-browser';

describe('ProfileFormComponent', () => {
let component: ProfileFormComponent;
Expand Down Expand Up @@ -291,6 +293,21 @@ describe('ProfileFormComponent', () => {
});
});

describe('with expired profile', () => {
beforeEach(() => {
component.selectedProfile = EXPIRED_PROFILE_MOCK;
fixture.detectChanges();
});

it('should have a form with "outdated" clsss', () => {
const form = fixture.debugElement.query(
By.css('.profile-form-outdated')
);

expect(form).toBeTruthy();
});
});

describe('with profile', () => {
beforeEach(() => {
component.selectedProfile = PROFILE_MOCK;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,22 @@ export class ProfileFormComponent implements OnInit, AfterViewInit {
}
}

get isSaveDisabled(): boolean | null {
if (!this.profileForm.valid) {
return true;
}

if (
this.profile &&
this.profile.status === ProfileStatus.DRAFT &&
this.profileHasNoChanges()
) {
return false;
}

return this.profileHasNoChanges();
}

get isDraftDisabled(): boolean | null {
return (
!this.nameControl.valid ||
Expand Down Expand Up @@ -240,12 +256,12 @@ export class ProfileFormComponent implements OnInit, AfterViewInit {
return false;
}

for (const question of profile1.questions) {
for (const question of profile2.questions) {
const answer1 = question.answer;
const answer2 = profile2.questions?.find(
const answer2 = profile1.questions?.find(
question2 => question2.question === question.question
)?.answer;
if (answer1 !== undefined && answer2 !== undefined) {
if (!this.isEmptyAnswer(answer1) && !this.isEmptyAnswer(answer2)) {
if (typeof question.answer === 'string') {
if (answer1 !== answer2) {
return false;
Expand All @@ -262,13 +278,19 @@ export class ProfileFormComponent implements OnInit, AfterViewInit {
)
return false;
}
} else {
return !!answer1 == !!answer2;
} else if (this.isEmptyAnswer(answer2) && !this.isEmptyAnswer(answer1)) {
return false;
}
}
return true;
}

private isEmptyAnswer(answer: unknown): boolean {
if (answer === undefined || answer === null || answer === '') return true;
if (Array.isArray(answer) && answer.length === 0) return true;
return false;
}

private get fieldsHasError(): boolean {
return this.profileFormat.some((field, index) => {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,14 @@
role="button"
tabindex="0"
#tooltip="matTooltip"
matTooltip="{{
profile.status === ProfileStatus.EXPIRED
? EXPIRED_TOOLTIP
: profile.status
}}"
matTooltip="{{ profile.status }}"
[attr.aria-label]="getProfileItemLabel(profile)"
(click)="profileClicked.emit(profile)"
(keydown.enter)="enterProfileItem(profile)"
(keydown.space)="enterProfileItem(profile)">
<span
class="profile-item-icon-container"
[attr.aria-label]="
profile.status === ProfileStatus.EXPIRED
? EXPIRED_TOOLTIP
: profile.status
">
[attr.aria-label]="profile.status">
@if (profile.status === ProfileStatus.VALID) {
<mat-icon class="profile-item-icon" fontSet="material-symbols-outlined">
check_circle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,7 @@ $profile-item-container-gap: 8px;
}
}

:host:has(.profile-item-container-expired) {
cursor: not-allowed;
}

.profile-item-container-expired {
pointer-events: none;
opacity: 0.5;
.profile-item-info {
.profile-item-icon,
.profile-item-name,
Expand Down
Loading