Skip to content

Commit

Permalink
Merge pull request #2283 from zowe/fix/binary-spool-files
Browse files Browse the repository at this point in the history
fix(jobs): Binary spool files corrupted by newline normalization
  • Loading branch information
t1m0thyj authored Oct 2, 2024
2 parents 65aa7d0 + 2a46100 commit 69005d4
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 73 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"audit:public": "npm audit --registry https://registry.npmjs.org/",
"bundle:webHelp": "cd packages/imperative/web-help && node build.js",
"prepare": "husky && npm run bundle:webHelp",
"package": "node scripts/bundleCliTgz.js"
"package": "lerna run prepublishOnly && node scripts/bundleCliTgz.js"
},
"devDependencies": {
"@lerna-lite/changed": "^3.6.0",
Expand Down
18 changes: 9 additions & 9 deletions packages/cli/src/zostso/issue/command/Command.handler.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/*
* 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.
*
*/
* 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 { IHandlerParameters } from "@zowe/imperative";
import {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -796,17 +796,17 @@ describe("Configuration Secure command handler", () => {
let handler: SecureHandler;
let params: any;
let myPromptSpy: jest.SpyInstance;

beforeEach(() => {
handler = new SecureHandler();
params = getIHandlerParametersObject();

params.arguments.userConfig = true;
params.arguments.globalConfig = true;

// Mock the console prompt to return an empty string
myPromptSpy = jest.spyOn(params.response.console, "prompt").mockResolvedValue("");

// Reset spies
keytarGetPasswordSpy.mockReturnValue(fakeSecureData);
keytarSetPasswordSpy.mockImplementation();
Expand All @@ -816,38 +816,38 @@ describe("Configuration Secure command handler", () => {
existsSyncSpy = jest.spyOn(fs, "existsSync");
writeFileSyncSpy.mockImplementation();
});
const runTest = async (profile: string, secureFields: string[], expectedPromptTimes: number, expectedSecureField: string) => {

const expectSecurePrompt = async (profile: string, secureFields: string[], expectedPromptTimes: number, expectedSecureField: string) => {
params.arguments.profile = profile;

// Mock fs calls
const eco = lodash.cloneDeep(expectedGlobalUserConfigObject);
eco.$schema = "./fakeapp.schema.json";
readFileSyncSpy.mockReturnValueOnce(JSON.stringify(eco));
existsSyncSpy.mockReturnValueOnce(false).mockReturnValueOnce(false).mockReturnValueOnce(true).mockReturnValue(false);
searchSpy.mockReturnValueOnce(fakeProjUserPath).mockReturnValueOnce(fakeProjPath);
await setupConfigToLoad(undefined, configOpts);

// Setup mock secure fields
jest.spyOn(ImperativeConfig.instance.config.api.secure, "secureFields").mockReturnValue(secureFields);

let caughtError;
try {
await handler.process(params);
} catch (error) {
caughtError = error;
}

// Verify prompt count and inclusion of expected secure fields
expect(myPromptSpy).toHaveBeenCalledTimes(expectedPromptTimes);
if (expectedPromptTimes > 0) {
expect(myPromptSpy).toHaveBeenCalledWith(expect.stringContaining(expectedSecureField), { "hideText": true });
}
expect(caughtError).toBeUndefined();
};

it("should only prompt for secure values that match the profile passed in through params", async () => {
await runTest(
await expectSecurePrompt(
"GoodProfile",
[
"profiles.noMatchProfile.properties.tokenValue",
Expand All @@ -858,9 +858,9 @@ describe("Configuration Secure command handler", () => {
"profiles.GoodProfile.properties.tokenValue"
);
});

it("should only prompt for secure values that match the profile passed in through params - nested profile", async () => {
await runTest(
await expectSecurePrompt(
"lpar1.GoodProfile",
[
"profiles.noMatchProfile.properties.tokenValue",
Expand All @@ -871,9 +871,9 @@ describe("Configuration Secure command handler", () => {
"profiles.lpar1.profiles.GoodProfile.properties.tokenValue"
);
});

it("should only prompt for secure values that match the profile passed in through params - ignore casing", async () => {
await runTest(
await expectSecurePrompt(
"gOODpROFILE",
[
"profiles.noMatchProfile.properties.tokenValue",
Expand All @@ -884,9 +884,9 @@ describe("Configuration Secure command handler", () => {
"profiles.GoodProfile.properties.tokenValue"
);
});

it("should prompt for all secure values given a profile in which no secure profile value matches", async () => {
await runTest(
await expectSecurePrompt(
"noMatchProfile",
[
"profiles.lpar1.profiles.test.properties.tokenValue",
Expand All @@ -898,6 +898,6 @@ describe("Configuration Secure command handler", () => {
);
});
});

});
});
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/*
* 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.
*
*/
* 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 {
ICommandArguments,
Expand Down
4 changes: 4 additions & 0 deletions packages/zosjobs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

All notable changes to the Zowe z/OS jobs SDK package will be documented in this file.

## Recent Changes

- BugFix: Fixed error in `DownloadJobs.downloadSpoolContentCommon` method causing binary spool files to be corrupted by newline normalization. [#2282](https://github.com/zowe/zowe-cli/issues/2282)

## `8.0.0`

- MAJOR: v8.0.0 Release
Expand Down
33 changes: 17 additions & 16 deletions packages/zosjobs/__tests__/__unit__/DownloadJobs.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*
*/

import { AbstractSession, ImperativeError, IO } from "@zowe/imperative";
import { AbstractSession, Headers, ImperativeError, IO } from "@zowe/imperative";
import { DownloadJobs, GetJobs, IDownloadAllSpoolContentParms, IDownloadSpoolContentParms, IJobFile } from "../../src";
import { ZosmfRestClient } from "@zowe/core-for-zowe-sdk";
import { Writable } from "stream";
Expand Down Expand Up @@ -103,10 +103,7 @@ describe("DownloadJobs", () => {

describe("downloadAllSpoolContentCommon", () => {
it("should allow users to call downloadAllSpoolContentCommon with correct parameters", async () => {
let uri: string = "";
ZosmfRestClient.getStreamed = jest.fn(async (session: AbstractSession, resource: string, reqHeaders?: any[]): Promise<any> => {
uri = resource;
});
const getStreamedSpy = jest.spyOn(ZosmfRestClient, "getStreamed");
const allSpoolParms: IDownloadAllSpoolContentParms = {
jobid: fakeJobID,
jobname: fakeJobName,
Expand All @@ -120,14 +117,15 @@ describe("DownloadJobs", () => {

expect(GetJobs.getSpoolFiles).toHaveBeenCalled();
expect(IO.createDirsSyncFromFilePath).toHaveBeenCalledWith(expectedFile);
expect(uri).not.toContain("fileEncoding");
expect(getStreamedSpy).toHaveBeenCalledTimes(1);
const [_session, resource, reqHeaders, _responseStream, normalizeResponseNewLines] = getStreamedSpy.mock.calls[0];
expect(resource).not.toContain("fileEncoding");
expect(reqHeaders).toContain(Headers.TEXT_PLAIN_UTF8);
expect(normalizeResponseNewLines).toBe(true);
});

it("should allow users to call downloadAllSpoolContentCommon with correct parameters and binary mode", async () => {
let uri: string = "";
ZosmfRestClient.getStreamed = jest.fn(async (session: AbstractSession, resource: string, reqHeaders?: any[]): Promise<any> => {
uri = resource;
});
const getStreamedSpy = jest.spyOn(ZosmfRestClient, "getStreamed");
const allSpoolParms: IDownloadAllSpoolContentParms = {
jobid: fakeJobID,
jobname: fakeJobName,
Expand All @@ -142,14 +140,14 @@ describe("DownloadJobs", () => {

expect(GetJobs.getSpoolFiles).toHaveBeenCalled();
expect(IO.createDirsSyncFromFilePath).toHaveBeenCalledWith(expectedFile);
expect(uri).toContain("?mode=binary");
expect(getStreamedSpy).toHaveBeenCalledTimes(1);
const [_session, resource, _reqHeaders, _responseStream, normalizeResponseNewLines] = getStreamedSpy.mock.calls[0];
expect(resource).toContain("?mode=binary");
expect(normalizeResponseNewLines).toBe(false);
});

it("should allow users to call downloadAllSpoolContentCommon with correct parameters and record mode", async () => {
let uri: string = "";
ZosmfRestClient.getStreamed = jest.fn(async (session: AbstractSession, resource: string, reqHeaders?: any[]): Promise<any> => {
uri = resource;
});
const getStreamedSpy = jest.spyOn(ZosmfRestClient, "getStreamed");
const allSpoolParms: IDownloadAllSpoolContentParms = {
jobid: fakeJobID,
jobname: fakeJobName,
Expand All @@ -164,7 +162,10 @@ describe("DownloadJobs", () => {

expect(GetJobs.getSpoolFiles).toHaveBeenCalled();
expect(IO.createDirsSyncFromFilePath).toHaveBeenCalledWith(expectedFile);
expect(uri).toContain("?mode=record");
expect(getStreamedSpy).toHaveBeenCalledTimes(1);
const [_session, resource, _reqHeaders, _responseStream, normalizeResponseNewLines] = getStreamedSpy.mock.calls[0];
expect(resource).toContain("?mode=record");
expect(normalizeResponseNewLines).toBe(false);
});

it("should allow users to call downloadAllSpoolContentCommon with a job containing duplicate step names", async () => {
Expand Down
3 changes: 2 additions & 1 deletion packages/zosjobs/src/DownloadJobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,9 @@ export class DownloadJobs {
}

const writeStream = parms.stream ?? IO.createWriteStream(file);
const normalizeResponseNewLines = !(parms.binary || parms.record);
await ZosmfRestClient.getStreamed(session, JobsConstants.RESOURCE + parameters, [Headers.TEXT_PLAIN_UTF8], writeStream,
true);
normalizeResponseNewLines);
}

/**
Expand Down
18 changes: 9 additions & 9 deletions packages/zostso/__tests__/__unit__/IssueTso.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/*
* 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.
*
*/
* 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.
*
*/

/* eslint-disable deprecation/deprecation */
import { ImperativeConfig, ImperativeError, Session } from "@zowe/imperative";
Expand Down
18 changes: 9 additions & 9 deletions packages/zostso/src/IssueTso.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/*
* 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.
*
*/
* 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, Headers, ImperativeError } from "@zowe/imperative";
import { IStartTsoParms } from "./doc/input/IStartTsoParms";
Expand Down

0 comments on commit 69005d4

Please sign in to comment.