ssh-client-wrapper 2.11.0 → 2.12.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/coverage_report.yml +1 -1
- package/.github/workflows/run_test.yml +3 -2
- package/eslint.config.mjs +206 -0
- package/lib/fork.js +25 -13
- package/lib/index.js +63 -16
- package/lib/rsyncExec.js +17 -17
- package/lib/sshExec.js +69 -59
- package/lib/util.js +25 -21
- package/package.json +10 -12
- package/test/getSshOption.js +7 -7
- package/test/interface.js +54 -54
- package/test/rsyncExec.js +72 -72
- package/test/sanityCheck.js +14 -14
- package/test/sshExec.js +34 -66
- package/test/testUtil/testFiles.js +21 -7
package/test/sshExec.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const path = require("path");
|
|
3
3
|
|
|
4
|
-
process.on("unhandledRejection", console.dir); //
|
|
4
|
+
process.on("unhandledRejection", console.dir); //eslint-disable-line no-console
|
|
5
5
|
Error.traceLimit = 100000;
|
|
6
6
|
|
|
7
|
-
//
|
|
7
|
+
//setup test framework
|
|
8
8
|
const chai = require("chai");
|
|
9
9
|
const { expect } = require("chai");
|
|
10
10
|
const sinon = require("sinon");
|
|
@@ -13,25 +13,25 @@ chai.use(require("chai-as-promised"));
|
|
|
13
13
|
|
|
14
14
|
const sshout = sinon.stub();
|
|
15
15
|
|
|
16
|
-
//
|
|
17
|
-
const { sshExec, canConnect, disconnect
|
|
16
|
+
//testee
|
|
17
|
+
const { sshExec, canConnect, disconnect } = require("../lib/sshExec.js");
|
|
18
18
|
|
|
19
|
-
//
|
|
19
|
+
//test helpers
|
|
20
20
|
const hostInfo = require("./testUtil/hostInfo.js");
|
|
21
|
-
const { clearRemoteTestFiles, clearLocalTestFiles,
|
|
21
|
+
const { clearRemoteTestFiles, clearLocalTestFiles, nonExisting } = require("./testUtil/testFiles.js");
|
|
22
22
|
const { send } = require("../lib/rsyncExec.js");
|
|
23
23
|
|
|
24
24
|
describe("test for ssh execution", function () {
|
|
25
25
|
const rcfilePath = "/home/testuser/testrc";
|
|
26
|
-
this.timeout(65000);//
|
|
27
|
-
beforeEach(async ()
|
|
26
|
+
this.timeout(65000);//eslint-disable-line no-invalid-this
|
|
27
|
+
beforeEach(async ()=>{
|
|
28
28
|
sshout.reset();
|
|
29
29
|
await clearRemoteTestFiles(hostInfo);
|
|
30
30
|
});
|
|
31
|
-
before(async ()
|
|
31
|
+
before(async ()=>{
|
|
32
32
|
await send(hostInfo, [path.resolve(__dirname, "./testUtil/testrc")], rcfilePath, [], 0);
|
|
33
33
|
});
|
|
34
|
-
after(async ()
|
|
34
|
+
after(async ()=>{
|
|
35
35
|
if (!process.env.TEST_KEEP_FILES) {
|
|
36
36
|
await clearRemoteTestFiles(hostInfo);
|
|
37
37
|
await clearLocalTestFiles();
|
|
@@ -39,28 +39,28 @@ describe("test for ssh execution", function () {
|
|
|
39
39
|
}
|
|
40
40
|
await disconnect(hostInfo);
|
|
41
41
|
});
|
|
42
|
-
describe("#exec", ()
|
|
42
|
+
describe("#exec", ()=>{
|
|
43
43
|
const testText = "hoge";
|
|
44
|
-
it("should execute single command with stdout", async ()
|
|
44
|
+
it("should execute single command with stdout", async ()=>{
|
|
45
45
|
const rt = await sshExec(hostInfo, `echo ${testText}`, 0, sshout);
|
|
46
46
|
expect(rt).to.equal(0);
|
|
47
47
|
expect(sshout).to.be.called;
|
|
48
48
|
|
|
49
49
|
expect(sshout).to.be.calledWithMatch(/^hoge/);
|
|
50
50
|
});
|
|
51
|
-
it("should execute single command with stderr", async ()
|
|
51
|
+
it("should execute single command with stderr", async ()=>{
|
|
52
52
|
const rt = await sshExec(hostInfo, `echo ${testText} >&2`, 0, sshout);
|
|
53
53
|
expect(rt).to.equal(0);
|
|
54
54
|
expect(sshout).to.be.called;
|
|
55
55
|
expect(sshout).to.be.calledWithMatch(/^hoge/);
|
|
56
56
|
});
|
|
57
|
-
it("should execute 2 command by prependCmd ", async ()
|
|
57
|
+
it("should execute 2 command by prependCmd ", async ()=>{
|
|
58
58
|
const rt = await sshExec(hostInfo, `echo ${testText}`, 0, sshout, null, "echo foo");
|
|
59
59
|
expect(rt).to.equal(0);
|
|
60
60
|
expect(sshout).to.be.calledOnce;
|
|
61
61
|
expect(sshout).to.be.calledWithMatch(/^foo\nhoge\n/);
|
|
62
62
|
});
|
|
63
|
-
it("should execute 2 command by hostInfo.prependCmd ", async ()
|
|
63
|
+
it("should execute 2 command by hostInfo.prependCmd ", async ()=>{
|
|
64
64
|
hostInfo.prependCmd = "echo foo";
|
|
65
65
|
const rt = await sshExec(hostInfo, `echo ${testText}`, 0, sshout, null);
|
|
66
66
|
expect(rt).to.equal(0);
|
|
@@ -68,13 +68,13 @@ describe("test for ssh execution", function () {
|
|
|
68
68
|
expect(sshout).to.be.calledWithMatch(/^foo\nhoge\n/);
|
|
69
69
|
delete hostInfo.prependCmd;
|
|
70
70
|
});
|
|
71
|
-
it("should execute 2 command by rcfile", async ()
|
|
71
|
+
it("should execute 2 command by rcfile", async ()=>{
|
|
72
72
|
const rt = await sshExec(hostInfo, `echo ${testText}`, 0, sshout, rcfilePath);
|
|
73
73
|
expect(rt).to.equal(0);
|
|
74
74
|
expect(sshout).to.be.calledOnce;
|
|
75
75
|
expect(sshout).to.be.calledWithMatch(/^this is test rcfile\nhoge\n/);
|
|
76
76
|
});
|
|
77
|
-
it("should execute 2 command by hostInfo.prependCmd ", async ()
|
|
77
|
+
it("should execute 2 command by hostInfo.prependCmd ", async ()=>{
|
|
78
78
|
hostInfo.rcfile = rcfilePath;
|
|
79
79
|
const rt = await sshExec(hostInfo, `echo ${testText}`, 0, sshout, null);
|
|
80
80
|
expect(rt).to.equal(0);
|
|
@@ -82,92 +82,60 @@ describe("test for ssh execution", function () {
|
|
|
82
82
|
expect(sshout).to.be.calledWithMatch(/^this is test rcfile\nhoge\n/);
|
|
83
83
|
delete hostInfo.rcfile;
|
|
84
84
|
});
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
it("should not execute command which do not have exec permission", async ()
|
|
85
|
+
//please note that exec() resolves with non-zero value
|
|
86
|
+
//(126 permission deny or 127 file not found)
|
|
87
|
+
//but does not reject in following 2 cases
|
|
88
|
+
it("should not execute command which do not have exec permission", async ()=>{
|
|
89
89
|
await sshExec(hostInfo, "echo echo hoge >hoge");
|
|
90
90
|
await sshExec(hostInfo, "chmod ugo-x hoge");
|
|
91
91
|
const rt = await sshExec(hostInfo, "./hoge");
|
|
92
92
|
expect(rt).to.equal(126);
|
|
93
93
|
});
|
|
94
|
-
it("should resolve with 127 if command is not found", async ()
|
|
94
|
+
it("should resolve with 127 if command is not found", async ()=>{
|
|
95
95
|
const rt = await sshExec(hostInfo, `./${nonExisting}`);
|
|
96
96
|
expect(rt).to.equal(127);
|
|
97
97
|
});
|
|
98
|
-
it("should be rejected if timeout expired", async ()
|
|
98
|
+
it("should be rejected if timeout expired", async ()=>{
|
|
99
99
|
return expect(sshExec(hostInfo, "sleep 60", 1)).to.be.rejectedWith("watchdog timer expired");
|
|
100
100
|
});
|
|
101
101
|
});
|
|
102
|
-
describe("#canConnect", ()
|
|
102
|
+
describe("#canConnect", ()=>{
|
|
103
103
|
let hostInfo2;
|
|
104
|
-
beforeEach(async ()
|
|
104
|
+
beforeEach(async ()=>{
|
|
105
105
|
await disconnect(hostInfo);
|
|
106
106
|
hostInfo2 = { ...hostInfo };
|
|
107
107
|
hostInfo2.masterPty = null;
|
|
108
108
|
});
|
|
109
|
-
afterEach(async ()
|
|
109
|
+
afterEach(async ()=>{
|
|
110
110
|
await disconnect(hostInfo2);
|
|
111
111
|
});
|
|
112
|
-
it("should be resolved with true", async ()
|
|
112
|
+
it("should be resolved with true", async ()=>{
|
|
113
113
|
return expect(await canConnect(hostInfo2, 5)).to.be.true;
|
|
114
114
|
});
|
|
115
|
-
it("should be rejected if user does not exist", async ()
|
|
115
|
+
it("should be rejected if user does not exist", async ()=>{
|
|
116
116
|
hostInfo2.user = "xxxx";
|
|
117
117
|
return expect(canConnect(hostInfo2, 2)).to.be.rejected;
|
|
118
118
|
});
|
|
119
|
-
it("should be rejected if password is wrong", async ()
|
|
119
|
+
it("should be rejected if password is wrong", async ()=>{
|
|
120
120
|
hostInfo2.password = "xxxx";
|
|
121
121
|
return expect(canConnect(hostInfo2, 2)).to.be.rejected;
|
|
122
122
|
});
|
|
123
|
-
it("should be rejected if host does not exist", async ()
|
|
123
|
+
it("should be rejected if host does not exist", async ()=>{
|
|
124
124
|
hostInfo2.host = "foo.bar.example.com";
|
|
125
125
|
return expect(canConnect(hostInfo2, 2)).to.be.rejectedWith(255);
|
|
126
126
|
});
|
|
127
|
-
it("should be rejected if host(ip address) does not exist", async ()
|
|
127
|
+
it("should be rejected if host(ip address) does not exist", async ()=>{
|
|
128
128
|
hostInfo2.host = "192.0.2.1";
|
|
129
|
-
hostInfo2.ConnectTimeout = 8; //
|
|
129
|
+
hostInfo2.ConnectTimeout = 8; //please note each test will be timed out in 20 seconds
|
|
130
130
|
return expect(canConnect(hostInfo2, 2)).to.be.rejectedWith(255);
|
|
131
131
|
});
|
|
132
|
-
it("should be rejected if port number is out of range(-1)", async ()
|
|
132
|
+
it("should be rejected if port number is out of range(-1)", async ()=>{
|
|
133
133
|
hostInfo2.port = -1;
|
|
134
134
|
return expect(canConnect(hostInfo2, 2)).to.be.rejectedWith("invalid port specified -1");
|
|
135
135
|
});
|
|
136
|
-
it("should be rejected if port number is out of range(65536)", async ()
|
|
136
|
+
it("should be rejected if port number is out of range(65536)", async ()=>{
|
|
137
137
|
hostInfo2.port = 65536;
|
|
138
138
|
return expect(canConnect(hostInfo2, 2)).to.be.rejectedWith("invalid port specified 65536");
|
|
139
139
|
});
|
|
140
140
|
});
|
|
141
|
-
describe("#ls", () => {
|
|
142
|
-
beforeEach(async () => {
|
|
143
|
-
await createRemoteFiles(hostInfo);
|
|
144
|
-
});
|
|
145
|
-
it("should return array of file and directory names in the specified directory", async () => {
|
|
146
|
-
expect(await ls(hostInfo, remoteRoot, [], 0)).to.have.members(["foo", "bar", "baz", "hoge", "huga"]);
|
|
147
|
-
});
|
|
148
|
-
it("should return array which has only specified file", async () => {
|
|
149
|
-
expect(await ls(hostInfo, path.posix.join(remoteRoot, "foo"), [], 0)).to.eql([path.posix.join(remoteRoot, "foo")]);
|
|
150
|
-
});
|
|
151
|
-
it("should return 'No such file or directory' if non-existing path is specified", async () => {
|
|
152
|
-
expect(await ls(hostInfo, path.posix.join(remoteRoot, nonExisting), [], 0)).to.match(/No such file or directory/);
|
|
153
|
-
});
|
|
154
|
-
it("should return only matched filenames, if glob is specified", async () => {
|
|
155
|
-
expect(await ls(hostInfo, path.posix.join(remoteRoot, "b*"), [], 0)).to.have.members(["bar", "baz"].map((e) => {
|
|
156
|
-
return path.posix.join(remoteRoot, e);
|
|
157
|
-
}));
|
|
158
|
-
});
|
|
159
|
-
it("should return only matched filenames src contains multipl glob pattern", async () => {
|
|
160
|
-
expect(await ls(hostInfo, path.posix.join(remoteRoot, "h*", "p[iu]yo"), [], 0)).to.have.members(["piyo", "puyo"].map((e) => {
|
|
161
|
-
return path.posix.join(remoteRoot, "hoge", e);
|
|
162
|
-
}));
|
|
163
|
-
});
|
|
164
|
-
it("should return only matched filenames, if specified glob contains /", async () => {
|
|
165
|
-
expect(await ls(hostInfo, path.posix.join(remoteRoot, "hoge/*yo"), [], 0)).to.have.members(["piyo", "puyo", "poyo"].map((e) => {
|
|
166
|
-
return path.posix.join(remoteRoot, "hoge", e);
|
|
167
|
-
}));
|
|
168
|
-
});
|
|
169
|
-
it("should return empty array if specified target is empty directory", async () => {
|
|
170
|
-
expect(await ls(hostInfo, remoteEmptyDir, [], 0)).to.be.an("array").that.is.empty;
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
141
|
});
|
|
@@ -47,33 +47,47 @@ const nonExisting = "ARSSH_nonExisting";
|
|
|
47
47
|
/*
|
|
48
48
|
* prepare local files which contain its filename
|
|
49
49
|
*/
|
|
50
|
-
|
|
50
|
+
/**
|
|
51
|
+
*
|
|
52
|
+
*/
|
|
53
|
+
async function createLocalFiles() {
|
|
51
54
|
const localDir2 = path.join(localRoot, "hoge");
|
|
52
55
|
const promises = [];
|
|
53
56
|
await fs.mkdir(localRoot);
|
|
54
57
|
await fs.mkdir(localDir2);
|
|
55
58
|
promises.push(fs.mkdir(localEmptyDir));
|
|
56
|
-
localFiles.forEach((localFile)
|
|
59
|
+
localFiles.forEach((localFile)=>{
|
|
57
60
|
promises.push(fs.writeFile(localFile, `${localFile}\n`));
|
|
58
61
|
});
|
|
59
62
|
return Promise.all(promises);
|
|
60
63
|
}
|
|
61
64
|
|
|
62
|
-
|
|
65
|
+
/**
|
|
66
|
+
*
|
|
67
|
+
*/
|
|
68
|
+
async function clearLocalTestFiles() {
|
|
63
69
|
return fs.remove(localRoot);
|
|
64
70
|
}
|
|
65
71
|
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
/**
|
|
73
|
+
*
|
|
74
|
+
* @param hostInfo
|
|
75
|
+
*/
|
|
76
|
+
async function createRemoteFiles(hostInfo) {
|
|
77
|
+
//create remote files
|
|
68
78
|
await sshExec(hostInfo, `mkdir -p ${remoteRoot}/hoge`);
|
|
69
79
|
await sshExec(hostInfo, `mkdir -p ${remoteEmptyDir}`);
|
|
70
|
-
const script = remoteFiles.reduce((a, remoteFile)
|
|
80
|
+
const script = remoteFiles.reduce((a, remoteFile)=>{
|
|
71
81
|
return `${a}echo ${remoteFile} > ${remoteFile};`;
|
|
72
82
|
}, "");
|
|
73
83
|
return sshExec(hostInfo, `${script}`);
|
|
74
84
|
}
|
|
75
85
|
|
|
76
|
-
|
|
86
|
+
/**
|
|
87
|
+
*
|
|
88
|
+
* @param hostInfo
|
|
89
|
+
*/
|
|
90
|
+
async function clearRemoteTestFiles(hostInfo) {
|
|
77
91
|
return sshExec(hostInfo, `rm -fr ${remoteRoot}`);
|
|
78
92
|
}
|
|
79
93
|
|