Skip to content

Commit

Permalink
Merge pull request #2460 from zowe/fix/ftp/create-member
Browse files Browse the repository at this point in the history
fix(ftp/ds): ECONNRESET FTPS error for empty dataset contents
  • Loading branch information
JillieBeanSim authored Sep 20, 2023
2 parents 5309909 + 3e24aab commit a449d97
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 10 deletions.
2 changes: 2 additions & 0 deletions packages/zowe-explorer-ftp-extension/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ All notable changes to the "zowe-explorer-ftp-extension" extension will be docum

### Bug fixes

- Fixed ECONNRESET error when trying to upload or create an empty data set member. [#2350](https://github.com/zowe/vscode-extension-for-zowe/issues/2350)

## `2.11.0`

### Bug fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const MvsApi = new FtpMvsApi();

describe("FtpMvsApi", () => {
beforeEach(() => {
MvsApi.checkedProfile = jest.fn().mockReturnValue({ message: "success", type: "zftp", failNotFound: false });
MvsApi.checkedProfile = jest.fn().mockReturnValue({ message: "success", type: "zftp", profile: { secureFtp: false }, failNotFound: false });
MvsApi.ftpClient = jest.fn().mockReturnValue({ host: "", user: "", password: "", port: "" });
MvsApi.releaseConnection = jest.fn();
sessionMap.get = jest.fn().mockReturnValue({ mvsListConnection: { connected: true } });
Expand Down Expand Up @@ -123,6 +123,43 @@ describe("FtpMvsApi", () => {
expect(MvsApi.releaseConnection).toBeCalled();
});

it("should upload single space to dataset when secureFtp is true and contents are empty", async () => {
const localFile = tmp.tmpNameSync({ tmpdir: "/tmp" });

fs.writeFileSync(localFile, "");
const response = TestUtils.getSingleLineStream();
DataSetUtils.listDataSets = jest.fn().mockReturnValue([{ dsname: "USER.EMPTYDS", dsorg: "PS", lrecl: 2 }]);
const uploadDataSetMock = jest.fn().mockReturnValue(response);
DataSetUtils.uploadDataSet = uploadDataSetMock;
jest.spyOn(MvsApi, "getContents").mockResolvedValue({ apiResponse: { etag: "123" } } as any);

const mockParams = {
inputFilePath: localFile,
dataSetName: "USER.EMPTYDS",
options: { encoding: "", returnEtag: true, etag: "utf8" },
};
jest.spyOn(MvsApi, "checkedProfile").mockReturnValueOnce({
type: "zftp",
message: "",
profile: {
secureFtp: true,
},
failNotFound: false,
});

jest.spyOn(MvsApi as any, "getContentsTag").mockReturnValue(undefined);
jest.spyOn(fs, "readFileSync").mockReturnValue("");
await MvsApi.putContents(mockParams.inputFilePath, mockParams.dataSetName, mockParams.options);
expect(DataSetUtils.uploadDataSet).toHaveBeenCalledWith({ host: "", password: "", port: "", user: "" }, "USER.EMPTYDS", {
content: " ",
encoding: "",
transferType: "ascii",
});
// ensure options object at runtime does not have localFile
expect(Object.keys(uploadDataSetMock.mock.calls[0][2]).find((k) => k === "localFile")).toBe(undefined);
expect(MvsApi.releaseConnection).toBeCalled();
});

it("should create dataset.", async () => {
DataSetUtils.allocateDataSet = jest.fn();
const DATA_SET_SEQUENTIAL = 4;
Expand Down
26 changes: 17 additions & 9 deletions packages/zowe-explorer-ftp-extension/src/ZoweExplorerFtpMvsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,6 @@ export class FtpMvsApi extends AbstractFtpApi implements ZoweExplorerApi.IMvs {
}

public async putContents(inputFilePath: string, dataSetName: string, options: IUploadOptions): Promise<zowe.IZosFilesResponse> {
const transferOptions = {
transferType: options.binary ? TRANSFER_TYPE_BINARY : TRANSFER_TYPE_ASCII,
localFile: inputFilePath,
encoding: options.encoding,
};
const file = path.basename(inputFilePath).replace(/[^a-z0-9]+/gi, "");
const member = file.substr(0, MAX_MEMBER_NAME_LEN);
let targetDataset: string;
Expand All @@ -135,9 +130,10 @@ export class FtpMvsApi extends AbstractFtpApi implements ZoweExplorerApi.IMvs {
targetDataset = dataSetName + "(" + member + ")";
}
const result = this.getDefaultResponse();
const profile = this.checkedProfile();
let connection;
try {
connection = await this.ftpClient(this.checkedProfile());
connection = await this.ftpClient(profile);
if (!connection) {
ZoweLogger.logImperativeMessage(result.commandResponse, MessageSeverity.ERROR);
throw new Error(result.commandResponse);
Expand All @@ -153,6 +149,16 @@ export class FtpMvsApi extends AbstractFtpApi implements ZoweExplorerApi.IMvs {
}
const lrecl: number = dsAtrribute.apiResponse.items[0].lrecl;
const data = fs.readFileSync(inputFilePath, { encoding: "utf8" });
const transferOptions: Record<string, any> = {
transferType: options.binary ? TRANSFER_TYPE_BINARY : TRANSFER_TYPE_ASCII,
localFile: inputFilePath,
encoding: options.encoding,
};
if (profile.profile.secureFtp && data === "") {
// substitute single space for empty DS contents when saving (avoids FTPS error)
transferOptions.content = " ";
delete transferOptions.localFile;
}
const lines = data.split(/\r?\n/);
const foundIndex = lines.findIndex((line) => line.length > lrecl);
if (foundIndex !== -1) {
Expand Down Expand Up @@ -242,15 +248,17 @@ export class FtpMvsApi extends AbstractFtpApi implements ZoweExplorerApi.IMvs {
}

public async createDataSetMember(dataSetName: string, options?: IUploadOptions): Promise<zowe.IZosFilesResponse> {
const profile = this.checkedProfile();
const transferOptions = {
transferType: options ? TRANSFER_TYPE_BINARY : TRANSFER_TYPE_ASCII,
content: "",
transferType: options.binary ? TRANSFER_TYPE_BINARY : TRANSFER_TYPE_ASCII,
// we have to provide a single space for content over FTPS, or it will fail to upload
content: profile.profile.secureFtp ? " " : "",
encoding: options.encoding,
};
const result = this.getDefaultResponse();
let connection;
try {
connection = await this.ftpClient(this.checkedProfile());
connection = await this.ftpClient(profile);
if (!connection) {
throw new Error(result.commandResponse);
}
Expand Down

0 comments on commit a449d97

Please sign in to comment.