1
+ import { mount } from '@vue/test-utils' ;
2
+ import { createStore } from 'vuex' ;
3
+ import ImageDetails from '../ImageDetails.vue' ;
4
+
5
+ // Define constants locally to avoid import issues
6
+ const RESOURCE = {
7
+ IMAGE : "sbombastic.rancher.io.image"
8
+ } ;
9
+
10
+ describe ( 'ImageDetails' , ( ) => {
11
+ let store : any ;
12
+ let wrapper : any ;
13
+
14
+ const mockImageData = {
15
+ metadata : { name : 'test-image' } ,
16
+ spec : {
17
+ repository : 'test-repo' ,
18
+ registry : 'test-registry' ,
19
+ scanResult : {
20
+ critical : 5 ,
21
+ high : 10 ,
22
+ medium : 15 ,
23
+ low : 8 ,
24
+ none : 2
25
+ }
26
+ }
27
+ } ;
28
+
29
+ beforeEach ( ( ) => {
30
+ store = createStore ( {
31
+ modules : {
32
+ cluster : {
33
+ namespaced : true ,
34
+ getters : {
35
+ 'all' : ( ) => ( type : string ) => {
36
+ if ( type === RESOURCE . IMAGE ) return [ mockImageData ] ;
37
+ return [ ] ;
38
+ }
39
+ } ,
40
+ actions : {
41
+ 'findAll' : jest . fn ( )
42
+ }
43
+ }
44
+ }
45
+ } ) ;
46
+
47
+ wrapper = mount ( ImageDetails , {
48
+ global : {
49
+ plugins : [ store ] ,
50
+ mocks : {
51
+ $route : {
52
+ params : {
53
+ cluster : 'test-cluster' ,
54
+ id : 'test-image'
55
+ }
56
+ } ,
57
+ $router : {
58
+ push : jest . fn ( )
59
+ } ,
60
+ $t : ( key : string ) => key ,
61
+ $store : store
62
+ } ,
63
+ stubs : {
64
+ RouterLink : {
65
+ template : '<a><slot /></a>' ,
66
+ props : [ 'to' ]
67
+ } ,
68
+ BadgeState : {
69
+ template : '<span class="badge-state"><slot /></span>' ,
70
+ props : [ 'color' , 'label' ]
71
+ } ,
72
+ SortableTable : {
73
+ template : '<div class="sortable-table"><slot name="header-left" /><slot name="search" /></div>' ,
74
+ props : [ 'rows' , 'headers' , 'hasAdvancedFiltering' , 'namespaced' , 'rowActions' , 'search' ]
75
+ } ,
76
+ ScoreBadge : {
77
+ template : '<span class="score-badge"><slot /></span>' ,
78
+ props : [ 'score' , 'scoreType' ]
79
+ } ,
80
+ BarChart : {
81
+ template : '<div class="bar-chart"></div>' ,
82
+ props : [ 'chartData' , 'description' , 'colorPrefix' ]
83
+ } ,
84
+ Checkbox : {
85
+ template : '<input type="checkbox" class="checkbox" />' ,
86
+ props : [ 'value' , 'label' ]
87
+ }
88
+ }
89
+ }
90
+ } ) ;
91
+ } ) ;
92
+
93
+ afterEach ( ( ) => {
94
+ wrapper . unmount ( ) ;
95
+ } ) ;
96
+
97
+ describe ( 'Component Initialization' , ( ) => {
98
+ it ( 'should render the component' , ( ) => {
99
+ expect ( wrapper . exists ( ) ) . toBe ( true ) ;
100
+ } ) ;
101
+
102
+ it ( 'should display the correct title with image name' , ( ) => {
103
+ const title = wrapper . find ( '.title' ) ;
104
+ expect ( title . exists ( ) ) . toBe ( true ) ;
105
+ expect ( title . text ( ) ) . toContain ( 'test-image' ) ;
106
+ } ) ;
107
+
108
+ it ( 'should display the severity badge' , ( ) => {
109
+ const severityBadge = wrapper . find ( '.severity-badge' ) ;
110
+ expect ( severityBadge . exists ( ) ) . toBe ( true ) ;
111
+ } ) ;
112
+
113
+ it ( 'should display the download report button' , ( ) => {
114
+ const downloadButton = wrapper . find ( '.btn.role-primary' ) ;
115
+ expect ( downloadButton . exists ( ) ) . toBe ( true ) ;
116
+ expect ( downloadButton . text ( ) ) . toContain ( 'imageScanner.images.downloadReport' ) ;
117
+ } ) ;
118
+ } ) ;
119
+
120
+ describe ( 'Image Information Section' , ( ) => {
121
+ it ( 'should display image details in info grid' , ( ) => {
122
+ const infoGrid = wrapper . find ( '.info-grid' ) ;
123
+ expect ( infoGrid . exists ( ) ) . toBe ( true ) ;
124
+ } ) ;
125
+
126
+ it ( 'should show basic image properties' , ( ) => {
127
+ const infoItems = wrapper . findAll ( '.info-item' ) ;
128
+ expect ( infoItems . length ) . toBeGreaterThan ( 0 ) ;
129
+ } ) ;
130
+
131
+ it ( 'should toggle show all properties when clicked' , async ( ) => {
132
+ const showPropertiesLink = wrapper . find ( '.show-properties-link a' ) ;
133
+ expect ( showPropertiesLink . exists ( ) ) . toBe ( true ) ;
134
+
135
+ await showPropertiesLink . trigger ( 'click' ) ;
136
+ expect ( wrapper . vm . showAllProperties ) . toBe ( true ) ;
137
+
138
+ await showPropertiesLink . trigger ( 'click' ) ;
139
+ expect ( wrapper . vm . showAllProperties ) . toBe ( false ) ;
140
+ } ) ;
141
+
142
+ it ( 'should show additional properties when showAllProperties is true' , async ( ) => {
143
+ wrapper . vm . showAllProperties = true ;
144
+ await wrapper . vm . $nextTick ( ) ;
145
+
146
+ // These should be visible when showAllProperties is true
147
+ expect ( wrapper . vm . showAllProperties ) . toBe ( true ) ;
148
+
149
+ // Check that additional properties are rendered
150
+ const infoItems = wrapper . findAll ( '.info-item' ) ;
151
+ expect ( infoItems . length ) . toBeGreaterThan ( 10 ) ; // Should have more items when expanded
152
+ } ) ;
153
+ } ) ;
154
+
155
+ describe ( 'Summary Section' , ( ) => {
156
+ it ( 'should render vulnerabilities section' , ( ) => {
157
+ const vulnerabilitiesSection = wrapper . find ( '.vulnerabilities-section' ) ;
158
+ expect ( vulnerabilitiesSection . exists ( ) ) . toBe ( true ) ;
159
+ } ) ;
160
+
161
+ it ( 'should render severity section with BarChart' , ( ) => {
162
+ const severitySection = wrapper . find ( '.severity-section' ) ;
163
+ expect ( severitySection . exists ( ) ) . toBe ( true ) ;
164
+
165
+ const barChart = wrapper . find ( '.bar-chart' ) ;
166
+ expect ( barChart . exists ( ) ) . toBe ( true ) ;
167
+ } ) ;
168
+
169
+ it ( 'should display most severe vulnerabilities' , ( ) => {
170
+ const vulnerabilitiesList = wrapper . find ( '.vulnerabilities-list' ) ;
171
+ expect ( vulnerabilitiesList . exists ( ) ) . toBe ( true ) ;
172
+ } ) ;
173
+ } ) ;
174
+
175
+ describe ( 'Vulnerability Table' , ( ) => {
176
+ it ( 'should render SortableTable component' , ( ) => {
177
+ const sortableTable = wrapper . find ( '.sortable-table' ) ;
178
+ expect ( sortableTable . exists ( ) ) . toBe ( true ) ;
179
+ } ) ;
180
+
181
+ it ( 'should display download custom report button' , ( ) => {
182
+ const downloadCustomButton = wrapper . find ( '.table-header-actions .btn.role-primary' ) ;
183
+ expect ( downloadCustomButton . exists ( ) ) . toBe ( true ) ;
184
+ expect ( downloadCustomButton . text ( ) ) . toContain ( 'imageScanner.images.buttons.downloadCustomReport' ) ;
185
+ } ) ;
186
+
187
+ it ( 'should show selected count when vulnerabilities are selected' , async ( ) => {
188
+ wrapper . vm . selectedVulnerabilities = [ 'CVE-2017-5337' , 'CVE-2018-1000007' ] ;
189
+ await wrapper . vm . $nextTick ( ) ;
190
+
191
+ const selectedCount = wrapper . find ( '.selected-count' ) ;
192
+ expect ( selectedCount . exists ( ) ) . toBe ( true ) ;
193
+ expect ( selectedCount . text ( ) ) . toContain ( '2' ) ;
194
+ } ) ;
195
+ } ) ;
196
+
197
+ describe ( 'Computed Properties' , ( ) => {
198
+ it ( 'should calculate total vulnerabilities correctly' , ( ) => {
199
+ wrapper . vm . severityDistribution = {
200
+ critical : 5 ,
201
+ high : 10 ,
202
+ medium : 15 ,
203
+ low : 8 ,
204
+ none : 2
205
+ } ;
206
+
207
+ expect ( wrapper . vm . totalVulnerabilities ) . toBe ( 40 ) ;
208
+ } ) ;
209
+
210
+ it ( 'should determine overall severity correctly' , ( ) => {
211
+ wrapper . vm . severityDistribution = {
212
+ critical : 5 ,
213
+ high : 0 ,
214
+ medium : 0 ,
215
+ low : 0 ,
216
+ none : 0
217
+ } ;
218
+
219
+ expect ( wrapper . vm . overallSeverity ) . toBe ( 'critical' ) ;
220
+ } ) ;
221
+
222
+ it ( 'should return none when no vulnerabilities' , ( ) => {
223
+ wrapper . vm . severityDistribution = {
224
+ critical : 0 ,
225
+ high : 0 ,
226
+ medium : 0 ,
227
+ low : 0 ,
228
+ none : 0
229
+ } ;
230
+
231
+ expect ( wrapper . vm . overallSeverity ) . toBe ( 'none' ) ;
232
+ } ) ;
233
+
234
+ it ( 'should calculate severity distribution with percentages' , ( ) => {
235
+ wrapper . vm . severityDistribution = {
236
+ critical : 10 ,
237
+ high : 20 ,
238
+ medium : 0 ,
239
+ low : 0 ,
240
+ none : 0
241
+ } ;
242
+
243
+ const distribution = wrapper . vm . severityDistributionWithPercentages ;
244
+ expect ( distribution . critical . percentage ) . toBe ( '33.3' ) ;
245
+ expect ( distribution . high . percentage ) . toBe ( '66.7' ) ;
246
+ } ) ;
247
+
248
+ it ( 'should filter vulnerabilities by CVE search' , ( ) => {
249
+ wrapper . vm . filters . cveSearch = 'CVE-2017' ;
250
+ const filtered = wrapper . vm . filteredVulnerabilities ;
251
+ expect ( filtered . every ( v => v . cveId . includes ( 'CVE-2017' ) ) ) . toBe ( true ) ;
252
+ } ) ;
253
+
254
+ it ( 'should filter vulnerabilities by score' , ( ) => {
255
+ wrapper . vm . filters . score = '9.0' ;
256
+ const filtered = wrapper . vm . filteredVulnerabilities ;
257
+ expect ( filtered . every ( v => {
258
+ const score = parseFloat ( v . score . split ( ' ' ) [ 0 ] ) ;
259
+ return score >= 9.0 ;
260
+ } ) ) . toBe ( true ) ;
261
+ } ) ;
262
+
263
+ it ( 'should filter vulnerabilities by package search' , ( ) => {
264
+ wrapper . vm . filters . packageSearch = 'tomcat' ;
265
+ const filtered = wrapper . vm . filteredVulnerabilities ;
266
+ expect ( filtered . every ( v => v . package . toLowerCase ( ) . includes ( 'tomcat' ) ) ) . toBe ( true ) ;
267
+ } ) ;
268
+
269
+ it ( 'should filter vulnerabilities by fix availability' , ( ) => {
270
+ wrapper . vm . filters . fixAvailable = 'available' ;
271
+ const filtered = wrapper . vm . filteredVulnerabilities ;
272
+ expect ( filtered . every ( v => v . fixAvailable === true ) ) . toBe ( true ) ;
273
+ } ) ;
274
+
275
+ it ( 'should filter vulnerabilities by severity' , ( ) => {
276
+ wrapper . vm . filters . severity = 'critical' ;
277
+ const filtered = wrapper . vm . filteredVulnerabilities ;
278
+ expect ( filtered . every ( v => v . severity === 'critical' ) ) . toBe ( true ) ;
279
+ } ) ;
280
+
281
+ it ( 'should filter vulnerabilities by exploitability' , ( ) => {
282
+ wrapper . vm . filters . exploitability = 'affected' ;
283
+ const filtered = wrapper . vm . filteredVulnerabilities ;
284
+ expect ( filtered . every ( v => v . exploitability === 'affected' ) ) . toBe ( true ) ;
285
+ } ) ;
286
+ } ) ;
287
+
288
+ describe ( 'Methods' , ( ) => {
289
+ it ( 'should return correct severity color' , ( ) => {
290
+ expect ( wrapper . vm . getSeverityColor ( 'critical' ) ) . toBe ( 'critical-severity' ) ;
291
+ expect ( wrapper . vm . getSeverityColor ( 'high' ) ) . toBe ( 'bg-error' ) ;
292
+ expect ( wrapper . vm . getSeverityColor ( 'medium' ) ) . toBe ( 'bg-warning' ) ;
293
+ expect ( wrapper . vm . getSeverityColor ( 'low' ) ) . toBe ( 'bg-warning' ) ;
294
+ expect ( wrapper . vm . getSeverityColor ( 'none' ) ) . toBe ( 'bg-info' ) ;
295
+ } ) ;
296
+
297
+ it ( 'should return correct severity bar color' , ( ) => {
298
+ expect ( wrapper . vm . getSeverityBarColor ( 'critical' ) ) . toBe ( '#850917' ) ;
299
+ expect ( wrapper . vm . getSeverityBarColor ( 'high' ) ) . toBe ( '#DE2136' ) ;
300
+ expect ( wrapper . vm . getSeverityBarColor ( 'medium' ) ) . toBe ( '#FF8533' ) ;
301
+ expect ( wrapper . vm . getSeverityBarColor ( 'low' ) ) . toBe ( '#FDD835' ) ;
302
+ expect ( wrapper . vm . getSeverityBarColor ( 'none' ) ) . toBe ( '#E0E0E0' ) ;
303
+ } ) ;
304
+
305
+ it ( 'should handle selection change' , ( ) => {
306
+ const selected = [ 'CVE-2017-5337' , 'CVE-2018-1000007' ] ;
307
+ wrapper . vm . onSelectionChange ( selected ) ;
308
+ expect ( wrapper . vm . selectedVulnerabilities ) . toEqual ( selected ) ;
309
+ } ) ;
310
+
311
+ it ( 'should handle download full report' , ( ) => {
312
+ const consoleSpy = jest . spyOn ( console , 'log' ) . mockImplementation ( ) ;
313
+ wrapper . vm . downloadFullReport ( ) ;
314
+ expect ( consoleSpy ) . toHaveBeenCalledWith ( 'Downloading full report for:' , wrapper . vm . imageName ) ;
315
+ consoleSpy . mockRestore ( ) ;
316
+ } ) ;
317
+
318
+ it ( 'should handle download custom report' , ( ) => {
319
+ const consoleSpy = jest . spyOn ( console , 'log' ) . mockImplementation ( ) ;
320
+ wrapper . vm . downloadCustomReport ( ) ;
321
+ expect ( consoleSpy ) . toHaveBeenCalledWith ( 'Downloading custom report with filters:' , wrapper . vm . filters ) ;
322
+ consoleSpy . mockRestore ( ) ;
323
+ } ) ;
324
+
325
+ it ( 'should calculate most severe vulnerabilities correctly' , ( ) => {
326
+ // Ensure vulnerability details are loaded first
327
+ wrapper . vm . vulnerabilityDetails = wrapper . vm . getMockVulnerabilityDetails ( ) ;
328
+ wrapper . vm . calculateMostSevereVulnerabilities ( ) ;
329
+ expect ( wrapper . vm . mostSevereVulnerabilities ) . toHaveLength ( 5 ) ;
330
+
331
+ // Should be sorted by score descending
332
+ const scores = wrapper . vm . mostSevereVulnerabilities . map ( v => parseFloat ( v . score . split ( ' ' ) [ 0 ] ) ) ;
333
+ for ( let i = 1 ; i < scores . length ; i ++ ) {
334
+ expect ( scores [ i - 1 ] ) . toBeGreaterThanOrEqual ( scores [ i ] ) ;
335
+ }
336
+ } ) ;
337
+ } ) ;
338
+
339
+ describe ( 'Component Data' , ( ) => {
340
+ it ( 'should have correct initial data properties' , ( ) => {
341
+ expect ( wrapper . vm . PRODUCT_NAME ) . toBeDefined ( ) ;
342
+ expect ( wrapper . vm . RESOURCE ) . toBeDefined ( ) ;
343
+ expect ( wrapper . vm . PAGE ) . toBeDefined ( ) ;
344
+ expect ( wrapper . vm . VULNERABILITY_DETAILS_TABLE ) . toBeDefined ( ) ;
345
+ expect ( wrapper . vm . showAllProperties ) . toBe ( false ) ;
346
+ expect ( wrapper . vm . selectedVulnerabilities ) . toEqual ( [ ] ) ;
347
+ } ) ;
348
+
349
+ it ( 'should have correct filter defaults' , ( ) => {
350
+ expect ( wrapper . vm . filters . cveSearch ) . toBe ( '' ) ;
351
+ expect ( wrapper . vm . filters . score ) . toBe ( '' ) ;
352
+ expect ( wrapper . vm . filters . packageSearch ) . toBe ( '' ) ;
353
+ expect ( wrapper . vm . filters . fixAvailable ) . toBe ( 'any' ) ;
354
+ expect ( wrapper . vm . filters . severity ) . toBe ( 'any' ) ;
355
+ expect ( wrapper . vm . filters . exploitability ) . toBe ( 'any' ) ;
356
+ } ) ;
357
+ } ) ;
358
+
359
+ describe ( 'Mock Data' , ( ) => {
360
+ it ( 'should generate mock vulnerability details' , ( ) => {
361
+ const mockData = wrapper . vm . getMockVulnerabilityDetails ( ) ;
362
+ expect ( Array . isArray ( mockData ) ) . toBe ( true ) ;
363
+ expect ( mockData . length ) . toBeGreaterThan ( 0 ) ;
364
+
365
+ // Check structure of first vulnerability
366
+ const firstVuln = mockData [ 0 ] ;
367
+ expect ( firstVuln ) . toHaveProperty ( 'cveId' ) ;
368
+ expect ( firstVuln ) . toHaveProperty ( 'score' ) ;
369
+ expect ( firstVuln ) . toHaveProperty ( 'package' ) ;
370
+ expect ( firstVuln ) . toHaveProperty ( 'fixAvailable' ) ;
371
+ expect ( firstVuln ) . toHaveProperty ( 'severity' ) ;
372
+ expect ( firstVuln ) . toHaveProperty ( 'exploitability' ) ;
373
+ } ) ;
374
+ } ) ;
375
+ } ) ;
0 commit comments