Skip to content

Commit

Permalink
like named members functionality for PDSs
Browse files Browse the repository at this point in the history
Signed-off-by: Pujal <pujal.gandhi@broadcom.com>
  • Loading branch information
pujal0909 committed Feb 3, 2025
1 parent 9340db9 commit 48f7d87
Show file tree
Hide file tree
Showing 7 changed files with 277 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe("DsHandler", () => {

},
response: {
console: { promptFn: jest.fn() }
console: { promptFn: jest.fn(), promptForLikeNamedMembers: jest.fn() }
}
};

Expand All @@ -68,7 +68,8 @@ describe("DsHandler", () => {
"replace": commandParameters.arguments.replace,
"responseTimeout": commandParameters.arguments.responseTimeout,
"safeReplace": commandParameters.arguments.safeReplace,
"promptFn": expect.any(Function)
"promptFn": expect.any(Function),
"promptForLikeNamedMembers": expect.any(Function)
}
);
expect(response).toBe(defaultReturn);
Expand Down Expand Up @@ -98,7 +99,7 @@ describe("DsHandler", () => {
responseTimeout
},
response: {
console: { promptFn: jest.fn() }
console: { promptFn: jest.fn(), promptForLikeNamedMembers: jest.fn() }
}
};

Expand All @@ -116,7 +117,8 @@ describe("DsHandler", () => {
"replace": commandParameters.arguments.replace,
"responseTimeout": commandParameters.arguments.responseTimeout,
"safeReplace": commandParameters.arguments.safeReplace,
"promptFn": expect.any(Function)
"promptFn": expect.any(Function),
"promptForLikeNamedMembers": expect.any(Function)
}
);
expect(response).toBe(defaultReturn);
Expand Down Expand Up @@ -162,7 +164,8 @@ describe("DsHandler", () => {
"replace": commandParameters.arguments.replace,
"responseTimeout": commandParameters.arguments.responseTimeout,
"safeReplace": commandParameters.arguments.safeReplace,
"promptFn": expect.any(Function)
"promptFn": expect.any(Function),
"promptForLikeNamedMembers": expect.any(Function)
}
);
expect(response).toBe(defaultReturn);
Expand Down Expand Up @@ -198,7 +201,7 @@ describe("DsHandler", () => {
const result = await promptFn(commandParameters.arguments.toDataSetName);

expect(promptMock).toHaveBeenCalledWith(
`The dataset '${toDataSetName}' exists on the target system. This copy will result in data loss.` +
`The dataset '${toDataSetName}' exists on the target system. This copy may result in data loss.` +
` Are you sure you want to continue? [y/N]: `
);
expect(result).toBe(true);
Expand Down Expand Up @@ -234,7 +237,79 @@ describe("DsHandler", () => {
const result = await promptFn(commandParameters.arguments.toDataSetName);

expect(promptMock).toHaveBeenCalledWith(
`The dataset '${toDataSetName}' exists on the target system. This copy will result in data loss.` +
`The dataset '${toDataSetName}' exists on the target system. This copy may result in data loss.` +
` Are you sure you want to continue? [y/N]: `
);
expect(result).toBe(false);
});
it("should prompt the user about duplicate member names and return true when input is 'y", async () => {
const handler = new DsHandler();

expect(handler).toBeInstanceOf(ZosFilesBaseHandler);
const fromDataSetName = "ABCD";
const toDataSetName = "EFGH";
const enq = "SHR";
const replace = false;
const safeReplace = false;
const responseTimeout: any = undefined;

const commandParameters: any = {

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note test

Unused variable commandParameters.
arguments: {
fromDataSetName,
toDataSetName,
enq,
replace,
safeReplace,
responseTimeout
},
response: {
console: { promptFn: jest.fn() }
}
};
const promptMock = jest.fn();
promptMock.mockResolvedValue("y");

const promptForDuplicates = (handler as any)["promptForLikeNamedMembers"]({ prompt: promptMock });
const result = await promptForDuplicates();

expect(promptMock).toHaveBeenCalledWith(
`The source and target data sets have like named member names. The contents of those members will be overwritten.` +
` Are you sure you want to continue? [y/N]: `
);
expect(result).toBe(true);
});
it("should prompt the user about duplicate member names and return false when input is 'N'", async () => {
const handler = new DsHandler();

expect(handler).toBeInstanceOf(ZosFilesBaseHandler);
const fromDataSetName = "ABCD";
const toDataSetName = "EFGH";
const enq = "SHR";
const replace = false;
const safeReplace = false;
const responseTimeout: any = undefined;

const commandParameters: any = {

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note test

Unused variable commandParameters.
arguments: {
fromDataSetName,
toDataSetName,
enq,
replace,
safeReplace,
responseTimeout
},
response: {
console: { promptFn: jest.fn() }
}
};
const promptMock = jest.fn();
promptMock.mockResolvedValue("N");

const promptForDuplicates = (handler as any)["promptForLikeNamedMembers"]({ prompt: promptMock });
const result = await promptForDuplicates();

expect(promptMock).toHaveBeenCalledWith(
`The source and target data sets have like named member names. The contents of those members will be overwritten.` +
` Are you sure you want to continue? [y/N]: `
);
expect(result).toBe(false);
Expand Down
15 changes: 13 additions & 2 deletions packages/cli/src/zosfiles/copy/ds/Ds.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ export default class DsHandler extends ZosFilesBaseHandler {
replace: commandParameters.arguments.replace,
responseTimeout: commandParameters.arguments.responseTimeout,
safeReplace: commandParameters.arguments.safeReplace,
promptFn: this.promptForSafeReplace(commandParameters.response.console)
promptFn: this.promptForSafeReplace(commandParameters.response.console),
promptForLikeNamedMembers: this.promptForLikeNamedMembers(commandParameters.response.console)
};

return Copy.dataSet(session, toDataSet, options);
Expand All @@ -35,10 +36,20 @@ export default class DsHandler extends ZosFilesBaseHandler {
private promptForSafeReplace(console: IHandlerResponseConsoleApi) {
return async (targetDSN: string) => {
const answer: string = await console.prompt(
`The dataset '${targetDSN}' exists on the target system. This copy will result in data loss.` +
`The dataset '${targetDSN}' exists on the target system. This copy may result in data loss.` +
` Are you sure you want to continue? [y/N]: `
);
return answer != null && (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
};
}

private promptForLikeNamedMembers(console: IHandlerResponseConsoleApi) {
return async() => {
const answer: string = await console.prompt (
`The source and target data sets have like named member names. The contents of those members will be overwritten.` +
` Are you sure you want to continue? [y/N]: `
)
return answer != null && (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,40 @@ describe("Copy", () => {
});
});

describe("hasLikeNamedMembers", () => {
beforeEach(async () => {
try {
await Create.dataSet(REAL_SESSION, CreateDataSetTypeEnum.DATA_SET_PARTITIONED, fromDataSetName);
await Create.dataSet(REAL_SESSION, CreateDataSetTypeEnum.DATA_SET_PARTITIONED, toDataSetName);
await Upload.fileToDataset(REAL_SESSION, fileLocation, fromDataSetName);
await Upload.fileToDataset(REAL_SESSION, fileLocation, toDataSetName);
}
catch (err) {
Imperative.console.info(`Error: ${inspect(err)}`);
}
});
afterEach(async () => {
try {
await Delete.dataSet(REAL_SESSION, fromDataSetName);
await Delete.dataSet(REAL_SESSION, toDataSetName);
} catch (err) {
Imperative.console.info(`Error: ${inspect(err)}`);
}
});
it("should return true if the source and target data sets have like-named members", async () => {
const response = await Copy["hasLikeNamedMembers"](REAL_SESSION, fromDataSetName, toDataSetName);
expect(response).toBe(true);
});

it("should return false if the source and target data sets do not have like-named members", async () => {
await Delete.dataSet(REAL_SESSION, toDataSetName);
await Create.dataSet(REAL_SESSION, CreateDataSetTypeEnum.DATA_SET_PARTITIONED, toDataSetName);

const response = await Copy["hasLikeNamedMembers"](REAL_SESSION, fromDataSetName, toDataSetName);
expect(response).toBe(false);
});
});

describe("Data Set Cross LPAR", () => {
describe("Common Failures", () => {
it("should fail if no fromDataSet data set name is supplied", async () => {
Expand Down
117 changes: 115 additions & 2 deletions packages/zosfiles/__tests__/__unit__/methods/copy/Copy.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,18 @@ describe("Copy", () => {
const toDataSetName = "USER.DATA.TO";
const toMemberName = "mem2";
const isPDSSpy = jest.spyOn(Copy as any, "isPDS");
const hasLikeNamedMembers = jest.spyOn(Copy as any, "hasLikeNamedMembers");
let dataSetExistsSpy: jest.SpyInstance;
const promptFn = jest.fn();
const promptForLikeNamedMembers = jest.fn();

beforeEach(() => {
copyPDSSpy.mockClear();
copyExpectStringSpy.mockClear().mockImplementation(async () => { return ""; });
isPDSSpy.mockClear().mockResolvedValue(false);
dataSetExistsSpy = jest.spyOn(Copy as any, "dataSetExists").mockResolvedValue(true);

hasLikeNamedMembers.mockClear().mockResolvedValue(false);
promptForLikeNamedMembers.mockClear();
});
afterAll(() => {
isPDSSpy.mockRestore();
Expand Down Expand Up @@ -619,6 +622,48 @@ describe("Copy", () => {
commandResponse: ZosFilesMessages.datasetCopiedSuccessfully.message
});
});
it("should display a prompt for like named members if there are duplicate member names and --safe-replace and --replace flags are not used", async () => {
hasLikeNamedMembers.mockResolvedValue(true);
promptForLikeNamedMembers.mockClear().mockResolvedValue(true);

const response = await Copy.dataSet(

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note test

Unused variable response.
dummySession,
{ dsn: toDataSetName },
{ "from-dataset": { dsn: fromDataSetName },
safeReplace: false,
replace: false,
promptForLikeNamedMembers }
);
expect(promptForLikeNamedMembers).toHaveBeenCalledWith();

})

Check notice

Code scanning / CodeQL

Semicolon insertion Note test

Avoid automated semicolon insertion (91% of all statements in
the enclosing function
have an explicit semicolon).
it("should not display a prompt for like named members if there are no duplicate member names", async () => {
const response = await Copy.dataSet(

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note test

Unused variable response.
dummySession,
{ dsn: toDataSetName },
{ "from-dataset": { dsn: fromDataSetName },
safeReplace: false,
replace: false,
promptForLikeNamedMembers }
);

expect(promptForLikeNamedMembers).not.toHaveBeenCalled();
});
it("should throw error if user declines to replace the dataset", async () => {
hasLikeNamedMembers.mockResolvedValue(true);
promptForLikeNamedMembers.mockClear().mockResolvedValue(false);

await expect(Copy.dataSet(
dummySession,
{ dsn: toDataSetName },
{ "from-dataset": { dsn: fromDataSetName },
safeReplace: false,
replace: false,
promptForLikeNamedMembers }
)).rejects.toThrow(new ImperativeError({ msg: ZosFilesMessages.datasetCopiedAborted.message }));

expect(promptForLikeNamedMembers).toHaveBeenCalled();
});
});
it("should return early if the source and target data sets are identical", async () => {
const response = await Copy.dataSet(
Expand Down Expand Up @@ -711,7 +756,7 @@ describe("Copy", () => {
});
});

describe("Copy Partitioned Data Set", () => {
describe("Partitioned Data Set", () => {
const listAllMembersSpy = jest.spyOn(List, "allMembers");
const downloadAllMembersSpy = jest.spyOn(Download, "allMembers");
const uploadSpy = jest.spyOn(Upload, "streamToDataSet");
Expand All @@ -722,6 +767,11 @@ describe("Copy", () => {
const readStream = jest.spyOn(IO, "createReadStream");
const rmSync = jest.spyOn(fs, "rmSync");
const listDatasetSpy = jest.spyOn(List, "dataSet");
const hasLikeNamedMembers = jest.spyOn(Copy as any, "hasLikeNamedMembers");

beforeEach(() => {
hasLikeNamedMembers.mockRestore();
});

const dsPO = {
dsname: fromDataSetName,
Expand Down Expand Up @@ -849,6 +899,69 @@ describe("Copy", () => {
commandResponse: ZosFilesMessages.datasetCopiedSuccessfully.message,
});
});

describe("hasLikeNamedMembers", () => {
const listAllMembersSpy = jest.spyOn(List, "allMembers");

beforeEach(() => {
jest.clearAllMocks();
});
it("should return true if the source and target have like-named members", async () => {
listAllMembersSpy.mockImplementation(async (session, dsName): Promise<any> => {
if (dsName === fromDataSetName) {
return {
apiResponse: {
items: [
{ member: "mem1" },
{ member: "mem2" },
]
}
};
} else if (dsName === toDataSetName) {
return {
apiResponse: {
items: [{ member: "mem1" }]
}
};
}
});

const response = await Copy["hasLikeNamedMembers"](dummySession, fromDataSetName, toDataSetName);
expect(response).toBe(true);
expect(listAllMembersSpy).toHaveBeenCalledWith(dummySession, fromDataSetName);
expect(listAllMembersSpy).toHaveBeenCalledWith(dummySession, toDataSetName);
});
it("should return false if the source and target do not have like-named members", async () => {
const sourceResponse = {
apiResponse: {
items: [
{ member: "mem1" },
{ member: "mem2" },
]
}
};
const targetResponse = {
apiResponse: {
items: [
{ member: "mem3" },
]
}
};
listAllMembersSpy.mockImplementation(async (session, dsName): Promise<any> => {
if (dsName === fromDataSetName) {
return sourceResponse;
} else if (dsName === toDataSetName) {
return targetResponse;
}
});

const response = await Copy["hasLikeNamedMembers"](dummySession, fromDataSetName, toDataSetName);

expect(response).toBe(false);
expect(listAllMembersSpy).toHaveBeenCalledWith(dummySession, fromDataSetName);
expect(listAllMembersSpy).toHaveBeenCalledWith(dummySession, toDataSetName);
});
});
});

describe("Data Set Cross LPAR", () => {
Expand Down
1 change: 0 additions & 1 deletion packages/zosfiles/src/constants/ZosFiles.messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ export const ZosFilesMessages: { [key: string]: IMessageDefinition } = {
message: "Member(s) downloaded successfully."
},


/**
* Message indicating that the member was downloaded successfully
* @type {IMessageDefinition}
Expand Down
Loading

0 comments on commit 48f7d87

Please sign in to comment.