From b7e4f6f997f0297d8eaddcabad1143933974fdd8 Mon Sep 17 00:00:00 2001 From: Khanh Nguyen Date: Tue, 10 Dec 2024 23:08:39 +0700 Subject: [PATCH] Update test UI --- .../product-detail.service.spec.ts | 83 +++++++++++++------ .../security-monitor.component.spec.ts | 58 +++++++++---- .../security-monitor.service.spec.ts | 77 +++++++++++++++++ 3 files changed, 178 insertions(+), 40 deletions(-) create mode 100644 marketplace-ui/src/app/modules/security-monitor/security-monitor.service.spec.ts diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail.service.spec.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail.service.spec.ts index aae29c669..b7f944104 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail.service.spec.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail.service.spec.ts @@ -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; + + 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'; + 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'; + + 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' }); }); -}); \ No newline at end of file +}); diff --git a/marketplace-ui/src/app/modules/security-monitor/security-monitor.component.spec.ts b/marketplace-ui/src/app/modules/security-monitor/security-monitor.component.spec.ts index 956e81cb7..e9b2b9de4 100644 --- a/marketplace-ui/src/app/modules/security-monitor/security-monitor.component.spec.ts +++ b/marketplace-ui/src/app/modules/security-monitor/security-monitor.component.spec.ts @@ -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; @@ -21,7 +22,7 @@ describe('SecurityMonitorComponent', () => { providers: [ { provide: SecurityMonitorService, useValue: spy }, { provide: TranslateService, useValue: spy } - ] + ], }).compileComponents(); securityMonitorService = TestBed.inject(SecurityMonitorService) as jasmine.SpyObj; @@ -37,7 +38,7 @@ 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'); @@ -45,14 +46,16 @@ describe('SecurityMonitorComponent', () => { 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(), }, ]; @@ -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)); @@ -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)); @@ -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']); + }); }); \ No newline at end of file diff --git a/marketplace-ui/src/app/modules/security-monitor/security-monitor.service.spec.ts b/marketplace-ui/src/app/modules/security-monitor/security-monitor.service.spec.ts new file mode 100644 index 000000000..8301aed8d --- /dev/null +++ b/marketplace-ui/src/app/modules/security-monitor/security-monitor.service.spec.ts @@ -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'; + 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'; + + 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' }); + }); +}); \ No newline at end of file