Skip to content

Commit

Permalink
Update test UI
Browse files Browse the repository at this point in the history
  • Loading branch information
ndkhanh-axonivy committed Dec 10, 2024
1 parent 98cf424 commit b7e4f6f
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 40 deletions.
Original file line number Diff line number Diff line change
@@ -1,47 +1,80 @@
import { TestBed } from '@angular/core/testing';
import { ProductDetailService } from './product-detail.service';
import { DisplayValue } from '../../../shared/models/display-value.model';
import { HttpClient, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { environment } from '../../../../environments/environment';
import { ProductSecurityInfo } from '../../../shared/models/product-security-info-model';
import { SecurityMonitorService } from '../../security-monitor/security-monitor.service';
import { SecurityMonitorComponent } from '../../security-monitor/security-monitor.component';

describe('ProductDetailService', () => {
let service: ProductDetailService;
describe('SecurityMonitorService', () => {
let service: SecurityMonitorService;
let httpMock: HttpTestingController;
let httpClient: jasmine.SpyObj<HttpClient>;

const mockApiUrl = environment.apiUrl + '/api/security-monitor';

beforeEach(() => {
TestBed.configureTestingModule({
providers: [ProductDetailService,
imports: [SecurityMonitorComponent],
providers: [
SecurityMonitorService,
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
{ provide: HttpClient, useValue: httpClient }
]
],
});
service = TestBed.inject(ProductDetailService);

service = TestBed.inject(SecurityMonitorService);
httpMock = TestBed.inject(HttpTestingController);
});

afterEach(() => {
httpMock.verify();
});

it('should be created', () => {
expect(service).toBeTruthy();
});

it('should have a default productId signal', () => {
expect(service.productId()).toBe('');
});
it('should call API with token and return security details', () => {
const mockToken = 'valid-token';

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical test

The hard-coded value "valid-token" is used as
authorization header
.
const mockResponse: ProductSecurityInfo[] = [
{
repoName: 'repo1',
visibility: 'public',
archived: false,
dependabot: { status: 'ENABLED', alerts: {} },
codeScanning: { status: 'ENABLED', alerts: {} },
secretScanning: { status: 'ENABLED', numberOfAlerts: 0 },
branchProtectionEnabled: true,
lastCommitSHA: '12345',
lastCommitDate: new Date(),
},
];

it('should update productId signal', () => {
const newProductId = '12345';
service.productId.set(newProductId);
expect(service.productId()).toBe(newProductId);
});
service.getSecurityDetails(mockToken).subscribe((data) => {
expect(data).toEqual(mockResponse);
});

const req = httpMock.expectOne(mockApiUrl);
expect(req.request.method).toBe('GET');
expect(req.request.headers.get('Authorization')).toBe(`Bearer ${mockToken}`);

it('should have a default productNames signal', () => {
expect(service.productNames()).toEqual({} as DisplayValue);
req.flush(mockResponse);
});

it('should update productNames signal', () => {
const newProductNames: DisplayValue = { en: 'en', de: 'de' };
service.productNames.set(newProductNames);
expect(service.productNames()).toEqual(newProductNames);
it('should handle error response gracefully', () => {
const mockToken = 'invalid-token';

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical test

The hard-coded value "invalid-token" is used as
authorization header
.

service.getSecurityDetails(mockToken).subscribe({
next: () => fail('Expected an error, but received data.'),
error: (error) => {
expect(error.status).toBe(401);
},
});

const req = httpMock.expectOne(mockApiUrl);
expect(req.request.method).toBe('GET');
expect(req.request.headers.get('Authorization')).toBe(`Bearer ${mockToken}`);

req.flush({ message: 'Unauthorized' }, { status: 401, statusText: 'Unauthorized' });
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { of, throwError } from 'rxjs';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { By } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { ProductSecurityInfo } from '../../shared/models/product-security-info-model';
import { HttpErrorResponse } from '@angular/common/http';
import { TranslateService } from '@ngx-translate/core';

describe('SecurityMonitorComponent', () => {
let component: SecurityMonitorComponent;
Expand All @@ -21,7 +22,7 @@ describe('SecurityMonitorComponent', () => {
providers: [
{ provide: SecurityMonitorService, useValue: spy },
{ provide: TranslateService, useValue: spy }
]
],
}).compileComponents();

securityMonitorService = TestBed.inject(SecurityMonitorService) as jasmine.SpyObj<SecurityMonitorService>;
Expand All @@ -37,22 +38,24 @@ describe('SecurityMonitorComponent', () => {
expect(component).toBeTruthy();
});

it('should show error message when token is empty and onSubmit is called', () => {
it('should show an error message when token is empty and onSubmit is called', () => {
component.token = '';
component.onSubmit();
expect(component.errorMessage).toBe('Token is required');
});

it('should call SecurityMonitorService and display repos when token is valid and response is successful', () => {
const mockRepos: ProductSecurityInfo[] = [
{
repoName: 'repo1', visibility: 'public',
archived: false, dependabot: { status: 'ENABLED', alerts: {} },
codeScanning: { status: 'ENABLED', alerts: {} },
secretScanning: { status: 'ENABLED', numberOfAlerts: 0 },
branchProtectionEnabled: true,
lastCommitSHA: '12345',
lastCommitDate: new Date()
{
repoName: 'repo1',
visibility: 'public',
archived: false,
dependabot: { status: 'ENABLED', alerts: {} },
codeScanning: { status: 'ENABLED', alerts: {} },
secretScanning: { status: 'ENABLED', numberOfAlerts: 0 },
branchProtectionEnabled: true,
lastCommitSHA: '12345',
lastCommitDate: new Date(),
},
];

Expand All @@ -72,8 +75,8 @@ describe('SecurityMonitorComponent', () => {
expect(repoCards[0].nativeElement.querySelector('h3').textContent).toBe('repo1');
});

it('should handle error when token is invalid (401 Unauthorized)', () => {
const mockError = { status: 401 };
it('should handle 401 Unauthorized error correctly', () => {
const mockError = new HttpErrorResponse({ status: 401 });

securityMonitorService.getSecurityDetails.and.returnValue(throwError(() => mockError));

Expand All @@ -85,8 +88,8 @@ describe('SecurityMonitorComponent', () => {
expect(component.errorMessage).toBe('Unauthorized access.');
});

it('should handle generic error when fetching security data fails', () => {
const mockError = { status: 500 };
it('should handle generic error correctly', () => {
const mockError = new HttpErrorResponse({ status: 500 });

securityMonitorService.getSecurityDetails.and.returnValue(throwError(() => mockError));

Expand All @@ -97,4 +100,29 @@ describe('SecurityMonitorComponent', () => {

expect(component.errorMessage).toBe('Failed to fetch security data. Check logs for details.');
});

it('should navigate to the correct URL for a repo page', () => {
spyOn(window, 'open');
component.navigateToRepoPage('example-repo', 'secretScanning');
expect(window.open).toHaveBeenCalledWith(
'https://github.com/axonivy-market/example-repo/security/secret-scanning',
'_blank'
);

component.navigateToRepoPage('example-repo', 'lastCommit', 'abc123');
expect(window.open).toHaveBeenCalledWith(
'https://github.com/axonivy-market/example-repo/commit/abc123',
'_blank'
);
});

it('should handle empty alerts correctly in hasAlerts', () => {
expect(component.hasAlerts({})).toBeFalse();
expect(component.hasAlerts({ alert1: 1 })).toBeTrue();
});

it('should return correct alert keys from alertKeys', () => {
const alerts = { alert1: 1, alert2: 2 };
expect(component.alertKeys(alerts)).toEqual(['alert1', 'alert2']);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { TestBed } from '@angular/core/testing';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { SecurityMonitorService } from './security-monitor.service';
import { environment } from '../../../environments/environment';
import { ProductSecurityInfo } from '../../shared/models/product-security-info-model';
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';

describe('SecurityMonitorService', () => {
let service: SecurityMonitorService;
let httpMock: HttpTestingController;

const mockApiUrl = environment.apiUrl + '/api/security-monitor';

beforeEach(() => {
TestBed.configureTestingModule({
providers: [
SecurityMonitorService,
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting()
]
});
service = TestBed.inject(SecurityMonitorService);
httpMock = TestBed.inject(HttpTestingController);
});

afterEach(() => {
httpMock.verify();
});

it('should be created', () => {
expect(service).toBeTruthy();
});

it('should call API with token and return security details', () => {
const mockToken = 'valid-token';

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical test

The hard-coded value "valid-token" is used as
authorization header
.
const mockResponse: ProductSecurityInfo[] = [
{
repoName: 'repo1',
visibility: 'public',
archived: false,
dependabot: { status: 'ENABLED', alerts: {} },
codeScanning: { status: 'ENABLED', alerts: {} },
secretScanning: { status: 'ENABLED', numberOfAlerts: 0 },
branchProtectionEnabled: true,
lastCommitSHA: '12345',
lastCommitDate: new Date(),
},
];

service.getSecurityDetails(mockToken).subscribe((data) => {
expect(data).toEqual(mockResponse);
});

const req = httpMock.expectOne(mockApiUrl);
expect(req.request.method).toBe('GET');
expect(req.request.headers.get('Authorization')).toBe(`Bearer ${mockToken}`);

req.flush(mockResponse);
});

it('should handle error response gracefully', () => {
const mockToken = 'invalid-token';

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical test

The hard-coded value "invalid-token" is used as
authorization header
.

service.getSecurityDetails(mockToken).subscribe({
next: () => fail('Expected an error, but received data.'),
error: (error) => {
expect(error.status).toBe(401);
},
});

const req = httpMock.expectOne(mockApiUrl);
expect(req.request.method).toBe('GET');
expect(req.request.headers.get('Authorization')).toBe(`Bearer ${mockToken}`);

req.flush({ message: 'Unauthorized' }, { status: 401, statusText: 'Unauthorized' });
});
});

0 comments on commit b7e4f6f

Please sign in to comment.