UNPKG

particle-cli

Version:

Simple Node commandline application for working with your Particle devices and using the Particle Cloud

374 lines (364 loc) 16.2 kB
const { expect, sinon } = require('../../test/setup'); const LogicFunction = require('./logic-function'); const nock = require('nock'); const logicFunc1 = require('../../test/__fixtures__/logic_functions/logicFunc1.json'); const { PATH_TMP_DIR } = require('../../test/lib/env'); const path = require('path'); const templateProcessor = require('./template-processor'); const fs = require('fs-extra'); describe('LogicFunction', () => { beforeEach(() => { nock.cleanAll(); fs.emptyDirSync(PATH_TMP_DIR); }); async function createLogicFunction({ name, description }) { const lfPath = path.join(PATH_TMP_DIR, 'logic_functions'); const code = 'import Particle from \'particle:core\';\nexport default function main({ event }) {\n console.log(\'Hello from logic function!\'); \n}'; const logicFunction = new LogicFunction({ name, description, _path: lfPath, }); logicFunction.files.sourceCode.content = code; await logicFunction.saveToDisk(); return logicFunction; } describe('list', () => { it('returns an empty array if there are no Logic Functions', async () => { nock('https://api.particle.io/v1/', ) .intercept('/logic/functions', 'GET') .reply(200, { logic_functions : [] } ); const logicFunctions = await LogicFunction.listFromCloud(); expect(logicFunctions).to.have.lengthOf(0); }); it('returns a list of Logic Functions', async () => { nock('https://api.particle.io/v1/', ) .intercept('/logic/functions', 'GET') .reply(200, { logic_functions : [{ name: 'my-logic-function' }] } ); const logicFunctions = await LogicFunction.listFromCloud(); expect(logicFunctions).to.have.lengthOf(1); // check if the Logic Function is an instance of LogicFunction expect(logicFunctions[0]).to.be.an.instanceof(LogicFunction); }); it('returns a list of Logic Functions from an specific org', async () => { nock('https://api.particle.io/v1/orgs/', ) .intercept('/my-org/logic/functions', 'GET') .reply(200, { logic_functions : [{ name: 'my-logic-function' }] } ); const logicFunctions = await LogicFunction.listFromCloud({ org: 'my-org' }); expect(logicFunctions).to.have.lengthOf(1); expect(logicFunctions[0]).to.be.an.instanceof(LogicFunction); }); it('propagates errors', async () => { nock('https://api.particle.io/v1/orgs/', ) .intercept('/my-org/logic/functions', 'GET') .reply(500, { error: 'Internal Server Error' } ); try { await LogicFunction.listFromCloud({ org: 'my-org' }); } catch (error) { expect(error.message).to.equal('Error listing Logic Functions: Internal Server Error'); } }); }); describe('getByIdOrName', () => { it ('throws an error if the Logic Function is not found', async () => { try { const logicFunctions = []; await LogicFunction.getByIdOrName({ org: 'my-org', name: 'my-logic-function', list: logicFunctions }); } catch (error) { expect(error.message).to.equal('Logic Function not found'); } }); it('returns a Logic Function by name', async () => { const logicFunctions = [new LogicFunction(logicFunc1.logic_functions[0])]; logicFunctions[0].files.sourceCode.content = logicFunc1.logic_functions[0].source.code; const logicFunction = await LogicFunction.getByIdOrName({ name: 'LF1', list: logicFunctions }); const expectedLogicFunction = { name: 'LF1', description: 'Logic Function 1 on SandBox', id: '0021e8f4-64ee-416d-83f3-898aa909fb1b', enabled: true, triggers: [], type: 'JavaScript', files: { sourceCode: { name: 'lf1.js', content: 'import Particle from \'particle:core\';\nexport default function main({ event }) {\n Particle.publish(\'logic-publish\', event.eventData, { productId: 18552 });\n}' }, configuration: { name: 'lf1.logic.json', content: '' }, types:[] }, }; Object.keys(expectedLogicFunction).forEach(key => { expect(logicFunction).to.have.property(key); expect(logicFunction[key]).to.deep.equal(expectedLogicFunction[key]); }); }); }); describe('saveToDisk', () => { it('saves a Logic Function to disk', async () => { const logicFunction = new LogicFunction(logicFunc1.logic_functions[0]); logicFunction.path = path.join(PATH_TMP_DIR, 'logic-functions', 'lf1'); await logicFunction.saveToDisk(); const files = await fs.readdir(logicFunction.path); expect(files).to.have.lengthOf(2); expect(files).to.include('lf1.js'); expect(files).to.include('lf1.logic.json'); // check if the files are correct const logicFunctionFile = await fs.readFile(path.join(logicFunction.path, 'lf1.logic.json')); const logicFunctionData = JSON.parse(logicFunctionFile); expect(logicFunctionData.logic_function).to.have.property('name', 'LF1'); expect(logicFunctionData.logic_function).to.have.property('description', 'Logic Function 1 on SandBox'); // check if the source code is correct const sourceCodeFile = await fs.readFile(path.join(logicFunction.path, 'lf1.js')); const sourceCode = sourceCodeFile.toString(); expect(sourceCode).to.equal(logicFunction.files.sourceCode.content); }); }); describe('initFromTemplate', () => { afterEach(() => { sinon.restore(); }); it('initializes a Logic Function from a template', async () => { const config = { logic_function: { 'name': 'my Logic Function', 'description': 'my test description', 'enabled': true, 'source': { 'type': 'JavaScript' }, 'logic_triggers': [] } }; const loadStub = sinon.stub(templateProcessor, 'loadTemplateFiles').resolves([ { fileName: path.join('my_path', 'lf1.js'), content: 'Logic Function content' }, { fileName: path.join('my_path', 'lf1.logic.json'), content: JSON.stringify(config) } ]); const logicFunction = new LogicFunction({ name: 'LF1', description: 'Logic Function 1 on SandBox', _path: path.join(PATH_TMP_DIR, 'logic-functions', 'lf1'), }); await logicFunction.initFromTemplate({ templatePath: path.join(__dirname, 'my-template') }); expect(loadStub).to.have.been.calledWith({ templatePath: path.join(__dirname, 'my-template'), contentReplacements: { name: 'LF1', description: 'Logic Function 1 on SandBox' }, fileNameReplacements: [{ fileName: 'lf1', template: 'logic_function_name' }] }); }); it('throws an error if the template does not exist', async () => { const loadStub = sinon.stub(templateProcessor, 'loadTemplateFiles').rejects(new Error('Template not found')); try { const logicFunction = new LogicFunction({ name: 'LF1', description: 'Logic Function 1 on SandBox', _path: path.join(PATH_TMP_DIR, 'logic-functions', 'lf1'), }); await logicFunction.initFromTemplate({ templatePath: path.join(__dirname, 'my-template') }); expect.fail('Should have thrown an error'); } catch (error) { expect(error.message).to.equal('Template not found'); expect(loadStub).to.have.been.calledWith({ templatePath: path.join(__dirname, 'my-template'), contentReplacements: { name: 'LF1', description: 'Logic Function 1 on SandBox' }, fileNameReplacements: [{ fileName: 'lf1', template: 'logic_function_name' }] }); } }); }); describe('listFromDisk', () => { afterEach(() => { sinon.restore(); fs.emptyDirSync(PATH_TMP_DIR); }); it('returns a list of Logic Functions', async () => { await createLogicFunction({ name: 'lf1', description: 'Logic Function 1 on SandBox' }); const { logicFunctions } = await LogicFunction.listFromDisk({ filepath: path.join(PATH_TMP_DIR, 'logic_functions') }); expect(logicFunctions).to.have.lengthOf(1); expect(logicFunctions[0]).to.be.an.instanceof(LogicFunction); }); it('returns a list of Logic Functions with org', async () => { await createLogicFunction({ name: 'lf1', description: 'Logic Function 1 on SandBox' }); const { logicFunctions } = await LogicFunction.listFromDisk({ filepath: path.join(PATH_TMP_DIR, 'logic_functions'), org: 'my-org' }); expect(logicFunctions).to.have.lengthOf(1); expect(logicFunctions[0]).to.be.an.instanceof(LogicFunction); expect(logicFunctions[0]).to.have.property('org', 'my-org'); }); it('returns a list of more than one Logic Functions', async () => { await createLogicFunction({ name: 'lf1', description: 'Logic Function 1 on SandBox' }); await createLogicFunction({ name: 'lf2', description: 'Logic Function 2 on SandBox' }); const { logicFunctions } = await LogicFunction.listFromDisk({ filepath: path.join(PATH_TMP_DIR, 'logic_functions') }); expect(logicFunctions).to.have.lengthOf(2); expect(logicFunctions[0]).to.be.an.instanceof(LogicFunction); }); it('filter out files that are not Logic Functions', async () => { await createLogicFunction({ name: 'lf1', description: 'Logic Function 1 on SandBox' }); await fs.writeFile(path.join(PATH_TMP_DIR, 'logic_functions', 'lf2.json'), '{}'); const { logicFunctions } = await LogicFunction.listFromDisk({ filepath: path.join(PATH_TMP_DIR, 'logic_functions') }); expect(logicFunctions).to.have.lengthOf(1); expect(logicFunctions[0]).to.be.an.instanceof(LogicFunction); }); it('filter out no well formed Logic Functions', async () => { await createLogicFunction({ name: 'lf1', description: 'Logic Function 1 on SandBox' }); await fs.writeFile(path.join(PATH_TMP_DIR, 'logic_functions', 'lf2.logic.json'), '{ "name": "lf2", "description": "Logic Function 2 on SandBox" }'); const { logicFunctions } = await LogicFunction.listFromDisk({ filepath: path.join(PATH_TMP_DIR, 'logic_functions') }); expect(logicFunctions).to.have.lengthOf(1); expect(logicFunctions[0]).to.be.an.instanceof(LogicFunction); }); it('returns a list of one element if the path is a file and it is a Logic Function', async () => { await createLogicFunction({ name: 'lf1', description: 'Logic Function 1 on SandBox' }); const { logicFunctions } = await LogicFunction.listFromDisk({ filepath: path.join(PATH_TMP_DIR, 'logic_functions', 'lf1.js') }); expect(logicFunctions).to.have.lengthOf(1); expect(logicFunctions[0]).to.be.an.instanceof(LogicFunction); }); it ('returns an empty list if the path is a file and it is not a Logic Function', async () => { await createLogicFunction({ name: 'lf1', description: 'Logic Function 1 on SandBox' }); await fs.writeFile(path.join(PATH_TMP_DIR, 'logic_functions', 'lf2.js'), '{ "name": "lf2", "description": "Logic Function 2 on SandBox" }'); const { logicFunctions } = await LogicFunction.listFromDisk({ filepath: path.join(PATH_TMP_DIR, 'logic_functions', 'lf2.js') }); expect(logicFunctions).to.have.lengthOf(0); }); it('returns an empty list if there are no Logic Functions', async () => { const { logicFunctions } = await LogicFunction.listFromDisk({ filepath: PATH_TMP_DIR }); expect(logicFunctions).to.have.lengthOf(0); }); it('returns a list of Logic Functions and a list of malformed Logic Functions', async () => { await createLogicFunction({ name: 'lf1', description: 'Logic Function 1 on SandBox' }); await fs.writeFile(path.join(PATH_TMP_DIR, 'logic_functions', 'lf2.logic.json'), '{ "name": "lf2", "description": "Logic Function 2 on SandBox" }'); const { logicFunctions, malformedLogicFunctions } = await LogicFunction.listFromDisk({ filepath: path.join(PATH_TMP_DIR, 'logic_functions') }); expect(logicFunctions).to.have.lengthOf(1); expect(logicFunctions[0]).to.be.an.instanceof(LogicFunction); expect(malformedLogicFunctions).to.have.lengthOf(1); expect(malformedLogicFunctions[0].name).to.equal('lf2'); }); it('fails if the path does not exist', async () => { try { await LogicFunction.listFromDisk({ filepath: '/path/does/not/exist' }); expect.fail('Should have thrown an error'); } catch (error) { expect(error.message).to.equal('Path does not exist'); } }); }); describe('execute', () => { afterEach(() => { sinon.restore(); fs.emptyDirSync(PATH_TMP_DIR); }); it('executes a Logic Function', async () => { nock('https://api.particle.io/v1/', ) .intercept('/logic/execute', 'POST') .reply(200, { result: { status: 'Success', logs: ['abc1'] } }); const trigger = { event: { event_name: 'my-event', event_data: 'my-event-data', product_id: 1, device_id: 'my-device-id', } }; const lf1 = await createLogicFunction({ name: 'lf1', description: 'Logic Function 1 on SandBox' }); const executeResult = await lf1.execute(trigger); expect(executeResult).to.have.property('status', 'Success'); expect(executeResult).to.have.property('logs'); expect(executeResult.logs).to.deep.equal(['abc1']); }); it('returns status as exception and errors if the execution fails', async () => { nock('https://api.particle.io/v1/', ) .intercept('/logic/execute', 'POST') .reply(200, { result: { status: 'Exception', err: 'Error', logs: [] } }); const trigger = { event: { event_name: 'my-event', event_data: 'my-event-data', product_id: 1, device_id: 'my-device-id', } }; const lf1 = await createLogicFunction({ name: 'lf1', description: 'Logic Function 1 on SandBox' }); const executeResult = await lf1.execute(trigger); expect(executeResult).to.have.property('status', 'Exception'); expect(executeResult).to.have.property('error', 'Error'); expect(executeResult).to.have.property('logs'); expect(executeResult.logs).to.deep.equal([]); }); it('propagates errors', async () => { nock('https://api.particle.io/v1/', ) .intercept('/logic/execute', 'POST') .reply(500, { error: 'Internal Server Error' } ); const trigger = { event: { event_name: 'my-event', event_data: 'my-event-data', product_id: 1, device_id: 'my-device-id', } }; const lf1 = await createLogicFunction({ name: 'lf1', description: 'Logic Function 1 on SandBox' }); try { await lf1.execute(trigger); expect.fail('Should have thrown an error'); } catch (error) { expect(error.message).to.equal('Error executing Logic Function: Internal Server Error'); } }); }); describe('deploy', () => { afterEach(() => { sinon.restore(); fs.emptyDirSync(PATH_TMP_DIR); }); it('deploys a new Logic Function', async () => { nock('https://api.particle.io/v1/', ) .intercept('/logic/functions', 'POST') .reply(200, { logic_function: { id: '1234', version: 1 } }); const lf1 = await createLogicFunction({ name: 'lf1', description: 'Logic Function 1 on SandBox' }); await lf1.deploy(); expect(lf1).to.have.property('id', '1234'); expect(lf1).to.have.property('version', 1); }); it ('deploys existent Logic Function', async () => { nock('https://api.particle.io/v1/', ) .intercept('/logic/functions/1234', 'PUT') .reply(200, { logic_function: { id: '1234', version: 1 } } ); const lf1 = await createLogicFunction({ name: 'lf1', description: 'Logic Function 1 on SandBox' }); lf1.id = '1234'; await lf1.deploy(); expect(lf1).to.have.property('id', '1234'); expect(lf1).to.have.property('version', 1); }); it('propagates errors', async () => { nock('https://api.particle.io/v1/', ) .intercept('/logic/functions', 'POST') .reply(500, { error: 'Internal Server Error' } ); const lf1 = await createLogicFunction({ name: 'lf1', description: 'Logic Function 1 on SandBox' }); try { await lf1.deploy(); expect.fail('Should have thrown an error'); } catch (error) { expect(error.message).to.equal('Error deploying Logic Function: Internal Server Error'); } }); it('propagates errors when updating', async () => { nock('https://api.particle.io/v1/', ) .intercept('/logic/functions/1234', 'PUT') .reply(500, { error: 'Internal Server Error' } ); const lf1 = await createLogicFunction({ name: 'lf1', description: 'Logic Function 1 on SandBox' }); lf1.id = '1234'; try { await lf1.deploy(); expect.fail('Should have thrown an error'); } catch (error) { expect(error.message).to.equal('Error deploying Logic Function: Internal Server Error'); } }); }); });