nodebots-interchange
Version:
Tool to build custom backpack firmwares to make devices I2C compliant
394 lines (311 loc) • 13.2 kB
JavaScript
const axios = require('axios');
const fs = require('fs');
const fsextra = require('fs-extra');
const child_process = require('child_process');
jest.mock('child_process');
const Downloader = require('../lib/downloader');
const data = require('./config/downloader');
const download_actions = () => describe('1. Download options return hex files', () => {
let dl;
beforeEach(() => {
jest.resetModules();
dl = new Downloader();
});
test('1.1 Pass in a firmware and it stays set', () => {
const {fw} = data;
const downloader = new Downloader({fw});
expect(downloader.fw.name).toBe(fw.name);
expect(downloader.fw.firmata).toBe(false);
expect(downloader.fw.repo).toBe(fw.repo);
});
test('1.2 No firmware set if not in constructor', () => {
expect(dl.fw).toBeUndefined();
});
test('1.3 call download with no firmware set throws an error', () => {
expect.assertions(1);
expect(dl.download()).rejects.toThrow(/no firmware/);
});
test('1.4 Firmware in NPM chooses NPM download method', async() => {
const {npm_fw} = data;
const dl2 = new Downloader({fw: npm_fw});
// set up a mock implementation for the download instance which passes and fails
const mock_download = jest.fn()
.mockResolvedValue('mock/filepath')
.mockResolvedValueOnce('mock-firstpath')
.mockRejectedValueOnce(new Error('not downloadable'));
dl2.download_from_npm = mock_download;
const hexfile = await dl2.download();
expect(mock_download).toBeCalled();
// test rejection form
expect(dl2.download()).rejects.toThrow(/not downloadable/);
});
test('1.5 Firmware in Github chooses Github download method', async() => {
const {gh_fw} = data;
const dl3 = new Downloader({fw: gh_fw});
// set up a mock implementation for the download instance which passes and fails
const mock_download = jest.fn()
.mockResolvedValue('mock/filepath')
.mockResolvedValueOnce('mock-firstpath')
.mockRejectedValueOnce(new Error('not downloadable'));
dl3.download_from_github = mock_download;
const hexfile = await dl3.download();
expect(mock_download).toBeCalled();
// test rejection form
expect(dl3.download()).rejects.toThrow(/not downloadable/);
});
test('1.6 Reading manifest file fails if no firmware or manifest data', () => {
const {npm_fw, manifest} = data;
const no_manifest = () => { dl.get_path_from_manifest(undefined) };
const no_options = () => { dl.get_path_from_manifest(manifest, undefined) };
expect(no_manifest).toThrow(/manifest/);
expect(no_options).toThrow(/options/);
});
test('1.7 Get hex path from manifest', () => {
const {npm_fw, manifest, options, backpack_options} = data;
// try standard form which gets firmata
const hexpath = dl.get_path_from_manifest(manifest, options);
expect(hexpath).toBe('/firmware/bin/firmata/nano/firmata.ino.hex');
// now try and get a backpack
const hp2 = dl.get_path_from_manifest(manifest, backpack_options);
expect(hp2).toBe('/firmware/bin/backpack/uno/backpack.ino.hex');
});
test('1.8 Manifest HexPath no leading / resolves correctly', () => {
const {npm_fw, manifest, backpack_options} = data;
const hexpath = dl.get_path_from_manifest(manifest, backpack_options);
expect(hexpath).toBe('/firmware/bin/backpack/uno/backpack.ino.hex');
});
test('1.9 No firmata name with a multi-firmata manifest should throw an error', () => {
const {npm_fw, multi_manifest, options} = data;
const no_name = () => { dl.get_path_from_manifest(multi_manifest, options) };
expect(no_name).toThrow(/Multiple firmatas/);
});
test('1.10 Using a named firmata should return it from the manifest', () => {
const {npm_fw, multi_manifest, named_firmata_options} = data;
const hexpath = dl.get_path_from_manifest(multi_manifest, named_firmata_options);
const expectedpath = `${multi_manifest.firmata.firmata2.bins}${named_firmata_options.board}${multi_manifest.firmata.firmata2.hexPath}`;
expect(hexpath).toBe(expectedpath);
});
});
const download_utilities = () => describe('2. Utilities to help download', () => {
// test the various utility functions
let dl;
beforeEach(() => {
jest.resetModules();
dl = new Downloader();
});
test('2.1 Basepath for npm fails if no is firmware provided', () => {
const no_firmware = () => { dl.get_npm_basepath() };
expect(no_firmware).toThrow(/firmware/);
});
test('2.2 Basepath for npm fails if no npm package provided', () => {
const no_npm = () => { dl.get_npm_basepath({npm: { repo: 'test'}}) };
expect(no_npm).toThrow(/npm/);
});
test('2.3 Basepath for npm is properly formed', () => {
const {npm_fw} = data;
expect(dl.get_npm_basepath(npm_fw)).toBe('node_modules/test-pkg');
});
});
const npm_actions = () => describe('3. NPM related actions for the downloader', () => {
// test actions related to the NPM method of downloading things.
let dl;
beforeEach(() => {
jest.resetModules();
dl = new Downloader();
});
test('3.1 Getting NPM manifest fails if no firmware supplied', () => {
expect(dl.get_manifest_from_npm).toThrow(/firmware/);
});
test('3.2 Get an NPM installed manifest and return the path to the hex file', async() => {
const {npm_fw, options, manifest} = data;
// set up a mock implementation so we don't need to install package via npm
child_process.execSync.mockReturnValue(true);
const mock_npm_get_manifest = jest.fn()
.mockReturnValue(manifest)
dl.get_manifest_from_npm = mock_npm_get_manifest;
const {hexpath} = await dl.download_from_npm(npm_fw, options);
expect(hexpath).toBe('node_modules/test-pkg/firmware/bin/firmata/nano/firmata.ino.hex');
});
test('3.3 If the npm installation process fails, it should return an error', (done) => {
const {npm_fw, options, manifest} = data;
// set up a mock implementation so we don't need to install package via npm
child_process.execSync
.mockImplementation(() => {
throw new Error('Installation failed')
});
const mock_npm_get_manifest = jest.fn()
.mockReturnValue(manifest)
dl.get_manifest_from_npm = mock_npm_get_manifest;
expect.assertions(1);
return dl.download_from_npm(npm_fw, options)
.catch(err => {
expect(err.toString()).toMatch(/Installation failed/);
done();
});
});
test('3.4 Installing via npm using a github repo should implement correct npm command', () => {
const {npm_fw_repo, options, manifest} = data;
// set up a mock implementation of the child process to be able to
// introspect the command parameter
child_process.execSync
.mockImplementation((command) => {
expect(command).toBe(`npm install --no-save ${npm_fw_repo.npm.repo}`);
return true;
});
const mock_npm_get_manifest = jest.fn()
.mockReturnValue(manifest)
dl.get_manifest_from_npm = mock_npm_get_manifest;
expect.assertions(1);
return dl.download_from_npm(npm_fw_repo, options);
});
test('3.4 Installing package with a version should set the right npm command', () => {
const {npm_fw_ver, options, manifest} = data;
// set up a mock implementation of the child process to be able to
// introspect the command parameter
child_process.execSync
.mockImplementation((command) => {
expect(command).toBe(`npm install --no-save ${npm_fw_ver.npm.package}@${npm_fw_ver.npm.version}`);
return true;
});
const mock_npm_get_manifest = jest.fn()
.mockReturnValue(manifest)
dl.get_manifest_from_npm = mock_npm_get_manifest;
expect.assertions(1);
return dl.download_from_npm(npm_fw_ver, options);
});
test('3.5 .get_manifest_from_npm() should return a manifest file from the node_modules folder', () => {
const {manifest, npm_fw} = data;
const backup = fs.readFileSync;
fs.readFileSync = jest.fn().mockImplementation((path) => {
return JSON.stringify(manifest);
});
const m = dl.get_manifest_from_npm(npm_fw);
fs.readFileSync = backup;
expect(m).toBeDefined();
expect(m.backpack).toBeDefined();
expect(m.firmata).toBeDefined();
});
test('3.6 .get_manifest_from_npm() should throw an error if manifest file is incorrect', () => {
const {manifest, npm_fw} = data;
const backup = fs.readFileSync;
fs.readFileSync = jest.fn().mockImplementation((path) => {
return 'This is some non json data';
});
const get_manifest_error = () => {dl.get_manifest_from_npm(npm_fw)};
expect(get_manifest_error).toThrow(/Manifest file incorrect/);
fs.readFileSync = backup;
});
});
const github_actions = () => describe('4. Github related actions for the downloader', () => {
// test actions relating to the GH way of getting the files
let dl;
let temppath;
beforeEach(() => {
jest.resetModules();
dl = new Downloader();
});
afterEach(() => {
// clean up temp path if it's been set
if (temppath) {
fsextra.removeSync(temppath.name);
temppath = null;
}
});
test('4.1 Getting GH manifest fails if no repo supplied', () => {
// const dl = new Downloader();
expect(dl.download_from_github()).rejects.toThrow(/firmware/);
});
test('4.2 getting GH manifest fails if GH configuration is wrong', () => {
// tests no repo provided and no git+ssh on path
const {gh_fw2, gh_fw3} = data;
// const dl = new Downloader();
expect(dl.download_from_github(gh_fw2)).rejects.toThrow(/github/);
expect(dl.download_from_github(gh_fw3)).rejects.toThrow(/protocol/);
});
test('4.3 Resolve manifest from GitHub if git branch is supplied', (done) => {
// const dl4 = new Downloader();
const {manifest, gh_branch_fw, options} = data;
const mock_download = jest.fn()
.mockResolvedValue(manifest)
.mockResolvedValueOnce(manifest)
.mockResolvedValueOnce('data');
dl.get_file_from_github = mock_download;
expect.assertions(3);
return dl.download_from_github(gh_branch_fw, options)
.then((m) => {
expect(dl.get_file_from_github).toHaveBeenCalled();
expect(m.hexpath).toBeDefined();
expect(m.tmpdir).toBeDefined();
// clean up tmp files
temppath = m.tmpdir;
done();
});
});
test('4.4 Resolve manifest file if branch not supplied', (done) => {
// this tests using master branch.
const {manifest, gh_master_branch_fw, options} = data;
const mock_download = jest.fn()
.mockResolvedValue(manifest)
.mockResolvedValueOnce(manifest)
.mockResolvedValueOnce('data');
dl.get_file_from_github = mock_download;
expect.assertions(3);
return dl.download_from_github(gh_master_branch_fw, options)
.then((m) => {
expect(dl.get_file_from_github).toHaveBeenCalled();
expect(m.hexpath).toBeDefined();
expect(m.tmpdir).toBeDefined();
// clean up tmp files
temppath = m.tmpdir;
done();
});
});
test('4.5 Handle not receiving the hex file from download', (done) => {
// this tests using master branch.
const {manifest, gh_master_branch_fw, options} = data;
const mock_download = jest.fn()
.mockResolvedValue(manifest)
.mockResolvedValueOnce(manifest)
.mockRejectedValueOnce(new Error('Bin download error'));
dl.get_file_from_github = mock_download;
expect.assertions(2);
return dl.download_from_github(gh_master_branch_fw, options)
.catch(err => {
expect(dl.get_file_from_github).toHaveBeenCalled();
expect(err.toString()).toMatch(/download/);
done();
});
});
test('4.6 get_file_from_github() handles downloads correctly', (done) => {
// test axios passing then failing.
jest.mock('axios');
// set up twp mocks one that passes and the other that fails.
axios.get = jest.fn().mockImplementationOnce((uri) => {
return Promise.resolve({status: '200', data: 'filedata'});
}).mockImplementationOnce((uri) => {
return Promise.reject(new Error('404'));
});
expect.assertions(2);
// call this the first time with a pass
return dl.get_file_from_github('passfile')
.then(file => {
expect(file).toBe('filedata');
// now we call it a second time to check failure condition
return dl.get_file_from_github('failfile')
.catch(err => {
expect(err.toString()).toMatch(/Unable to retrieve file/);
done();
});
});
});
test('4.7 normalise_gh_repo_path() processes urls correctly', () => {
// detemrine if we put in the right repo paths we get the right reponses.
expect(dl.normalise_gh_repo_path('/test/test-package')).toBe('/test/test-package');
expect(dl.normalise_gh_repo_path('/test/test-package.git')).toBe('/test/test-package');
expect(dl.normalise_gh_repo_path('/test/test-package/')).toBe('/test/test-package');
});
});
download_actions();
download_utilities();
npm_actions();
github_actions();