@@ -3,8 +3,13 @@ import userEvent from '@testing-library/user-event';
33import React from 'react' ;
44import { Teaspoons , TeaspoonsContract } from 'src/libs/ajax/teaspoons/Teaspoons' ;
55import { Pipeline , PipelineInput , PipelineList , PipelineWithDetails } from 'src/libs/ajax/teaspoons/teaspoons-models' ;
6+ import { notify } from 'src/libs/notifications' ;
67import { mockUserPipelineQuotaDetails } from 'src/pages/scientificServices/pipelines/utils/mock-utils' ;
7- import { preparePipelineRun } from 'src/pages/scientificServices/pipelines/utils/submission-utils' ;
8+ import {
9+ preparePipelineRun ,
10+ startPipelineRun ,
11+ uploadPipelineFiles ,
12+ } from 'src/pages/scientificServices/pipelines/utils/submission-utils' ;
813import { asMockedFn , partial , renderWithAppContexts as render } from 'src/testing/test-utils' ;
914
1015import { RunJob } from './RunJob' ;
@@ -19,6 +24,11 @@ jest.mock('src/libs/nav', () => ({
1924 getLink : jest . fn ( ( ) => '/' ) ,
2025} ) ) ;
2126
27+ jest . mock ( 'src/libs/notifications' , ( ) => ( {
28+ ...jest . requireActual ( 'src/libs/notifications' ) ,
29+ notify : jest . fn ( ) ,
30+ } ) ) ;
31+
2232jest . mock ( 'src/pages/scientificServices/pipelines/utils/submission-utils' , ( ) => ( {
2333 ...jest . requireActual ( 'src/pages/scientificServices/pipelines/utils/submission-utils' ) ,
2434 preparePipelineRun : jest . fn ( ) ,
@@ -340,6 +350,112 @@ describe('RunJob Component', () => {
340350 expect . any ( String )
341351 ) ;
342352 } ) ;
353+
354+ it ( 'handles error when preparePipelineRun fails' , async ( ) => {
355+ const mockError = new Error ( 'Failed to prepare pipeline' ) ;
356+ asMockedFn ( preparePipelineRun ) . mockRejectedValue ( mockError ) ;
357+
358+ const user = userEvent . setup ( ) ;
359+ render ( < RunJob /> ) ;
360+
361+ await waitFor ( ( ) => {
362+ expect ( screen . getByText ( 'Submit' ) ) . toBeInTheDocument ( ) ;
363+ } ) ;
364+
365+ // Fill in required fields
366+ const outputPrefixInput = screen . getByLabelText ( 'outputBasename text input' ) ;
367+ await user . type ( outputPrefixInput , 'test_output' ) ;
368+
369+ const fileInput = document . querySelector ( 'input[type="file"]' ) as HTMLInputElement ;
370+ const file = new File ( [ 'test' ] , 'test.vcf.gz' , { type : 'text/plain' } ) ;
371+ await waitFor ( ( ) => userEvent . upload ( fileInput , file ) ) ;
372+
373+ const submitButton = screen . getByText ( 'Submit' ) ;
374+ await waitFor ( ( ) => user . click ( submitButton ) ) ;
375+
376+ await waitFor ( ( ) => {
377+ expect ( notify ) . toHaveBeenCalledWith ( 'error' , 'Error: Failed to prepare pipeline' ) ;
378+ } ) ;
379+
380+ // Verify submit button is re-enabled after error
381+ expect ( submitButton ) . not . toHaveAttribute ( 'aria-disabled' , 'true' ) ;
382+ } ) ;
383+
384+ it ( 'handles error when uploadPipelineFiles fails' , async ( ) => {
385+ asMockedFn ( preparePipelineRun ) . mockResolvedValue ( {
386+ jobId : 'mock-job-id' ,
387+ fileInputUploadUrls : {
388+ multiSampleVcf : { signedUrl : 'https://mock-signed-url.com/upload' } ,
389+ } ,
390+ } ) ;
391+
392+ const mockError = new Error ( 'Upload failed :(' ) ;
393+ asMockedFn ( uploadPipelineFiles ) . mockRejectedValue ( mockError ) ;
394+
395+ const user = userEvent . setup ( ) ;
396+ render ( < RunJob /> ) ;
397+
398+ await waitFor ( ( ) => {
399+ expect ( screen . getByText ( 'Submit' ) ) . toBeInTheDocument ( ) ;
400+ } ) ;
401+
402+ // Fill in required fields
403+ const outputPrefixInput = screen . getByLabelText ( 'outputBasename text input' ) ;
404+ await user . type ( outputPrefixInput , 'test_output' ) ;
405+
406+ const fileInput = document . querySelector ( 'input[type="file"]' ) as HTMLInputElement ;
407+ const file = new File ( [ 'test' ] , 'test.vcf.gz' , { type : 'text/plain' } ) ;
408+ await waitFor ( ( ) => userEvent . upload ( fileInput , file ) ) ;
409+
410+ const submitButton = screen . getByText ( 'Submit' ) ;
411+ await waitFor ( ( ) => user . click ( submitButton ) ) ;
412+
413+ await waitFor ( ( ) => {
414+ expect ( notify ) . toHaveBeenCalledWith ( 'error' , 'Error: Upload failed :(' ) ;
415+ } ) ;
416+
417+ // Verify submit button is re-enabled after error
418+ expect ( submitButton ) . not . toHaveAttribute ( 'aria-disabled' , 'true' ) ;
419+ } ) ;
420+
421+ it ( 'handles error when startPipelineRun fails' , async ( ) => {
422+ asMockedFn ( preparePipelineRun ) . mockResolvedValue ( {
423+ jobId : 'mock-job-id' ,
424+ fileInputUploadUrls : {
425+ multiSampleVcf : { signedUrl : 'https://mock-signed-url.com/upload' } ,
426+ } ,
427+ } ) ;
428+
429+ asMockedFn ( uploadPipelineFiles ) . mockResolvedValue ( undefined ) ;
430+
431+ const mockError = new Error ( 'Failed to start run' ) ;
432+ asMockedFn ( startPipelineRun ) . mockRejectedValue ( mockError ) ;
433+
434+ const user = userEvent . setup ( ) ;
435+ render ( < RunJob /> ) ;
436+
437+ await waitFor ( ( ) => {
438+ expect ( screen . getByText ( 'Submit' ) ) . toBeInTheDocument ( ) ;
439+ } ) ;
440+
441+ // Fill in required fields
442+ const outputPrefixInput = screen . getByLabelText ( 'outputBasename text input' ) ;
443+ await user . type ( outputPrefixInput , 'test_output' ) ;
444+
445+ const fileInput = document . querySelector ( 'input[type="file"]' ) as HTMLInputElement ;
446+ const file = new File ( [ 'test' ] , 'test.vcf.gz' , { type : 'text/plain' } ) ;
447+ await waitFor ( ( ) => userEvent . upload ( fileInput , file ) ) ;
448+
449+ const submitButton = screen . getByText ( 'Submit' ) ;
450+ await waitFor ( ( ) => user . click ( submitButton ) ) ;
451+
452+ await waitFor ( ( ) => {
453+ expect ( notify ) . toHaveBeenCalledWith ( 'error' , 'Error: Failed to start run' ) ;
454+ } ) ;
455+
456+ // Verify submit button is re-enabled after error
457+ expect ( submitButton ) . not . toHaveAttribute ( 'aria-disabled' , 'true' ) ;
458+ } ) ;
343459} ) ;
344460
345461describe ( 'uploadPipelineFiles function' , ( ) => {
0 commit comments