Merge branch 'master' of github.com:strongloop/loopback-explorer into feature/remove-express-dep

This commit is contained in:
Raymond Feng 2015-03-05 21:04:27 -08:00
commit 5377503c8c
19 changed files with 577 additions and 73 deletions

313
CHANGES.md Normal file
View File

@ -0,0 +1,313 @@
2015-02-23, Version 1.7.1
=========================
* Remove unused external font "Droid Sans". (Miroslav Bajtoš)
2015-02-17, Version 1.7.0
=========================
* Made API doc of class use the http.path of the class if available, or the name of the class as a fallback (gandrianakis)
2015-01-09, Version 1.6.4
=========================
* Prevent double slash in the resource URLs (Miroslav Bajtoš)
* Allow `uiDirs` to be defined as a String (Simon Ho)
* Save accessToken in localStorage. Fixes #47 (Samuel Reed)
2015-01-06, Version 1.6.3
=========================
* Fix bad CLA URL in CONTRIBUTING.md (Ryan Graham)
* Add X-UA-Compatible tag (Nick Van Dyck)
2014-12-12, Version 1.6.2
=========================
* Move 200 response to `type` on the operation object. See #75. (Samuel Reed)
2014-12-08, Version 1.6.1
=========================
* Use full lodash instead of lodash components (Ryan Graham)
2014-12-02, Version 1.6.0
=========================
* Remove model name from nickname, swagger spec understands op context. (Samuel Reed)
2014-11-29, Version 1.5.2
=========================
* model-helper: ignore unknown property types (Miroslav Bajtoš)
2014-10-24, Version 1.5.1
=========================
2014-10-24, Version 1.5.0
=========================
* Add an option `uiDirs` (Miroslav Bajtoš)
* swagger: honour X-Forwarded-Proto header (Miroslav Bajtoš)
2014-10-21, Version 1.4.0
=========================
* Bump version (Raymond Feng)
* Add integration tests for included models (Miroslav Bajtoš)
* route-helper: add `responseMessages` (Miroslav Bajtoš)
* model-helper: support anonymous object types (Miroslav Bajtoš)
* swagger: include models from accepts/returns args (Miroslav Bajtoš)
* loopbackStyles: improve spacing in small window (Miroslav Bajtoš)
* swagger: Deprecate `opts.swaggerVersion` (Miroslav Bajtoš)
* swagger: use X-Forwarded-Host for basePath (Miroslav Bajtoš)
* example: use PersistedModel instead of Model (Miroslav Bajtoš)
* models: include model's `description` (Miroslav Bajtoš)
* Refactor conversion of data types (Miroslav Bajtoš)
* Move `convertText` to `typeConverter` (Miroslav Bajtoš)
* Add support for `context` and `res` param types (Krishna Raman)
* package: update devDependencies (Miroslav Bajtoš)
* gitignore: add .idea, *.tgz, *.iml (Miroslav Bajtoš)
* Support multi-line array `description` and `notes` (Miroslav Bajtoš)
* Use `1.0.0` as the default app version. (Miroslav Bajtoš)
* Extend `consumes` and `produces` metadata (Miroslav Bajtoš)
* route-helper: include `notes` and `deprecated` (Miroslav Bajtoš)
* Pull model description from ctor.settings first (Shelby Sanders)
2014-10-08, Version 1.3.0
=========================
* swagger: allow cross-origin requests (Miroslav Bajtoš)
* Sort endpoints by letter. (Samuel Reed)
* Add syntax highlighting styles & highlight threshold. (Samuel Reed)
* Add contribution guidelines (Ryan Graham)
2014-09-22, Version 1.2.11
==========================
* Bump version (Raymond Feng)
* Fix how the array of models is iterated (Raymond Feng)
2014-09-05, Version 1.2.10
==========================
* Bump version (Raymond Feng)
* Make sure nested/referenced models in array are mapped to swagger (Clark Wang)
* Make sure nested/referenced models are mapped to swagger (Raymond Feng)
2014-08-15, Version 1.2.9
=========================
* Bump version (Raymond Feng)
* Newest Swagger UI requires application/x-www-form-urlencoded. (Samuel Reed)
* Use `dist` property from swagger-ui package. (Samuel Reed)
* Fixed undefined modelClass when using polymorphic relations (Navid Nikpour)
2014-08-08, Version 1.2.8
=========================
* Bump version (Raymond Feng)
* Fix the type name for a property if model class is used (Raymond Feng)
2014-08-04, Version 1.2.7
=========================
* Bump version (Raymond Feng)
* Set up default consumes/produces media types (Raymond Feng)
* Fix the default opts (Raymond Feng)
* Add required swagger 1.2 items property for property type array (Ritchie Martori)
* Allow passing a custom protocol. (Samuel Reed)
2014-07-29, Version 1.2.6
=========================
* Bump version (Raymond Feng)
* res.send deprecated - updated to res.status (Geoffroy)
* Remove hidden properties from definition. (Samuel Reed)
2014-07-25, Version 1.2.5
=========================
* Bump version (Raymond Feng)
* Ensure models from relations are included (Raymond Feng)
2014-07-22, Version 1.2.4
=========================
* model-helper: handle arrays with undefined items (Miroslav Bajtoš)
2014-07-22, Version 1.2.3
=========================
* model-helper: handle array types with no item type (Miroslav Bajtoš)
2014-07-20, Version 1.2.2
=========================
* Bump version (Raymond Feng)
* Properly convert complex return types. (Samuel Reed)
2014-07-18, Version 1.2.1
=========================
* Bump version (Raymond Feng)
* Fix up loopback.rest() model definition hack. (Samuel Reed)
2014-07-14, Version 1.2.0
=========================
* Bump version and update deps (Raymond Feng)
* s/accessToken/access_token in authorization key name (Samuel Reed)
* Fix resources if the explorer is at a deep path. (Samuel Reed)
* Fix debug namespace, express version. (Samuel Reed)
* Remove forgotten TODO. (Samuel Reed)
* Simplify `accepts` and `returns` hacks. (Samuel Reed)
* More consise type tests (Samuel Reed)
* Remove preMiddleware. (Samuel Reed)
* Remove swagger.test.js license (Samuel Reed)
* Remove peerDependencies, use express directly. (Samuel Reed)
* Add url-join so path.join() doesn't break windows (Samuel Reed)
* Rename translateKeys to translateDataTypeKeys. (Samuel Reed)
* Refactor route-helper & add tests. (Samuel Reed)
* LDL to Swagger fixes & extensions. (Samuel Reed)
* Use express routes instead of modifying remoting. (Samuel Reed)
* Fix missing strong-remoting devDependency. (Samuel Reed)
* Restore existing styles. (Samuel Reed)
* Allow easy setting of accessToken in explorer UI. (Samuel Reed)
* Refactor key translations between LDL & Swagger. (Samuel Reed)
* Refactoring swagger 1.2 rework. (Samuel Reed)
* Make sure body parameter is shown. (Raymond Feng)
* Some swagger 1.2 migration cleanup. (Samuel Reed)
* Fix api resource path and type ref to models. (Raymond Feng)
* Swagger 1.2 compatability. Moved strong-remoting/ext/swagger to this module. (Samuel Reed)
* Load swagger ui from `swagger-ui` package instead. (Samuel Reed)
2014-05-28, Version 1.1.1
=========================
* package.json: add support for loopback 2.x (Miroslav Bajtoš)
* Make sure X-Powered-By header is disabled (Alex Pica)
* Fix license url (Raymond Feng)
* Update to dual MIT/StrongLoop license (Raymond Feng)
2014-01-14, Version 1.1.0
=========================
* Bump up loopback min version to 1.5 (Miroslav Bajtoš)
* Use `app.get('restApiRoot')` as default basePath (Miroslav Bajtoš)
* Replace strong-remoting ext/swagger with app.docs (Miroslav Bajtoš)
2014-01-13, Version 1.0.2
=========================
* Bump version (Raymond Feng)
* README: mount REST at /api in the sample code (Miroslav Bajtos)
* Reorder middleware to fix unit-test failures. (Miroslav Bajtos)
* Fix loading of loopback dependencies. (Miroslav Bajtos)
2013-12-04, Version 1.0.1
=========================
* First release!

