Merge pull request #61 from strongloop/feature/godaddy-improvements-round-2
GoDaddy improvements - round 2
This commit is contained in:
commit
530808312a
|
@ -121,9 +121,12 @@ var modelHelper = module.exports = {
|
|||
if (typeof propType === 'function') {
|
||||
// See https://github.com/strongloop/loopback-explorer/issues/32
|
||||
// The type can be a model class
|
||||
propType = propType.modelName || propType.name.toLowerCase();
|
||||
} else if(Array.isArray(propType)) {
|
||||
propType = 'array';
|
||||
return propType.modelName || propType.name.toLowerCase();
|
||||
} else if (Array.isArray(propType)) {
|
||||
return 'array';
|
||||
} else if (typeof propType === 'object') {
|
||||
// Anonymous objects, they are allowed e.g. in accepts/returns definitions
|
||||
return 'object';
|
||||
}
|
||||
return propType;
|
||||
},
|
||||
|
|
|
@ -123,21 +123,40 @@ var routeHelper = module.exports = {
|
|||
|
||||
debug('route %j', route);
|
||||
|
||||
var responseDoc = modelHelper.LDLPropToSwaggerDataType(returns);
|
||||
|
||||
// Note: Swagger Spec does not provide a way how to specify
|
||||
// that the responseModel is "array of X". However,
|
||||
// Swagger UI converts Arrays to the item types anyways,
|
||||
// therefore it should be ok to do the same here.
|
||||
var responseModel = responseDoc.type === 'array' ?
|
||||
responseDoc.items.type : responseDoc.type;
|
||||
|
||||
var responseMessages = [{
|
||||
code: route.returns && route.returns.length ? 200 : 204,
|
||||
message: 'Request was successful',
|
||||
responseModel: responseModel
|
||||
}];
|
||||
|
||||
if (route.errors) {
|
||||
responseMessages.push.apply(responseMessages, route.errors);
|
||||
}
|
||||
|
||||
var apiDoc = {
|
||||
path: routeHelper.convertPathFragments(route.path),
|
||||
// Create the operation doc. Use `extendWithType` to add the necessary
|
||||
// `items` and `format` fields.
|
||||
operations: [routeHelper.extendWithType({
|
||||
// Create the operation doc.
|
||||
// Note that we are not calling `extendWithType`, as the response type
|
||||
// is specified in the first response message.
|
||||
operations: [{
|
||||
method: routeHelper.convertVerb(route.verb),
|
||||
// [rfeng] Swagger UI doesn't escape '.' for jQuery selector
|
||||
nickname: route.method.replace(/\./g, '_'),
|
||||
nickname: route.method.replace(/\./g, '_'),
|
||||
parameters: accepts,
|
||||
// TODO(schoon) - We don't have descriptions for this yet.
|
||||
responseMessages: [],
|
||||
responseMessages: responseMessages,
|
||||
summary: typeConverter.convertText(route.description),
|
||||
notes: typeConverter.convertText(route.notes),
|
||||
deprecated: route.deprecated
|
||||
}, returns)]
|
||||
}]
|
||||
};
|
||||
|
||||
return apiDoc;
|
||||
|
|
|
@ -12,6 +12,7 @@ var urlJoin = require('./url-join');
|
|||
var _defaults = require('lodash.defaults');
|
||||
var classHelper = require('./class-helper');
|
||||
var routeHelper = require('./route-helper');
|
||||
var modelHelper = require('./model-helper');
|
||||
var cors = require('cors');
|
||||
|
||||
/**
|
||||
|
@ -23,6 +24,9 @@ var cors = require('cors');
|
|||
* @param {Object} opts Options.
|
||||
*/
|
||||
function Swagger(loopbackApplication, swaggerApp, opts) {
|
||||
if (opts && opts.swaggerVersion)
|
||||
console.warn('loopback-explorer\'s options.swaggerVersion is deprecated.');
|
||||
|
||||
opts = _defaults(opts || {}, {
|
||||
swaggerVersion: '1.2',
|
||||
basePath: loopbackApplication.get('restApiRoot') || '/api',
|
||||
|
@ -81,6 +85,47 @@ function Swagger(loopbackApplication, swaggerApp, opts) {
|
|||
routeHelper.addRouteToAPIDeclaration(route, classDef, doc);
|
||||
});
|
||||
|
||||
// Add models referenced from routes (e.g. accepts/returns)
|
||||
Object.keys(apiDocs).forEach(function(className) {
|
||||
var classDoc = apiDocs[className];
|
||||
classDoc.apis.forEach(function(api) {
|
||||
api.operations.forEach(function(routeDoc) {
|
||||
routeDoc.parameters.forEach(function(param) {
|
||||
var type = param.type;
|
||||
if (type === 'array' && param.items)
|
||||
type = param.items.type;
|
||||
|
||||
addTypeToModels(type);
|
||||
});
|
||||
|
||||
addTypeToModels(routeDoc.type);
|
||||
|
||||
routeDoc.responseMessages.forEach(function(msg) {
|
||||
addTypeToModels(msg.responseModel);
|
||||
});
|
||||
|
||||
function addTypeToModels(name) {
|
||||
if (!name || name === 'void') return;
|
||||
|
||||
var model = loopbackApplication.models[name];
|
||||
if (!model) {
|
||||
var loopback = loopbackApplication.loopback;
|
||||
if (!loopback) return;
|
||||
|
||||
if (loopback.findModel) {
|
||||
model = loopback.findModel(name); // LoopBack 2.x
|
||||
} else {
|
||||
model = loopback.getModel(name); // LoopBack 1.x
|
||||
}
|
||||
}
|
||||
if (!model) return;
|
||||
|
||||
modelHelper.generateModelDefinition(model, classDoc.models);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* The topmost Swagger resource is a description of all (non-Swagger)
|
||||
* resources available on the system, and where to find more
|
||||
|
@ -114,12 +159,14 @@ function addRoute(app, uri, doc, opts) {
|
|||
// can't guarantee this path is either reachable or desirable if it's set
|
||||
// as a part of the options.
|
||||
//
|
||||
// The simplest way around this is to reflect the value of the `Host` HTTP
|
||||
// header as the `basePath`. Because we pre-build the Swagger data, we don't
|
||||
// know that header at the time the data is built.
|
||||
// The simplest way around this is to reflect the value of the `Host` and/or
|
||||
// `X-Forwarded-Host` HTTP headers as the `basePath`.
|
||||
// Because we pre-build the Swagger data, we don't know that header at
|
||||
// the time the data is built.
|
||||
if (hasBasePath) {
|
||||
var headers = req.headers;
|
||||
var host = headers.Host || headers.host;
|
||||
// NOTE header names (keys) are always all-lowercase
|
||||
var host = headers['x-forwarded-host'] || headers.host;
|
||||
doc.basePath = (opts.protocol || req.protocol) + '://' +
|
||||
host + initialPath;
|
||||
}
|
||||
|
|
|
@ -34,7 +34,12 @@
|
|||
color: #080;
|
||||
}
|
||||
|
||||
/*
|
||||
FIXME: Separate the overrides from the rest of the styles, rather than override screen.css entirely.
|
||||
*/
|
||||
/* Improve spacing when the browser window is small */
|
||||
#message-bar, #swagger-ui-container {
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
#api_selector {
|
||||
padding: 0px 20px;
|
||||
}
|
||||
|
|
|
@ -214,6 +214,13 @@ describe('model-helper', function() {
|
|||
expect(def.properties).to.have.property('visibleProperty');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPropType', function() {
|
||||
it('converts anonymous object types', function() {
|
||||
var type = modelHelper.getPropType({ name: 'string', value: 'string' });
|
||||
expect(type).to.eql('object');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Simulates the format of a remoting class.
|
||||
|
|
|
@ -13,7 +13,8 @@ describe('route-helper', function() {
|
|||
{ arg: 'avg', type: 'number' }
|
||||
]
|
||||
});
|
||||
expect(doc.operations[0].type).to.equal('object');
|
||||
expect(doc.operations[0].type).to.equal(undefined);
|
||||
expect(getResponseType(doc.operations[0])).to.equal('object');
|
||||
});
|
||||
|
||||
it('converts path params when they exist in the route name', function() {
|
||||
|
@ -60,19 +61,12 @@ describe('route-helper', function() {
|
|||
]
|
||||
});
|
||||
var opDoc = doc.operations[0];
|
||||
expect(opDoc.type).to.equal('array');
|
||||
expect(opDoc.items).to.eql({type: 'customType'});
|
||||
});
|
||||
// Note: swagger-ui treat arrays of X the same way as object X
|
||||
expect(getResponseType(opDoc)).to.equal('customType');
|
||||
|
||||
it('correctly converts return types (format)', function() {
|
||||
var doc = createAPIDoc({
|
||||
returns: [
|
||||
{arg: 'data', type: 'buffer'}
|
||||
]
|
||||
});
|
||||
var opDoc = doc.operations[0];
|
||||
expect(opDoc.type).to.equal('string');
|
||||
expect(opDoc.format).to.equal('byte');
|
||||
// NOTE(bajtos) this would be the case if there was a single response type
|
||||
// expect(opDoc.type).to.equal('array');
|
||||
// expect(opDoc.items).to.eql({type: 'customType'});
|
||||
});
|
||||
|
||||
it('includes `notes` metadata', function() {
|
||||
|
@ -151,12 +145,45 @@ describe('route-helper', function() {
|
|||
.to.have.property('enum').eql([1,2,3]);
|
||||
});
|
||||
|
||||
it('preserves `enum` returns arg metadata', function() {
|
||||
it('includes the default response message with code 200', function() {
|
||||
var doc = createAPIDoc({
|
||||
returns: [{ name: 'arg', root: true, type: 'number', enum: [1,2,3] }]
|
||||
returns: [{ name: 'result', type: 'object', root: true }]
|
||||
});
|
||||
expect(doc.operations[0].responseMessages).to.eql([
|
||||
{
|
||||
code: 200,
|
||||
message: 'Request was successful',
|
||||
responseModel: 'object'
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('uses the response code 204 when `returns` is empty', function() {
|
||||
var doc = createAPIDoc({
|
||||
returns: []
|
||||
});
|
||||
expect(doc.operations[0].responseMessages).to.eql([
|
||||
{
|
||||
code: 204,
|
||||
message: 'Request was successful',
|
||||
responseModel: 'void'
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('includes custom error response in `responseMessages`', function() {
|
||||
var doc = createAPIDoc({
|
||||
errors: [{
|
||||
code: 422,
|
||||
message: 'Validation failed',
|
||||
responseModel: 'ValidationError'
|
||||
}]
|
||||
});
|
||||
expect(doc.operations[0].responseMessages[1]).to.eql({
|
||||
code: 422,
|
||||
message: 'Validation failed',
|
||||
responseModel: 'ValidationError'
|
||||
});
|
||||
expect(doc.operations[0])
|
||||
.to.have.property('enum').eql([1,2,3]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -168,3 +195,7 @@ function createAPIDoc(def) {
|
|||
method: 'test.get'
|
||||
}));
|
||||
}
|
||||
|
||||
function getResponseType(operationDoc) {
|
||||
return operationDoc.responseMessages[0].responseModel;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ describe('swagger definition', function() {
|
|||
describe('basePath', function() {
|
||||
// No basepath on resource doc in 1.2
|
||||
it('no longer exists on resource doc', function(done) {
|
||||
var app = mountSwagger();
|
||||
var app = givenAppWithSwagger();
|
||||
|
||||
var getReq = getSwaggerResources(app);
|
||||
getReq.end(function(err, res) {
|
||||
|
@ -24,7 +24,7 @@ describe('swagger definition', function() {
|
|||
});
|
||||
|
||||
it('is "http://{host}/api" by default', function(done) {
|
||||
var app = mountSwagger();
|
||||
var app = givenAppWithSwagger();
|
||||
|
||||
var getReq = getAPIDeclaration(app, 'products');
|
||||
getReq.end(function(err, res) {
|
||||
|
@ -35,7 +35,7 @@ describe('swagger definition', function() {
|
|||
});
|
||||
|
||||
it('is "http://{host}/{basePath}" when basePath is a path', function(done){
|
||||
var app = mountSwagger({ basePath: '/api-root'});
|
||||
var app = givenAppWithSwagger({ basePath: '/api-root'});
|
||||
|
||||
var getReq = getAPIDeclaration(app, 'products');
|
||||
getReq.end(function(err, res) {
|
||||
|
@ -47,7 +47,7 @@ describe('swagger definition', function() {
|
|||
});
|
||||
|
||||
it('infers API basePath from app', function(done){
|
||||
var app = mountSwagger({}, {apiRoot: '/custom-api-root'});
|
||||
var app = givenAppWithSwagger({}, {apiRoot: '/custom-api-root'});
|
||||
|
||||
var getReq = getAPIDeclaration(app, 'products');
|
||||
getReq.end(function(err, res) {
|
||||
|
@ -60,7 +60,7 @@ describe('swagger definition', function() {
|
|||
|
||||
it('is reachable when explorer mounting location is changed', function(done){
|
||||
var explorerRoot = '/erforscher';
|
||||
var app = mountSwagger({}, {explorerRoot: explorerRoot});
|
||||
var app = givenAppWithSwagger({}, {explorerRoot: explorerRoot});
|
||||
|
||||
var getReq = getSwaggerResources(app, explorerRoot, 'products');
|
||||
getReq.end(function(err, res) {
|
||||
|
@ -71,7 +71,7 @@ describe('swagger definition', function() {
|
|||
});
|
||||
|
||||
it('respects a hardcoded protocol (behind SSL terminator)', function(done){
|
||||
var app = mountSwagger({protocol: 'https'});
|
||||
var app = givenAppWithSwagger({protocol: 'https'});
|
||||
|
||||
var getReq = getAPIDeclaration(app, 'products');
|
||||
getReq.end(function(err, res) {
|
||||
|
@ -81,11 +81,23 @@ describe('swagger definition', function() {
|
|||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('respects X-Forwarded-Host header (behind a proxy)', function(done) {
|
||||
var app = givenAppWithSwagger();
|
||||
getAPIDeclaration(app, 'products')
|
||||
.set('X-Forwarded-Host', 'example.com')
|
||||
.end(function(err, res) {
|
||||
if (err) return done(err);
|
||||
var baseUrl = url.parse(res.body.basePath);
|
||||
expect(baseUrl.hostname).to.equal('example.com');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Model definition attributes', function() {
|
||||
it('Properly defines basic attributes', function(done) {
|
||||
var app = mountSwagger();
|
||||
var app = givenAppWithSwagger();
|
||||
|
||||
var getReq = getAPIDeclaration(app, 'products');
|
||||
getReq.end(function(err, res) {
|
||||
|
@ -106,7 +118,7 @@ describe('swagger definition', function() {
|
|||
});
|
||||
|
||||
it('includes `consumes`', function(done) {
|
||||
var app = mountSwagger();
|
||||
var app = givenAppWithSwagger();
|
||||
getAPIDeclaration(app, 'products').end(function(err, res) {
|
||||
if (err) return done(err);
|
||||
expect(res.body.consumes).to.have.members([
|
||||
|
@ -119,7 +131,7 @@ describe('swagger definition', function() {
|
|||
});
|
||||
|
||||
it('includes `produces`', function(done) {
|
||||
var app = mountSwagger();
|
||||
var app = givenAppWithSwagger();
|
||||
getAPIDeclaration(app, 'products').end(function(err, res) {
|
||||
if (err) return done(err);
|
||||
expect(res.body.produces).to.have.members([
|
||||
|
@ -131,11 +143,167 @@ describe('swagger definition', function() {
|
|||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('includes models from `accepts` args', function(done) {
|
||||
var app = createLoopbackAppWithModel();
|
||||
givenPrivateAppModel(app, 'Image');
|
||||
givenSharedMethod(app.models.Product, 'setImage', {
|
||||
accepts: { name: 'image', type: 'Image' }
|
||||
});
|
||||
mountExplorer(app);
|
||||
|
||||
getAPIDeclaration(app, 'products').end(function(err, res) {
|
||||
expect(Object.keys(res.body.models)).to.include('Image');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('includes models from `returns` args', function(done) {
|
||||
var app = createLoopbackAppWithModel();
|
||||
givenPrivateAppModel(app, 'Image');
|
||||
givenSharedMethod(app.models.Product, 'getImage', {
|
||||
returns: { name: 'image', type: 'Image' }
|
||||
});
|
||||
mountExplorer(app);
|
||||
|
||||
getAPIDeclaration(app, 'products').end(function(err, res) {
|
||||
expect(Object.keys(res.body.models)).to.include('Image');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('includes `accepts` models not attached to the app', function(done) {
|
||||
var app = createLoopbackAppWithModel();
|
||||
loopback.createModel('Image');
|
||||
givenSharedMethod(app.models.Product, 'setImage', {
|
||||
accepts: { name: 'image', type: 'Image' }
|
||||
});
|
||||
mountExplorer(app);
|
||||
|
||||
getAPIDeclaration(app, 'products').end(function(err, res) {
|
||||
expect(Object.keys(res.body.models)).to.include('Image');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('includes `responseMessages` models', function(done) {
|
||||
var app = createLoopbackAppWithModel();
|
||||
loopback.createModel('ValidationError');
|
||||
givenSharedMethod(app.models.Product, 'setImage', {
|
||||
errors: [{
|
||||
code: '422',
|
||||
message: 'Validation failed',
|
||||
responseModel: 'ValidationError'
|
||||
}]
|
||||
});
|
||||
|
||||
expectProductDocIncludesModels(app, 'ValidationError', done);
|
||||
});
|
||||
|
||||
it('includes nested model references in properties', function(done) {
|
||||
var app = createLoopbackAppWithModel();
|
||||
givenWarehouseWithAddressModels(app);
|
||||
|
||||
app.models.Product.defineProperty('location', { type: 'Warehouse' });
|
||||
|
||||
expectProductDocIncludesModels(app, ['Address', 'Warehouse'], done);
|
||||
});
|
||||
|
||||
it('includes nested array model references in properties', function(done) {
|
||||
var app = createLoopbackAppWithModel();
|
||||
givenWarehouseWithAddressModels(app);
|
||||
|
||||
app.models.Product.defineProperty('location', { type: ['Warehouse'] });
|
||||
|
||||
expectProductDocIncludesModels(app, ['Address', 'Warehouse'], done);
|
||||
});
|
||||
|
||||
it('includes nested model references in modelTo relation', function(done) {
|
||||
var app = createLoopbackAppWithModel();
|
||||
givenWarehouseWithAddressModels(app);
|
||||
|
||||
app.models.Product.belongsTo(app.models.Warehouse);
|
||||
|
||||
expectProductDocIncludesModels(app, ['Address', 'Warehouse'], done);
|
||||
});
|
||||
|
||||
it('includes nested model references in modelTo relation', function(done) {
|
||||
var app = createLoopbackAppWithModel();
|
||||
givenWarehouseWithAddressModels(app);
|
||||
givenPrivateAppModel(app, 'ProductLocations');
|
||||
|
||||
app.models.Product.hasMany(app.models.Warehouse,
|
||||
{ through: app.models.ProductLocations });
|
||||
|
||||
expectProductDocIncludesModels(
|
||||
app,
|
||||
['Address', 'Warehouse', 'ProductLocations'],
|
||||
done);
|
||||
});
|
||||
|
||||
it('includes nested model references in accept args', function(done) {
|
||||
var app = createLoopbackAppWithModel();
|
||||
givenWarehouseWithAddressModels(app);
|
||||
|
||||
givenSharedMethod(app.models.Product, 'aMethod', {
|
||||
accepts: { arg: 'w', type: 'Warehouse' }
|
||||
});
|
||||
|
||||
expectProductDocIncludesModels(app, ['Address', 'Warehouse'], done);
|
||||
});
|
||||
|
||||
it('includes nested array model references in accept args', function(done) {
|
||||
var app = createLoopbackAppWithModel();
|
||||
givenWarehouseWithAddressModels(app);
|
||||
|
||||
givenSharedMethod(app.models.Product, 'aMethod', {
|
||||
accepts: { arg: 'w', type: [ 'Warehouse' ] }
|
||||
});
|
||||
|
||||
expectProductDocIncludesModels(app, ['Address', 'Warehouse'], done);
|
||||
});
|
||||
|
||||
it('includes nested model references in return args', function(done) {
|
||||
var app = createLoopbackAppWithModel();
|
||||
givenWarehouseWithAddressModels(app);
|
||||
|
||||
givenSharedMethod(app.models.Product, 'aMethod', {
|
||||
returns: { arg: 'w', type: 'Warehouse', root: true }
|
||||
});
|
||||
|
||||
expectProductDocIncludesModels(app, ['Address', 'Warehouse'], done);
|
||||
});
|
||||
|
||||
it('includes nested array model references in return args', function(done) {
|
||||
var app = createLoopbackAppWithModel();
|
||||
givenWarehouseWithAddressModels(app);
|
||||
|
||||
givenSharedMethod(app.models.Product, 'aMethod', {
|
||||
returns: { arg: 'w', type: ['Warehouse'], root: true }
|
||||
});
|
||||
|
||||
expectProductDocIncludesModels(app, ['Address', 'Warehouse'], done);
|
||||
});
|
||||
|
||||
it('includes nested model references in error responses', function(done) {
|
||||
var app = createLoopbackAppWithModel();
|
||||
givenWarehouseWithAddressModels(app);
|
||||
|
||||
givenSharedMethod(app.models.Product, 'aMethod', {
|
||||
errors: {
|
||||
code: '222',
|
||||
message: 'Warehouse',
|
||||
responseModel: 'Warehouse'
|
||||
}
|
||||
});
|
||||
|
||||
expectProductDocIncludesModels(app, ['Address', 'Warehouse'], done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Cross-origin resource sharing', function() {
|
||||
it('allows cross-origin requests by default', function(done) {
|
||||
var app = mountSwagger();
|
||||
var app = givenAppWithSwagger();
|
||||
request(app)
|
||||
.options('/explorer/resources')
|
||||
.set('Origin', 'http://example.com/')
|
||||
|
@ -145,7 +313,7 @@ describe('swagger definition', function() {
|
|||
});
|
||||
|
||||
it('can be disabled by configuration', function(done) {
|
||||
var app = mountSwagger({}, { remoting: { cors: { origin: false } } });
|
||||
var app = givenAppWithSwagger({}, { remoting: { cors: { origin: false } } });
|
||||
request(app)
|
||||
.options('/explorer/resources')
|
||||
.end(function(err, res) {
|
||||
|
@ -162,34 +330,43 @@ describe('swagger definition', function() {
|
|||
return request(app)
|
||||
.get(urlJoin(restPath || '/explorer', '/resources', classPath || ''))
|
||||
.set('Accept', 'application/json')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200);
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/);
|
||||
}
|
||||
|
||||
function getAPIDeclaration(app, className) {
|
||||
return getSwaggerResources(app, '', urlJoin('/', className));
|
||||
}
|
||||
|
||||
function mountSwagger(options, addlOptions) {
|
||||
addlOptions = addlOptions || {};
|
||||
var app = createLoopbackAppWithModel(addlOptions.apiRoot);
|
||||
function givenAppWithSwagger(swaggerOptions, appConfig) {
|
||||
appConfig = appConfig || {};
|
||||
var app = createLoopbackAppWithModel(appConfig.apiRoot);
|
||||
|
||||
if (appConfig.remoting) app.set('remoting', appConfig.remoting);
|
||||
if (appConfig.explorerRoot) app.set('explorerRoot', appConfig.explorerRoot);
|
||||
|
||||
mountExplorer(app, swaggerOptions);
|
||||
return app;
|
||||
}
|
||||
|
||||
function mountExplorer(app, options) {
|
||||
var swaggerApp = express();
|
||||
if (addlOptions.remoting) app.set('remoting', addlOptions.remoting);
|
||||
swagger(app, swaggerApp, options);
|
||||
app.use(addlOptions.explorerRoot || '/explorer', swaggerApp);
|
||||
app.use(app.get('explorerRoot') || '/explorer', swaggerApp);
|
||||
return app;
|
||||
}
|
||||
|
||||
function createLoopbackAppWithModel(apiRoot) {
|
||||
var app = loopback();
|
||||
|
||||
app.dataSource('db', { connector: 'memory' });
|
||||
|
||||
var Product = loopback.Model.extend('product', {
|
||||
foo: {type: 'string', required: true},
|
||||
bar: 'string',
|
||||
aNum: {type: 'number', min: 1, max: 10, required: true, default: 5}
|
||||
});
|
||||
Product.attachTo(loopback.memory());
|
||||
app.model(Product);
|
||||
app.model(Product, { dataSource: 'db'});
|
||||
|
||||
// Simulate a restApiRoot set in config
|
||||
app.set('restApiRoot', apiRoot || '/api');
|
||||
|
@ -197,4 +374,33 @@ describe('swagger definition', function() {
|
|||
|
||||
return app;
|
||||
}
|
||||
|
||||
function givenSharedMethod(model, name, metadata) {
|
||||
model[name] = function(){};
|
||||
loopback.remoteMethod(model[name], metadata);
|
||||
}
|
||||
|
||||
function givenPrivateAppModel(app, name, properties) {
|
||||
var model = loopback.createModel(name, properties);
|
||||
app.model(model, { dataSource: 'db', public: false} );
|
||||
}
|
||||
|
||||
function givenWarehouseWithAddressModels(app) {
|
||||
givenPrivateAppModel(app, 'Address');
|
||||
givenPrivateAppModel(app, 'Warehouse', {
|
||||
shippingAddress: { type: 'Address' }
|
||||
});
|
||||
}
|
||||
|
||||
function expectProductDocIncludesModels(app, modelNames, done) {
|
||||
if (!Array.isArray(modelNames)) modelNames = [modelNames];
|
||||
|
||||
mountExplorer(app);
|
||||
|
||||
getAPIDeclaration(app, 'products').end(function(err, res) {
|
||||
if (err) return done(err);
|
||||
expect(Object.keys(res.body.models)).to.include.members(modelNames);
|
||||
done();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue