ssh-client-wrapper 2.10.2 → 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/{docker-compose.yml → compose.yml} +2 -2
- package/eslint.config.mjs +206 -0
- package/lib/fork.js +25 -13
- package/lib/index.js +70 -17
- package/lib/rsyncExec.js +17 -17
- package/lib/sshExec.js +81 -58
- package/lib/util.js +28 -21
- package/package.json +10 -12
- package/run_test_all.sh +3 -12
- package/run_test_local.sh +13 -0
- 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 +63 -61
- package/test/testUtil/testFiles.js +21 -7
- package/test/testUtil/testrc +1 -0
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,127 +13,129 @@ 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
|
+
const { send } = require("../lib/rsyncExec.js");
|
|
22
23
|
|
|
23
24
|
describe("test for ssh execution", function () {
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
const rcfilePath = "/home/testuser/testrc";
|
|
26
|
+
this.timeout(65000);//eslint-disable-line no-invalid-this
|
|
27
|
+
beforeEach(async ()=>{
|
|
26
28
|
sshout.reset();
|
|
27
29
|
await clearRemoteTestFiles(hostInfo);
|
|
28
30
|
});
|
|
29
|
-
|
|
31
|
+
before(async ()=>{
|
|
32
|
+
await send(hostInfo, [path.resolve(__dirname, "./testUtil/testrc")], rcfilePath, [], 0);
|
|
33
|
+
});
|
|
34
|
+
after(async ()=>{
|
|
30
35
|
if (!process.env.TEST_KEEP_FILES) {
|
|
31
36
|
await clearRemoteTestFiles(hostInfo);
|
|
32
37
|
await clearLocalTestFiles();
|
|
38
|
+
await sshExec(hostInfo, `rm ${rcfilePath}`);
|
|
33
39
|
}
|
|
34
40
|
await disconnect(hostInfo);
|
|
35
41
|
});
|
|
36
|
-
describe("#exec", ()
|
|
42
|
+
describe("#exec", ()=>{
|
|
37
43
|
const testText = "hoge";
|
|
38
|
-
it("should execute single command with stdout", async ()
|
|
44
|
+
it("should execute single command with stdout", async ()=>{
|
|
39
45
|
const rt = await sshExec(hostInfo, `echo ${testText}`, 0, sshout);
|
|
40
46
|
expect(rt).to.equal(0);
|
|
41
47
|
expect(sshout).to.be.called;
|
|
42
48
|
|
|
43
49
|
expect(sshout).to.be.calledWithMatch(/^hoge/);
|
|
44
50
|
});
|
|
45
|
-
it("should execute single command with stderr", async ()
|
|
51
|
+
it("should execute single command with stderr", async ()=>{
|
|
46
52
|
const rt = await sshExec(hostInfo, `echo ${testText} >&2`, 0, sshout);
|
|
47
53
|
expect(rt).to.equal(0);
|
|
48
54
|
expect(sshout).to.be.called;
|
|
49
55
|
expect(sshout).to.be.calledWithMatch(/^hoge/);
|
|
50
56
|
});
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
it("should execute 2 command by prependCmd ", async ()=>{
|
|
58
|
+
const rt = await sshExec(hostInfo, `echo ${testText}`, 0, sshout, null, "echo foo");
|
|
59
|
+
expect(rt).to.equal(0);
|
|
60
|
+
expect(sshout).to.be.calledOnce;
|
|
61
|
+
expect(sshout).to.be.calledWithMatch(/^foo\nhoge\n/);
|
|
62
|
+
});
|
|
63
|
+
it("should execute 2 command by hostInfo.prependCmd ", async ()=>{
|
|
64
|
+
hostInfo.prependCmd = "echo foo";
|
|
65
|
+
const rt = await sshExec(hostInfo, `echo ${testText}`, 0, sshout, null);
|
|
66
|
+
expect(rt).to.equal(0);
|
|
67
|
+
expect(sshout).to.be.calledOnce;
|
|
68
|
+
expect(sshout).to.be.calledWithMatch(/^foo\nhoge\n/);
|
|
69
|
+
delete hostInfo.prependCmd;
|
|
70
|
+
});
|
|
71
|
+
it("should execute 2 command by rcfile", async ()=>{
|
|
72
|
+
const rt = await sshExec(hostInfo, `echo ${testText}`, 0, sshout, rcfilePath);
|
|
73
|
+
expect(rt).to.equal(0);
|
|
74
|
+
expect(sshout).to.be.calledOnce;
|
|
75
|
+
expect(sshout).to.be.calledWithMatch(/^this is test rcfile\nhoge\n/);
|
|
76
|
+
});
|
|
77
|
+
it("should execute 2 command by hostInfo.prependCmd ", async ()=>{
|
|
78
|
+
hostInfo.rcfile = rcfilePath;
|
|
79
|
+
const rt = await sshExec(hostInfo, `echo ${testText}`, 0, sshout, null);
|
|
80
|
+
expect(rt).to.equal(0);
|
|
81
|
+
expect(sshout).to.be.calledOnce;
|
|
82
|
+
expect(sshout).to.be.calledWithMatch(/^this is test rcfile\nhoge\n/);
|
|
83
|
+
delete hostInfo.rcfile;
|
|
84
|
+
});
|
|
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 ()=>{
|
|
55
89
|
await sshExec(hostInfo, "echo echo hoge >hoge");
|
|
56
90
|
await sshExec(hostInfo, "chmod ugo-x hoge");
|
|
57
91
|
const rt = await sshExec(hostInfo, "./hoge");
|
|
58
92
|
expect(rt).to.equal(126);
|
|
59
93
|
});
|
|
60
|
-
it("should resolve with 127 if command is not found", async ()
|
|
94
|
+
it("should resolve with 127 if command is not found", async ()=>{
|
|
61
95
|
const rt = await sshExec(hostInfo, `./${nonExisting}`);
|
|
62
96
|
expect(rt).to.equal(127);
|
|
63
97
|
});
|
|
64
|
-
it("should be rejected if timeout expired", async ()
|
|
98
|
+
it("should be rejected if timeout expired", async ()=>{
|
|
65
99
|
return expect(sshExec(hostInfo, "sleep 60", 1)).to.be.rejectedWith("watchdog timer expired");
|
|
66
100
|
});
|
|
67
101
|
});
|
|
68
|
-
describe("#canConnect", ()
|
|
102
|
+
describe("#canConnect", ()=>{
|
|
69
103
|
let hostInfo2;
|
|
70
|
-
beforeEach(async ()
|
|
104
|
+
beforeEach(async ()=>{
|
|
71
105
|
await disconnect(hostInfo);
|
|
72
106
|
hostInfo2 = { ...hostInfo };
|
|
73
107
|
hostInfo2.masterPty = null;
|
|
74
108
|
});
|
|
75
|
-
afterEach(async ()
|
|
109
|
+
afterEach(async ()=>{
|
|
76
110
|
await disconnect(hostInfo2);
|
|
77
111
|
});
|
|
78
|
-
it("should be resolved with true", async ()
|
|
112
|
+
it("should be resolved with true", async ()=>{
|
|
79
113
|
return expect(await canConnect(hostInfo2, 5)).to.be.true;
|
|
80
114
|
});
|
|
81
|
-
it("should be rejected if user does not exist", async ()
|
|
115
|
+
it("should be rejected if user does not exist", async ()=>{
|
|
82
116
|
hostInfo2.user = "xxxx";
|
|
83
117
|
return expect(canConnect(hostInfo2, 2)).to.be.rejected;
|
|
84
118
|
});
|
|
85
|
-
it("should be rejected if password is wrong", async ()
|
|
119
|
+
it("should be rejected if password is wrong", async ()=>{
|
|
86
120
|
hostInfo2.password = "xxxx";
|
|
87
121
|
return expect(canConnect(hostInfo2, 2)).to.be.rejected;
|
|
88
122
|
});
|
|
89
|
-
it("should be rejected if host does not exist", async ()
|
|
123
|
+
it("should be rejected if host does not exist", async ()=>{
|
|
90
124
|
hostInfo2.host = "foo.bar.example.com";
|
|
91
125
|
return expect(canConnect(hostInfo2, 2)).to.be.rejectedWith(255);
|
|
92
126
|
});
|
|
93
|
-
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 ()=>{
|
|
94
128
|
hostInfo2.host = "192.0.2.1";
|
|
95
|
-
hostInfo2.ConnectTimeout = 8; //
|
|
129
|
+
hostInfo2.ConnectTimeout = 8; //please note each test will be timed out in 20 seconds
|
|
96
130
|
return expect(canConnect(hostInfo2, 2)).to.be.rejectedWith(255);
|
|
97
131
|
});
|
|
98
|
-
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 ()=>{
|
|
99
133
|
hostInfo2.port = -1;
|
|
100
134
|
return expect(canConnect(hostInfo2, 2)).to.be.rejectedWith("invalid port specified -1");
|
|
101
135
|
});
|
|
102
|
-
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 ()=>{
|
|
103
137
|
hostInfo2.port = 65536;
|
|
104
138
|
return expect(canConnect(hostInfo2, 2)).to.be.rejectedWith("invalid port specified 65536");
|
|
105
139
|
});
|
|
106
140
|
});
|
|
107
|
-
describe("#ls", () => {
|
|
108
|
-
beforeEach(async () => {
|
|
109
|
-
await createRemoteFiles(hostInfo);
|
|
110
|
-
});
|
|
111
|
-
it("should return array of file and directory names in the specified directory", async () => {
|
|
112
|
-
expect(await ls(hostInfo, remoteRoot, [], 0)).to.have.members(["foo", "bar", "baz", "hoge", "huga"]);
|
|
113
|
-
});
|
|
114
|
-
it("should return array which has only specified file", async () => {
|
|
115
|
-
expect(await ls(hostInfo, path.posix.join(remoteRoot, "foo"), [], 0)).to.eql([path.posix.join(remoteRoot, "foo")]);
|
|
116
|
-
});
|
|
117
|
-
it("should return 'No such file or directory' if non-existing path is specified", async () => {
|
|
118
|
-
expect(await ls(hostInfo, path.posix.join(remoteRoot, nonExisting), [], 0)).to.match(/No such file or directory/);
|
|
119
|
-
});
|
|
120
|
-
it("should return only matched filenames, if glob is specified", async () => {
|
|
121
|
-
expect(await ls(hostInfo, path.posix.join(remoteRoot, "b*"), [], 0)).to.have.members(["bar", "baz"].map((e) => {
|
|
122
|
-
return path.posix.join(remoteRoot, e);
|
|
123
|
-
}));
|
|
124
|
-
});
|
|
125
|
-
it("should return only matched filenames src contains multipl glob pattern", async () => {
|
|
126
|
-
expect(await ls(hostInfo, path.posix.join(remoteRoot, "h*", "p[iu]yo"), [], 0)).to.have.members(["piyo", "puyo"].map((e) => {
|
|
127
|
-
return path.posix.join(remoteRoot, "hoge", e);
|
|
128
|
-
}));
|
|
129
|
-
});
|
|
130
|
-
it("should return only matched filenames, if specified glob contains /", async () => {
|
|
131
|
-
expect(await ls(hostInfo, path.posix.join(remoteRoot, "hoge/*yo"), [], 0)).to.have.members(["piyo", "puyo", "poyo"].map((e) => {
|
|
132
|
-
return path.posix.join(remoteRoot, "hoge", e);
|
|
133
|
-
}));
|
|
134
|
-
});
|
|
135
|
-
it("should return empty array if specified target is empty directory", async () => {
|
|
136
|
-
expect(await ls(hostInfo, remoteEmptyDir, [], 0)).to.be.an("array").that.is.empty;
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
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
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
echo this is test rcfile
|