loopback-swagger
Version:
Integration between LoopBack and Swagger API specs
830 lines (688 loc) • 29.6 kB
JavaScript
// Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Node module: loopback-swagger
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
;
var _ = require('lodash');
var url = require('url');
var loopback = require('loopback');
var createSwaggerObject = require('../..').generateSwaggerSpec;
var expect = require('chai').expect;
describe('swagger definition', function() {
describe('defaults', function() {
var swaggerResource;
before(function() {
var app = createLoopbackAppWithModel();
swaggerResource = createSwaggerObject(app);
});
it('advertises Swagger Spec version 2.0', function() {
expect(swaggerResource).to.have.property('swagger', '2.0');
});
it('has "basePath" set to "/api"', function() {
expect(swaggerResource).to.have.property('basePath', '/api');
});
it('uses the "host" serving the documentation', function() {
// see swagger-spec/2.0.md#fixed-fields
// If the host is not included, the host serving the documentation is to
// be used (including the port).
expect(swaggerResource).to.have.property('host', undefined);
});
it('uses the "schemes" serving the documentation', function() {
// see swagger-spec/2.0.md#fixed-fields
// If the schemes is not included, the default scheme to be used is the
// one used to access the Swagger definition itself.
expect(swaggerResource).to.have.property('schemes', undefined);
});
it('provides info.title', function() {
expect(swaggerResource.info)
.to.have.property('title', 'loopback-swagger');
});
it('provides info.description', function() {
expect(swaggerResource.info).to.have.property(
'description',
'Integration between LoopBack and Swagger API specs'
);
});
});
describe('basePath', function() {
it('is "{basePath}" when basePath is a path', function() {
var app = createLoopbackAppWithModel();
var swaggerResource = createSwaggerObject(app, {
basePath: '/api-root',
});
expect(swaggerResource.basePath).to.equal('/api-root');
});
it('has custom config', function() {
var swaggerSpec = {customConfig: 'myCustomConfig'};
var app = createLoopbackAppWithModel();
app.set('swagger', swaggerSpec);
var swaggerResource = createSwaggerObject(app);
expect(swaggerResource.customConfig).to.eql('myCustomConfig');
});
it('overrides config in sequence', function() {
var swaggerSpec = {
swagger: 'invalid-swagger-version',
host: '127.0.0.1',
};
var options = {host: 'invalid-host'};
var app = createLoopbackAppWithModel();
app.set('swagger', swaggerSpec);
var swaggerResource = createSwaggerObject(app, options);
expect(swaggerResource.swagger).eql('2.0');
expect(swaggerResource.host).eql('127.0.0.1');
});
it('is inferred from app.get("apiRoot")', function() {
var app = createLoopbackAppWithModel();
app.set('restApiRoot', '/custom-api-root');
var swaggerResource = createSwaggerObject(app);
expect(swaggerResource.basePath).to.equal('/custom-api-root');
});
it('respects a hardcoded protocol (behind SSL terminator)', function() {
var app = createLoopbackAppWithModel();
var swaggerResource = createSwaggerObject(app, {
protocol: 'https',
});
expect(swaggerResource.schemes).to.eql(['https']);
});
it('supports opts.host', function() {
var app = createLoopbackAppWithModel();
var swaggerResource = createSwaggerObject(app, {
host: 'example.com:8080',
});
expect(swaggerResource.host).to.equal('example.com:8080');
});
});
it('has global "consumes"', function() {
var app = createLoopbackAppWithModel();
var swaggerResource = createSwaggerObject(app);
expect(swaggerResource.consumes).to.have.members([
'application/json',
'application/x-www-form-urlencoded',
'application/xml', 'text/xml',
]);
});
it('has global "produces"', function() {
var app = createLoopbackAppWithModel();
var swaggerResource = createSwaggerObject(app);
expect(swaggerResource.produces).to.have.members([
'application/json',
'application/xml', 'text/xml',
// JSONP content types
'application/javascript', 'text/javascript',
]);
});
describe('tags', function() {
it('has one tag for each model', function() {
var app = createLoopbackAppWithModel();
var swaggerResource = createSwaggerObject(app);
expect(swaggerResource.tags).to.eql([
{name: 'Product', description: 'a-description\nline2', externalDocs: undefined},
]);
});
});
describe('definitions node', function() {
it('properly defines basic attributes', function() {
var app = createLoopbackAppWithModel();
var swaggerResource = createSwaggerObject(app);
var data = swaggerResource.definitions.Product;
expect(data.required.sort()).to.eql(['aNum', 'foo'].sort());
expect(data.properties.foo.type).to.equal('string');
expect(data.properties.bar.type).to.equal('string');
expect(data.properties.aNum.type).to.equal('number');
// These will be Numbers for Swagger 2.0
expect(data.properties.aNum.minimum).to.equal(1);
expect(data.properties.aNum.maximum).to.equal(10);
// Should be Number even in 1.2
expect(data.properties.aNum.default).to.equal(5);
});
it('includes models from "accepts" args', function() {
var app = createLoopbackAppWithModel();
givenPrivateAppModel(app, 'Image');
givenSharedMethod(app.models.Product, 'setImage', {
accepts: {name: 'image', type: 'Image'},
});
var swaggerResource = createSwaggerObject(app);
expect(Object.keys(swaggerResource.definitions)).to.include('Image');
});
it('includes models from "returns" args', function() {
var app = createLoopbackAppWithModel();
givenPrivateAppModel(app, 'Image');
givenSharedMethod(app.models.Product, 'getImage', {
returns: {name: 'image', type: 'Image', root: true},
});
var swaggerResource = createSwaggerObject(app);
expect(Object.keys(swaggerResource.definitions)).to.include('Image');
});
it('includes "accepts" models not attached to the app', function() {
var app = createLoopbackAppWithModel();
loopback.createModel('Image');
givenSharedMethod(app.models.Product, 'setImage', {
accepts: {name: 'image', type: 'Image'},
});
var swaggerResource = createSwaggerObject(app);
expect(Object.keys(swaggerResource.definitions)).to.include('Image');
});
it('includes "responseMessages" models', function() {
var app = createLoopbackAppWithModel();
loopback.createModel('ValidationError');
givenSharedMethod(app.models.Product, 'setImage', {
errors: [{
code: '422',
message: 'Validation failed',
responseModel: 'ValidationError',
}],
});
var swaggerResource = createSwaggerObject(app);
expect(Object.keys(swaggerResource.definitions))
.to.include('ValidationError');
});
it('includes nested model references in properties', function() {
var app = createLoopbackAppWithModel();
givenWarehouseWithAddressModels(app);
app.models.Product.defineProperty('location', {type: 'Warehouse'});
var swaggerResource = createSwaggerObject(app);
expect(Object.keys(swaggerResource.definitions))
.to.include.members(['Address', 'Warehouse']);
});
it('includes nested array model references in properties', function() {
var app = createLoopbackAppWithModel();
givenWarehouseWithAddressModels(app);
app.models.Product.defineProperty('location', {type: ['Warehouse']});
var swaggerResource = createSwaggerObject(app);
expect(Object.keys(swaggerResource.definitions))
.to.include.members(['Address', 'Warehouse']);
});
it('includes nested model references in modelTo relation', function() {
var app = createLoopbackAppWithModel();
givenWarehouseWithAddressModels(app);
app.models.Product.belongsTo(app.models.Warehouse);
var swaggerResource = createSwaggerObject(app);
expect(Object.keys(swaggerResource.definitions))
.to.include.members(['Address', 'Warehouse']);
});
it('includes nested model references in modelThrough relation', function() {
var app = createLoopbackAppWithModel();
givenWarehouseWithAddressModels(app);
givenPrivateAppModel(app, 'ProductLocations');
app.models.Product.hasMany(app.models.Warehouse,
{through: app.models.ProductLocations});
var swaggerResource = createSwaggerObject(app);
expect(Object.keys(swaggerResource.definitions))
.to.include.members(['Address', 'Warehouse', 'ProductLocations']);
});
it('includes nested model references in accept args', function() {
var app = createLoopbackAppWithModel();
givenWarehouseWithAddressModels(app);
givenSharedMethod(app.models.Product, 'aMethod', {
accepts: {arg: 'w', type: 'Warehouse'},
});
var swaggerResource = createSwaggerObject(app);
expect(Object.keys(swaggerResource.definitions))
.to.include.members(['Address', 'Warehouse']);
});
it('includes nested array model references in accept args', function() {
var app = createLoopbackAppWithModel();
givenWarehouseWithAddressModels(app);
givenSharedMethod(app.models.Product, 'aMethod', {
accepts: {arg: 'w', type: ['Warehouse']},
});
var swaggerResource = createSwaggerObject(app);
expect(Object.keys(swaggerResource.definitions))
.to.include.members(['Address', 'Warehouse']);
});
it('includes nested model references in return args', function() {
var app = createLoopbackAppWithModel();
givenWarehouseWithAddressModels(app);
givenSharedMethod(app.models.Product, 'aMethod', {
returns: {arg: 'w', type: 'Warehouse', root: true},
});
var swaggerResource = createSwaggerObject(app);
expect(Object.keys(swaggerResource.definitions))
.to.include.members(['Address', 'Warehouse']);
});
it('includes nested array model references in return args', function() {
var app = createLoopbackAppWithModel();
givenWarehouseWithAddressModels(app);
givenSharedMethod(app.models.Product, 'aMethod', {
returns: {arg: 'w', type: ['Warehouse'], root: true},
});
var swaggerResource = createSwaggerObject(app);
expect(Object.keys(swaggerResource.definitions))
.to.include.members(['Address', 'Warehouse']);
});
it('includes nested model references in error responses', function() {
var app = createLoopbackAppWithModel();
givenWarehouseWithAddressModels(app);
givenSharedMethod(app.models.Product, 'aMethod', {
errors: {
code: '222',
message: 'Warehouse',
responseModel: 'Warehouse',
},
});
var swaggerResource = createSwaggerObject(app);
expect(Object.keys(swaggerResource.definitions))
.to.include.members(['Address', 'Warehouse']);
});
it('includes hidden models referenced by public models', function() {
var app = loopback({localRegistry: true, loadBuiltinModels: true});
app.dataSource('db', {connector: 'memory'});
app.model(app.registry.AccessToken, {public: false, dataSource: 'db'});
app.model(app.registry.User, {public: true, dataSource: 'db'});
var swaggerResource = createSwaggerObject(app);
expect(Object.keys(swaggerResource.definitions))
.to.include.members(['AccessToken']);
});
it('excludes hidden models referenced by hidden models only', function() {
var app = loopback({localRegistry: true, loadBuiltinModels: true});
app.dataSource('db', {connector: 'memory'});
app.model(app.registry.RoleMapping, {public: false, dataSource: 'db'});
app.model(app.registry.Role, {public: false, dataSource: 'db'});
var swaggerResource = createSwaggerObject(app);
expect(Object.keys(swaggerResource.definitions))
.to.not.include.members(['Role', 'RoleMapping']);
});
it('excludes definitions referenced by hidden models', function() {
var app = loopback({localRegistry: true});
var MyModel = app.registry.createModel({
name: 'MyModel',
base: 'Model',
properties: {
data: {type: 'any'},
},
});
app.model(MyModel, {public: false, dataSource: null});
var swaggerResource = createSwaggerObject(app);
// the app does not have any public models,
// therefore there should be no definitions
expect(Object.keys(swaggerResource.definitions))
.to.have.length(0);
});
});
describe('prototype.patchAttributes', function() {
it('has parameters in the correct order', function() {
var app = createLoopbackAppWithModel();
var swaggerResource = createSwaggerObject(app);
var operation = getAllOperations(swaggerResource).
find(_.matchesProperty('operationId', 'Product.prototype.patchAttributes'));
var parameters = _(operation.parameters)
.map(_.property('name'))
.value();
expect(parameters).eql(['id', 'data']);
});
});
describe('paths node', function() {
it('contains model routes for static methods', function() {
var app = createLoopbackAppWithModel();
var swaggerResource = createSwaggerObject(app);
expect(swaggerResource.paths).to.have.property('/Products');
var products = swaggerResource.paths['/Products'];
var verbs = Object.keys(products);
verbs.sort();
expect(verbs).to.contain('get', 'patch', 'post', 'put');
});
it('has unique operation ids', function() {
var app = createLoopbackAppWithModel();
app.models.Product.remoteMethod('multipath', {
isStatic: true,
http: [
{verb: 'get', path: '/multipath'},
{verb: 'post', path: '/multipath'},
],
});
var swaggerResource = createSwaggerObject(app);
// extract swaggerResource.{path}.{verb}.operationId
var ids = getAllOperations(swaggerResource).map(_.property('operationId')).value();
var conflicts = _(ids).countBy().reduce(function(result, value, key) {
return value > 1 ? result.concat([key]) : result;
}, []);
expect(conflicts, 'duplicate ids').to.be.have.length(0);
expect(swaggerResource.paths['/Products/multipath'].get.operationId)
.to.contain('Product.multipath');
});
});
describe('updateOnly', function() {
it('should generate two swagger model definitions when forceId is undefined',
function() {
// forceId is undefined since forceId is not passed into the model
var app = createLoopbackAppWithModel();
var swaggerResource = createSwaggerObject(app, {
generateOperationScopedModels: true,
});
// Additional swagger object - $new_Product is generated since Product
// model has generated ID and forceId is not set to false. This object
// is used for create operation where it excludes 'id' property
expect(Object.keys(swaggerResource.definitions))
.to.include.members(['$new_Product', 'Product']);
});
it('should generate two swagger model definitions when forceId is true', function() {
const options = {
forceId: true,
};
var app = createLoopbackAppWithModel(options);
var swaggerResource = createSwaggerObject(app, {
generateOperationScopedModels: true,
});
// Additional swagger object - $new_Product is generated since Product
// model has generated ID and forceId is not set to false. This object
// is used for create operation where it excludes 'id' property
expect(Object.keys(swaggerResource.definitions))
.to.include.members(['$new_Product', 'Product']);
});
it('should generate one swagger model definition when forceId is false', function() {
const options = {
forceId: false,
};
var app = createLoopbackAppWithModel(options);
var swaggerResource = createSwaggerObject(app, {
generateOperationScopedModels: true,
});
expect(Object.keys(swaggerResource.definitions))
.to.not.include(['$new_Product']);
expect(Object.keys(swaggerResource.definitions))
.to.include.members(['Product']);
});
it('should use $new_Product definition for post/create operation when ' +
'forceId is in effect', function() {
var app = createLoopbackAppWithModel();
var swaggerResource = createSwaggerObject(app, {
generateOperationScopedModels: true,
});
// Post(create) operation should reference $new_Product
expect(swaggerResource.paths['/Products'].post.parameters[0].schema.$ref)
.to.equal('#/definitions/$new_Product');
// patch or any other operation should reference Product
expect(swaggerResource.paths['/Products'].patch.parameters[0].schema.$ref)
.to.equal('#/definitions/Product');
});
it('should use Product swagger definition for all operations when ' +
'forceId is false', function() {
const options = {
forceId: false,
};
var app = createLoopbackAppWithModel(options);
var swaggerResource = createSwaggerObject(app, {
generateOperationScopedModels: true,
});
// post(create), patch or any other operation should reference Product
expect(swaggerResource.paths['/Products'].post.parameters[0].schema.$ref)
.to.equal('#/definitions/Product');
expect(swaggerResource.paths['/Products'].patch.parameters[0].schema.$ref)
.to.equal('#/definitions/Product');
});
it('should generate one swagger model definitions when ' +
'generateOperationScopedModels is false',
function() {
var app = createLoopbackAppWithModel();
var swaggerResource = createSwaggerObject(app, {
generateOperationScopedModels: false,
});
// when generateOperationScopedModels is false, then even if forceId is true and
// generated id is true there will be only one model (Product) generated.
expect(Object.keys(swaggerResource.definitions))
.to.not.include(['$new_Product']);
expect(Object.keys(swaggerResource.definitions))
.to.include.members(['Product']);
});
it('should generate one swagger model definitions when ' +
'generateOperationScopedModels is undefined(false)',
function() {
var app = createLoopbackAppWithModel();
var swaggerResource = createSwaggerObject(app);
// when generateOperationScopedModels is undefined the value defaults to false. Then even if
// forceId is true and generated id is true there will be only one model (Product) generated.
expect(Object.keys(swaggerResource.definitions))
.to.not.include(['$new_Product']);
expect(Object.keys(swaggerResource.definitions))
.to.include.members(['Product']);
});
});
describe('model with relations', function() {
it('define custom model with relation property', function() {
var app = createConversationAndMessageModelsWithRelations();
var swaggerResource = createSwaggerObject(app, {
generateRelationProperties: true,
});
var definitions = swaggerResource.definitions;
expect(Object.keys(definitions))
.to.include.members([
'UserWithRelations',
'ConversationWithRelations',
'MessageWithRelations',
]);
var userWithRelationsModelDefinition = definitions
.UserWithRelations;
expect(Object.keys(userWithRelationsModelDefinition.properties))
.to.include.members(['conversations']);
expect(userWithRelationsModelDefinition.properties.conversations).to.be.eql({
type: 'array',
items: {
$ref: '#/definitions/ConversationWithRelations',
},
});
var conversationWithRelationsModelDefinition = definitions
.ConversationWithRelations;
expect(Object.keys(conversationWithRelationsModelDefinition.properties))
.to.include.members(['messages', 'user']);
// Message hasMany relation
expect(conversationWithRelationsModelDefinition.properties.messages).to.be.eql({
type: 'array',
items: {
$ref: '#/definitions/MessageWithRelations',
},
});
// User belongsTo relation
expect(conversationWithRelationsModelDefinition.properties.user).to.be.eql({
$ref: '#/definitions/UserWithRelations',
});
var messageWithRelationsModelDefinition = definitions
.MessageWithRelations;
expect(Object.keys(messageWithRelationsModelDefinition.properties))
.to.include.members(['conversation']);
// Conversation belongsTo relation
expect(conversationWithRelationsModelDefinition.properties.user).to.be.eql({
$ref: '#/definitions/UserWithRelations',
});
});
it('includes model with relation property only for `find*` and ' +
'`__(get|findById)__*` methods', function() {
var app = createConversationAndMessageModelsWithRelations();
var swaggerResource = createSwaggerObject(app, {
generateRelationProperties: true,
});
// Model with relations
verifyResponseWithRelations(swaggerResource, 'get', [
'/Users'], 'UserWithRelations', true);
verifyResponseWithRelations(swaggerResource, 'get', [
'/Users/{id}',
'/Users/findOne',
'/Conversations/{id}/user',
'/Users/{id}/conversations/{nk}/user',
], 'UserWithRelations', false);
verifyResponseWithRelations(swaggerResource, 'get', [
'/Users/{id}/conversations',
'/Conversations',
], 'ConversationWithRelations', true);
verifyResponseWithRelations(swaggerResource, 'get', [
'/Users/{id}/conversations/{fk}',
'/Conversations/{id}',
'/Conversations/findOne',
'/Messages/{id}/conversation',
], 'ConversationWithRelations', false);
verifyResponseWithRelations(swaggerResource, 'get', [
'/Conversations/{id}/messages',
'/Messages',
'/Users/{id}/conversations/{nk}/messages',
], 'MessageWithRelations', true);
verifyResponseWithRelations(swaggerResource, 'get', [
'/Messages/{id}',
'/Conversations/{id}/messages/{fk}',
'/Users/{id}/conversations/{nk}/messages/{fk}',
], 'MessageWithRelations', false);
// Model without relations
verifyResponseWithRelations(swaggerResource, 'post', [
'/Users',
'/Users/replaceOrCreate',
'/Users/upsertWithWhere',
'/Users/{id}/replace',
], 'User', false);
verifyResponseWithRelations(swaggerResource, 'put', ['/Users'], 'User', false);
verifyResponseWithRelations(swaggerResource, 'patch', ['/Users'], 'User', false);
verifyResponseWithRelations(swaggerResource, 'post', [
'/Conversations',
'/Conversations/replaceOrCreate',
'/Conversations/upsertWithWhere',
'/Conversations/{id}/replace',
], 'Conversation', false);
verifyResponseWithRelations(swaggerResource, 'put', [
'/Users/{id}/conversations/{fk}',
], 'Conversation', false);
verifyResponseWithRelations(swaggerResource, 'post', [
'/Users/{id}/conversations',
], 'Conversation', false);
verifyResponseWithRelations(swaggerResource, 'post', [
'/Messages',
'/Messages/replaceOrCreate',
'/Messages/upsertWithWhere',
'/Messages/{id}/replace',
'/Users/{id}/conversations/{nk}/messages',
], 'Message', false);
verifyResponseWithRelations(swaggerResource, 'post', [
'/Conversations/{id}/messages',
], 'Message', false);
verifyResponseWithRelations(swaggerResource, 'put', [
'/Conversations/{id}/messages/{fk}',
'/Users/{id}/conversations/{nk}/messages/{fk}',
], 'Message', false);
});
it('honors model relation setting "disableInclude"', function() {
var app = createConversationAndMessageModelsWithRelations();
var swaggerResource = createSwaggerObject(app, {
generateRelationProperties: true,
});
var definitions = swaggerResource.definitions;
expect(Object.keys(definitions))
.to.include.members(['UserWithRelations']);
var userModelDefinition = definitions
.UserWithRelations;
expect(Object.keys(userModelDefinition.properties))
.to.not.include.members(['accessTokens']);
});
it('should enable generateRelationProperties from application config', function() {
var app = createConversationAndMessageModelsWithRelations();
app.set('swagger', {
generateRelationProperties: true,
});
var swaggerResource = createSwaggerObject(app);
var definitions = swaggerResource.definitions;
expect(Object.keys(definitions))
.to.include.members([
'UserWithRelations',
'ConversationWithRelations',
'MessageWithRelations',
]);
});
it('honor generateRelationProperties method config', function() {
var app = createConversationAndMessageModelsWithRelations();
app.set('swagger', {
generateRelationProperties: true,
});
var swaggerResource = createSwaggerObject(app, {
generateRelationProperties: false,
});
var definitions = swaggerResource.definitions;
expect(Object.keys(definitions))
.to.not.include.members([
'UserWithRelations',
'ConversationWithRelations',
'MessageWithRelations',
]);
});
});
function createLoopbackAppWithModel(options) {
var app = loopback();
app.dataSource('db', {connector: 'memory'});
const modelSettings = {description: ['a-description', 'line2']};
if (options && options.forceId !== undefined) {
modelSettings.forceId = options.forceId;
}
var Product = loopback.createModel('Product', {
foo: {type: 'string', required: true},
bar: 'string',
aNum: {type: 'number', min: 1, max: 10, required: true, default: 5},
}, modelSettings);
app.model(Product, {dataSource: 'db'});
// Simulate a restApiRoot set in config
app.set('restApiRoot', options && options.apiRoot || '/api');
app.use(app.get('restApiRoot'), loopback.rest());
return app;
}
function verifyResponseWithRelations(swaggerResource, verb, paths,
modelWithRelations, isArray) {
const definition = '#/definitions/' + modelWithRelations;
const refs = Object.keys(swaggerResource.paths)
.filter(key => paths.includes(key))
.map(key => {
const path = swaggerResource.paths[key];
const schema = path[verb].responses[200].schema;
return isArray ? schema.items.$ref : schema.$ref;
});
expect(refs).to.have.lengthOf(paths.length);
const wrongDefinitions = refs.filter(ref => ref !== definition);
expect(wrongDefinitions).to.have.lengthOf(0);
}
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 createConversationAndMessageModelsWithRelations() {
var app = loopback({localRegistry: true, loadBuiltinModels: true});
var User = app.registry.User;
var AccessToken = app.registry.AccessToken;
var Message = loopback.createModel('Message', {
text: 'string',
created: 'date',
});
var Conversation = loopback.createModel('Conversation', {
created: 'date',
});
app.dataSource('db', {connector: 'memory'});
app.model(AccessToken, {public: false, dataSource: 'db'});
app.model(User, {public: true, dataSource: 'db'});
app.model(Conversation, {dataSource: 'db'});
app.model(Message, {dataSource: 'db'});
User.hasMany(Conversation);
Conversation.hasMany(Message);
Message.belongsTo(Conversation);
Conversation.belongsTo(User);
User.nestRemoting('conversations');
Conversation.nestRemoting('user');
Conversation.nestRemoting('messages');
Message.nestRemoting('conversation');
return app;
}
// Simple url joiner. Ensure we don't have to care about whether or not
// we are fed paths with leading/trailing slashes.
function urlJoin() {
var args = Array.prototype.slice.call(arguments);
return args.join('/').replace(/\/+/g, '/');
}
// Returns a lodash wrapper
function getAllOperations(swagger) {
// flatten swaggerResource.paths.{path}.{verb} into a single array
return _(swagger.paths)
.values()
.map(_.values)
.flatten();
}
});