kinvey-flex-sdk
Version:
SDK for creating Kinvey Flex Services
513 lines (468 loc) • 14.8 kB
JavaScript
/**
* Copyright (c) 2018 Kinvey Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
const flexPackageJson = require('../../../package.json');
const should = require('should');
const proxyquire = require('proxyquire');
const sinon = require('sinon');
const uuid = require('uuid');
const sdkVersion = require('../../../package.json').version;
const mockTaskReceiver = require('./mocks/mockTaskReceiver.js');
describe('service creation', () => {
let sdk = null;
before(() => {
this.loggerMock = { error: sinon.spy() };
sdk = proxyquire('../../../lib/flex', {
'kinvey-code-task-runner': mockTaskReceiver,
'./service/logger': this.loggerMock
});
});
beforeEach(() => {
this.loggerMock.error = sinon.spy();
});
it('can create a new service', (done) => {
sdk.service((err, flex) => {
should.not.exist(err);
should.exist(flex.data);
should.exist(flex.functions);
should.exist(flex.moduleGenerator);
should.exist(flex.logger);
should.exist(flex.version);
should.exist(flex.auth);
flex.version.should.eql(flexPackageJson.version);
done();
});
});
it('should set the type to http by default', (done) => {
const spy = sinon.spy(mockTaskReceiver, 'start');
sdk.service(() => {
spy.args[0][0].type.should.eql('http');
mockTaskReceiver.start.restore();
done();
});
});
it('should accept an optional host and port', (done) => {
const spy = sinon.spy(mockTaskReceiver, 'start');
const host = 'localhost';
const port = '7777';
sdk.service({ host, port }, () => {
spy.args[0][0].type.should.eql('http');
spy.args[0][0].host.should.eql(host);
spy.args[0][0].port.should.eql(port);
mockTaskReceiver.start.restore();
done();
});
});
it('should initialize the SDK with a null options', (done) => {
const spy = sinon.spy(mockTaskReceiver, 'start');
const host = 'localhost';
const port = '7777';
sdk.service(null, () => {
mockTaskReceiver.start.restore();
done();
});
});
it('should set the type to tcp if the SDK_RECEIVER environment variable is set', (done) => {
const spy = sinon.spy(mockTaskReceiver, 'start');
process.env.SDK_RECEIVER = 'tcp';
sdk.service(() => {
spy.args[0][0].type.should.eql('tcp');
delete process.env.SDK_RECEIVER;
mockTaskReceiver.start.restore();
done();
});
});
it('should process a data task', (done) => {
sdk.service((err, sdk) => {
const task = {
appMetadata: {
_id: '12345',
appsecret: 'appsecret',
mastersecret: 'mastersecret',
pushService: undefined,
restrictions: {
level: 'starter'
},
API_version: 3,
name: 'DevApp',
platform: null
},
taskType: 'data',
method: 'GET',
request: {
method: 'GET',
headers: {},
body: {},
serviceObjectName: 'foo'
},
response: {
headers: {}
}
};
sdk.data.serviceObject('foo').onGetAll(() => {
done();
});
mockTaskReceiver.taskReceived()(task, () => {
});
});
});
it('should process a function task', (done) => {
sdk.service((err, sdk) => {
const task = {
appMetadata: {
_id: '12345',
appsecret: 'appsecret',
mastersecret: 'mastersecret',
pushService: undefined,
restrictions: {
level: 'starter'
},
API_version: 3,
name: 'DevApp',
platform: null
},
taskType: 'functions',
taskName: 'foo',
hookType: 'customEndpoint',
method: 'GET',
request: {
method: 'GET',
headers: {},
body: {}
},
response: {
headers: {}
}
};
sdk.functions.register('foo', () => done());
mockTaskReceiver.taskReceived()(task, () => {
});
});
});
it('should process an auth function task', (done) => {
sdk.service((err, sdk) => {
const task = {
appMetadata: {
_id: '12345',
appsecret: 'appsecret',
mastersecret: 'mastersecret',
pushService: undefined,
restrictions: {
level: 'starter'
},
API_version: 3,
name: 'DevApp',
platform: null
},
taskType: 'auth',
taskName: 'foo',
method: 'GET',
request: {
method: 'GET',
headers: {},
body: {
username: 'foo',
password: 'bar',
options: {}
}
},
response: {
headers: {}
}
};
sdk.auth.register('foo', () => done());
mockTaskReceiver.taskReceived()(task, () => {
});
});
});
it('should process a discovery task', (done) => {
sdk.service(() => {
const task = {
taskType: 'serviceDiscovery'
};
mockTaskReceiver.taskReceived()(task, (err, result) => {
should.not.exist(err);
should.exist(result.discoveryObjects);
result.discoveryObjects.dataLink.should.be.an.Object();
result.discoveryObjects.businessLogic.should.be.an.Object();
result.discoveryObjects.auth.should.be.an.Object();
done();
});
});
});
it('should not call callback with an error when task result is an error', (done) => {
sdk.service(() => {
const task = {
appMetadata: {
_id: '12345',
},
taskType: 'functions',
taskName: null, // this causes a validation error during process
method: 'GET',
request: {
method: 'GET',
headers: {},
body: {}
},
response: {
headers: {}
}
};
mockTaskReceiver.taskReceived()(task, (err, result) => {
should.not.exist(err);
should.exist(result);
done();
});
});
});
it('should reject a task if shared secret auth is enabled and the authKey is not included', (done) => {
sdk.service({ sharedSecret: uuid.v4() }, (err, flex) => {
const task = {
appMetadata: {
_id: '12345',
appsecret: 'appsecret',
mastersecret: 'mastersecret',
pushService: undefined,
restrictions: {
level: 'starter'
},
API_version: 3,
name: 'DevApp',
platform: null
},
taskType: 'data',
method: 'GET',
request: {
method: 'GET',
headers: {},
body: {},
serviceObjectName: 'foo'
},
response: {
headers: {}
}
};
flex.data.serviceObject('foo').onGetAll((context, complete) => {
const body = { foo: 'bar' };
complete().setBody(body).ok().done();
});
mockTaskReceiver.taskReceived()(task, (err, result) => {
result.response.body.error.should.eql('InvalidCredentials');
result.response.body.description.should.eql(
'Invalid credentials. Please retry your request with correct credentials.'
);
result.response.statusCode.should.eql(401);
result.response.body.debug.should.eql('The Authorization Key was not valid or missing.');
result.sdkVersion.should.eql(sdkVersion);
should.not.exist(err);
should.not.exist(result.response.body.foo);
done();
});
});
});
it('should reject a task if shared secret auth is enabled and the authKey does not match', (done) => {
sdk.service({ sharedSecret: uuid.v4() }, (err, flex) => {
const task = {
appMetadata: {
_id: '12345',
appsecret: 'appsecret',
mastersecret: 'mastersecret',
pushService: undefined,
restrictions: {
level: 'starter'
},
API_version: 3,
name: 'DevApp',
platform: null
},
authKey: uuid.v4(),
taskType: 'data',
method: 'GET',
request: {
method: 'GET',
headers: {},
body: {},
serviceObjectName: 'foo'
},
response: {
headers: {}
}
};
flex.data.serviceObject('foo').onGetAll((context, complete) => {
const body = { foo: 'bar' };
complete().setBody(body).ok().done();
});
mockTaskReceiver.taskReceived()(task, (err, result) => {
result.response.body.error.should.eql('InvalidCredentials');
result.response.body.description.should.eql(
'Invalid credentials. Please retry your request with correct credentials.'
);
result.response.statusCode.should.eql(401);
result.response.body.debug.should.eql('The Authorization Key was not valid or missing.');
should.not.exist(err);
should.not.exist(result.response.body.foo);
done();
});
});
});
it('should process a task if shared secret auth is enabled and the taskType is discovery', (done) => {
sdk.service({ sharedSecret: uuid.v4() }, (err, flex) => {
const task = {
taskType: 'serviceDiscovery',
};
flex.data.serviceObject('foo').onGetAll((context, complete) => {
const body = { foo: 'bar' };
complete().setBody(body).ok().done();
});
mockTaskReceiver.taskReceived()(task, (err, result) => {
should.not.exist(err);
should.exist(result.discoveryObjects);
result.discoveryObjects.dataLink.should.be.an.Object();
result.discoveryObjects.businessLogic.should.be.an.Object();
result.discoveryObjects.auth.should.be.an.Object();
Array.isArray(result.discoveryObjects.dataLink.serviceObjects).should.eql(true);
result.discoveryObjects.dataLink.serviceObjects.length.should.eql(1);
result.discoveryObjects.dataLink.serviceObjects[0].should.eql('foo');
done();
});
});
});
it('should process a task if shared secret auth is enabled and the authKey is included', (done) => {
const authKey = uuid.v4();
sdk.service({ sharedSecret: authKey }, (err, flex) => {
const task = {
appMetadata: {
_id: '12345',
appsecret: 'appsecret',
mastersecret: 'mastersecret',
pushService: undefined,
restrictions: {
level: 'starter'
},
API_version: 3,
name: 'DevApp',
platform: null
},
authKey,
taskType: 'data',
method: 'GET',
request: {
method: 'GET',
headers: {},
body: {},
serviceObjectName: 'foo'
},
response: {
headers: {}
}
};
flex.data.serviceObject('foo').onGetAll((context, complete) => {
const body = { foo: 'bar' };
complete().setBody(body).ok().done();
});
mockTaskReceiver.taskReceived()(task, (err, result) => {
should.not.exist(err);
result.response.body.foo.should.eql('bar');
result.response.statusCode.should.eql(200);
should.not.exist(result.response.body.message);
should.not.exist(result.response.body.description);
should.not.exist(result.response.body.debug);
done();
});
});
});
it('should log a timeout error, if receiver returns a client disconnect error', (done) => {
process.env.SDK_RECEIVER = 'tcp';
sdk.service((err, sdk) => {
const task = {
taskId: `some id ${Math.random()}`,
appMetadata: {
_id: '12345',
appsecret: 'appsecret',
mastersecret: 'mastersecret',
pushService: undefined,
restrictions: {
level: 'starter'
},
API_version: 3,
name: 'DevApp',
platform: null
},
taskType: 'functions',
taskName: 'foo',
hookType: 'customEndpoint',
method: 'GET',
request: {
method: 'GET',
headers: {},
body: {}
},
response: {
headers: {}
}
};
sdk.functions.register('foo', (context, complete) => {
complete().ok().done();
});
mockTaskReceiver.taskReceived()(task, (err, result) => {
return 'Connection ended by client.';
});
this.loggerMock.error.calledOnce.should.eql(true);
this.loggerMock.error.firstCall.args.length.should.eql(1);
this.loggerMock.error.firstCall.args[0].should.eql(`Unable to call complete() in ${task.hookType} "${task.taskName}" for request with id "${task.requestId}": Flex Service request timed out.`);
done();
});
});
it('should log an unknown network error, if receiver returns an error which isn\'t a client disconnect', (done) => {
process.env.SDK_RECEIVER = 'tcp';
sdk.service((err, sdk) => {
const task = {
taskId: `some id ${Math.random()}`,
appMetadata: {
_id: '12345',
appsecret: 'appsecret',
mastersecret: 'mastersecret',
pushService: undefined,
restrictions: {
level: 'starter'
},
API_version: 3,
name: 'DevApp',
platform: null
},
taskType: 'functions',
taskName: 'foo',
hookType: 'customEndpoint',
method: 'GET',
request: {
method: 'GET',
headers: {},
body: {}
},
response: {
headers: {}
}
};
sdk.functions.register('foo', (context, complete) => {
complete().ok().done();
});
mockTaskReceiver.taskReceived()(task, (err, result) => {
return `Any other message ${Math.random()}`;
});
this.loggerMock.error.calledOnce.should.eql(true);
this.loggerMock.error.firstCall.args.length.should.eql(1);
this.loggerMock.error.firstCall.args[0].should.eql(`Unable to call complete() in ${task.hookType} "${task.taskName}" for request with id "${task.requestId}": Unexpected network issue.`);
done();
});
});
});