juice-shop

Форк
0
/
search-result.component.spec.ts 
352 строки · 14.9 Кб
1
/*
2
 * Copyright (c) 2014-2024 Bjoern Kimminich & the OWASP Juice Shop contributors.
3
 * SPDX-License-Identifier: MIT
4
 */
5

6
import { TranslateModule, TranslateService } from '@ngx-translate/core'
7
import { MatDividerModule } from '@angular/material/divider'
8
import { HttpClientTestingModule } from '@angular/common/http/testing'
9
import { type ComponentFixture, fakeAsync, TestBed, waitForAsync } from '@angular/core/testing'
10
import { SearchResultComponent } from './search-result.component'
11
import { ProductService } from '../Services/product.service'
12
import { RouterTestingModule } from '@angular/router/testing'
13
import { MatGridListModule } from '@angular/material/grid-list'
14
import { MatCardModule } from '@angular/material/card'
15
import { MatSnackBar } from '@angular/material/snack-bar'
16

17
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
18
import { MatTableModule } from '@angular/material/table'
19
import { MatPaginatorModule } from '@angular/material/paginator'
20
import { MatDialog, MatDialogModule } from '@angular/material/dialog'
21
import { of } from 'rxjs'
22
import { DomSanitizer } from '@angular/platform-browser'
23
import { throwError } from 'rxjs/internal/observable/throwError'
24
import { ProductDetailsComponent } from 'src/app/product-details/product-details.component'
25
import { BasketService } from '../Services/basket.service'
26
import { EventEmitter } from '@angular/core'
27
import { ActivatedRoute } from '@angular/router'
28
import { SocketIoService } from '../Services/socket-io.service'
29
import { type Product } from '../Models/product.model'
30
import { QuantityService } from '../Services/quantity.service'
31
import { DeluxeGuard } from '../app.guard'
32

33
class MockSocket {
34
  on (str: string, callback: any) {
35
    callback(str)
36
  }
37

38
  emit (a: any, b: any) {
39
    return null
40
  }
41
}
42

43
class MockActivatedRoute {
44
  snapshot = { queryParams: { q: '' } }
45

46
  setQueryParameter (arg: string) {
47
    this.snapshot.queryParams.q = arg
48
  }
49
}
50

