Skip to content

Commit

Permalink
updated copy pds functionality and system tests
Browse files Browse the repository at this point in the history
Signed-off-by: Pujal <[email protected]>
  • Loading branch information
pujal0909 committed Dec 11, 2024
1 parent e684b78 commit 2c8783c
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@
*/

import { Create, Upload, Delete, CreateDataSetTypeEnum, Copy, ZosFilesMessages, Get, IDataSet,
ICrossLparCopyDatasetOptions, IGetOptions, IZosFilesResponse } from "../../../../src";
import { Imperative, Session } from "@zowe/imperative";
ICrossLparCopyDatasetOptions, IGetOptions, IZosFilesResponse,
ZosFilesUtils} from "../../../../src";
import { Imperative, IO, Session } from "@zowe/imperative";
import { inspect } from "util";
import { TestEnvironment } from "../../../../../../__tests__/__src__/environment/TestEnvironment";
import { ITestPropertiesSchema } from "../../../../../../__tests__/__src__/properties/ITestPropertiesSchema";
import { join } from "path";
import { readFileSync } from "fs";
import { ITestEnvironment } from "../../../../../../__tests__/__src__/environment/ITestEnvironment";
import { tmpdir } from "os";
import path = require("path");
import * as fs from "fs";

let REAL_SESSION: Session;
let REAL_TARGET_SESSION: Session;
Expand Down Expand Up @@ -98,6 +102,56 @@ describe("Copy", () => {
expect(contents1.toString()).toEqual(contents2.toString());
});
});
describe("Partioned > Partioned", () => {
beforeEach(async () => {
try {
const downloadDir = path.join(tmpdir(), fromDataSetName);
fs.mkdirSync(downloadDir, { recursive: true });
const mockFile = path.join(downloadDir, "mockFile.txt");
fs.writeFileSync(mockFile, "test file content");

Check failure

Code scanning / CodeQL

Insecure temporary file High test

Insecure creation of file in
the os temp dir
.

const uploadFileList: string[] = ZosFilesUtils.getFileListFromPath(downloadDir);
const stream = IO.createReadStream(uploadFileList[0]);
await Create.dataSet(REAL_SESSION, CreateDataSetTypeEnum.DATA_SET_SEQUENTIAL, fromDataSetName);
await Create.dataSet(REAL_SESSION, CreateDataSetTypeEnum.DATA_SET_SEQUENTIAL, toDataSetName);
await Upload.streamToDataSet(REAL_SESSION, stream, fromDataSetName);
} catch (err) {
Imperative.console.info(`Error: ${inspect(err)}`);
}
});
it("Should copy a partitioned data set", async () => {
let error;
let response;
let contents1;
let contents2;

try {
response = await Copy.dataSet(
REAL_SESSION,
{dsn: toDataSetName},
{"from-dataset": {
dsn:fromDataSetName
}}
);
contents1 = await Get.dataSet(REAL_SESSION, fromDataSetName);
contents2 = await Get.dataSet(REAL_SESSION, toDataSetName);
Imperative.console.info(`Response: ${inspect(response)}`);
} catch (err) {
error = err;
Imperative.console.info(`Error: ${inspect(err)}`);
}

expect(error).toBeFalsy();

expect(response).toBeTruthy();
expect(response.success).toBe(true);
expect(response.commandResponse).toContain(ZosFilesMessages.datasetCopiedSuccessfully.message);

expect(contents1).toBeTruthy();
expect(contents2).toBeTruthy();
expect(contents1.toString()).toEqual(contents2.toString());
});
});
describe("Member > Member", () => {
beforeEach(async () => {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@

import { Session, ImperativeError } from "@zowe/imperative";
import { posix } from "path";

import * as fs from "fs";

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note test

Unused import fs.
import { error } from "console";

import { Copy, Create, Get, List, Upload, ZosFilesConstants, ZosFilesMessages, IZosFilesResponse } from "../../../../src";
import { Copy, Create, Get, List, Upload, ZosFilesConstants, ZosFilesMessages, IZosFilesResponse, Download, ZosFilesUtils } from "../../../../src";
import { ZosmfHeaders, ZosmfRestClient } from "@zowe/core-for-zowe-sdk";
import { tmpdir } from "os";

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note test

Unused import tmpdir.
import path = require("path");

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note test

Unused import path.

describe("Copy", () => {
const dummySession = new Session({
Expand All @@ -29,6 +31,8 @@ describe("Copy", () => {

describe("Data Set", () => {
const copyExpectStringSpy = jest.spyOn(ZosmfRestClient, "putExpectString");
let copyPDSSpy = jest.spyOn(Copy, "copyPDS");
let isPDSSpy: jest.SpyInstance;
const fromDataSetName = "USER.DATA.FROM";
const fromMemberName = "mem1";
const toDataSetName = "USER.DATA.TO";
Expand All @@ -39,6 +43,12 @@ describe("Copy", () => {
copyExpectStringSpy.mockImplementation(async () => {
return "";
});
copyPDSSpy.mockClear();
copyPDSSpy = jest.spyOn(Copy, "copyPDS").mockResolvedValue({
success:true,
commandResponse: ZosFilesMessages.datasetCopiedSuccessfully.message,
});
isPDSSpy = jest.spyOn(Copy as any, "isPDS").mockResolvedValue(true);
});

describe("Success Scenarios", () => {
Expand Down Expand Up @@ -438,6 +448,28 @@ describe("Copy", () => {
expect(lastArgumentOfCall).toHaveProperty("replace", false);
});
});
describe("Partitioned > Partitioned", () => {
it("should call copyPDS to copy members of source PDS to target PDS", async () => {
const response = await Copy.dataSet(
dummySession,
{dsn: toDataSetName},
{"from-dataset": {
dsn:fromDataSetName
}}
);
expect(isPDSSpy).toHaveBeenCalledTimes(2);
expect(isPDSSpy).toHaveBeenNthCalledWith(1, dummySession, fromDataSetName);
expect(isPDSSpy).toHaveBeenNthCalledWith(2, dummySession, toDataSetName);

expect(copyPDSSpy).toHaveBeenCalledTimes(1);
expect(copyPDSSpy).toHaveBeenCalledWith(dummySession, fromDataSetName, toDataSetName);

expect(response).toEqual({
success: true,
commandResponse: ZosFilesMessages.datasetCopiedSuccessfully.message
});
});
});
});
describe("Failure Scenarios", () => {
it("should fail if the zOSMF REST client fails", async () => {
Expand Down Expand Up @@ -515,6 +547,41 @@ describe("Copy", () => {
});
});

describe("Copy Partitioned Data Set", () => {
const listAllMembersSpy = jest.spyOn(List, "allMembers");

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note test

Unused variable listAllMembersSpy.
const downloadAllMembersSpy = jest.spyOn(Download, "allMembers");

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note test

Unused variable downloadAllMembersSpy.
const uploadSpy = jest.spyOn(Upload, "streamToDataSet");

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note test

Unused variable uploadSpy.
const fileListPathSpy = jest.spyOn(ZosFilesUtils, "getFileListFromPath");

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note test

Unused variable fileListPathSpy.
const fromDataSetName = "USER.DATA.FROM";

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note test

Unused variable fromDataSetName.
const toDataSetName = "USER.DATA.TO";

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note test

Unused variable toDataSetName.
it("should successfully copy members from source to target PDS", async () => {
// listAllMembersSpy.mockImplementation(async (): Promise<any> => ({
// apiResponse: {
// items: [
// {member: "mem1"},
// {member: "mem2"}
// ]
// }
// }));
// downloadAllMembersSpy.mockImplementation(async (): Promise<any> => undefined);

// uploadSpy.mockImplementation(async (): Promise<any> => undefined);

// const response = await Copy.copyPDS(dummySession, fromDataSetName, toDataSetName);
// // const downloadDir = path.join(tmpdir(), fromDataSetName);
// expect(listAllMembersSpy).toHaveBeenCalledWith(dummySession, fromDataSetName);
// expect(downloadAllMembersSpy).toHaveBeenCalled();
// // expect(fileListPathSpy).toHaveBeenCalledWith(path.join(tmpdir(), fromDataSetName));
// expect(uploadSpy).toHaveBeenCalledTimes(2);

// // expect(fs.rmSync).toHaveBeenCalled();
// expect(response).toEqual({
// success: true,
// commandResponse: ZosFilesMessages.datasetCopiedSuccessfully.message,
// });
});
});

describe("Data Set Cross LPAR", () => {
const getDatasetSpy = jest.spyOn(Get, "dataSet");
const listDatasetSpy = jest.spyOn(List, "dataSet");
Expand Down
86 changes: 83 additions & 3 deletions packages/zosfiles/src/methods/copy/Copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
*
*/

import { AbstractSession, ImperativeError, ImperativeExpect, ITaskWithStatus, Logger, Headers,
IHeaderContent, TaskStage } from "@zowe/imperative";
import { AbstractSession, ImperativeError, ImperativeExpect, ITaskWithStatus,
Logger, Headers, IHeaderContent, TaskStage, IO} from "@zowe/imperative";
import { posix } from "path";

import * as fs from "fs";
import { Create, CreateDataSetTypeEnum, ICreateDataSetOptions } from "../create";
import { Get } from "../get";
import { Upload } from "../upload";
Expand All @@ -26,6 +26,10 @@ import { IZosmfListResponse } from "../list/doc/IZosmfListResponse";
import { IDataSet } from "../../doc/IDataSet";
import { ICopyDatasetOptions } from "./doc/ICopyDatasetOptions";
import { ICrossLparCopyDatasetOptions } from "./doc/ICrossLparCopyDatasetOptions";
import { Download } from "../download";
import { ZosFilesUtils } from "../../utils/ZosFilesUtils";
import { tmpdir } from "os";
import path = require("path");
/**
* This class holds helper functions that are used to copy the contents of datasets through the
* z/OSMF APIs.
Expand Down Expand Up @@ -53,6 +57,12 @@ export class Copy {
ImperativeExpect.toBeDefinedAndNonBlank(options["from-dataset"].dsn, "fromDataSetName");
ImperativeExpect.toBeDefinedAndNonBlank(toDataSetName, "toDataSetName");

const sourceIsPds = await Copy.isPDS(session, options["from-dataset"].dsn);
const targetIsPds = await Copy.isPDS(session, toDataSetName);

if(sourceIsPds && targetIsPds) {
return await Copy.copyPDS(session, options["from-dataset"].dsn, toDataSetName);
}
const endpoint: string = posix.join(
ZosFilesConstants.RESOURCE,
ZosFilesConstants.RES_DS_FILES,
Expand Down Expand Up @@ -93,6 +103,76 @@ export class Copy {
}
}

/**
* Private function that checks if a dataset is type PDS
**/
private static async isPDS(
session: AbstractSession,
dataSetName: string
): Promise<boolean> {
try {
const response = await List.dataSet(session, dataSetName, {attributes: true});
const dsntp = response.apiResponse.items[0].dsntp;
const dsorg = response.apiResponse.items[0].dsorg;
return dsntp === "PDS" && dsorg === "PO";
}
catch(error) {
Logger.getAppLogger().error(error);
throw error;
}
}

/**
* Copy the members of a Partitioned dataset into another Partitioned dataset
*
* @param {AbstractSession} session - z/OSMF connection info
* @param {IDataSet} toDataSet - The data set to copy to
* @param {IDataSetOptions} options - Options
*
* @returns {Promise<IZosFilesResponse>} A response indicating the status of the copying
*
* @throws {ImperativeError} Data set name must be specified as a non-empty string
* @throws {Error} When the {@link ZosmfRestClient} throws an error
*
* @see https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.1.0/com.ibm.zos.v2r1.izua700/IZUHPINFO_API_PutDataSetMemberUtilities.htm
*/

public static async copyPDS (
session: AbstractSession,
fromPds: string,
toPds: string
): Promise<IZosFilesResponse> {
try {
const sourceResponse = await List.allMembers(session, fromPds);
const sourceMemberList: Array<{ member: string }> = sourceResponse.apiResponse.items;

if(sourceMemberList.length == 0) {
return {
success: false,
commandResponse: `Source dataset (${fromPds}) - ` + ZosFilesMessages.noMembersFound.message
};
}

const downloadDir = path.join(tmpdir(), fromPds);
await Download.allMembers(session, fromPds, {directory:downloadDir});
const uploadFileList: string[] = ZosFilesUtils.getFileListFromPath(downloadDir);

for (const file of uploadFileList) {
const uploadingDsn = `${toPds}(${ZosFilesUtils.generateMemberName(file)})`;
const uploadStream = IO.createReadStream(file);
await Upload.streamToDataSet(session, uploadStream, uploadingDsn);
}
fs.rmSync(downloadDir, {recursive: true});
return {
success:true,
commandResponse: ZosFilesMessages.datasetCopiedSuccessfully.message
};
}
catch (error) {
Logger.getAppLogger().error(error);
throw error;
}
}

/**
* Copy the contents of a dataset from one LPAR to another LPAR
Expand Down

0 comments on commit 2c8783c

Please sign in to comment.