Skip to content
This repository has been archived by the owner on Nov 13, 2023. It is now read-only.

Add streaming APIs to Rest client #199

Merged
merged 9 commits into from Apr 12, 2019
51 changes: 32 additions & 19 deletions package.json
Expand Up @@ -4,30 +4,34 @@
"description": "framework for building configurable CLIs",
"author": "Broadcom",
"license": "EPL-2.0",
"repository": {
"repository": {
"type": "git",
"url": "https://github.com/zowe/imperative.git"
},
"keywords": [
"keywords": [
"CLI",
"framework",
"zowe"
],
"pkg": {
"scripts": [
"pkg": {
"scripts": [
"lib/**/*.js",
"lib/auth/*.js"
],
"assets": [
"assets": [
"../../npm/node_modules/node-gyp/**/*",
"../../npm/lib/"
]
},
"files": ["lib"],
"publishConfig": {"registry": "https://gizaartifactory.jfrog.io/gizaartifactory/api/npm/npm-local-release/"},
"files": [
"lib"
],
"publishConfig": {
"registry": "https://gizaartifactory.jfrog.io/gizaartifactory/api/npm/npm-local-release/"
},
"main": "lib/index.js",
"typings": "lib/index.d.ts",
"scripts": {
"scripts": {
"audit:public": "npm audit --registry https://registry.npmjs.org/",
"build:packages": "gulp build",
"build": "gulp build",
Expand All @@ -43,7 +47,7 @@
"lint:tests": "tslint --format stylish -c ./tslint-tests.json \"**/__tests__/**/*.ts\"",
"watch": "gulp watch"
},
"dependencies": {
"dependencies": {
"@types/yargs": "8.0.2",
"@zowe/perf-timing": "^1.0.3-alpha.201902201448",
"chalk": "2.1.0",
Expand Down Expand Up @@ -71,7 +75,7 @@
"yamljs": "0.3.0",
"yargs": "8.0.2"
},
"devDependencies": {
"devDependencies": {
"@types/body-parser": "1.16.4",
"@types/chai": "4.0.1",
"@types/chai-string": "1.1.30",
Expand Down Expand Up @@ -122,26 +126,34 @@
"uuid": "3.2.1",
"yargs-parser": "9.0.2"
},
"engines": {"node": ">=6.0.0"},
"jest-junit": {"output": "./__tests__/__results__/junit/junit.xml"},
"jestSonar": {"reportPath": "__tests__/__results__/jest-sonar"},
"jest-stare": {
"engines": {
"node": ">=6.0.0"
},
"jest-junit": {
"output": "./__tests__/__results__/junit/junit.xml"
},
"jestSonar": {
"reportPath": "__tests__/__results__/jest-sonar"
},
"jest-stare": {
"resultDir": "__tests__/__results__/jest-stare",
"additionalResultsProcessors": [
"additionalResultsProcessors": [
"jest-junit",
"jest-sonar-reporter"
],
"coverageLink": "../unit/coverage/lcov-report/index.html"
},
"jest": {
"globals": {"ts-jest": {"disableSourceMapSupport": true}},
"jest": {
"globals": {
"ts-jest": {
"disableSourceMapSupport": true}},
"watchPathIgnorePatterns": [".*jest-stare.*\\.js"],
"modulePathIgnorePatterns": ["__tests__/__snapshots__/"],
"setupFilesAfterEnv": ["./__tests__/__integration__/imperative/__tests__/beforeTests.js"],
"testResultsProcessor": "jest-stare",
"transform": {".(ts)": "ts-jest"},
"testRegex": "__tests__.*\\.(spec|test)\\.ts$",
"moduleFileExtensions": [
"moduleFileExtensions": [
"ts",
"js"
],
Expand All @@ -160,5 +172,6 @@
"cobertura"
],
"coverageDirectory": "<rootDir>/__tests__/__results__/unit/coverage"

}
}
}
18 changes: 18 additions & 0 deletions packages/io/__tests__/IO.test.ts
Expand Up @@ -317,6 +317,24 @@ describe("IO tests", () => {
expect(error.message).toMatchSnapshot();
});

it("should get an error for no input on createReadStream", () => {
let error;
try {
IO.createReadStream(" ");
} catch (thrownError) {
error = thrownError;
}
expect(error.message).toMatchSnapshot();
});
it("should get an error for no input on createWriteStream", () => {
let error;
try {
IO.createWriteStream(" ");
} catch (thrownError) {
error = thrownError;
}
expect(error.message).toMatchSnapshot();
});
it("should get an error for no input on createFileSync", () => {
let error;
try {
Expand Down
4 changes: 4 additions & 0 deletions packages/io/__tests__/__snapshots__/IO.test.ts.snap
Expand Up @@ -8,6 +8,10 @@ exports[`IO tests should get an error for no input on createDirsSyncFromFilePath

exports[`IO tests should get an error for no input on createFileSync 1`] = `"Expect Error: Required parameter 'file' must not be blank"`;

exports[`IO tests should get an error for no input on createReadStream 1`] = `"Expect Error: Required parameter 'file' must not be blank"`;

exports[`IO tests should get an error for no input on createWriteStream 1`] = `"Expect Error: Required parameter 'file' must not be blank"`;

exports[`IO tests should get an error for no input on deleteDir 1`] = `"Expect Error: Required parameter 'dir' must not be blank"`;

exports[`IO tests should get an error for no input on deleteFile 1`] = `"Expect Error: Required parameter 'file' must not be blank"`;
Expand Down
23 changes: 23 additions & 0 deletions packages/io/src/IO.ts
Expand Up @@ -16,6 +16,7 @@ import { isNullOrUndefined } from "util";
import { ImperativeReject } from "../../interfaces";
import { ImperativeError } from "../../error";
import { ImperativeExpect } from "../../expect";
import { Readable, Writable } from "stream";

const mkdirp = require("mkdirp");

Expand Down Expand Up @@ -231,6 +232,28 @@ export class IO {
}
}

/**
* Create a Node.js Readable stream from a file
* @param file - the file from which to create a read stream
* @return Buffer - the content of the file
* @memberof IO
*/
public static createReadStream(file: string): Readable {
ImperativeExpect.toBeDefinedAndNonBlank(file, "file");
return fs.createReadStream(file, {autoClose: true});
}

/**
* Create a Node.js Readable stream from a file
* @param file - the file from which to create a read stream
* @return Buffer - the content of the file
* @memberof IO
*/
public static createWriteStream(file: string): Writable {
ImperativeExpect.toBeDefinedAndNonBlank(file, "file");
return fs.createWriteStream(file, {autoClose: true});
}

/**
* Process a string so that its line endings are operating system
* appropriate before you save it to disk
Expand Down
68 changes: 67 additions & 1 deletion packages/rest/__tests__/client/AbstractRestClient.test.ts
Expand Up @@ -133,7 +133,7 @@ describe("AbstractRestClient tests", () => {
});

(https.request as any) = requestFnc;
const headers = [{"My-Header": "value is here"}];
const headers: any = [{"My-Header": "value is here"}];
const payload: any = {"my payload object": "hello"};
let error;
try {
Expand Down Expand Up @@ -416,4 +416,70 @@ describe("AbstractRestClient tests", () => {
expect(httpsRequestFnc).toBeCalled();
expect(httpRequestFnc).not.toBeCalled();
});

it("should not error when streaming data", async () => {

interface IPayload {
data: string;
}

const fakeResponseStream: any = {
write: jest.fn(),
on: jest.fn(),
end: jest.fn()
};
const fakeRequestStream: any = {
on: jest.fn((eventName: string, callback: any) => {
// do nothing
}),
};
const emitter = new MockHttpRequestResponse();
const requestFnc = jest.fn((options, callback) => {
ProcessUtils.nextTick(async () => {

const newEmit = new MockHttpRequestResponse();
callback(newEmit);

await ProcessUtils.nextTick(() => {
newEmit.emit("data", Buffer.from("{\"newData\":", "utf8"));
});

await ProcessUtils.nextTick(() => {
newEmit.emit("data", Buffer.from("\"response data\"}", "utf8"));
});

await ProcessUtils.nextTick(() => {
newEmit.emit("end");
});
});

return emitter;
});

(https.request as any) = requestFnc;

await RestClient.putStreamed(new Session({
hostname: "test",
}), "/resource", [Headers.APPLICATION_JSON], fakeResponseStream, fakeRequestStream);

await RestClient.postStreamed(new Session({
hostname: "test",
}), "/resource", [Headers.APPLICATION_JSON], fakeResponseStream, fakeRequestStream);

await RestClient.putStreamedRequestOnly(new Session({
hostname: "test",
}), "/resource", [Headers.APPLICATION_JSON], fakeRequestStream);

await RestClient.postStreamedRequestOnly(new Session({
hostname: "test",
}), "/resource", [Headers.APPLICATION_JSON], fakeRequestStream);

await RestClient.getStreamed(new Session({
hostname: "test",
}), "/resource", [Headers.APPLICATION_JSON], fakeResponseStream);

await RestClient.deleteStreamed(new Session({
hostname: "test",
}), "/resource", [Headers.APPLICATION_JSON], fakeResponseStream);
});
});