View File

@ -14,7 +14,7 @@ Contributing to `loopback-explorer` is easy. In a few simple steps:
* Adhere to code style outlined in the [Google C++ Style Guide][] and
[Google Javascript Style Guide][].
* Sign the [Contributor License Agreement](https://cla.strongloop.com/strongloop/loopback-explorer)
* Sign the [Contributor License Agreement](https://cla.strongloop.com/agreements/strongloop/loopback-explorer)
* Submit a pull request through Github.

View File

@ -25,7 +25,7 @@ app.listen(port);
## Advanced Usage
Many aspects of the explorer are configurable.
Many aspects of the explorer are configurable.
See [options](#options) for a description of these options:
@ -34,7 +34,10 @@ See [options](#options) for a description of these options:
app.use('/explorer', loopback.basicAuth('user', 'password'));
app.use('/explorer', explorer(app, {
basePath: '/custom-api-root',
swaggerDistRoot: '/swagger',
uiDirs: [
path.resolve(__dirname, 'public'),
path.resolve(__dirname, 'node_modules', 'swagger-ui')
]
apiInfo: {
'title': 'My API',
'description': 'Explorer example app.'
@ -67,27 +70,27 @@ Options are passed to `explorer(app, options)`.
> and thus needs to report its endpoints as `https`, even though incoming traffic is auto-detected
> as `http`.
`swaggerDistRoot`: **String**
`uiDirs`: **Array of Strings**
> Sets a path within your application for overriding Swagger UI files.
> Sets a list of paths within your application for overriding Swagger UI files.
> If present, will search `swaggerDistRoot` first when attempting to load Swagger UI, allowing
> you to pick and choose overrides to the interface. Use this to style your explorer or
> add additional functionality.
> If present, will search `uiDirs` first when attempting to load Swagger UI,
> allowing 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.
> 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
> 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
> 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.

View File

@ -5,7 +5,7 @@
var url = require('url');
var path = require('path');
var urlJoin = require('./lib/url-join');
var _defaults = require('lodash.defaults');
var _defaults = require('lodash').defaults;
var swagger = require('./lib/swagger');
var SWAGGER_UI_ROOT = require('swagger-ui').dist;
var STATIC_ROOT = path.join(__dirname, 'public');
@ -46,15 +46,29 @@ function explorer(loopbackApplication, options) {
});
});
// 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. make changes to index.html without having
// Allow specifying a static file roots for swagger files. Any files in
// these folders will override those in the swagger-ui distribution.
// In this way one could e.g. make changes to index.html without having
// to worry about constantly pulling in JS updates.
if (options.uiDirs) {
if (typeof options.uiDirs === 'string') {
app.use(loopback.static(options.uiDirs));
} else if (Array.isArray(options.uiDirs)) {
options.uiDirs.forEach(function(dir) {
app.use(loopback.static(dir));
});
}
}
if (options.swaggerDistRoot) {
app.use(loopback.static(options.swaggerDistRoot));
console.warn('loopback-explorer: `swaggerDistRoot` is deprecated,' +
' use `uiDirs` instead');
}
// File in node_modules are overridden by a few customizations
app.use(loopback.static(STATIC_ROOT));
// Swagger UI distribution
app.use(loopback.static(SWAGGER_UI_ROOT));

View File

@ -23,11 +23,16 @@ var classHelper = module.exports = {
* @return {Object} API Declaration.
*/
generateAPIDoc: function(aClass, opts) {
var resourcePath = urlJoin('/', aClass.name);
if(aClass.http && aClass.http.path) {
resourcePath = aClass.http.path;
}
return {
apiVersion: opts.version,
swaggerVersion: opts.swaggerVersion,
basePath: opts.basePath,
resourcePath: urlJoin('/', opts.resourcePath),
resourcePath: urlJoin('/', resourcePath),
apis: [],
consumes: aClass.http.consumes || opts.consumes,
produces: aClass.http.produces || opts.produces,

View File

@ -3,8 +3,8 @@
/**
* Module dependencies.
*/
var _cloneDeep = require('lodash.clonedeep');
var _pick = require('lodash.pick');
var _cloneDeep = require('lodash').cloneDeep;
var _pick = require('lodash').pick;
var translateDataTypeKeys = require('./translate-data-type-keys');
var typeConverter = require('./type-converter');
@ -21,8 +21,15 @@ var modelHelper = module.exports = {
*/
generateModelDefinition: function generateModelDefinition(modelClass, definitions) {
var def = modelClass.definition;
var name = def.name;
var out = definitions || {};
if (!def) {
// The model does not have any definition, it was most likely
// created as a placeholder for an unknown property type
return out;
}
var name = def.name;
if (out[name]) {
// The model is already included
return out;

View File

@ -5,8 +5,8 @@
*/
var debug = require('debug')('loopback:explorer:routeHelpers');
var _cloneDeep = require('lodash.clonedeep');
var _assign = require('lodash.assign');
var _cloneDeep = require('lodash').cloneDeep;
var _assign = require('lodash').assign;
var modelHelper = require('./model-helper');
var typeConverter = require('./type-converter');
@ -125,17 +125,9 @@ var routeHelper = module.exports = {
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
message: 'Request was successful'
}];
if (route.errors) {
@ -145,18 +137,21 @@ var routeHelper = module.exports = {
var apiDoc = {
path: routeHelper.convertPathFragments(route.path),
// Create the operation doc.
// Note that we are not calling `extendWithType`, as the response type
// is specified in the first response message.
operations: [{
// We are using extendWithType to use `type` for the top-level (200)
// response type. We use responseModels for error responses.
// see https://github.com/strongloop/loopback-explorer/issues/75
operations: [routeHelper.extendWithType({
method: routeHelper.convertVerb(route.verb),
// [rfeng] Swagger UI doesn't escape '.' for jQuery selector
nickname: route.method.replace(/\./g, '_'),
// [strml] remove leading model name from op, swagger uses leading
// path as class name so it remains unique between models.
// route.method is always #{className}.#{methodName}
nickname: route.method.replace(/.*?\./, ''),
parameters: accepts,
responseMessages: responseMessages,
summary: typeConverter.convertText(route.description),
notes: typeConverter.convertText(route.notes),
deprecated: route.deprecated
}]
}, returns)]
};
return apiDoc;

View File

@ -9,7 +9,7 @@ module.exports = Swagger;
*/
var path = require('path');
var urlJoin = require('./url-join');
var _defaults = require('lodash.defaults');
var _defaults = require('lodash').defaults;
var classHelper = require('./class-helper');
var routeHelper = require('./route-helper');
var modelHelper = require('./model-helper');
@ -98,7 +98,11 @@ function Swagger(loopbackApplication, swaggerApp, opts) {
addTypeToModels(type);
});
addTypeToModels(routeDoc.type);
if (routeDoc.type === 'array') {
addTypeToModels(routeDoc.items.type);
} else {
addTypeToModels(routeDoc.type);
}
routeDoc.responseMessages.forEach(function(msg) {
addTypeToModels(msg.responseModel);
@ -152,6 +156,11 @@ function addRoute(app, uri, doc, opts) {
var hasBasePath = Object.keys(doc).indexOf('basePath') !== -1;
var initialPath = doc.basePath || '';
// Remove the trailing slash, see
// https://github.com/strongloop/loopback-explorer/issues/48
if (initialPath[initialPath.length-1] === '/')
initialPath = initialPath.slice(0, -1);
app.get(urlJoin('/', uri), function(req, res) {
// There's a few forces at play that require this "hack". The Swagger spec
@ -166,9 +175,9 @@ function addRoute(app, uri, doc, opts) {
if (hasBasePath) {
var headers = req.headers;
// NOTE header names (keys) are always all-lowercase
var proto = headers['x-forwarded-proto'] || opts.protocol || req.protocol;
var host = headers['x-forwarded-host'] || headers.host;
doc.basePath = (opts.protocol || req.protocol) + '://' +
host + initialPath;
doc.basePath = proto + '://' + host + initialPath;
}
res.status(200).send(doc);
});

View File

@ -4,7 +4,7 @@
* Module dependencies.
*/
var _cloneDeep = require('lodash.clonedeep');
var _cloneDeep = require('lodash').cloneDeep;
// Keys that are different between LDL and Swagger
var KEY_TRANSLATIONS = {

View File

@ -1,6 +1,6 @@
{
"name": "loopback-explorer",
"version": "1.3.0",
"version": "1.7.1",
"description": "Browse and test your LoopBack app's APIs",
"main": "index.js",
"scripts": {
@ -23,7 +23,7 @@
"devDependencies": {
"loopback": "^2.14.0",
"mocha": "^2.1.0",
"supertest": "~0.15.0",
"supertest": "^0.15.0",
"chai": "^2.1.1"
},
"license": {
@ -33,10 +33,7 @@
"dependencies": {
"cors": "^2.5.3",
"debug": "~2.1.2",
"lodash.assign": "^3.0.0",
"lodash.clonedeep": "^3.0.0",
"lodash.defaults": "^3.0.0",
"lodash.pick": "^3.0.0",
"lodash": "^3.0.0",
"swagger-ui": "~2.0.24"
}
}

View File

@ -1,12 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>StrongLoop API Explorer</title>
<link href='https://fonts.googleapis.com/css?family=Droid+Sans:400,700' rel='stylesheet' type='text/css'/>
<link href='css/reset.css' media='screen' rel='stylesheet' type='text/css'/>
<link href='css/screen.css' media='screen' rel='stylesheet' type='text/css'/>
<link href='css/reset.css' media='print' rel='stylesheet' type='text/css'/>
<link href='css/screen.css' media='print' rel='stylesheet' type='text/css'/>
<link href='css/reset.css' media='screen,print' rel='stylesheet' type='text/css'/>
<link href='css/screen.css' media='screen,print' rel='stylesheet' type='text/css'/>
<link href='css/loopbackStyles.css' rel='stylesheet' type='text/css'/>
<script type="text/javascript" src="lib/shred.bundle.js"></script>
<script src='lib/jquery-1.8.0.min.js' type='text/javascript'></script>

View File

@ -3,9 +3,10 @@
// Refactoring of inline script from index.html.
/*global SwaggerUi, log, ApiKeyAuthorization, hljs, window, $ */
$(function() {
var lsKey = 'swagger_accessToken';
$.getJSON('config.json', function(config) {
log(config);
loadSwaggerUi(config);
log(config);
loadSwaggerUi(config);
});
var accessToken;
@ -35,6 +36,14 @@ $(function() {
$('#api_selector').submit(setAccessToken);
$('#input_accessToken').keyup(onInputChange);
// Recover accessToken from localStorage if present.
if (window.localStorage) {
var key = window.localStorage.getItem(lsKey);
if (key) {
$('#input_accessToken').val(key).submit();
}
}
window.swaggerUi.load();
}
@ -49,6 +58,11 @@ $(function() {
accessToken = key;
$('.accessTokenDisplay').text('Token Set.').addClass('set');
$('.accessTokenDisplay').attr('data-tooltip', 'Current Token: ' + key);
// Save this token to localStorage if we can to make it persist on refresh.
if (window.localStorage) {
window.localStorage.setItem(lsKey, key);
}
}
}

View File

@ -2,7 +2,7 @@
var classHelper = require('../lib/class-helper');
var expect = require('chai').expect;
var _defaults = require('lodash.defaults');
var _defaults = require('lodash').defaults;
describe('class-helper', function() {
it('joins array descriptions', function() {
@ -12,12 +12,32 @@ describe('class-helper', function() {
expect(doc.description).to.equal('line1\nline2');
});
it('sets resourcePath from aClass.http.path', function() {
var doc = generateAPIDoc({}, 'otherPath');
expect(doc.resourcePath).to.equal('/otherPath');
});
it('sets resourcePath from aClass.name', function() {
var doc = generateAPIDoc({});
expect(doc.resourcePath).to.equal('/test');
});
});
// Easy wrapper around createRoute
function generateResourceDocAPIEntry(def) {
return classHelper.generateResourceDocAPIEntry(_defaults(def, {
http: { path: '/test' },
ctor: { settings: { } },
}));
return classHelper.generateResourceDocAPIEntry(_defaults(def, {
http: { path: '/test' },
ctor: { settings: { } }
}));
}
function generateAPIDoc(def, httpPath) {
return classHelper.generateAPIDoc(_defaults(def, {
http: { path: httpPath || null },
name: 'test',
ctor: { settings: { } }
}), {resourcePath: 'resources'});
}

View File

@ -2,7 +2,10 @@ var loopback = require('loopback');
var explorer = require('../');
var request = require('supertest');
var assert = require('assert');
var path = require('path');
var expect = require('chai').expect;
var urlJoin = require('../lib/url-join');
var os = require('os');
describe('explorer', function() {
@ -24,7 +27,7 @@ describe('explorer', function() {
.end(function(err, res) {
if (err) throw err;
assert(!!~res.text.indexOf('<title>StrongLoop API Explorer</title>'),
assert(!!~res.text.indexOf('<title>StrongLoop API Explorer</title>'),
'text does not contain expected string');
done();
});
@ -77,6 +80,83 @@ describe('explorer', function() {
done();
});
});
it('removes trailing slash from baseUrl', function(done) {
// SwaggerUI builds resource URL by concatenating basePath + resourcePath
// Since the resource paths are always startign with a slash,
// if the basePath ends with a slash too, an incorrect URL is produced
var app = loopback();
app.set('restApiRoot', '/');
configureRestApiAndExplorer(app);
request(app)
.get('/explorer/resources/products')
.expect(200)
.end(function(err, res) {
if (err) return done(err);
var baseUrl = res.body.basePath;
var apiPath = res.body.apis[0].path;
expect(baseUrl + apiPath).to.match(/http:\/\/[^\/]+\/products/);
done();
});
});
});
describe('with custom front-end files', function() {
var app;
beforeEach(function setupExplorerWithUiDirs() {
app = loopback();
app.use('/explorer', explorer(app, {
uiDirs: [ path.resolve(__dirname, 'fixtures', 'dummy-swagger-ui') ]
}));
});
it('overrides swagger-ui files', function(done) {
request(app).get('/explorer/swagger-ui.js')
.expect(200)
// expect the content of `dummy-swagger-ui/swagger-ui.js`
.expect('/* custom swagger-ui file */' + os.EOL)
.end(done);
});
it('overrides strongloop overrides', function(done) {
request(app).get('/explorer/')
.expect(200)
// expect the content of `dummy-swagger-ui/index.html`
.expect('custom index.html' + os.EOL)
.end(done);
});
});
describe('when specifying custom static file root directories', function() {
var app;
beforeEach(function() {
app = loopback();
});
it('should allow `uiDirs` to be defined as an Array', function(done) {
app.use('/explorer', explorer(app, {
uiDirs: [ path.resolve(__dirname, 'fixtures', 'dummy-swagger-ui') ]
}));
request(app).get('/explorer/')
.expect(200)
// expect the content of `dummy-swagger-ui/index.html`
.expect('custom index.html' + os.EOL)
.end(done);
});
it('should allow `uiDirs` to be defined as an String', function(done) {
app.use('/explorer', explorer(app, {
uiDirs: path.resolve(__dirname, 'fixtures', 'dummy-swagger-ui')
}));
request(app).get('/explorer/')
.expect(200)
// expect the content of `dummy-swagger-ui/index.html`
.expect('custom index.html' + os.EOL)
.end(done);
});
});
function givenLoopBackAppWithExplorer(explorerBase) {
@ -88,7 +168,7 @@ describe('explorer', function() {
}
function configureRestApiAndExplorer(app, explorerBase) {
var Product = loopback.Model.extend('product');
var Product = loopback.PersistedModel.extend('product');
Product.attachTo(loopback.memory());
app.model(Product);

View File

@ -0,0 +1 @@
custom index.html

View File

@ -0,0 +1 @@
/* custom swagger-ui file */

View File

@ -1,7 +1,7 @@
'use strict';
var modelHelper = require('../lib/model-helper');
var _defaults = require('lodash.defaults');
var _defaults = require('lodash').defaults;
var loopback = require('loopback');
var expect = require('chai').expect;
@ -198,6 +198,18 @@ describe('model-helper', function() {
expect(Object.keys(defs)).has.property('length', 1);
});
// https://github.com/strongloop/loopback-explorer/issues/71
it('should skip unknown types', function() {
var Model8 = loopback.createModel('Model8', {
patient: {
model: 'physician',
type: 'hasMany',
through: 'appointment'
}
});
var defs = modelHelper.generateModelDefinition(Model8, {});
expect(Object.keys(defs)).to.not.contain('hasMany');
});
});
describe('hidden properties', function() {

View File

@ -2,7 +2,7 @@
var routeHelper = require('../lib/route-helper');
var expect = require('chai').expect;
var _defaults = require('lodash.defaults');
var _defaults = require('lodash').defaults;
describe('route-helper', function() {
it('returns "object" when a route has multiple return values', function() {
@ -13,8 +13,8 @@ describe('route-helper', function() {
{ arg: 'avg', type: 'number' }
]
});
expect(doc.operations[0].type).to.equal(undefined);
expect(getResponseType(doc.operations[0])).to.equal('object');
expect(doc.operations[0].type).to.equal('object');
expect(getResponseType(doc.operations[0])).to.equal(undefined);
});
it('converts path params when they exist in the route name', function() {
@ -61,12 +61,22 @@ describe('route-helper', function() {
]
});
var opDoc = doc.operations[0];
// Note: swagger-ui treat arrays of X the same way as object X
expect(getResponseType(opDoc)).to.equal('customType');
expect(getResponseType(opDoc)).to.equal(undefined);
// 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'});
expect(opDoc.type).to.equal('array');
expect(opDoc.items).to.eql({type: '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');
});
it('includes `notes` metadata', function() {
@ -149,11 +159,11 @@ describe('route-helper', function() {
var doc = createAPIDoc({
returns: [{ name: 'result', type: 'object', root: true }]
});
expect(doc.operations[0].type).to.eql('object');
expect(doc.operations[0].responseMessages).to.eql([
{
code: 200,
message: 'Request was successful',
responseModel: 'object'
message: 'Request was successful'
}
]);
});
@ -162,11 +172,11 @@ describe('route-helper', function() {
var doc = createAPIDoc({
returns: []
});
expect(doc.operations[0].type).to.eql('void');
expect(doc.operations[0].responseMessages).to.eql([
{
code: 204,
message: 'Request was successful',
responseModel: 'void'
message: 'Request was successful'
}
]);
});
@ -185,11 +195,24 @@ describe('route-helper', function() {
responseModel: 'ValidationError'
});
});
it('route nickname does not include model name.', function() {
var doc = createAPIDoc();
expect(doc.operations[0].nickname).to.equal('get');
});
it('route nickname with a period is shorted correctly', function() {
// Method is built by remoting to always be #{className}.#{methodName}
var doc = createAPIDoc({
method: 'test.get.me'
});
expect(doc.operations[0].nickname).to.eql('get.me');
});
});
// Easy wrapper around createRoute
function createAPIDoc(def) {
return routeHelper.routeToAPIDoc(_defaults(def, {
return routeHelper.routeToAPIDoc(_defaults(def || {}, {
path: '/test',
verb: 'GET',
method: 'test.get'

View File

@ -92,6 +92,18 @@ describe('swagger definition', function() {
done();
});
});
it('respects X-Forwarded-Proto header (behind a proxy)', function(done) {
var app = givenAppWithSwagger();
getAPIDeclaration(app, 'products')
.set('X-Forwarded-Proto', 'https')
.end(function(err, res) {
if (err) return done(err);
var baseUrl = url.parse(res.body.basePath);
expect(baseUrl.protocol).to.equal('https:');
done();
});
});
});
describe('Model definition attributes', function() {