diff --git a/packages/cli/__tests__/zosfiles/__unit__/search/ds/__snapshots__/Datasets.definition.unit.test.ts.snap b/packages/cli/__tests__/zosfiles/__unit__/search/ds/__snapshots__/Datasets.definition.unit.test.ts.snap index 21f8b7bed1..5723856c47 100644 --- a/packages/cli/__tests__/zosfiles/__unit__/search/ds/__snapshots__/Datasets.definition.unit.test.ts.snap +++ b/packages/cli/__tests__/zosfiles/__unit__/search/ds/__snapshots__/Datasets.definition.unit.test.ts.snap @@ -49,26 +49,39 @@ Array [ "name": "timeout", "type": "number", }, + Object { + "aliases": Array [ + "re", + ], + "defaultValue": false, + "description": "Whether the search string is a regular expression.", + "name": "regex", + "type": "boolean", + }, ] `; exports[`zos-files search ds command definition should not have changed 2`] = ` Array [ Object { - "description": "Search all of IBMUSER's, data sets for 'ZOWE'", + "description": "Search all of IBMUSER's data sets for 'ZOWE'", "options": "'IBMUSER.*' 'ZOWE'", }, Object { - "description": "Search all of IBMUSER's, data sets for 'ZOWE' in uppercase only", + "description": "Search all of IBMUSER's data sets for 'ZOWE' in uppercase only", "options": "'IBMUSER.*' 'ZOWE' --case-sensitive", }, Object { - "description": "Search all of IBMUSER's, data sets for 'ZOWE', and time out in 1 minute", + "description": "Search all of IBMUSER's data sets for 'ZOWE', and time out in 1 minute", "options": "'IBMUSER.*' 'ZOWE' --timeout 60", }, Object { - "description": "Search all of IBMUSER's, data sets for 'ZOWE', and perform 8 parallel tasks", + "description": "Search all of IBMUSER's data sets for 'ZOWE', and perform 8 parallel tasks", "options": "'IBMUSER.*' 'ZOWE' --max-concurrent-requests 8", }, + Object { + "description": "Search all of IBMUSER's data sets using a regular expression", + "options": "'IBMUSER.*' 'Z([A-Za-z]){3}' --regex", + }, ] `; diff --git a/packages/cli/src/zosfiles/-strings-/en.ts b/packages/cli/src/zosfiles/-strings-/en.ts index 5697f44875..c5dbf999d2 100644 --- a/packages/cli/src/zosfiles/-strings-/en.ts +++ b/packages/cli/src/zosfiles/-strings-/en.ts @@ -649,23 +649,28 @@ export default { TIMEOUT: "The number of seconds to search before timing out.", ENCODING: "Search the file content with encoding mode, which means that data conversion is performed using the file encoding " + "specified.", + REGEX: "Whether the search string is a regular expression.", }, EXAMPLES: { EX1: { - DESCRIPTION: "Search all of IBMUSER's, data sets for 'ZOWE'", + DESCRIPTION: "Search all of IBMUSER's data sets for 'ZOWE'", OPTIONS: "'IBMUSER.*' 'ZOWE'" }, EX2: { - DESCRIPTION: "Search all of IBMUSER's, data sets for 'ZOWE' in uppercase only", + DESCRIPTION: "Search all of IBMUSER's data sets for 'ZOWE' in uppercase only", OPTIONS: "'IBMUSER.*' 'ZOWE' --case-sensitive" }, EX3: { - DESCRIPTION: "Search all of IBMUSER's, data sets for 'ZOWE', and time out in 1 minute", + DESCRIPTION: "Search all of IBMUSER's data sets for 'ZOWE', and time out in 1 minute", OPTIONS: "'IBMUSER.*' 'ZOWE' --timeout 60" }, EX4: { - DESCRIPTION: "Search all of IBMUSER's, data sets for 'ZOWE', and perform 8 parallel tasks", + DESCRIPTION: "Search all of IBMUSER's data sets for 'ZOWE', and perform 8 parallel tasks", OPTIONS: "'IBMUSER.*' 'ZOWE' --max-concurrent-requests 8" + }, + EX5: { + DESCRIPTION: "Search all of IBMUSER's data sets using a regular expression", + OPTIONS: "'IBMUSER.*' 'Z([A-Za-z]){3}' --regex" } } } diff --git a/packages/cli/src/zosfiles/search/ds/DataSets.definition.ts b/packages/cli/src/zosfiles/search/ds/DataSets.definition.ts index 437a9df57c..8d2c0cfa7e 100644 --- a/packages/cli/src/zosfiles/search/ds/DataSets.definition.ts +++ b/packages/cli/src/zosfiles/search/ds/DataSets.definition.ts @@ -79,6 +79,13 @@ export const DataSetsDefinition: ICommandDefinition = { aliases: ["to"], description: dataSetStrings.OPTIONS.TIMEOUT, type: "number" + }, + { + name: "regex", + aliases: ["re"], + description: dataSetStrings.OPTIONS.REGEX, + type: "boolean", + defaultValue: false } ], examples: [ @@ -97,6 +104,10 @@ export const DataSetsDefinition: ICommandDefinition = { { description: dataSetStrings.EXAMPLES.EX4.DESCRIPTION, options: dataSetStrings.EXAMPLES.EX4.OPTIONS + }, + { + description: dataSetStrings.EXAMPLES.EX5.DESCRIPTION, + options: dataSetStrings.EXAMPLES.EX5.OPTIONS } ] }; diff --git a/packages/cli/src/zosfiles/search/ds/DataSets.handler.ts b/packages/cli/src/zosfiles/search/ds/DataSets.handler.ts index 205f97ba9e..a73d92f327 100644 --- a/packages/cli/src/zosfiles/search/ds/DataSets.handler.ts +++ b/packages/cli/src/zosfiles/search/ds/DataSets.handler.ts @@ -32,6 +32,7 @@ export default class DataSetsHandler extends ZosFilesBaseHandler { mainframeSearch: commandParameters.arguments.mainframeSearch, maxConcurrentRequests: commandParameters.arguments.maxConcurrentRequests, caseSensitive: commandParameters.arguments.caseSensitive, + regex: commandParameters.arguments.regex, progressTask: task, getOptions: { encoding: commandParameters.arguments.encoding } }; diff --git a/packages/zosfiles/CHANGELOG.md b/packages/zosfiles/CHANGELOG.md index c410662b0b..a8fa7412aa 100644 --- a/packages/zosfiles/CHANGELOG.md +++ b/packages/zosfiles/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to the Zowe z/OS files SDK package will be documented in thi ## Recent Changes - BugFix: Users were not warned when copying partitioned data sets with identical member names. Now, the user is prompted to confirm before continuing the copy operation to avoid potential data loss. [#2349] (https://github.com/zowe/zowe-cli/issues/2349) +- Enhancement: Add the ability to search data sets with regex patterns by passing `--regex` into the search command. [#2432](https://github.com/zowe/zowe-cli/issues/2432) ## `8.13.0` diff --git a/packages/zosfiles/__tests__/__system__/methods/search/Search.system.test.ts b/packages/zosfiles/__tests__/__system__/methods/search/Search.system.test.ts index c4ea458f1f..28cc2ec840 100644 --- a/packages/zosfiles/__tests__/__system__/methods/search/Search.system.test.ts +++ b/packages/zosfiles/__tests__/__system__/methods/search/Search.system.test.ts @@ -302,4 +302,4 @@ describe("Search", () => { expect(response).not.toBeDefined(); }); }); -}); \ No newline at end of file +}); diff --git a/packages/zosfiles/__tests__/__unit__/methods/search/Search.unit.test.ts b/packages/zosfiles/__tests__/__unit__/methods/search/Search.unit.test.ts index 185d0e67d6..5917a156c4 100644 --- a/packages/zosfiles/__tests__/__unit__/methods/search/Search.unit.test.ts +++ b/packages/zosfiles/__tests__/__unit__/methods/search/Search.unit.test.ts @@ -13,7 +13,6 @@ import { ImperativeError, Session, TaskStage } from "@zowe/imperative"; import { Get, IDataSet, ISearchItem, ISearchOptions, IZosFilesResponse, List, Search } from "../../../../src"; describe("Search", () => { - const getDataSetSpy = jest.spyOn(Get, "dataSet"); const dummySession = new Session({ user: "ibmuser", @@ -27,10 +26,12 @@ describe("Search", () => { let testDataString = "THIS DATA SET CONTAINS SOME TESTDATA"; let expectedCol = 29; let expectedLine = 1; + let expectedMatchLengths = [8, 8, 8, 8, 8]; let searchOptions: ISearchOptions = { pattern: "TEST*", searchString: "TESTDATA", + regex: false, caseSensitive: false, getOptions: {}, listOptions: {}, @@ -79,6 +80,7 @@ describe("Search", () => { beforeEach(() => { expectedLine = 1; expectedCol = 29; + expectedMatchLengths = [8, 8, 8, 8, 8]; testDataString = "THIS DATA SET CONTAINS SOME TESTDATA"; getDataSetSpy.mockClear(); @@ -90,6 +92,7 @@ describe("Search", () => { searchOptions = { pattern: "TEST*", searchString: "TESTDATA", + regex: false, caseSensitive: false, getOptions: {}, listOptions: {}, @@ -149,19 +152,21 @@ describe("Search", () => { searchLocalSpy.mockImplementation(async (session, searchOptions: ISearchOptions, searchItems: ISearchItem[]) => { if ((Search as any).timerExpired != true && !(searchOptions.abortSearch && searchOptions.abortSearch())) { const searchItemArray: ISearchItem[] = []; + const matchLengths: number[] = []; for (const searchItem of searchItems) { const localSearchItem: ISearchItem = searchItem; localSearchItem.matchList = [{column: expectedCol, line: expectedLine, contents: testDataString}]; searchItemArray.push(localSearchItem); + matchLengths.push(testDataString.length); } - return {responses: searchItemArray, failures: []}; + return {responses: searchItemArray, failures: [], matchLengths}; } else { const failures: string[] = []; for (const searchItem of searchItems) { if (searchItem.member) { failures.push(searchItem.dsn + "(" + searchItem.member + ")"); } else { failures.push(searchItem.dsn); } } - return {responses: [], failures}; + return {responses: [], failures, matchLengths: []}; } }); listDataSetsMatchingPatternSpy.mockImplementation(async (_session, _patterns, _options) => { @@ -274,12 +279,14 @@ describe("Search", () => { it("Should search for the data sets containing a word and sort out of order responses", async () => { searchLocalSpy.mockImplementation(async (session, searchOptions, searchItems: ISearchItem[]) => { const searchItemArray: ISearchItem[] = []; + const matchLengths: number[] = []; for (const searchItem of searchItems) { const localSearchItem: ISearchItem = searchItem; localSearchItem.matchList = [{column: expectedCol, line: expectedLine, contents: testDataString}]; searchItemArray.unshift(localSearchItem); + matchLengths.push(testDataString.length); } - return {responses: searchItemArray, failures: []}; + return {responses: searchItemArray, failures: [], matchLengths}; }); const response = await Search.dataSets(dummySession, searchOptions); @@ -811,13 +818,15 @@ describe("Search", () => { }; searchLocalSpy.mockImplementation(async (session, searchOptions, searchItems: ISearchItem[]) => { const searchItemArray: ISearchItem[] = []; + const matchLengths: number[] = []; for (const searchItem of searchItems) { const localSearchItem: ISearchItem = searchItem; localSearchItem.matchList = [{column: expectedCol, line: expectedLine, contents: testDataString}]; searchItemArray.push(localSearchItem); + matchLengths.push(testDataString.length); } (Search as any).timerExpired = true; - return {responses: searchItemArray, failures: []}; + return {responses: searchItemArray, failures: [], matchLengths}; }); const response = await Search.dataSets(dummySession, searchOptions); @@ -1128,512 +1137,907 @@ describe("Search", () => { }); describe("searchOnMainframe", () => { - it("Should return a list of members that contain the search term (all)", async () => { - const searchString = searchOptions.searchString.toLowerCase(); - const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); - const queryParams = "?search=" + searchString + "&maxreturnsize=1"; - - expect(getDataSetSpy).toHaveBeenCalledTimes(5); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); - expect(response).toEqual({responses: [ - {dsn: "TEST1.DS", member: undefined, matchList: undefined}, - {dsn: "TEST2.DS", member: undefined, matchList: undefined}, - {dsn: "TEST3.PDS", member: "MEMBER1", matchList: undefined}, - {dsn: "TEST3.PDS", member: "MEMBER2", matchList: undefined}, - {dsn: "TEST3.PDS", member: "MEMBER3", matchList: undefined} - ], failures: []}); - }); + describe("literal string", () => { + it("Should return a list of members that contain the search term (all)", async () => { + const searchString = searchOptions.searchString.toLowerCase(); + const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); + const queryParams = "?search=" + searchString + "&maxreturnsize=1"; + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: undefined}, + {dsn: "TEST2.DS", member: undefined, matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: undefined} + ], failures: []}); + }); - it("Should return a list of members that contain the search term (none)", async () => { - // Return empty buffers for all entries - getDataSetSpy.mockImplementation(async (_session, _dsn, _options) => { - return Buffer.from(""); + it("Should return a list of members that contain the search term (none)", async () => { + // Return empty buffers for all entries + getDataSetSpy.mockImplementation(async (_session, _dsn, _options) => { + return Buffer.from(""); + }); + + const searchString = searchOptions.searchString.toLowerCase(); + const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); + const queryParams = "?search=" + searchString + "&maxreturnsize=1"; + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); + expect(response).toEqual({responses: [], failures: []}); }); - const searchString = searchOptions.searchString.toLowerCase(); - const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); - const queryParams = "?search=" + searchString + "&maxreturnsize=1"; - - expect(getDataSetSpy).toHaveBeenCalledTimes(5); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); - expect(response).toEqual({responses: [], failures: []}); - }); + it("Should return a list of members that contain the search term (some)", async () => { + // Return empty buffers for the final 2 entries + getDataSetSpy.mockImplementation(async (_session, _dsn, _options) => { + return Buffer.from(""); + }).mockImplementationOnce(async (_session, _dsn, _options) => { + return Buffer.from(testDataString); + }).mockImplementationOnce(async (_session, _dsn, _options) => { + return Buffer.from(testDataString); + }).mockImplementationOnce(async (_session, _dsn, _options) => { + return Buffer.from(testDataString); + }); - it("Should return a list of members that contain the search term (some)", async () => { - // Return empty buffers for the final 2 entries - getDataSetSpy.mockImplementation(async (_session, _dsn, _options) => { - return Buffer.from(""); - }).mockImplementationOnce(async (_session, _dsn, _options) => { - return Buffer.from(testDataString); - }).mockImplementationOnce(async (_session, _dsn, _options) => { - return Buffer.from(testDataString); - }).mockImplementationOnce(async (_session, _dsn, _options) => { - return Buffer.from(testDataString); + const searchString = searchOptions.searchString.toLowerCase(); + const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); + const queryParams = "?search=" + searchString + "&maxreturnsize=1"; + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: undefined}, + {dsn: "TEST2.DS", member: undefined, matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: undefined} + ], failures: []}); }); - const searchString = searchOptions.searchString.toLowerCase(); - const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); - const queryParams = "?search=" + searchString + "&maxreturnsize=1"; - - expect(getDataSetSpy).toHaveBeenCalledTimes(5); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); - expect(response).toEqual({responses: [ - {dsn: "TEST1.DS", member: undefined, matchList: undefined}, - {dsn: "TEST2.DS", member: undefined, matchList: undefined}, - {dsn: "TEST3.PDS", member: "MEMBER1", matchList: undefined} - ], failures: []}); - }); + it("Should return failures if the timer expired", async () => { + (Search as any).timerExpired = true; + + const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); - it("Should return failures if the timer expired", async () => { - (Search as any).timerExpired = true; + expect(getDataSetSpy).toHaveBeenCalledTimes(0); + expect(response).toEqual({ + responses: [], + failures: ["TEST1.DS", "TEST2.DS", "TEST3.PDS(MEMBER1)", "TEST3.PDS(MEMBER2)", "TEST3.PDS(MEMBER3)"] + }); + }); - const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); + it("Should return failures if aborted", async () => { + searchOptions.abortSearch = function fakeAbort() { return true; }; - expect(getDataSetSpy).toHaveBeenCalledTimes(0); - expect(response).toEqual({ - responses: [], - failures: ["TEST1.DS", "TEST2.DS", "TEST3.PDS(MEMBER1)", "TEST3.PDS(MEMBER2)", "TEST3.PDS(MEMBER3)"] + const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); + + expect(getDataSetSpy).toHaveBeenCalledTimes(0); + expect(response).toEqual({ + responses: [], + failures: ["TEST1.DS", "TEST2.DS", "TEST3.PDS(MEMBER1)", "TEST3.PDS(MEMBER2)", "TEST3.PDS(MEMBER3)"] + }); + }); + + it("Should handle a data set get failure", async () => { + getDataSetSpy.mockImplementation(async (_session, _dsn, _options) => { + return Buffer.from(testDataString); + }).mockImplementationOnce(async (_session, _dsn, _options) => { + return Buffer.from(testDataString); + }).mockImplementationOnce(async (_session, _dsn, _options) => { + throw new ImperativeError({msg: "Failed to retrieve contents of data set"}); + }); + + const searchString = searchOptions.searchString.toLowerCase(); + const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); + const queryParams = "?search=" + searchString + "&maxreturnsize=1"; + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); + expect(response).toEqual({ + responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: undefined} + ], + failures: ["TEST2.DS"] + }); }); - }); - it("Should return failures if aborted", async () => { - searchOptions.abortSearch = function fakeAbort() { return true; }; + it("Should update the progress task, if present", async () => { + searchOptions.progressTask = { + percentComplete: 0, + statusMessage: "Getting Ready to Start", + stageName: TaskStage.IN_PROGRESS + }; + const searchString = searchOptions.searchString.toLowerCase(); + const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); + const queryParams = "?search=" + searchString + "&maxreturnsize=1"; + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: undefined}, + {dsn: "TEST2.DS", member: undefined, matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: undefined} + ], failures: []}); + expect(searchOptions.progressTask.stageName).toEqual(TaskStage.IN_PROGRESS); - const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); + // Because the 5th entry is the last, there will have been 4 completed tasks + expect(searchOptions.progressTask.statusMessage).toEqual("Initial mainframe search: 4 of 5 entries checked"); + expect(searchOptions.progressTask.percentComplete).toEqual(40); + }); - expect(getDataSetSpy).toHaveBeenCalledTimes(0); - expect(response).toEqual({ - responses: [], - failures: ["TEST1.DS", "TEST2.DS", "TEST3.PDS(MEMBER1)", "TEST3.PDS(MEMBER2)", "TEST3.PDS(MEMBER3)"] + it("Should handle case sensitivity", async () => { + searchOptions.caseSensitive = true; + const searchString = searchOptions.searchString; + const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); + const queryParams = "?search=" + searchString + "&maxreturnsize=1&insensitive=false"; + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: undefined}, + {dsn: "TEST2.DS", member: undefined, matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: undefined} + ], failures: []}); }); - }); - it("Should handle a data set get failure", async () => { - getDataSetSpy.mockImplementation(async (_session, _dsn, _options) => { - return Buffer.from(testDataString); - }).mockImplementationOnce(async (_session, _dsn, _options) => { - return Buffer.from(testDataString); - }).mockImplementationOnce(async (_session, _dsn, _options) => { - throw new ImperativeError({msg: "Failed to retrieve contents of data set"}); + it("Should handle multiple concurrent requests", async () => { + searchOptions.maxConcurrentRequests = 2; + const searchString = searchOptions.searchString.toLowerCase(); + const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); + const queryParams = "?search=" + searchString + "&maxreturnsize=1"; + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: undefined}, + {dsn: "TEST2.DS", member: undefined, matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: undefined} + ], failures: []}); }); - const searchString = searchOptions.searchString.toLowerCase(); - const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); - const queryParams = "?search=" + searchString + "&maxreturnsize=1"; - - expect(getDataSetSpy).toHaveBeenCalledTimes(5); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); - expect(response).toEqual({ - responses: [ + it("Should handle no concurrent requests passed in", async () => { + searchOptions.maxConcurrentRequests = undefined; + const searchString = searchOptions.searchString.toLowerCase(); + const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); + const queryParams = "?search=" + searchString + "&maxreturnsize=1"; + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); + expect(response).toEqual({responses: [ {dsn: "TEST1.DS", member: undefined, matchList: undefined}, + {dsn: "TEST2.DS", member: undefined, matchList: undefined}, {dsn: "TEST3.PDS", member: "MEMBER1", matchList: undefined}, {dsn: "TEST3.PDS", member: "MEMBER2", matchList: undefined}, {dsn: "TEST3.PDS", member: "MEMBER3", matchList: undefined} - ], - failures: ["TEST2.DS"] + ], failures: []}); }); - }); - it("Should update the progress task, if present", async () => { - searchOptions.progressTask = { - percentComplete: 0, - statusMessage: "Getting Ready to Start", - stageName: TaskStage.IN_PROGRESS - }; - const searchString = searchOptions.searchString.toLowerCase(); - const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); - const queryParams = "?search=" + searchString + "&maxreturnsize=1"; - - expect(getDataSetSpy).toHaveBeenCalledTimes(5); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); - expect(response).toEqual({responses: [ - {dsn: "TEST1.DS", member: undefined, matchList: undefined}, - {dsn: "TEST2.DS", member: undefined, matchList: undefined}, - {dsn: "TEST3.PDS", member: "MEMBER1", matchList: undefined}, - {dsn: "TEST3.PDS", member: "MEMBER2", matchList: undefined}, - {dsn: "TEST3.PDS", member: "MEMBER3", matchList: undefined} - ], failures: []}); - expect(searchOptions.progressTask.stageName).toEqual(TaskStage.IN_PROGRESS); - - // Because the 5th entry is the last, there will have been 4 completed tasks - expect(searchOptions.progressTask.statusMessage).toEqual("Initial mainframe search: 4 of 5 entries checked"); - expect(searchOptions.progressTask.percentComplete).toEqual(40); - }); + it("Should handle being passed an empty list of search entries", async () => { + const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, []); - it("Should handle case sensitivity", async () => { - searchOptions.caseSensitive = true; - const searchString = searchOptions.searchString; - const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); - const queryParams = "?search=" + searchString + "&maxreturnsize=1&insensitive=false"; - - expect(getDataSetSpy).toHaveBeenCalledTimes(5); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); - expect(response).toEqual({responses: [ - {dsn: "TEST1.DS", member: undefined, matchList: undefined}, - {dsn: "TEST2.DS", member: undefined, matchList: undefined}, - {dsn: "TEST3.PDS", member: "MEMBER1", matchList: undefined}, - {dsn: "TEST3.PDS", member: "MEMBER2", matchList: undefined}, - {dsn: "TEST3.PDS", member: "MEMBER3", matchList: undefined} - ], failures: []}); + expect(getDataSetSpy).toHaveBeenCalledTimes(0); + expect(response).toEqual({responses: [], failures: []}); + }); }); - it("Should handle multiple concurrent requests", async () => { - searchOptions.maxConcurrentRequests = 2; - const searchString = searchOptions.searchString.toLowerCase(); - const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); - const queryParams = "?search=" + searchString + "&maxreturnsize=1"; - - expect(getDataSetSpy).toHaveBeenCalledTimes(5); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); - expect(response).toEqual({responses: [ - {dsn: "TEST1.DS", member: undefined, matchList: undefined}, - {dsn: "TEST2.DS", member: undefined, matchList: undefined}, - {dsn: "TEST3.PDS", member: "MEMBER1", matchList: undefined}, - {dsn: "TEST3.PDS", member: "MEMBER2", matchList: undefined}, - {dsn: "TEST3.PDS", member: "MEMBER3", matchList: undefined} - ], failures: []}); - }); + describe("regular expression", () => { + beforeEach(() => { + searchOptions.regex = true; + }); - it("Should handle no concurrent requests passed in", async () => { - searchOptions.maxConcurrentRequests = undefined; - const searchString = searchOptions.searchString.toLowerCase(); - const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); - const queryParams = "?search=" + searchString + "&maxreturnsize=1"; - - expect(getDataSetSpy).toHaveBeenCalledTimes(5); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); - expect(response).toEqual({responses: [ - {dsn: "TEST1.DS", member: undefined, matchList: undefined}, - {dsn: "TEST2.DS", member: undefined, matchList: undefined}, - {dsn: "TEST3.PDS", member: "MEMBER1", matchList: undefined}, - {dsn: "TEST3.PDS", member: "MEMBER2", matchList: undefined}, - {dsn: "TEST3.PDS", member: "MEMBER3", matchList: undefined} - ], failures: []}); - }); + it("Should return a list of members that contain the literal search term with regex enabled (all)", async () => { + testDataString = "HELLO WORLD THIS IS A TEST STRING"; + searchOptions.searchString = "TEST"; + const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); + const queryParams = "?research=" + searchOptions.searchString + "&maxreturnsize=1"; + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: undefined}, + {dsn: "TEST2.DS", member: undefined, matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: undefined} + ], failures: []}); + }); + + it("Should return a list of members that contain the multiple words expression search term (all)", async () => { + testDataString = "SIMPLE TEXT WITH MULTIPLE WORDS"; + searchOptions.searchString = "\\bMULTIPLE\\b"; + expectedCol = 18; + const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); + const queryParams = "?research=" + encodeURIComponent(searchOptions.searchString) + "&maxreturnsize=1"; + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: undefined}, + {dsn: "TEST2.DS", member: undefined, matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: undefined} + ], failures: []}); + }); + + it("Should return a list of members that contain matches on the number search term (all)", async () => { + testDataString = "FIND NUMBERS LIKE 1234 AND 567890"; + searchOptions.searchString = "\\d+"; + const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); + const queryParams = "?research=" + encodeURIComponent(searchOptions.searchString) + "&maxreturnsize=1"; + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: undefined}, + {dsn: "TEST2.DS", member: undefined, matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: undefined} + ], failures: []}); + }); + + it("Should return list of members that contain matches on all words beginning with a capital C (regardless of case) (all)", async () => { + testDataString = "CAPITAL LETTERS ARE cool"; + searchOptions.searchString = "\\bC\\w+"; + const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); + const queryParams = "?research=" + encodeURIComponent(searchOptions.searchString) + "&maxreturnsize=1"; + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: undefined}, + {dsn: "TEST2.DS", member: undefined, matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: undefined} + ], failures: []}); + }); + + it("Should return a list of members that contain the literal search term (case sensitive) (all)", async () => { + testDataString = "HELLO WORLD THIS IS A Test STRING"; + searchOptions.searchString = "Test"; + searchOptions.caseSensitive = true; + expectedCol = 23; + const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); + const queryParams = "?research=" + searchOptions.searchString + "&maxreturnsize=1&insensitive=false"; + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: undefined}, + {dsn: "TEST2.DS", member: undefined, matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: undefined} + ], failures: []}); + }); + + it("Should return a list of members that contain matches on the number search term (case sensitive) (all)", async () => { + testDataString = "FIND NUMBERS LIKE 1234 and 567890"; + searchOptions.searchString = "\\d+"; + searchOptions.caseSensitive = true; + const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); + const queryParams = "?research=" + encodeURIComponent(searchOptions.searchString) + "&maxreturnsize=1&insensitive=false"; + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: undefined}, + {dsn: "TEST2.DS", member: undefined, matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: undefined} + ], failures: []}); + }); - it("Should handle being passed an empty list of search entries", async () => { - const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, []); + it("Should return a list of members that contain matches on all words beginning with capital C (case sensitive) (all)", async () => { + testDataString = "CAPITAL LETTERS ARE cool"; + searchOptions.searchString = "\\bC\\w+"; + searchOptions.caseSensitive = true; + expectedCol = 1; + const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, searchItems); + const queryParams = "?research=" + encodeURIComponent(searchOptions.searchString) + "&maxreturnsize=1&insensitive=false"; + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {queryParams}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {queryParams}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: undefined}, + {dsn: "TEST2.DS", member: undefined, matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: undefined}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: undefined} + ], failures: []}); + }); + + it("Should handle being passed an empty list of search entries", async () => { + const response = await (Search as any).searchOnMainframe(dummySession, searchOptions, []); - expect(getDataSetSpy).toHaveBeenCalledTimes(0); - expect(response).toEqual({responses: [], failures: []}); + expect(getDataSetSpy).toHaveBeenCalledTimes(0); + expect(response).toEqual({responses: [], failures: []}); + }); }); }); describe("searchLocal", () => { - it("Should return a list of members that contain the search term (all)", async () => { - const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); - - expect(getDataSetSpy).toHaveBeenCalledTimes(5); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); - expect(response).toEqual({responses: [ - {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST2.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER2", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER3", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} - ], failures: []}); - }); + describe("literal string", () => { + it("Should return a list of members that contain the search term (all)", async () => { + const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST2.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} + ], failures: [], matchLengths: expectedMatchLengths}); + }); - it("Should return a list of members that contain the search term (all) at the beginning", async () => { - expectedCol = 1; - expectedLine = 1; - testDataString = "TESTDATA IS AT THE BEGINNING OF THE STRING"; - const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); - - expect(getDataSetSpy).toHaveBeenCalledTimes(5); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); - expect(response).toEqual({responses: [ - {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST2.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER2", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER3", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} - ], failures: []}); - }); + it("Should return a list of members that contain the search term (all) at the beginning", async () => { + expectedCol = 1; + expectedLine = 1; + testDataString = "TESTDATA IS AT THE BEGINNING OF THE STRING"; + const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST2.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} + ], failures: [], matchLengths: expectedMatchLengths}); + }); - it("Should return a list of members that contain the search term (none)", async () => { - // Return non-matching buffers for all entries - getDataSetSpy.mockImplementation(async (_session, _dsn, _options) => { - return Buffer.from("This data set does not contain any test data."); + it("Should return a list of members that contain the search term (none)", async () => { + // Return non-matching buffers for all entries + getDataSetSpy.mockImplementation(async (_session, _dsn, _options) => { + return Buffer.from("This data set does not contain any test data."); + }); + + const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); + expect(response).toEqual({responses: [], failures: [], matchLengths: []}); }); - const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + it("Should return a list of members that contain the search term (some)", async () => { + expectedMatchLengths = [8, 8, 8]; + + // Return empty buffers for the final 2 entries + getDataSetSpy.mockImplementation(async (_session, _dsn, _options) => { + return Buffer.from(""); + }).mockImplementationOnce(async (_session, _dsn, _options) => { + return Buffer.from(testDataString); + }).mockImplementationOnce(async (_session, _dsn, _options) => { + return Buffer.from(testDataString); + }).mockImplementationOnce(async (_session, _dsn, _options) => { + return Buffer.from(testDataString); + }); - expect(getDataSetSpy).toHaveBeenCalledTimes(5); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); - expect(response).toEqual({responses: [], failures: []}); - }); + const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); - it("Should return a list of members that contain the search term (some)", async () => { - // Return empty buffers for the final 2 entries - getDataSetSpy.mockImplementation(async (_session, _dsn, _options) => { - return Buffer.from(""); - }).mockImplementationOnce(async (_session, _dsn, _options) => { - return Buffer.from(testDataString); - }).mockImplementationOnce(async (_session, _dsn, _options) => { - return Buffer.from(testDataString); - }).mockImplementationOnce(async (_session, _dsn, _options) => { - return Buffer.from(testDataString); + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST2.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} + ], failures: [], matchLengths: expectedMatchLengths}); }); - const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + it("Should return failures if the timer expired", async () => { + (Search as any).timerExpired = true; - expect(getDataSetSpy).toHaveBeenCalledTimes(5); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); - expect(response).toEqual({responses: [ - {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST2.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} - ], failures: []}); - }); + const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); - it("Should return failures if the timer expired", async () => { - (Search as any).timerExpired = true; + expect(getDataSetSpy).toHaveBeenCalledTimes(0); + expect(response).toEqual({ + responses: [], + failures: ["TEST1.DS", "TEST2.DS", "TEST3.PDS(MEMBER1)", "TEST3.PDS(MEMBER2)", "TEST3.PDS(MEMBER3)"], + matchLengths: [] + }); + }); - const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + it("Should return failures if aborted", async () => { + searchOptions.abortSearch = function fakeAbort() { return true; }; - expect(getDataSetSpy).toHaveBeenCalledTimes(0); - expect(response).toEqual({ - responses: [], - failures: ["TEST1.DS", "TEST2.DS", "TEST3.PDS(MEMBER1)", "TEST3.PDS(MEMBER2)", "TEST3.PDS(MEMBER3)"] + const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + + expect(getDataSetSpy).toHaveBeenCalledTimes(0); + expect(response).toEqual({ + responses: [], + failures: ["TEST1.DS", "TEST2.DS", "TEST3.PDS(MEMBER1)", "TEST3.PDS(MEMBER2)", "TEST3.PDS(MEMBER3)"], + matchLengths: [] + }); }); - }); - it("Should return failures if aborted", async () => { - searchOptions.abortSearch = function fakeAbort() { return true; }; + it("Should handle a data set get failure", async () => { + expectedMatchLengths = [8, 8, 8, 8]; - const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + getDataSetSpy.mockImplementation(async (_session, _dsn, _options) => { + return Buffer.from(testDataString); + }).mockImplementationOnce(async (_session, _dsn, _options) => { + return Buffer.from(testDataString); + }).mockImplementationOnce(async (_session, _dsn, _options) => { + throw new ImperativeError({msg: "Failed to retrieve contents of data set"}); + }); - expect(getDataSetSpy).toHaveBeenCalledTimes(0); - expect(response).toEqual({ - responses: [], - failures: ["TEST1.DS", "TEST2.DS", "TEST3.PDS(MEMBER1)", "TEST3.PDS(MEMBER2)", "TEST3.PDS(MEMBER3)"] + const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); + expect(response).toEqual({ + responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} + ], + failures: ["TEST2.DS"], + matchLengths: expectedMatchLengths + }); }); - }); - it("Should handle a data set get failure", async () => { - getDataSetSpy.mockImplementation(async (_session, _dsn, _options) => { - return Buffer.from(testDataString); - }).mockImplementationOnce(async (_session, _dsn, _options) => { - return Buffer.from(testDataString); - }).mockImplementationOnce(async (_session, _dsn, _options) => { - throw new ImperativeError({msg: "Failed to retrieve contents of data set"}); + it("Should update the progress task, if present 1", async () => { + searchOptions.progressTask = { + percentComplete: 0, + statusMessage: "Getting Ready to Start", + stageName: TaskStage.IN_PROGRESS + }; + searchOptions.mainframeSearch = false; + const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST2.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} + ], failures: [], matchLengths: expectedMatchLengths}); + expect(searchOptions.progressTask.stageName).toEqual(TaskStage.IN_PROGRESS); + + // Because the 5th entry is the last, there will have been 4 completed tasks + expect(searchOptions.progressTask.statusMessage).toEqual("Performing search: 4 of 5 entries checked"); + expect(searchOptions.progressTask.percentComplete).toEqual(80); + }); + + it("Should update the progress task, if present 2", async () => { + searchOptions.progressTask = { + percentComplete: 40, + statusMessage: "Initial mainframe search: 4 of 5 entries checked", + stageName: TaskStage.IN_PROGRESS + }; + const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST2.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} + ], failures: [], matchLengths: expectedMatchLengths}); + expect(searchOptions.progressTask.stageName).toEqual(TaskStage.IN_PROGRESS); + + // Because the 5th entry is the last, there will have been 4 completed tasks + expect(searchOptions.progressTask.statusMessage).toEqual("Performing search: 4 of 5 entries checked"); + expect(searchOptions.progressTask.percentComplete).toEqual(90); }); - const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + it("Should handle case sensitivity 1", async () => { + expectedMatchLengths = [8, 8, 8]; + searchOptions.caseSensitive = true; + // Return empty buffers for the final 2 entries + getDataSetSpy.mockImplementation(async (_session, _dsn, _options) => { + return Buffer.from(testDataString.toLowerCase()); + }).mockImplementationOnce(async (_session, _dsn, _options) => { + return Buffer.from(testDataString); + }).mockImplementationOnce(async (_session, _dsn, _options) => { + return Buffer.from(testDataString); + }).mockImplementationOnce(async (_session, _dsn, _options) => { + return Buffer.from(testDataString); + }); + + const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST2.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} + ], failures: [], matchLengths: expectedMatchLengths}); + }); - expect(getDataSetSpy).toHaveBeenCalledTimes(5); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); - expect(response).toEqual({ - responses: [ + it("Should handle case sensitivity 2", async () => { + searchOptions.caseSensitive = true; + const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); + expect(response).toEqual({responses: [ {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST2.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, {dsn: "TEST3.PDS", member: "MEMBER2", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, {dsn: "TEST3.PDS", member: "MEMBER3", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} - ], - failures: ["TEST2.DS"] + ], failures: [], matchLengths: expectedMatchLengths}); }); - }); - it("Should update the progress task, if present 1", async () => { - searchOptions.progressTask = { - percentComplete: 0, - statusMessage: "Getting Ready to Start", - stageName: TaskStage.IN_PROGRESS - }; - searchOptions.mainframeSearch = false; - const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); - - expect(getDataSetSpy).toHaveBeenCalledTimes(5); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); - expect(response).toEqual({responses: [ - {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST2.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER2", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER3", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} - ], failures: []}); - expect(searchOptions.progressTask.stageName).toEqual(TaskStage.IN_PROGRESS); + it("Should handle multiple concurrent requests", async () => { + searchOptions.maxConcurrentRequests = 2; + const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST2.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} + ], failures: [], matchLengths: expectedMatchLengths}); + }); - // Because the 5th entry is the last, there will have been 4 completed tasks - expect(searchOptions.progressTask.statusMessage).toEqual("Performing search: 4 of 5 entries checked"); - expect(searchOptions.progressTask.percentComplete).toEqual(80); - }); + it("Should handle no concurrent requests passed in", async () => { + searchOptions.maxConcurrentRequests = undefined; + const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST2.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} + ], failures: [], matchLengths: expectedMatchLengths}); + }); - it("Should update the progress task, if present 2", async () => { - searchOptions.progressTask = { - percentComplete: 40, - statusMessage: "Initial mainframe search: 4 of 5 entries checked", - stageName: TaskStage.IN_PROGRESS - }; - const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); - - expect(getDataSetSpy).toHaveBeenCalledTimes(5); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); - expect(response).toEqual({responses: [ - {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST2.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER2", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER3", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} - ], failures: []}); - expect(searchOptions.progressTask.stageName).toEqual(TaskStage.IN_PROGRESS); + it("Should handle being passed an empty list of search entries", async () => { + const response = await (Search as any).searchLocal(dummySession, searchOptions, []); - // Because the 5th entry is the last, there will have been 4 completed tasks - expect(searchOptions.progressTask.statusMessage).toEqual("Performing search: 4 of 5 entries checked"); - expect(searchOptions.progressTask.percentComplete).toEqual(90); + expect(getDataSetSpy).toHaveBeenCalledTimes(0); + expect(response).toEqual({responses: [], failures: [], matchLengths: []}); + }); }); - it("Should handle case sensitivity 1", async () => { - searchOptions.caseSensitive = true; - // Return empty buffers for the final 2 entries - getDataSetSpy.mockImplementation(async (_session, _dsn, _options) => { - return Buffer.from(testDataString.toLowerCase()); - }).mockImplementationOnce(async (_session, _dsn, _options) => { - return Buffer.from(testDataString); - }).mockImplementationOnce(async (_session, _dsn, _options) => { - return Buffer.from(testDataString); - }).mockImplementationOnce(async (_session, _dsn, _options) => { - return Buffer.from(testDataString); + describe("regular expression", () => { + // This is required when the regex matches on several columns on the same line + let expectedCols: number[]; + + beforeEach(() => { + searchOptions.regex = true; }); - const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + it("Should return a list of members that contain the literal search term with regex enabled (all)", async () => { + testDataString = "HELLO WORLD THIS IS A TEST STRING"; + searchOptions.searchString = "TEST"; + expectedCol = 23; + expectedMatchLengths = [4, 4, 4, 4, 4]; + const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST2.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} + ], failures: [], matchLengths: expectedMatchLengths}); + }); - expect(getDataSetSpy).toHaveBeenCalledTimes(5); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); - expect(response).toEqual({responses: [ - {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST2.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} - ], failures: []}); - }); + it("Should return a list of members that contain the multiple words expression search term (all)", async () => { + testDataString = "SIMPLE TEXT WITH MULTIPLE WORDS"; + searchOptions.searchString = "\\bMULTIPLE\\b"; + expectedCol = 18; + const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST2.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} + ], failures: [], matchLengths: expectedMatchLengths}); + }); - it("Should handle case sensitivity 2", async () => { - searchOptions.caseSensitive = true; - const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); - - expect(getDataSetSpy).toHaveBeenCalledTimes(5); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); - expect(response).toEqual({responses: [ - {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST2.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER2", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER3", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} - ], failures: []}); - }); + it("Should return a list of members that contain matches on the number search term (all)", async () => { + testDataString = "FIND NUMBERS LIKE 1234 AND 567890"; + searchOptions.searchString = "\\d+"; + expectedCols = [19, 28]; + expectedMatchLengths = [4, 6, 4, 6, 4, 6, 4, 6, 4, 6]; + const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: [ + {column: expectedCols[0], line: expectedLine, contents: testDataString}, + {column: expectedCols[1], line: expectedLine, contents: testDataString} + ]}, + {dsn: "TEST2.DS", member: undefined, matchList: [ + {column: expectedCols[0], line: expectedLine, contents: testDataString}, + {column: expectedCols[1], line: expectedLine, contents: testDataString} + ]}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [ + {column: expectedCols[0], line: expectedLine, contents: testDataString}, + {column: expectedCols[1], line: expectedLine, contents: testDataString} + ]}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: [ + {column: expectedCols[0], line: expectedLine, contents: testDataString}, + {column: expectedCols[1], line: expectedLine, contents: testDataString} + ]}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: [ + {column: expectedCols[0], line: expectedLine, contents: testDataString}, + {column: expectedCols[1], line: expectedLine, contents: testDataString} + ]} + ], failures: [], matchLengths: expectedMatchLengths}); + }); - it("Should handle multiple concurrent requests", async () => { - searchOptions.maxConcurrentRequests = 2; - const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); - - expect(getDataSetSpy).toHaveBeenCalledTimes(5); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); - expect(response).toEqual({responses: [ - {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST2.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER2", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER3", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} - ], failures: []}); - }); + it("Should return list of members that contain matches on all words beginning with a capital C (regardless of case) (all)", async () => { + testDataString = "CAPITAL LETTERS ARE cool"; + searchOptions.searchString = "\\bC\\w+"; + expectedCols = [1, 21]; + expectedMatchLengths = [7, 4, 7, 4, 7, 4, 7, 4, 7, 4]; + const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: [ + {column: expectedCols[0], line: expectedLine, contents: testDataString}, + {column: expectedCols[1], line: expectedLine, contents: testDataString} + ]}, + {dsn: "TEST2.DS", member: undefined, matchList: [ + {column: expectedCols[0], line: expectedLine, contents: testDataString}, + {column: expectedCols[1], line: expectedLine, contents: testDataString} + ]}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [ + {column: expectedCols[0], line: expectedLine, contents: testDataString}, + {column: expectedCols[1], line: expectedLine, contents: testDataString} + ]}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: [ + {column: expectedCols[0], line: expectedLine, contents: testDataString}, + {column: expectedCols[1], line: expectedLine, contents: testDataString} + ]}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: [ + {column: expectedCols[0], line: expectedLine, contents: testDataString}, + {column: expectedCols[1], line: expectedLine, contents: testDataString} + ]} + ], failures: [], matchLengths: expectedMatchLengths}); + }); - it("Should handle no concurrent requests passed in", async () => { - searchOptions.maxConcurrentRequests = undefined; - const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); - - expect(getDataSetSpy).toHaveBeenCalledTimes(5); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); - expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); - expect(response).toEqual({responses: [ - {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST2.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER2", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, - {dsn: "TEST3.PDS", member: "MEMBER3", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} - ], failures: []}); - }); + it("Should return a list of members that contain the literal search term (case sensitive) (all)", async () => { + testDataString = "HELLO WORLD THIS IS A Test STRING"; + searchOptions.searchString = "Test"; + searchOptions.caseSensitive = true; + expectedCol = 23; + expectedMatchLengths = [4, 4, 4, 4, 4]; + const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST2.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} + ], failures: [], matchLengths: expectedMatchLengths}); + }); - it("Should handle being passed an empty list of search entries", async () => { - const response = await (Search as any).searchLocal(dummySession, searchOptions, []); + it("Should return a list of members that contain matches on the number search term (case sensitive) (all)", async () => { + testDataString = "FIND NUMBERS LIKE 1234 and 567890"; + searchOptions.searchString = "\\d+"; + searchOptions.caseSensitive = true; + expectedCols = [19, 28]; + expectedMatchLengths = [4, 6, 4, 6, 4, 6, 4, 6, 4, 6]; + const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: [ + {column: expectedCols[0], line: expectedLine, contents: testDataString}, + {column: expectedCols[1], line: expectedLine, contents: testDataString} + ]}, + {dsn: "TEST2.DS", member: undefined, matchList: [ + {column: expectedCols[0], line: expectedLine, contents: testDataString}, + {column: expectedCols[1], line: expectedLine, contents: testDataString} + ]}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [ + {column: expectedCols[0], line: expectedLine, contents: testDataString}, + {column: expectedCols[1], line: expectedLine, contents: testDataString} + ]}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: [ + {column: expectedCols[0], line: expectedLine, contents: testDataString}, + {column: expectedCols[1], line: expectedLine, contents: testDataString} + ]}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: [ + {column: expectedCols[0], line: expectedLine, contents: testDataString}, + {column: expectedCols[1], line: expectedLine, contents: testDataString} + ]} + ], failures: [], matchLengths: expectedMatchLengths}); + }); - expect(getDataSetSpy).toHaveBeenCalledTimes(0); - expect(response).toEqual({responses: [], failures: []}); + it("Should return a list of members that contain matches on all words beginning with capital C (case sensitive) (all)", async () => { + testDataString = "CAPITAL LETTERS ARE cool"; + searchOptions.searchString = "\\bC\\w+"; + searchOptions.caseSensitive = true; + expectedCol = 1; + expectedMatchLengths = [7, 7, 7, 7, 7]; + const response = await (Search as any).searchLocal(dummySession, searchOptions, searchItems); + + expect(getDataSetSpy).toHaveBeenCalledTimes(5); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST1.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST2.DS", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER1)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER2)", {}); + expect(getDataSetSpy).toHaveBeenCalledWith(dummySession, "TEST3.PDS(MEMBER3)", {}); + expect(response).toEqual({responses: [ + {dsn: "TEST1.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST2.DS", member: undefined, matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER1", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER2", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]}, + {dsn: "TEST3.PDS", member: "MEMBER3", matchList: [{column: expectedCol, line: expectedLine, contents: testDataString}]} + ], failures: [], matchLengths: expectedMatchLengths}); + }); + + it("Should handle being passed an empty list of search entries", async () => { + const response = await (Search as any).searchLocal(dummySession, searchOptions, []); + + expect(getDataSetSpy).toHaveBeenCalledTimes(0); + expect(response).toEqual({responses: [], failures: [], matchLengths: []}); + }); }); }); -}); \ No newline at end of file +}); diff --git a/packages/zosfiles/src/methods/search/Search.ts b/packages/zosfiles/src/methods/search/Search.ts index f1431e77f4..2ac0034b12 100644 --- a/packages/zosfiles/src/methods/search/Search.ts +++ b/packages/zosfiles/src/methods/search/Search.ts @@ -24,7 +24,8 @@ import { ISearchResponse } from "./doc/ISearchResponse"; // This interface isn't used outside of the private functions, so just keeping it here. interface IInternalSearchResponse { responses: ISearchItem[], - failures: string[] + failures: string[], + matchLengths?: number[] } /** @@ -203,14 +204,15 @@ export class Search { const lineLen = maxLine.toString().length; const colLen = maxCol.toString().length; - const searchLen = searchOptions.searchString.length; - for (const {line, column, contents} of entry.matchList) { + for (const [index, {line, column, contents}] of entry.matchList.entries()) { + const matchLength = response.matchLengths[index] !== undefined ? response.matchLengths[index] : 0; + // eslint-disable-next-line no-control-regex let localContents = contents.replace(/[\u0000-\u001F\u007F-\u009F]/g, "\uFFFD"); const beforeString = chalk.grey(localContents.substring(0, column - 1)); - const selectedString = chalk.white.bold(localContents.substring(column - 1, column - 1 + searchLen)); - const afterString = chalk.grey(localContents.substring(column - 1 + searchLen, localContents.length + 1)); + const selectedString = chalk.white.bold(localContents.substring(column - 1, column - 1 + matchLength)); + const afterString = chalk.grey(localContents.substring(column - 1 + matchLength, localContents.length + 1)); localContents = beforeString + selectedString + afterString; apiResponse.commandResponse += chalk.yellow("Line:") + " " + line.toString().padStart(lineLen) + ", " + chalk.yellow("Column:") + " " + column.toString().padStart(colLen) + ", " + chalk.yellow("Contents:") + @@ -267,7 +269,14 @@ export class Search { } // Set up the query - let queryParams = "?search=" + encodeURIComponent(searchOptions.searchString) + "&maxreturnsize=1"; + let queryParams = ""; + if (searchOptions.regex) { + queryParams = "?research=" + encodeURIComponent(searchOptions.searchString); + } else { + queryParams = "?search=" + encodeURIComponent(searchOptions.searchString); + } + queryParams += "&maxreturnsize=1"; + if (searchOptions.caseSensitive === true) { queryParams += "&insensitive=false"; } let dsn = searchItem.dsn; if (searchItem.member) { dsn += "(" + searchItem.member + ")"; } @@ -313,6 +322,7 @@ export class Search { Promise { const matchedItems: ISearchItem[] = []; const failures: string[] = []; + const matchLengths: number[] = []; const total = searchItems.length; let complete = 0; let searchAborted: boolean = searchOptions.abortSearch?.(); @@ -367,16 +377,26 @@ export class Search { searchLine = line.toLowerCase(); } - if (searchLine.includes(searchOptions.searchString)) { - let lastCol = 0; - let lastColIndexPlusLen = 0; - while (lastCol != -1) { - const column = searchLine.indexOf(searchOptions.searchString, lastColIndexPlusLen); - lastCol = column; - lastColIndexPlusLen = column + searchOptions.searchString.length; - if (column != -1) { - // Append the real line - 1 indexed - indicies.push({line: lineNum + 1, column: column + 1, contents: line}); + if (searchOptions.regex) { + const regex = new RegExp(searchOptions.searchString, searchOptions.caseSensitive ? "g" : "gi"); + const matches = searchLine.matchAll(regex); + for (const match of matches) { + indicies.push({ line: lineNum + 1, column: match.index + 1, contents: line }); + matchLengths.push(match[0].length); + } + } else { + if (searchLine.includes(searchOptions.searchString)) { + let lastCol = 0; + let lastColIndexPlusLen = 0; + while (lastCol != -1) { + const column = searchLine.indexOf(searchOptions.searchString, lastColIndexPlusLen); + lastCol = column; + lastColIndexPlusLen = column + searchOptions.searchString.length; + if (column != -1) { + // Append the real line - 1 indexed + indicies.push({ line: lineNum + 1, column: column + 1, contents: line }); + matchLengths.push(searchOptions.searchString.length); + } } } } @@ -397,6 +417,6 @@ export class Search { } }; await asyncPool(searchOptions.maxConcurrentRequests || 1, searchItems, createFindPromise); - return {responses: matchedItems, failures}; + return { responses: matchedItems, failures, matchLengths }; } } diff --git a/packages/zosfiles/src/methods/search/doc/ISearchOptions.ts b/packages/zosfiles/src/methods/search/doc/ISearchOptions.ts index b554b976f8..0e1d18aa73 100644 --- a/packages/zosfiles/src/methods/search/doc/ISearchOptions.ts +++ b/packages/zosfiles/src/methods/search/doc/ISearchOptions.ts @@ -42,6 +42,9 @@ export interface ISearchOptions { /* The search should be case sensitive */ caseSensitive?: boolean; + /* Whether the search string is a regular expression */ + regex?: boolean; + /* A function that, if provided, is called with a list of data sets and members that are about to be searched. */ /* If true, continue search. If false, terminate search. */ continueSearch?: (dataSets: IDataSet[]) => Promise | boolean; @@ -50,4 +53,4 @@ export interface ISearchOptions { /* If abortSearch returns true, then the search should terminate immediately with the current available results. */ /* This prevents searches from continuing to run in the background in the case that a user wishes to cancel a search (i.e. in VS Code) */ abortSearch?: () => boolean; -} \ No newline at end of file +}