@@ -696,6 +696,36 @@ describe("fetchSpoolAtUri", () => {
696696 jesApiMock . mockRestore ( ) ;
697697 lookupAsFileMock . mockRestore ( ) ;
698698 } ) ;
699+
700+ it ( "resolves profile from URI when metadata is missing" , async ( ) => {
701+ const spoolEntryWithoutMetadata = { ...testEntries . spool , metadata : undefined } ;
702+ const lookupAsFileMock = vi . spyOn ( JobFSProvider . instance as any , "_lookupAsFile" ) . mockReturnValueOnce ( spoolEntryWithoutMetadata ) ;
703+ const getInfoFromUriMock = vi . spyOn ( JobFSProvider . instance as any , "_getInfoFromUri" ) . mockReturnValue ( {
704+ profile : testProfile ,
705+ path : "/TESTJOB(JOB1234) - ACTIVE/JES2.JESMSGLG.2" ,
706+ } ) ;
707+
708+ const mockJesApi = {
709+ downloadSingleSpool : vi . fn ( ( opts ) => {
710+ opts . stream . write ( "test data" ) ;
711+ } ) ,
712+ } ;
713+
714+ const jesApiMock = vi . spyOn ( ZoweExplorerApiRegister , "getInstance" ) . mockReturnValue ( {
715+ getJesApi : vi . fn ( ) . mockReturnValue ( mockJesApi ) ,
716+ registeredJesApiTypes : vi . fn ( ) . mockReturnValue ( [ "zosmf" ] ) ,
717+ } as any ) ;
718+
719+ const entry = await JobFSProvider . instance . fetchSpoolAtUri ( testUris . spool ) ;
720+
721+ expect ( getInfoFromUriMock ) . toHaveBeenCalledWith ( testUris . spool ) ;
722+ expect ( entry . metadata ) . toBeDefined ( ) ;
723+ expect ( entry . metadata . profile ) . toBe ( testProfile ) ;
724+
725+ lookupAsFileMock . mockRestore ( ) ;
726+ getInfoFromUriMock . mockRestore ( ) ;
727+ jesApiMock . mockRestore ( ) ;
728+ } ) ;
699729} ) ;
700730
701731describe ( "readFile" , ( ) => {
@@ -718,6 +748,295 @@ describe("readFile", () => {
718748 lookupAsFileMock . mockRestore ( ) ;
719749 fetchSpoolAtUriMock . mockRestore ( ) ;
720750 } ) ;
751+
752+ it ( "creates entries from URI when spool entry doesn't exist" , async ( ) => {
753+ const spoolEntry = { ...testEntries . spool } ;
754+ const fileNotFoundError = vscode . FileSystemError . FileNotFound ( testUris . spool ) ;
755+ const lookupAsFileMock = vi
756+ . spyOn ( JobFSProvider . instance as any , "_lookupAsFile" )
757+ . mockImplementationOnce ( ( ) => {
758+ throw fileNotFoundError ;
759+ } )
760+ . mockReturnValueOnce ( spoolEntry ) ;
761+ const createEntriesFromUriMock = vi . spyOn ( JobFSProvider . instance as any , "_createEntriesFromUri" ) . mockResolvedValueOnce ( undefined ) ;
762+ const fetchSpoolAtUriMock = vi . spyOn ( JobFSProvider . instance , "fetchSpoolAtUri" ) . mockResolvedValueOnce ( spoolEntry ) ;
763+
764+ expect ( await JobFSProvider . instance . readFile ( testUris . spool ) ) . toBe ( spoolEntry . data ) ;
765+ expect ( createEntriesFromUriMock ) . toHaveBeenCalledWith ( testUris . spool ) ;
766+ expect ( spoolEntry . wasAccessed ) . toBe ( true ) ;
767+
768+ lookupAsFileMock . mockRestore ( ) ;
769+ createEntriesFromUriMock . mockRestore ( ) ;
770+ fetchSpoolAtUriMock . mockRestore ( ) ;
771+ } ) ;
772+
773+ it ( "throws error if entry creation fails" , async ( ) => {
774+ const fileNotFoundError = vscode . FileSystemError . FileNotFound ( testUris . spool ) ;
775+ const lookupAsFileMock = vi . spyOn ( JobFSProvider . instance as any , "_lookupAsFile" ) . mockImplementation ( ( ) => {
776+ throw fileNotFoundError ;
777+ } ) ;
778+ const createEntriesFromUriMock = vi
779+ . spyOn ( JobFSProvider . instance as any , "_createEntriesFromUri" )
780+ . mockRejectedValueOnce ( new Error ( "Failed to create entries" ) ) ;
781+
782+ await expect ( JobFSProvider . instance . readFile ( testUris . spool ) ) . rejects . toThrow ( "Failed to create entries" ) ;
783+
784+ lookupAsFileMock . mockRestore ( ) ;
785+ createEntriesFromUriMock . mockRestore ( ) ;
786+ } ) ;
787+
788+ it ( "throws non-FileNotFound errors immediately without creating entries" , async ( ) => {
789+ const customError = new Error ( "Custom error that is not FileNotFound" ) ;
790+ const lookupAsFileMock = vi . spyOn ( JobFSProvider . instance as any , "_lookupAsFile" ) . mockImplementation ( ( ) => {
791+ throw customError ;
792+ } ) ;
793+ const createEntriesFromUriMock = vi . spyOn ( JobFSProvider . instance as any , "_createEntriesFromUri" ) ;
794+
795+ await expect ( JobFSProvider . instance . readFile ( testUris . spool ) ) . rejects . toThrow ( "Custom error that is not FileNotFound" ) ;
796+ expect ( createEntriesFromUriMock ) . not . toHaveBeenCalled ( ) ;
797+
798+ lookupAsFileMock . mockRestore ( ) ;
799+ createEntriesFromUriMock . mockRestore ( ) ;
800+ } ) ;
801+ } ) ;
802+
803+ describe ( "_createEntriesFromUri" , ( ) => {
804+ const mockJob = createIJobObject ( ) ;
805+ const mockSpoolFile = createIJobFile ( ) ;
806+ // buildUniqueSpoolName will create: TESTJOB.JOB1234.STEP.STDOUT.101
807+ const testJobUri = Uri . from ( { scheme : ZoweScheme . Jobs , path : "/sestest/JOB1234/TESTJOB.JOB1234.STEP.STDOUT.101" } ) ;
808+
809+ beforeEach ( ( ) => {
810+ vi . clearAllMocks ( ) ;
811+ } ) ;
812+
813+ it ( "throws FileNotFound error when URI has insufficient path parts" , async ( ) => {
814+ const invalidUri = Uri . from ( { scheme : ZoweScheme . Jobs , path : "/sestest/JOB1234" } ) ;
815+ const getInfoFromUriMock = vi . spyOn ( JobFSProvider . instance as any , "_getInfoFromUri" ) . mockReturnValue ( {
816+ profile : testProfile ,
817+ path : "/JOB1234" ,
818+ } ) ;
819+
820+ await expect ( ( JobFSProvider . instance as any ) . _createEntriesFromUri ( invalidUri ) ) . rejects . toThrow ( ) ;
821+
822+ getInfoFromUriMock . mockRestore ( ) ;
823+ } ) ;
824+
825+ it ( "creates profile directory when it doesn't exist" , async ( ) => {
826+ const getInfoFromUriMock = vi . spyOn ( JobFSProvider . instance as any , "_getInfoFromUri" ) . mockReturnValue ( {
827+ profile : testProfile ,
828+ path : "/JOB1234/TESTJOB.JOB1234.STEP.STDOUT.101" ,
829+ } ) ;
830+ const existsMock = vi . spyOn ( JobFSProvider . instance , "exists" ) . mockReturnValue ( false ) ;
831+ const createDirectoryMock = vi . spyOn ( JobFSProvider . instance , "createDirectory" ) . mockImplementation ( ( ) => undefined ) ;
832+ const jobEntryWithSpools = {
833+ ...testEntries . job ,
834+ entries : new Map ( ) ,
835+ } ;
836+ const lookupAsDirMock = vi . spyOn ( JobFSProvider . instance as any , "_lookupAsDirectory" ) . mockReturnValue ( jobEntryWithSpools ) ;
837+
838+ const mockJesApi = {
839+ getJobsByParameters : vi . fn ( ) . mockResolvedValue ( [ mockJob ] ) ,
840+ getSpoolFiles : vi . fn ( ) . mockResolvedValue ( [ mockSpoolFile ] ) ,
841+ } ;
842+ const jesApiMock = vi . spyOn ( ZoweExplorerApiRegister , "getInstance" ) . mockReturnValue ( {
843+ getJesApi : vi . fn ( ) . mockReturnValue ( mockJesApi ) ,
844+ registeredJesApiTypes : vi . fn ( ) . mockReturnValue ( [ "zosmf" ] ) ,
845+ } as any ) ;
846+ const ensureAuthNotCancelledMock = vi . spyOn ( AuthUtils , "ensureAuthNotCancelled" ) . mockResolvedValue ( undefined ) ;
847+ const waitForUnlockMock = vi . spyOn ( AuthHandler , "waitForUnlock" ) . mockResolvedValue ( undefined ) ;
848+
849+ // Mock writeFile to add the spool entry to the job's entries map
850+ const writeFileMock = vi . spyOn ( JobFSProvider . instance , "writeFile" ) . mockImplementation ( ( uri , content , options : any ) => {
851+ if ( options ?. name ) {
852+ jobEntryWithSpools . entries . set ( options . name , testEntries . spool ) ;
853+ }
854+ } ) ;
855+
856+ await ( JobFSProvider . instance as any ) . _createEntriesFromUri ( testJobUri ) ;
857+
858+ expect ( createDirectoryMock ) . toHaveBeenCalledWith ( expect . objectContaining ( { path : "/sestest" } ) , expect . objectContaining ( { isFilter : true } ) ) ;
859+
860+ getInfoFromUriMock . mockRestore ( ) ;
861+ existsMock . mockRestore ( ) ;
862+ createDirectoryMock . mockRestore ( ) ;
863+ lookupAsDirMock . mockRestore ( ) ;
864+ jesApiMock . mockRestore ( ) ;
865+ ensureAuthNotCancelledMock . mockRestore ( ) ;
866+ waitForUnlockMock . mockRestore ( ) ;
867+ writeFileMock . mockRestore ( ) ;
868+ } ) ;
869+
870+ it ( "creates job directory and fetches job information when job doesn't exist" , async ( ) => {
871+ const getInfoFromUriMock = vi . spyOn ( JobFSProvider . instance as any , "_getInfoFromUri" ) . mockReturnValue ( {
872+ profile : testProfile ,
873+ path : "/JOB1234/TESTJOB.JOB1234.STEP.STDOUT.101" ,
874+ } ) ;
875+ const existsMock = vi
876+ . spyOn ( JobFSProvider . instance , "exists" )
877+ . mockReturnValueOnce ( true ) // profile exists
878+ . mockReturnValueOnce ( false ) ; // job doesn't exist
879+ const createDirectoryMock = vi . spyOn ( JobFSProvider . instance , "createDirectory" ) . mockImplementation ( ( ) => undefined ) ;
880+ const jobEntryWithSpools = {
881+ ...testEntries . job ,
882+ entries : new Map ( ) ,
883+ } ;
884+ const lookupAsDirMock = vi . spyOn ( JobFSProvider . instance as any , "_lookupAsDirectory" ) . mockReturnValue ( jobEntryWithSpools ) ;
885+
886+ const mockJesApi = {
887+ getJobsByParameters : vi . fn ( ) . mockResolvedValue ( [ mockJob ] ) ,
888+ getSpoolFiles : vi . fn ( ) . mockResolvedValue ( [ mockSpoolFile ] ) ,
889+ } ;
890+ const jesApiMock = vi . spyOn ( ZoweExplorerApiRegister , "getInstance" ) . mockReturnValue ( {
891+ getJesApi : vi . fn ( ) . mockReturnValue ( mockJesApi ) ,
892+ registeredJesApiTypes : vi . fn ( ) . mockReturnValue ( [ "zosmf" ] ) ,
893+ } as any ) ;
894+ const ensureAuthNotCancelledMock = vi . spyOn ( AuthUtils , "ensureAuthNotCancelled" ) . mockResolvedValue ( undefined ) ;
895+ const waitForUnlockMock = vi . spyOn ( AuthHandler , "waitForUnlock" ) . mockResolvedValue ( undefined ) ;
896+
897+ // Mock writeFile to add the spool entry to the job's entries map
898+ const writeFileMock = vi . spyOn ( JobFSProvider . instance , "writeFile" ) . mockImplementation ( ( uri , content , options : any ) => {
899+ if ( options ?. name ) {
900+ jobEntryWithSpools . entries . set ( options . name , testEntries . spool ) ;
901+ }
902+ } ) ;
903+
904+ await ( JobFSProvider . instance as any ) . _createEntriesFromUri ( testJobUri ) ;
905+
906+ expect ( mockJesApi . getJobsByParameters ) . toHaveBeenCalledWith ( { jobid : "JOB1234" } ) ;
907+ expect ( createDirectoryMock ) . toHaveBeenCalledWith (
908+ expect . objectContaining ( { path : "/sestest/JOB1234" } ) ,
909+ expect . objectContaining ( { job : mockJob } )
910+ ) ;
911+ expect ( ensureAuthNotCancelledMock ) . toHaveBeenCalledWith ( testProfile ) ;
912+ expect ( waitForUnlockMock ) . toHaveBeenCalledWith ( testProfile ) ;
913+
914+ getInfoFromUriMock . mockRestore ( ) ;
915+ existsMock . mockRestore ( ) ;
916+ createDirectoryMock . mockRestore ( ) ;
917+ lookupAsDirMock . mockRestore ( ) ;
918+ jesApiMock . mockRestore ( ) ;
919+ ensureAuthNotCancelledMock . mockRestore ( ) ;
920+ waitForUnlockMock . mockRestore ( ) ;
921+ writeFileMock . mockRestore ( ) ;
922+ } ) ;
923+
924+ it ( "throws FileNotFound error when job is not found on mainframe" , async ( ) => {
925+ const getInfoFromUriMock = vi . spyOn ( JobFSProvider . instance as any , "_getInfoFromUri" ) . mockReturnValue ( {
926+ profile : testProfile ,
927+ path : "/JOB1234/TESTJOB.JOB1234.STEP.STDOUT.101" ,
928+ } ) ;
929+ const existsMock = vi
930+ . spyOn ( JobFSProvider . instance , "exists" )
931+ . mockReturnValueOnce ( true ) // profile exists
932+ . mockReturnValueOnce ( false ) ; // job doesn't exist
933+
934+ const mockJesApi = {
935+ getJobsByParameters : vi . fn ( ) . mockResolvedValue ( [ ] ) , // No jobs found
936+ } ;
937+ const jesApiMock = vi . spyOn ( ZoweExplorerApiRegister , "getInstance" ) . mockReturnValue ( {
938+ getJesApi : vi . fn ( ) . mockReturnValue ( mockJesApi ) ,
939+ registeredJesApiTypes : vi . fn ( ) . mockReturnValue ( [ "zosmf" ] ) ,
940+ } as any ) ;
941+ const ensureAuthNotCancelledMock = vi . spyOn ( AuthUtils , "ensureAuthNotCancelled" ) . mockResolvedValue ( undefined ) ;
942+ const waitForUnlockMock = vi . spyOn ( AuthHandler , "waitForUnlock" ) . mockResolvedValue ( undefined ) ;
943+
944+ await expect ( ( JobFSProvider . instance as any ) . _createEntriesFromUri ( testJobUri ) ) . rejects . toThrow ( ) ;
945+
946+ getInfoFromUriMock . mockRestore ( ) ;
947+ existsMock . mockRestore ( ) ;
948+ jesApiMock . mockRestore ( ) ;
949+ ensureAuthNotCancelledMock . mockRestore ( ) ;
950+ waitForUnlockMock . mockRestore ( ) ;
951+ } ) ;
952+
953+ it ( "fetches spool files when job entry has no entries" , async ( ) => {
954+ const getInfoFromUriMock = vi . spyOn ( JobFSProvider . instance as any , "_getInfoFromUri" ) . mockReturnValue ( {
955+ profile : testProfile ,
956+ path : "/JOB1234/TESTJOB.JOB1234.STEP.STDOUT.101" ,
957+ } ) ;
958+ const existsMock = vi . spyOn ( JobFSProvider . instance , "exists" ) . mockReturnValue ( true ) ;
959+ const jobEntryWithNoSpools = {
960+ ...testEntries . job ,
961+ entries : new Map ( ) ,
962+ } ;
963+ const lookupAsDirMock = vi . spyOn ( JobFSProvider . instance as any , "_lookupAsDirectory" ) . mockReturnValue ( jobEntryWithNoSpools ) ;
964+
965+ const mockJesApi = {
966+ getSpoolFiles : vi . fn ( ) . mockResolvedValue ( [ mockSpoolFile ] ) ,
967+ } ;
968+ const jesApiMock = vi . spyOn ( ZoweExplorerApiRegister , "getInstance" ) . mockReturnValue ( {
969+ getJesApi : vi . fn ( ) . mockReturnValue ( mockJesApi ) ,
970+ registeredJesApiTypes : vi . fn ( ) . mockReturnValue ( [ "zosmf" ] ) ,
971+ } as any ) ;
972+
973+ // Mock writeFile to add the spool entry to the job's entries map with the correct name
974+ const writeFileMock = vi . spyOn ( JobFSProvider . instance , "writeFile" ) . mockImplementation ( ( uri , content , options : any ) => {
975+ if ( options ?. name ) {
976+ jobEntryWithNoSpools . entries . set ( options . name , testEntries . spool ) ;
977+ }
978+ } ) ;
979+
980+ await ( JobFSProvider . instance as any ) . _createEntriesFromUri ( testJobUri ) ;
981+
982+ expect ( mockJesApi . getSpoolFiles ) . toHaveBeenCalledWith ( mockJob . jobname , mockJob . jobid ) ;
983+ expect ( writeFileMock ) . toHaveBeenCalled ( ) ;
984+
985+ getInfoFromUriMock . mockRestore ( ) ;
986+ existsMock . mockRestore ( ) ;
987+ lookupAsDirMock . mockRestore ( ) ;
988+ jesApiMock . mockRestore ( ) ;
989+ writeFileMock . mockRestore ( ) ;
990+ } ) ;
991+
992+ it ( "skips fetching spool files when job entry already has entries" , async ( ) => {
993+ const getInfoFromUriMock = vi . spyOn ( JobFSProvider . instance as any , "_getInfoFromUri" ) . mockReturnValue ( {
994+ profile : testProfile ,
995+ path : "/JOB1234/TESTJOB.JOB1234.STEP.STDOUT.101" ,
996+ } ) ;
997+ const existsMock = vi . spyOn ( JobFSProvider . instance , "exists" ) . mockReturnValue ( true ) ;
998+ const jobEntryWithSpools = {
999+ ...testEntries . job ,
1000+ entries : new Map ( [ [ "TESTJOB.JOB1234.STEP.STDOUT.101" , testEntries . spool ] ] ) ,
1001+ } ;
1002+ const lookupAsDirMock = vi . spyOn ( JobFSProvider . instance as any , "_lookupAsDirectory" ) . mockReturnValue ( jobEntryWithSpools ) ;
1003+
1004+ const mockJesApi = {
1005+ getSpoolFiles : vi . fn ( ) . mockResolvedValue ( [ mockSpoolFile ] ) ,
1006+ } ;
1007+ const jesApiMock = vi . spyOn ( ZoweExplorerApiRegister , "getInstance" ) . mockReturnValue ( {
1008+ getJesApi : vi . fn ( ) . mockReturnValue ( mockJesApi ) ,
1009+ registeredJesApiTypes : vi . fn ( ) . mockReturnValue ( [ "zosmf" ] ) ,
1010+ } as any ) ;
1011+
1012+ await ( JobFSProvider . instance as any ) . _createEntriesFromUri ( testJobUri ) ;
1013+
1014+ expect ( mockJesApi . getSpoolFiles ) . not . toHaveBeenCalled ( ) ;
1015+
1016+ getInfoFromUriMock . mockRestore ( ) ;
1017+ existsMock . mockRestore ( ) ;
1018+ lookupAsDirMock . mockRestore ( ) ;
1019+ jesApiMock . mockRestore ( ) ;
1020+ } ) ;
1021+
1022+ it ( "throws FileNotFound error when spool file is not found in job entries" , async ( ) => {
1023+ const getInfoFromUriMock = vi . spyOn ( JobFSProvider . instance as any , "_getInfoFromUri" ) . mockReturnValue ( {
1024+ profile : testProfile ,
1025+ path : "/JOB1234/NONEXISTENT.SPOOL.FILE" ,
1026+ } ) ;
1027+ const existsMock = vi . spyOn ( JobFSProvider . instance , "exists" ) . mockReturnValue ( true ) ;
1028+ const jobEntryWithDifferentSpool = {
1029+ ...testEntries . job ,
1030+ entries : new Map ( [ [ "DIFFERENT.SPOOL.NAME" , testEntries . spool ] ] ) ,
1031+ } ;
1032+ const lookupAsDirMock = vi . spyOn ( JobFSProvider . instance as any , "_lookupAsDirectory" ) . mockReturnValue ( jobEntryWithDifferentSpool ) ;
1033+
1034+ await expect ( ( JobFSProvider . instance as any ) . _createEntriesFromUri ( testJobUri ) ) . rejects . toThrow ( ) ;
1035+
1036+ getInfoFromUriMock . mockRestore ( ) ;
1037+ existsMock . mockRestore ( ) ;
1038+ lookupAsDirMock . mockRestore ( ) ;
1039+ } ) ;
7211040} ) ;
7221041
7231042describe ( "writeFile" , ( ) => {
0 commit comments