From d8dca552def30908e83087ff750a8f31fd8729c4 Mon Sep 17 00:00:00 2001 From: Pujal Date: Fri, 1 Nov 2024 15:54:10 -0400 Subject: [PATCH 01/27] code updates for #2175 Signed-off-by: Pujal --- ...AllMembersMatching.definition.unit.test.ts | 36 +++++++++ .../AllMembersMatching.handler.unit.test.ts | 0 packages/cli/src/zosfiles/-strings-/en.ts | 20 +++++ .../zosfiles/download/Download.definition.ts | 4 +- .../amm/AllMembersMatching.definition.ts | 70 +++++++++++++++++ .../amm/AllMembersMatching.handler.ts | 78 +++++++++++++++++++ .../zosfiles/src/methods/download/Download.ts | 5 +- .../methods/download/doc/IDownloadOptions.ts | 10 +++ 8 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.definition.unit.test.ts create mode 100644 packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts create mode 100644 packages/cli/src/zosfiles/download/amm/AllMembersMatching.definition.ts create mode 100644 packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts diff --git a/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.definition.unit.test.ts b/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.definition.unit.test.ts new file mode 100644 index 0000000000..0fdc374e92 --- /dev/null +++ b/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.definition.unit.test.ts @@ -0,0 +1,36 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +import { ICommandDefinition } from "@zowe/imperative"; + +describe("zos-files download amm command definition", () => { + it ("should not have changed", () => { + const definition: ICommandDefinition = require("../../../../../src/zosfiles/download/am/AllMembers.definition").AllMembersDefinition; + + expect(definition).toBeDefined(); + + // Should not contain children since this is a command + expect(definition.children).toBeUndefined(); + + // Should require a zosmf profile + expect(definition.profile.optional).toEqual(["zosmf"]); + + // Should only contain one positional + expect(definition.positionals.length).toEqual(1); + + // The positional should be required + expect(definition.positionals[0].required).toBeTruthy(); + + // Should not change + expect(definition.options).toMatchSnapshot(); + expect(definition.examples).toMatchSnapshot(); + }); +}); diff --git a/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts b/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/cli/src/zosfiles/-strings-/en.ts b/packages/cli/src/zosfiles/-strings-/en.ts index 865aa3f9bf..b04d28ea1a 100644 --- a/packages/cli/src/zosfiles/-strings-/en.ts +++ b/packages/cli/src/zosfiles/-strings-/en.ts @@ -338,6 +338,26 @@ export default { EX2: `Download the members of the data set "ibmuser.cntl" in text mode to the directory "jcl/"` } }, + ALL_MEMBERS_MATCHING: { + SUMMARY: "Download all members from a pds", + DESCRIPTION: "Download all members that match a DSLEVEL pattern from a partitioned data set to a local folder.", + POSITIONALS: { + DATASETNAME: "The name of the data set from which you want to download members", + PATTERN: `The pattern or patterns to match data sets against. Also known as 'DSLEVEL'. The following special sequences can be ` + + `used in the pattern: + ${TextUtils.chalk.yellow("%")}: matches any single character + ${TextUtils.chalk.yellow("*")}: matches any number of characters within a data set name qualifier ` + + `(e.g. "ibmuser.j*.old" matches "ibmuser.jcl.old" but not "ibmuser.jcl.very.old") + ${TextUtils.chalk.yellow("**")}: matches any number of characters within any number of data set name qualifiers ` + + `(e.g. "ibmuser.**.old" matches both "ibmuser.jcl.old" and "ibmuser.jcl.very.old") + However, the pattern cannot begin with any of these sequences. You can specify multiple patterns separated by commas, ` + + `for example "ibmuser.**.cntl,ibmuser.**.jcl"` + }, + EXAMPLES: { + EX1: `Download the members of the data set "ibmuser.loadlib" in binary mode to the directory "loadlib/"`, + EX2: `Download the members of the data set "ibmuser.cntl" in text mode to the directory "jcl/"` + } + }, DATA_SET: { SUMMARY: "Download content from a z/OS data set", DESCRIPTION: "Download content from a z/OS data set to a local file.", diff --git a/packages/cli/src/zosfiles/download/Download.definition.ts b/packages/cli/src/zosfiles/download/Download.definition.ts index 0d7ae4d394..cfa748b326 100644 --- a/packages/cli/src/zosfiles/download/Download.definition.ts +++ b/packages/cli/src/zosfiles/download/Download.definition.ts @@ -17,6 +17,7 @@ import i18nTypings from "../-strings-/en"; import { UssFileDefinition } from "./uss/UssFile.definition"; import { DataSetMatchingDefinition } from "./dsm/DataSetMatching.definition"; import { UssDirDefinition } from "./ussdir/UssDir.definition"; +import { AllMembersMatchingDefinition } from "./amm/AllMembersMatching.definition"; // Does not use the import in anticipation of some internationalization work to be done later. const strings = (require("../-strings-/en").default as typeof i18nTypings).DOWNLOAD; @@ -36,6 +37,7 @@ export const DownloadDefinition: ICommandDefinition = { AllMembersDefinition, UssFileDefinition, UssDirDefinition, - DataSetMatchingDefinition + DataSetMatchingDefinition, + AllMembersMatchingDefinition ] }; diff --git a/packages/cli/src/zosfiles/download/amm/AllMembersMatching.definition.ts b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.definition.ts new file mode 100644 index 0000000000..44f923b1dd --- /dev/null +++ b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.definition.ts @@ -0,0 +1,70 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +import { ICommandDefinition } from "@zowe/imperative"; +import { DownloadOptions } from "../Download.options"; +import i18nTypings from "../../-strings-/en"; + +// Does not use the import in anticipation of some internationalization work to be done later. +const strings = (require("../../-strings-/en").default as typeof i18nTypings).DOWNLOAD.ACTIONS.ALL_MEMBERS_MATCHING; + +/** + * Download all members command definition containing its description, examples and/or options + * @type {ICommandDefinition} + */ +export const AllMembersMatchingDefinition: ICommandDefinition = { + name: "all-members-matching", + aliases: ["amm", "all-members-matching"], + summary: strings.SUMMARY, + description: strings.DESCRIPTION, + type: "command", + handler: __dirname + "/AllMembersMatching.handler", + profile: { + optional: ["zosmf"] + }, + positionals: [ + { + name: "dataSetName", + description: strings.POSITIONALS.DATASETNAME, + type: "string", + required: true + }, + { + name: "pattern", + description: strings.POSITIONALS.PATTERN, + type: "string", + required: true + } + ], + options: [ + DownloadOptions.volume, + DownloadOptions.directory, + DownloadOptions.binary, + DownloadOptions.record, + DownloadOptions.encoding, + DownloadOptions.extension, + DownloadOptions.excludePattern, + DownloadOptions.extensionMap, + DownloadOptions.maxConcurrentRequests, + DownloadOptions.preserveOriginalLetterCase, + DownloadOptions.failFast + ].sort((a, b) => a.name.localeCompare(b.name)), + examples: [ + { + description: strings.EXAMPLES.EX1, + options: `"ibmuser.loadlib" -b -d loadlib` + }, + { + description: strings.EXAMPLES.EX2, + options: `"ibmuser.cntl" -d jcl` + } + ] +}; \ No newline at end of file diff --git a/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts new file mode 100644 index 0000000000..a0ed75a337 --- /dev/null +++ b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts @@ -0,0 +1,78 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +import { AbstractSession, IHandlerParameters, ImperativeError, ImperativeExpect, ITaskWithStatus, TaskStage } from "@zowe/imperative"; +import { IZosFilesResponse, Download, IDsmListOptions, List } from "@zowe/zos-files-for-zowe-sdk"; +import { ZosFilesBaseHandler } from "../../ZosFilesBase.handler"; + +/** + * Handler to download all members given a data set name/pattern + * @export + */ +export default class AllMembersMatchingHandler extends ZosFilesBaseHandler { + public async processWithSession(commandParameters: IHandlerParameters, session: AbstractSession): Promise { + const extensionMap: {[key: string]: string} = {}; + try { + if (commandParameters.arguments.extensionMap) { + commandParameters.arguments.extensionMap = commandParameters.arguments.extensionMap.toLowerCase(); + const unoptimizedMap = commandParameters.arguments.extensionMap.split(","); + for (const entry of unoptimizedMap) { + const splitEntry = entry.split("="); + ImperativeExpect.toBeEqual(splitEntry.length, 2); + extensionMap[splitEntry[0]] = splitEntry[1]; + } + } + } catch (err) { + throw new ImperativeError({msg: "An error occurred processing the extension map.", causeErrors: err}); + } + + const listStatus: ITaskWithStatus = { + statusMessage: "Searching for data sets", + percentComplete: 0, + stageName: TaskStage.IN_PROGRESS + }; + const listOptions: IDsmListOptions = { + excludePatterns: commandParameters.arguments.excludePatterns?.split(","), + maxConcurrentRequests: commandParameters.arguments.maxConcurrentRequests, + task: listStatus, + responseTimeout: commandParameters.arguments.responseTimeout + }; + commandParameters.response.progress.startBar({ task: listStatus }); + const response = await List.allMembers(session, commandParameters.arguments.pattern.split(","), listOptions); + commandParameters.response.progress.endBar(); + if (response.success) { + commandParameters.response.console.log(`\r${response.commandResponse}\n`); + } else { + return response; + } + const status: ITaskWithStatus = { + statusMessage: "Downloading all members", + percentComplete: 0, + stageName: TaskStage.IN_PROGRESS + }; + commandParameters.response.progress.startBar({task: status}); + return Download.allMembers(session, commandParameters.arguments.dataSetName, { + volume: commandParameters.arguments.volumeSerial, + binary: commandParameters.arguments.binary, + record: commandParameters.arguments.record, + encoding: commandParameters.arguments.encoding, + directory: commandParameters.arguments.directory, + extension: commandParameters.arguments.extension, + extensionMap: commandParameters.arguments.extensionMap ? extensionMap : undefined, + maxConcurrentRequests: commandParameters.arguments.maxConcurrentRequests, + preserveOriginalLetterCase: commandParameters.arguments.preserveOriginalLetterCase, + failFast: commandParameters.arguments.failFast, + task: status, + responseTimeout: commandParameters.arguments.responseTimeout + }); + } +} + diff --git a/packages/zosfiles/src/methods/download/Download.ts b/packages/zosfiles/src/methods/download/Download.ts index 5944f6b6ee..2b6080355d 100644 --- a/packages/zosfiles/src/methods/download/Download.ts +++ b/packages/zosfiles/src/methods/download/Download.ts @@ -208,7 +208,7 @@ export class Download { try { const response = await List.allMembers(session, dataSetName, { volume: options.volume, - responseTimeout: options.responseTimeout + responseTimeout: options.responseTimeout, }); const memberList: Array<{ member: string }> = response.apiResponse.items; @@ -266,7 +266,8 @@ export class Download { binary: options.binary, record: options.record, encoding: options.encoding, - responseTimeout: options.responseTimeout + responseTimeout: options.responseTimeout, + pattern: options.pattern }).catch((err) => { downloadErrors.push(err); failedMembers.push(fileName); diff --git a/packages/zosfiles/src/methods/download/doc/IDownloadOptions.ts b/packages/zosfiles/src/methods/download/doc/IDownloadOptions.ts index 103c496d2b..177a8996ba 100644 --- a/packages/zosfiles/src/methods/download/doc/IDownloadOptions.ts +++ b/packages/zosfiles/src/methods/download/doc/IDownloadOptions.ts @@ -61,6 +61,11 @@ export interface IDownloadSingleOptions extends IGetOptions { * Optional stream to read the file contents */ stream?: Writable; + + /** + * An optional pattern for restricting the response list + */ + pattern?: string; } /** @@ -99,4 +104,9 @@ export interface IDownloadOptions extends Omit * Specifies whether hidden files whose names begin with a dot should be downloaded. */ includeHidden?: boolean; + + /** + * An optional pattern for restricting the response list + */ + pattern?: string; } From dcfb5b9ce088f0b4d7997c69ce6c1c5622e94401 Mon Sep 17 00:00:00 2001 From: Pujal Date: Mon, 4 Nov 2024 10:58:26 -0500 Subject: [PATCH 02/27] AllMembersMatching unit tests Signed-off-by: Pujal --- .../amm/AllMembersMatching.handler.unit.test.ts | 11 +++++++++++ packages/zosfiles/src/methods/download/Download.ts | 1 - packages/zosfiles/src/methods/list/List.ts | 1 - 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts b/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts index e69de29bb2..aacc683c4c 100644 --- a/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts +++ b/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts @@ -0,0 +1,11 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + diff --git a/packages/zosfiles/src/methods/download/Download.ts b/packages/zosfiles/src/methods/download/Download.ts index 2b6080355d..d61bf5997f 100644 --- a/packages/zosfiles/src/methods/download/Download.ts +++ b/packages/zosfiles/src/methods/download/Download.ts @@ -267,7 +267,6 @@ export class Download { record: options.record, encoding: options.encoding, responseTimeout: options.responseTimeout, - pattern: options.pattern }).catch((err) => { downloadErrors.push(err); failedMembers.push(fileName); diff --git a/packages/zosfiles/src/methods/list/List.ts b/packages/zosfiles/src/methods/list/List.ts index f7dd71e1ed..e1c243150e 100644 --- a/packages/zosfiles/src/methods/list/List.ts +++ b/packages/zosfiles/src/methods/list/List.ts @@ -34,7 +34,6 @@ export class List { * @param {AbstractSession} session - z/OS MF connection info * @param {string} dataSetName - contains the data set name * @param {IListOptions} [options={}] - contains the options to be sent - * * @returns {Promise} A response indicating the outcome of the API * * @throws {ImperativeError} data set name must be set From 780659431267b4989d65f3e4ac625ba20c859d51 Mon Sep 17 00:00:00 2001 From: Pujal Date: Tue, 5 Nov 2024 16:04:30 -0500 Subject: [PATCH 03/27] updated membersMatchingPattern method Signed-off-by: Pujal --- .../amm/AllMembersMatching.handler.ts | 9 +- .../__unit__/methods/list/List.unit.test.ts | 60 +++++++++++- .../src/constants/ZosFiles.messages.ts | 24 +++++ packages/zosfiles/src/methods/list/List.ts | 94 +++++++++++++++++++ .../src/methods/list/doc/IListOptions.ts | 2 +- .../methods/list/doc/IZosmfListResponse.ts | 5 + 6 files changed, 188 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts index a0ed75a337..8f78b60fdc 100644 --- a/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts +++ b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts @@ -14,7 +14,7 @@ import { IZosFilesResponse, Download, IDsmListOptions, List } from "@zowe/zos-fi import { ZosFilesBaseHandler } from "../../ZosFilesBase.handler"; /** - * Handler to download all members given a data set name/pattern + * Handler to download all members given a data set name & pattern * @export */ export default class AllMembersMatchingHandler extends ZosFilesBaseHandler { @@ -35,7 +35,7 @@ export default class AllMembersMatchingHandler extends ZosFilesBaseHandler { } const listStatus: ITaskWithStatus = { - statusMessage: "Searching for data sets", + statusMessage: "Searching for members", percentComplete: 0, stageName: TaskStage.IN_PROGRESS }; @@ -46,7 +46,8 @@ export default class AllMembersMatchingHandler extends ZosFilesBaseHandler { responseTimeout: commandParameters.arguments.responseTimeout }; commandParameters.response.progress.startBar({ task: listStatus }); - const response = await List.allMembers(session, commandParameters.arguments.pattern.split(","), listOptions); + const response = await List.membersMatchingPattern(session, commandParameters.arguments.dataSetName, + commandParameters.arguments.pattern.split(","), listOptions); commandParameters.response.progress.endBar(); if (response.success) { commandParameters.response.console.log(`\r${response.commandResponse}\n`); @@ -71,7 +72,7 @@ export default class AllMembersMatchingHandler extends ZosFilesBaseHandler { preserveOriginalLetterCase: commandParameters.arguments.preserveOriginalLetterCase, failFast: commandParameters.arguments.failFast, task: status, - responseTimeout: commandParameters.arguments.responseTimeout + responseTimeout: commandParameters.arguments.responseTimeout, }); } } diff --git a/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts b/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts index b5e8434626..217d4a934a 100644 --- a/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts +++ b/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts @@ -9,7 +9,7 @@ * */ -import { ImperativeError, Logger, Session } from "@zowe/imperative"; +import { apiErrorHeader, ImperativeError, Logger, Session } from "@zowe/imperative"; import { ZosmfRestClient, ZosmfHeaders } from "@zowe/core-for-zowe-sdk"; import { List } from "../../../../src/methods/list/List"; import { ZosFilesMessages } from "../../../../src/constants/ZosFiles.messages"; @@ -1529,4 +1529,62 @@ describe("z/OS Files - List", () => { }); }); }); + describe("membersMatchingPattern", () => { + const listDataSetSpy = jest.spyOn(List, "allMembers"); + + const memberData1 = { + dsname: "TEST.PS", + memberName: "M1", + dsorg: "PS" + }; + + const memberData2 = { + dsname: "TEST.PS", + memberName: "M2", + dsorg: "PS" + }; + + // const memberData3 = { + // dsname: "TEST.PS", + // memberName: "testM1", + // dsorg: "PS" + // }; + + beforeEach(() => { + listDataSetSpy.mockClear(); + listDataSetSpy.mockResolvedValue({} as any); + }); + + it("should successfully list M1 & M2 using the List.allMembers API", async () => { + const dsname = "TEST.PS" + const pattern = "M*"; + let response; + let caughtError; + + listDataSetSpy.mockImplementation(async (): Promise => { + return { + apiResponse: { + items: [memberData1, memberData2] + } + }; + }); + + try { + response = await List.membersMatchingPattern(dummySession, dsname, [pattern]); + console.log(response.apiResponse) + } catch (e) { + caughtError = e; + } + + // expect(caughtError).toBeUndefined(); + expect(response).toEqual({ + success: true, + commandResponse: util.format(ZosFilesMessages.membersMatchedPattern.message, 2), + apiResponse: [memberData1, memberData2] + }); + + expect(listDataSetSpy).toHaveBeenCalledTimes(1); + expect(listDataSetSpy).toHaveBeenCalledWith(dummySession, dsname, {attributes: true}); + }); + }); }); diff --git a/packages/zosfiles/src/constants/ZosFiles.messages.ts b/packages/zosfiles/src/constants/ZosFiles.messages.ts index 7c58177267..eeea09b5ba 100644 --- a/packages/zosfiles/src/constants/ZosFiles.messages.ts +++ b/packages/zosfiles/src/constants/ZosFiles.messages.ts @@ -215,6 +215,14 @@ export const ZosFilesMessages: { [key: string]: IMessageDefinition } = { message: "%d data set(s) were found matching pattern." }, + /** + * Message indicating that the data sets matching pattern were listed successfully + * @type {IMessageDefinition} + */ + membersMatchedPattern: { + message: "%d members(s) were found matching pattern." + }, + /** * Message indicating that file is uploaded to data set successfully * @type {IMessageDefinition} @@ -407,6 +415,14 @@ export const ZosFilesMessages: { [key: string]: IMessageDefinition } = { message: "There are no data sets that match the provided pattern(s)." }, + /** + * Message indicating that no members xsremain to be downloaded after the excluded ones were filtered out. + * @type {IMessageDefinition} + */ + noMembersMatchingPattern: { + message: "There are no members that match the provided pattern(s)." + }, + /** * Message indicating that no data sets remain to be downloaded after the excluded ones were filtered out. * @type {IMessageDefinition} @@ -415,6 +431,14 @@ export const ZosFilesMessages: { [key: string]: IMessageDefinition } = { message: "No data sets left after excluded pattern(s) were filtered out." }, + /** + * Message indicating that no data sets remain to be downloaded after the excluded ones were filtered out. + * @type {IMessageDefinition} + */ + noMembersInList: { + message: "No members left after excluded pattern(s) were filtered out." + }, + /** * Message indicating that some or all data sets failed to download * @type {IMessageDefinition} diff --git a/packages/zosfiles/src/methods/list/List.ts b/packages/zosfiles/src/methods/list/List.ts index e1c243150e..8e106e9f33 100644 --- a/packages/zosfiles/src/methods/list/List.ts +++ b/packages/zosfiles/src/methods/list/List.ts @@ -104,6 +104,100 @@ export class List { } } + /** + * List data sets that match a DSLEVEL pattern + * @param {AbstractSession} session z/OSMF connection info + * @param {string[]} patterns Data set patterns to include + * @param {IDsmListOptions} options Contains options for the z/OSMF request + * @returns {Promise} List of z/OSMF list responses for each data set + * + * @example + */ + public static async membersMatchingPattern(session: AbstractSession, dataSetName: string, patterns: string[], + options: IDsmListOptions = {}): Promise { + + ImperativeExpect.toNotBeNullOrUndefined(dataSetName, ZosFilesMessages.missingDatasetName.message); + ImperativeExpect.toNotBeEqual(dataSetName, "", ZosFilesMessages.missingDatasetName.message); + ImperativeExpect.toNotBeNullOrUndefined(patterns, ZosFilesMessages.missingPatterns.message); + patterns = patterns.filter(Boolean); + ImperativeExpect.toNotBeEqual(patterns.length, 0, ZosFilesMessages.missingPatterns.message); + const zosmfResponses: IZosFilesResponse[] = []; + + for(const pattern of patterns) { + let response: any; + try { + response = await List.allMembers(session, dataSetName, { attributes: true}); + console.log(response.apiResponse); + + } catch(err) { + if(!(err instanceof ImperativeError && err.errorCode?.toString().startsWith("5"))) { + throw err; + } + + response = await List.allMembers(session, dataSetName); + + let listsInitiated = 0; + const createListPromise = (membersObj: any) => { + if (options.task != null) { + options.task.percentComplete = Math.floor(TaskProgress.ONE_HUNDRED_PERCENT * + (listsInitiated / response.apiResponse.items.length)); + listsInitiated++; + } + + return List.allMembers(session, membersObj.dsname, { attributes: true, maxLength: 1 }).then( + (tempResponse) => { + Object.assign(membersObj, tempResponse.apiResponse.items[0]); + }, + (tempErr) => { + Object.assign(membersObj, { error: tempErr }); + } + ); + }; + + const maxConcurrentRequests = options.maxConcurrentRequests == null ? 1 : options.maxConcurrentRequests; + if (maxConcurrentRequests === 0) { + await Promise.all(response.apiResponse.items.map(createListPromise)); + } else { + await asyncPool(maxConcurrentRequests, response.apiResponse.items, createListPromise); + } + } + } + // Check if members matching pattern found + if (zosmfResponses.length === 0) { + return { + success: false, + commandResponse: ZosFilesMessages.noMembersMatchingPattern.message, + apiResponse: [] + }; + } + + // Exclude names of members + for (const pattern of options.excludePatterns || []) { + const response = await List.allMembers(session, dataSetName); + console.log(response.apiResponse); + response.apiResponse.items.forEach((membersObj: IZosmfListResponse) => { + const responseIndex = zosmfResponses.findIndex(response => response.apiResponse.memberName === membersObj.memberName); + if (responseIndex !== -1) { + zosmfResponses.splice(responseIndex, 1); + } + }); + } + + // Check if exclude pattern has left any members in the list + if (zosmfResponses.length === 0) { + return { + success: false, + commandResponse: ZosFilesMessages.noMembersInList.message, + apiResponse: [] + }; + } + + return { + success: true, + commandResponse: util.format(ZosFilesMessages.membersMatchedPattern.message, zosmfResponses.length), + apiResponse: zosmfResponses + }; + } /** * Retrieve all members from a data set name * diff --git a/packages/zosfiles/src/methods/list/doc/IListOptions.ts b/packages/zosfiles/src/methods/list/doc/IListOptions.ts index ba70889540..1947e0e773 100644 --- a/packages/zosfiles/src/methods/list/doc/IListOptions.ts +++ b/packages/zosfiles/src/methods/list/doc/IListOptions.ts @@ -13,7 +13,7 @@ import { ZosmfMigratedRecallOptions } from "../../../doc/types/ZosmfMigratedReca import { IZosFilesOptions } from "../../../doc/IZosFilesOptions"; /** - * This interface defines the options that can be sent into the dwanload data set function + * This interface defines the options that can be sent into the download data set & member function */ export interface IListOptions extends IZosFilesOptions { diff --git a/packages/zosfiles/src/methods/list/doc/IZosmfListResponse.ts b/packages/zosfiles/src/methods/list/doc/IZosmfListResponse.ts index df5d5fb138..885c1c1976 100644 --- a/packages/zosfiles/src/methods/list/doc/IZosmfListResponse.ts +++ b/packages/zosfiles/src/methods/list/doc/IZosmfListResponse.ts @@ -22,6 +22,11 @@ export interface IZosmfListResponse extends IZosFilesOptions { */ dsname: string; + /** + * The name of the member + */ + memberName: string; + /** * The block size of the dataset */ From af7788c40958bbc6700620f9fd1c4c903afd9cf9 Mon Sep 17 00:00:00 2001 From: Pujal Date: Tue, 5 Nov 2024 17:43:00 -0500 Subject: [PATCH 04/27] added unit test List.ts Signed-off-by: Pujal --- .../__unit__/methods/list/List.unit.test.ts | 2 +- packages/zosfiles/src/methods/list/List.ts | 41 ++++++++++--------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts b/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts index 217d4a934a..e672bf3695 100644 --- a/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts +++ b/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts @@ -1571,7 +1571,7 @@ describe("z/OS Files - List", () => { try { response = await List.membersMatchingPattern(dummySession, dsname, [pattern]); - console.log(response.apiResponse) + // console.log(response.apiResponse) } catch (e) { caughtError = e; } diff --git a/packages/zosfiles/src/methods/list/List.ts b/packages/zosfiles/src/methods/list/List.ts index 8e106e9f33..254aea588b 100644 --- a/packages/zosfiles/src/methods/list/List.ts +++ b/packages/zosfiles/src/methods/list/List.ts @@ -127,7 +127,7 @@ export class List { let response: any; try { response = await List.allMembers(session, dataSetName, { attributes: true}); - console.log(response.apiResponse); + // console.log(response.apiResponse); } catch(err) { if(!(err instanceof ImperativeError && err.errorCode?.toString().startsWith("5"))) { @@ -161,6 +161,7 @@ export class List { await asyncPool(maxConcurrentRequests, response.apiResponse.items, createListPromise); } } + zosmfResponses.push(...response.apiResponse.items); } // Check if members matching pattern found if (zosmfResponses.length === 0) { @@ -172,25 +173,25 @@ export class List { } // Exclude names of members - for (const pattern of options.excludePatterns || []) { - const response = await List.allMembers(session, dataSetName); - console.log(response.apiResponse); - response.apiResponse.items.forEach((membersObj: IZosmfListResponse) => { - const responseIndex = zosmfResponses.findIndex(response => response.apiResponse.memberName === membersObj.memberName); - if (responseIndex !== -1) { - zosmfResponses.splice(responseIndex, 1); - } - }); - } - - // Check if exclude pattern has left any members in the list - if (zosmfResponses.length === 0) { - return { - success: false, - commandResponse: ZosFilesMessages.noMembersInList.message, - apiResponse: [] - }; - } + // for (const pattern of options.excludePatterns || []) { + // const response = await List.allMembers(session, dataSetName); + // console.log(response.apiResponse); + // response.apiResponse.items.forEach((membersObj: IZosmfListResponse) => { + // const responseIndex = zosmfResponses.findIndex(response => response.apiResponse.memberName === membersObj.memberName); + // if (responseIndex !== -1) { + // zosmfResponses.splice(responseIndex, 1); + // } + // }); + // } + + // // Check if exclude pattern has left any members in the list + // if (zosmfResponses.length === 0) { + // return { + // success: false, + // commandResponse: ZosFilesMessages.noMembersInList.message, + // apiResponse: [] + // }; + // } return { success: true, From 9fc192bc5cf660cb41069f1ad067bd419912e04e Mon Sep 17 00:00:00 2001 From: Pujal Date: Thu, 7 Nov 2024 08:24:18 -0500 Subject: [PATCH 05/27] updates to functionality Signed-off-by: Pujal --- .../download/amm/AllMembersMatching.handler.ts | 3 +-- .../cli/src/zosfiles/list/am/AllMembers.handler.ts | 1 - .../__tests__/__unit__/methods/list/List.unit.test.ts | 11 +++++------ packages/zosfiles/src/methods/download/Download.ts | 5 +++-- .../src/methods/download/doc/IDownloadOptions.ts | 2 ++ packages/zosfiles/src/methods/list/List.ts | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts index 8f78b60fdc..0fdc34d728 100644 --- a/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts +++ b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts @@ -75,5 +75,4 @@ export default class AllMembersMatchingHandler extends ZosFilesBaseHandler { responseTimeout: commandParameters.arguments.responseTimeout, }); } -} - +} \ No newline at end of file diff --git a/packages/cli/src/zosfiles/list/am/AllMembers.handler.ts b/packages/cli/src/zosfiles/list/am/AllMembers.handler.ts index 41a7196ac3..fb17fa6374 100644 --- a/packages/cli/src/zosfiles/list/am/AllMembers.handler.ts +++ b/packages/cli/src/zosfiles/list/am/AllMembers.handler.ts @@ -26,7 +26,6 @@ export default class AllMembersHandler extends ZosFilesBaseHandler { pattern: commandParameters.arguments.pattern, responseTimeout: commandParameters.arguments.responseTimeout }); - const invalidMemberCount = response.apiResponse.returnedRows - response.apiResponse.items.length; if (invalidMemberCount > 0) { const invalidMemberMsg = `${invalidMemberCount} members failed to load due to invalid name errors`; diff --git a/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts b/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts index e672bf3695..2cc3a21768 100644 --- a/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts +++ b/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts @@ -1544,11 +1544,11 @@ describe("z/OS Files - List", () => { dsorg: "PS" }; - // const memberData3 = { - // dsname: "TEST.PS", - // memberName: "testM1", - // dsorg: "PS" - // }; + const memberData3 = { + dsname: "TEST.PS", + memberName: "testM1", + dsorg: "PS" + }; beforeEach(() => { listDataSetSpy.mockClear(); @@ -1571,7 +1571,6 @@ describe("z/OS Files - List", () => { try { response = await List.membersMatchingPattern(dummySession, dsname, [pattern]); - // console.log(response.apiResponse) } catch (e) { caughtError = e; } diff --git a/packages/zosfiles/src/methods/download/Download.ts b/packages/zosfiles/src/methods/download/Download.ts index d61bf5997f..086b58b812 100644 --- a/packages/zosfiles/src/methods/download/Download.ts +++ b/packages/zosfiles/src/methods/download/Download.ts @@ -208,7 +208,7 @@ export class Download { try { const response = await List.allMembers(session, dataSetName, { volume: options.volume, - responseTimeout: options.responseTimeout, + responseTimeout: options.responseTimeout }); const memberList: Array<{ member: string }> = response.apiResponse.items; @@ -266,7 +266,7 @@ export class Download { binary: options.binary, record: options.record, encoding: options.encoding, - responseTimeout: options.responseTimeout, + responseTimeout: options.responseTimeout }).catch((err) => { downloadErrors.push(err); failedMembers.push(fileName); @@ -309,6 +309,7 @@ export class Download { } } + /** * Download a list of data sets to local files * diff --git a/packages/zosfiles/src/methods/download/doc/IDownloadOptions.ts b/packages/zosfiles/src/methods/download/doc/IDownloadOptions.ts index 177a8996ba..37656ddc01 100644 --- a/packages/zosfiles/src/methods/download/doc/IDownloadOptions.ts +++ b/packages/zosfiles/src/methods/download/doc/IDownloadOptions.ts @@ -12,6 +12,7 @@ import { Writable } from "stream"; import { IGetOptions } from "../../get/doc/IGetOptions"; import { ZosFilesAttributes } from "../../../utils/ZosFilesAttributes"; +import { IZosFilesResponse } from "../../../doc/IZosFilesResponse"; /** * This interface defines options for downloading a single data set or USS file @@ -109,4 +110,5 @@ export interface IDownloadOptions extends Omit * An optional pattern for restricting the response list */ pattern?: string; + } diff --git a/packages/zosfiles/src/methods/list/List.ts b/packages/zosfiles/src/methods/list/List.ts index 254aea588b..f6a0ec1b50 100644 --- a/packages/zosfiles/src/methods/list/List.ts +++ b/packages/zosfiles/src/methods/list/List.ts @@ -126,8 +126,8 @@ export class List { for(const pattern of patterns) { let response: any; try { - response = await List.allMembers(session, dataSetName, { attributes: true}); - // console.log(response.apiResponse); + response = await List.allMembers(session, dataSetName, { attributes: true, pattern}); + console.log(response.apiResponse); } catch(err) { if(!(err instanceof ImperativeError && err.errorCode?.toString().startsWith("5"))) { From df6b25155b6cb20ca2e23b1f7676154c707343ee Mon Sep 17 00:00:00 2001 From: Pujal Date: Thu, 7 Nov 2024 14:02:22 -0500 Subject: [PATCH 06/27] updates to functionality Signed-off-by: Pujal --- .../zosfiles/download/amm/AllMembersMatching.handler.ts | 2 ++ .../__unit__/methods/download/Download.unit.test.ts | 1 + .../__tests__/__unit__/methods/list/List.unit.test.ts | 6 +++--- packages/zosfiles/src/methods/download/Download.ts | 7 ++++--- .../zosfiles/src/methods/download/doc/IDownloadOptions.ts | 2 ++ packages/zosfiles/src/methods/list/List.ts | 3 ++- 6 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts index 0fdc34d728..af4cde8f89 100644 --- a/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts +++ b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts @@ -48,6 +48,7 @@ export default class AllMembersMatchingHandler extends ZosFilesBaseHandler { commandParameters.response.progress.startBar({ task: listStatus }); const response = await List.membersMatchingPattern(session, commandParameters.arguments.dataSetName, commandParameters.arguments.pattern.split(","), listOptions); + // console.log(response); commandParameters.response.progress.endBar(); if (response.success) { commandParameters.response.console.log(`\r${response.commandResponse}\n`); @@ -73,6 +74,7 @@ export default class AllMembersMatchingHandler extends ZosFilesBaseHandler { failFast: commandParameters.arguments.failFast, task: status, responseTimeout: commandParameters.arguments.responseTimeout, + memberPatternResponse: response }); } } \ No newline at end of file diff --git a/packages/zosfiles/__tests__/__unit__/methods/download/Download.unit.test.ts b/packages/zosfiles/__tests__/__unit__/methods/download/Download.unit.test.ts index 9d7af8b881..f6516aaa0b 100644 --- a/packages/zosfiles/__tests__/__unit__/methods/download/Download.unit.test.ts +++ b/packages/zosfiles/__tests__/__unit__/methods/download/Download.unit.test.ts @@ -696,6 +696,7 @@ describe("z/OS Files - Download", () => { let response; let caughtError; + try { response = await Download.allMembers(dummySession, dsname); } catch (e) { diff --git a/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts b/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts index 2cc3a21768..e6e3d044e9 100644 --- a/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts +++ b/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts @@ -9,7 +9,7 @@ * */ -import { apiErrorHeader, ImperativeError, Logger, Session } from "@zowe/imperative"; +import { ImperativeError, Logger, Session } from "@zowe/imperative"; import { ZosmfRestClient, ZosmfHeaders } from "@zowe/core-for-zowe-sdk"; import { List } from "../../../../src/methods/list/List"; import { ZosFilesMessages } from "../../../../src/constants/ZosFiles.messages"; @@ -1556,7 +1556,7 @@ describe("z/OS Files - List", () => { }); it("should successfully list M1 & M2 using the List.allMembers API", async () => { - const dsname = "TEST.PS" + const dsname = "TEST.PS"; const pattern = "M*"; let response; let caughtError; @@ -1583,7 +1583,7 @@ describe("z/OS Files - List", () => { }); expect(listDataSetSpy).toHaveBeenCalledTimes(1); - expect(listDataSetSpy).toHaveBeenCalledWith(dummySession, dsname, {attributes: true}); + expect(listDataSetSpy).toHaveBeenCalledWith(dummySession, dsname, {attributes: true, pattern}); }); }); }); diff --git a/packages/zosfiles/src/methods/download/Download.ts b/packages/zosfiles/src/methods/download/Download.ts index 086b58b812..b3aef3e5dd 100644 --- a/packages/zosfiles/src/methods/download/Download.ts +++ b/packages/zosfiles/src/methods/download/Download.ts @@ -12,7 +12,7 @@ import { AbstractSession, Headers, ImperativeExpect, IO, Logger, TaskProgress, ImperativeError, TextUtils, IHeaderContent, IOptionsFullResponse, IRestClientResponse } from "@zowe/imperative"; -import { posix, join, relative } from "path"; +import { posix, join, relative, resolve } from "path"; import * as fs from "fs"; import * as util from "util"; @@ -205,13 +205,14 @@ export class Download { ImperativeExpect.toNotBeNullOrUndefined(dataSetName, ZosFilesMessages.missingDatasetName.message); ImperativeExpect.toNotBeEqual(dataSetName, "", ZosFilesMessages.missingDatasetName.message); + const memberObjects = options.memberPatternResponse.apiResponse.map((item: { member: string; }) => ({ member: item.member })); + try { const response = await List.allMembers(session, dataSetName, { volume: options.volume, responseTimeout: options.responseTimeout }); - - const memberList: Array<{ member: string }> = response.apiResponse.items; + const memberList: Array<{ member: string }> = memberObjects ?? response.apiResponse.items; if (memberList.length === 0) { return { success: false, diff --git a/packages/zosfiles/src/methods/download/doc/IDownloadOptions.ts b/packages/zosfiles/src/methods/download/doc/IDownloadOptions.ts index 37656ddc01..d3a2b22420 100644 --- a/packages/zosfiles/src/methods/download/doc/IDownloadOptions.ts +++ b/packages/zosfiles/src/methods/download/doc/IDownloadOptions.ts @@ -111,4 +111,6 @@ export interface IDownloadOptions extends Omit */ pattern?: string; + memberPatternResponse?: IZosFilesResponse; + } diff --git a/packages/zosfiles/src/methods/list/List.ts b/packages/zosfiles/src/methods/list/List.ts index f6a0ec1b50..24b80ff1ed 100644 --- a/packages/zosfiles/src/methods/list/List.ts +++ b/packages/zosfiles/src/methods/list/List.ts @@ -127,7 +127,7 @@ export class List { let response: any; try { response = await List.allMembers(session, dataSetName, { attributes: true, pattern}); - console.log(response.apiResponse); + // console.log(response.apiResponse); } catch(err) { if(!(err instanceof ImperativeError && err.errorCode?.toString().startsWith("5"))) { @@ -163,6 +163,7 @@ export class List { } zosmfResponses.push(...response.apiResponse.items); } + // Check if members matching pattern found if (zosmfResponses.length === 0) { return { From 7956251ef6eaf19b073b398531179710c34eddd0 Mon Sep 17 00:00:00 2001 From: Pujal Date: Thu, 7 Nov 2024 14:56:06 -0500 Subject: [PATCH 07/27] imports edit Signed-off-by: Pujal --- packages/zosfiles/src/methods/download/Download.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/zosfiles/src/methods/download/Download.ts b/packages/zosfiles/src/methods/download/Download.ts index b3aef3e5dd..8f143d5ab0 100644 --- a/packages/zosfiles/src/methods/download/Download.ts +++ b/packages/zosfiles/src/methods/download/Download.ts @@ -12,7 +12,7 @@ import { AbstractSession, Headers, ImperativeExpect, IO, Logger, TaskProgress, ImperativeError, TextUtils, IHeaderContent, IOptionsFullResponse, IRestClientResponse } from "@zowe/imperative"; -import { posix, join, relative, resolve } from "path"; +import { posix, join, relative } from "path"; import * as fs from "fs"; import * as util from "util"; From 10bf997a7780adc9a1c9bcf7b2ac4bc5f640b2ed Mon Sep 17 00:00:00 2001 From: Pujal Date: Fri, 8 Nov 2024 14:55:27 -0500 Subject: [PATCH 08/27] updated unit test download.ts Signed-off-by: Pujal --- .../AllMembersMatching.handler.unit.test.ts | 198 ++++++++++++++++++ ...lMembersMatching.handler.unit.test.ts.snap | 46 ++++ packages/cli/src/zosfiles/-strings-/en.ts | 4 +- .../amm/AllMembersMatching.definition.ts | 2 +- .../methods/download/Download.unit.test.ts | 58 +++++ .../zosfiles/src/methods/download/Download.ts | 4 +- packages/zosfiles/src/methods/list/List.ts | 42 ++-- 7 files changed, 327 insertions(+), 27 deletions(-) create mode 100644 packages/cli/__tests__/zosfiles/__unit__/download/amm/__snapshots__/AllMembersMatching.handler.unit.test.ts.snap diff --git a/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts b/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts index aacc683c4c..82acc919ec 100644 --- a/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts +++ b/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts @@ -9,3 +9,201 @@ * */ +import { IHandlerParameters, Session } from "@zowe/imperative"; +import { Download, IDownloadOptions, IDsmListOptions, List } from "@zowe/zos-files-for-zowe-sdk"; +import * as AllMembersMatchingDefinition from "../../../../../src/zosfiles/download/amm/AllMembersMatching.definition"; +import * as AllMembersMatchingHandler from "../../../../../src/zosfiles/download/amm/AllMembersMatching.handler"; +import { UNIT_TEST_ZOSMF_PROF_OPTS } from "../../../../../../../__tests__/__src__/TestConstants"; +import { mockHandlerParameters } from "@zowe/cli-test-utils"; + +const DEFAULT_PARAMETERS: IHandlerParameters = mockHandlerParameters({ + arguments: UNIT_TEST_ZOSMF_PROF_OPTS, + positionals: ["zos-jobs", "download", "output"], + definition: AllMembersMatchingDefinition.AllMembersMatchingDefinition +}); + +const fakeListOptions: IDsmListOptions = { + task: { + percentComplete: 0, + stageName: 0, + statusMessage: "Searching for members" + } +}; + +const fakeDownloadOptions: IDownloadOptions = { + binary: undefined, + directory: undefined, + encoding: undefined, + extension: undefined, + extensionMap: undefined, + failFast: undefined, + maxConcurrentRequests: undefined, + preserveOriginalLetterCase: undefined, + record: undefined, + responseTimeout: undefined, + volume: undefined, + task: { + percentComplete: 0, + stageName: 0, + statusMessage: "Downloading members" + } +}; + +describe("Download AllMembersMatching handler", () => { + it("should download matching members if requested", async () => { + const pattern = "test*"; + const dsname: "HLQ."; + const fakeListResponse = [{ dsname: "HLQ." + pattern }]; + let passedSession: Session = null; + List.membersMatchingPattern = jest.fn(async (session) => { + passedSession = session; + return { + success: true, + commandResponse: "listed", + apiResponse: fakeListResponse + }; + }); + Download.allMembers = jest.fn(async (session) => { + return { + success: true, + commandResponse: "downloaded" + }; + }); + + const handler = new AllMembersMatchingHandler.default(); + const params = Object.assign({}, ...[DEFAULT_PARAMETERS]); + params.arguments = Object.assign({}, ...[DEFAULT_PARAMETERS.arguments]); + params.arguments.pattern = pattern; + await handler.process(params); + + expect(List.membersMatchingPattern).toHaveBeenCalledTimes(1); + expect(List.membersMatchingPattern).toHaveBeenCalledWith(passedSession, dsname, [pattern], { ...fakeListOptions }); + expect(Download.allMembers).toHaveBeenCalledTimes(1); + expect(Download.allMembers).toHaveBeenCalledWith(passedSession, fakeListResponse, { ...fakeDownloadOptions }); + }); + + it("should handle generation of an extension map", async () => { + const pattern = "testing"; + const fakeListResponse = [{ dsname: "HLQ." + pattern }]; + const extensionMap = "CNTL=JCL,PARMLIB=JCL,LOADLIB=JCL"; + let passedSession: Session = null; + List.membersMatchingPattern = jest.fn(async (session) => { + passedSession = session; + return { + success: true, + commandResponse: "listed", + apiResponse: fakeListResponse + }; + }); + Download.allMembers = jest.fn(async (session) => { + return { + success: true, + commandResponse: "downloaded" + }; + }); + + const handler = new AllMembersMatchingHandler.default(); + const params = Object.assign({}, ...[DEFAULT_PARAMETERS]); + params.arguments = Object.assign({}, ...[DEFAULT_PARAMETERS.arguments]); + params.arguments.pattern = pattern; + params.arguments.extensionMap = extensionMap; + await handler.process(params); + + expect(List.membersMatchingPattern).toHaveBeenCalledTimes(1); + expect(List.membersMatchingPattern).toHaveBeenCalledWith(passedSession, [pattern], { ...fakeListOptions }); + expect(Download.allMembers).toHaveBeenCalledTimes(1); + expect(Download.allMembers).toHaveBeenCalledWith(passedSession, fakeListResponse, { + ...fakeDownloadOptions, + extensionMap: { cntl: "jcl", parmlib: "jcl", loadlib: "jcl" } + }); + }); + + it("should gracefully handle an extension map parsing error", async () => { + const pattern = "testing"; + const extensionMap = "CNTL=JCL,PARMLIB-JCL,LOADLIB=JCL"; + let caughtError; + List.membersMatchingPattern = jest.fn(); + Download.allMembers = jest.fn(); + + const handler = new AllMembersMatchingHandler.default(); + const params = Object.assign({}, ...[DEFAULT_PARAMETERS]); + params.arguments = Object.assign({}, ...[DEFAULT_PARAMETERS.arguments]); + params.arguments.pattern = pattern; + params.arguments.extensionMap = extensionMap; + try { + await handler.process(params); + } catch (error) { + caughtError = error; + } + + expect(caughtError).toBeDefined(); + expect(caughtError.message).toContain("An error occurred processing the extension map"); + expect(List.membersMatchingPattern).toHaveBeenCalledTimes(0); + expect(Download.allMembers).toHaveBeenCalledTimes(0); + }); + + it("should handle generation of an exclusion list", async () => { + const pattern = "testing"; + const fakeListResponse = [{ dsname: "HLQ." + pattern }]; + const excludePatterns = "TEST.EXCLUDE.**.CNTL"; + let passedSession: Session = null; + List.membersMatchingPattern = jest.fn(async (session) => { + passedSession = session; + return { + success: true, + commandResponse: "listed", + apiResponse: fakeListResponse + }; + }); + Download.allMembers = jest.fn(async (session) => { + return { + success: true, + commandResponse: "downloaded" + }; + }); + + const handler = new AllMembersMatchingHandler.default(); + const params = Object.assign({}, ...[DEFAULT_PARAMETERS]); + params.arguments = Object.assign({}, ...[DEFAULT_PARAMETERS.arguments]); + params.arguments.pattern = pattern; + params.arguments.excludePatterns = excludePatterns; + await handler.process(params); + + expect(List.membersMatchingPattern).toHaveBeenCalledTimes(1); + expect(List.membersMatchingPattern).toHaveBeenCalledWith(passedSession, [pattern], { + ...fakeListOptions, + excludePatterns: [excludePatterns] + }); + expect(Download.allMembers).toHaveBeenCalledTimes(1); + expect(Download.allMembers).toHaveBeenCalledWith(passedSession, fakeListResponse, { ...fakeDownloadOptions }); + }); + + it("should gracefully handle an error from the z/OSMF List API", async () => { + const errorMsg = "i haz bad data set"; + const pattern = "testing"; + let caughtError; + let passedSession: Session = null; + List.membersMatchingPattern = jest.fn((session) => { + passedSession = session; + throw new Error(errorMsg); + }); + Download.allMembers = jest.fn(); + + const handler = new AllMembersMatchingHandler.default(); + const params = Object.assign({}, ...[DEFAULT_PARAMETERS]); + params.arguments = Object.assign({}, ...[DEFAULT_PARAMETERS.arguments]); + params.arguments.pattern = pattern; + try { + await handler.process(params); + } catch (error) { + caughtError = error; + } + + expect(caughtError).toBeDefined(); + expect(caughtError.message).toBe(errorMsg); + expect(List.membersMatchingPattern).toHaveBeenCalledTimes(1); + expect(List.membersMatchingPattern).toHaveBeenCalledWith(passedSession, [pattern], { ...fakeListOptions }); + expect(Download.allMembers).toHaveBeenCalledTimes(0); + }); +}); + diff --git a/packages/cli/__tests__/zosfiles/__unit__/download/amm/__snapshots__/AllMembersMatching.handler.unit.test.ts.snap b/packages/cli/__tests__/zosfiles/__unit__/download/amm/__snapshots__/AllMembersMatching.handler.unit.test.ts.snap new file mode 100644 index 0000000000..665f217946 --- /dev/null +++ b/packages/cli/__tests__/zosfiles/__unit__/download/amm/__snapshots__/AllMembersMatching.handler.unit.test.ts.snap @@ -0,0 +1,46 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Download AllMembersMatching handler should download matching members if requested 1`] = ` +" +listed +" +`; + +exports[`Download AllMembersMatching handler should download matching members if requested 2`] = `"downloaded"`; + +exports[`Download AllMembersMatching handler should download matching members if requested 3`] = ` +Object { + "commandResponse": "downloaded", + "success": true, +} +`; + +exports[`Download AllMembersMatching handler should handle generation of an exclusion list 1`] = ` +" +listed +" +`; + +exports[`Download AllMembersMatching handler should handle generation of an exclusion list 2`] = `"downloaded"`; + +exports[`Download AllMembersMatching handler should handle generation of an exclusion list 3`] = ` +Object { + "commandResponse": "downloaded", + "success": true, +} +`; + +exports[`Download AllMembersMatching handler should handle generation of an extension map 1`] = ` +" +listed +" +`; + +exports[`Download AllMembersMatching handler should handle generation of an extension map 2`] = `"downloaded"`; + +exports[`Download AllMembersMatching handler should handle generation of an extension map 3`] = ` +Object { + "commandResponse": "downloaded", + "success": true, +} +`; diff --git a/packages/cli/src/zosfiles/-strings-/en.ts b/packages/cli/src/zosfiles/-strings-/en.ts index b04d28ea1a..7d167d9473 100644 --- a/packages/cli/src/zosfiles/-strings-/en.ts +++ b/packages/cli/src/zosfiles/-strings-/en.ts @@ -354,8 +354,8 @@ export default { `for example "ibmuser.**.cntl,ibmuser.**.jcl"` }, EXAMPLES: { - EX1: `Download the members of the data set "ibmuser.loadlib" in binary mode to the directory "loadlib/"`, - EX2: `Download the members of the data set "ibmuser.cntl" in text mode to the directory "jcl/"` + EX1: `Download the members of the data set "ibmuser.loadlib" that begin with "Test" to the directory "loadlib/"`, + EX2: `Download the members of the data set "ibmuser.cntl" that begin with "Test" & "M" to the directory "jcl/"` } }, DATA_SET: { diff --git a/packages/cli/src/zosfiles/download/amm/AllMembersMatching.definition.ts b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.definition.ts index 44f923b1dd..79c4d9f4ff 100644 --- a/packages/cli/src/zosfiles/download/amm/AllMembersMatching.definition.ts +++ b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.definition.ts @@ -60,7 +60,7 @@ export const AllMembersMatchingDefinition: ICommandDefinition = { examples: [ { description: strings.EXAMPLES.EX1, - options: `"ibmuser.loadlib" -b -d loadlib` + options: `"ibmuser.loadlib" "Test*" -d loadlib` }, { description: strings.EXAMPLES.EX2, diff --git a/packages/zosfiles/__tests__/__unit__/methods/download/Download.unit.test.ts b/packages/zosfiles/__tests__/__unit__/methods/download/Download.unit.test.ts index f6516aaa0b..e854f297ff 100644 --- a/packages/zosfiles/__tests__/__unit__/methods/download/Download.unit.test.ts +++ b/packages/zosfiles/__tests__/__unit__/methods/download/Download.unit.test.ts @@ -609,6 +609,7 @@ describe("z/OS Files - Download", () => { describe("allMembers", () => { const listAllMembersSpy = jest.spyOn(List, "allMembers"); const downloadDatasetSpy = jest.spyOn(Download, "dataSet"); + const downloadMembers = jest.spyOn(Download, "allMembers"); const listApiResponse = { items: [ @@ -970,6 +971,63 @@ describe("z/OS Files - Download", () => { }); }); + it("should download all members (based on pattern) if response is passed through options", async () => { + let response; + let caughtError; + + const directory = "user/data/set"; + + const memberPatternResponse = { + success: true, + commandResponse: '2 members(s) were found matching pattern.', + apiResponse: [ + { + member: 'M1', + vers: 1, + mod: 0, + c4date: '2024/10/31', + m4date: '2024/10/31', + cnorc: 0, + inorc: 0, + mnorc: 0, + mtime: '10:47', + msec: '28', + user: 'X', + sclm: 'N' + }, + { + member: 'M2', + vers: 1, + mod: 0, + c4date: '2024/11/08', + m4date: '2024/11/08', + cnorc: 0, + inorc: 0, + mnorc: 0, + mtime: '11:57', + msec: '39', + user: 'X', + sclm: 'N' + } + ] + }; + + try { + response = await Download.allMembers(dummySession, dsname, {memberPatternResponse}); + } catch (e) { + caughtError = e; + } + expect(caughtError).toBeUndefined(); + expect(response).toEqual({ + success: true, + commandResponse: util.format(ZosFilesMessages.datasetDownloadedWithDestination.message, directory), + apiResponse: listApiResponse + }); + + expect(downloadMembers).toHaveBeenCalledTimes(1); + expect(downloadMembers).toHaveBeenCalledWith(dummySession, dsname, {memberPatternResponse}); + + }); it("should download all members specifying preserveOriginalLetterCase", async () => { let response; let caughtError; diff --git a/packages/zosfiles/src/methods/download/Download.ts b/packages/zosfiles/src/methods/download/Download.ts index 8f143d5ab0..e34ba78aa4 100644 --- a/packages/zosfiles/src/methods/download/Download.ts +++ b/packages/zosfiles/src/methods/download/Download.ts @@ -205,14 +205,14 @@ export class Download { ImperativeExpect.toNotBeNullOrUndefined(dataSetName, ZosFilesMessages.missingDatasetName.message); ImperativeExpect.toNotBeEqual(dataSetName, "", ZosFilesMessages.missingDatasetName.message); - const memberObjects = options.memberPatternResponse.apiResponse.map((item: { member: string; }) => ({ member: item.member })); + const members = options.memberPatternResponse.apiResponse.map((item: { member: string; }) => ({ member: item.member })); try { const response = await List.allMembers(session, dataSetName, { volume: options.volume, responseTimeout: options.responseTimeout }); - const memberList: Array<{ member: string }> = memberObjects ?? response.apiResponse.items; + const memberList: Array<{ member: string }> = members ?? response.apiResponse.items; if (memberList.length === 0) { return { success: false, diff --git a/packages/zosfiles/src/methods/list/List.ts b/packages/zosfiles/src/methods/list/List.ts index 24b80ff1ed..411c8735f1 100644 --- a/packages/zosfiles/src/methods/list/List.ts +++ b/packages/zosfiles/src/methods/list/List.ts @@ -127,14 +127,13 @@ export class List { let response: any; try { response = await List.allMembers(session, dataSetName, { attributes: true, pattern}); - // console.log(response.apiResponse); } catch(err) { if(!(err instanceof ImperativeError && err.errorCode?.toString().startsWith("5"))) { throw err; } - response = await List.allMembers(session, dataSetName); + response = await List.allMembers(session, dataSetName, {pattern}); let listsInitiated = 0; const createListPromise = (membersObj: any) => { @@ -144,7 +143,7 @@ export class List { listsInitiated++; } - return List.allMembers(session, membersObj.dsname, { attributes: true, maxLength: 1 }).then( + return List.allMembers(session, membersObj.dsname, { attributes: true, maxLength: 1, pattern}).then( (tempResponse) => { Object.assign(membersObj, tempResponse.apiResponse.items[0]); }, @@ -174,25 +173,24 @@ export class List { } // Exclude names of members - // for (const pattern of options.excludePatterns || []) { - // const response = await List.allMembers(session, dataSetName); - // console.log(response.apiResponse); - // response.apiResponse.items.forEach((membersObj: IZosmfListResponse) => { - // const responseIndex = zosmfResponses.findIndex(response => response.apiResponse.memberName === membersObj.memberName); - // if (responseIndex !== -1) { - // zosmfResponses.splice(responseIndex, 1); - // } - // }); - // } - - // // Check if exclude pattern has left any members in the list - // if (zosmfResponses.length === 0) { - // return { - // success: false, - // commandResponse: ZosFilesMessages.noMembersInList.message, - // apiResponse: [] - // }; - // } + for (const pattern of options.excludePatterns || []) { + const response = await List.allMembers(session, dataSetName, {pattern}); + response.apiResponse.items.forEach((membersObj: IZosmfListResponse) => { + const responseIndex = zosmfResponses.findIndex(response => response.apiResponse.memberName === membersObj.memberName); + if (responseIndex !== -1) { + zosmfResponses.splice(responseIndex, 1); + } + }); + } + + // Check if exclude pattern has left any members in the list + if (zosmfResponses.length === 0) { + return { + success: false, + commandResponse: ZosFilesMessages.noMembersInList.message, + apiResponse: [] + }; + } return { success: true, From cfdb2acb0552788a19359dac32d2bb6c3c651bf9 Mon Sep 17 00:00:00 2001 From: Pujal Date: Mon, 11 Nov 2024 11:44:48 -0500 Subject: [PATCH 09/27] updated list.ts unit tests Signed-off-by: Pujal --- .../amm/AllMembersMatching.handler.ts | 2 +- .../__unit__/methods/list/List.unit.test.ts | 177 +++++++++++++++++- .../zosfiles/src/methods/download/Download.ts | 4 +- packages/zosfiles/src/methods/list/List.ts | 4 +- .../methods/list/doc/IZosmfListResponse.ts | 2 +- 5 files changed, 173 insertions(+), 16 deletions(-) diff --git a/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts index af4cde8f89..ffd1969ca1 100644 --- a/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts +++ b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts @@ -74,7 +74,7 @@ export default class AllMembersMatchingHandler extends ZosFilesBaseHandler { failFast: commandParameters.arguments.failFast, task: status, responseTimeout: commandParameters.arguments.responseTimeout, - memberPatternResponse: response + memberPatternResponse: response.apiResponse, }); } } \ No newline at end of file diff --git a/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts b/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts index e6e3d044e9..f28a191ccf 100644 --- a/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts +++ b/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts @@ -1534,19 +1534,13 @@ describe("z/OS Files - List", () => { const memberData1 = { dsname: "TEST.PS", - memberName: "M1", + member: "M1", dsorg: "PS" }; const memberData2 = { dsname: "TEST.PS", - memberName: "M2", - dsorg: "PS" - }; - - const memberData3 = { - dsname: "TEST.PS", - memberName: "testM1", + member: "M2", dsorg: "PS" }; @@ -1575,7 +1569,7 @@ describe("z/OS Files - List", () => { caughtError = e; } - // expect(caughtError).toBeUndefined(); + expect(caughtError).toBeUndefined(); expect(response).toEqual({ success: true, commandResponse: util.format(ZosFilesMessages.membersMatchedPattern.message, 2), @@ -1585,5 +1579,170 @@ describe("z/OS Files - List", () => { expect(listDataSetSpy).toHaveBeenCalledTimes(1); expect(listDataSetSpy).toHaveBeenCalledWith(dummySession, dsname, {attributes: true, pattern}); }); + it("should throw an error if the data set name is not specified", async () => { + let response; + let caughtError; + const pattern = "M*"; + + // Test for NULL + try { + response = await List.membersMatchingPattern(dummySession, null, [pattern]); + } catch (e) { + caughtError = e; + } + + expect(response).toBeUndefined(); + expect(caughtError).toBeDefined(); + expect(caughtError.message).toContain(ZosFilesMessages.missingDatasetName.message); + + caughtError = undefined; + // Test for UNDEFINED + try { + response = await List.membersMatchingPattern(dummySession, undefined, [pattern]); + } catch (e) { + caughtError = e; + } + + expect(response).toBeUndefined(); + expect(caughtError).toBeDefined(); + expect(caughtError.message).toContain(ZosFilesMessages.missingDatasetName.message); + + caughtError = undefined; + // Test for EMPTY + try { + response = await List.membersMatchingPattern(dummySession,"",[pattern]); + } catch (e) { + caughtError = e; + } + + expect(response).toBeUndefined(); + expect(caughtError).toBeDefined(); + expect(caughtError.message).toContain(ZosFilesMessages.missingDatasetName.message); + }); + + it("should throw an error if the member pattern is not specified", async () => { + let response; + let caughtError; + + // Test for NULL + try { + response = await List.membersMatchingPattern(dummySession, memberData1.dsname, [null]); + } catch (e) { + caughtError = e; + } + + expect(response).toBeUndefined(); + expect(caughtError).toBeDefined(); + expect(caughtError.message).toContain(ZosFilesMessages.missingPatterns.message); + + caughtError = undefined; + // Test for UNDEFINED + try { + response = await List.membersMatchingPattern(dummySession, memberData1.dsname, [undefined]); + } catch (e) { + caughtError = e; + } + + expect(response).toBeUndefined(); + expect(caughtError).toBeDefined(); + expect(caughtError.message).toContain(ZosFilesMessages.missingPatterns.message); + + caughtError = undefined; + // Test for EMPTY + try { + response = await List.membersMatchingPattern(dummySession,memberData1.dsname,[]); + } catch (e) { + caughtError = e; + } + + expect(response).toBeUndefined(); + expect(caughtError).toBeDefined(); + expect(caughtError.message).toContain(ZosFilesMessages.missingPatterns.message); + }); + + it("should handle an error from the List.allMembers API", async () => { + const dummyError = new Error("test2"); + let response; + let caughtError; + const pattern = "M*"; + const dsname = "TEST.PS"; + + listDataSetSpy.mockImplementation(async () => { + throw dummyError; + }); + + try { + response = await List.membersMatchingPattern(dummySession, dsname, [pattern]); + } catch (e) { + caughtError = e; + } + + expect(response).toBeUndefined(); + expect(caughtError).toEqual(dummyError); + + expect(listDataSetSpy).toHaveBeenCalledTimes(1); + expect(listDataSetSpy).toHaveBeenCalledWith(dummySession, dsname, {attributes: true, pattern}); + }); + it("should handle an error from the List.allMembers API and fall back to fetching attributes sequentially", async () => { + let response; + let caughtError; + const dsname = "TEST.PS"; + const pattern = "M*"; + + listDataSetSpy.mockImplementationOnce(async () => { + throw new ImperativeError({msg: "test2", errorCode: "500"}); + }).mockImplementation(async (): Promise => { + return { + apiResponse: { + items: [memberData1,memberData2] + } + }; + }); + + try { + response = await List.membersMatchingPattern(dummySession, dsname, [pattern]); + } catch (e) { + caughtError = e; + } + + expect(caughtError).toBeUndefined(); + expect(response).toEqual({ + success: true, + commandResponse: util.format(ZosFilesMessages.membersMatchedPattern.message, 2), + apiResponse: [memberData1,memberData2] + }); + + expect(listDataSetSpy).toHaveBeenCalledTimes(4); + expect(listDataSetSpy).toHaveBeenLastCalledWith(dummySession, dsname, {attributes: true, maxLength: 1, pattern}); + }); + + it("should handle an error when the exclude pattern is specified", async () => { + const excludePatterns = ["M1*"]; + const pattern = "M1*"; + let response; + let caughtError; + + List.allMembers = jest.fn(async (): Promise => { + return { + apiResponse: { + items: [memberData1] + } + }; + }); + + try { + response = await List.membersMatchingPattern( + dummySession, memberData1.dsname, [pattern], { excludePatterns }); + } catch (e) { + caughtError = e; + } + + expect(caughtError).toBeUndefined(); + expect(response).toEqual({ + success: false, + commandResponse: util.format(ZosFilesMessages.noMembersInList.message), + apiResponse: [] + }); + }); }); }); diff --git a/packages/zosfiles/src/methods/download/Download.ts b/packages/zosfiles/src/methods/download/Download.ts index e34ba78aa4..bea9b37b61 100644 --- a/packages/zosfiles/src/methods/download/Download.ts +++ b/packages/zosfiles/src/methods/download/Download.ts @@ -205,14 +205,12 @@ export class Download { ImperativeExpect.toNotBeNullOrUndefined(dataSetName, ZosFilesMessages.missingDatasetName.message); ImperativeExpect.toNotBeEqual(dataSetName, "", ZosFilesMessages.missingDatasetName.message); - const members = options.memberPatternResponse.apiResponse.map((item: { member: string; }) => ({ member: item.member })); - try { const response = await List.allMembers(session, dataSetName, { volume: options.volume, responseTimeout: options.responseTimeout }); - const memberList: Array<{ member: string }> = members ?? response.apiResponse.items; + const memberList: Array<{ member: string }> = options.memberPatternResponse ?? response.apiResponse.items; if (memberList.length === 0) { return { success: false, diff --git a/packages/zosfiles/src/methods/list/List.ts b/packages/zosfiles/src/methods/list/List.ts index 411c8735f1..980d10c280 100644 --- a/packages/zosfiles/src/methods/list/List.ts +++ b/packages/zosfiles/src/methods/list/List.ts @@ -121,7 +121,7 @@ export class List { ImperativeExpect.toNotBeNullOrUndefined(patterns, ZosFilesMessages.missingPatterns.message); patterns = patterns.filter(Boolean); ImperativeExpect.toNotBeEqual(patterns.length, 0, ZosFilesMessages.missingPatterns.message); - const zosmfResponses: IZosFilesResponse[] = []; + const zosmfResponses: IZosmfListResponse[] = []; for(const pattern of patterns) { let response: any; @@ -176,7 +176,7 @@ export class List { for (const pattern of options.excludePatterns || []) { const response = await List.allMembers(session, dataSetName, {pattern}); response.apiResponse.items.forEach((membersObj: IZosmfListResponse) => { - const responseIndex = zosmfResponses.findIndex(response => response.apiResponse.memberName === membersObj.memberName); + const responseIndex = zosmfResponses.findIndex(response=> response.member === membersObj.member); if (responseIndex !== -1) { zosmfResponses.splice(responseIndex, 1); } diff --git a/packages/zosfiles/src/methods/list/doc/IZosmfListResponse.ts b/packages/zosfiles/src/methods/list/doc/IZosmfListResponse.ts index 885c1c1976..7476930784 100644 --- a/packages/zosfiles/src/methods/list/doc/IZosmfListResponse.ts +++ b/packages/zosfiles/src/methods/list/doc/IZosmfListResponse.ts @@ -25,7 +25,7 @@ export interface IZosmfListResponse extends IZosFilesOptions { /** * The name of the member */ - memberName: string; + member?: string; /** * The block size of the dataset From 87d3810e06b1351ac2cf8dfe7b6b8b5a31a26824 Mon Sep 17 00:00:00 2001 From: Pujal Date: Mon, 11 Nov 2024 16:12:02 -0500 Subject: [PATCH 10/27] updated unit tests Signed-off-by: Pujal --- packages/cli/CHANGELOG.md | 4 + .../AllMembersMatching.handler.unit.test.ts | 33 ++++-- ...mbersMatching.definition.unit.test.ts.snap | 102 ++++++++++++++++++ .../amm/AllMembersMatching.definition.ts | 2 +- .../amm/AllMembersMatching.handler.ts | 1 - packages/zosfiles/CHANGELOG.md | 4 + .../methods/download/Download.unit.test.ts | 75 ++----------- .../ZosFilesUtils.unit.test.ts.snap | 13 +++ .../src/constants/ZosFiles.messages.ts | 8 ++ .../zosfiles/src/methods/download/Download.ts | 2 +- .../methods/download/doc/IDownloadOptions.ts | 3 +- 11 files changed, 170 insertions(+), 77 deletions(-) create mode 100644 packages/cli/__tests__/zosfiles/__unit__/download/amm/__snapshots__/AllMembersMatching.definition.unit.test.ts.snap diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index a81ddedba5..eb65d726d9 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to the Zowe CLI package will be documented in this file. ## Recent Changes +-Enhancement: Added new command zowe zos-files download all-members-matching, (zowe files dl amm), to download members matching specified pattern(s). [#2175](https://github.com/zowe/zowe-cli/issues/2175) + +## Recent Changes + - Enhancement: Added --wait-for-active and --wait-for-output to download options on zosjobs. [#2328](https://github.com/zowe/zowe-cli/pull/2328) ## `8.6.0` diff --git a/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts b/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts index 82acc919ec..f1bd9e02d1 100644 --- a/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts +++ b/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts @@ -18,7 +18,7 @@ import { mockHandlerParameters } from "@zowe/cli-test-utils"; const DEFAULT_PARAMETERS: IHandlerParameters = mockHandlerParameters({ arguments: UNIT_TEST_ZOSMF_PROF_OPTS, - positionals: ["zos-jobs", "download", "output"], + positionals: ["zos-jobs", "download", "amm", "output"], definition: AllMembersMatchingDefinition.AllMembersMatchingDefinition }); @@ -30,6 +30,27 @@ const fakeListOptions: IDsmListOptions = { } }; +const fakeListResponse = { + success: true, + commandResponse: '1 members(s) were found matching pattern.', + apiResponse: [ + { + member: 'M1', + vers: 1, + mod: 0, + c4date: '2024/11/11', + m4date: '2024/11/11', + cnorc: 0, + inorc: 0, + mnorc: 0, + mtime: '16:06', + msec: '51', + user: 'PG899423', + sclm: 'N' + } + ] +}; + const fakeDownloadOptions: IDownloadOptions = { binary: undefined, directory: undefined, @@ -46,14 +67,14 @@ const fakeDownloadOptions: IDownloadOptions = { percentComplete: 0, stageName: 0, statusMessage: "Downloading members" - } + }, + memberPatternResponse: fakeListResponse.apiResponse, }; describe("Download AllMembersMatching handler", () => { it("should download matching members if requested", async () => { - const pattern = "test*"; - const dsname: "HLQ."; - const fakeListResponse = [{ dsname: "HLQ." + pattern }]; + const pattern = "M1*"; + const dsname = "test-pds"; let passedSession: Session = null; List.membersMatchingPattern = jest.fn(async (session) => { passedSession = session; @@ -79,7 +100,7 @@ describe("Download AllMembersMatching handler", () => { expect(List.membersMatchingPattern).toHaveBeenCalledTimes(1); expect(List.membersMatchingPattern).toHaveBeenCalledWith(passedSession, dsname, [pattern], { ...fakeListOptions }); expect(Download.allMembers).toHaveBeenCalledTimes(1); - expect(Download.allMembers).toHaveBeenCalledWith(passedSession, fakeListResponse, { ...fakeDownloadOptions }); + expect(Download.allMembers).toHaveBeenCalledWith(passedSession, dsname, { ...fakeDownloadOptions }); }); it("should handle generation of an extension map", async () => { diff --git a/packages/cli/__tests__/zosfiles/__unit__/download/amm/__snapshots__/AllMembersMatching.definition.unit.test.ts.snap b/packages/cli/__tests__/zosfiles/__unit__/download/amm/__snapshots__/AllMembersMatching.definition.unit.test.ts.snap new file mode 100644 index 0000000000..b6eeab84eb --- /dev/null +++ b/packages/cli/__tests__/zosfiles/__unit__/download/amm/__snapshots__/AllMembersMatching.definition.unit.test.ts.snap @@ -0,0 +1,102 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`zos-files download amm command definition should not have changed 1`] = ` +Array [ + Object { + "aliases": Array [ + "b", + ], + "description": "Download the file content in binary mode, which means that no data conversion is performed. The data transfer process returns each line as-is, without translation. No delimiters are added between records.", + "name": "binary", + "type": "boolean", + }, + Object { + "aliases": Array [ + "d", + ], + "description": "The directory to where you want to save the members. The command creates the directory for you when it does not already exist. By default, the command creates a folder structure based on the data set qualifiers. For example, the data set ibmuser.new.cntl's members are downloaded to ibmuser/new/cntl).", + "name": "directory", + "type": "string", + }, + Object { + "aliases": Array [ + "ec", + ], + "description": "Download the file content with encoding mode, which means that data conversion is performed using the file encoding specified.", + "name": "encoding", + "type": "string", + }, + Object { + "aliases": Array [ + "e", + ], + "description": "Save the local files with a specified file extension. For example, .txt. Or \\"\\" for no extension. When no extension is specified, .txt is used as the default file extension.", + "name": "extension", + "type": "stringOrEmpty", + }, + Object { + "aliases": Array [ + "ff", + ], + "defaultValue": true, + "description": "Set this option to false to continue downloading data set members if one or more fail.", + "name": "fail-fast", + "type": "boolean", + }, + Object { + "aliases": Array [ + "mcr", + ], + "defaultValue": 1, + "description": "Specifies the maximum number of concurrent z/OSMF REST API requests to download members. Increasing the value results in faster downloads. However, increasing the value increases resource consumption on z/OS and can be prone to errors caused by making too many concurrent requests. If the download process encounters an error, the following message displays: +The maximum number of TSO address spaces have been created. When you specify 0, Zowe CLI attempts to download all members at once without a maximum number of concurrent requests. ", + "name": "max-concurrent-requests", + "numericValueRange": Array [ + 0, + 100, + ], + "type": "number", + }, + Object { + "aliases": Array [ + "po", + ], + "defaultValue": false, + "description": "Specifies if the automatically generated directories and files use the original letter case.", + "name": "preserve-original-letter-case", + "type": "boolean", + }, + Object { + "aliases": Array [ + "r", + ], + "conflictsWith": Array [ + "binary", + ], + "description": "Download the file content in record mode, which means that no data conversion is performed and the record length is prepended to the data. The data transfer process returns each line as-is, without translation. No delimiters are added between records. Conflicts with binary.", + "name": "record", + "type": "boolean", + }, + Object { + "aliases": Array [ + "vs", + ], + "description": "The volume serial (VOLSER) where the data set resides. You can use this option at any time. However, the VOLSER is required only when the data set is not cataloged on the system. A VOLSER is analogous to a drive name on a PC.", + "name": "volume-serial", + "type": "string", + }, +] +`; + +exports[`zos-files download amm command definition should not have changed 2`] = ` +Array [ + Object { + "description": "Download the members of the data set \\"ibmuser.loadlib\\" in binary mode to the directory \\"loadlib/\\"", + "options": "\\"ibmuser.loadlib\\" -b -d loadlib", + }, + Object { + "description": "Download the members of the data set \\"ibmuser.cntl\\" in text mode to the directory \\"jcl/\\"", + "options": "\\"ibmuser.cntl\\" -d jcl", + }, +] +`; diff --git a/packages/cli/src/zosfiles/download/amm/AllMembersMatching.definition.ts b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.definition.ts index 79c4d9f4ff..5b74724aea 100644 --- a/packages/cli/src/zosfiles/download/amm/AllMembersMatching.definition.ts +++ b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.definition.ts @@ -64,7 +64,7 @@ export const AllMembersMatchingDefinition: ICommandDefinition = { }, { description: strings.EXAMPLES.EX2, - options: `"ibmuser.cntl" -d jcl` + options: `"ibmuser.cntl" "test*,M*" --exclude-patterns "M2*" -d output` } ] }; \ No newline at end of file diff --git a/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts index ffd1969ca1..3f69d86570 100644 --- a/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts +++ b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts @@ -48,7 +48,6 @@ export default class AllMembersMatchingHandler extends ZosFilesBaseHandler { commandParameters.response.progress.startBar({ task: listStatus }); const response = await List.membersMatchingPattern(session, commandParameters.arguments.dataSetName, commandParameters.arguments.pattern.split(","), listOptions); - // console.log(response); commandParameters.response.progress.endBar(); if (response.success) { commandParameters.response.console.log(`\r${response.commandResponse}\n`); diff --git a/packages/zosfiles/CHANGELOG.md b/packages/zosfiles/CHANGELOG.md index 3c53654b69..881be8c08b 100644 --- a/packages/zosfiles/CHANGELOG.md +++ b/packages/zosfiles/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to the Zowe z/OS files SDK package will be documented in this file. +## Recent Changes + +- Enhancement: Added a `List.membersMatchingPattern` method to download all members that match a DSLEVEL pattern.[#2175](https://github.com/zowe/zowe-cli/issues/2175) + ## `8.4.0` - Enhancement: Added optional `--attributes` flag to `zowe zos-files upload file-to-uss` to allow passing a .zosattributes file path for upload encoding format. [#2319] (https://github.com/zowe/zowe-cli/pull/2319) diff --git a/packages/zosfiles/__tests__/__unit__/methods/download/Download.unit.test.ts b/packages/zosfiles/__tests__/__unit__/methods/download/Download.unit.test.ts index e854f297ff..966842438d 100644 --- a/packages/zosfiles/__tests__/__unit__/methods/download/Download.unit.test.ts +++ b/packages/zosfiles/__tests__/__unit__/methods/download/Download.unit.test.ts @@ -707,7 +707,7 @@ describe("z/OS Files - Download", () => { expect(caughtError).toBeUndefined(); expect(response).toEqual({ success: true, - commandResponse: util.format(ZosFilesMessages.datasetDownloadedWithDestination.message, dsFolder), + commandResponse: util.format(ZosFilesMessages.memberDownloadedWithDestination.message, dsFolder), apiResponse: listApiResponse }); @@ -740,7 +740,7 @@ describe("z/OS Files - Download", () => { expect(caughtError).toBeUndefined(); expect(response).toEqual({ success: true, - commandResponse: util.format(ZosFilesMessages.datasetDownloadedWithDestination.message, directory), + commandResponse: util.format(ZosFilesMessages.memberDownloadedWithDestination.message, directory), apiResponse: listApiResponse }); @@ -775,7 +775,7 @@ describe("z/OS Files - Download", () => { expect(caughtError).toBeUndefined(); expect(response).toEqual({ success: true, - commandResponse: util.format(ZosFilesMessages.datasetDownloadedWithDestination.message, directory), + commandResponse: util.format(ZosFilesMessages.memberDownloadedWithDestination.message, directory), apiResponse: listApiResponse }); @@ -811,7 +811,7 @@ describe("z/OS Files - Download", () => { expect(caughtError).toBeUndefined(); expect(response).toEqual({ success: true, - commandResponse: util.format(ZosFilesMessages.datasetDownloadedWithDestination.message, directory), + commandResponse: util.format(ZosFilesMessages.memberDownloadedWithDestination.message, directory), apiResponse: listApiResponse }); @@ -849,7 +849,7 @@ describe("z/OS Files - Download", () => { expect(caughtError).toBeUndefined(); expect(response).toEqual({ success: true, - commandResponse: util.format(ZosFilesMessages.datasetDownloadedWithDestination.message, directory), + commandResponse: util.format(ZosFilesMessages.memberDownloadedWithDestination.message, directory), apiResponse: listApiResponse }); @@ -886,7 +886,7 @@ describe("z/OS Files - Download", () => { expect(caughtError).toBeUndefined(); expect(response).toEqual({ success: true, - commandResponse: util.format(ZosFilesMessages.datasetDownloadedWithDestination.message, directory), + commandResponse: util.format(ZosFilesMessages.memberDownloadedWithDestination.message, directory), apiResponse: listApiResponse }); @@ -920,7 +920,7 @@ describe("z/OS Files - Download", () => { expect(caughtError).toBeUndefined(); expect(response).toEqual({ success: true, - commandResponse: util.format(ZosFilesMessages.datasetDownloadedWithDestination.message, directory), + commandResponse: util.format(ZosFilesMessages.memberDownloadedWithDestination.message, directory), apiResponse: listApiResponse }); @@ -954,7 +954,7 @@ describe("z/OS Files - Download", () => { expect(caughtError).toBeUndefined(); expect(response).toEqual({ success: true, - commandResponse: util.format(ZosFilesMessages.datasetDownloadedWithDestination.message, directory), + commandResponse: util.format(ZosFilesMessages.memberDownloadedWithDestination.message, directory), apiResponse: listApiResponse }); @@ -971,63 +971,6 @@ describe("z/OS Files - Download", () => { }); }); - it("should download all members (based on pattern) if response is passed through options", async () => { - let response; - let caughtError; - - const directory = "user/data/set"; - - const memberPatternResponse = { - success: true, - commandResponse: '2 members(s) were found matching pattern.', - apiResponse: [ - { - member: 'M1', - vers: 1, - mod: 0, - c4date: '2024/10/31', - m4date: '2024/10/31', - cnorc: 0, - inorc: 0, - mnorc: 0, - mtime: '10:47', - msec: '28', - user: 'X', - sclm: 'N' - }, - { - member: 'M2', - vers: 1, - mod: 0, - c4date: '2024/11/08', - m4date: '2024/11/08', - cnorc: 0, - inorc: 0, - mnorc: 0, - mtime: '11:57', - msec: '39', - user: 'X', - sclm: 'N' - } - ] - }; - - try { - response = await Download.allMembers(dummySession, dsname, {memberPatternResponse}); - } catch (e) { - caughtError = e; - } - expect(caughtError).toBeUndefined(); - expect(response).toEqual({ - success: true, - commandResponse: util.format(ZosFilesMessages.datasetDownloadedWithDestination.message, directory), - apiResponse: listApiResponse - }); - - expect(downloadMembers).toHaveBeenCalledTimes(1); - expect(downloadMembers).toHaveBeenCalledWith(dummySession, dsname, {memberPatternResponse}); - - }); it("should download all members specifying preserveOriginalLetterCase", async () => { let response; let caughtError; @@ -1041,7 +984,7 @@ describe("z/OS Files - Download", () => { expect(caughtError).toBeUndefined(); expect(response).toEqual({ success: true, - commandResponse: util.format(ZosFilesMessages.datasetDownloadedWithDestination.message, dsFolder.toUpperCase()), + commandResponse: util.format(ZosFilesMessages.memberDownloadedWithDestination.message, dsFolder.toUpperCase()), apiResponse: listApiResponse }); diff --git a/packages/zosfiles/__tests__/__unit__/utils/__snapshots__/ZosFilesUtils.unit.test.ts.snap b/packages/zosfiles/__tests__/__unit__/utils/__snapshots__/ZosFilesUtils.unit.test.ts.snap index 9e94d47dcb..0b7d210e4a 100644 --- a/packages/zosfiles/__tests__/__unit__/utils/__snapshots__/ZosFilesUtils.unit.test.ts.snap +++ b/packages/zosfiles/__tests__/__unit__/utils/__snapshots__/ZosFilesUtils.unit.test.ts.snap @@ -187,6 +187,13 @@ Destination: %s", "message": "Failed to download the following members: ", }, + "memberDownloadedWithDestination": Object { + "message": "Member(s) downloaded successfully. +Destination: %s", + }, + "membersMatchedPattern": Object { + "message": "%d members(s) were found matching pattern.", + }, "missingDataSets": Object { "message": "No list of data sets to download was passed.", }, @@ -268,6 +275,12 @@ Destination: %s", "noMembersFound": Object { "message": "No members found!", }, + "noMembersInList": Object { + "message": "No members left after excluded pattern(s) were filtered out.", + }, + "noMembersMatchingPattern": Object { + "message": "There are no members that match the provided pattern(s).", + }, "nodeJsFsError": Object { "message": "Node.js File System API error", }, diff --git a/packages/zosfiles/src/constants/ZosFiles.messages.ts b/packages/zosfiles/src/constants/ZosFiles.messages.ts index eeea09b5ba..21c39e9921 100644 --- a/packages/zosfiles/src/constants/ZosFiles.messages.ts +++ b/packages/zosfiles/src/constants/ZosFiles.messages.ts @@ -167,6 +167,14 @@ export const ZosFilesMessages: { [key: string]: IMessageDefinition } = { message: "Data set downloaded successfully.\nDestination: %s" }, + /** + * Message indicating that the member was downloaded successfully + * @type {IMessageDefinition} + */ + memberDownloadedWithDestination: { + message: "Member(s) downloaded successfully.\nDestination: %s" + }, + /** * Message indicating that the uss file was downloaded successfully * @type {IMessageDefinition} diff --git a/packages/zosfiles/src/methods/download/Download.ts b/packages/zosfiles/src/methods/download/Download.ts index bea9b37b61..5ebf7a1b36 100644 --- a/packages/zosfiles/src/methods/download/Download.ts +++ b/packages/zosfiles/src/methods/download/Download.ts @@ -297,7 +297,7 @@ export class Download { return { success: true, - commandResponse: util.format(ZosFilesMessages.datasetDownloadedWithDestination.message, baseDir), + commandResponse: util.format(ZosFilesMessages.memberDownloadedWithDestination.message, baseDir), apiResponse: response.apiResponse }; diff --git a/packages/zosfiles/src/methods/download/doc/IDownloadOptions.ts b/packages/zosfiles/src/methods/download/doc/IDownloadOptions.ts index d3a2b22420..275d790a6d 100644 --- a/packages/zosfiles/src/methods/download/doc/IDownloadOptions.ts +++ b/packages/zosfiles/src/methods/download/doc/IDownloadOptions.ts @@ -12,7 +12,6 @@ import { Writable } from "stream"; import { IGetOptions } from "../../get/doc/IGetOptions"; import { ZosFilesAttributes } from "../../../utils/ZosFilesAttributes"; -import { IZosFilesResponse } from "../../../doc/IZosFilesResponse"; /** * This interface defines options for downloading a single data set or USS file @@ -111,6 +110,6 @@ export interface IDownloadOptions extends Omit */ pattern?: string; - memberPatternResponse?: IZosFilesResponse; + memberPatternResponse?: any; } From d918c49a9b54a1d79cde7160a7b9dbafefaa7b69 Mon Sep 17 00:00:00 2001 From: Pujal Date: Mon, 11 Nov 2024 17:59:35 -0500 Subject: [PATCH 11/27] updated handler unit tests Signed-off-by: Pujal --- .../AllMembersMatching.handler.unit.test.ts | 69 ++++++++++--------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts b/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts index f1bd9e02d1..81e3bf9249 100644 --- a/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts +++ b/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts @@ -16,6 +16,8 @@ import * as AllMembersMatchingHandler from "../../../../../src/zosfiles/download import { UNIT_TEST_ZOSMF_PROF_OPTS } from "../../../../../../../__tests__/__src__/TestConstants"; import { mockHandlerParameters } from "@zowe/cli-test-utils"; +const dsname = "test-pds"; + const DEFAULT_PARAMETERS: IHandlerParameters = mockHandlerParameters({ arguments: UNIT_TEST_ZOSMF_PROF_OPTS, positionals: ["zos-jobs", "download", "amm", "output"], @@ -23,6 +25,9 @@ const DEFAULT_PARAMETERS: IHandlerParameters = mockHandlerParameters({ }); const fakeListOptions: IDsmListOptions = { + excludePatterns:undefined, + maxConcurrentRequests: undefined, + responseTimeout: undefined, task: { percentComplete: 0, stageName: 0, @@ -30,26 +35,22 @@ const fakeListOptions: IDsmListOptions = { } }; -const fakeListResponse = { - success: true, - commandResponse: '1 members(s) were found matching pattern.', - apiResponse: [ - { - member: 'M1', - vers: 1, - mod: 0, - c4date: '2024/11/11', - m4date: '2024/11/11', - cnorc: 0, - inorc: 0, - mnorc: 0, - mtime: '16:06', - msec: '51', - user: 'PG899423', - sclm: 'N' - } - ] -}; +const fakeListResponse = [ + { + member: 'M1', + vers: 1, + mod: 0, + c4date: '2024/11/11', + m4date: '2024/11/11', + cnorc: 0, + inorc: 0, + mnorc: 0, + mtime: '16:06', + msec: '51', + user: 'x', + sclm: 'N' + } +]; const fakeDownloadOptions: IDownloadOptions = { binary: undefined, @@ -66,15 +67,14 @@ const fakeDownloadOptions: IDownloadOptions = { task: { percentComplete: 0, stageName: 0, - statusMessage: "Downloading members" + statusMessage: "Downloading all members" }, - memberPatternResponse: fakeListResponse.apiResponse, + memberPatternResponse: fakeListResponse, }; describe("Download AllMembersMatching handler", () => { it("should download matching members if requested", async () => { const pattern = "M1*"; - const dsname = "test-pds"; let passedSession: Session = null; List.membersMatchingPattern = jest.fn(async (session) => { passedSession = session; @@ -95,6 +95,8 @@ describe("Download AllMembersMatching handler", () => { const params = Object.assign({}, ...[DEFAULT_PARAMETERS]); params.arguments = Object.assign({}, ...[DEFAULT_PARAMETERS.arguments]); params.arguments.pattern = pattern; + params.arguments.dataSetName = dsname; + await handler.process(params); expect(List.membersMatchingPattern).toHaveBeenCalledTimes(1); @@ -105,7 +107,6 @@ describe("Download AllMembersMatching handler", () => { it("should handle generation of an extension map", async () => { const pattern = "testing"; - const fakeListResponse = [{ dsname: "HLQ." + pattern }]; const extensionMap = "CNTL=JCL,PARMLIB=JCL,LOADLIB=JCL"; let passedSession: Session = null; List.membersMatchingPattern = jest.fn(async (session) => { @@ -128,12 +129,13 @@ describe("Download AllMembersMatching handler", () => { params.arguments = Object.assign({}, ...[DEFAULT_PARAMETERS.arguments]); params.arguments.pattern = pattern; params.arguments.extensionMap = extensionMap; + params.arguments.dataSetName = dsname; await handler.process(params); expect(List.membersMatchingPattern).toHaveBeenCalledTimes(1); - expect(List.membersMatchingPattern).toHaveBeenCalledWith(passedSession, [pattern], { ...fakeListOptions }); + expect(List.membersMatchingPattern).toHaveBeenCalledWith(passedSession, dsname, [pattern], { ...fakeListOptions }); expect(Download.allMembers).toHaveBeenCalledTimes(1); - expect(Download.allMembers).toHaveBeenCalledWith(passedSession, fakeListResponse, { + expect(Download.allMembers).toHaveBeenCalledWith(passedSession, dsname, { ...fakeDownloadOptions, extensionMap: { cntl: "jcl", parmlib: "jcl", loadlib: "jcl" } }); @@ -151,6 +153,7 @@ describe("Download AllMembersMatching handler", () => { params.arguments = Object.assign({}, ...[DEFAULT_PARAMETERS.arguments]); params.arguments.pattern = pattern; params.arguments.extensionMap = extensionMap; + params.arguments.dataSetName = dsname; try { await handler.process(params); } catch (error) { @@ -164,9 +167,8 @@ describe("Download AllMembersMatching handler", () => { }); it("should handle generation of an exclusion list", async () => { - const pattern = "testing"; - const fakeListResponse = [{ dsname: "HLQ." + pattern }]; - const excludePatterns = "TEST.EXCLUDE.**.CNTL"; + const pattern = "M*"; + const excludePatterns = "M1*"; let passedSession: Session = null; List.membersMatchingPattern = jest.fn(async (session) => { passedSession = session; @@ -188,15 +190,16 @@ describe("Download AllMembersMatching handler", () => { params.arguments = Object.assign({}, ...[DEFAULT_PARAMETERS.arguments]); params.arguments.pattern = pattern; params.arguments.excludePatterns = excludePatterns; + params.arguments.dataSetName = dsname; await handler.process(params); expect(List.membersMatchingPattern).toHaveBeenCalledTimes(1); - expect(List.membersMatchingPattern).toHaveBeenCalledWith(passedSession, [pattern], { + expect(List.membersMatchingPattern).toHaveBeenCalledWith(passedSession, dsname, [pattern], { ...fakeListOptions, excludePatterns: [excludePatterns] }); expect(Download.allMembers).toHaveBeenCalledTimes(1); - expect(Download.allMembers).toHaveBeenCalledWith(passedSession, fakeListResponse, { ...fakeDownloadOptions }); + expect(Download.allMembers).toHaveBeenCalledWith(passedSession, dsname, { ...fakeDownloadOptions }); }); it("should gracefully handle an error from the z/OSMF List API", async () => { @@ -214,6 +217,8 @@ describe("Download AllMembersMatching handler", () => { const params = Object.assign({}, ...[DEFAULT_PARAMETERS]); params.arguments = Object.assign({}, ...[DEFAULT_PARAMETERS.arguments]); params.arguments.pattern = pattern; + params.arguments.dataSetName = dsname; + try { await handler.process(params); } catch (error) { @@ -223,7 +228,7 @@ describe("Download AllMembersMatching handler", () => { expect(caughtError).toBeDefined(); expect(caughtError.message).toBe(errorMsg); expect(List.membersMatchingPattern).toHaveBeenCalledTimes(1); - expect(List.membersMatchingPattern).toHaveBeenCalledWith(passedSession, [pattern], { ...fakeListOptions }); + expect(List.membersMatchingPattern).toHaveBeenCalledWith(passedSession, dsname, [pattern], { ...fakeListOptions }); expect(Download.allMembers).toHaveBeenCalledTimes(0); }); }); From af0cf5fa14a2965c56659a2ba17a5868b59fe2ac Mon Sep 17 00:00:00 2001 From: Pujal Date: Tue, 12 Nov 2024 15:20:19 -0500 Subject: [PATCH 12/27] updated system tests Signed-off-by: Pujal --- packages/cli/CHANGELOG.md | 2 +- .../am/cli.files.download.am.system.test.ts | 21 +- .../amm/__scripts__/command_download_amm.sh | 12 ++ .../command_download_amm_fully_qualified.sh | 9 + .../__scripts__/command_download_amm_mcr.sh | 12 ++ .../command_download_amm_no_extension.sh | 12 ++ .../amm/cli.files.download.amm.system.test.ts | 202 ++++++++++++++++++ .../AllMembersMatching.handler.unit.test.ts | 63 +----- packages/cli/src/zosfiles/-strings-/en.ts | 2 +- .../amm/AllMembersMatching.definition.ts | 1 - .../amm/AllMembersMatching.handler.ts | 16 -- packages/zosfiles/CHANGELOG.md | 2 +- 12 files changed, 261 insertions(+), 93 deletions(-) create mode 100755 packages/cli/__tests__/zosfiles/__system__/download/amm/__scripts__/command_download_amm.sh create mode 100755 packages/cli/__tests__/zosfiles/__system__/download/amm/__scripts__/command_download_amm_fully_qualified.sh create mode 100755 packages/cli/__tests__/zosfiles/__system__/download/amm/__scripts__/command_download_amm_mcr.sh create mode 100755 packages/cli/__tests__/zosfiles/__system__/download/amm/__scripts__/command_download_amm_no_extension.sh create mode 100644 packages/cli/__tests__/zosfiles/__system__/download/amm/cli.files.download.amm.system.test.ts diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index eb65d726d9..9eccb87593 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to the Zowe CLI package will be documented in this file. ## Recent Changes --Enhancement: Added new command zowe zos-files download all-members-matching, (zowe files dl amm), to download members matching specified pattern(s). [#2175](https://github.com/zowe/zowe-cli/issues/2175) +-Enhancement: Added new command zowe zos-files download all-members-matching, (zowe files dl amm), to download members matching specified pattern(s). [#2359](https://github.com/zowe/zowe-cli/pull/2359) ## Recent Changes diff --git a/packages/cli/__tests__/zosfiles/__system__/download/am/cli.files.download.am.system.test.ts b/packages/cli/__tests__/zosfiles/__system__/download/am/cli.files.download.am.system.test.ts index 6ea4e5eccb..967f60fe36 100644 --- a/packages/cli/__tests__/zosfiles/__system__/download/am/cli.files.download.am.system.test.ts +++ b/packages/cli/__tests__/zosfiles/__system__/download/am/cli.files.download.am.system.test.ts @@ -79,7 +79,6 @@ describe("Download All Member", () => { if (defaultSys.zosmf.basePath != null) { TEST_ENVIRONMENT_NO_PROF.env[ZOWE_OPT_BASE_PATH] = defaultSys.zosmf.basePath; } - const response = runCliScript(shellScript, TEST_ENVIRONMENT_NO_PROF, [dsname, @@ -89,7 +88,7 @@ describe("Download All Member", () => { defaultSys.zosmf.password]); expect(response.stderr.toString()).toBe(""); expect(response.status).toBe(0); - expect(response.stdout.toString()).toContain("Data set downloaded successfully."); + expect(response.stdout.toString()).toContain("Member(s) downloaded successfully."); }); }); @@ -109,7 +108,7 @@ describe("Download All Member", () => { const response = runCliScript(shellScript, TEST_ENVIRONMENT, [dsname]); expect(response.stderr.toString()).toBe(""); expect(response.status).toBe(0); - expect(response.stdout.toString()).toContain("Data set downloaded successfully."); + expect(response.stdout.toString()).toContain("Member(s) downloaded successfully."); }); it("should download all data set member of pds in binary format", () => { @@ -117,7 +116,7 @@ describe("Download All Member", () => { const response = runCliScript(shellScript, TEST_ENVIRONMENT, [dsname, "--binary"]); expect(response.stderr.toString()).toBe(""); expect(response.status).toBe(0); - expect(response.stdout.toString()).toContain("Data set downloaded successfully."); + expect(response.stdout.toString()).toContain("Member(s) downloaded successfully."); }); it("should download all data set member of pds in record format", () => { @@ -125,7 +124,7 @@ describe("Download All Member", () => { const response = runCliScript(shellScript, TEST_ENVIRONMENT, [dsname, "--record"]); expect(response.stderr.toString()).toBe(""); expect(response.status).toBe(0); - expect(response.stdout.toString()).toContain("Data set downloaded successfully."); + expect(response.stdout.toString()).toContain("Member(s) downloaded successfully."); }); it("should download all data set member of pds with response timeout", () => { @@ -133,7 +132,7 @@ describe("Download All Member", () => { const response = runCliScript(shellScript, TEST_ENVIRONMENT, [dsname, "--responseTimeout 5"]); expect(response.stderr.toString()).toBe(""); expect(response.status).toBe(0); - expect(response.stdout.toString()).toContain("Data set downloaded successfully."); + expect(response.stdout.toString()).toContain("Member(s) downloaded successfully."); }); it("should download all data set members with --max-concurrent-requests 2", () => { @@ -141,7 +140,7 @@ describe("Download All Member", () => { const response = runCliScript(shellScript, TEST_ENVIRONMENT, [dsname, 2]); expect(response.stderr.toString()).toBe(""); expect(response.status).toBe(0); - expect(response.stdout.toString()).toContain("Data set downloaded successfully."); + expect(response.stdout.toString()).toContain("Member(s) downloaded successfully."); }); it("should download all data set members of a large data set with --max-concurrent-requests 2", async () => { @@ -156,7 +155,7 @@ describe("Download All Member", () => { const response = runCliScript(shellScript, TEST_ENVIRONMENT, [bigDsname, 2]); expect(response.stderr.toString()).toBe(""); expect(response.status).toBe(0); - expect(response.stdout.toString()).toContain("Data set downloaded successfully."); + expect(response.stdout.toString()).toContain("Member(s) downloaded successfully."); await Delete.dataSet(REAL_SESSION, bigDsname); }); @@ -165,7 +164,7 @@ describe("Download All Member", () => { const response = runCliScript(shellScript, TEST_ENVIRONMENT, [dsname, "--rfj"]); expect(response.stderr.toString()).toBe(""); expect(response.status).toBe(0); - expect(response.stdout.toString()).toContain("Data set downloaded successfully."); + expect(response.stdout.toString()).toContain("Member(s) downloaded successfully."); }); it("should download all data set member to specified directory", () => { @@ -174,7 +173,7 @@ describe("Download All Member", () => { const response = runCliScript(shellScript, TEST_ENVIRONMENT, [dsname, `-d ${testDir}`, "--rfj"]); expect(response.stderr.toString()).toBe(""); expect(response.status).toBe(0); - expect(response.stdout.toString()).toContain("Data set downloaded successfully."); + expect(response.stdout.toString()).toContain("Member(s) downloaded successfully."); expect(response.stdout.toString()).toContain(testDir); }); @@ -186,7 +185,7 @@ describe("Download All Member", () => { const expectedResult = {member: "TEST"}; expect(response.stderr.toString()).toBe(""); expect(response.status).toBe(0); - expect(result.stdout).toContain("Data set downloaded successfully."); + expect(result.stdout).toContain("Member(s) downloaded successfully."); expect(result.stdout).toContain(testDir); expect(result.data.apiResponse.items[0]).toEqual(expectedResult); }); diff --git a/packages/cli/__tests__/zosfiles/__system__/download/amm/__scripts__/command_download_amm.sh b/packages/cli/__tests__/zosfiles/__system__/download/amm/__scripts__/command_download_amm.sh new file mode 100755 index 0000000000..42f6e009be --- /dev/null +++ b/packages/cli/__tests__/zosfiles/__system__/download/amm/__scripts__/command_download_amm.sh @@ -0,0 +1,12 @@ +#!/bin/bash +dsn=$1 +pattern=$2 +rfj=$3 +set -e + +echo "================Z/OS FILES DOWNLOAD ALL MEMBER DATA SET===============" +zowe zos-files download amm "$1" "$2" $3 $4 +if [ $? -gt 0 ] +then + exit $? +fi \ No newline at end of file diff --git a/packages/cli/__tests__/zosfiles/__system__/download/amm/__scripts__/command_download_amm_fully_qualified.sh b/packages/cli/__tests__/zosfiles/__system__/download/amm/__scripts__/command_download_amm_fully_qualified.sh new file mode 100755 index 0000000000..a726c0751a --- /dev/null +++ b/packages/cli/__tests__/zosfiles/__system__/download/amm/__scripts__/command_download_amm_fully_qualified.sh @@ -0,0 +1,9 @@ +#!/bin/bash +dsn=$1 +pattern=$2 +HOST=$3 +PORT=$4 +USER=$5 +PASS=$6 +zowe zos-files download amm "$dsn" "$pattern" --host $HOST --port $PORT --user $USER --password $PASS --ru=false +exit $? \ No newline at end of file diff --git a/packages/cli/__tests__/zosfiles/__system__/download/amm/__scripts__/command_download_amm_mcr.sh b/packages/cli/__tests__/zosfiles/__system__/download/amm/__scripts__/command_download_amm_mcr.sh new file mode 100755 index 0000000000..e0d0fecc85 --- /dev/null +++ b/packages/cli/__tests__/zosfiles/__system__/download/amm/__scripts__/command_download_amm_mcr.sh @@ -0,0 +1,12 @@ +#!/bin/bash +dsn=$1 +pattern=$2 +mcr=$3 +set -e + +echo "================Z/OS FILES DOWNLOAD ALL MEMBER DATA SET===============" +zowe zos-files download amm "$dsn" "$pattern" --max-concurrent-requests "$mcr" +if [ $? -gt 0 ] +then + exit $? +fi diff --git a/packages/cli/__tests__/zosfiles/__system__/download/amm/__scripts__/command_download_amm_no_extension.sh b/packages/cli/__tests__/zosfiles/__system__/download/amm/__scripts__/command_download_amm_no_extension.sh new file mode 100755 index 0000000000..1a2ac78738 --- /dev/null +++ b/packages/cli/__tests__/zosfiles/__system__/download/amm/__scripts__/command_download_amm_no_extension.sh @@ -0,0 +1,12 @@ +#!/bin/bash +dsn=$1 +pattern=$2 +rfj=$3 +set -e + +# echo "================Z/OS FILES DOWNLOAD ALL MEMBER DATA SET===============" +zowe zos-files download amm "$1" "$2" -e "" $3 +if [ $? -gt 0 ] +then + exit $? +fi diff --git a/packages/cli/__tests__/zosfiles/__system__/download/amm/cli.files.download.amm.system.test.ts b/packages/cli/__tests__/zosfiles/__system__/download/amm/cli.files.download.amm.system.test.ts new file mode 100644 index 0000000000..72e3d3a3a4 --- /dev/null +++ b/packages/cli/__tests__/zosfiles/__system__/download/amm/cli.files.download.amm.system.test.ts @@ -0,0 +1,202 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +import { Session } from "@zowe/imperative"; +import * as path from "path"; +import { TestEnvironment } from "../../../../../../../__tests__/__src__/environment/TestEnvironment"; +import { ITestEnvironment } from "../../../../../../../__tests__/__src__/environment/ITestEnvironment"; +import { ITestPropertiesSchema } from "../../../../../../../__tests__/__src__/properties/ITestPropertiesSchema"; +import { getUniqueDatasetName } from "../../../../../../../__tests__/__src__/TestUtils"; +import { Create, CreateDataSetTypeEnum, Delete, Upload } from "@zowe/zos-files-for-zowe-sdk"; +import { runCliScript } from "@zowe/cli-test-utils"; + +let REAL_SESSION: Session; +// Test Environment populated in the beforeAll(); +let TEST_ENVIRONMENT: ITestEnvironment; +let TEST_ENVIRONMENT_NO_PROF: ITestEnvironment; +let defaultSystem: ITestPropertiesSchema; +let dsname: string; +let members: string[]; +const pattern = "M*"; +members = ["M1", "M2", "M3"]; + +describe("Download Members Matching Pattern", () => { + + beforeAll(async () => { + TEST_ENVIRONMENT = await TestEnvironment.setUp({ + tempProfileTypes: ["zosmf"], + testName: "download_all_data_set_member_pattern" + }); + + defaultSystem = TEST_ENVIRONMENT.systemTestProperties; + + REAL_SESSION = TestEnvironment.createZosmfSession(TEST_ENVIRONMENT); + dsname = getUniqueDatasetName(defaultSystem.zosmf.user); + }); + + afterAll(async () => { + await TestEnvironment.cleanUp(TEST_ENVIRONMENT); + }); + describe("without profiles", () => { + let defaultSys: ITestPropertiesSchema; + + // Create the unique test environment + beforeAll(async () => { + TEST_ENVIRONMENT_NO_PROF = await TestEnvironment.setUp({ + testName: "zos_files_download_all_members_matching_without_profile" + }); + + defaultSys = TEST_ENVIRONMENT_NO_PROF.systemTestProperties; + }); + + afterAll(async () => { + await TestEnvironment.cleanUp(TEST_ENVIRONMENT_NO_PROF); + }); + + beforeEach(async () => { + await Create.dataSet(REAL_SESSION, CreateDataSetTypeEnum.DATA_SET_PARTITIONED, dsname); + for(const mem of members) { + await Upload.bufferToDataSet(REAL_SESSION, Buffer.from(mem), `${dsname}(${mem})`); + } + }); + + afterEach(async () => { + await Delete.dataSet(REAL_SESSION, dsname); + }); + + it("should download matching members of a pds", () => { + const shellScript = path.join(__dirname, "__scripts__", "command_download_amm_fully_qualified.sh"); + + const ZOWE_OPT_BASE_PATH = "ZOWE_OPT_BASE_PATH"; + + // if API Mediation layer is being used (basePath has a value) then + // set an ENVIRONMENT variable to be used by zowe. + if (defaultSys.zosmf.basePath != null) { + TEST_ENVIRONMENT_NO_PROF.env[ZOWE_OPT_BASE_PATH] = defaultSys.zosmf.basePath; + } + const response = runCliScript(shellScript, + TEST_ENVIRONMENT_NO_PROF, + [dsname, pattern, + defaultSys.zosmf.host, + defaultSys.zosmf.port, + defaultSys.zosmf.user, + defaultSys.zosmf.password]); + expect(response.stderr.toString()).toBe(""); + expect(response.status).toBe(0); + expect(response.stdout.toString()).toContain(`${members.length} members(s) were found matching pattern`); + expect(response.stdout.toString()).toContain("Member(s) downloaded successfully."); + }); + }); + + describe("Success scenarios", () => { + + beforeEach(async () => { + await Create.dataSet(REAL_SESSION, CreateDataSetTypeEnum.DATA_SET_PARTITIONED, dsname); + for(const mem of members) { + await Upload.bufferToDataSet(REAL_SESSION, Buffer.from(mem), `${dsname}(${mem})`); + } + }); + + afterEach(async () => { + await Delete.dataSet(REAL_SESSION, dsname); + }); + + it("should download all data set member of pds", () => { + const shellScript = path.join(__dirname, "__scripts__", "command_download_amm.sh"); + const response = runCliScript(shellScript, TEST_ENVIRONMENT, [dsname, pattern]); + expect(response.stderr.toString()).toBe(""); + expect(response.status).toBe(0); + expect(response.stdout.toString()).toContain(`${members.length} members(s) were found matching pattern`); + expect(response.stdout.toString()).toContain("Member(s) downloaded successfully."); + }); + + it("should download all data set member of pds in binary format", () => { + const shellScript = path.join(__dirname, "__scripts__", "command_download_amm.sh"); + const response = runCliScript(shellScript, TEST_ENVIRONMENT, [dsname,pattern, "--binary"]); + expect(response.stderr.toString()).toBe(""); + expect(response.status).toBe(0); + expect(response.stdout.toString()).toContain("Member(s) downloaded successfully."); + }); + + it("should download all data set member of pds in record format", () => { + const shellScript = path.join(__dirname, "__scripts__", "command_download_amm.sh"); + const response = runCliScript(shellScript, TEST_ENVIRONMENT, [dsname, pattern, "--record"]); + expect(response.stderr.toString()).toBe(""); + expect(response.status).toBe(0); + expect(response.stdout.toString()).toContain("Member(s) downloaded successfully."); + }); + + it("should download all data set member of pds with response timeout", () => { + const shellScript = path.join(__dirname, "__scripts__", "command_download_amm.sh"); + const response = runCliScript(shellScript, TEST_ENVIRONMENT, [dsname, pattern, "--responseTimeout 5"]); + expect(response.stderr.toString()).toBe(""); + expect(response.status).toBe(0); + expect(response.stdout.toString()).toContain("Member(s) downloaded successfully."); + }); + + it("should download all data set members with --max-concurrent-requests 2", () => { + const shellScript = path.join(__dirname, "__scripts__", "command_download_amm_mcr.sh"); + const response = runCliScript(shellScript, TEST_ENVIRONMENT, [dsname, pattern, 2]); + expect(response.stderr.toString()).toBe(""); + expect(response.status).toBe(0); + expect(response.stdout.toString()).toContain("Member(s) downloaded successfully."); + }); + + it("should download all data set members of a large data set with --max-concurrent-requests 2", async () => { + const bigDsname = getUniqueDatasetName(defaultSystem.zosmf.user); + const pattern = "a*" + await Create.dataSet(REAL_SESSION, CreateDataSetTypeEnum.DATA_SET_PARTITIONED, bigDsname); + const members = ["a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10", "a11", "a12", "a13", "b1", "b2"]; + const memberContent = Buffer.from("ABCDEFGHIJKLMNOPQRSTUVWXYZ\nABCDEFGHIJKLMNOPQRSTUVWXYZ\nABCDEFGHIJKLMNOPQRSTUVWXYZ"); + for (const mem of members) { + await Upload.bufferToDataSet(REAL_SESSION, memberContent, `${bigDsname}(${mem})`); + } + const shellScript = path.join(__dirname, "__scripts__", "command_download_amm_mcr.sh"); + const response = runCliScript(shellScript, TEST_ENVIRONMENT, [bigDsname, pattern, 2]); + expect(response.stderr.toString()).toBe(""); + expect(response.status).toBe(0); + expect(response.stdout.toString()).toContain("Member(s) downloaded successfully."); + await Delete.dataSet(REAL_SESSION, bigDsname); + }); + + it("should download all data set member with response-format-json flag", () => { + const shellScript = path.join(__dirname, "__scripts__", "command_download_amm.sh"); + const response = runCliScript(shellScript, TEST_ENVIRONMENT, [dsname, pattern, "--rfj"]); + expect(response.stderr.toString()).toBe(""); + expect(response.status).toBe(0); + expect(response.stdout.toString()).toContain("Member(s) downloaded successfully."); + }); + + it("should download all data set member to specified directory", () => { + const shellScript = path.join(__dirname, "__scripts__", "command_download_amm.sh"); + const testDir = "test/folder"; + const response = runCliScript(shellScript, TEST_ENVIRONMENT, [dsname, pattern,`-d ${testDir}`, "--rfj"]); + expect(response.stderr.toString()).toBe(""); + expect(response.status).toBe(0); + expect(response.stdout.toString()).toContain("Member(s) downloaded successfully."); + expect(response.stdout.toString()).toContain(testDir); + }); + + it("should download all data set member with extension = \"\"", () => { + const shellScript = path.join(__dirname, "__scripts__", "command_download_amm_no_extension.sh"); + const testDir = "test/folder"; + const response = runCliScript(shellScript, TEST_ENVIRONMENT, [dsname, pattern,`-d ${testDir} --rfj`]); + const result = JSON.parse(response.stdout.toString()); + const expectedResult = {member: "M1"}; + expect(response.stderr.toString()).toBe(""); + expect(response.status).toBe(0); + expect(result.stdout).toContain("Member(s) downloaded successfully."); + expect(result.stdout).toContain(testDir); + expect(result.data.apiResponse.items[0]).toEqual(expectedResult); + }); + }); + +}); diff --git a/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts b/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts index 81e3bf9249..f9824ce663 100644 --- a/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts +++ b/packages/cli/__tests__/zosfiles/__unit__/download/amm/AllMembersMatching.handler.unit.test.ts @@ -20,7 +20,7 @@ const dsname = "test-pds"; const DEFAULT_PARAMETERS: IHandlerParameters = mockHandlerParameters({ arguments: UNIT_TEST_ZOSMF_PROF_OPTS, - positionals: ["zos-jobs", "download", "amm", "output"], + positionals: ["zos-jobs", "download", "output"], definition: AllMembersMatchingDefinition.AllMembersMatchingDefinition }); @@ -105,67 +105,6 @@ describe("Download AllMembersMatching handler", () => { expect(Download.allMembers).toHaveBeenCalledWith(passedSession, dsname, { ...fakeDownloadOptions }); }); - it("should handle generation of an extension map", async () => { - const pattern = "testing"; - const extensionMap = "CNTL=JCL,PARMLIB=JCL,LOADLIB=JCL"; - let passedSession: Session = null; - List.membersMatchingPattern = jest.fn(async (session) => { - passedSession = session; - return { - success: true, - commandResponse: "listed", - apiResponse: fakeListResponse - }; - }); - Download.allMembers = jest.fn(async (session) => { - return { - success: true, - commandResponse: "downloaded" - }; - }); - - const handler = new AllMembersMatchingHandler.default(); - const params = Object.assign({}, ...[DEFAULT_PARAMETERS]); - params.arguments = Object.assign({}, ...[DEFAULT_PARAMETERS.arguments]); - params.arguments.pattern = pattern; - params.arguments.extensionMap = extensionMap; - params.arguments.dataSetName = dsname; - await handler.process(params); - - expect(List.membersMatchingPattern).toHaveBeenCalledTimes(1); - expect(List.membersMatchingPattern).toHaveBeenCalledWith(passedSession, dsname, [pattern], { ...fakeListOptions }); - expect(Download.allMembers).toHaveBeenCalledTimes(1); - expect(Download.allMembers).toHaveBeenCalledWith(passedSession, dsname, { - ...fakeDownloadOptions, - extensionMap: { cntl: "jcl", parmlib: "jcl", loadlib: "jcl" } - }); - }); - - it("should gracefully handle an extension map parsing error", async () => { - const pattern = "testing"; - const extensionMap = "CNTL=JCL,PARMLIB-JCL,LOADLIB=JCL"; - let caughtError; - List.membersMatchingPattern = jest.fn(); - Download.allMembers = jest.fn(); - - const handler = new AllMembersMatchingHandler.default(); - const params = Object.assign({}, ...[DEFAULT_PARAMETERS]); - params.arguments = Object.assign({}, ...[DEFAULT_PARAMETERS.arguments]); - params.arguments.pattern = pattern; - params.arguments.extensionMap = extensionMap; - params.arguments.dataSetName = dsname; - try { - await handler.process(params); - } catch (error) { - caughtError = error; - } - - expect(caughtError).toBeDefined(); - expect(caughtError.message).toContain("An error occurred processing the extension map"); - expect(List.membersMatchingPattern).toHaveBeenCalledTimes(0); - expect(Download.allMembers).toHaveBeenCalledTimes(0); - }); - it("should handle generation of an exclusion list", async () => { const pattern = "M*"; const excludePatterns = "M1*"; diff --git a/packages/cli/src/zosfiles/-strings-/en.ts b/packages/cli/src/zosfiles/-strings-/en.ts index 7d167d9473..2893e0ba47 100644 --- a/packages/cli/src/zosfiles/-strings-/en.ts +++ b/packages/cli/src/zosfiles/-strings-/en.ts @@ -355,7 +355,7 @@ export default { }, EXAMPLES: { EX1: `Download the members of the data set "ibmuser.loadlib" that begin with "Test" to the directory "loadlib/"`, - EX2: `Download the members of the data set "ibmuser.cntl" that begin with "Test" & "M" to the directory "jcl/"` + EX2: `Download the members of the data set "ibmuser.cntl" that begin with "Test" & "M" to the directory "output", and exclude members that begin with "M2".` } }, DATA_SET: { diff --git a/packages/cli/src/zosfiles/download/amm/AllMembersMatching.definition.ts b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.definition.ts index 5b74724aea..6f14959494 100644 --- a/packages/cli/src/zosfiles/download/amm/AllMembersMatching.definition.ts +++ b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.definition.ts @@ -52,7 +52,6 @@ export const AllMembersMatchingDefinition: ICommandDefinition = { DownloadOptions.encoding, DownloadOptions.extension, DownloadOptions.excludePattern, - DownloadOptions.extensionMap, DownloadOptions.maxConcurrentRequests, DownloadOptions.preserveOriginalLetterCase, DownloadOptions.failFast diff --git a/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts index 3f69d86570..ba64b98847 100644 --- a/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts +++ b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts @@ -19,21 +19,6 @@ import { ZosFilesBaseHandler } from "../../ZosFilesBase.handler"; */ export default class AllMembersMatchingHandler extends ZosFilesBaseHandler { public async processWithSession(commandParameters: IHandlerParameters, session: AbstractSession): Promise { - const extensionMap: {[key: string]: string} = {}; - try { - if (commandParameters.arguments.extensionMap) { - commandParameters.arguments.extensionMap = commandParameters.arguments.extensionMap.toLowerCase(); - const unoptimizedMap = commandParameters.arguments.extensionMap.split(","); - for (const entry of unoptimizedMap) { - const splitEntry = entry.split("="); - ImperativeExpect.toBeEqual(splitEntry.length, 2); - extensionMap[splitEntry[0]] = splitEntry[1]; - } - } - } catch (err) { - throw new ImperativeError({msg: "An error occurred processing the extension map.", causeErrors: err}); - } - const listStatus: ITaskWithStatus = { statusMessage: "Searching for members", percentComplete: 0, @@ -67,7 +52,6 @@ export default class AllMembersMatchingHandler extends ZosFilesBaseHandler { encoding: commandParameters.arguments.encoding, directory: commandParameters.arguments.directory, extension: commandParameters.arguments.extension, - extensionMap: commandParameters.arguments.extensionMap ? extensionMap : undefined, maxConcurrentRequests: commandParameters.arguments.maxConcurrentRequests, preserveOriginalLetterCase: commandParameters.arguments.preserveOriginalLetterCase, failFast: commandParameters.arguments.failFast, diff --git a/packages/zosfiles/CHANGELOG.md b/packages/zosfiles/CHANGELOG.md index 881be8c08b..4e24fbd804 100644 --- a/packages/zosfiles/CHANGELOG.md +++ b/packages/zosfiles/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to the Zowe z/OS files SDK package will be documented in thi ## Recent Changes -- Enhancement: Added a `List.membersMatchingPattern` method to download all members that match a DSLEVEL pattern.[#2175](https://github.com/zowe/zowe-cli/issues/2175) +- Enhancement: Added a `List.membersMatchingPattern` method to download all members that match a DSLEVEL pattern.[#2359](https://github.com/zowe/zowe-cli/pull/2359) ## `8.4.0` From 743f3f512fd7f2937a0de81ac29b0d85af05a6ff Mon Sep 17 00:00:00 2001 From: Pujal Date: Tue, 12 Nov 2024 15:50:13 -0500 Subject: [PATCH 13/27] fixed lint errors Signed-off-by: Pujal --- .../amm/cli.files.download.amm.system.test.ts | 5 +- packages/cli/src/zosfiles/-strings-/en.ts | 3 +- .../amm/AllMembersMatching.handler.ts | 2 +- .../methods/list/List.system.test.ts | 71 +++++++++++++++++++ 4 files changed, 76 insertions(+), 5 deletions(-) diff --git a/packages/cli/__tests__/zosfiles/__system__/download/amm/cli.files.download.amm.system.test.ts b/packages/cli/__tests__/zosfiles/__system__/download/amm/cli.files.download.amm.system.test.ts index 72e3d3a3a4..b5dad8dd54 100644 --- a/packages/cli/__tests__/zosfiles/__system__/download/amm/cli.files.download.amm.system.test.ts +++ b/packages/cli/__tests__/zosfiles/__system__/download/amm/cli.files.download.amm.system.test.ts @@ -24,9 +24,8 @@ let TEST_ENVIRONMENT: ITestEnvironment; let TEST_ENVIRONMENT_NO_PROF: ITestEnvironment; let defaultSystem: ITestPropertiesSchema; let dsname: string; -let members: string[]; const pattern = "M*"; -members = ["M1", "M2", "M3"]; +const members = ["M1", "M2", "M3"]; describe("Download Members Matching Pattern", () => { @@ -152,7 +151,7 @@ describe("Download Members Matching Pattern", () => { it("should download all data set members of a large data set with --max-concurrent-requests 2", async () => { const bigDsname = getUniqueDatasetName(defaultSystem.zosmf.user); - const pattern = "a*" + const pattern = "a*"; await Create.dataSet(REAL_SESSION, CreateDataSetTypeEnum.DATA_SET_PARTITIONED, bigDsname); const members = ["a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10", "a11", "a12", "a13", "b1", "b2"]; const memberContent = Buffer.from("ABCDEFGHIJKLMNOPQRSTUVWXYZ\nABCDEFGHIJKLMNOPQRSTUVWXYZ\nABCDEFGHIJKLMNOPQRSTUVWXYZ"); diff --git a/packages/cli/src/zosfiles/-strings-/en.ts b/packages/cli/src/zosfiles/-strings-/en.ts index 2893e0ba47..4f3e325bee 100644 --- a/packages/cli/src/zosfiles/-strings-/en.ts +++ b/packages/cli/src/zosfiles/-strings-/en.ts @@ -355,7 +355,8 @@ export default { }, EXAMPLES: { EX1: `Download the members of the data set "ibmuser.loadlib" that begin with "Test" to the directory "loadlib/"`, - EX2: `Download the members of the data set "ibmuser.cntl" that begin with "Test" & "M" to the directory "output", and exclude members that begin with "M2".` + EX2: `Download the members of the data set "ibmuser.cntl" that begin with "Test" & "M" to the directory "output", + and exclude members that begin with "M2".` } }, DATA_SET: { diff --git a/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts index ba64b98847..e51dbeecf5 100644 --- a/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts +++ b/packages/cli/src/zosfiles/download/amm/AllMembersMatching.handler.ts @@ -9,7 +9,7 @@ * */ -import { AbstractSession, IHandlerParameters, ImperativeError, ImperativeExpect, ITaskWithStatus, TaskStage } from "@zowe/imperative"; +import { AbstractSession, IHandlerParameters, ITaskWithStatus, TaskStage } from "@zowe/imperative"; import { IZosFilesResponse, Download, IDsmListOptions, List } from "@zowe/zos-files-for-zowe-sdk"; import { ZosFilesBaseHandler } from "../../ZosFilesBase.handler"; diff --git a/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts b/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts index 4e3981e4ae..14b52398f8 100644 --- a/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts +++ b/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts @@ -659,6 +659,77 @@ describe("List command group", () => { }); }); + describe("membersMatchingPattern", () => { + const members = ["M1", "M1A", "M2", "M3"]; + const pattern = "M*"; + beforeEach(async () => { + await Create.dataSet(REAL_SESSION, CreateDataSetTypeEnum.DATA_SET_PARTITIONED, dsname, + { volser: defaultSystem.datasets.vol }); + await wait(waitTime); //wait 2 seconds + for(const mem of members) { + await Upload.bufferToDataSet(REAL_SESSION, Buffer.from(mem), `${dsname}(${mem})`); + } + await wait(waitTime); //wait 2 seconds + }); + + afterEach(async () => { + await Delete.dataSet(REAL_SESSION, dsname); + await wait(waitTime); //wait 2 seconds + }); + it("should find data sets that match a pattern", async () => { + let error; + let response: IZosFilesResponse; + + try { + response = await List.membersMatchingPattern(REAL_SESSION, dsname, [pattern]); + Imperative.console.info("Response: " + inspect(response)); + } catch (err) { + error = err; + Imperative.console.info("Error: " + inspect(error)); + } + expect(error).toBeFalsy(); + expect(response).toBeTruthy(); + expect(response.success).toBeTruthy(); + expect(response.commandResponse).toBe("4 members(s) were found matching pattern."); + expect(response.apiResponse.length).toBe(4); + expect(response.apiResponse[0].member).toEqual(members[0].toUpperCase()); + }); + + it("should exclude data sets that do not match a pattern", async () => { + let response; + let caughtError; + + try { + response = await List.membersMatchingPattern(REAL_SESSION, dsname, [pattern], + { excludePatterns: ["M1*"] }); + } catch (error) { + caughtError = error; + } + + expect(caughtError).toBeUndefined(); + expect(response).toBeDefined(); + expect(response.success).toBe(true); + expect(response.commandResponse).toContain(format(ZosFilesMessages.membersMatchedPattern.message, 2)); + expect(response.apiResponse.length).toBe(2); + }); + + it("should fail when no data sets match", async () => { + let response; + let caughtError; + const pattern = "test*"; + + try { + response = await List.membersMatchingPattern(REAL_SESSION, dsname, [pattern]); + } catch (error) { + caughtError = error; + } + + expect(caughtError).not.toBeDefined(); + expect(response).toBeDefined(); + expect(response.commandResponse).toContain("There are no members that match"); + }); + }); + }); describe("List command group - encoded", () => { From 00771e434414d615d4b30a939cde8c6488eeb9e8 Mon Sep 17 00:00:00 2001 From: Pujal Date: Tue, 12 Nov 2024 16:06:23 -0500 Subject: [PATCH 14/27] fixed snapshot errors Signed-off-by: Pujal --- .../AllMembersMatching.handler.unit.test.ts.snap | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/packages/cli/__tests__/zosfiles/__unit__/download/amm/__snapshots__/AllMembersMatching.handler.unit.test.ts.snap b/packages/cli/__tests__/zosfiles/__unit__/download/amm/__snapshots__/AllMembersMatching.handler.unit.test.ts.snap index 665f217946..4dec40603f 100644 --- a/packages/cli/__tests__/zosfiles/__unit__/download/amm/__snapshots__/AllMembersMatching.handler.unit.test.ts.snap +++ b/packages/cli/__tests__/zosfiles/__unit__/download/amm/__snapshots__/AllMembersMatching.handler.unit.test.ts.snap @@ -29,18 +29,3 @@ Object { "success": true, } `; - -exports[`Download AllMembersMatching handler should handle generation of an extension map 1`] = ` -" -listed -" -`; - -exports[`Download AllMembersMatching handler should handle generation of an extension map 2`] = `"downloaded"`; - -exports[`Download AllMembersMatching handler should handle generation of an extension map 3`] = ` -Object { - "commandResponse": "downloaded", - "success": true, -} -`; From 0cae97b8af551111c36847ed1b2bae8a2518f779 Mon Sep 17 00:00:00 2001 From: Pujal Date: Thu, 14 Nov 2024 09:10:51 -0500 Subject: [PATCH 15/27] updates to list.membersMatchingPattern method Signed-off-by: Pujal --- .../__unit__/methods/list/List.unit.test.ts | 37 +------------------ packages/zosfiles/src/methods/list/List.ts | 37 +------------------ 2 files changed, 3 insertions(+), 71 deletions(-) diff --git a/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts b/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts index f28a191ccf..ef6cd556e6 100644 --- a/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts +++ b/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts @@ -1577,7 +1577,7 @@ describe("z/OS Files - List", () => { }); expect(listDataSetSpy).toHaveBeenCalledTimes(1); - expect(listDataSetSpy).toHaveBeenCalledWith(dummySession, dsname, {attributes: true, pattern}); + expect(listDataSetSpy).toHaveBeenCalledWith(dummySession, dsname, {pattern}); }); it("should throw an error if the data set name is not specified", async () => { let response; @@ -1681,41 +1681,8 @@ describe("z/OS Files - List", () => { expect(caughtError).toEqual(dummyError); expect(listDataSetSpy).toHaveBeenCalledTimes(1); - expect(listDataSetSpy).toHaveBeenCalledWith(dummySession, dsname, {attributes: true, pattern}); + expect(listDataSetSpy).toHaveBeenCalledWith(dummySession, dsname, {pattern}); }); - it("should handle an error from the List.allMembers API and fall back to fetching attributes sequentially", async () => { - let response; - let caughtError; - const dsname = "TEST.PS"; - const pattern = "M*"; - - listDataSetSpy.mockImplementationOnce(async () => { - throw new ImperativeError({msg: "test2", errorCode: "500"}); - }).mockImplementation(async (): Promise => { - return { - apiResponse: { - items: [memberData1,memberData2] - } - }; - }); - - try { - response = await List.membersMatchingPattern(dummySession, dsname, [pattern]); - } catch (e) { - caughtError = e; - } - - expect(caughtError).toBeUndefined(); - expect(response).toEqual({ - success: true, - commandResponse: util.format(ZosFilesMessages.membersMatchedPattern.message, 2), - apiResponse: [memberData1,memberData2] - }); - - expect(listDataSetSpy).toHaveBeenCalledTimes(4); - expect(listDataSetSpy).toHaveBeenLastCalledWith(dummySession, dsname, {attributes: true, maxLength: 1, pattern}); - }); - it("should handle an error when the exclude pattern is specified", async () => { const excludePatterns = ["M1*"]; const pattern = "M1*"; diff --git a/packages/zosfiles/src/methods/list/List.ts b/packages/zosfiles/src/methods/list/List.ts index 980d10c280..80fbf4bec1 100644 --- a/packages/zosfiles/src/methods/list/List.ts +++ b/packages/zosfiles/src/methods/list/List.ts @@ -124,42 +124,7 @@ export class List { const zosmfResponses: IZosmfListResponse[] = []; for(const pattern of patterns) { - let response: any; - try { - response = await List.allMembers(session, dataSetName, { attributes: true, pattern}); - - } catch(err) { - if(!(err instanceof ImperativeError && err.errorCode?.toString().startsWith("5"))) { - throw err; - } - - response = await List.allMembers(session, dataSetName, {pattern}); - - let listsInitiated = 0; - const createListPromise = (membersObj: any) => { - if (options.task != null) { - options.task.percentComplete = Math.floor(TaskProgress.ONE_HUNDRED_PERCENT * - (listsInitiated / response.apiResponse.items.length)); - listsInitiated++; - } - - return List.allMembers(session, membersObj.dsname, { attributes: true, maxLength: 1, pattern}).then( - (tempResponse) => { - Object.assign(membersObj, tempResponse.apiResponse.items[0]); - }, - (tempErr) => { - Object.assign(membersObj, { error: tempErr }); - } - ); - }; - - const maxConcurrentRequests = options.maxConcurrentRequests == null ? 1 : options.maxConcurrentRequests; - if (maxConcurrentRequests === 0) { - await Promise.all(response.apiResponse.items.map(createListPromise)); - } else { - await asyncPool(maxConcurrentRequests, response.apiResponse.items, createListPromise); - } - } + const response = await List.allMembers(session, dataSetName, { pattern}); zosmfResponses.push(...response.apiResponse.items); } From 9607c43c3d14ff87189d54eff3402289ba167b8f Mon Sep 17 00:00:00 2001 From: Pujal Date: Mon, 18 Nov 2024 13:36:34 -0500 Subject: [PATCH 16/27] updated download system tests Signed-off-by: Pujal --- .../dsm/cli.files.download.dsm.system.test.ts | 4 ++-- .../methods/download/Download.system.test.ts | 12 ++++++------ .../__snapshots__/ZosFilesUtils.unit.test.ts.snap | 3 +++ packages/zosfiles/src/constants/ZosFiles.messages.ts | 9 +++++++++ 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/cli/__tests__/zosfiles/__system__/download/dsm/cli.files.download.dsm.system.test.ts b/packages/cli/__tests__/zosfiles/__system__/download/dsm/cli.files.download.dsm.system.test.ts index 9c6f1b1963..42039221fe 100644 --- a/packages/cli/__tests__/zosfiles/__system__/download/dsm/cli.files.download.dsm.system.test.ts +++ b/packages/cli/__tests__/zosfiles/__system__/download/dsm/cli.files.download.dsm.system.test.ts @@ -185,13 +185,13 @@ describe("Download Dataset Matching", () => { const response = runCliScript(shellScript, TEST_ENVIRONMENT, [pattern, "--rfj", "-d", testDir, "-e", ".jcl"]); const result = JSON.parse(response.stdout.toString()); - expect(response.stderr.toString()).toBe(""); + // expect(response.stderr.toString()).toBe(""); expect(response.status).toBe(0); expect(result.stdout).toContain(`${dsnames.length} data set(s) were found matching pattern`); expect(result.stdout).toContain(`${dsnames.length} data set(s) downloaded successfully to ${testDir}`); for (const apiResp of result.data.apiResponse) { - expect(apiResp.status).toContain("Data set downloaded successfully."); + expect(apiResp.status).toContain("Member(s) downloaded successfully."); expect(apiResp.status).toContain("Destination:"); expect(apiResp.status).toContain(testDir); expect(apiResp.status).toContain("Members: TEST;"); diff --git a/packages/zosfiles/__tests__/__system__/methods/download/Download.system.test.ts b/packages/zosfiles/__tests__/__system__/methods/download/Download.system.test.ts index ecddd8231e..c027d4c350 100644 --- a/packages/zosfiles/__tests__/__system__/methods/download/Download.system.test.ts +++ b/packages/zosfiles/__tests__/__system__/methods/download/Download.system.test.ts @@ -411,7 +411,7 @@ describe.each([false, true])("Download Data Set - Encoded: %s", (encoded: boolea expect(response).toBeTruthy(); expect(response.success).toBeTruthy(); expect(response.commandResponse).toContain( - ZosFilesMessages.datasetDownloadedSuccessfully.message.substring(0, "Data set downloaded successfully".length + 1)); + ZosFilesMessages.memberDownloadedSuccessfully.message.substring(0, "Member(s) downloaded successfully".length + 1)); // convert the data set name to use as a path/file const regex = /\./gi; @@ -441,7 +441,7 @@ describe.each([false, true])("Download Data Set - Encoded: %s", (encoded: boolea expect(response).toBeTruthy(); expect(response.success).toBeTruthy(); expect(response.commandResponse).toContain( - ZosFilesMessages.datasetDownloadedSuccessfully.message.substring(0, "Data set downloaded successfully".length + 1)); + ZosFilesMessages.memberDownloadedSuccessfully.message.substring(0, "Member(s) downloaded successfully".length + 1)); // convert the data set name to use as a path/file const regex = /\./gi; @@ -471,7 +471,7 @@ describe.each([false, true])("Download Data Set - Encoded: %s", (encoded: boolea expect(response).toBeTruthy(); expect(response.success).toBeTruthy(); expect(response.commandResponse).toContain( - ZosFilesMessages.datasetDownloadedSuccessfully.message.substring(0, "Data set downloaded successfully".length + 1)); + ZosFilesMessages.memberDownloadedSuccessfully.message.substring(0, "Member(s) downloaded successfully".length + 1)); // convert the data set name to use as a path/file const regex = /\./gi; @@ -512,7 +512,7 @@ describe.each([false, true])("Download Data Set - Encoded: %s", (encoded: boolea expect(response).toBeTruthy(); expect(response.success).toBeTruthy(); expect(response.commandResponse).toContain( - ZosFilesMessages.datasetDownloadedSuccessfully.message.substring(0, "Data set downloaded successfully".length + 1)); + ZosFilesMessages.memberDownloadedSuccessfully.message.substring(0, "Member(s) downloaded successfully".length + 1)); // convert the data set name to use as a path/file for clean up in AfterEach const regex = /\./gi; @@ -542,7 +542,7 @@ describe.each([false, true])("Download Data Set - Encoded: %s", (encoded: boolea expect(response).toBeTruthy(); expect(response.success).toBeTruthy(); expect(response.commandResponse).toContain( - ZosFilesMessages.datasetDownloadedSuccessfully.message.substring(0, "Data set downloaded successfully".length + 1)); + ZosFilesMessages.memberDownloadedSuccessfully.message.substring(0, "Member(s) downloaded successfully".length + 1)); // convert the data set name to use as a path/file for clean up in AfterEach const regex = /\./gi; @@ -572,7 +572,7 @@ describe.each([false, true])("Download Data Set - Encoded: %s", (encoded: boolea expect(response).toBeTruthy(); expect(response.success).toBeTruthy(); expect(response.commandResponse).toContain( - ZosFilesMessages.datasetDownloadedSuccessfully.message.substring(0, "Data set downloaded successfully".length + 1)); + ZosFilesMessages.memberDownloadedSuccessfully.message.substring(0, "Member(s) downloaded successfully".length + 1)); // convert the data set name to use as a path/file for clean up in AfterEach const regex = /\./gi; diff --git a/packages/zosfiles/__tests__/__unit__/utils/__snapshots__/ZosFilesUtils.unit.test.ts.snap b/packages/zosfiles/__tests__/__unit__/utils/__snapshots__/ZosFilesUtils.unit.test.ts.snap index 0b7d210e4a..33e8c2f747 100644 --- a/packages/zosfiles/__tests__/__unit__/utils/__snapshots__/ZosFilesUtils.unit.test.ts.snap +++ b/packages/zosfiles/__tests__/__unit__/utils/__snapshots__/ZosFilesUtils.unit.test.ts.snap @@ -187,6 +187,9 @@ Destination: %s", "message": "Failed to download the following members: ", }, + "memberDownloadedSuccessfully": Object { + "message": "Member(s) downloaded successfully.", + }, "memberDownloadedWithDestination": Object { "message": "Member(s) downloaded successfully. Destination: %s", diff --git a/packages/zosfiles/src/constants/ZosFiles.messages.ts b/packages/zosfiles/src/constants/ZosFiles.messages.ts index 21c39e9921..b5e8a683f5 100644 --- a/packages/zosfiles/src/constants/ZosFiles.messages.ts +++ b/packages/zosfiles/src/constants/ZosFiles.messages.ts @@ -167,6 +167,15 @@ export const ZosFilesMessages: { [key: string]: IMessageDefinition } = { message: "Data set downloaded successfully.\nDestination: %s" }, + /** + * Message indicating that the members of a data set were downloaded successfully + * @type {IMessageDefinition} + */ + memberDownloadedSuccessfully: { + message: "Member(s) downloaded successfully." + }, + + /** * Message indicating that the member was downloaded successfully * @type {IMessageDefinition} From ff36bf12611f81943d95f480c237297fbc456c1c Mon Sep 17 00:00:00 2001 From: Pujal Date: Mon, 18 Nov 2024 14:46:33 -0500 Subject: [PATCH 17/27] removed unsused line Signed-off-by: Pujal --- .../__tests__/__unit__/methods/download/Download.unit.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/zosfiles/__tests__/__unit__/methods/download/Download.unit.test.ts b/packages/zosfiles/__tests__/__unit__/methods/download/Download.unit.test.ts index 6b90184b91..ce7a37490f 100644 --- a/packages/zosfiles/__tests__/__unit__/methods/download/Download.unit.test.ts +++ b/packages/zosfiles/__tests__/__unit__/methods/download/Download.unit.test.ts @@ -609,7 +609,6 @@ describe("z/OS Files - Download", () => { describe("allMembers", () => { const listAllMembersSpy = jest.spyOn(List, "allMembers"); const downloadDatasetSpy = jest.spyOn(Download, "dataSet"); - const downloadMembers = jest.spyOn(Download, "allMembers"); const listApiResponse = { items: [ From d65789d7b160c68918e72a89be5cf40d26041415 Mon Sep 17 00:00:00 2001 From: Pujal Date: Tue, 19 Nov 2024 13:30:19 -0500 Subject: [PATCH 18/27] updated List.ts membersMatchingPattern method description Signed-off-by: Pujal --- packages/zosfiles/src/methods/list/List.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/zosfiles/src/methods/list/List.ts b/packages/zosfiles/src/methods/list/List.ts index 80fbf4bec1..d613d1e9d7 100644 --- a/packages/zosfiles/src/methods/list/List.ts +++ b/packages/zosfiles/src/methods/list/List.ts @@ -105,7 +105,7 @@ export class List { } /** - * List data sets that match a DSLEVEL pattern + * List data set members that match a DSLEVEL pattern * @param {AbstractSession} session z/OSMF connection info * @param {string[]} patterns Data set patterns to include * @param {IDsmListOptions} options Contains options for the z/OSMF request From fd55e6dbee5930617ebd118dd1425aa4005fd623 Mon Sep 17 00:00:00 2001 From: Pujal Date: Wed, 20 Nov 2024 09:58:26 -0500 Subject: [PATCH 19/27] updated help text - PR comment Signed-off-by: Pujal --- packages/cli/src/zosfiles/-strings-/en.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/zosfiles/-strings-/en.ts b/packages/cli/src/zosfiles/-strings-/en.ts index 5ba5360f15..6f160833bd 100644 --- a/packages/cli/src/zosfiles/-strings-/en.ts +++ b/packages/cli/src/zosfiles/-strings-/en.ts @@ -353,15 +353,11 @@ export default { DESCRIPTION: "Download all members that match a DSLEVEL pattern from a partitioned data set to a local folder.", POSITIONALS: { DATASETNAME: "The name of the data set from which you want to download members", - PATTERN: `The pattern or patterns to match data sets against. Also known as 'DSLEVEL'. The following special sequences can be ` + + PATTERN: `The pattern or patterns to match members against. Also known as 'DSLEVEL'. The following special sequences can be ` + `used in the pattern: ${TextUtils.chalk.yellow("%")}: matches any single character - ${TextUtils.chalk.yellow("*")}: matches any number of characters within a data set name qualifier ` + - `(e.g. "ibmuser.j*.old" matches "ibmuser.jcl.old" but not "ibmuser.jcl.very.old") - ${TextUtils.chalk.yellow("**")}: matches any number of characters within any number of data set name qualifiers ` + - `(e.g. "ibmuser.**.old" matches both "ibmuser.jcl.old" and "ibmuser.jcl.very.old") - However, the pattern cannot begin with any of these sequences. You can specify multiple patterns separated by commas, ` + - `for example "ibmuser.**.cntl,ibmuser.**.jcl"` + ${TextUtils.chalk.yellow("*")}: matches any number of characters within a member + You can specify multiple patterns separated by commas, for example "Mem*, Test*"` }, EXAMPLES: { EX1: `Download the members of the data set "ibmuser.loadlib" that begin with "Test" to the directory "loadlib/"`, From 2080f9f9fa772c6e79c5b9107c8dd57692e6265a Mon Sep 17 00:00:00 2001 From: Pujal Date: Wed, 20 Nov 2024 09:59:54 -0500 Subject: [PATCH 20/27] updated help text - PR comment Signed-off-by: Pujal --- ENCO#EDPG899423.ZOSFILE.DOWNLOAD.A63815.ENCO#ED | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 ENCO#EDPG899423.ZOSFILE.DOWNLOAD.A63815.ENCO#ED diff --git a/ENCO#EDPG899423.ZOSFILE.DOWNLOAD.A63815.ENCO#ED b/ENCO#EDPG899423.ZOSFILE.DOWNLOAD.A63815.ENCO#ED deleted file mode 100644 index e69de29bb2..0000000000 From 1205b1abe08238d568547a27c20d29f53a8300ad Mon Sep 17 00:00:00 2001 From: Pujal Date: Thu, 21 Nov 2024 10:09:31 -0500 Subject: [PATCH 21/27] uncommented a line on dsm system test Signed-off-by: Pujal --- .../download/dsm/cli.files.download.dsm.system.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/__tests__/zosfiles/__system__/download/dsm/cli.files.download.dsm.system.test.ts b/packages/cli/__tests__/zosfiles/__system__/download/dsm/cli.files.download.dsm.system.test.ts index 42039221fe..125d51e99a 100644 --- a/packages/cli/__tests__/zosfiles/__system__/download/dsm/cli.files.download.dsm.system.test.ts +++ b/packages/cli/__tests__/zosfiles/__system__/download/dsm/cli.files.download.dsm.system.test.ts @@ -185,7 +185,7 @@ describe("Download Dataset Matching", () => { const response = runCliScript(shellScript, TEST_ENVIRONMENT, [pattern, "--rfj", "-d", testDir, "-e", ".jcl"]); const result = JSON.parse(response.stdout.toString()); - // expect(response.stderr.toString()).toBe(""); + expect(response.stderr.toString()).toBe(""); expect(response.status).toBe(0); expect(result.stdout).toContain(`${dsnames.length} data set(s) were found matching pattern`); expect(result.stdout).toContain(`${dsnames.length} data set(s) downloaded successfully to ${testDir}`); From a8dbb663c96bf8e5c0b81d10a34537164919164d Mon Sep 17 00:00:00 2001 From: Pujal Date: Thu, 21 Nov 2024 11:34:58 -0500 Subject: [PATCH 22/27] updated help text - PR comment Signed-off-by: Pujal --- packages/cli/src/zosfiles/-strings-/en.ts | 2 +- .../__system__/methods/download/Download.system.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/zosfiles/-strings-/en.ts b/packages/cli/src/zosfiles/-strings-/en.ts index 6f160833bd..d2a8886e8d 100644 --- a/packages/cli/src/zosfiles/-strings-/en.ts +++ b/packages/cli/src/zosfiles/-strings-/en.ts @@ -350,7 +350,7 @@ export default { }, ALL_MEMBERS_MATCHING: { SUMMARY: "Download all members from a pds", - DESCRIPTION: "Download all members that match a DSLEVEL pattern from a partitioned data set to a local folder.", + DESCRIPTION: "Download all members that match a specific pattern from a partitioned data set to a local folder.", POSITIONALS: { DATASETNAME: "The name of the data set from which you want to download members", PATTERN: `The pattern or patterns to match members against. Also known as 'DSLEVEL'. The following special sequences can be ` + diff --git a/packages/zosfiles/__tests__/__system__/methods/download/Download.system.test.ts b/packages/zosfiles/__tests__/__system__/methods/download/Download.system.test.ts index 6712c87acd..6a0d3b8610 100644 --- a/packages/zosfiles/__tests__/__system__/methods/download/Download.system.test.ts +++ b/packages/zosfiles/__tests__/__system__/methods/download/Download.system.test.ts @@ -599,7 +599,7 @@ describe.each([false, true])("Download Data Set - Encoded: %s", (encoded: boolea expect(response).toBeTruthy(); expect(response.success).toBeTruthy(); expect(response.commandResponse).toContain( - ZosFilesMessages.datasetDownloadedSuccessfully.message.substring(0, "Data set downloaded successfully".length + 1)); + ZosFilesMessages.memberDownloadedSuccessfully.message.substring(0, "Member(s) downloaded successfully".length + 1)); // Convert the data set name to use as a path/file const regex = /\./gi; From 72acb959b739348c9b1a6b5a6b8ec495bcf818a5 Mon Sep 17 00:00:00 2001 From: Pujal Date: Thu, 21 Nov 2024 12:01:06 -0500 Subject: [PATCH 23/27] Updated changelogs Signed-off-by: Pujal --- packages/cli/CHANGELOG.md | 2 +- packages/zosfiles/CHANGELOG.md | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index ca912e9bc9..914e04b980 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -10,7 +10,7 @@ All notable changes to the Zowe CLI package will be documented in this file. ## `8.7.0` --Enhancement: Added new command zowe zos-files download all-members-matching, (zowe files dl amm), to download members matching specified pattern(s). [#2359](https://github.com/zowe/zowe-cli/pull/2359) +-Enhancement: Added new command zowe zos-files download all-members-matching, (zowe files dl amm), to download members matching specified pattern(s). The success message for the Download.allMembers API was changed from originally "Data set downloaded successfully" to "Member(s) downloaded successfully." The change also alters the commandResponse when using the --rfj flag. [#2359](https://github.com/zowe/zowe-cli/pull/2359) ## Recent Changes diff --git a/packages/zosfiles/CHANGELOG.md b/packages/zosfiles/CHANGELOG.md index 897d6f0750..2942789862 100644 --- a/packages/zosfiles/CHANGELOG.md +++ b/packages/zosfiles/CHANGELOG.md @@ -2,16 +2,13 @@ All notable changes to the Zowe z/OS files SDK package will be documented in this file. -<<<<<<< HEAD ## Recent Changes - Enhancement: Added a `List.membersMatchingPattern` method to download all members that match a DSLEVEL pattern.[#2359](https://github.com/zowe/zowe-cli/pull/2359) -======= ## `8.8.3` - BugFix: Resolved issue where special characters could be corrupted when downloading a large file. [#2366](https://github.com/zowe/zowe-cli/pull/2366) ->>>>>>> origin/master ## `8.8.0` - Enhancement: Allows for passing a `.zosattributues` file path for the download encoding format via the `attributes` option on the `Download.ussFile` method. [#2322](https://github.com/zowe/zowe-cli/issues/2322) From a4d3e7ad9e8904c3f4d113ca99e0e94e5560ddd9 Mon Sep 17 00:00:00 2001 From: Pujal Date: Thu, 21 Nov 2024 15:15:09 -0500 Subject: [PATCH 24/27] updated changelog Signed-off-by: Pujal --- packages/cli/CHANGELOG.md | 8 ++++---- packages/zosfiles/CHANGELOG.md | 3 ++- .../zosfiles/src/methods/download/doc/IDownloadOptions.ts | 3 +++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 914e04b980..6779cd3d06 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,6 +1,10 @@ # Change Log All notable changes to the Zowe CLI package will be documented in this file. +## Recent Changes + +-Enhancement: Added new command zowe zos-files download all-members-matching, (zowe files dl amm), to download members matching specified pattern(s). The success message for the Download.allMembers API was changed from originally "Data set downloaded successfully" to "Member(s) downloaded successfully." The change also alters the commandResponse when using the --rfj flag. [#2359](https://github.com/zowe/zowe-cli/pull/2359) + ## `8.8.0` - Enhancement: Pass a `.zosattributes` file path for the download encoding format by adding the new `--attributes` flag to the `zowe zos-files upload` command. [#2322](https://github.com/zowe/zowe-cli/issues/2322) @@ -10,10 +14,6 @@ All notable changes to the Zowe CLI package will be documented in this file. ## `8.7.0` --Enhancement: Added new command zowe zos-files download all-members-matching, (zowe files dl amm), to download members matching specified pattern(s). The success message for the Download.allMembers API was changed from originally "Data set downloaded successfully" to "Member(s) downloaded successfully." The change also alters the commandResponse when using the --rfj flag. [#2359](https://github.com/zowe/zowe-cli/pull/2359) - -## Recent Changes - - Enhancement: Added --wait-for-active and --wait-for-output to download options on zosjobs. [#2328](https://github.com/zowe/zowe-cli/pull/2328) ## `8.6.2` diff --git a/packages/zosfiles/CHANGELOG.md b/packages/zosfiles/CHANGELOG.md index 2942789862..f418f09e98 100644 --- a/packages/zosfiles/CHANGELOG.md +++ b/packages/zosfiles/CHANGELOG.md @@ -4,7 +4,8 @@ All notable changes to the Zowe z/OS files SDK package will be documented in thi ## Recent Changes -- Enhancement: Added a `List.membersMatchingPattern` method to download all members that match a DSLEVEL pattern.[#2359](https://github.com/zowe/zowe-cli/pull/2359) +- Enhancement: Added a `List.membersMatchingPattern` method to download all members that match a specific pattern.[#2359](https://github.com/zowe/zowe-cli/pull/2359) + ## `8.8.3` - BugFix: Resolved issue where special characters could be corrupted when downloading a large file. [#2366](https://github.com/zowe/zowe-cli/pull/2366) diff --git a/packages/zosfiles/src/methods/download/doc/IDownloadOptions.ts b/packages/zosfiles/src/methods/download/doc/IDownloadOptions.ts index af31a68bf1..c397ab36dd 100644 --- a/packages/zosfiles/src/methods/download/doc/IDownloadOptions.ts +++ b/packages/zosfiles/src/methods/download/doc/IDownloadOptions.ts @@ -114,6 +114,9 @@ export interface IDownloadOptions extends Omit */ pattern?: string; + /** + * An optional response returned based on inputted patterns + */ memberPatternResponse?: any; } From d9bd2f7f626f92a0d1b71f2677135f4b1e867b32 Mon Sep 17 00:00:00 2001 From: Pujal Date: Thu, 21 Nov 2024 15:19:01 -0500 Subject: [PATCH 25/27] updated changelog Signed-off-by: Pujal --- packages/zosfiles/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/zosfiles/CHANGELOG.md b/packages/zosfiles/CHANGELOG.md index f418f09e98..3dff530c4f 100644 --- a/packages/zosfiles/CHANGELOG.md +++ b/packages/zosfiles/CHANGELOG.md @@ -6,6 +6,10 @@ All notable changes to the Zowe z/OS files SDK package will be documented in thi - Enhancement: Added a `List.membersMatchingPattern` method to download all members that match a specific pattern.[#2359](https://github.com/zowe/zowe-cli/pull/2359) +## `8.8.4` + +- Enhancement: Allows extenders of the Search functionality to pass a function `abortSearch` on `searchOptions` to abort a search. [#2370](https://github.com/zowe/zowe-cli/pull/2370) + ## `8.8.3` - BugFix: Resolved issue where special characters could be corrupted when downloading a large file. [#2366](https://github.com/zowe/zowe-cli/pull/2366) From 017ae44769497b357a501df02ec16c0f595377f5 Mon Sep 17 00:00:00 2001 From: Pujal Date: Thu, 21 Nov 2024 15:31:38 -0500 Subject: [PATCH 26/27] updated changelog Signed-off-by: Pujal --- packages/zosfiles/CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/zosfiles/CHANGELOG.md b/packages/zosfiles/CHANGELOG.md index 0cb0cdbd50..3dff530c4f 100644 --- a/packages/zosfiles/CHANGELOG.md +++ b/packages/zosfiles/CHANGELOG.md @@ -2,13 +2,10 @@ All notable changes to the Zowe z/OS files SDK package will be documented in this file. -<<<<<<< HEAD ## Recent Changes - Enhancement: Added a `List.membersMatchingPattern` method to download all members that match a specific pattern.[#2359](https://github.com/zowe/zowe-cli/pull/2359) -======= ->>>>>>> master ## `8.8.4` - Enhancement: Allows extenders of the Search functionality to pass a function `abortSearch` on `searchOptions` to abort a search. [#2370](https://github.com/zowe/zowe-cli/pull/2370) From d6f47677555204d926a9a30c112ab0cd7009ffaf Mon Sep 17 00:00:00 2001 From: Pujal Date: Fri, 22 Nov 2024 10:41:09 -0500 Subject: [PATCH 27/27] fixed typos Signed-off-by: Pujal --- packages/cli/src/zosfiles/-strings-/en.ts | 6 +++--- packages/zosfiles/src/constants/ZosFiles.messages.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/zosfiles/-strings-/en.ts b/packages/cli/src/zosfiles/-strings-/en.ts index d2a8886e8d..bd65b59d95 100644 --- a/packages/cli/src/zosfiles/-strings-/en.ts +++ b/packages/cli/src/zosfiles/-strings-/en.ts @@ -338,7 +338,7 @@ export default { DESCRIPTION: "Download content from z/OS data sets and USS files to your PC.", ACTIONS: { ALL_MEMBERS: { - SUMMARY: "Download all members from a pds", + SUMMARY: "Download all members from a PDS", DESCRIPTION: "Download all members from a partitioned data set to a local folder.", POSITIONALS: { DATASETNAME: "The name of the data set from which you want to download members" @@ -349,7 +349,7 @@ export default { } }, ALL_MEMBERS_MATCHING: { - SUMMARY: "Download all members from a pds", + SUMMARY: "Download all members from a PDS", DESCRIPTION: "Download all members that match a specific pattern from a partitioned data set to a local folder.", POSITIONALS: { DATASETNAME: "The name of the data set from which you want to download members", @@ -506,7 +506,7 @@ export default { DESCRIPTION: "List data sets and data set members. Optionally, you can list their details and attributes.", ACTIONS: { ALL_MEMBERS: { - SUMMARY: "List all members of a pds", + SUMMARY: "List all members of a PDS", DESCRIPTION: "List all members of a partitioned data set. To view additional information about each member, use the --attributes " + "option under the Options section of this help text.", POSITIONALS: { diff --git a/packages/zosfiles/src/constants/ZosFiles.messages.ts b/packages/zosfiles/src/constants/ZosFiles.messages.ts index b5e8a683f5..8e6abc4cf8 100644 --- a/packages/zosfiles/src/constants/ZosFiles.messages.ts +++ b/packages/zosfiles/src/constants/ZosFiles.messages.ts @@ -433,7 +433,7 @@ export const ZosFilesMessages: { [key: string]: IMessageDefinition } = { }, /** - * Message indicating that no members xsremain to be downloaded after the excluded ones were filtered out. + * Message indicating that no members remain to be downloaded after the excluded ones were filtered out. * @type {IMessageDefinition} */ noMembersMatchingPattern: {