Skip to content

Commit ea75ced

Browse files
author
Pavel Petroshenko
authored
Merge pull request #41 from electricimp/develop
Tests development
2 parents e42cbdb + b09e2b4 commit ea75ced

File tree

113 files changed

+17475
-2008
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

113 files changed

+17475
-2008
lines changed

Diff for: README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# impt #
1+
# impt #
22

33
*impt* is a command line tool which allows you to interact with the Electric Imp impCloud™ via the [impCentral™ API](https://apidoc.electricimp.com) for the development, testing and deployment of application and factory code, and for device and product management.
44

@@ -753,7 +753,7 @@ Many *impt* commands combine several impCentral API requests which change impCen
753753
754754
## Testing ##
755755
756-
There are [Jasmine](https://www.npmjs.com/package/jasmine) tests available to verify *impt* implementation itself. The [Testing read me](./spec/TestingReadme.md) describes how to run the tests and extend them, if necessary.
756+
There are [Jasmine](https://www.npmjs.com/package/jasmine) tests available to verify *impt* implementation itself. The [Readme](./spec/README.md) describes how to run the tests and extend them, if necessary.
757757
758758
## License ##
759759

Diff for: TestingGuide.md

+47-45
Large diffs are not rendered by default.

Diff for: spec/ImptTestHelper.js

+317
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
// MIT License
2+
//
3+
// Copyright 2018 Electric Imp
4+
//
5+
// SPDX-License-Identifier: MIT
6+
//
7+
// Permission is hereby granted, free of charge, to any person obtaining a copy
8+
// of this software and associated documentation files (the "Software"), to deal
9+
// in the Software without restriction, including without limitation the rights
10+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
// copies of the Software, and to permit persons to whom the Software is
12+
// furnished to do so, subject to the following conditions:
13+
//
14+
// The above copyright notice and this permission notice shall be
15+
// included in all copies or substantial portions of the Software.
16+
//
17+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18+
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
20+
// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
21+
// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
22+
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23+
// OTHER DEALINGS IN THE SOFTWARE.
24+
25+
'use strict';
26+
27+
require('jasmine-expect');
28+
29+
const Shell = require('shelljs');
30+
const FS = require('fs');
31+
const config = require('./config');
32+
const Utils = require('../lib/util/Utils');
33+
const UserInteractor = require('../lib/util/UserInteractor');
34+
const child_process = require('child_process');
35+
36+
const TIMEOUT_MS = 300000;
37+
const TESTS_EXECUTION_FOLDER = `${__dirname}/../__test${process.env.IMPT_FOLDER_SUFFIX ? process.env.IMPT_FOLDER_SUFFIX : ''}`;
38+
const KEY_ANSWER = {
39+
CTRL_C: '\x03',
40+
ENTER: '\n',
41+
YES: 'y',
42+
NO: 'n'
43+
};
44+
const ENV_VARS = [
45+
'IMPT_USER',
46+
'IMPT_PASSWORD',
47+
'IMPT_LOGINKEY',
48+
'IMPT_ENDPOINT',
49+
'IMPT_AUTH_FILE_PATH'
50+
]
51+
52+
// Helper class for testing impt tool.
53+
// Contains common methods for testing environment initialization and cleanup,
54+
// running impt commands, check commands output, ...
55+
class ImptTestHelper {
56+
static get TIMEOUT() {
57+
return TIMEOUT_MS;
58+
}
59+
60+
static get TESTS_EXECUTION_FOLDER() {
61+
return TESTS_EXECUTION_FOLDER;
62+
}
63+
64+
static get ATTR_NAME() {
65+
return 'name';
66+
}
67+
68+
static get ATTR_DESCRIPTION() {
69+
return 'description';
70+
}
71+
72+
static get ATTR_ID() {
73+
return 'id';
74+
}
75+
76+
static get ATTR_TYPE() {
77+
return 'type';
78+
}
79+
80+
static get OUTPUT_MODES() {
81+
const VALID_MODES = ['minimal', 'json', 'debug'];
82+
// default output mode present always
83+
let modes = [''];
84+
config.outputModes.forEach((item) => { if (VALID_MODES.includes(item)) modes.push(`-z ${item}`) });
85+
return modes;
86+
}
87+
88+
static get KEY_ANSWER() {
89+
return KEY_ANSWER;
90+
}
91+
92+
// Initializes testing environment: creates test execution folder, sets default jasmine test timeout.
93+
// Optionally executes 'impt auth login --local' command in the test execution folder.
94+
// Should be called from any test suite beforeAll method.
95+
static init(login = true) {
96+
jasmine.DEFAULT_TIMEOUT_INTERVAL = TIMEOUT_MS;
97+
Utils.removeDirSync(TESTS_EXECUTION_FOLDER);
98+
FS.mkdirSync(TESTS_EXECUTION_FOLDER);
99+
if (!config.username || !config.password) {
100+
console.log("Error! Environment variable IMPT_USER_NAME and/or IMPT_USER_PASSWORD not set");
101+
return;
102+
}
103+
if (login) {
104+
const endpoint = config.apiEndpoint ? `--endpoint ${config.apiEndpoint}` : '';
105+
return ImptTestHelper.runCommand(
106+
`impt auth login --local --user ${config.username} --pwd ${config.password} ${endpoint}`,
107+
ImptTestHelper.checkSuccessStatus);
108+
}
109+
return Promise.resolve();
110+
}
111+
112+
// Cleans up testing environment.
113+
// Should be called from any test suite afterAll method.
114+
static cleanUp() {
115+
Shell.cd(__dirname);
116+
Utils.removeDirSync(TESTS_EXECUTION_FOLDER);
117+
return Promise.resolve();
118+
}
119+
120+
// Executes impt command and calls outputChecker function to check the command output
121+
static runCommand(command, outputChecker, env = {}) {
122+
return new Promise((resolve, reject) => {
123+
if (config.debug) {
124+
console.log('Running command: ' + command);
125+
}
126+
Shell.cd(TESTS_EXECUTION_FOLDER);
127+
ENV_VARS.forEach(function (val) {
128+
if (env[val] === undefined) delete Shell.env[val];
129+
else Shell.env[val] = env[val];
130+
});
131+
Shell.exec(`node ${__dirname}/../bin/${command}`,
132+
{ silent: !config.debug },
133+
(code, stdout, stderr) => {
134+
resolve({ code: code, output: stdout.replace(/((\u001b\[2K.*\u001b\[1G)|(\u001b\[[0-9]{2}m))/g, '') });
135+
}
136+
);
137+
}).then(outputChecker);
138+
}
139+
140+
static runCommandInteractive(command, outputChecker, env = {}) {
141+
return new Promise((resolve, reject) => {
142+
if (config.debug) {
143+
console.log('Running command: ' + command);
144+
}
145+
ENV_VARS.forEach(function (val) {
146+
if (env[val] === undefined) delete Shell.env[val];
147+
else Shell.env[val] = env[val];
148+
});
149+
let child = Shell.exec(`node ${__dirname}/../bin/${command}`,
150+
{ silent: !config.debug },
151+
(code, stdout, stderr) => {
152+
resolve({ code: code, output: stdout.replace(/((\u001b\[2K.*\u001b\[1G)|(\u001b\[[0-9]{2}m))/g, '') });
153+
});
154+
child.stdin.end();
155+
}).then(outputChecker);
156+
}
157+
158+
static runCommandWithTerminate(command, outputChecker) {
159+
var child;
160+
return Promise.all([
161+
new Promise((resolve, reject) => {
162+
if (config.debug) {
163+
console.log('Running command: ' + command);
164+
}
165+
child = Shell.exec(`node ${__dirname}/../bin/${command}`,
166+
{ silent: !config.debug },
167+
(code, stdout, stderr) => {
168+
resolve({ code: code, output: stdout.replace(/((\u001b\[2K.*\u001b\[1G)|(\u001b\[[0-9]{2}m))/g, '') });
169+
});
170+
171+
}).then(outputChecker),
172+
this.delayMs(20000).
173+
then(() => child.kill('SIGINT')).
174+
then(() => child.kill())
175+
]);
176+
}
177+
178+
static delayMs(ms) {
179+
return new Promise((resolve) => {
180+
setTimeout(resolve, ms);
181+
});
182+
}
183+
184+
static getDeviceAttrs(product, dg, output) {
185+
let jsonInfo = null;
186+
return ImptTestHelper.runCommand(`impt product create -n ${product}`, ImptTestHelper.emptyCheckEx).
187+
then(() => ImptTestHelper.runCommand(`impt dg create -n ${dg} -p ${product}`, ImptTestHelper.emptyCheck)).
188+
then(() => ImptTestHelper.runCommand(`impt device assign -d ${config.devices[config.deviceidx]} -g ${dg} -q`, ImptTestHelper.emptyCheckEx)).
189+
then(() => ImptTestHelper.runCommand(`impt build deploy -g ${dg}`, ImptTestHelper.emptyCheckEx)).
190+
then(() => ImptTestHelper.runCommand(`impt device info -d ${config.devices[config.deviceidx]} -z json`, (commandOut) => {
191+
jsonInfo = commandOut.output;
192+
ImptTestHelper.emptyCheck(commandOut);
193+
})).
194+
then(() => ImptTestHelper.runCommand(`impt product delete -p ${product} -f -b -q`, ImptTestHelper.emptyCheckEx)).
195+
then(() => {
196+
return new Promise((resolve) => {
197+
let json = JSON.parse(jsonInfo);
198+
if (json.Device) {
199+
let device_mac = json.Device.mac_address;
200+
let device_name = json.Device.name;
201+
let agent_id = json.Device.agent_id;
202+
resolve({ mac: device_mac, name: device_name, agentid: agent_id });
203+
}
204+
else
205+
resolve(null);
206+
})
207+
}).
208+
then(output);
209+
}
210+
211+
static getAuthAttrs(output) {
212+
let jsonInfo = null;
213+
return ImptTestHelper.runCommand(`impt auth info -z json`, (commandOut) => {
214+
jsonInfo = commandOut.output;
215+
ImptTestHelper.emptyCheck(commandOut);
216+
}).
217+
then(() => {
218+
return new Promise((resolve) => {
219+
let json = JSON.parse(jsonInfo);
220+
if (json.Auth) {
221+
let auth_email = json.Auth.Email;
222+
let user_id = json.Auth['Account id'];
223+
resolve({ email: auth_email, id: user_id });
224+
}
225+
else
226+
resolve(null);
227+
})
228+
}).
229+
then(output);
230+
}
231+
232+
// Checks if file exist in the TESTS_EXECUTION_FOLDER
233+
static checkFileExist(fileName) {
234+
expect(Shell.test('-e', `${TESTS_EXECUTION_FOLDER}/${fileName}`)).toBe(true);
235+
}
236+
237+
// Checks if file not exist in the TESTS_EXECUTION_FOLDER
238+
static checkFileNotExist(fileName) {
239+
expect(Shell.test('-e', `${TESTS_EXECUTION_FOLDER}/${fileName}`)).not.toBe(true);
240+
}
241+
242+
static checkFileEqual(fileName, fileName2) {
243+
expect(Shell.test('-e', `${TESTS_EXECUTION_FOLDER}/${fileName}`)).toBe(true);
244+
expect(Shell.test('-e', `${TESTS_EXECUTION_FOLDER}/${fileName2}`)).toBe(true);
245+
let file = Shell.cat(`${TESTS_EXECUTION_FOLDER}/${fileName}`);
246+
let file2 = Shell.cat(`${TESTS_EXECUTION_FOLDER}/${fileName2}`);
247+
expect(file).toEqual(file2);
248+
}
249+
250+
static projectCreate(dg, dfile = 'device.nut', afile = 'agent.nut') {
251+
return ImptTestHelper.runCommand(`impt project link -g ${dg} -x ${dfile} -y ${afile} -q`, ImptTestHelper.emptyCheckEx);
252+
}
253+
254+
static projectDelete() {
255+
return ImptTestHelper.runCommand(`impt project delete -f -q`, ImptTestHelper.emptyCheckEx);
256+
}
257+
258+
static deviceAssign(dg) {
259+
return ImptTestHelper.runCommand(`impt device assign -d ${config.devices[config.deviceidx]} -g ${dg} -q`, ImptTestHelper.emptyCheckEx);
260+
}
261+
262+
static deviceRestart() {
263+
return ImptTestHelper.runCommand(`impt device restart -d ${config.devices[config.deviceidx]}`, ImptTestHelper.emptyCheckEx);
264+
}
265+
266+
static deviceUnassign(dg) {
267+
return ImptTestHelper.runCommand(`impt dg unassign -g ${dg}`, ImptTestHelper.emptyCheckEx);
268+
}
269+
270+
// Checks success return code of the command
271+
static checkSuccessStatus(commandOut) {
272+
expect(commandOut.code).toEqual(0);
273+
}
274+
275+
// Checks fail return code of the command
276+
static checkFailStatus(commandOut) {
277+
expect(commandOut.output).not.toMatch(UserInteractor.ERRORS.ACCESS_FAILED);
278+
expect(commandOut.code).not.toEqual(0);
279+
}
280+
281+
// Does not check command status, just check 'Access to impCentral failed or timed out' error doesn't occur.
282+
static emptyCheck(commandOut) {
283+
expect(commandOut.output).not.toMatch(UserInteractor.ERRORS.ACCESS_FAILED);
284+
}
285+
286+
// Checks if the command output contains the specified attribute name and value
287+
static checkAttribute(commandOut, attrName, attrValue) {
288+
expect(commandOut.output).toMatch(new RegExp(`${attrName}"?:\\s+"?${attrValue.replace(new RegExp(/"/g), '\\\\?"')}"?`));
289+
}
290+
291+
// Checks if the command output contains the specified message for default or debug output mode
292+
static checkOutputMessage(outputMode, commandOut, message) {
293+
const matcher = outputMode.match('-(z|-output)\\s+(json|minimal)');
294+
if (matcher && matcher.length) expect(true).toBeTrue;
295+
else expect(commandOut.output).toMatch(new RegExp(message.replace(new RegExp(/[()\\]/g), `\\$&`).replace(new RegExp(/"/g), '\\\\?"')));
296+
}
297+
298+
// parse ID from command output and return id value if success, otherwise return null
299+
static parseId(commandOut) {
300+
const idMatcher = commandOut.output.match(new RegExp(`${ImptTestHelper.ATTR_ID}"?:\\s+"?([A-Za-z0-9-]+)`));
301+
if (idMatcher && idMatcher.length > 1) {
302+
return idMatcher[1];
303+
}
304+
else return null;
305+
}
306+
307+
// parse sha from command output and return sha value if success, otherwise return null
308+
static parseSha(commandOut) {
309+
const idMatcher = commandOut.output.match(new RegExp(`sha"?:\\s+"?([A-Za-z0-9]{64})`));
310+
if (idMatcher && idMatcher.length > 1) {
311+
return idMatcher[1];
312+
}
313+
else return null;
314+
}
315+
}
316+
317+
module.exports = ImptTestHelper;

0 commit comments

Comments
 (0)