@@ -16,6 +16,16 @@ vi.mock('sonner', () => ({
1616 toast : { error : vi . fn ( ) } ,
1717} ) ) ;
1818
19+ async function confirmAuthorization ( user : ReturnType < typeof userEvent . setup > ) {
20+ await user . click (
21+ screen . getByText ( 'I own this target or have written authorization to test it.' ) ,
22+ ) ;
23+ }
24+
25+ function checkInput ( label : RegExp ) {
26+ return screen . getByLabelText ( label , { selector : 'input' } ) ;
27+ }
28+
1929describe ( 'CreateRunPanel' , ( ) => {
2030 beforeEach ( ( ) => {
2131 vi . clearAllMocks ( ) ;
@@ -48,6 +58,7 @@ describe('CreateRunPanel', () => {
4858 render ( < CreateRunPanel orgId = "org_1" balance = { 1 } onSubmit = { onSubmit } /> ) ;
4959
5060 await user . type ( screen . getByLabelText ( / t a r g e t u r l / i) , 'app.example.com' ) ;
61+ await confirmAuthorization ( user ) ;
5162 await user . click ( screen . getByRole ( 'button' , { name : / s t a r t s c a n / i } ) ) ;
5263
5364 await waitFor ( ( ) => {
@@ -85,8 +96,7 @@ describe('CreateRunPanel', () => {
8596 render ( < CreateRunPanel orgId = "org_1" balance = { 1 } onSubmit = { vi . fn ( ) } /> ) ;
8697
8798 await user . click ( screen . getByText ( 'Quick' ) ) ;
88- await user . click ( screen . getByRole ( 'button' , { name : / c u s t o m i z e s c a n / i } ) ) ;
89- await user . click ( screen . getByLabelText ( / ^ x s s $ / i) ) ;
99+ await user . click ( checkInput ( / ^ x s s $ / i) ) ;
90100
91101 expect ( screen . getByText ( 'Custom · 11 min-25 min' ) ) . toBeInTheDocument ( ) ;
92102 expect ( screen . queryByText ( / b a s e d o n / i) ) . not . toBeInTheDocument ( ) ;
@@ -103,20 +113,20 @@ describe('CreateRunPanel', () => {
103113 render ( < CreateRunPanel orgId = "org_1" balance = { 1 } onSubmit = { onSubmit } /> ) ;
104114
105115 await user . click ( screen . getByText ( 'Quick' ) ) ;
106- await user . click ( screen . getByRole ( 'button' , { name : / c u s t o m i z e s c a n / i } ) ) ;
107- await user . click ( validationLevel ( ) . getByLabelText ( / s a f e p r o o f / i) ) ;
108- await user . click ( screen . getByLabelText ( / ^ x s s $ / i) ) ;
109- await user . click ( screen . getByLabelText ( / ^ i n j e c t i o n $ / i) ) ;
110- await user . click ( screen . getByLabelText ( / ^ a u t h e n t i c a t i o n $ / i) ) ;
111- await user . click ( screen . getByLabelText ( / ^ a u t h o r i z a t i o n $ / i) ) ;
112- await user . click ( screen . getByLabelText ( / i d o r \/ b o l a / i) ) ;
113- await user . click ( screen . getByLabelText ( / s s r f \/ x x e / i) ) ;
114- await user . click ( screen . getByLabelText ( / ^ c s r f $ / i) ) ;
116+ await user . click ( validationLevel ( ) . getByLabelText ( / s a f e p r o o f / i, { selector : 'input' } ) ) ;
117+ await user . click ( checkInput ( / ^ x s s $ / i) ) ;
118+ await user . click ( checkInput ( / ^ i n j e c t i o n $ / i) ) ;
119+ await user . click ( checkInput ( / ^ a u t h e n t i c a t i o n $ / i) ) ;
120+ await user . click ( checkInput ( / ^ a u t h o r i z a t i o n $ / i) ) ;
121+ await user . click ( checkInput ( / ^ i d o r \/ b o l a $ / i) ) ;
122+ await user . click ( checkInput ( / ^ s s r f \/ x x e $ / i) ) ;
123+ await user . click ( checkInput ( / ^ c s r f $ / i) ) ;
115124
116125 expect ( screen . getByText ( 'Standard · 30-90 min' ) ) . toBeInTheDocument ( ) ;
117126 expect ( screen . queryByText ( / b a s e d o n / i) ) . not . toBeInTheDocument ( ) ;
118127
119128 await user . type ( screen . getByLabelText ( / t a r g e t u r l / i) , 'app.example.com' ) ;
129+ await confirmAuthorization ( user ) ;
120130 await user . click ( screen . getByRole ( 'button' , { name : / s t a r t s c a n / i } ) ) ;
121131
122132 await waitFor ( ( ) => {
@@ -142,12 +152,11 @@ describe('CreateRunPanel', () => {
142152 render ( < CreateRunPanel orgId = "org_1" balance = { 1 } onSubmit = { vi . fn ( ) } /> ) ;
143153
144154 await user . click ( screen . getByText ( 'Quick' ) ) ;
145- await user . click ( screen . getByRole ( 'button' , { name : / c u s t o m i z e s c a n / i } ) ) ;
146- await user . click ( screen . getByLabelText ( / ^ x s s $ / i) ) ;
155+ await user . click ( checkInput ( / ^ x s s $ / i) ) ;
147156
148157 expect ( screen . getByText ( 'Custom · 11 min-25 min' ) ) . toBeInTheDocument ( ) ;
149158
150- await user . click ( validationLevel ( ) . getByLabelText ( / s a f e p r o o f / i) ) ;
159+ await user . click ( validationLevel ( ) . getByLabelText ( / s a f e p r o o f / i, { selector : 'input' } ) ) ;
151160
152161 expect ( screen . getByText ( 'Custom · 17 min-38 min' ) ) . toBeInTheDocument ( ) ;
153162 } ) ;
@@ -158,10 +167,9 @@ describe('CreateRunPanel', () => {
158167 render ( < CreateRunPanel orgId = "org_1" balance = { 1 } onSubmit = { vi . fn ( ) } /> ) ;
159168
160169 await user . click ( screen . getByText ( 'Quick' ) ) ;
161- await user . click ( screen . getByRole ( 'button' , { name : / c u s t o m i z e s c a n / i } ) ) ;
162- await user . click ( screen . getByLabelText ( / s e c r e t s & i n f o d i s c l o s u r e / i) ) ;
163- await user . click ( screen . getByLabelText ( / t e c h n o l o g y c o n f i g / i) ) ;
164- await user . click ( screen . getByLabelText ( / ^ d i s c o v e r y $ / i) ) ;
170+ await user . click ( checkInput ( / ^ s e c r e t s & i n f o d i s c l o s u r e $ / i) ) ;
171+ await user . click ( checkInput ( / ^ t e c h n o l o g y c o n f i g $ / i) ) ;
172+ await user . click ( checkInput ( / ^ d i s c o v e r y $ / i) ) ;
165173
166174 expect ( screen . getByText ( 'Custom · 5 min-5 min' ) ) . toBeInTheDocument ( ) ;
167175 } ) ;
@@ -172,13 +180,10 @@ describe('CreateRunPanel', () => {
172180 expect ( screen . getByLabelText ( / r e p o s i t o r y / i) ) . toBeInTheDocument ( ) ;
173181 } ) ;
174182
175- it ( 'uses design-system radio styling for evidence options' , async ( ) => {
176- const user = userEvent . setup ( ) ;
183+ it ( 'uses design-system radio styling for evidence options' , ( ) => {
177184 render ( < CreateRunPanel orgId = "org_1" balance = { 1 } onSubmit = { vi . fn ( ) } /> ) ;
178185
179- await user . click ( screen . getByRole ( 'button' , { name : / c u s t o m i z e s c a n / i } ) ) ;
180-
181- expect ( validationLevel ( ) . getByLabelText ( / s a f e p r o o f / i) . closest ( 'label' ) ) . toHaveClass (
186+ expect ( validationLevel ( ) . getByLabelText ( / s a f e p r o o f / i, { selector : 'input' } ) . closest ( 'label' ) ) . toHaveClass (
182187 'has-[[data-checked]]:border-primary' ,
183188 'has-[[data-checked]]:bg-primary/5' ,
184189 'has-[[data-checked]]:text-primary' ,
@@ -191,50 +196,51 @@ describe('CreateRunPanel', () => {
191196 render ( < CreateRunPanel orgId = "org_1" balance = { 1 } onSubmit = { vi . fn ( ) } /> ) ;
192197
193198 await user . click ( screen . getByText ( 'Quick' ) ) ;
194- await user . click ( screen . getByRole ( 'button' , { name : / c u s t o m i z e s c a n / i } ) ) ;
195- await user . click ( screen . getByLabelText ( / s e c r e t s & i n f o d i s c l o s u r e / i) ) ;
196- await user . click ( screen . getByLabelText ( / t e c h n o l o g y c o n f i g / i) ) ;
197- await user . click ( screen . getByLabelText ( / ^ d i s c o v e r y $ / i) ) ;
198- await user . click ( screen . getByLabelText ( / ^ x s s $ / i) ) ;
199+ await user . click ( checkInput ( / ^ s e c r e t s & i n f o d i s c l o s u r e $ / i) ) ;
200+ await user . click ( checkInput ( / ^ t e c h n o l o g y c o n f i g $ / i) ) ;
201+ await user . click ( checkInput ( / ^ d i s c o v e r y $ / i) ) ;
202+ await user . click ( checkInput ( / ^ x s s $ / i) ) ;
199203
200- expect ( screen . getByLabelText ( / ^ d i s c o v e r y $ / i) ) . toBeChecked ( ) ;
204+ expect ( checkInput ( / ^ d i s c o v e r y $ / i) ) . toBeChecked ( ) ;
201205 } ) ;
202206
203- it ( 'shows report-only helper text' , async ( ) => {
207+ it ( 'shows report-only helper text when report_only is selected ' , async ( ) => {
204208 const user = userEvent . setup ( ) ;
205209
206210 render ( < CreateRunPanel orgId = "org_1" balance = { 1 } onSubmit = { vi . fn ( ) } /> ) ;
207211
208212 await user . click ( screen . getByText ( 'Quick' ) ) ;
209- await user . click ( screen . getByRole ( 'button' , { name : / c u s t o m i z e s c a n / i } ) ) ;
210213
211214 expect (
212- screen . getByText ( / f a s t e s t a n d l o w e s t r i s k / i) ,
215+ screen . getByText ( / f i n d i n g s a r e r e p o r t e d w i t h o u t e x p l o i t a t i o n / i) ,
213216 ) . toBeInTheDocument ( ) ;
214217 } ) ;
215218
216- it ( 'shows validation level choices and selected helper copy ' , async ( ) => {
219+ it ( 'renders the Scan coverage panel open by default and exposes validation level options ' , async ( ) => {
217220 const user = userEvent . setup ( ) ;
218221
219222 render ( < CreateRunPanel orgId = "org_1" balance = { 1 } onSubmit = { vi . fn ( ) } /> ) ;
220223
221- expect ( screen . getByText ( 'Customize scan' ) ) . toBeInTheDocument ( ) ;
222-
223- await user . click ( screen . getByRole ( 'button' , { name : / c u s t o m i z e s c a n / i } ) ) ;
224-
224+ expect ( screen . getByText ( 'Scan coverage' ) ) . toBeInTheDocument ( ) ;
225225 expect ( screen . getByText ( 'Validation level' ) ) . toBeInTheDocument ( ) ;
226- expect ( validationLevel ( ) . getByLabelText ( / r e p o r t o n l y / i) ) . toBeInTheDocument ( ) ;
227- expect ( validationLevel ( ) . getByLabelText ( / s a f e p r o o f / i) ) . toBeInTheDocument ( ) ;
228- expect ( validationLevel ( ) . getByLabelText ( / i m p a c t p r o o f / i) ) . toBeInTheDocument ( ) ;
226+ expect ( validationLevel ( ) . getByLabelText ( / r e p o r t o n l y / i, { selector : 'input' } ) ) . toBeInTheDocument ( ) ;
227+ expect ( validationLevel ( ) . getByLabelText ( / s a f e p r o o f / i, { selector : 'input' } ) ) . toBeInTheDocument ( ) ;
228+ expect ( validationLevel ( ) . getByLabelText ( / i m p a c t p r o o f / i, { selector : 'input' } ) ) . toBeInTheDocument ( ) ;
229229
230- await user . click ( validationLevel ( ) . getByLabelText ( / r e p o r t o n l y / i) ) ;
231- expect ( screen . getByText ( / f a s t e s t a n d l o w e s t r i s k / i) ) . toBeInTheDocument ( ) ;
230+ await user . click ( validationLevel ( ) . getByLabelText ( / r e p o r t o n l y / i, { selector : 'input' } ) ) ;
231+ expect (
232+ screen . getByText ( / f i n d i n g s a r e r e p o r t e d w i t h o u t e x p l o i t a t i o n / i) ,
233+ ) . toBeInTheDocument ( ) ;
232234
233- await user . click ( validationLevel ( ) . getByLabelText ( / s a f e p r o o f / i) ) ;
234- expect ( screen . getByText ( / b a l a n c e d v a l i d a t i o n / i) ) . toBeInTheDocument ( ) ;
235+ await user . click ( validationLevel ( ) . getByLabelText ( / s a f e p r o o f / i, { selector : 'input' } ) ) ;
236+ expect (
237+ screen . getByText ( / f i n d i n g s a r e v a l i d a t e d w i t h n o n - d e s t r u c t i v e p r o o f s / i) ,
238+ ) . toBeInTheDocument ( ) ;
235239
236- await user . click ( validationLevel ( ) . getByLabelText ( / i m p a c t p r o o f / i) ) ;
237- expect ( screen . getByText ( / h i g h e s t c o n f i d e n c e , l o n g e r r u n t i m e / i) ) . toBeInTheDocument ( ) ;
240+ await user . click ( validationLevel ( ) . getByLabelText ( / i m p a c t p r o o f / i, { selector : 'input' } ) ) ;
241+ expect (
242+ screen . getByText ( / f i n d i n g s a r e v a l i d a t e d w i t h a c t i v e e x p l o i t a t i o n / i) ,
243+ ) . toBeInTheDocument ( ) ;
238244 } ) ;
239245
240246 it ( 'keeps preset cards concise without validation metadata' , ( ) => {
@@ -245,21 +251,64 @@ describe('CreateRunPanel', () => {
245251 expect ( screen . queryByText ( 'Impact proof · 12 checks' ) ) . not . toBeInTheDocument ( ) ;
246252 } ) ;
247253
248- it ( 'requires confirmation for Deep profile before submit' , async ( ) => {
254+ it ( 'blocks submit when authorization is not confirmed' , async ( ) => {
255+ const user = userEvent . setup ( ) ;
256+ const onSubmit = vi . fn ( async ( ) => ( { id : 'never' } ) ) ;
257+
258+ render ( < CreateRunPanel orgId = "org_1" balance = { 1 } onSubmit = { onSubmit } /> ) ;
259+
260+ await user . type ( screen . getByLabelText ( / t a r g e t u r l / i) , 'app.example.com' ) ;
261+ await user . click ( screen . getByRole ( 'button' , { name : / s t a r t s c a n / i } ) ) ;
262+
263+ await waitFor ( ( ) => {
264+ expect (
265+ screen . getByText ( / c o n f i r m y o u o w n o r a r e a u t h o r i z e d t o t e s t t h i s t a r g e t / i) ,
266+ ) . toBeInTheDocument ( ) ;
267+ } ) ;
268+ expect ( onSubmit ) . not . toHaveBeenCalled ( ) ;
269+ } ) ;
270+
271+ it ( 'exposes authorization error to assistive tech via aria-invalid and aria-describedby' , async ( ) => {
272+ const user = userEvent . setup ( ) ;
273+
274+ render ( < CreateRunPanel orgId = "org_1" balance = { 1 } onSubmit = { vi . fn ( ) } /> ) ;
275+
276+ const checkbox = screen . getByRole ( 'checkbox' , { name : / i o w n t h i s t a r g e t / i } ) ;
277+ expect ( checkbox ) . toHaveAttribute ( 'aria-invalid' , 'false' ) ;
278+ expect ( checkbox ) . toHaveAttribute ( 'aria-describedby' , 'pt-authorized-help' ) ;
279+
280+ await user . type ( screen . getByLabelText ( / t a r g e t u r l / i) , 'app.example.com' ) ;
281+ await user . click ( screen . getByRole ( 'button' , { name : / s t a r t s c a n / i } ) ) ;
282+
283+ await waitFor ( ( ) => {
284+ expect ( checkbox ) . toHaveAttribute ( 'aria-invalid' , 'true' ) ;
285+ } ) ;
286+ expect ( checkbox . getAttribute ( 'aria-describedby' ) ) . toContain ( 'pt-authorized-error' ) ;
287+
288+ const alert = screen . getByRole ( 'alert' ) ;
289+ expect ( alert ) . toHaveAttribute ( 'id' , 'pt-authorized-error' ) ;
290+ expect ( alert ) . toHaveTextContent ( / c o n f i r m y o u o w n o r a r e a u t h o r i z e d t o t e s t t h i s t a r g e t / i) ;
291+ } ) ;
292+
293+ it ( 'requires confirmation for impact-proof validation before submit' , async ( ) => {
249294 const user = userEvent . setup ( ) ;
250295 const onSubmit = vi . fn ( async ( ) => ( { id : 'run_deep' } ) ) ;
251296
252297 render ( < CreateRunPanel orgId = "org_1" balance = { 1 } onSubmit = { onSubmit } /> ) ;
253298
254299 await user . type ( screen . getByLabelText ( / t a r g e t u r l / i) , 'app.example.com' ) ;
255300 await user . click ( screen . getByText ( 'Deep' ) ) ;
301+ await confirmAuthorization ( user ) ;
256302 await user . click ( screen . getByRole ( 'button' , { name : / s t a r t s c a n / i } ) ) ;
257303
258304 expect ( onSubmit ) . not . toHaveBeenCalled ( ) ;
259305 const dialog = screen . getByRole ( 'alertdialog' ) ;
260- expect ( within ( dialog ) . getByText ( / c o n f i r m s c a n i n t e n s i t y / i) ) . toBeInTheDocument ( ) ;
306+ expect ( within ( dialog ) . getByText ( / c o n f i r m i m p a c t - p r o o f s c a n / i) ) . toBeInTheDocument ( ) ;
307+ expect (
308+ within ( dialog ) . getByText ( / a c t i v e l y e x p l o i t s f i n d i n g s / i) ,
309+ ) . toBeInTheDocument ( ) ;
261310
262- await user . click ( within ( dialog ) . getByRole ( 'button' , { name : / s t a r t s c a n / i } ) ) ;
311+ await user . click ( within ( dialog ) . getByRole ( 'button' , { name : / r u n i m p a c t - p r o o f s c a n / i } ) ) ;
263312
264313 await waitFor ( ( ) => {
265314 expect ( onSubmit ) . toHaveBeenCalledWith (
@@ -270,6 +319,30 @@ describe('CreateRunPanel', () => {
270319 ) ;
271320 } ) ;
272321 } ) ;
322+
323+ it ( 'does not show the impact-proof modal for Standard profile' , async ( ) => {
324+ const user = userEvent . setup ( ) ;
325+ const onSubmit = vi . fn ( async ( ) => ( { id : 'run_standard' } ) ) ;
326+
327+ render ( < CreateRunPanel orgId = "org_1" balance = { 1 } onSubmit = { onSubmit } /> ) ;
328+
329+ await user . type ( screen . getByLabelText ( / t a r g e t u r l / i) , 'app.example.com' ) ;
330+ await confirmAuthorization ( user ) ;
331+ await user . click ( screen . getByRole ( 'button' , { name : / s t a r t s c a n / i } ) ) ;
332+
333+ await waitFor ( ( ) => {
334+ expect ( onSubmit ) . toHaveBeenCalled ( ) ;
335+ } ) ;
336+ expect ( screen . queryByRole ( 'alertdialog' ) ) . not . toBeInTheDocument ( ) ;
337+ } ) ;
338+
339+ it ( 'exposes a tooltip trigger for each vulnerability check' , ( ) => {
340+ render ( < CreateRunPanel orgId = "org_1" balance = { 1 } onSubmit = { vi . fn ( ) } /> ) ;
341+
342+ expect ( screen . getByRole ( 'button' , { name : / a b o u t x s s / i } ) ) . toBeInTheDocument ( ) ;
343+ expect ( screen . getByRole ( 'button' , { name : / a b o u t c s r f / i } ) ) . toBeInTheDocument ( ) ;
344+ expect ( screen . getByRole ( 'button' , { name : / a b o u t b u s i n e s s l o g i c / i } ) ) . toBeInTheDocument ( ) ;
345+ } ) ;
273346} ) ;
274347
275348function validationLevel ( ) {
0 commit comments