Use express routes instead of modifying remoting.
This completes the migration of swagger processing from strong-remoting into loopback-explorer. Added additional usage instructions to README and additional testing. This commit introduces a change into where resource descriptors are hosted. They are no longer hosted under /swagger, but instead under the same path as the Explorer, wherever that may be. Generally, the resource listing will be available at /explorer/resources, and api listings under /explorer/resources/{modelName}.
This commit is contained in:
parent
19c3fe3870
commit
70dddef296
66
README.md
66
README.md
|
@ -16,28 +16,82 @@ var Product = loopback.Model.extend('product');
|
||||||
Product.attachTo(loopback.memory());
|
Product.attachTo(loopback.memory());
|
||||||
app.model(Product);
|
app.model(Product);
|
||||||
|
|
||||||
app.use('/explorer', explorer(app, {} /* options */));
|
app.use('/explorer', explorer(app, {basePath: '/api'}));
|
||||||
app.use(loopback.rest());
|
app.use('/api', loopback.rest());
|
||||||
console.log("Explorer mounted at localhost:" + port + "/explorer");
|
console.log("Explorer mounted at localhost:" + port + "/explorer");
|
||||||
|
|
||||||
app.listen(port);
|
app.listen(port);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Advanced Usage
|
||||||
|
|
||||||
|
Many aspects of the explorer are configurable.
|
||||||
|
|
||||||
|
See [options](#options) for a description of these options:
|
||||||
|
|
||||||
|
```js
|
||||||
|
app.use('/explorer', explorer(app, {
|
||||||
|
basePath: '/custom-api-root',
|
||||||
|
preMiddleware: [
|
||||||
|
// You can add as many items to this middleware chain as you like
|
||||||
|
loopback.basicAuth(bitmex.settings.basicAuth.user, bitmex.settings.basicAuth.password)
|
||||||
|
],
|
||||||
|
swaggerDistRoot: '/swagger',
|
||||||
|
apiInfo: {
|
||||||
|
'title': 'My API',
|
||||||
|
'description': 'Explorer example app.'
|
||||||
|
},
|
||||||
|
resourcePath: 'swaggerResources',
|
||||||
|
version: '0.1-unreleasable'
|
||||||
|
}));
|
||||||
|
app.use('/custom-api-root', loopback.rest());
|
||||||
|
```
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
Options are passed to `explorer(app, options)`.
|
Options are passed to `explorer(app, options)`.
|
||||||
|
|
||||||
`basePath`: **String**
|
`basePath`: **String**
|
||||||
|
|
||||||
> Set the base path for swagger resources.
|
> Default: `app.get('restAPIRoot')` or `'/api'`.
|
||||||
> Default: `app.get('restAPIRoot')` or `/swagger/resources`.
|
|
||||||
|
> Sets the API's base path. This must be set if you are mounting your api
|
||||||
|
> to a path different than '/api', e.g. with
|
||||||
|
> `loopback.use('/custom-api-root', loopback.rest());
|
||||||
|
|
||||||
|
|
||||||
`swaggerDistRoot`: **String**
|
`swaggerDistRoot`: **String**
|
||||||
|
|
||||||
> Set a path within your application for overriding Swagger UI files.
|
> Sets a path within your application for overriding Swagger UI files.
|
||||||
|
|
||||||
> If present, will search `swaggerDistRoot` first when attempting to load Swagger UI, allowing
|
> If present, will search `swaggerDistRoot` first when attempting to load Swagger UI, allowing
|
||||||
you to pick and choose overrides.
|
> you to pick and choose overrides to the interface. Use this to style your explorer or
|
||||||
|
> add additional functionality.
|
||||||
|
|
||||||
> See [index.html](public/index.html), where you may want to begin your overrides.
|
> See [index.html](public/index.html), where you may want to begin your overrides.
|
||||||
> The rest of the UI is provided by [Swagger UI](https://github.com/wordnik/swagger-ui).
|
> The rest of the UI is provided by [Swagger UI](https://github.com/wordnik/swagger-ui).
|
||||||
|
|
||||||
|
`apiInfo`: **Object**
|
||||||
|
|
||||||
|
> Additional information about your API. See the
|
||||||
|
> [spec](https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#513-info-object).
|
||||||
|
|
||||||
|
`resourcePath`: **String**
|
||||||
|
|
||||||
|
> Default: `'resources'`
|
||||||
|
|
||||||
|
> Sets a different path for the
|
||||||
|
> [resource listing](https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#51-resource-listing).
|
||||||
|
> You generally shouldn't have to change this.
|
||||||
|
|
||||||
|
`version`: **String**
|
||||||
|
|
||||||
|
> Default: Read from package.json
|
||||||
|
|
||||||
|
> Sets your API version. If not present, will read from your app's package.json.
|
||||||
|
|
||||||
|
`preMiddleware`: **Array<Function>|Function**
|
||||||
|
|
||||||
|
> Middleware to run before any explorer routes, including static routes.
|
||||||
|
|
||||||
|
> Useful for setting HTTP Auth, modifying the `Host` header, and so on.
|
||||||
|
|
|
@ -11,8 +11,9 @@ var Product = loopback.Model.extend('product', {
|
||||||
Product.attachTo(loopback.memory());
|
Product.attachTo(loopback.memory());
|
||||||
app.model(Product);
|
app.model(Product);
|
||||||
|
|
||||||
app.use('/explorer', explorer(app));
|
var apiPath = '/api';
|
||||||
app.use(loopback.rest());
|
app.use('/explorer', explorer(app, {basePath: apiPath}));
|
||||||
console.log("Explorer mounted at localhost:" + port + "/explorer");
|
app.use(apiPath, loopback.rest());
|
||||||
|
console.log('Explorer mounted at localhost:' + port + '/explorer');
|
||||||
|
|
||||||
app.listen(port);
|
app.listen(port);
|
||||||
|
|
35
index.js
35
index.js
|
@ -9,7 +9,8 @@ var loopback = require('loopback');
|
||||||
var express = requireLoopbackDependency('express');
|
var express = requireLoopbackDependency('express');
|
||||||
var swagger = require('./lib/swagger');
|
var swagger = require('./lib/swagger');
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var SWAGGER_UI_ROOT = path.join(__dirname, 'node_modules', 'swagger-ui', 'dist');
|
var SWAGGER_UI_ROOT = path.join(__dirname, 'node_modules',
|
||||||
|
'swagger-ui', 'dist');
|
||||||
var STATIC_ROOT = path.join(__dirname, 'public');
|
var STATIC_ROOT = path.join(__dirname, 'public');
|
||||||
|
|
||||||
module.exports = explorer;
|
module.exports = explorer;
|
||||||
|
@ -23,27 +24,38 @@ module.exports = explorer;
|
||||||
|
|
||||||
function explorer(loopbackApplication, options) {
|
function explorer(loopbackApplication, options) {
|
||||||
options = _defaults({}, options, {
|
options = _defaults({}, options, {
|
||||||
basePath: loopbackApplication.get('restApiRoot') || '',
|
|
||||||
name: 'swagger',
|
|
||||||
resourcePath: 'resources',
|
resourcePath: 'resources',
|
||||||
apiInfo: loopbackApplication.get('apiInfo') || {}
|
apiInfo: loopbackApplication.get('apiInfo') || {},
|
||||||
|
preMiddleware: []
|
||||||
});
|
});
|
||||||
|
|
||||||
swagger(loopbackApplication.remotes(), options);
|
|
||||||
|
|
||||||
var app = express();
|
var app = express();
|
||||||
|
|
||||||
|
swagger(loopbackApplication, app, options);
|
||||||
|
|
||||||
|
// Allow the user to attach middleware that will run before any
|
||||||
|
// explorer routes, e.g. for access control.
|
||||||
|
if (typeof options.preMiddleware === 'function' ||
|
||||||
|
(Array.isArray(options.preMiddleware) && options.preMiddleware.length)) {
|
||||||
|
app.use(options.preMiddleware);
|
||||||
|
}
|
||||||
|
|
||||||
app.disable('x-powered-by');
|
app.disable('x-powered-by');
|
||||||
|
|
||||||
|
// config.json is loaded by swagger-ui. The server should respond
|
||||||
|
// with the relative URI of the resource doc.
|
||||||
app.get('/config.json', function(req, res) {
|
app.get('/config.json', function(req, res) {
|
||||||
|
var resourcePath = req.originalUrl.replace(/\/config.json(\?.*)?$/,
|
||||||
|
path.join('/', options.resourcePath));
|
||||||
res.send({
|
res.send({
|
||||||
url: path.join(options.basePath || '/', options.name, options.resourcePath)
|
url: resourcePath
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// Allow specifying a static file root for swagger files. Any files in that folder
|
|
||||||
// will override those in the swagger-ui distribution. In this way one could e.g.
|
// Allow specifying a static file root for swagger files. Any files in
|
||||||
// make changes to index.html without having to worry about constantly pulling in
|
// that folder will override those in the swagger-ui distribution.
|
||||||
// JS updates.
|
// In this way one could e.g. make changes to index.html without having
|
||||||
|
// to worry about constantly pulling in JS updates.
|
||||||
if (options.swaggerDistRoot) {
|
if (options.swaggerDistRoot) {
|
||||||
app.use(loopback.static(options.swaggerDistRoot));
|
app.use(loopback.static(options.swaggerDistRoot));
|
||||||
}
|
}
|
||||||
|
@ -51,6 +63,7 @@ function explorer(loopbackApplication, options) {
|
||||||
app.use(loopback.static(STATIC_ROOT));
|
app.use(loopback.static(STATIC_ROOT));
|
||||||
// Swagger UI distribution
|
// Swagger UI distribution
|
||||||
app.use(loopback.static(SWAGGER_UI_ROOT));
|
app.use(loopback.static(SWAGGER_UI_ROOT));
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,6 @@ var path = require('path');
|
||||||
* Export the classHelper singleton.
|
* Export the classHelper singleton.
|
||||||
*/
|
*/
|
||||||
var classHelper = module.exports = {
|
var classHelper = module.exports = {
|
||||||
// See below.
|
|
||||||
addDynamicBasePathGetter: addDynamicBasePathGetter,
|
|
||||||
/**
|
/**
|
||||||
* Given a remoting class, generate an API doc.
|
* Given a remoting class, generate an API doc.
|
||||||
* See https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#52-api-declaration
|
* See https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#52-api-declaration
|
||||||
|
@ -47,52 +45,3 @@ var classHelper = module.exports = {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* There's a few forces at play that require this "hack". The Swagger spec
|
|
||||||
* requires a `basePath` to be set at various points in the API/Resource
|
|
||||||
* descriptions. However, we 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. Hence, the getter function.
|
|
||||||
* We can use a `before` hook to pluck the `Host`, then the getter kicks in to
|
|
||||||
* return that path as the `basePath` during JSON serialization.
|
|
||||||
*
|
|
||||||
* @param {SharedClassCollection} remotes The Collection to register a `before`
|
|
||||||
* hook on.
|
|
||||||
* @param {String} path The full path of the route to register
|
|
||||||
* a `before` hook on.
|
|
||||||
* @param {Object} obj The Object to install the `basePath`
|
|
||||||
* getter on.
|
|
||||||
*/
|
|
||||||
function addDynamicBasePathGetter(remotes, path, obj) {
|
|
||||||
var initialPath = obj.basePath || '';
|
|
||||||
var basePath = String(obj.basePath) || '';
|
|
||||||
|
|
||||||
if (!/^https?:\/\//.test(basePath)) {
|
|
||||||
remotes.before(path, function (ctx, next) {
|
|
||||||
var headers = ctx.req.headers;
|
|
||||||
var host = headers.Host || headers.host;
|
|
||||||
|
|
||||||
basePath = ctx.req.protocol + '://' + host + initialPath;
|
|
||||||
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return setter(obj);
|
|
||||||
|
|
||||||
function getter() {
|
|
||||||
return basePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setter(obj) {
|
|
||||||
return Object.defineProperty(obj, 'basePath', {
|
|
||||||
configurable: false,
|
|
||||||
enumerable: true,
|
|
||||||
get: getter
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ module.exports = Swagger;
|
||||||
/**
|
/**
|
||||||
* Module dependencies.
|
* Module dependencies.
|
||||||
*/
|
*/
|
||||||
var Remoting = require('strong-remoting');
|
|
||||||
var debug = require('debug')('loopback-explorer:swagger');
|
var debug = require('debug')('loopback-explorer:swagger');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var _defaults = require('lodash.defaults');
|
var _defaults = require('lodash.defaults');
|
||||||
|
@ -17,38 +16,38 @@ var routeHelper = require('./route-helper');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a remotable Swagger module for plugging into `RemoteObjects`.
|
* Create a remotable Swagger module for plugging into `RemoteObjects`.
|
||||||
|
*
|
||||||
|
* @param {Application} loopbackApplication Host loopback application.
|
||||||
|
* @param {Application} swaggerApp Swagger application used for hosting
|
||||||
|
* these files.
|
||||||
|
* @param {Object} opts Options.
|
||||||
*/
|
*/
|
||||||
function Swagger(remotes, opts) {
|
function Swagger(loopbackApplication, swaggerApp, opts) {
|
||||||
opts = _defaults({}, opts, {
|
opts = _defaults({}, opts, {
|
||||||
name: 'swagger',
|
|
||||||
swaggerVersion: '1.2',
|
swaggerVersion: '1.2',
|
||||||
|
basePath: loopbackApplication.get('restApiRoot') || '/api',
|
||||||
resourcePath: 'resources',
|
resourcePath: 'resources',
|
||||||
version: getVersion(),
|
version: getVersion()
|
||||||
basePath: '/'
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// We need a temporary REST adapter to discover our available routes.
|
// We need a temporary REST adapter to discover our available routes.
|
||||||
|
var remotes = loopbackApplication.remotes();
|
||||||
var adapter = remotes.handler('rest').adapter;
|
var adapter = remotes.handler('rest').adapter;
|
||||||
var routes = adapter.allRoutes();
|
var routes = adapter.allRoutes();
|
||||||
var classes = remotes.classes();
|
var classes = remotes.classes();
|
||||||
|
|
||||||
// Create a new Remoting instance to host the swagger docs.
|
|
||||||
var extension = {};
|
|
||||||
var helper = Remoting.extend(extension);
|
|
||||||
|
|
||||||
// These are the docs we will be sending from the /swagger endpoints.
|
// These are the docs we will be sending from the /swagger endpoints.
|
||||||
var resourceDoc = generateResourceDoc(opts);
|
var resourceDoc = generateResourceDoc(opts);
|
||||||
var apiDocs = {};
|
var apiDocs = {};
|
||||||
|
|
||||||
// A class is an endpoint root; e.g. /users, /products, and so on.
|
// A class is an endpoint root; e.g. /users, /products, and so on.
|
||||||
classes.forEach(function (aClass) {
|
classes.forEach(function (aClass) {
|
||||||
apiDocs[aClass.name] = classHelper.generateAPIDoc(aClass, opts);
|
var doc = apiDocs[aClass.name] = classHelper.generateAPIDoc(aClass, opts);
|
||||||
resourceDoc.apis.push(classHelper.generateResourceDocAPIEntry(aClass));
|
resourceDoc.apis.push(classHelper.generateResourceDocAPIEntry(aClass));
|
||||||
|
|
||||||
// Add the getter for this doc.
|
// Add the getter for this doc.
|
||||||
var docPath = path.join(opts.resourcePath, aClass.http.path);
|
var docPath = path.join(opts.resourcePath, aClass.http.path);
|
||||||
addRoute(helper, apiDocs[aClass.name], docPath);
|
addRoute(swaggerApp, docPath, doc);
|
||||||
classHelper.addDynamicBasePathGetter(remotes, opts.name + '.' + docPath, apiDocs[aClass.name]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// A route is an endpoint, such as /users/findOne.
|
// A route is an endpoint, such as /users/findOne.
|
||||||
|
@ -69,31 +68,41 @@ function Swagger(remotes, opts) {
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The topmost Swagger resource is a description of all (non-Swagger) resources
|
* The topmost Swagger resource is a description of all (non-Swagger)
|
||||||
* available on the system, and where to find more information about them.
|
* resources available on the system, and where to find more
|
||||||
|
* information about them.
|
||||||
*/
|
*/
|
||||||
addRoute(helper, resourceDoc, opts.resourcePath);
|
addRoute(swaggerApp, opts.resourcePath, resourceDoc);
|
||||||
|
|
||||||
// Bind all the above routes to the endpoint at /#{name}.
|
|
||||||
remotes.exports[opts.name] = extension;
|
|
||||||
|
|
||||||
return extension;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a route to this remoting extension.
|
* Add a route to this remoting extension.
|
||||||
* @param {Remote} helper Remoting extension.
|
* @param {Application} app Express application.
|
||||||
* @param {Object} doc Doc to serve.
|
* @param {String} uri Path from which to serve the doc.
|
||||||
* @param {String} path Path from which to serve the doc.
|
* @param {Object} doc Doc to serve.
|
||||||
*/
|
*/
|
||||||
function addRoute(helper, doc, path) {
|
function addRoute(app, uri, doc) {
|
||||||
helper.method(getDoc, {
|
|
||||||
path: path,
|
var hasBasePath = Object.keys(doc).indexOf('basePath') !== -1;
|
||||||
returns: { type: 'object', root: true }
|
var initialPath = doc.basePath || '';
|
||||||
|
|
||||||
|
app.get(path.join('/', uri), function(req, res) {
|
||||||
|
|
||||||
|
// There's a few forces at play that require this "hack". The Swagger spec
|
||||||
|
// requires a `basePath` to be set in the API descriptions. However, we
|
||||||
|
// 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.
|
||||||
|
if (hasBasePath) {
|
||||||
|
var headers = req.headers;
|
||||||
|
var host = headers.Host || headers.host;
|
||||||
|
doc.basePath = req.protocol + '://' + host + initialPath;
|
||||||
|
}
|
||||||
|
res.send(200, doc);
|
||||||
});
|
});
|
||||||
function getDoc(callback) {
|
|
||||||
callback(null, doc);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
"mocha": "~1.14.0",
|
"mocha": "~1.14.0",
|
||||||
"supertest": "~0.8.1",
|
"supertest": "~0.8.1",
|
||||||
"chai": "~1.8.1",
|
"chai": "~1.8.1",
|
||||||
"strong-remoting": "^2.0.0-beta4"
|
"express": "3.x"
|
||||||
},
|
},
|
||||||
"license": {
|
"license": {
|
||||||
"name": "Dual MIT/StrongLoop",
|
"name": "Dual MIT/StrongLoop",
|
||||||
|
|
|
@ -38,24 +38,24 @@ describe('explorer', function() {
|
||||||
.end(function(err, res) {
|
.end(function(err, res) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
expect(res.body).to
|
expect(res.body).to
|
||||||
.have.property('url', '/swagger/resources');
|
.have.property('url', '/explorer/resources');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with custom baseUrl', function() {
|
describe('with custom explorer base', function() {
|
||||||
beforeEach(givenLoopBackAppWithExplorer('/api'));
|
beforeEach(givenLoopBackAppWithExplorer('/swagger'));
|
||||||
|
|
||||||
it('should serve correct swagger-ui config', function(done) {
|
it('should serve correct swagger-ui config', function(done) {
|
||||||
request(this.app)
|
request(this.app)
|
||||||
.get('/explorer/config.json')
|
.get('/swagger/config.json')
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.end(function(err, res) {
|
.end(function(err, res) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
expect(res.body).to
|
expect(res.body).to
|
||||||
.have.property('url', '/api/swagger/resources');
|
.have.property('url', '/swagger/resources');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -73,36 +73,26 @@ describe('explorer', function() {
|
||||||
.end(function(err, res) {
|
.end(function(err, res) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
expect(res.body).to
|
expect(res.body).to
|
||||||
.have.property('url', '/rest-api-root/swagger/resources');
|
.have.property('url', '/explorer/resources');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function givenLoopBackAppWithExplorer(restUrlBase) {
|
function givenLoopBackAppWithExplorer(explorerBase) {
|
||||||
return function(done) {
|
return function(done) {
|
||||||
var app = this.app = loopback();
|
var app = this.app = loopback();
|
||||||
configureRestApiAndExplorer(app, restUrlBase);
|
configureRestApiAndExplorer(app, explorerBase);
|
||||||
done();
|
done();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function configureRestApiAndExplorer(app, restUrlBase) {
|
function configureRestApiAndExplorer(app, explorerBase) {
|
||||||
var Product = loopback.Model.extend('product');
|
var Product = loopback.Model.extend('product');
|
||||||
Product.attachTo(loopback.memory());
|
Product.attachTo(loopback.memory());
|
||||||
app.model(Product);
|
app.model(Product);
|
||||||
|
|
||||||
if (restUrlBase) {
|
app.use(explorerBase || '/explorer', explorer(app));
|
||||||
app.use(restUrlBase, loopback.rest());
|
app.use(app.get('restApiRoot') || '/', loopback.rest());
|
||||||
app.use('/explorer', explorer(app, { basePath: restUrlBase }));
|
|
||||||
} else {
|
|
||||||
// LoopBack REST adapter owns the whole URL space and does not
|
|
||||||
// let other middleware handle same URLs.
|
|
||||||
// It's possible to circumvent this measure by installing
|
|
||||||
// the explorer middleware before the REST middleware.
|
|
||||||
// This way we can acess `/explorer` even when REST is mounted at `/`
|
|
||||||
app.use('/explorer', explorer(app));
|
|
||||||
app.use(app.get('restApiRoot') || '/', loopback.rest());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,26 +22,19 @@
|
||||||
var url = require('url');
|
var url = require('url');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var loopback = require('loopback');
|
var loopback = require('loopback');
|
||||||
|
var express = require('express');
|
||||||
var RemoteObjects = require('strong-remoting');
|
|
||||||
var swagger = require('../lib/swagger.js');
|
var swagger = require('../lib/swagger.js');
|
||||||
|
|
||||||
var request = require('supertest');
|
var request = require('supertest');
|
||||||
var expect = require('chai').expect;
|
var expect = require('chai').expect;
|
||||||
|
|
||||||
describe('swagger definition', function() {
|
describe('swagger definition', function() {
|
||||||
var app;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
app = createLoopbackAppWithModel();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('basePath', function() {
|
describe('basePath', function() {
|
||||||
// No basepath on resource doc in 1.2
|
// No basepath on resource doc in 1.2
|
||||||
it('no longer exists on resource doc', function(done) {
|
it('no longer exists on resource doc', function(done) {
|
||||||
swagger(app.remotes());
|
var app = mountSwagger();
|
||||||
|
|
||||||
var getReq = getSwaggerResources();
|
var getReq = getSwaggerResources(app);
|
||||||
getReq.end(function(err, res) {
|
getReq.end(function(err, res) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
expect(res.body.basePath).to.equal(undefined);
|
expect(res.body.basePath).to.equal(undefined);
|
||||||
|
@ -49,21 +42,21 @@ describe('swagger definition', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('is "http://{host}/" by default', function(done) {
|
it('is "http://{host}/api" by default', function(done) {
|
||||||
swagger(app.remotes());
|
var app = mountSwagger();
|
||||||
|
|
||||||
var getReq = getAPIDeclaration('products');
|
var getReq = getAPIDeclaration(app, 'products');
|
||||||
getReq.end(function(err, res) {
|
getReq.end(function(err, res) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
expect(res.body.basePath).to.equal(url.resolve(getReq.url, '/'));
|
expect(res.body.basePath).to.equal(url.resolve(getReq.url, '/api'));
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('is "http://{host}/{basePath}" when basePath is a path', function(done){
|
it('is "http://{host}/{basePath}" when basePath is a path', function(done){
|
||||||
swagger(app.remotes(), { basePath: '/api-root'});
|
var app = mountSwagger({ basePath: '/api-root'});
|
||||||
|
|
||||||
var getReq = getAPIDeclaration('products');
|
var getReq = getAPIDeclaration(app, 'products');
|
||||||
getReq.end(function(err, res) {
|
getReq.end(function(err, res) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
var apiRoot = url.resolve(getReq.url, '/api-root');
|
var apiRoot = url.resolve(getReq.url, '/api-root');
|
||||||
|
@ -72,15 +65,26 @@ describe('swagger definition', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('is custom URL when basePath is a http(s) URL', function(done) {
|
it('infers API basePath from app', function(done){
|
||||||
var apiUrl = 'http://custom-api-url/';
|
var app = mountSwagger({}, {apiRoot: '/custom-api-root'});
|
||||||
|
|
||||||
swagger(app.remotes(), { basePath: apiUrl });
|
var getReq = getAPIDeclaration(app, 'products');
|
||||||
|
|
||||||
var getReq = getAPIDeclaration('products');
|
|
||||||
getReq.end(function(err, res) {
|
getReq.end(function(err, res) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
expect(res.body.basePath).to.equal(apiUrl);
|
var apiRoot = url.resolve(getReq.url, '/custom-api-root');
|
||||||
|
expect(res.body.basePath).to.equal(apiRoot);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is reachable when explorer mounting location is changed', function(done){
|
||||||
|
var explorerRoot = '/erforscher';
|
||||||
|
var app = mountSwagger({}, {explorerRoot: explorerRoot});
|
||||||
|
|
||||||
|
var getReq = getSwaggerResources(app, explorerRoot, 'products');
|
||||||
|
getReq.end(function(err, res) {
|
||||||
|
if (err) return done(err);
|
||||||
|
expect(res.body.basePath).to.be.a('string');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -88,8 +92,12 @@ describe('swagger definition', function() {
|
||||||
|
|
||||||
describe('Model definition attributes', function() {
|
describe('Model definition attributes', function() {
|
||||||
it('Properly defines basic attributes', function(done) {
|
it('Properly defines basic attributes', function(done) {
|
||||||
var extension = swagger(app.remotes(), {});
|
var app = mountSwagger();
|
||||||
getModelFromRemoting(extension, 'product', function(data) {
|
|
||||||
|
var getReq = getAPIDeclaration(app, 'products');
|
||||||
|
getReq.end(function(err, res) {
|
||||||
|
if (err) return done(err);
|
||||||
|
var data = res.body.models.product;
|
||||||
expect(data.id).to.equal('product');
|
expect(data.id).to.equal('product');
|
||||||
expect(data.required.sort()).to.eql(['id', 'aNum', 'foo'].sort());
|
expect(data.required.sort()).to.eql(['id', 'aNum', 'foo'].sort());
|
||||||
expect(data.properties.foo.type).to.equal('string');
|
expect(data.properties.foo.type).to.equal('string');
|
||||||
|
@ -105,19 +113,28 @@ describe('swagger definition', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function getSwaggerResources(restPath, classPath) {
|
function getSwaggerResources(app, restPath, classPath) {
|
||||||
return request(app)
|
return request(app)
|
||||||
.get(path.join(restPath || '', '/swagger/resources', classPath || ''))
|
.get(path.join(restPath || '/explorer', '/resources', classPath || ''))
|
||||||
.set('Accept', 'application/json')
|
.set('Accept', 'application/json')
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAPIDeclaration(className) {
|
function getAPIDeclaration(app, className) {
|
||||||
return getSwaggerResources('', path.join('/', className));
|
return getSwaggerResources(app, '', path.join('/', className));
|
||||||
}
|
}
|
||||||
|
|
||||||
function createLoopbackAppWithModel() {
|
function mountSwagger(options, addlOptions) {
|
||||||
|
addlOptions = addlOptions || {};
|
||||||
|
var app = createLoopbackAppWithModel(addlOptions.apiRoot);
|
||||||
|
var swaggerApp = express();
|
||||||
|
swagger(app, swaggerApp, options);
|
||||||
|
app.use(addlOptions.explorerRoot || '/explorer', swaggerApp);
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createLoopbackAppWithModel(apiRoot) {
|
||||||
var app = loopback();
|
var app = loopback();
|
||||||
|
|
||||||
var Product = loopback.Model.extend('product', {
|
var Product = loopback.Model.extend('product', {
|
||||||
|
@ -128,14 +145,10 @@ describe('swagger definition', function() {
|
||||||
Product.attachTo(loopback.memory());
|
Product.attachTo(loopback.memory());
|
||||||
app.model(Product);
|
app.model(Product);
|
||||||
|
|
||||||
app.use(loopback.rest());
|
// Simulate a restApiRoot set in config
|
||||||
|
app.set('restApiRoot', apiRoot || '/api');
|
||||||
|
app.use(app.get('restApiRoot'), loopback.rest());
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getModelFromRemoting(extension, modelName, cb) {
|
|
||||||
extension['resources/' + modelName + 's'](function(err, data) {
|
|
||||||
cb(data.models[modelName]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue