| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| import { async, ComponentFixture, TestBed, fakeAsync, tick, flush } from '@angular/core/testing'; |
| |
| import { AlertDetailsComponent, AlertCommentWrapper } from './alert-details.component'; |
| import { SharedModule } from 'app/shared/shared.module'; |
| import { AlertDetailsKeysPipe } from './alert-details-keys.pipe'; |
| import { AuthenticationService } from 'app/service/authentication.service'; |
| import { AlertsService } from 'app/service/alerts.service'; |
| import { UpdateService } from 'app/service/update.service'; |
| import { RouterTestingModule } from '@angular/router/testing'; |
| import { SearchService } from 'app/service/search.service'; |
| import { HttpClientTestingModule } from '@angular/common/http/testing'; |
| import { AppConfigService } from 'app/service/app-config.service'; |
| import { GlobalConfigService } from 'app/service/global-config.service'; |
| import { DataSource } from 'app/service/data-source'; |
| import { ElasticSearchLocalstorageImpl } from 'app/service/elasticsearch-localstorage-impl'; |
| import { DialogService } from 'app/service/dialog.service'; |
| import { By } from '@angular/platform-browser'; |
| import { AlertComment } from './alert-comment'; |
| import { Subject } from 'rxjs'; |
| import { ConfirmationType } from 'app/model/confirmation-type'; |
| import { CommentAddRemoveRequest } from '../../model/comment-add-remove-request'; |
| import { AlertSource } from '../../model/alert-source'; |
| import { of } from 'rxjs/index'; |
| import { Router } from '@angular/router'; |
| |
| const alertDetail = { |
| 'enrichments:geo:ip_dst_addr:locID': '5308655', |
| 'bro_timestamp': '1554222181.202211', |
| 'status_code': 404, |
| 'enrichments:geo:ip_dst_addr:location_point': '33.4589,-112.0709', |
| 'ip_dst_port': 80, |
| 'enrichments:geo:ip_dst_addr:dmaCode': '753', |
| 'adapter:geoadapter:begin:ts': '1554727717061', |
| 'enrichments:geo:ip_dst_addr:latitude': '33.4589', |
| 'parallelenricher:enrich:end:ts': '1554727717078', |
| 'uid': 'CBt5FP2TVetZJjaZbi', |
| 'resp_mime_types': ['text/html'], |
| 'trans_depth': 1, |
| 'protocol': 'http', |
| 'source:type': 'bro', |
| 'adapter:threatinteladapter:end:ts': '1554727717076', |
| 'original_string': 'HTTP | id.orig_p:49199', |
| 'ip_dst_addr': '204.152.254.221', |
| 'adapter:hostfromjsonlistadapter:end:ts': '1554727717069', |
| 'host': 'runlove.us', |
| 'adapter:geoadapter:end:ts': '1554727717069', |
| 'ip_src_addr': '192.168.138.158', |
| 'enrichments:geo:ip_dst_addr:longitude': '-112.0709', |
| 'user_agent': '', |
| 'resp_fuids': ['FeDAUx1IIW621Aw6Y8'], |
| 'timestamp': 1554222181202, |
| 'method': 'POST', |
| 'parallelenricher:enrich:begin:ts': '1554727717075', |
| 'request_body_len': 96, |
| 'enrichments:geo:ip_dst_addr:city': 'Phoenix', |
| 'enrichments:geo:ip_dst_addr:postalCode': '85004', |
| 'adapter:hostfromjsonlistadapter:begin:ts': '1554727717061', |
| 'orig_mime_types': ['text/plain'], |
| 'uri': '/wp-content/themes/twentyfifteen/img5.php?l=8r1gf1b2t1kuq42', |
| 'tags': [], |
| 'parallelenricher:splitter:begin:ts': '1554727717075', |
| 'alert_status': 'RESOLVE', |
| 'orig_fuids': ['FTTvSE5Asee5tJr99'], |
| 'ip_src_port': 49199, |
| 'parallelenricher:splitter:end:ts': '1554727717075', |
| 'adapter:threatinteladapter:begin:ts': '1554727717075', |
| 'status_msg': 'Not Found', |
| 'guid': 'fe9e058e-6d5a-4ba5-8b79-d8e6a2792931', |
| 'enrichments:geo:ip_dst_addr:country': 'US', |
| 'response_body_len': 357 |
| }; |
| |
| describe('AlertDetailsComponent', () => { |
| let component: AlertDetailsComponent; |
| let fixture: ComponentFixture<AlertDetailsComponent>; |
| let updateService: UpdateService; |
| let router: Router; |
| let alertsService: AlertsService; |
| |
| beforeEach(async(() => { |
| TestBed.configureTestingModule({ |
| imports: [ |
| SharedModule, |
| HttpClientTestingModule, |
| RouterTestingModule.withRoutes([]) |
| ], |
| declarations: [ AlertDetailsComponent, AlertDetailsKeysPipe ], |
| providers: [ |
| SearchService, |
| AuthenticationService, |
| UpdateService, |
| { provide: GlobalConfigService, useValue: { |
| get: () => { return of({})} |
| }}, |
| { |
| provide: DialogService, |
| useValue: { |
| launchDialog: () => { |
| const confirmed = new Subject<ConfirmationType>(); |
| setTimeout(() => { |
| confirmed.next(ConfirmationType.Confirmed); |
| }); |
| return confirmed; |
| } |
| } |
| }, |
| { |
| provide: AppConfigService, useValue: { |
| appConfigStatic: {}, |
| getApiRoot: () => {}, |
| } }, |
| { |
| provide: DataSource, |
| useClass: ElasticSearchLocalstorageImpl |
| }, |
| { provide: AlertsService, |
| useValue: { |
| escalate: () => {} |
| } |
| }, |
| ], |
| }) |
| .compileComponents(); |
| alertsService = TestBed.get(AlertsService); |
| })); |
| |
| beforeEach(() => { |
| fixture = TestBed.createComponent(AlertDetailsComponent); |
| component = fixture.componentInstance; |
| updateService = fixture.debugElement.injector.get(UpdateService); |
| router = TestBed.get(Router) |
| fixture.detectChanges(); |
| }); |
| |
| it('should be in a loading state if no alertSources loaded', () => { |
| component.alertSources = []; |
| fixture.detectChanges(); |
| expect(fixture.debugElement.query(By.css('[data-qe-id="preloader"]'))).toBeTruthy(); |
| }); |
| |
| it('should show details if alertSources loaded', () => { |
| component.alertSources = [alertDetail]; |
| fixture.detectChanges(); |
| expect(fixture.debugElement.query(By.css('[data-qe-id="preloader"]'))).toBeFalsy(); |
| }); |
| |
| it('should delete a comment.', fakeAsync(() => { |
| const responseMock = new AlertSource(); |
| responseMock.guid = 'guid'; |
| const removeCommentSpy = spyOn(updateService, 'removeComment').and.returnValue( |
| of(responseMock) |
| ); |
| const setAlertSpy = spyOn(component, 'setAlert'); |
| |
| expect(component).toBeTruthy(); |
| component.alertSource = new AlertSource(); |
| component.alertSource.guid = 'guid'; |
| component.alertSourceType = 'sourceType'; |
| const now = Date.now(); |
| |
| component.alertSources = [alertDetail]; |
| fixture.detectChanges(); |
| |
| component.alertCommentsWrapper = [ |
| new AlertCommentWrapper( |
| new AlertComment('lorem ipsum', 'user', now), |
| (new Date()).toString() |
| ) |
| ]; |
| const element = fixture.debugElement.query(By.css('[data-qe-id="comments"]')); |
| element.nativeElement.click(); |
| fixture.detectChanges(); |
| const deleteComment = fixture.debugElement.query(By.css('[data-qe-id="delete-comment"]')); |
| deleteComment.nativeElement.click(); |
| tick(500); |
| fixture.detectChanges(); |
| |
| const expectedCommentRequest = new CommentAddRemoveRequest(); |
| expectedCommentRequest.guid = 'guid'; |
| expectedCommentRequest.comment = 'lorem ipsum'; |
| expectedCommentRequest.username = 'user'; |
| expectedCommentRequest.sensorType = 'sourceType'; |
| expectedCommentRequest.timestamp = now; |
| |
| const expectedAlertSource = new AlertSource(); |
| expectedAlertSource.guid = 'guid'; |
| |
| expect(removeCommentSpy).toHaveBeenCalledWith(expectedCommentRequest); |
| expect(removeCommentSpy).toHaveBeenCalledTimes(1); |
| expect(setAlertSpy).toHaveBeenCalledWith(expectedAlertSource); |
| expect(setAlertSpy).toHaveBeenCalledTimes(1); |
| })); |
| |
| it('should return to alert list page when goBack() is called', () => { |
| let navigateSpy = spyOn(router, 'navigateByUrl'); |
| component.goBack(); |
| expect(navigateSpy).toHaveBeenCalledWith('/alerts-list'); |
| }); |
| |
| it('should set alert source with setAlert', () => { |
| const alertSource = { |
| name: 'test', |
| comments: ['test comment 1', 'test comment 2'], |
| metron_alert: '' |
| }; |
| component.setAlert(alertSource); |
| expect(component.alertName).toBe('test'); |
| expect(component.alertSource).toBe(alertSource); |
| |
| alertSource.metron_alert = 'test alert'; |
| component.setAlert(alertSource); |
| expect(component.alertSources).toBe(alertSource.metron_alert); |
| }); |
| |
| it('should return the correct alert state with getAlertState', () => { |
| expect(component.getAlertState('OPEN')).toBe(component.alertState.OPEN); |
| expect(component.getAlertState('ESCALATE')).toBe( |
| component.alertState.ESCALATE |
| ); |
| expect(component.getAlertState('DISMISS')).toBe( |
| component.alertState.DISMISS |
| ); |
| expect(component.getAlertState('RESOLVE')).toBe( |
| component.alertState.RESOLVE |
| ); |
| expect(component.getAlertState('NEW')).toBe(component.alertState.NEW); |
| }); |
| |
| it('should update alert state', () => { |
| spyOn(alertsService, 'escalate').and.returnValue(of({})); |
| spyOn(component, 'updateAlertState').and.callThrough(); |
| |
| component.alertSource = new AlertSource(); |
| component.processEscalate(); |
| expect(component.updateAlertState).toHaveBeenCalledWith('ESCALATE'); |
| |
| component.processOpen(); |
| expect(component.updateAlertState).toHaveBeenCalledWith('OPEN'); |
| |
| component.processNew(); |
| expect(component.updateAlertState).toHaveBeenCalledWith('NEW'); |
| |
| component.processDismiss(); |
| expect(component.updateAlertState).toHaveBeenCalledWith('DISMISS'); |
| |
| component.processResolve(); |
| expect(component.updateAlertState).toHaveBeenCalledWith('RESOLVE'); |
| }); |
| |
| it('should toggle editor with toggleNameEditor', fakeAsync(() => { |
| expect(component.showEditor).toBe(false); |
| component.alertSources = ['test1', 'test2']; |
| component.toggleNameEditor(); |
| fixture.detectChanges(); |
| |
| const focus = spyOn(component.metaAlertNameInput.nativeElement, 'focus'); |
| expect(component.showEditor).toBe(true); |
| tick(500); |
| fixture.detectChanges(); |
| expect(focus).toHaveBeenCalled(); |
| |
| component.alertSources = []; |
| component.toggleNameEditor(); |
| fixture.detectChanges(); |
| expect(component.showEditor).toBe(true); |
| flush(); |
| })); |
| |
| it('should send a request to update an alert name with saveName', () => { |
| const responseMock = new AlertSource(); |
| const patchSpy = spyOn(updateService, 'patch').and.returnValue( |
| of(responseMock) |
| ); |
| component.alertName = 'test name'; |
| component.alertId = '123'; |
| fixture.detectChanges(); |
| |
| component.saveName(); |
| expect(patchSpy).toHaveBeenCalled(); |
| }); |
| |
| it('should send a request to add a comment with onAddComment', () => { |
| const addCommentSpy = spyOn(updateService, 'addComment').and.returnValue( |
| of() |
| ); |
| component.alertSources = ['test']; |
| component.activeTab = component.tabs.COMMENTS; |
| component.alertCommentStr = 'abcd'; |
| fixture.detectChanges(); |
| |
| const addCommentBtn = fixture.debugElement.query( |
| By.css('[data-qe-id="add-comment-button"]') |
| ); |
| addCommentBtn.nativeElement.click(); |
| expect(addCommentSpy).toHaveBeenCalled(); |
| }); |
| |
| it('should update a meta alert name or escape the input based on certain keyup events', () => { |
| component.alertSources = ['test1', 'test2']; |
| component.toggleNameEditor(); |
| component.alertSource.name = 'test'; |
| fixture.detectChanges(); |
| const saveNameSpy = spyOn(component, 'saveName'); |
| const toggleSpy = spyOn(component, 'toggleNameEditor'); |
| |
| const metaAlertNameInput = fixture.debugElement.query( |
| By.css('[data-qe-id="meta-alert-name-input"') |
| ); |
| const enterEvent = new KeyboardEvent('keyup', { |
| code: 'Enter' |
| }); |
| const escEvent = new KeyboardEvent('keyup', { |
| code: 'Escape' |
| }); |
| |
| metaAlertNameInput.nativeElement.value = 'test'; |
| metaAlertNameInput.nativeElement.dispatchEvent(enterEvent); |
| expect(saveNameSpy).toHaveBeenCalled(); |
| |
| metaAlertNameInput.nativeElement.value = 'test'; |
| metaAlertNameInput.nativeElement.dispatchEvent(escEvent); |
| expect(component.alertName).toBe(component.alertSource.name); |
| expect(toggleSpy).toHaveBeenCalled(); |
| }); |
| |
| it('should restore the alert name if cancel is clicked', () => { |
| component.alertSources = ['test1', 'test2']; |
| component.toggleNameEditor(); |
| component.alertSource.name = 'test'; |
| fixture.detectChanges(); |
| const toggleSpy = spyOn(component, 'toggleNameEditor'); |
| const cancelInputBtn = fixture.debugElement.query( |
| By.css('[data-qe-id="cancel-name-input"]') |
| ); |
| cancelInputBtn.nativeElement.click(); |
| |
| expect(component.alertName).toBe(component.alertSource.name); |
| expect(toggleSpy).toHaveBeenCalled(); |
| }); |
| |
| }); |