Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ftp/ds): ECONNRESET FTPS error for empty dataset contents #2460

Merged
merged 11 commits into from
Sep 20, 2023
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 @@ -123,6 +123,35 @@ describe("FtpMvsApi", () => {
expect(MvsApi.releaseConnection).toBeCalled();
});

it("should upload empty contents to dataset.", 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 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
20 changes: 13 additions & 7 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 Down Expand Up @@ -153,6 +148,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 = {
transferType: options.binary ? TRANSFER_TYPE_BINARY : TRANSFER_TYPE_ASCII,
localFile: inputFilePath,
encoding: options.encoding,
};
if (data == "") {
// substitute single space for empty DS contents when saving (prevents FTP 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 @@ -243,8 +248,9 @@ export class FtpMvsApi extends AbstractFtpApi implements ZoweExplorerApi.IMvs {

public async createDataSetMember(dataSetName: string, options?: IUploadOptions): Promise<zowe.IZosFilesResponse> {
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, or zos-node-accessor will fail to upload the data set over FTP
content: " ",
encoding: options.encoding,
};
const result = this.getDefaultResponse();
Expand Down