51
describe('SearchResultComponent', () => {
52
  let component: SearchResultComponent
53
  let fixture: ComponentFixture<SearchResultComponent>
54
  let productService: any
55
  let basketService: any
56
  let translateService: any
57
  let activatedRoute: MockActivatedRoute
58
  let dialog: any
59
  let sanitizer: any
60
  let socketIoService: any
61
  let mockSocket: any
62
  let quantityService
63
  let deluxeGuard
64
  let snackBar: any
65

66
  beforeEach(waitForAsync(() => {
67
    dialog = jasmine.createSpyObj('MatDialog', ['open'])
68
    dialog.open.and.returnValue(null)
69
    quantityService = jasmine.createSpyObj('QuantityService', ['getAll'])
70
    quantityService.getAll.and.returnValue(of([]))
71
    snackBar = jasmine.createSpyObj('MatSnackBar', ['open'])
72
    productService = jasmine.createSpyObj('ProductService', ['search', 'get'])
73
    productService.search.and.returnValue(of([]))
74
    productService.get.and.returnValue(of({}))
75
    basketService = jasmine.createSpyObj('BasketService', ['find', 'get', 'put', 'save', 'updateNumberOfCartItems'])
76
    basketService.find.and.returnValue(of({ Products: [] }))
77
    basketService.get.and.returnValue(of({ quantinty: 1 }))
78
    basketService.put.and.returnValue(of({ ProductId: 1 }))
79
    basketService.save.and.returnValue(of({ ProductId: 1 }))
80
    basketService.updateNumberOfCartItems.and.returnValue(of({}))
81
    translateService = jasmine.createSpyObj('TranslateService', ['get'])
82
    translateService.get.and.returnValue(of({}))
83
    translateService.onLangChange = new EventEmitter()
84
    translateService.onTranslationChange = new EventEmitter()
85
    translateService.onDefaultLangChange = new EventEmitter()
86
    sanitizer = jasmine.createSpyObj('DomSanitizer', ['bypassSecurityTrustHtml', 'sanitize'])
87
    sanitizer.bypassSecurityTrustHtml.and.returnValue(of({}))
88
    sanitizer.sanitize.and.returnValue({})
89
    activatedRoute = new MockActivatedRoute()
90
    mockSocket = new MockSocket()
91
    socketIoService = jasmine.createSpyObj('SocketIoService', ['socket'])
92
    socketIoService.socket.and.returnValue(mockSocket)
93
    deluxeGuard = jasmine.createSpyObj('', ['isDeluxe'])
94
    deluxeGuard.isDeluxe.and.returnValue(of(false))
95

96
    TestBed.configureTestingModule({
97
      declarations: [SearchResultComponent],
98
      imports: [
99
        RouterTestingModule,
100
        HttpClientTestingModule,
101
        TranslateModule.forRoot(),
102
        BrowserAnimationsModule,
103
        MatTableModule,
104
        MatPaginatorModule,
105
        MatDialogModule,
106
        MatDividerModule,
107
        MatGridListModule,
108
        MatCardModule
109
      ],
110
      providers: [
111
        { provide: TranslateService, useValue: translateService },
112
        { provide: MatDialog, useValue: dialog },
113
        { provide: MatSnackBar, useValue: snackBar },
114
        { provide: BasketService, useValue: basketService },
115
        { provide: ProductService, useValue: productService },
116
        { provide: DomSanitizer, useValue: sanitizer },
117
        { provide: ActivatedRoute, useValue: activatedRoute },
118
        { provide: SocketIoService, useValue: socketIoService },
119
        { provide: QuantityService, useValue: quantityService },
120
        { provide: DeluxeGuard, useValue: deluxeGuard }
121
      ]
122
    })
123
      .compileComponents()
124
  }))
125

126
  beforeEach(() => {
127
    fixture = TestBed.createComponent(SearchResultComponent)
128
    component = fixture.componentInstance
129
    component.ngAfterViewInit()
130
    fixture.detectChanges()
131
  })
132

133
  it('should create', () => {
134
    expect(component).toBeTruthy()
135
  })
136

137
  it('should render product descriptions as trusted HTML', () => {
138
    productService.search.and.returnValue(of([{ description: '<script>alert("XSS")</script>' }]))
139
    component.ngAfterViewInit()
140
    fixture.detectChanges()
141
    expect(sanitizer.bypassSecurityTrustHtml).toHaveBeenCalledWith('<script>alert("XSS")</script>')
142
  })
143

144
  it('should hold no products when product search API call fails', () => {
145
    productService.search.and.returnValue(throwError('Error'))
146
    component.ngAfterViewInit()
147
    fixture.detectChanges()
148
    expect(component.tableData).toEqual([])
149
  })
150

151
  it('should log error from product search API call directly to browser console', fakeAsync(() => {
152
    productService.search.and.returnValue(throwError('Error'))
153
    console.log = jasmine.createSpy('log')
154
    component.ngAfterViewInit()
155
    fixture.detectChanges()
156
    expect(console.log).toHaveBeenCalledWith('Error')
157
  }))
158

159
  it('should hold no products when quantity getAll API call fails', () => {
160
    quantityService.getAll.and.returnValue(throwError('Error'))
161
    component.ngAfterViewInit()
162
    fixture.detectChanges()
163
    expect(component.tableData).toEqual([])
164
  })
165

166
  it('should log error from quantity getAll API call directly to browser console', fakeAsync(() => {
167
    quantityService.getAll.and.returnValue(throwError('Error'))
168
    console.log = jasmine.createSpy('log')
169
    component.ngAfterViewInit()
170
    fixture.detectChanges()
171
    expect(console.log).toHaveBeenCalledWith('Error')
172
  }))
173

174
  it('should notify socket if search query includes DOM XSS payload while filtering table', () => {
175
    activatedRoute.setQueryParameter('<iframe src="javascript:alert(`xss`)"> Payload')
176
    spyOn(mockSocket, 'emit')
177
    component.filterTable()
178
    expect(mockSocket.emit.calls.mostRecent().args[0]).toBe('verifyLocalXssChallenge')
179
    expect(mockSocket.emit.calls.mostRecent().args[1]).toBe(activatedRoute.snapshot.queryParams.q)
180
  })
181

182
  it('should trim the queryparameter while filtering the datasource', () => {
183
    activatedRoute.setQueryParameter('  Product Search   ')
184
    component.filterTable()
185
    expect(component.dataSource.filter).toEqual('product search')
186
  })
187

188
  it('should pass the search query as trusted HTML', () => {
189
    activatedRoute.setQueryParameter('<script>scripttag</script>')
190
    component.filterTable()
191
    expect(sanitizer.bypassSecurityTrustHtml).toHaveBeenCalledWith('<script>scripttag</script>')
192
  })
193

194
  it('should open a modal dialog with product details', () => {
195
    component.showDetail({ id: 42 } as Product)
196
    expect(dialog.open).toHaveBeenCalledWith(ProductDetailsComponent, {
197
      width: '500px',
198
      height: 'max-content',
199
      data: {
200
        productData: { id: 42 }
201
      }
202
    })
203
  })
204

205
  it('should add new product to basket', () => {
206
    basketService.find.and.returnValue(of({ Products: [] }))
207
    productService.search.and.returnValue(of([]))
208
    basketService.save.and.returnValue(of({ ProductId: 1 }))
209
    productService.get.and.returnValue(of({ name: 'Cherry Juice' }))
210
    sessionStorage.setItem('bid', '4711')
211
    component.addToBasket(1)
212
    expect(basketService.find).toHaveBeenCalled()
213
    expect(basketService.save).toHaveBeenCalled()
214
    expect(productService.get).toHaveBeenCalled()
215
    expect(translateService.get).toHaveBeenCalledWith('BASKET_ADD_PRODUCT', { product: 'Cherry Juice' })
216
  })
217

218
  it('should translate BASKET_ADD_PRODUCT message', () => {
219
    basketService.find.and.returnValue(of({ Products: [] }))
220
    productService.search.and.returnValue(of([]))
221
    basketService.save.and.returnValue(of({ ProductId: 1 }))
222
    productService.get.and.returnValue(of({ name: 'Cherry Juice' }))
223
    translateService.get.and.returnValue(of('Translation of BASKET_ADD_PRODUCT'))
224
    sessionStorage.setItem('bid', '4711')
225
    component.addToBasket(1)
226
    expect(basketService.find).toHaveBeenCalled()
227
    expect(basketService.save).toHaveBeenCalled()
228
    expect(productService.get).toHaveBeenCalled()
229
    expect(snackBar.open).toHaveBeenCalled()
230
  })
231

232
  it('should add similar product to basket', () => {
233
    basketService.find.and.returnValue(of({ Products: [{ id: 1 }, { id: 2, name: 'Tomato Juice', BasketItem: { id: 42 } }] }))
234
    basketService.get.and.returnValue(of({ id: 42, quantity: 5 }))
235
    basketService.put.and.returnValue(of({ ProductId: 2 }))
236
    productService.get.and.returnValue(of({ name: 'Tomato Juice' }))
237
    translateService.get.and.returnValue(of(undefined))
238
    sessionStorage.setItem('bid', '4711')
239
    component.addToBasket(2)
240
    expect(basketService.find).toHaveBeenCalled()
241
    expect(basketService.get).toHaveBeenCalled()
242
    expect(basketService.put).toHaveBeenCalled()
243
    expect(productService.get).toHaveBeenCalled()
244
    expect(translateService.get).toHaveBeenCalledWith('BASKET_ADD_SAME_PRODUCT', { product: 'Tomato Juice' })
245
  })
246

247
  it('should translate BASKET_ADD_SAME_PRODUCT message', () => {
248
    basketService.find.and.returnValue(of({ Products: [{ id: 1 }, { id: 2, name: 'Tomato Juice', BasketItem: { id: 42 } }] }))
249
    basketService.get.and.returnValue(of({ id: 42, quantity: 5 }))
250
    basketService.put.and.returnValue(of({ ProductId: 2 }))
251
    productService.get.and.returnValue(of({ name: 'Tomato Juice' }))
252
    translateService.get.and.returnValue(of('Translation of BASKET_ADD_SAME_PRODUCT'))
253
    sessionStorage.setItem('bid', '4711')
254
    component.addToBasket(2)
255
    expect(basketService.find).toHaveBeenCalled()
256
    expect(basketService.get).toHaveBeenCalled()
257
    expect(basketService.put).toHaveBeenCalled()
258
    expect(productService.get).toHaveBeenCalled()
259
  })
260

261
  it('should not add anything to basket on error retrieving basket', fakeAsync(() => {
262
    basketService.find.and.returnValue(throwError('Error'))
263
    sessionStorage.setItem('bid', '815')
264
    component.addToBasket(undefined)
265
    expect(snackBar.open).not.toHaveBeenCalled()
266
  }))
267

268
  it('should log errors retrieving basket directly to browser console', fakeAsync(() => {
269
    basketService.find.and.returnValue(throwError('Error'))
270
    sessionStorage.setItem('bid', '815')
271
    console.log = jasmine.createSpy('log')
272
    component.addToBasket(2)
273
    expect(console.log).toHaveBeenCalledWith('Error')
274
  }))
275

276
  it('should not add anything to basket on error retrieving existing basket item', fakeAsync(() => {
277
    basketService.find.and.returnValue(of({ Products: [{ id: 1 }, { id: 2, name: 'Tomato Juice', BasketItem: { id: 42 } }] }))
278
    basketService.get.and.returnValue(throwError('Error'))
279
    sessionStorage.setItem('bid', '4711')
280
    component.addToBasket(2)
281
    expect(snackBar.open).not.toHaveBeenCalled()
282
  }))
283

284
  it('should log errors retrieving basket item directly to browser console', fakeAsync(() => {
285
    basketService.find.and.returnValue(of({ Products: [{ id: 1 }, { id: 2, name: 'Tomato Juice', BasketItem: { id: 42 } }] }))
286
    basketService.get.and.returnValue(throwError('Error'))
287
    sessionStorage.setItem('bid', '4711')
288
    console.log = jasmine.createSpy('log')
289
    component.addToBasket(2)
290
    expect(console.log).toHaveBeenCalledWith('Error')
291
  }))
292

293
  it('should log errors updating basket directly to browser console', fakeAsync(() => {
294
    basketService.find.and.returnValue(of({ Products: [{ id: 1 }, { id: 2, name: 'Tomato Juice', BasketItem: { id: 42 } }] }))
295
    basketService.put.and.returnValue(throwError('Error'))
296
    sessionStorage.setItem('bid', '4711')
297
    console.log = jasmine.createSpy('log')
298
    component.addToBasket(2)
299
    expect(console.log).toHaveBeenCalledWith('Error')
300
  }))
301

302
  it('should not add anything to basket on error retrieving product associated with basket item', fakeAsync(() => {
303
    basketService.find.and.returnValue(of({ Products: [{ id: 1 }, { id: 2, name: 'Tomato Juice', BasketItem: { id: 42 } }] }))
304
    productService.get.and.returnValue(throwError('Error'))
305
    sessionStorage.setItem('bid', '4711')
306
    component.addToBasket(2)
307
    expect(snackBar.open).not.toHaveBeenCalled()
308
  }))
309

310
  it('should log errors retrieving product associated with basket item directly to browser console', fakeAsync(() => {
311
    basketService.find.and.returnValue(of({ Products: [{ id: 1 }, { id: 2, name: 'Tomato Juice', BasketItem: { id: 42 } }] }))
312
    productService.get.and.returnValue(throwError('Error'))
313
    sessionStorage.setItem('bid', '4711')
314
    console.log = jasmine.createSpy('log')
315
    component.addToBasket(2)
316
    expect(console.log).toHaveBeenCalledWith('Error')
317
  }))
318

319
  it('should not add anything on error creating new basket item', fakeAsync(() => {
320
    basketService.find.and.returnValue(of({ Products: [] }))
321
    basketService.save.and.returnValue(throwError('Error'))
322
    sessionStorage.setItem('bid', '4711')
323
    component.addToBasket(2)
324
    expect(snackBar.open).toHaveBeenCalled()
325
  }))
326

327
  it('should log errors creating new basket item directly to browser console', fakeAsync(() => {
328
    basketService.find.and.returnValue(of({ Products: [] }))
329
    basketService.save.and.returnValue(throwError('Error'))
330
    console.log = jasmine.createSpy('log')
331
    sessionStorage.setItem('bid', '4711')
332
    component.addToBasket(2)
333
    expect(snackBar.open).toHaveBeenCalled()
334
  }))
335

336
  it('should not add anything on error retrieving product after creating new basket item', fakeAsync(() => {
337
    basketService.find.and.returnValue(of({ Products: [] }))
338
    productService.get.and.returnValue(throwError('Error'))
339
    sessionStorage.setItem('bid', '4711')
340
    component.addToBasket(2)
341
    expect(snackBar.open).not.toHaveBeenCalled()
342
  }))
343

344
  it('should log errors retrieving product after creating new basket item directly to browser console', fakeAsync(() => {
345
    basketService.find.and.returnValue(of({ Products: [] }))
346
    productService.get.and.returnValue(throwError('Error'))
347
    console.log = jasmine.createSpy('log')
348
    sessionStorage.setItem('bid', '4711')
349
    component.addToBasket(2)
350
    expect(console.log).toHaveBeenCalledWith('Error')
351
  }))
352
})
353

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.