juice-shop
283 строки · 10.9 Кб
1/*
2* Copyright (c) 2014-2024 Bjoern Kimminich & the OWASP Juice Shop contributors.
3* SPDX-License-Identifier: MIT
4*/
5
6import { ChallengeService } from '../Services/challenge.service'
7import { SearchResultComponent } from '../search-result/search-result.component'
8import { TranslateModule, TranslateService } from '@ngx-translate/core'
9import { UserService } from '../Services/user.service'
10import { ConfigurationService } from '../Services/configuration.service'
11import { type ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'
12import { HttpClientTestingModule } from '@angular/common/http/testing'
13import { NavbarComponent } from './navbar.component'
14import { Location } from '@angular/common'
15
16import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
17import { MatSelectModule } from '@angular/material/select'
18import { MatFormFieldModule } from '@angular/material/form-field'
19import { MatIconModule } from '@angular/material/icon'
20import { MatToolbarModule } from '@angular/material/toolbar'
21import { MatButtonModule } from '@angular/material/button'
22import { AdministrationService } from '../Services/administration.service'
23import { RouterTestingModule } from '@angular/router/testing'
24import { MatMenuModule } from '@angular/material/menu'
25import { MatTooltipModule } from '@angular/material/tooltip'
26import { CookieModule, CookieService } from 'ngx-cookie'
27import { SocketIoService } from '../Services/socket-io.service'
28import { of, throwError } from 'rxjs'
29import { MatCardModule } from '@angular/material/card'
30import { MatInputModule } from '@angular/material/input'
31import { MatTableModule } from '@angular/material/table'
32import { MatPaginatorModule } from '@angular/material/paginator'
33import { MatDialogModule } from '@angular/material/dialog'
34import { MatDividerModule } from '@angular/material/divider'
35import { MatGridListModule } from '@angular/material/grid-list'
36import { NgMatSearchBarModule } from 'ng-mat-search-bar'
37import { LoginGuard } from '../app.guard'
38import { MatRadioModule } from '@angular/material/radio'
39import { MatSnackBarModule } from '@angular/material/snack-bar'
40
41class MockSocket {
42on (str: string, callback: any) {
43callback(str)
44}
45}
46
47describe('NavbarComponent', () => {
48let component: NavbarComponent
49let fixture: ComponentFixture<NavbarComponent>
50let administrationService: any
51let configurationService: any
52let userService: any
53let challengeService: any
54let translateService: any
55let cookieService: any
56let mockSocket: any
57let socketIoService: any
58let location: Location
59let loginGuard
60
61beforeEach(waitForAsync(() => {
62administrationService = jasmine.createSpyObj('AdministrationService', ['getApplicationVersion'])
63administrationService.getApplicationVersion.and.returnValue(of(undefined))
64configurationService = jasmine.createSpyObj('ConfigurationService', ['getApplicationConfiguration'])
65configurationService.getApplicationConfiguration.and.returnValue(of({}))
66userService = jasmine.createSpyObj('UserService', ['whoAmI', 'getLoggedInState', 'saveLastLoginIp'])
67userService.whoAmI.and.returnValue(of({}))
68userService.getLoggedInState.and.returnValue(of(true))
69userService.saveLastLoginIp.and.returnValue(of({}))
70userService.isLoggedIn = jasmine.createSpyObj('userService.isLoggedIn', ['next'])
71userService.isLoggedIn.next.and.returnValue({})
72challengeService = jasmine.createSpyObj('ChallengeService', ['find'])
73challengeService.find.and.returnValue(of([{ solved: false }]))
74cookieService = jasmine.createSpyObj('CookieService', ['remove', 'get', 'put'])
75mockSocket = new MockSocket()
76socketIoService = jasmine.createSpyObj('SocketIoService', ['socket'])
77socketIoService.socket.and.returnValue(mockSocket)
78loginGuard = jasmine.createSpyObj('LoginGuard', ['tokenDecode'])
79loginGuard.tokenDecode.and.returnValue(of(true))
80
81TestBed.configureTestingModule({
82declarations: [NavbarComponent, SearchResultComponent],
83imports: [
84RouterTestingModule.withRoutes([
85{ path: 'search', component: SearchResultComponent }
86]),
87HttpClientTestingModule,
88CookieModule.forRoot(),
89TranslateModule.forRoot(),
90BrowserAnimationsModule,
91MatToolbarModule,
92MatIconModule,
93MatFormFieldModule,
94MatSelectModule,
95MatButtonModule,
96MatMenuModule,
97MatTooltipModule,
98MatCardModule,
99MatInputModule,
100MatTableModule,
101MatPaginatorModule,
102MatDialogModule,
103MatDividerModule,
104MatGridListModule,
105NgMatSearchBarModule,
106MatRadioModule,
107MatSnackBarModule
108],
109providers: [
110{ provide: AdministrationService, useValue: administrationService },
111{ provide: ConfigurationService, useValue: configurationService },
112{ provide: UserService, useValue: userService },
113{ provide: ChallengeService, useValue: challengeService },
114{ provide: CookieService, useValue: cookieService },
115{ provide: SocketIoService, useValue: socketIoService },
116{ provide: LoginGuard, useValue: loginGuard },
117TranslateService
118]
119})
120.compileComponents()
121
122location = TestBed.inject(Location)
123translateService = TestBed.inject(TranslateService)
124}))
125
126beforeEach(() => {
127fixture = TestBed.createComponent(NavbarComponent)
128component = fixture.componentInstance
129localStorage.removeItem('token')
130fixture.detectChanges()
131})
132
133it('should create', () => {
134expect(component).toBeTruthy()
135})
136
137it('should hold application version', () => {
138administrationService.getApplicationVersion.and.returnValue(of('x.y.z'))
139component.ngOnInit()
140expect(component.version).toBe('vx.y.z')
141})
142
143it('should show nothing on missing application version', () => {
144administrationService.getApplicationVersion.and.returnValue(of(undefined))
145component.ngOnInit()
146expect(component.version).toBe('')
147})
148
149it('should show nothing on error retrieving application version', fakeAsync(() => {
150administrationService.getApplicationVersion.and.returnValue(throwError('Error'))
151component.ngOnInit()
152expect(component.version).toBe('')
153}))
154
155it('should log errors directly to browser console', fakeAsync(() => {
156administrationService.getApplicationVersion.and.returnValue(throwError('Error'))
157console.log = jasmine.createSpy('log')
158component.ngOnInit()
159expect(console.log).toHaveBeenCalledWith('Error')
160}))
161
162it('should use default application name if not customized', () => {
163configurationService.getApplicationConfiguration.and.returnValue(of({}))
164component.ngOnInit()
165expect(component.applicationName).toBe('OWASP Juice Shop')
166})
167
168it('should use custom application name URL if configured', () => {
169configurationService.getApplicationConfiguration.and.returnValue(of({ application: { name: 'name' } }))
170component.ngOnInit()
171expect(component.applicationName).toBe('name')
172})
173
174it('should set user email on page reload if user is authenticated', () => {
175userService.whoAmI.and.returnValue(of({ email: 'dummy@dummy.com' }))
176localStorage.setItem('token', 'token')
177component.ngOnInit()
178expect(component.userEmail).toBe('dummy@dummy.com')
179})
180
181it('should set user email on getting logged in', () => {
182localStorage.removeItem('token')
183userService.getLoggedInState.and.returnValue(of(true))
184userService.whoAmI.and.returnValue(of({ email: 'dummy@dummy.com' }))
185component.ngOnInit()
186expect(component.userEmail).toBe('dummy@dummy.com')
187})
188
189it('should log errors directly to browser console when getting user failed', fakeAsync(() => {
190userService.whoAmI.and.returnValue(throwError('Error'))
191console.log = jasmine.createSpy('log')
192component.ngOnInit()
193expect(console.log).toHaveBeenCalledWith('Error')
194}))
195
196it('should show GitHub button by default', () => {
197configurationService.getApplicationConfiguration.and.returnValue(of({}))
198component.ngOnInit()
199expect(component.showGitHubLink).toBe(true)
200})
201
202it('should hide GitHub ribbon if so configured', () => {
203configurationService.getApplicationConfiguration.and.returnValue(of({ application: { showGitHubLinks: false } }))
204component.ngOnInit()
205expect(component.showGitHubLink).toBe(false)
206})
207
208it('should log error while getting application configuration from backend API directly to browser console', fakeAsync(() => {
209configurationService.getApplicationConfiguration.and.returnValue(throwError('Error'))
210console.log = jasmine.createSpy('log')
211component.ngOnInit()
212expect(console.log).toHaveBeenCalledWith('Error')
213}))
214
215it('should hide Score Board menu item when corresponding challenge was not solved yet', () => {
216challengeService.find.and.returnValue(of([{ solved: false }]))
217component.ngOnInit()
218expect(component.scoreBoardVisible).toBeFalsy()
219})
220
221it('should show Score Board menu item if corresponding challenge has been solved', () => {
222challengeService.find.and.returnValue(of([{ solved: true }]))
223component.ngOnInit()
224expect(component.scoreBoardVisible).toBe(true)
225})
226
227it('forwards to search result with search query as URL parameter', fakeAsync(() => {
228component.search('lemon juice')
229tick()
230expect(location.path()).toBe(encodeURI('/search?q=lemon juice'))
231}))
232
233it('forwards to search result with empty search criteria if no search query is present', fakeAsync(() => {
234component.search('')
235tick()
236expect(location.path()).toBe(encodeURI('/search'))
237}))
238
239it('should remove authentication token from localStorage', () => {
240spyOn(localStorage, 'removeItem')
241component.logout()
242expect(localStorage.removeItem).toHaveBeenCalledWith('token')
243})
244
245it('should remove authentication token from cookies', () => {
246component.logout()
247expect(cookieService.remove).toHaveBeenCalledWith('token')
248})
249
250it('should remove basket id from session storage', () => {
251spyOn(sessionStorage, 'removeItem')
252component.logout()
253expect(sessionStorage.removeItem).toHaveBeenCalledWith('bid')
254})
255
256it('should remove basket item total from session storage', () => {
257spyOn(sessionStorage, 'removeItem')
258component.logout()
259expect(sessionStorage.removeItem).toHaveBeenCalledWith('itemTotal')
260})
261
262it('should set the login status to be false via UserService', () => {
263component.logout()
264expect(userService.isLoggedIn.next).toHaveBeenCalledWith(false)
265})
266
267it('should save the last login IP address', () => {
268component.logout()
269expect(userService.saveLastLoginIp).toHaveBeenCalled()
270})
271
272it('should forward to main page', fakeAsync(() => {
273component.logout()
274tick()
275expect(location.path()).toBe('/')
276}))
277
278it('should set selected a language', () => {
279spyOn(translateService, 'use').and.callFake((lang: any) => lang)
280component.changeLanguage('xx')
281expect(translateService.use).toHaveBeenCalledWith('xx')
282})
283})
284