├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── auth ├── google-jwt.js └── google-oauth.js ├── bin ├── blueoak-server.js ├── decrypt.js └── encrypt.js ├── defaults.json ├── examples ├── custom-auth │ ├── auth │ │ ├── get-age.js │ │ └── my-custom-auth.js │ ├── config │ │ ├── default.json │ │ └── routes.json │ ├── handlers │ │ └── test.js │ ├── index.js │ ├── nodemon.json │ └── package.json ├── custom-logging │ ├── config │ │ └── default.json │ ├── index.js │ └── logger.js ├── ext-modules │ ├── config │ │ └── default.json │ ├── index.js │ ├── nodemon.json │ └── package.json ├── fileupload │ ├── config │ │ ├── default.json │ │ ├── default_custom_disk.json │ │ └── default_memory_storage.json │ ├── handlers │ │ └── upload.js │ ├── index.js │ ├── nodemon.json │ ├── services │ │ └── multerService.js │ ├── static │ │ └── index.html │ ├── swagger │ │ └── upload.json │ └── uploads │ │ └── uploads.end.up.here.txt ├── google-auth │ ├── client │ │ ├── README │ │ └── index.html │ ├── config │ │ ├── default.json │ │ └── routes.json │ ├── handlers │ │ ├── test.js │ │ └── test2.js │ ├── index.js │ ├── nodemon.json │ └── package.json ├── kitchensink │ ├── README.md │ ├── config │ │ ├── default.json │ │ ├── production.json │ │ └── routes.json │ ├── handlers │ │ ├── foo.js │ │ └── hello.js │ ├── index.js │ ├── middleware │ │ └── errors.js │ ├── nodemon.json │ ├── package.json │ └── services │ │ └── randomizer.js ├── packages │ ├── echo-service │ │ ├── index.js │ │ └── package.json │ └── my-handler │ │ ├── index.js │ │ └── package.json ├── redis │ ├── config │ │ └── default.json │ ├── handlers │ │ └── main.js │ └── index.js ├── ssl │ ├── certs │ │ ├── server.crt │ │ └── server.key │ ├── config │ │ └── default.json │ ├── handlers │ │ └── secure.js │ └── index.js └── swagger │ ├── config │ └── default.json │ ├── handlers │ ├── api-v1.js │ ├── petstore.js │ └── uber-v1.js │ ├── index.js │ ├── middleware │ └── errors.js │ ├── nodemon.json │ └── swagger │ ├── api-v1.yaml │ ├── petstore.json │ ├── public │ ├── definitions │ │ ├── Contact.yaml │ │ ├── CuriousPerson.yaml │ │ ├── EnthusiasticPerson.yaml │ │ ├── OtherPerson.yaml │ │ ├── Person.yaml │ │ └── SuperFunTime.yaml │ ├── parameters │ │ └── PathId.yaml │ ├── paths.yaml │ └── paths │ │ └── superfuntime-{id}.yaml │ ├── uber-v1.yaml │ └── v1-assets │ ├── Tags.yaml │ ├── definitions │ ├── Activities.yaml │ ├── Activity.yaml │ ├── Error.yaml │ ├── FoodProduct.yaml │ ├── Label.yaml │ ├── PriceEstimate.yaml │ ├── Product.yaml │ ├── Profile.yaml │ ├── ToyLabel.yaml │ └── ToyProduct.yaml │ └── paths │ ├── estimates.price.yaml │ ├── estimates.time.yaml │ ├── history.yaml │ ├── me.yaml │ └── products.yaml ├── handlers ├── _routes.js └── _swagger.js ├── index.js ├── lib ├── dependencyCalc.js ├── di.js ├── loader.js ├── nodeCacheInterface.js ├── project.js ├── security.js ├── subRequire.js ├── swaggerRefCompiler.js └── swaggerUtil.js ├── middleware ├── _errors.js ├── body-parser.js ├── cors.js ├── csrf.js ├── express-monitor.js ├── express-static.js └── session.js ├── package-lock.json ├── package.json ├── services ├── auth.js ├── cache.js ├── config.js ├── express.js ├── logger.js ├── middleware.js ├── monitor.js ├── postMiddleware.js ├── redis.js ├── stats.js ├── swagger.js └── system.js ├── test ├── .eslintrc ├── integration │ ├── fixtures │ │ ├── manual-server-starter.js │ │ ├── server1 │ │ │ ├── config │ │ │ │ └── default.json │ │ │ └── handlers │ │ │ │ └── handler1.js │ │ ├── server10 │ │ │ ├── config │ │ │ │ └── default.json │ │ │ └── handlers │ │ │ │ └── test.handler.js │ │ ├── server11 │ │ │ ├── config │ │ │ │ └── default.json │ │ │ ├── handlers │ │ │ │ ├── foo.js │ │ │ │ └── petstore.js │ │ │ ├── node_modules │ │ │ │ ├── dummy-middleware │ │ │ │ │ └── index.js │ │ │ │ └── test-post-middleware │ │ │ │ │ └── index.js │ │ │ └── swagger │ │ │ │ ├── petstore.json │ │ │ │ └── petstore2.json │ │ ├── server2 │ │ │ ├── config │ │ │ │ ├── default.json │ │ │ │ └── routes.json │ │ │ └── handlers │ │ │ │ ├── camelCase.js │ │ │ │ └── handler2.js │ │ ├── server3 │ │ │ ├── config │ │ │ │ └── default.json │ │ │ └── handlers │ │ │ │ └── handler.js │ │ ├── server4 │ │ │ ├── config │ │ │ │ └── default.json │ │ │ ├── handlers │ │ │ │ ├── petstore.js │ │ │ │ ├── petstore2.js │ │ │ │ └── petstore3.js │ │ │ └── swagger │ │ │ │ ├── petstore.json │ │ │ │ ├── petstore2.json │ │ │ │ ├── petstore3.json │ │ │ │ └── petstore4.json │ │ ├── server5 │ │ │ ├── config │ │ │ │ ├── default.json │ │ │ │ └── test-response-validation-errors.json │ │ │ ├── handlers │ │ │ │ ├── foo.js │ │ │ │ └── petstore.js │ │ │ └── swagger │ │ │ │ ├── petstore.json │ │ │ │ └── petstore2.json │ │ ├── server6 │ │ │ ├── config │ │ │ │ └── default.json │ │ │ └── services │ │ │ │ └── logger.js │ │ ├── server7 │ │ │ ├── config │ │ │ │ └── default.json │ │ │ ├── handlers │ │ │ │ ├── index.js │ │ │ │ └── petstore.js │ │ │ └── swagger │ │ │ │ ├── definitions │ │ │ │ ├── User.yaml │ │ │ │ └── index.yaml │ │ │ │ ├── index.yaml │ │ │ │ ├── info │ │ │ │ └── index.yaml │ │ │ │ ├── paths │ │ │ │ ├── bar.yaml │ │ │ │ ├── foo.yaml │ │ │ │ └── index.yaml │ │ │ │ └── petstore.yaml │ │ ├── server8 │ │ │ ├── common │ │ │ │ └── swagger │ │ │ │ │ ├── petstore.json │ │ │ │ │ └── petstore2.json │ │ │ └── server │ │ │ │ ├── config │ │ │ │ └── default.json │ │ │ │ ├── handlers │ │ │ │ ├── foo.js │ │ │ │ └── petstore.js │ │ │ │ └── pet.txt │ │ └── server9 │ │ │ ├── config │ │ │ └── default.json │ │ │ └── services │ │ │ └── test.service.js │ ├── launchUtil.js │ ├── testDeclarativeRoutes.js │ ├── testHandlers.js │ ├── testLoader.js │ ├── testSession.js │ └── testSwagger.js └── unit │ ├── data │ └── example.json │ ├── fixtures │ ├── config │ │ ├── config │ │ │ ├── cluster.json │ │ │ └── default.json │ │ └── timeout │ │ │ └── config │ │ │ └── default.json │ ├── loader │ │ ├── test1 │ │ │ ├── service1.js │ │ │ └── service2.js │ │ ├── test10 │ │ │ ├── service18.js │ │ │ └── service19.js │ │ ├── test11 │ │ │ ├── consumers │ │ │ │ └── dir1 │ │ │ │ │ └── consumer5.js │ │ │ └── services │ │ │ │ └── dir2 │ │ │ │ ├── dir3 │ │ │ │ └── service22.js │ │ │ │ └── service21.js │ │ ├── test2 │ │ │ ├── service3.js │ │ │ └── service4.js │ │ ├── test3 │ │ │ ├── service5.js │ │ │ ├── service6.js │ │ │ └── service7.js │ │ ├── test4 │ │ │ ├── service8.js │ │ │ └── service9.js │ │ ├── test5 │ │ │ ├── service10.js │ │ │ └── service11.js │ │ ├── test6 │ │ │ ├── badConsumers │ │ │ │ ├── consumer1.js │ │ │ │ └── consumer2.js │ │ │ ├── consumers │ │ │ │ ├── consumer1.js │ │ │ │ └── consumer2.js │ │ │ ├── otherConsumers │ │ │ │ └── consumer1.js │ │ │ └── services │ │ │ │ └── service20.js │ │ ├── test7 │ │ │ ├── service12.js │ │ │ ├── service13.js │ │ │ ├── service14.js │ │ │ └── service15.js │ │ └── test8 │ │ │ ├── service-one.js │ │ │ └── service15.js │ ├── logger │ │ ├── logger.js │ │ └── transports │ │ │ └── transport.js │ └── subrequire │ │ ├── test1 │ │ └── test.js │ │ ├── test2 │ │ └── node_modules │ │ │ └── test │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── test3 │ │ └── node_modules │ │ │ └── express │ │ │ ├── index.js │ │ │ └── package.json │ │ └── test4 │ │ └── node_modules │ │ └── test │ │ ├── index.js │ │ ├── node_modules │ │ └── express │ │ │ ├── index.js │ │ │ └── package.json │ │ └── package.json │ ├── testCaching.js │ ├── testConfig.js │ ├── testDependencyCalc.js │ ├── testLoader.js │ ├── testLogger.js │ ├── testRefCompiler.js │ ├── testSecurity.js │ ├── testSubRequire.js │ ├── testSwaggerModelPolymorphism.js │ ├── testSwaggerParameterValidation.js │ ├── testSwaggerService.js │ └── testTestUtil.js ├── testlib ├── mocks │ ├── config.js │ ├── logger.js │ └── monitor.js └── util.js └── tsd.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = false 7 | 8 | [*.js] 9 | indent_style = space 10 | indent_size = 4 11 | 12 | [*.{json,yaml}] 13 | indent_style = space 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | test/unit/**/node_modules -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "node": true 5 | }, 6 | "globals": { 7 | // `false` means overwriting is not allowed 8 | "services": false 9 | }, 10 | "plugins":[ 11 | "lodash" 12 | ], 13 | "extends": [ 14 | "eslint:recommended", 15 | "plugin:lodash/canonical" 16 | ], 17 | "rules": { 18 | // 4-space indentation 19 | "indent": 2, 20 | // Require semicolons 21 | "semi": [2, "always"], 22 | // Require strings to use single quotes 23 | "quotes": [2, "single"], 24 | // Require curly braces for all control statements 25 | "curly": 2, 26 | // Disallow using variables before they've been defined (allow functions) 27 | "no-use-before-define": [2, "nofunc"], 28 | // Allow any case for variable naming 29 | "camelcase": 0, 30 | // Disallow unused variables, except as function arguments 31 | "no-unused-vars": [2, {"args":"none"}], 32 | // Use if () { } 33 | // ^ space 34 | "keyword-spacing": 2, 35 | // Use if () { } 36 | // ^ space 37 | "space-before-blocks": [2, "always"], 38 | // allow use of synchronous methods - TODO: Remove sync methods from code 39 | "no-sync": 0, 40 | // disallow string concatenation with __dirname and __filename 41 | "no-path-concat": 1, 42 | // disallow use of new operator with the require function 43 | "no-new-require": 1, 44 | // disallow mixing regular variable and require declarations 45 | "no-mixed-requires": [1, false], 46 | // enforce return after a callback 47 | "callback-return": 1, 48 | // enforces error handling in callbacks (node environment) 49 | "handle-callback-err": 1, 50 | "no-console": 0, 51 | "max-len": [2, { "code": 120 }] 52 | } 53 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | typings/ 3 | coverage/ 4 | tests-report.xml 5 | !test/unit/**/node_modules 6 | !test/integration/**/node_modules 7 | .idea 8 | .vscode/ 9 | *.swp 10 | *.swo 11 | *.log 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | - "6" 5 | - "8" 6 | - "10" 7 | - "12" 8 | sudo: false 9 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of 4 | fostering an open and welcoming community, we pledge to respect all people who 5 | contribute through reporting issues, posting feature requests, updating 6 | documentation, submitting pull requests or patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free 9 | experience for everyone, regardless of level of experience, gender, gender 10 | identity and expression, sexual orientation, disability, personal appearance, 11 | body size, race, ethnicity, age, religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic 20 | addresses, without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or 24 | reject comments, commits, code, wiki edits, issues, and other contributions 25 | that are not aligned to this Code of Conduct, or to ban temporarily or 26 | permanently any contributor for other behaviors that they deem inappropriate, 27 | threatening, offensive, or harmful. 28 | 29 | By adopting this Code of Conduct, project maintainers commit themselves to 30 | fairly and consistently applying these principles to every aspect of managing 31 | this project. Project maintainers who do not follow or enforce the Code of 32 | Conduct may be permanently removed from the project team. 33 | 34 | This Code of Conduct applies both within project spaces and in public spaces 35 | when an individual is representing the project or its community. 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 38 | reported by contacting a project maintainer at patrick.wolf@pointsource.com. All 39 | complaints will be reviewed and investigated and will result in a response that 40 | is deemed necessary and appropriate to the circumstances. Maintainers are 41 | obligated to maintain confidentiality with regard to the reporter of an 42 | incident. 43 | 44 | 45 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 46 | version 1.3.0, available at 47 | [http://contributor-covenant.org/version/1/3/0/][version] 48 | 49 | [homepage]: http://contributor-covenant.org 50 | [version]: http://contributor-covenant.org/version/1/3/0/ 51 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to BlueOak Server 2 | We welcome contributions to BlueOak Server. Follow this guide for reporting bugs or making code changes. 3 | 4 | ## Bugs 5 | Bugs can be handled by submitting pull requests or opening issues in our GitHub Repository. 6 | 7 | ## Pull Requests 8 | * Pull requests must follow style guidelines. Run `npm run lint` to validate style. 9 | * Ensure tests pass, both unit tests (`npm test`) and functional tests (`npm run test-integration`). 10 | * Add new tests as necessary. 11 | * [Pull requests](http://help.github.com/send-pull-requests/) should be made to the master branch. 12 | 13 | # Code of Conduct 14 | Always follow the [code of conduct](CODE_OF_CONDUCT.md). 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2015-2016 PointSource, LLC. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /auth/google-jwt.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable lodash/prefer-lodash-method */ 2 | /* 3 | * Copyright (c) 2015-2016 PointSource, LLC. 4 | * MIT Licensed 5 | */ 6 | 7 | var request = require('request'), 8 | jwt = require('jsonwebtoken'), 9 | base64 = require('base64url'), 10 | getPem = require('rsa-pem-from-mod-exp'); 11 | 12 | var _discovery = null, _certs, _pems = {}; 13 | var _cfg = null; 14 | 15 | module.exports.init = function (app, logger, config, auth, callback) { 16 | 17 | var cfg = config.get('google-jwt'); 18 | _cfg = cfg; 19 | if (cfg.clientId) { 20 | 21 | logger.warn('Using experimental google-jwt auth'); 22 | 23 | //get the discovery data, which we need to look up certificates 24 | request.get('https://accounts.google.com/.well-known/openid-configuration', function (err, resp, body) { 25 | if (err) { 26 | return callback(err); 27 | } 28 | 29 | if (resp.statusCode !== 200) { 30 | return callback(new Error('Could not access google discovery service.')); 31 | } 32 | 33 | _discovery = JSON.parse(body); 34 | 35 | //Download the certificates 36 | request.get(_discovery.jwks_uri, function (err, resp, body) { 37 | if (err) { 38 | return callback(err); 39 | } 40 | 41 | if (resp.statusCode !== 200) { 42 | return callback(new Error('Could not access google certs.')); 43 | } 44 | 45 | _certs = JSON.parse(body); 46 | 47 | //convert the jwks to pem certificates 48 | _certs.keys.forEach(function (key) { 49 | _pems[key.kid] = getPem(key.n, key.e); 50 | }); 51 | callback(); 52 | }); 53 | }); 54 | } else { 55 | return callback(); //nothing to do 56 | } 57 | 58 | }; 59 | 60 | //either return token from the Bearer, Bearer , or false if malformed 61 | function extractToken(token) { 62 | var regex = /^Bearer\s(\S+)$/i; 63 | var result = regex.exec(token); 64 | return result ? result[1] : false; 65 | } 66 | 67 | module.exports.authenticate = function (req, res, next) { 68 | var bearerToken = req.headers['authorization']; 69 | if (bearerToken) { 70 | 71 | var bearer = extractToken(bearerToken); 72 | if (!bearer) { 73 | return res.sendStatus(400); //bad request, malformed bearer 74 | } 75 | 76 | //we need to decode just the header of the jwt so that we can figure out the kid 77 | //TODO: Validate that bearer token is well formed before trying to split 78 | var parts = bearer.split('.'); 79 | try { 80 | var header = JSON.parse(base64.decode(parts[0])); 81 | var kid = header.kid; 82 | 83 | //with the kid, we can look up the corresponding pem 84 | var pem = _pems[kid]; 85 | 86 | //for some reason, the iss can be either accounts.google.com or https://accounts.google.com 87 | //let's go ahead and use the one from the token itself 88 | var payload = JSON.parse(base64.decode(parts[1])); 89 | var iss = _discovery.issuer; 90 | if (payload.iss === 'accounts.google.com') { 91 | iss = 'accounts.google.com'; 92 | } 93 | 94 | jwt.verify(bearer, pem, { 95 | issuer: iss, 96 | algorithms: _discovery.id_token_signing_alg_values_supported, 97 | audience: _cfg.clientId 98 | }, function (err, decoded) { 99 | //console.log(decoded) 100 | if (err) { 101 | return res.status(401).send(err.message); 102 | } 103 | req.user = { 104 | email: decoded.email, 105 | id: decoded.sub 106 | }; 107 | next(); 108 | }); 109 | } catch (err) { 110 | //invalid token 111 | res.sendStatus(401); 112 | } 113 | } else { 114 | res.sendStatus(401); 115 | } 116 | }; 117 | -------------------------------------------------------------------------------- /bin/blueoak-server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * Copyright (c) 2015-2016 PointSource, LLC. 4 | * MIT Licensed 5 | */ 6 | var server = require('../'); 7 | 8 | server.init({ 9 | appDir: process.cwd() 10 | }, function(err) { 11 | if (err) { 12 | console.warn('Startup failed', err); 13 | } else { 14 | var logger = this.services.get('logger'); 15 | logger.info('Server started'); 16 | } 17 | 18 | }); 19 | -------------------------------------------------------------------------------- /bin/decrypt.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * Copyright (c) 2015-2016 PointSource, LLC. 4 | * MIT Licensed 5 | */ 6 | 7 | var security = require('../lib/security'); 8 | 9 | if (process.argv.length === 4) { 10 | try { 11 | console.log(decrypt(process.argv[3], process.argv[2])); 12 | } catch (err) { 13 | console.warn(err.message); 14 | } 15 | } else { 16 | printHelp(); 17 | } 18 | 19 | function printHelp() { 20 | console.log('Decodes text from the blueoak-server config files.'); 21 | console.log('Usage: decrypt.js \n'); 22 | console.log('Encoded text should be of the form {}text='); 23 | } 24 | 25 | function decrypt(text, key) { 26 | return security.decrypt(text, key); 27 | } 28 | -------------------------------------------------------------------------------- /bin/encrypt.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * Copyright (c) 2015-2016 PointSource, LLC. 4 | * MIT Licensed 5 | */ 6 | 7 | var crypto = require('crypto'); 8 | var defaultCipher = 'aes-256-cbc'; 9 | 10 | if (process.argv.length === 4 && process.argv[2] !== '-c') { 11 | //use default cipher 12 | console.log(encrypt(process.argv[3], process.argv[2], defaultCipher)); 13 | } else if (process.argv.length === 6 && process.argv[2] === '-c') { 14 | try { 15 | console.log(encrypt(process.argv[5], process.argv[4], process.argv[3])); 16 | } catch (err) { 17 | console.warn('Error'); 18 | } 19 | } else { 20 | printHelp(); 21 | } 22 | 23 | function printHelp() { 24 | console.log('Encodes text for use in the blueoak-server config files.'); 25 | console.log('Usage: encrypt.js [options] key text\n'); 26 | console.log('Options:'); 27 | console.log('\t-c \tCipher to use\n'); 28 | console.log('Available ciphers:'); 29 | 30 | var ciphers = crypto.getCiphers(); 31 | 32 | for (var i = 0; i < ciphers.length; i+=4 ) { 33 | console.log(' ' + pad(ciphers[i]) + pad(ciphers[i + 1]) + pad(ciphers[i + 2]) + pad(ciphers[i + 3]) ); 34 | } 35 | console.log('* denotes default cipher'); 36 | } 37 | 38 | function pad(str) { 39 | if (!str) { 40 | return ''; 41 | } 42 | if (str === defaultCipher) { 43 | str = '*' + str + '*'; 44 | } 45 | while (str.length < 23) { 46 | str = str + ' '; 47 | } 48 | return str; 49 | } 50 | 51 | function encrypt(text, key, cipherType) { 52 | var cipher = crypto.createCipher(cipherType, key); 53 | var crypted = cipher.update(text, 'utf8', 'hex'); 54 | crypted += cipher.final('hex'); 55 | return '{' + cipherType + '}' + crypted + '='; 56 | } 57 | -------------------------------------------------------------------------------- /defaults.json: -------------------------------------------------------------------------------- 1 | { 2 | "logger": { 3 | "levels": { 4 | "silly": 0, 5 | "debug": 1, 6 | "verbose": 2, 7 | "info": 3, 8 | "warn": 4, 9 | "error": 5 10 | }, 11 | "colors": { 12 | "silly": "magenta", 13 | "debug": "cyan", 14 | "verbose": "blue", 15 | "info": "green", 16 | "warn": "yellow", 17 | "error": "red" 18 | }, 19 | "transports": [ 20 | { 21 | "package": "winston", 22 | "field": "transports.Console", 23 | "options": { 24 | "level": "debug", 25 | "colorize": true 26 | } 27 | } 28 | ], 29 | "showLocation": true 30 | }, 31 | "crashDump": { 32 | "enable": false, 33 | "length": 1000, 34 | "type": "console" 35 | }, 36 | "cluster": { 37 | "maxWorkers": 1 38 | }, 39 | "session": { 40 | "type": "cookie" 41 | }, 42 | "express": { 43 | "port": 3000 44 | }, 45 | "monitor": { 46 | "port": 8125 //statsd port 47 | }, 48 | "express-monitor": { 49 | "prefix": "express" 50 | }, 51 | "swagger": { 52 | "context": "/swagger", 53 | "serve": true, 54 | "useLocalhost": true, 55 | "polymorphicValidation": "on", 56 | "refCompiler": "off", 57 | "validateResponseModels": false 58 | }, 59 | "node": { 60 | "version": { 61 | "min": ">=6.9.4", 62 | "recommended": "https://github.com/nodejs/Release#release-schedule" 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /examples/custom-auth/auth/get-age.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable lodash/prefer-noop */ 2 | /* 3 | * Copyright (c) 2015-2016 PointSource, LLC. 4 | * MIT Licensed 5 | */ 6 | exports.init = function (app) { 7 | 8 | }; 9 | 10 | //simulates what could be a call to look up some additional profile data 11 | exports.authenticate = function (req, res, next) { 12 | if (req.user) { 13 | req.user.profile = {age: Math.floor((Math.random() * 80) + 1)}; 14 | } 15 | next(); 16 | }; -------------------------------------------------------------------------------- /examples/custom-auth/auth/my-custom-auth.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | /** 6 | * Custom auth system for demostrative purposes. 7 | * 8 | * To login, do a GET to /login. Pass the name query parameter to control your name, or leave blank for anonymous 9 | * Use /logout to log out 10 | * All routes will require you to be logged in or else you'll get a 401. 11 | * @param app 12 | */ 13 | exports.init = function (app) { 14 | 15 | app.get('/login', function (req, res) { 16 | var username = req.query.name || 'anonymous'; 17 | req.session.auth = { 18 | id: username 19 | }; 20 | res.status(200).send('You logged in as ' + username); 21 | }); 22 | 23 | app.get('/logout', function (req, res) { 24 | var username = 'anonymous'; 25 | if (req.session.auth) { 26 | username = req.session.auth.id; 27 | } 28 | 29 | delete req.session.auth; 30 | 31 | res.status(200).send('You logged out as ' + username); 32 | }); 33 | }; 34 | 35 | exports.authenticate = function (req, res, next) { 36 | if (req.session.auth) { 37 | req.user = { 38 | id: req.session.auth.id 39 | }; 40 | } else { 41 | return res.status(401).send('Log in first at /login'); 42 | } 43 | next(); 44 | }; -------------------------------------------------------------------------------- /examples/custom-auth/config/default.json: -------------------------------------------------------------------------------- 1 | //Default config for my app 2 | { 3 | "express": { 4 | "port": "3000", 5 | "middleware": ["csrf", "cors", "session"] 6 | }, 7 | "cluster": { 8 | "maxWorkers": 1 9 | }, 10 | "security": { 11 | "key": "testkey" //When set, don't prompt during startup for password 12 | }, 13 | "csrf": { 14 | "allowedOrigins": ["http://localhost:3000", "chrome-extension://fdmmgilgnpjigdojojpjoooidkmcomcm"] 15 | }, 16 | 17 | "cors": { 18 | "origin": "http://example.com" 19 | }, 20 | 21 | "session": { 22 | "keys": ["sessionkey"] 23 | }, 24 | 25 | "auth": { 26 | "provider": "my-custom-auth" 27 | } 28 | } -------------------------------------------------------------------------------- /examples/custom-auth/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "get /something": { 3 | "handler": "test.getSomething" 4 | }, 5 | 6 | "get /something2": { 7 | "handler": "test.getSomething2", 8 | "auth": "get-age", 9 | "validate": "$user.profile.age > 20" 10 | } 11 | } -------------------------------------------------------------------------------- /examples/custom-auth/handlers/test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | 6 | module.exports.getSomething = function (req, res, next) { 7 | res.json({id: req.user.id}); 8 | }; 9 | 10 | module.exports.getSomething2 = function (req, res, next) { 11 | res.json({id: req.user.id, age: req.user.profile.age}); 12 | }; -------------------------------------------------------------------------------- /examples/custom-auth/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var server = require('../../'); 6 | 7 | server.init(function(err) { 8 | if (err) { 9 | console.warn('Startup failed', err); 10 | } else { 11 | console.log('started'); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /examples/custom-auth/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ext": ".js,.json,.xsl", 3 | "ignore": ["*/node_modules/"], 4 | "watch": [".", "../../"] 5 | } 6 | -------------------------------------------------------------------------------- /examples/custom-auth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "google-auth", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/custom-logging/config/default.json: -------------------------------------------------------------------------------- 1 | //Default config for my app 2 | { 3 | "express": { 4 | "port": "3000" 5 | }, 6 | "cluster": { 7 | "maxWorkers": 1 8 | } 9 | 10 | } -------------------------------------------------------------------------------- /examples/custom-logging/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var server = require('../../'); 6 | 7 | server.init(function(err) { 8 | if (err) { 9 | console.warn('Startup failed', err); 10 | } else { 11 | console.log('started'); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /examples/custom-logging/logger.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | //Placing a logger.js in my app root let's me setup a logger via code rather than config. 6 | var winston = require('winston'); 7 | 8 | //From within the init, I'm free to setup whatever transports I want 9 | module.exports.init = function(logger) { 10 | 11 | logger.add(winston.transports.Console, { 12 | timestamp: function() { 13 | return Date.now(); 14 | } 15 | }); 16 | }; -------------------------------------------------------------------------------- /examples/ext-modules/config/default.json: -------------------------------------------------------------------------------- 1 | //Default config for my app 2 | { 3 | "express": { 4 | "port": "3000" 5 | }, 6 | 7 | "cluster": { 8 | "maxWorkers": 1 9 | }, 10 | 11 | "handlers": [ //optional list of additional handler modules 12 | "my-handler" 13 | ], 14 | 15 | "services": [ //optional list of additional service modules 16 | "echo-service" 17 | ] 18 | 19 | } -------------------------------------------------------------------------------- /examples/ext-modules/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var server = require('../../'); 6 | 7 | server.init(function(err) { 8 | if (err) { 9 | console.warn('Startup failed', err); 10 | } else { 11 | console.log('started'); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /examples/ext-modules/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ext": ".js,.json,.xsl", 3 | "ignore": ["*/node_modules/"], 4 | "watch": [".", "../../"] 5 | } 6 | -------------------------------------------------------------------------------- /examples/ext-modules/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "modules", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "postinstall": "npm link ../packages/echo-service; npm link ../packages/my-handler" 9 | }, 10 | "author": "", 11 | "license": "ISC" 12 | } 13 | -------------------------------------------------------------------------------- /examples/fileupload/config/default.json: -------------------------------------------------------------------------------- 1 | //Default config for my app 2 | { 3 | "express": { 4 | "port": "3000", 5 | "middleware": ["express-static", "body-parser"] 6 | }, 7 | "cluster": { 8 | "maxWorkers": 1 9 | }, 10 | "body-parser": { 11 | "json": {} 12 | }, 13 | 14 | "express-static": { 15 | "docs": "static" 16 | }, 17 | 18 | "multer": { 19 | "dest": "uploads/" 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /examples/fileupload/config/default_custom_disk.json: -------------------------------------------------------------------------------- 1 | //Default config for my app 2 | { 3 | "express": { 4 | "port": "3000", 5 | "middleware": ["express-static", "body-parser"] 6 | }, 7 | "cluster": { 8 | "maxWorkers": 1 9 | }, 10 | "body-parser": { 11 | "json": {} 12 | }, 13 | 14 | "express-static": { 15 | "docs": "static" 16 | }, 17 | 18 | "multer": { 19 | "storage": "multerService" // the name of the service in services 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /examples/fileupload/config/default_memory_storage.json: -------------------------------------------------------------------------------- 1 | //Default config for my app 2 | { 3 | "express": { 4 | "port": "3000", 5 | "middleware": ["express-static", "body-parser"] 6 | }, 7 | "cluster": { 8 | "maxWorkers": 1 9 | }, 10 | "body-parser": { 11 | "json": {} 12 | }, 13 | 14 | "express-static": { 15 | "docs": "static" 16 | }, 17 | 18 | "multer": { 19 | "storage": "multerMemoryStorage" // inits multer with multer.memoryStorage() 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /examples/fileupload/handlers/upload.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | 6 | exports.fileUpload = function(req, res, next) { 7 | console.log('FILE UPLOAD!'); 8 | console.log(req.files.file1); 9 | console.log(req.body.text); 10 | res.json({ files: req.files, number: req.body.text}); 11 | }; 12 | -------------------------------------------------------------------------------- /examples/fileupload/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var server = require('../../'); 6 | 7 | server.init(function(err) { 8 | if (err) { 9 | console.warn('Startup failed', err); 10 | } else { 11 | console.log('started'); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /examples/fileupload/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ext": ".js,.json,.xsl", 3 | "ignore": ["*/node_modules/"], 4 | "watch": [".", "../../"] 5 | } 6 | -------------------------------------------------------------------------------- /examples/fileupload/services/multerService.js: -------------------------------------------------------------------------------- 1 | var multer = require('multer'); 2 | var log; 3 | 4 | exports.init = function (logger) { 5 | log = logger; 6 | }; 7 | 8 | exports.storage = multer.diskStorage({ 9 | destination: function (req, file, cb) { 10 | log.debug('destination: uploads/'); 11 | cb(null, 'uploads/'); 12 | }, 13 | filename: function (req, file, cb) { 14 | var filename = file.fieldname + '-' + Date.now(); 15 | log.debug('filename: ', filename); 16 | cb(null, filename); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /examples/fileupload/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |

11 |

12 |

13 |

14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/fileupload/swagger/upload.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "1.0.0", 5 | "title": "Swagger Fileupload", 6 | "description": "Test multipart form data" 7 | }, 8 | "host": "example.com", 9 | "basePath": "/api", 10 | "schemes": [ 11 | "http" 12 | ], 13 | "consumes": [ 14 | "application/json" 15 | ], 16 | "produces": [ 17 | "application/json" 18 | ], 19 | "paths": { 20 | "/test/fileUpload": { 21 | "post": { 22 | "tags": ["api"], 23 | "operationId": "fileUpload", 24 | "parameters": [ 25 | { 26 | "required": true, 27 | "type": "number", 28 | "name": "text", 29 | "in": "formData" 30 | }, 31 | { 32 | "required": true, 33 | "type": "file", 34 | "name": "file1", 35 | "in": "formData" 36 | } 37 | ], 38 | "consumes": ["multipart/form-data"], 39 | "responses": { 40 | "default": { 41 | "description": "fail" 42 | } 43 | }, 44 | "produces": ["application/json"] 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /examples/fileupload/uploads/uploads.end.up.here.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlueOakJS/blueoak-server/61385d91c0da1be4e86025a2956f3c232202dee7/examples/fileupload/uploads/uploads.end.up.here.txt -------------------------------------------------------------------------------- /examples/google-auth/client/README: -------------------------------------------------------------------------------- 1 | This is an example of web client using Google Sign-In. -------------------------------------------------------------------------------- /examples/google-auth/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 9 | 10 | Google Sign In Example 11 | 12 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 35 |
36 | 37 | Sign out 38 | 49 | 50 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /examples/google-auth/config/default.json: -------------------------------------------------------------------------------- 1 | //Default config for my app 2 | { 3 | "express": { 4 | "port": "3000", 5 | "middleware": ["csrf", "express-static", "body-parser", "session"] 6 | }, 7 | "cluster": { 8 | "maxWorkers": 1 9 | }, 10 | "security": { 11 | "key": "testkey" //When set, don't prompt during startup for password 12 | }, 13 | "csrf": { 14 | "allowedOrigins": ["http://localhost:3000", "http://localhost:8080", "chrome-extension://fdmmgilgnpjigdojojpjoooidkmcomcm"] 15 | }, 16 | 17 | "express-static": { 18 | "docs": "client" 19 | }, 20 | 21 | "session": { 22 | "keys": ["sessionkey"] 23 | }, 24 | 25 | "body-parser": { 26 | "json": {} 27 | }, 28 | 29 | "google-oauth": { 30 | "callbackPath": "/auth", 31 | "signoutPath": "/logout", 32 | "clientId": "826839015121-rs7t7ick1ib0uvui9iihbb82gcqg64r9.apps.googleusercontent.com", 33 | "clientSecret": "", 34 | "redirectURI": "postmessage", 35 | "profile": true 36 | }, 37 | 38 | "google-jwt": { 39 | "clientId": "826839015121-rs7t7ick1ib0uvui9iihbb82gcqg64r9.apps.googleusercontent.com" 40 | } 41 | } -------------------------------------------------------------------------------- /examples/google-auth/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "get /something": { 3 | "handler": "test.getSomething", 4 | "auth": "google-oauth" 5 | }, 6 | 7 | "get /something2": { 8 | "handler": "test.getSomething2", 9 | "auth": ["google-jwt"] 10 | } 11 | } -------------------------------------------------------------------------------- /examples/google-auth/handlers/test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | 6 | module.exports.getSomething = function (req, res, next) { 7 | res.json({email: req.user.email, profile: req.user.profile}); 8 | }; 9 | 10 | module.exports.getSomething2 = function (req, res, next) { 11 | res.json({email: req.user.email}); 12 | }; -------------------------------------------------------------------------------- /examples/google-auth/handlers/test2.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.init = function(app, auth) { 6 | 7 | //manual injection of auth middleware 8 | app.get('/test2', auth.get('google-oauth'), function(req, res) { 9 | res.json({email: req.user.email}); 10 | }); 11 | }; -------------------------------------------------------------------------------- /examples/google-auth/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var server = require('../../'); 6 | 7 | server.init(function(err) { 8 | if (err) { 9 | console.warn('Startup failed', err); 10 | } else { 11 | console.log('started'); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /examples/google-auth/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ext": ".js,.json,.xsl", 3 | "ignore": ["*/node_modules/"], 4 | "watch": [".", "../../"] 5 | } 6 | -------------------------------------------------------------------------------- /examples/google-auth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "google-auth", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/kitchensink/README.md: -------------------------------------------------------------------------------- 1 | # TODO -------------------------------------------------------------------------------- /examples/kitchensink/config/default.json: -------------------------------------------------------------------------------- 1 | //Default config for my app 2 | { 3 | "express": { 4 | "middleware": ["csrf", "cors", "session", "body-parser"], 5 | "middleware$": ["errors"] 6 | }, 7 | "cluster": { 8 | "maxWorkers": 1 9 | }, 10 | "logger": { 11 | }, 12 | "randomizer": { 13 | "min": 0, 14 | "max": 1000 15 | }, 16 | "test": { 17 | "value": { 18 | "encrypted": "{aes-256-cbc}7d343a60de6b2ee4cca096509745f9fd=" //decryption key is testkey 19 | } 20 | }, 21 | "security": { 22 | "key": "testkey" //When set, don't prompt during startup for password 23 | }, 24 | 25 | "csrf": { 26 | "allowedOrigins": ["http://localhost:3000", "chrome-extension://fdmmgilgnpjigdojojpjoooidkmcomcm"] 27 | }, 28 | 29 | "cors": { 30 | "origin": "http://example.com" 31 | }, 32 | 33 | "session": { 34 | "keys": ["sessionkey"] 35 | }, 36 | 37 | "body-parser": { 38 | "json": {} 39 | }, 40 | 41 | "monitor": { 42 | "host": "localhost", 43 | "debug": true 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /examples/kitchensink/config/production.json: -------------------------------------------------------------------------------- 1 | { 2 | "cluster": { 3 | "maxWorkers": -1 4 | } 5 | } -------------------------------------------------------------------------------- /examples/kitchensink/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "get /test/route": { 3 | "handler": "foo.bar" 4 | }, 5 | "get /test/route2": { 6 | "handler": "foo.bar" 7 | } 8 | } -------------------------------------------------------------------------------- /examples/kitchensink/handlers/foo.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | 6 | //This is a declared route from the routes.json 7 | module.exports = { 8 | bar: function(req, res, next) { 9 | res.json({foo: 'bar'}); 10 | } 11 | }; -------------------------------------------------------------------------------- /examples/kitchensink/handlers/hello.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | 6 | module.exports.init = function(app, randomizer, monitor) { 7 | app.get('/hello', monitor.express('hello_ENDPOINT'), function(req, res) { 8 | var rcount = req.session.rcount || 0; 9 | rcount += 1; 10 | req.session.rcount = rcount; 11 | randomizer.get(function(number) { 12 | res.status(200).send('hello world, here is a random number!...' + number + '.' + 13 | ' You have used this service ' + rcount + ' times.'); 14 | }); 15 | }); 16 | 17 | //Use to validate csrf check 18 | app.post('/hello', function(req, res) { 19 | console.log(typeof req.body, req.body); 20 | res.status(200).send('POST worked. Body is ' + req.body); 21 | }); 22 | 23 | //Use to validate CORS 24 | app.put('/hello', function(req, res) { 25 | res.status(200).send('PUT worked'); 26 | }); 27 | 28 | app.get('/', function(req, res) { 29 | res.status(200).send('GET worked'); 30 | }); 31 | 32 | app.get('/foo/:bar', function(req, res) { 33 | setTimeout(function() { 34 | res.status(200).send('Got :bar of value ' + req.params.bar); 35 | }, 1000); 36 | 37 | }); 38 | 39 | app.get('/error', function(req, res, next) { 40 | next(new Error('I found an error!')); 41 | }); 42 | 43 | }; -------------------------------------------------------------------------------- /examples/kitchensink/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var server = require('../../'); 6 | 7 | server.init(function(err) { 8 | if (err) { 9 | console.warn('Startup failed', err); 10 | } else { 11 | console.log('started'); 12 | 13 | //At this point we could safely control the server through the server object 14 | } 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /examples/kitchensink/middleware/errors.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.init = function(app, logger) { 6 | logger.debug('init error handler'); 7 | app.use(function(err, req, res, next) { 8 | logger.error(err.stack); 9 | res.status(500).send(err.message); 10 | }); 11 | }; -------------------------------------------------------------------------------- /examples/kitchensink/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ext": ".js,.json,.xsl", 3 | "ignore": ["*/node_modules/"], 4 | "watch": [".", "../../"] 5 | } 6 | -------------------------------------------------------------------------------- /examples/kitchensink/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kitchensink", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "request": "^2.51.0" 13 | }, 14 | "scripts": { 15 | "postinstall": "npm link ../echo-service", 16 | "postupdate": "npm link ../echo-service" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/kitchensink/services/randomizer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | //Uses random.org to generate a random integer 6 | var request = require('request'); 7 | var url, _logger; 8 | 9 | exports.init = function(logger, config, callback) { 10 | var cfg = config.get('randomizer'); 11 | var min = cfg.min || 0; 12 | var max = cfg.max || 100; 13 | url = 'http://www.random.org/integers/?num=1&min=' + min + '&max=' + max + '&col=1&base=10&format=plain&rnd=new'; 14 | _logger = logger; 15 | logger.info('started randomizer service', {url: url}); 16 | callback(); 17 | }; 18 | 19 | exports.get = function(callback) { 20 | 21 | //custom log level 22 | _logger.info('request URL: ' + url); 23 | request.get({url: url}, function(err, response, body) { 24 | if (err) { 25 | throw err; 26 | } 27 | callback(Number(body)); 28 | }); 29 | }; -------------------------------------------------------------------------------- /examples/packages/echo-service/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable lodash/prefer-noop */ 2 | /* 3 | * Copyright (c) 2015-2016 PointSource, LLC. 4 | * MIT Licensed 5 | */ 6 | exports.init = function() { 7 | 8 | }; 9 | 10 | exports.echo = function(txt) { 11 | console.log(txt); 12 | }; -------------------------------------------------------------------------------- /examples/packages/echo-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "echo-service", 3 | "version": "1.0.0", 4 | "description": "Echo Service", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC" 11 | } 12 | -------------------------------------------------------------------------------- /examples/packages/my-handler/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | module.exports.init = function (app, logger, echoService) { 6 | logger.info('init my-handler'); 7 | app.get('/my-handler', function (req, res) { 8 | echoService.echo('calling my-handler'); 9 | res.json({}); 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /examples/packages/my-handler/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-handler", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC" 11 | } 12 | -------------------------------------------------------------------------------- /examples/redis/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "cache": { 3 | "type": "redis" 4 | }, 5 | 6 | "redis": { 7 | "host": "localhost", 8 | "port": "6379" 9 | }, 10 | 11 | "express": { 12 | "middleware": [] 13 | }, 14 | 15 | "cluster": { 16 | "maxWorkers": 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/redis/handlers/main.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable lodash/prefer-lodash-typecheck */ 2 | /* eslint-disable lodash/prefer-is-nil */ 3 | /* 4 | * Copyright (c) 2015-2016 PointSource, LLC. 5 | * MIT Licensed 6 | */ 7 | exports.init = function (app, cache, logger) { 8 | 9 | app.get('/cache', function (req, res, next) { 10 | 11 | cache.get('/main/counter', function (err, val) { 12 | 13 | if (err) { 14 | return next(err); 15 | } 16 | 17 | //if we don't have a value for counter, set it to 1 18 | if (typeof val === 'undefined' || val === null) { 19 | val = 1; 20 | cache.set('/main/counter', val); 21 | } 22 | 23 | //return response 24 | res.json({val: val}); 25 | 26 | //increment 27 | val = val + 1; 28 | cache.set('/main/counter', val); 29 | }); 30 | }); 31 | 32 | }; -------------------------------------------------------------------------------- /examples/redis/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var server = require('../../'); 6 | 7 | server.init(function(err) { 8 | if (err) { 9 | console.warn('Startup failed', err); 10 | } else { 11 | console.log('started'); 12 | 13 | //At this point we could safely control the server through the server object 14 | } 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /examples/ssl/certs/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICUTCCAboCCQDtoI8a/hTEKTANBgkqhkiG9w0BAQUFADBtMQswCQYDVQQGEwJV 3 | UzELMAkGA1UECBMCTkMxEDAOBgNVBAcTB1JhbGVpZ2gxGDAWBgNVBAoTD1BvaW50 4 | U291cmNlIExMQzERMA8GA1UECxMIQ3VycmVuY3kxEjAQBgNVBAMTCWxvY2FsaG9z 5 | dDAeFw0xNDA4MDUxNjIzNDJaFw0xNTA4MDUxNjIzNDJaMG0xCzAJBgNVBAYTAlVT 6 | MQswCQYDVQQIEwJOQzEQMA4GA1UEBxMHUmFsZWlnaDEYMBYGA1UEChMPUG9pbnRT 7 | b3VyY2UgTExDMREwDwYDVQQLEwhDdXJyZW5jeTESMBAGA1UEAxMJbG9jYWxob3N0 8 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL/faGP8ErUWOCs7WUFeOY76k2 9 | wv88SqmSE0Bd3VBI1DWUjkarwS/6QBPswl1ZLHtf6lFFN8dMpSh191u0MVVH1hIA 10 | mO8QN1J7ldmqq5YBmGDXbSXeB9FHeFO6DS7OPKzIPbaFdKd6Mic+KInIF8D/RBTK 11 | xItTv33P9KobBPeoeQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADZK5XLSMBuAc8ep 12 | HsIB+bfU89plib4ruXSqNp7OsK1rJdvs5C/1m4wrsNgnlaMZfqKDAls3rZwTi6I8 13 | az7qEPZ6hCw3xoSBaitJt4uwdCp03jX+XGwS3lvE7pY9mfFlHwXCr3j6krxxbIdG 14 | HOlfUZ00DuzwBmJiPeH2sijBfDKK 15 | -----END CERTIFICATE----- 16 | -------------------------------------------------------------------------------- /examples/ssl/certs/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQDL/faGP8ErUWOCs7WUFeOY76k2wv88SqmSE0Bd3VBI1DWUjkar 3 | wS/6QBPswl1ZLHtf6lFFN8dMpSh191u0MVVH1hIAmO8QN1J7ldmqq5YBmGDXbSXe 4 | B9FHeFO6DS7OPKzIPbaFdKd6Mic+KInIF8D/RBTKxItTv33P9KobBPeoeQIDAQAB 5 | AoGAFH8jGyAiz7Bw2DwoXYXJhZ9nGDqFnNA/s469o/DN1VOFQrT9Atr5cCKqHdr1 6 | uDUQ/HU1z0eofdoGc7vz+eoPEGfUnXk2BXXmrA/xZAlrrVekzR5lnSf/puWE6xgZ 7 | t+/PUfzj8Fckf/UT7UbYIjIz5w0WyrAdY3dGrBlGtBk/kxECQQDlaewTCuBUC1R+ 8 | PbEpY5fkN2gmhSyXOeAf82U2tcUL8Fe8L3sLNRHHYI71X+tLW8HdvUK8upjHb8UJ 9 | V+5AnEHlAkEA46HZIHufbT6Z8pTcb73o+Nm/ZGDgLyGc3wMLILHfGxJxCsNyA476 10 | X/kX0x+PCGqiPruvanD+lnhaxeQQFAbzBQJAeid8XGdeK2IdC1suCivNpOcbvecL 11 | ZzqZ7Glda1Q+J61CCiH+Emmfndn8RQtd7jJdeARelL+Guir5b5AyfJS3QQJBAM8G 12 | X8bfJ/vfN3wcTu+BtOS+hQbx5HJ5C4b36aLuoo6okw7K+mKqIqQuk7B7v0ZUVQLh 13 | B4SzB5gqFcRsywKr31ECQEHEgUymJpHCJrEVlg4+I09ohjN5BZbAK84TdSlmggj3 14 | COAo/WI/dBRZG8EgDQdya9FaZwZITsaIIjrY4Zrt8ks= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /examples/ssl/config/default.json: -------------------------------------------------------------------------------- 1 | //Default config for my app 2 | { 3 | "express": { 4 | "port": "3001", 5 | "ssl": { 6 | "key": "certs/server.key", 7 | "cert": "certs/server.crt", 8 | "passphrase": "{aes-256-cbc}41fc0c955caf2765e1f9dec69314d087=" //encoded string 'devmode' using 'testkey' 9 | }, 10 | "middleware": ["csrf", "cors"] 11 | }, 12 | "cluster": { 13 | "maxWorkers": 1 14 | }, 15 | "security": { 16 | "key": "testkey" //When set, don't prompt during startup for password 17 | }, 18 | "csrf": { 19 | "allowedOrigins": ["http://localhost:3000", "chrome-extension://fdmmgilgnpjigdojojpjoooidkmcomcm"] 20 | }, 21 | "cors": { 22 | "origin": "http://example.com" 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /examples/ssl/handlers/secure.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | module.exports.init = function(app) { 6 | app.get('/hello', function (req, res) { 7 | res.status(200).send('Secure connection? ' + req.secure); 8 | }); 9 | }; -------------------------------------------------------------------------------- /examples/ssl/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var server = require('../../'); 6 | 7 | server.init(function(err) { 8 | if (err) { 9 | console.warn('Startup failed', err); 10 | } else { 11 | console.log('started'); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /examples/swagger/config/default.json: -------------------------------------------------------------------------------- 1 | //Default config for my app 2 | { 3 | "express": { 4 | "port": "3000", 5 | "middleware": ["cors", "body-parser"], 6 | "middleware$": ["errors"] 7 | }, 8 | 9 | "cors": { 10 | "origin": "*" 11 | }, 12 | 13 | "body-parser": { 14 | "json": {} 15 | }, 16 | 17 | "cluster": { 18 | "maxWorkers": 1 19 | }, 20 | 21 | "swagger": { 22 | "refCompiler": { 23 | "petstore": { 24 | "baseSpecFile": "petstore.json", 25 | "refDirs": [ 26 | "v1-assets" 27 | ] 28 | }, 29 | "api-v1": { 30 | "baseSpecFile": "api-v1.yaml", 31 | "refDirs": [ 32 | "public" 33 | ] 34 | } 35 | } 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /examples/swagger/handlers/api-v1.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable lodash/prefer-noop */ 2 | exports.init = function() { 3 | 4 | }; 5 | 6 | exports.getFunTimeById = function(req, res, next) { 7 | res.status(200).json({ 8 | 'curiousPeople': [ 9 | { 10 | 'kind': 'OtherPerson', 11 | 'curiousPersonReqField': 'hey!', 12 | 'enthusiasticPersonReqField': 'hola!' 13 | } 14 | ] 15 | }); 16 | }; 17 | 18 | -------------------------------------------------------------------------------- /examples/swagger/handlers/petstore.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable lodash/prefer-lodash-method */ 2 | /* 3 | * Copyright (c) 2015-2016 PointSource, LLC. 4 | * MIT Licensed 5 | */ 6 | 7 | var logger = services.get('logger'); 8 | 9 | exports.init = function(swagger) { 10 | swagger.addFormat('pet-tag', function(data, schema) { 11 | //check that a tag is uppercase 12 | if (data !== data.toUpperCase()) { 13 | return 'Pet tag must be all uppercase'; 14 | } 15 | }); 16 | }; 17 | 18 | exports.findPets = function(req, res, next) { 19 | res.status(200).json([]); 20 | }; 21 | 22 | exports.findPetById = function(req, res, next) { 23 | logger.debug('Fetching pet %s', req.params.id); 24 | res.status(200).json({}); 25 | }; 26 | 27 | exports.addPet = function(req, res, next) { 28 | res.status(201).json({}); 29 | }; 30 | 31 | exports.deletePet = function(req, res, next) { 32 | res.status(200).json({}); 33 | }; -------------------------------------------------------------------------------- /examples/swagger/handlers/uber-v1.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable lodash/prefer-noop */ 2 | exports.init = function() { 3 | 4 | }; 5 | 6 | exports.getProducts = function(req, res, next) { 7 | res.status(200).json([]); 8 | }; -------------------------------------------------------------------------------- /examples/swagger/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var server = require('../../'); 6 | 7 | server.init(function(err) { 8 | if (err) { 9 | console.warn('Startup failed', err); 10 | } else { 11 | console.log('started'); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /examples/swagger/middleware/errors.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.init = function(app, logger) { 6 | app.use(function(err, req, res, next) { 7 | if (err.name === 'ValidationError') { 8 | logger.debug('Validation Error details: ' + JSON.stringify(err, null, 2)); 9 | res.json({error: err.message}); 10 | } else { 11 | return next(); 12 | } 13 | }); 14 | }; -------------------------------------------------------------------------------- /examples/swagger/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ext": ".js,.json,.yaml,.xsl", 3 | "ignore": ["*/node_modules/", "swagger/petstore.json", "swagger/api-v1.yaml"], 4 | "watch": [".", "../../"] 5 | } 6 | -------------------------------------------------------------------------------- /examples/swagger/swagger/api-v1.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | version: 2.0.0-rc.1 4 | title: Test API 5 | 6 | basePath: /api/v1 7 | produces: 8 | - application/json 9 | paths: 10 | $ref: 'public/paths.yaml' 11 | 12 | ### ref-compiler: BEGIN 13 | definitions: 14 | Contact: 15 | $ref: 'public/definitions/Contact.yaml' 16 | CuriousPerson: 17 | $ref: 'public/definitions/CuriousPerson.yaml' 18 | EnthusiasticPerson: 19 | $ref: 'public/definitions/EnthusiasticPerson.yaml' 20 | OtherPerson: 21 | $ref: 'public/definitions/OtherPerson.yaml' 22 | Person: 23 | $ref: 'public/definitions/Person.yaml' 24 | SuperFunTime: 25 | $ref: 'public/definitions/SuperFunTime.yaml' 26 | parameters: 27 | PathId: 28 | $ref: 'public/parameters/PathId.yaml' 29 | -------------------------------------------------------------------------------- /examples/swagger/swagger/public/definitions/Contact.yaml: -------------------------------------------------------------------------------- 1 | properties: 2 | firstName: 3 | type: string 4 | lastName: 5 | type: string 6 | email: 7 | type: string 8 | format: email 9 | gender: 10 | type: string 11 | enum: 12 | - Female 13 | - Male -------------------------------------------------------------------------------- /examples/swagger/swagger/public/definitions/CuriousPerson.yaml: -------------------------------------------------------------------------------- 1 | allOf: 2 | - $ref: 'EnthusiasticPerson.yaml' 3 | - type: object 4 | required: 5 | - kind 6 | - curiousPersonReqField 7 | properties: 8 | kind: 9 | type: string 10 | enum: 11 | - CuriousPerson 12 | curiousPersonReqField: 13 | type: string 14 | -------------------------------------------------------------------------------- /examples/swagger/swagger/public/definitions/EnthusiasticPerson.yaml: -------------------------------------------------------------------------------- 1 | allOf: 2 | - $ref: 'Person.yaml' 3 | - type: object 4 | required: 5 | - kind 6 | - enthusiasticPersonReqField 7 | properties: 8 | kind: 9 | type: string 10 | enum: 11 | - EnthusiasticPerson 12 | enthusiasticPersonReqField: 13 | type: string 14 | 15 | -------------------------------------------------------------------------------- /examples/swagger/swagger/public/definitions/OtherPerson.yaml: -------------------------------------------------------------------------------- 1 | allOf: 2 | - $ref: 'CuriousPerson.yaml' 3 | - type: object 4 | required: 5 | - kind 6 | - notes 7 | properties: 8 | kind: 9 | type: string 10 | enum: 11 | - OtherPerson 12 | notes: 13 | type: string 14 | 15 | -------------------------------------------------------------------------------- /examples/swagger/swagger/public/definitions/Person.yaml: -------------------------------------------------------------------------------- 1 | allOf: 2 | - $ref: 'Contact.yaml' 3 | - type: object 4 | discriminator: kind 5 | required: 6 | - kind 7 | properties: 8 | kind: 9 | type: string 10 | 11 | -------------------------------------------------------------------------------- /examples/swagger/swagger/public/definitions/SuperFunTime.yaml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | curiousPeople: 4 | type: array 5 | items: 6 | $ref: 'CuriousPerson.yaml' 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/swagger/swagger/public/parameters/PathId.yaml: -------------------------------------------------------------------------------- 1 | name: id 2 | in: path 3 | required: true 4 | type: string 5 | description: | 6 | The ID of the resource to which the call applies. 7 | -------------------------------------------------------------------------------- /examples/swagger/swagger/public/paths.yaml: -------------------------------------------------------------------------------- 1 | /superfuntime/{id}: 2 | $ref: 'paths/superfuntime-{id}.yaml' 3 | 4 | -------------------------------------------------------------------------------- /examples/swagger/swagger/public/paths/superfuntime-{id}.yaml: -------------------------------------------------------------------------------- 1 | get: 2 | summary: Get super fun time 3 | operationId: getFunTimeById 4 | tags: 5 | - Fun Times 6 | parameters: 7 | - $ref: '../parameters/PathId.yaml' 8 | responses: 9 | 200: 10 | schema: 11 | $ref: '../definitions/SuperFunTime.yaml' 12 | description: I want a super fun time 13 | post: 14 | summary: Make it even more of a fun time 15 | operationId: addMoreFun 16 | tags: 17 | - Fun Times 18 | parameters: 19 | - $ref: '../parameters/PathId.yaml' 20 | - in: body 21 | schema: 22 | $ref: '../definitions/Person.yaml' 23 | name: more 24 | required: true 25 | responses: 26 | 200: 27 | schema: 28 | $ref: '../definitions/SuperFunTime.yaml' 29 | description: I want an even more fun time 30 | -------------------------------------------------------------------------------- /examples/swagger/swagger/uber-v1.yaml: -------------------------------------------------------------------------------- 1 | # from editor.swagger.io, with the addition of external references (and some other changes) 2 | 3 | # this is an example of the Uber API 4 | # as a demonstration of an API spec in YAML 5 | swagger: '2.0' 6 | info: 7 | title: Uber API 8 | description: Move your app forward with the Uber API 9 | version: "1.0.0" 10 | # the domain of the service 11 | host: localhost:3000 12 | # array of all schemes that your API supports 13 | schemes: 14 | - http 15 | # will be prefixed to all paths 16 | basePath: /api/v1 17 | produces: 18 | - application/json 19 | tags: 20 | $ref: 'v1-assets/Tags.yaml' 21 | paths: 22 | /products: 23 | $ref: 'v1-assets/paths/products.yaml' 24 | /estimates/price: 25 | $ref: 'v1-assets/paths/estimates.price.yaml' 26 | /estimates/time: 27 | $ref: 'v1-assets/paths/estimates.time.yaml' 28 | /me: 29 | $ref: 'v1-assets/paths/me.yaml' 30 | /history: 31 | $ref: 'v1-assets/paths/history.yaml' 32 | 33 | # we keep the deinfitions, parameters, and responses sections in the main api document 34 | # this little bit of double entry (they're refered to already in the path documents) 35 | # allows the spec to be assembled as a bundle and shown as pretty as possible in swagger-ui 36 | 37 | definitions: 38 | Product: 39 | $ref: 'v1-assets/definitions/Product.yaml' 40 | PriceEstimate: 41 | $ref: 'v1-assets/definitions/PriceEstimate.yaml' 42 | Profile: 43 | $ref: 'v1-assets/definitions/Profile.yaml' 44 | Activity: 45 | $ref: 'v1-assets/definitions/Activity.yaml' 46 | Activities: 47 | $ref: 'v1-assets/definitions/Activities.yaml' 48 | Error: 49 | $ref: 'v1-assets/definitions/Error.yaml' 50 | ToyProduct: 51 | $ref: 'v1-assets/definitions/ToyProduct.yaml' 52 | FoodProduct: 53 | $ref: 'v1-assets/definitions/FoodProduct.yaml' 54 | Label: 55 | $ref: 'v1-assets/definitions/Label.yaml' 56 | ToyLabel: 57 | $ref: 'v1-assets/definitions/ToyLabel.yaml' 58 | -------------------------------------------------------------------------------- /examples/swagger/swagger/v1-assets/Tags.yaml: -------------------------------------------------------------------------------- 1 | # this is an example of a top-level object added by reference to the main swagger doc 2 | - name: Estimates 3 | description: | 4 | Give me your best guess. 5 | - name: Products 6 | description: | 7 | What vehicles and services are available? 8 | - name: User 9 | description: | 10 | Who'd want to use this anyway? -------------------------------------------------------------------------------- /examples/swagger/swagger/v1-assets/definitions/Activities.yaml: -------------------------------------------------------------------------------- 1 | properties: 2 | offset: 3 | type: integer 4 | format: int32 5 | description: Position in pagination. 6 | limit: 7 | type: integer 8 | format: int32 9 | description: Number of items to retrieve (100 max). 10 | count: 11 | type: integer 12 | format: int32 13 | description: Total number of items available. 14 | history: 15 | type: array 16 | items: 17 | $ref: './Activity.yaml' 18 | -------------------------------------------------------------------------------- /examples/swagger/swagger/v1-assets/definitions/Activity.yaml: -------------------------------------------------------------------------------- 1 | properties: 2 | uuid: 3 | type: string 4 | description: Unique identifier for the activity 5 | -------------------------------------------------------------------------------- /examples/swagger/swagger/v1-assets/definitions/Error.yaml: -------------------------------------------------------------------------------- 1 | properties: 2 | code: 3 | type: integer 4 | format: int32 5 | message: 6 | type: string 7 | fields: 8 | type: string -------------------------------------------------------------------------------- /examples/swagger/swagger/v1-assets/definitions/FoodProduct.yaml: -------------------------------------------------------------------------------- 1 | allOf: 2 | - $ref: '../definitions/Product.yaml' 3 | properties: 4 | food_type: 5 | type: string 6 | description: Description of food. -------------------------------------------------------------------------------- /examples/swagger/swagger/v1-assets/definitions/Label.yaml: -------------------------------------------------------------------------------- 1 | discriminator: label_type 2 | required: 3 | - label_type 4 | - name 5 | 6 | properties: 7 | name: 8 | type: string 9 | description: | 10 | Unique identifier representing a specific product for a given latitude & longitude. 11 | For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles. -------------------------------------------------------------------------------- /examples/swagger/swagger/v1-assets/definitions/PriceEstimate.yaml: -------------------------------------------------------------------------------- 1 | properties: 2 | product_id: 3 | type: string 4 | description: Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles 5 | currency_code: 6 | type: string 7 | description: "[ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code." 8 | display_name: 9 | type: string 10 | description: Display name of product. 11 | estimate: 12 | type: string 13 | description: Formatted string of estimate in local currency of the start location. Estimate could be a range, a single number (flat rate) or "Metered" for TAXI. 14 | low_estimate: 15 | type: number 16 | description: Lower bound of the estimated price. 17 | high_estimate: 18 | type: number 19 | description: Upper bound of the estimated price. 20 | surge_multiplier: 21 | type: number 22 | description: Expected surge multiplier. Surge is active if surge_multiplier is greater than 1. Price estimate already factors in the surge multiplier. -------------------------------------------------------------------------------- /examples/swagger/swagger/v1-assets/definitions/Product.yaml: -------------------------------------------------------------------------------- 1 | discriminator: product_type 2 | required: 3 | - product_type 4 | - product_id 5 | 6 | properties: 7 | product_id: 8 | type: string 9 | description: | 10 | Unique identifier representing a specific product for a given latitude & longitude. 11 | For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles. 12 | description: 13 | type: string 14 | description: Description of product. 15 | display_name: 16 | type: string 17 | description: Display name of product. 18 | capacity: 19 | type: string 20 | description: Capacity of product. For example, 4 people. 21 | image: 22 | type: string 23 | description: Image URL representing the product. 24 | product_type: 25 | type: string 26 | description: type of product. 27 | labels: 28 | type: object 29 | $ref: '../definitions/ToyLabel.yaml' 30 | 31 | -------------------------------------------------------------------------------- /examples/swagger/swagger/v1-assets/definitions/Profile.yaml: -------------------------------------------------------------------------------- 1 | properties: 2 | first_name: 3 | type: string 4 | description: First name of the Uber user. 5 | last_name: 6 | type: string 7 | description: Last name of the Uber user. 8 | email: 9 | type: string 10 | description: Email address of the Uber user 11 | picture: 12 | type: string 13 | description: Image URL of the Uber user. 14 | promo_code: 15 | type: string 16 | description: Promo code of the Uber user. -------------------------------------------------------------------------------- /examples/swagger/swagger/v1-assets/definitions/ToyLabel.yaml: -------------------------------------------------------------------------------- 1 | allOf: 2 | - $ref: '../definitions/Label.yaml' 3 | - properties: 4 | age: 5 | type: number 6 | description: age of model. 7 | -------------------------------------------------------------------------------- /examples/swagger/swagger/v1-assets/definitions/ToyProduct.yaml: -------------------------------------------------------------------------------- 1 | allOf: 2 | - $ref: '../definitions/Product.yaml' 3 | - required: 4 | - toy_type 5 | properties: 6 | toy_type: 7 | type: string 8 | description: Description of toy. 9 | 10 | -------------------------------------------------------------------------------- /examples/swagger/swagger/v1-assets/paths/estimates.price.yaml: -------------------------------------------------------------------------------- 1 | get: 2 | summary: Price Estimates 3 | description: | 4 | The Price Estimates endpoint returns an estimated price range 5 | for each product offered at a given location. The price estimate is 6 | provided as a formatted string with the full price range and the localized 7 | currency symbol.

The response also includes low and high estimates, 8 | and the [ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code for 9 | situations requiring currency conversion. When surge is active for a particular 10 | product, its surge_multiplier will be greater than 1, but the price estimate 11 | already factors in this multiplier. 12 | parameters: 13 | - name: start_latitude 14 | in: query 15 | description: Latitude component of start location. 16 | required: true 17 | type: number 18 | format: double 19 | - name: start_longitude 20 | in: query 21 | description: Longitude component of start location. 22 | required: true 23 | type: number 24 | format: double 25 | - name: end_latitude 26 | in: query 27 | description: Latitude component of end location. 28 | required: true 29 | type: number 30 | format: double 31 | - name: end_longitude 32 | in: query 33 | description: Longitude component of end location. 34 | required: true 35 | type: number 36 | format: double 37 | tags: 38 | - Estimates 39 | responses: 40 | 200: 41 | description: An array of price estimates by product 42 | schema: 43 | type: array 44 | items: 45 | $ref: '../definitions/PriceEstimate.yaml' 46 | default: 47 | description: Unexpected error 48 | schema: 49 | $ref: '../definitions/Error.yaml' -------------------------------------------------------------------------------- /examples/swagger/swagger/v1-assets/paths/estimates.time.yaml: -------------------------------------------------------------------------------- 1 | get: 2 | summary: Time Estimates 3 | description: | 4 | The Time Estimates endpoint returns ETAs for all products offered at a given location, with the responses expressed as integers in seconds. We recommend that this endpoint be called every minute to provide the most accurate, up-to-date ETAs. 5 | parameters: 6 | - name: start_latitude 7 | in: query 8 | description: Latitude component of start location. 9 | required: true 10 | type: number 11 | format: double 12 | - name: start_longitude 13 | in: query 14 | description: Longitude component of start location. 15 | required: true 16 | type: number 17 | format: double 18 | - name: customer_uuid 19 | in: query 20 | type: string 21 | format: uuid 22 | description: Unique customer identifier to be used for experience customization. 23 | - name: product_id 24 | in: query 25 | type: string 26 | description: Unique identifier representing a specific product for a given latitude & longitude. 27 | tags: 28 | - Estimates 29 | responses: 30 | 200: 31 | description: An array of products 32 | schema: 33 | type: array 34 | items: 35 | $ref: '../definitions/Product.yaml' 36 | default: 37 | description: Unexpected error 38 | schema: 39 | $ref: '../definitions/Error.yaml' -------------------------------------------------------------------------------- /examples/swagger/swagger/v1-assets/paths/history.yaml: -------------------------------------------------------------------------------- 1 | get: 2 | summary: User Activity 3 | description: | 4 | The User Activity endpoint returns data about a user's lifetime activity with Uber. The response will include pickup locations and times, dropoff locations and times, the distance of past requests, and information about which products were requested. 5 | 6 | The history array in the response will have a maximum length based on the limit parameter. The response value count may exceed limit, therefore subsequent API requests may be necessary. 7 | parameters: 8 | - name: offset 9 | in: query 10 | type: integer 11 | format: int32 12 | description: | 13 | Offset the list of returned results by this amount. Default is zero. 14 | - name: limit 15 | in: query 16 | type: integer 17 | format: int32 18 | description: | 19 | Number of items to retrieve. Default is 5, maximum is 100. 20 | tags: 21 | - User 22 | responses: 23 | 200: 24 | description: History information for the given user 25 | schema: 26 | $ref: '../definitions/Activities.yaml' 27 | default: 28 | description: Unexpected error 29 | schema: 30 | $ref: '../definitions/Error.yaml' 31 | -------------------------------------------------------------------------------- /examples/swagger/swagger/v1-assets/paths/me.yaml: -------------------------------------------------------------------------------- 1 | get: 2 | summary: User Profile 3 | description: | 4 | The User Profile endpoint returns information about the Uber user that has authorized with the application. 5 | tags: 6 | - User 7 | responses: 8 | 200: 9 | description: Profile information for a user 10 | schema: 11 | $ref: '../definitions/Profile.yaml' 12 | default: 13 | description: Unexpected error 14 | schema: 15 | $ref: '../definitions/Error.yaml' 16 | -------------------------------------------------------------------------------- /examples/swagger/swagger/v1-assets/paths/products.yaml: -------------------------------------------------------------------------------- 1 | get: 2 | summary: Product Types 3 | operationId: getProducts 4 | description: | 5 | The Products endpoint returns information about the *Uber* products 6 | offered at a given location. The response includes the display name 7 | and other details about each product, and lists the products in the 8 | proper display order. 9 | parameters: 10 | - name: latitude 11 | in: query 12 | description: Latitude component of location. 13 | required: false 14 | type: number 15 | format: double 16 | - name: longitude 17 | in: query 18 | description: Longitude component of location. 19 | required: false 20 | type: number 21 | format: double 22 | tags: 23 | - Products 24 | responses: 25 | 200: 26 | description: An array of products 27 | schema: 28 | type: array 29 | items: 30 | $ref: '../definitions/Product.yaml' 31 | default: 32 | description: Unexpected error 33 | schema: 34 | $ref: '../definitions/Error.yaml' -------------------------------------------------------------------------------- /handlers/_routes.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var _ = require('lodash'); 6 | 7 | exports.init = function (app, config, serviceLoader, auth, logger) { 8 | var routes = config.get('routes'); 9 | registerDeclarativeRoutes(app, config, routes, serviceLoader, auth, logger); 10 | }; 11 | 12 | 13 | /* 14 | * Load the "routes" config and register all the routes. 15 | * Any errors that might occur because of mis-configured routes will be logged. 16 | */ 17 | function registerDeclarativeRoutes(app, config, routes, serviceLoader, auth, logger) { 18 | 19 | _.forEach(_.keys(routes), function (routePath) { 20 | //routePath should be of form " path" 21 | var parts = _.split(routePath, ' '); 22 | if (parts.length !== 2) { 23 | return logger.warn('Invalid route path "%s"', routePath); 24 | } 25 | 26 | var methods = ['get', 'post', 'put', 'delete', 'all']; 27 | 28 | if (!_.includes(methods, parts[0])) { 29 | return logger.warn('Invalid method "%s" on route "%s"', parts[0], parts[1]); 30 | } 31 | 32 | var handler = routes[routePath].handler; 33 | if (!handler) { 34 | return logger.warn('Missing handler for route "%s"', parts[1]); 35 | } 36 | 37 | //handler is something like foo.bar where there's a foo.js handler module which exports bar 38 | var handlerParts = _.split(handler, '.'); 39 | 40 | if (handlerParts.length !== 2) { 41 | return logger.warn('Invalid handler reference "%s"', parts[1]); 42 | } 43 | 44 | var handlerMod = serviceLoader.getConsumer('handlers', handlerParts[0]); 45 | if (!handlerMod) { 46 | return logger.warn('Could not find handler module named "%s".', handlerParts[0]); 47 | } 48 | 49 | var handlerFunc = handlerMod[handlerParts[1]]; 50 | if (!handlerFunc) { 51 | return logger.warn('Could not find handler function "%s" for module "%s"', 52 | handlerParts[1], handlerParts[0]); 53 | } 54 | 55 | //Some other projects have defined their own auth scheme. 56 | //As a temporary workaround, we ignore auth if the auth field is using their scheme, 57 | //which is an object instead of a string or array. 58 | var middleware = []; 59 | if (routes[routePath].auth && !_.isPlainObject(routes[routePath].auth)) { 60 | middleware = auth.getAuthMiddleware(routes[routePath].auth); 61 | } else if (!routes[routePath].auth) { //at least set up global auth if it's available 62 | middleware = auth.getAuthMiddleware(); 63 | } else { 64 | logger.debug('Ignoring auth'); 65 | } 66 | 67 | //Set up custom validator function on the route 68 | if (routes[routePath].validate) { 69 | logger.warn('Using experimental validators. This might change in the future.'); 70 | var expression = routes[routePath].validate; 71 | middleware.push(auth.validate(expression)); 72 | } 73 | 74 | middleware.push(handlerFunc); 75 | 76 | //register the route and handler function with express 77 | app[parts[0]].call(app, parts[1], middleware); 78 | }); 79 | 80 | } -------------------------------------------------------------------------------- /lib/dependencyCalc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | /* 6 | * Use to calculate the order for performing dependencies 7 | * 8 | * Imagine we have a dependency tree 9 | * a 10 | * / \ 11 | * c d 12 | * \/ | 13 | * e f 14 | * 15 | * We can create this graph using 16 | * addNode('a') 17 | * addNode('c', ['a']); 18 | * addNode('d', ['a']); 19 | * addNode('e', ['c', 'd']); 20 | * addNode('f', ['d']); 21 | * 22 | * If we want to execute these nodes, we can parallelize them by grouping them. 23 | * The items within each group can safely be executed in parallel before moving on to the next group 24 | * [['a'], ['c', 'd'], ['e', 'f']] 25 | * 26 | * which is the output of the calcGroups call. 27 | */ 28 | 29 | var _ = require('lodash'); 30 | 31 | /* 32 | * Key is the name of the node 33 | * value is array of dependencies 34 | */ 35 | var _nodes = {}; 36 | 37 | module.exports.addNode = function (name, deps) { 38 | _nodes[name] = deps; 39 | }; 40 | 41 | function findUnmetDependencies() { 42 | _.forEach(_.keys(_nodes), function (key) { 43 | if (_nodes[key]) { 44 | var deps = _nodes[key]; 45 | _.forEach(deps, function(depName) { 46 | if (!(depName in _nodes)) { 47 | //reset itself 48 | _nodes = {}; 49 | throw new Error('Unmet dependency: ' + key + ' -> ' + depName); 50 | } 51 | }); 52 | } 53 | }); 54 | } 55 | 56 | module.exports.calcGroups = function () { 57 | var nodes = _nodes; 58 | var groups = []; 59 | 60 | //First let's do a quick scan for unmet dependencies so that we can fail out early 61 | findUnmetDependencies(nodes); 62 | 63 | //Iterate until we don't have any nodes left to process 64 | while (_.keys(nodes).length > 0) { 65 | 66 | //Find the nodes that don't have any dependencies 67 | var emptyNodes = getNodesWithoutDeps(nodes); 68 | 69 | //If there aren't any, we must have hit a cycle 70 | if (emptyNodes.length === 0) { 71 | //reset itself 72 | _nodes = {}; 73 | throw new Error('Cycle found in dependency list: ' + JSON.stringify(nodes)); 74 | } 75 | 76 | //Add all the empty nodes to a group 77 | groups.push(emptyNodes); 78 | 79 | //remove the empty nodes from all dependency lists 80 | _.forEach(_.keys(nodes), function (key) { 81 | var val = nodes[key] || []; 82 | nodes[key] = diffArray(val, emptyNodes); 83 | }); 84 | 85 | //remove the empty nodes from the node list 86 | for (var key in nodes) { 87 | if (_.includes(emptyNodes, key)) { 88 | delete nodes[key]; 89 | } 90 | } 91 | } 92 | return groups; 93 | }; 94 | 95 | function getNodesWithoutDeps(nodes) { 96 | var toReturn = []; 97 | _.forEach(_.keys(nodes), function (key) { 98 | var val = nodes[key]; 99 | if (!val || val.length === 0) { 100 | toReturn.push(key); 101 | } 102 | }); 103 | return toReturn; 104 | } 105 | 106 | function diffArray(a, b) { 107 | return _.filter(a, function (i) { 108 | return !_.includes(b, i); 109 | }); 110 | } 111 | 112 | 113 | -------------------------------------------------------------------------------- /lib/di.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | //Dependency injection utils 6 | 7 | var _ = require('lodash'), 8 | fs = require('fs'), 9 | path = require('path'); 10 | 11 | //Checks if last parameter is named callback 12 | exports.hasCallback = function(fn) { 13 | var funStr = fn.toString(); 14 | var params = funStr.slice(funStr.indexOf('(') + 1, funStr.indexOf(')')).match(/([^\s,]+)/g); 15 | return params !== null && _.includes(params, 'callback'); 16 | }; 17 | 18 | /* 19 | * Return an array of the argument names for a function 20 | * If the function has no arguments, [] is returned. 21 | */ 22 | exports.getParamNames = function(fn) { 23 | var funStr = fn.toString(); 24 | var toReturn = funStr.slice(funStr.indexOf('(') + 1, funStr.indexOf(')')).match(/([^\s,]+)/g); 25 | 26 | //strip the callback 27 | if (toReturn === null) { 28 | return []; 29 | } 30 | if (toReturn.length > 0 && toReturn[toReturn.length - 1] === 'callback') { 31 | toReturn = toReturn.slice(0, toReturn.length - 1); 32 | } 33 | return toReturn; 34 | }; 35 | 36 | var MAX_LOAD_DEPTH = 1; //don't recurse below one subdir when looking for services/consumers 37 | 38 | /* 39 | * Use this to locate modules in the given directory. 40 | * For each js file found, callback is called with two parameters callback(dir, file) 41 | * where dir is the current directory and file is the file name. 42 | * depth is used to keep track of how deep we've recursed. 43 | * We won't recurse beyond MAX_LOAD_DEPTH 44 | */ 45 | function iterateOverJsFiles(dir, callback, depth) { 46 | 47 | depth = depth || 0; 48 | if (depth > MAX_LOAD_DEPTH) { 49 | return; 50 | } 51 | //logger.debug('Looking for files in %s at depth %s', dir, depth); 52 | 53 | if (fs.existsSync(dir)) { 54 | var files = fs.readdirSync(dir); 55 | _.forEach(files, function (file) { 56 | if (path.extname(file) === '.js') { 57 | return callback(dir, file); 58 | } else { 59 | var subDir = path.resolve(dir, file); 60 | if (fs.lstatSync(subDir).isDirectory()) { 61 | iterateOverJsFiles(subDir, callback, depth + 1); 62 | } 63 | } 64 | }); 65 | } else { // jshint ignore:line 66 | //logger.warn('%s is not a valid directory', dir); 67 | } 68 | } 69 | 70 | exports.iterateOverJsFiles = iterateOverJsFiles; -------------------------------------------------------------------------------- /lib/nodeCacheInterface.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | //This is a cache interface for the in-memory node cache. 6 | //The point of an interface is to provide a common set of methods that the cache service 7 | //can call regardless of the underlying implementation. 8 | //The redis service implements the same interface. 9 | var _ = require('lodash'), 10 | NodeCache = require('node-cache'); 11 | 12 | var client = null; 13 | 14 | module.exports = function(cfg) { 15 | client = new NodeCache(cfg); 16 | 17 | return { 18 | //node cache can return the value directly rather than using a callback 19 | get: function (key, callback) { 20 | return client.get(key, function (err, result) { 21 | return callback(err, result); 22 | }); 23 | }, 24 | 25 | //myCache.set( key, val, [ ttl ], [callback] ) 26 | set: function (key, val, ttl, callback) { 27 | //ttl is optional 28 | if (_.isFunction(ttl)) { 29 | callback = ttl; 30 | ttl = undefined; 31 | } 32 | 33 | //callback is optional 34 | callback = callback || _.noop; 35 | client.set(key, val, ttl, callback); 36 | }, 37 | 38 | stop: function () { 39 | client.close(); //kills the timer that checks for invalidation 40 | }, 41 | 42 | getClient: function () { 43 | return client; 44 | } 45 | }; 46 | 47 | }; 48 | -------------------------------------------------------------------------------- /lib/project.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var path = require('path'), 6 | _ = require('lodash'); 7 | 8 | var serverRoot = path.resolve(__dirname, '../'); 9 | 10 | module.exports = function(loader) { 11 | 12 | return { 13 | 14 | //Loads the config and logger services 15 | //This should always be called before initProject 16 | //For the master process, only bootstrap will be called 17 | bootstrap: function(callback) { 18 | var toInit = ['config', 'logger']; 19 | loader.loadServices(path.resolve(serverRoot, 'services')); 20 | injectDependencies(loader); 21 | 22 | loader.init(toInit, function(err) { 23 | callback(err); 24 | }); 25 | }, 26 | 27 | initProject: function(callback) { 28 | loader.loadServices(path.resolve(serverRoot, 'services')); 29 | injectDependencies(loader); 30 | var config = loader.get('config'); 31 | 32 | loader.loadServiceModules(config.get('services')); 33 | 34 | try { 35 | //failOnDup tells the loader to error out if there are any services with duplicate names 36 | //We surround in a try-catch to handle such a case 37 | //e.g. if a user tries to create a service named 'logger' 38 | loader.loadServices(path.resolve(global.__appDir, 'services'), true /*failOnDup*/); //app services 39 | } catch (err) { 40 | return callback(err); 41 | } 42 | 43 | loader.loadConsumerModules('handlers', config.get('handlers')); 44 | loader.loadConsumers(path.resolve(serverRoot, 'middleware'), 'middleware'); //BO middleware 45 | loader.loadConsumers(path.resolve(global.__appDir, 'middleware'), 'middleware'); //app middleware 46 | 47 | loader.loadConsumers(path.resolve(serverRoot, 'handlers'), 'handlers'); //BO handlers 48 | loader.loadConsumers(path.resolve(global.__appDir, 'handlers'), 'handlers'); //app handlers 49 | 50 | loader.loadConsumers(path.resolve(global.__appDir, 'auth'), 'auth'); //app authenticators 51 | loader.loadConsumers(path.resolve(serverRoot, 'auth'), 'auth'); //BO authenticators 52 | 53 | var serverProvisions = config.get('server-provisions'); 54 | _.forEach(serverProvisions, function(provision) { 55 | var root = path.resolve(global.__appDir, 'node_modules'); 56 | loader.loadServices(path.resolve(root, provision, 'services')); 57 | loader.loadConsumers(path.resolve(root, provision, 'middleware') , 'middleware'); 58 | }); 59 | 60 | loader.init(null, function(err) { 61 | callback(err); 62 | }); 63 | } 64 | }; 65 | 66 | }; 67 | 68 | //Injects items into the loader that aren't loaded through the normal services mechanism 69 | function injectDependencies(serviceLoader) { 70 | 71 | var EventEmitter = require('events').EventEmitter; 72 | serviceLoader.inject('events', new EventEmitter()); 73 | 74 | //inject itself so that services can directly use the service loader 75 | serviceLoader.inject('serviceLoader', serviceLoader); 76 | 77 | //app will be injected by middleware, so this is a placeholder to force our dependency calculations to be correct 78 | serviceLoader.inject('app', {}, ['middleware']); 79 | } 80 | 81 | -------------------------------------------------------------------------------- /lib/security.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var crypto = require('crypto'), 6 | prompt = require('prompt'), // jshint ignore:line 7 | _ = require('lodash'); 8 | 9 | var ENCRYPTED_TEXT_PATTERN = /^{([\w-]+)}(.*)=$/; 10 | /** 11 | * Decrypts a text string from the config 12 | * @param text a string of the format {}text= 13 | * @param key the key to decrypt the text 14 | * @returns a string 15 | */ 16 | module.exports.decrypt = function (text, key) { 17 | 18 | var match = ENCRYPTED_TEXT_PATTERN.exec(text); 19 | 20 | //match is null if it doesn't match the pattern 21 | if (match === null) { 22 | throw new Error('Invalid string: must be in the form {}text='); 23 | } 24 | 25 | var encoding = match[1]; 26 | var data = match[2]; 27 | var decipher = crypto.createDecipher(encoding, key); 28 | var dec = decipher.update(data, 'hex', 'utf8'); 29 | dec += decipher.final('utf8'); 30 | return dec; 31 | }; 32 | 33 | /** 34 | * Prompt for a password 35 | * @param callback callback(err, password) 36 | */ 37 | module.exports.promptForPassword = function (callback) { 38 | 39 | var properties = [ 40 | { 41 | description: 'Enter your password', 42 | name: 'password', 43 | hidden: true, 44 | type: 'string', 45 | pattern: /^\w+$/ 46 | } 47 | ]; 48 | prompt.start(); 49 | 50 | prompt.get(properties, function (err, result) { 51 | if (err) { 52 | return callback(err); 53 | } 54 | callback(null, result.password); 55 | }); 56 | }; 57 | 58 | 59 | function isEncrypted(str) { 60 | return ENCRYPTED_TEXT_PATTERN.test(str); 61 | } 62 | 63 | /** 64 | * Recurse through the provided json object, looking for strings that are encrypted 65 | * @param obj 66 | * @returns {*} 67 | */ 68 | function containsEncryptedData(obj) { 69 | var foundEncrypted = false; 70 | if (_.isString(obj)) { 71 | foundEncrypted = isEncrypted(obj); 72 | } else if (_.isArray(obj)) { 73 | for (var i = 0; i < obj.length; i++) { 74 | foundEncrypted = foundEncrypted || containsEncryptedData(obj[i]); 75 | } 76 | } else if (_.isObject(obj)) { 77 | for (var key in obj) { 78 | foundEncrypted = foundEncrypted || containsEncryptedData(obj[key]); 79 | } 80 | } 81 | 82 | return foundEncrypted; 83 | } 84 | 85 | /** 86 | * Recurse through an object looking for encrypted strings. If found, replace the string with the decrypted text. 87 | * @param obj 88 | * @param decryptFunction 89 | */ 90 | function decryptObject(obj, decryptFunction) { 91 | if (_.isArray(obj)) { 92 | for (var i = 0; i < obj.length; i++) { 93 | if (_.isString(obj[i])) { 94 | if (isEncrypted(obj[i])) { 95 | obj[i] = decryptFunction(obj[i]); 96 | } 97 | } else { 98 | decryptObject(obj[i], decryptFunction); 99 | } 100 | } 101 | } else if (_.isObject(obj)) { 102 | for (var key in obj) { 103 | if (_.isString(obj[key])) { 104 | if (isEncrypted(obj[key])) { 105 | obj[key] = decryptFunction(obj[key]); 106 | } 107 | } else { 108 | decryptObject(obj[key], decryptFunction); 109 | } 110 | } 111 | } 112 | } 113 | 114 | module.exports.containsEncryptedData = containsEncryptedData; 115 | module.exports.decryptObject = decryptObject; -------------------------------------------------------------------------------- /lib/subRequire.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | /* This provides some extended 'require' functionality needed for the loader 6 | * Imagine we have a handler with a dependency on a service X. 7 | * To load X, we do a require, which resolves to the location in node_modules/X 8 | * 9 | * Now X has a dependency on another service, Y. Y is located at node_modules/X/node_modules/Y. 10 | * If we do a require on Y, it will fail because require doesn't traverse down into submodules. 11 | * 12 | * subRequire provides a require(id, parentId) method, which is able to traverse down. 13 | * In this case we could call subRequire(Y, X), and it would require Y from the context of X. 14 | */ 15 | var path = require('path'), 16 | _ = require('lodash'); 17 | 18 | var modulePath = {}; //maintain a list of the paths where we resolved files - needed for unloading the modules 19 | 20 | /* 21 | * Similar to require(...). 22 | * 23 | * Can be used with JS files, e.g. require('/some/file.js') 24 | * Normal modules, e.g. require('someModule'), 25 | * but also provides a way to load submodules by passing the optional parentId argument. 26 | */ 27 | module.exports = function (id, parentId) { 28 | 29 | if (_.includes(id, '.js')) { 30 | return loadJsFile(id); 31 | } else { 32 | return loadModule(id, parentId); 33 | } 34 | }; 35 | 36 | //Attempts to delete a file from the require cache 37 | module.exports.unload = function (id) { 38 | var path = modulePath[id]; 39 | if (path && require.cache[path]) { 40 | delete require.cache[path]; 41 | modulePath[id] = null; 42 | delete modulePath[id]; 43 | } else { 44 | return false; 45 | } 46 | }; 47 | 48 | 49 | //Performs a require directly on a file 50 | //A module id is calculated based on the name of the file, e.g. 51 | // /some/dir/file.js has an id of 'file'. 52 | //That ID is added as a __id field of the module 53 | function loadJsFile(file) { 54 | var modId = path.basename(file).slice(0, -3); 55 | var mod = require(file); 56 | modulePath[modId] = file; 57 | mod.__id = modId; 58 | return mod; 59 | } 60 | 61 | /* 62 | * Load modules in the following order: 63 | * First try within the app's node_modules 64 | * Next, if this module is required by another module, load it relative to that other module 65 | * Finally, use the normal require call, which will look in the BO server's node_modules 66 | */ 67 | function loadModule(id, parentId) { 68 | var mod = null; 69 | try { 70 | mod = loadFromAppDir(id); 71 | } catch (err) { 72 | if (parentId) { 73 | try { 74 | mod = loadFromParent(id, parentId); 75 | } catch (err2) { 76 | mod = loadGlobally(id); 77 | } 78 | } else { 79 | mod = loadGlobally(id); 80 | } 81 | } 82 | mod.__id = id; 83 | return mod; 84 | } 85 | 86 | function loadFromAppDir(id) { 87 | var dir = path.resolve(global.__appDir, 'node_modules/' + id); 88 | var mod = require(dir); 89 | modulePath[id] = require.resolve(dir); 90 | return mod; 91 | } 92 | 93 | function loadGlobally(id) { 94 | var mod = require(id); 95 | modulePath[id] = require.resolve(id); 96 | return mod; 97 | } 98 | 99 | function loadFromParent(id, parentId) { 100 | var parentPath = modulePath[parentId]; 101 | var parentMod = require.cache[parentPath]; 102 | var mod = parentMod.require(id); //do a require from the context of the parent module 103 | 104 | //I don't like this, but it's the only way I've found to get the path of a child module 105 | //Normally we would do a require.resolve(id), but there's no way to do that within the context of a submodule 106 | //Instead, we search through the modules children and find the entry for the one we're interested in. 107 | //The id for that entry is the path to the module. 108 | var path = _.find(parentMod.children, {exports: mod}).id; 109 | modulePath[id] = path; 110 | return mod; 111 | } -------------------------------------------------------------------------------- /middleware/_errors.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | 6 | var _ = require('lodash'); 7 | 8 | exports.init = function (app, logger) { 9 | 10 | app.use(function (err, req, res, next) { 11 | 12 | //Validation errors are thrown by the swagger validator. 13 | //The can wrap a tv4 validation error which are passed under subErrors 14 | if (err.name === 'ValidationError') { 15 | var payload = { 16 | message: err.message, 17 | status: 422, 18 | type: 'ValidationError', 19 | source: err.source 20 | }; 21 | 22 | if (err.subErrors) { 23 | payload.validation_errors = _.map(err.subErrors, function (subError) { 24 | return _.pick(subError, ['message', 'schemaPath', 'model', 'code', 'field', 'in']); 25 | }); 26 | } 27 | res.status(422).json(payload); 28 | } else { 29 | logger.error(err.message, err.stack); 30 | res.status(500).send(err.message); 31 | } 32 | 33 | }); 34 | }; 35 | -------------------------------------------------------------------------------- /middleware/body-parser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | /** 6 | * Enables the body-parser middleware: https://github.com/expressjs/body-parser 7 | * 8 | * Body parser supports four types: urlencoded, json, raw, text, which each have their own options. 9 | * 10 | * To configure, add config for body parser for whichever type(s) you want to enable, e.g. 11 | * 12 | * "body-parser": { 13 | * "json": { 14 | * "strict": true 15 | * }, 16 | * 17 | * "urlencoded": { 18 | * "extended": false 19 | * } 20 | * } 21 | */ 22 | var bodyParser = require('body-parser'); 23 | var _ = require('lodash'); 24 | 25 | exports.init = function(app, config, logger) { 26 | 27 | //allow to be configured through both bodyParser and body-parser 28 | var cfg = _.assignIn({}, config.get('bodyParser'), config.get('body-parser')); 29 | 30 | var types = ['urlencoded', 'json', 'raw', 'text']; 31 | _.forEach(types, function (type) { 32 | if (cfg[type]) { 33 | logger.debug('Enabled body parser for type %s.', type); 34 | app.use(bodyParser[type](cfg[type])); 35 | } 36 | }); 37 | }; -------------------------------------------------------------------------------- /middleware/cors.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | /* 6 | * Use the node-cors library to enable CORS on all express.js apps and endpoints. 7 | * 8 | * All config options are passed directly to the cors library. 9 | * See https://github.com/troygoode/node-cors#configuration-options 10 | */ 11 | 12 | var cors = require('cors'); 13 | 14 | exports.init = function(app, config, logger, callback) { 15 | var cfg = config.get('cors'); 16 | app.use(cors(cfg)); 17 | logger.debug('Enabled CORS.'); 18 | callback(); 19 | }; 20 | -------------------------------------------------------------------------------- /middleware/csrf.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var whitelist = []; 6 | var _logger = null; 7 | 8 | exports.init = function(app, logger, config, callback) { 9 | 10 | _logger = logger; 11 | 12 | whitelist = config.get('csrf').allowedOrigins; 13 | if (!whitelist) { 14 | logger.warn('No allowedOrigins set for CSRF'); 15 | whitelist = []; 16 | } 17 | app.use(csrfCheck); 18 | logger.debug('Enabled CSRF protection.'); 19 | callback(); 20 | }; 21 | 22 | /* From the Origin spec 23 | * 24 | * If the request method is safe (as defined by RFC 2616, Section 9.1.1, e.g. either "GET" nor "HEAD"), 25 | * return "MUST NOT modify state" and abort these steps. 26 | * If the request does not contain a header named "Origin", return "MAY modify state" abort these steps. 27 | * For each request header named "Origin", let the /initiating origin list/ be the list of origins represented: 28 | * If there exists a origin in the /initiating origin list/ is not a member of the /origin white list/ for this server, 29 | * return "MUST NOT modify state" and abort these steps. 30 | * Return "MAY modify state". 31 | */ 32 | function csrfCheck(req, res, next) { 33 | 34 | //Only check for non-GET requests, since GETs don't modify state 35 | if ('GET' !== req.method && 'HEAD' !== req.method) { 36 | 37 | if (req.get('Origin')) { 38 | 39 | for (var i = 0; i < whitelist.length; i++) { 40 | //whitelist can also contain regexps 41 | if (whitelist[i] instanceof RegExp && whitelist[i].test(req.headers.origin)) { 42 | return next(); 43 | } else if (whitelist[i] === req.headers.origin) { 44 | return next(); 45 | } 46 | } 47 | 48 | _logger.warn('CSRF request failed for origin, %s', req.headers.origin); 49 | return res.sendStatus(400); 50 | } else { 51 | return next(); 52 | } 53 | } else { 54 | return next(); //no need to check origin 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /middleware/express-monitor.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.init = function(app, config, logger, monitor) { 6 | var cfg = config.get('express-monitor'); 7 | if (monitor.enabled()) { 8 | logger.debug('Enabled express monitor.'); 9 | app.use(monitor.express(cfg.prefix, true)); 10 | } 11 | 12 | }; -------------------------------------------------------------------------------- /middleware/express-static.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | /* 6 | * Use the express-static library to enable static file loading on all express.js apps and endpoints. 7 | * 8 | * All config options are passed directly to the express-static library. 9 | * See https://github.com/song940/express-static for more information 10 | * 11 | * Add a JSON block to the application default.json with the following: 12 | * "express-static" : { 13 | * "www": "./mywwwdir" 14 | * } 15 | * This will serve the static content from the mywwwdir directory relative to the default 16 | * application server directory. 17 | * 18 | * If you want to use a different context root, specify the "context" property. 19 | * 20 | * "express-static" : { 21 | * "www": "./mywwwdir", 22 | * "context": "/static" 23 | * } 24 | * 25 | * To enable: add "express-static" to the middleware list of services to load in default.json. 26 | */ 27 | 28 | var path = require('path'), 29 | es = require('express-static'); 30 | 31 | exports.init = function (app, config, logger) { 32 | var cfg = config.get('express-static'); 33 | var cfgDir = cfg.www; 34 | if (!cfgDir) { 35 | cfgDir = cfg.docs; 36 | } 37 | if (!cfgDir) { 38 | logger.warn('No document root is configured for express-static.'); 39 | } else { 40 | var docsDir = path.resolve(global.__appDir, cfgDir); 41 | if (typeof cfg.context == 'undefined') { 42 | logger.info('Serving static content from: %s.', docsDir); 43 | app.use(es(docsDir)); 44 | } else { 45 | logger.info('Serving static content from %s at %s.', docsDir, cfg.context); 46 | app.use(cfg.context, es(docsDir)); 47 | } 48 | 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /middleware/session.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | /* 6 | * Use the node-cors library to enable CORS on all express.js apps and endpoints. 7 | * 8 | * All config options are passed directly to the cors library. 9 | * See https://github.com/troygoode/node-cors#configuration-options 10 | */ 11 | 12 | 13 | var session = require('cookie-session'); 14 | 15 | 16 | exports.init = function(app, config, logger, callback) { 17 | var cfg = config.get('session'); 18 | if (cfg.type === 'cookie') { 19 | 20 | var keys = cfg.keys; 21 | if (!keys) { 22 | logger.error('Cookie sessions require a key to be set.'); 23 | } else { 24 | 25 | app.use(session({ 26 | keys: keys 27 | })); 28 | logger.debug('Enabled cookie session.'); 29 | } 30 | } else { 31 | logger.error('Unknown session type', cfg.type); 32 | } 33 | callback(); 34 | }; 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blueoak-server", 3 | "version": "2.13.1", 4 | "description": "BlueOak Server", 5 | "repository": { 6 | "type": "git", 7 | "url": "git@github.com:BlueOakJS/blueoak-server.git" 8 | }, 9 | "engines": { 10 | "node": ">=6" 11 | }, 12 | "license": "MIT", 13 | "main": "index.js", 14 | "author": "PointSource LLC ", 15 | "contributors": [ 16 | "David Roberts ", 17 | "Sean Kennedy ", 18 | "Erik Daughtrey", 19 | "Ryan Sheppard", 20 | "Erin Bartholomew", 21 | "Greg Considine", 22 | "Benjamin Schell", 23 | "Patrick Wolf" 24 | ], 25 | "keywords": [ 26 | "node", 27 | "integration", 28 | "framework", 29 | "swagger", 30 | "openapi", 31 | "rest", 32 | "express", 33 | "middleware", 34 | "api" 35 | ], 36 | "scripts": { 37 | "preversion": "npm test", 38 | "test": "npm run test-style && npm run test-unit && npm run test-integration", 39 | "test-unit": "JUNIT_REPORT_PATH=tests-report.xml mocha --check-leaks --reporter mocha-jenkins-reporter --globals services,__appDir test/unit", 40 | "test-integration": "mocha test/integration", 41 | "test-cov": "istanbul cover node_modules/mocha/bin/_mocha --report html -- test/unit", 42 | "test-style": "eslint index.js auth examples handlers lib middleware services test testlib && echo lint complete." 43 | }, 44 | "bin": { 45 | "blueoak-server": "bin/blueoak-server.js" 46 | }, 47 | "dependencies": { 48 | "async": "^1.0.0", 49 | "base64url": "^3.0.1", 50 | "body-parser": "^1.19.0", 51 | "cfenv": "^1.0.0", 52 | "config": "^1.21.0", 53 | "cookie-session": "^1.2.0", 54 | "cors": "^2.6.0", 55 | "debug": "^2.2.0", 56 | "express": "^4.17.1", 57 | "express-static": "^1.0.3", 58 | "import-fresh": "^2.0.0", 59 | "jsonwebtoken": "^5.4.1", 60 | "lodash": "^4.17.14", 61 | "multer": "^1.1.0", 62 | "node-cache": "^3.0.0", 63 | "node-statsd": "^0.1.1", 64 | "on-headers": "^1.0.0", 65 | "prompt": "^0.2.14", 66 | "raw-body": "^2.4.1", 67 | "redis": "^2.4.2", 68 | "request": "^2.88.0", 69 | "rsa-pem-from-mod-exp": "^0.8.4", 70 | "semver": "^5.1.0", 71 | "stack-trace": "0.0.9", 72 | "strip-json-comments": "^2.0.0", 73 | "swagger-parser": "^3.3.0", 74 | "tv4": "^1.1.9", 75 | "tv4-formats": "^1.0.0", 76 | "verror": "^1.6.0", 77 | "winston": "^1.1.0" 78 | }, 79 | "devDependencies": { 80 | "assert": "^1.5.0", 81 | "eslint": "^5.9.0", 82 | "eslint-plugin-lodash": "^6.0.0", 83 | "istanbul": "^0.4.5", 84 | "mocha": "^5.2.0", 85 | "mocha-jenkins-reporter": "^0.4.1" 86 | }, 87 | "files": [ 88 | "bin", 89 | "lib", 90 | "testlib", 91 | "middleware", 92 | "handlers", 93 | "services", 94 | "auth", 95 | "defaults.json", 96 | "index.js", 97 | "LICENSE" 98 | ] 99 | } 100 | -------------------------------------------------------------------------------- /services/auth.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | /* 6 | * Initializes the 'auth' consumers. 7 | * 8 | * For each authentication consumer, the express middleware is registered so that it can be accessed by calling 'get' 9 | * 10 | * Example: 11 | * 12 | * var expressFunc = auth.get('name'); 13 | * app.get('/route', expressFunc, function(req, res, next) {...}); 14 | * 15 | * In general apps shouldn't ever need to directly access the auth service. 16 | */ 17 | var _ = require('lodash'), 18 | vm = require('vm'); 19 | 20 | var callbacks = {}; 21 | var cfg = null; 22 | 23 | // the dependency on middleware ensures it starts after middleware 24 | module.exports.init = function(logger, config, app, middleware, serviceLoader, callback) { 25 | logger.info('Starting auth services'); 26 | cfg = config.get('auth'); 27 | 28 | serviceLoader.initConsumers('auth', function initAuthCallback(err) { 29 | 30 | //registerDeclarativeRoutes(middleware.getApp(), config.get('routes'), serviceLoader, logger); 31 | if (err) { 32 | return callback(err); 33 | } 34 | 35 | registerCallbacks(logger, serviceLoader); 36 | 37 | callback(); 38 | }); 39 | }; 40 | 41 | //Look up the middleware functions for the global auth, as well as 42 | //any specified under additionalAuthNames, e.g. names of auth on individual routes. 43 | //Return an array of middleware functions 44 | module.exports.getAuthMiddleware = function(additionalAuthNames) { 45 | var middleware = []; 46 | var authNames = []; 47 | authNames = authNames.concat(cfg.provider || []); 48 | authNames = authNames.concat(additionalAuthNames || []); 49 | 50 | if (authNames.length > 0) { 51 | _.forEach(authNames, function (name) { 52 | var authCallback = exports.get(name); 53 | middleware.push(authCallback); 54 | }); 55 | } 56 | 57 | return middleware; 58 | }; 59 | 60 | 61 | function registerCallbacks(logger, serviceLoader) { 62 | var authList = serviceLoader.getConsumers('auth'); 63 | _.forEach(authList, function(auther) { 64 | if (auther.authenticate) { 65 | callbacks[auther.__id] = auther.authenticate; 66 | } else { 67 | logger.error('%s does not contain an authenticate or roles method.', auther.__id); 68 | } 69 | }); 70 | } 71 | 72 | module.exports.get = function(name) { 73 | if (!callbacks[name]) { 74 | throw new Error('Cannot find auth named ' + name); 75 | } 76 | return callbacks[name]; 77 | }; 78 | 79 | //Returns middleware that will evaluate the given expression. 80 | //Expressions are evaluated in a sandbox. 81 | //Currently the request's user data is the only thing exposed to the validator (via $user object). 82 | //If expression returns true, the request is allowed. Otherwise it returns 401. 83 | module.exports.validate = function(expression) { 84 | return function(req, res, next) { 85 | var sandbox = { 86 | '$user': req.user 87 | }; 88 | 89 | try { 90 | var result = vm.runInNewContext(expression, sandbox); 91 | if (result === true) { 92 | return next(); 93 | } else { 94 | return res.sendStatus(401); 95 | } 96 | } catch (err) { 97 | services.get('logger').error('Error evaluating validator expression %s: %s', expression, err.toString()); 98 | return res.sendStatus(500); 99 | } 100 | 101 | }; 102 | }; -------------------------------------------------------------------------------- /services/cache.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | //Provides a cache service that can support multiple underlying interfaces 6 | //if the "type" field is set to "redis", we use a redis interface, 7 | //otherwise we default to a node-cache interface. 8 | 9 | var _ = require('lodash'); 10 | 11 | exports.init = function (monitor, config) { 12 | 13 | var cfg = config.get('cache'); 14 | 15 | var impl = null; 16 | if (cfg.type === 'redis') { 17 | impl = services.get('redis').cacheInterface; 18 | } else { 19 | cfg = config.get('node-cache'); 20 | impl = require('../lib/nodeCacheInterface')(cfg); 21 | } 22 | 23 | exports.stop = function() { 24 | impl.stop(); 25 | }; 26 | 27 | exports.getClient = function () { 28 | return impl['getClient'].apply(impl, arguments); 29 | }; 30 | 31 | exports.get = function (key, callback) { 32 | 33 | //callback is optional 34 | callback = callback || _.noop; 35 | 36 | function cb(err, result) { 37 | var keyStr = (_.isString(key)) ? key : null; 38 | if (err) { 39 | if (keyStr) { 40 | monitor.increment(keyStr + '.' + 'cacheMisses', 1); 41 | } 42 | monitor.increment('cacheMisses', 1); 43 | return callback(err); 44 | } else { 45 | if (result/*result[key]*/) { 46 | if (keyStr) { 47 | monitor.increment(keyStr + '.' + 'cacheHits', 1); 48 | } 49 | monitor.increment('cacheHits', 1); 50 | return callback(null, result); //result[key] 51 | } else { 52 | if (keyStr) { 53 | monitor.increment(keyStr + '.' + 'cacheMisses', 1); 54 | } 55 | monitor.increment('cacheMisses', 1); 56 | //If key doesn't exist, send null 57 | return callback(null, null); 58 | } 59 | } 60 | } 61 | 62 | return impl.get(key, cb); 63 | }; 64 | 65 | exports.set = function (key, value, ttl, callback) { 66 | if (_.isFunction(ttl)) { 67 | callback = ttl; 68 | ttl = undefined; 69 | } 70 | //callback is optional 71 | callback = callback || _.noop; 72 | 73 | function cb(err, success) { 74 | var keyStr = (_.isString(key)) ? key : null; 75 | if (!err && success) { 76 | if (keyStr) { 77 | monitor.increment(keyStr + '.' + 'cacheUpdates', 1); 78 | } 79 | monitor.increment('cacheUpdates', 1); 80 | } 81 | callback(err, success); 82 | } 83 | 84 | return impl.set(key, value, ttl, cb); 85 | }; 86 | }; 87 | 88 | //If we're going to use redis, we can't init the cache until redis and all its dependencies have been initialized 89 | exports.getDependencies = function (serviceLoader) { 90 | var cfg = serviceLoader.get('config').get('cache'); 91 | if (cfg.type === 'redis') { 92 | return ['redis']; 93 | } 94 | return []; //nothing else needed for node cache 95 | }; 96 | 97 | 98 | -------------------------------------------------------------------------------- /services/config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var _ = require('lodash'), 6 | stripJsonComments = require('strip-json-comments'), 7 | fs = require('fs'), 8 | cluster = require('cluster'), 9 | security = require('../lib/security'), 10 | path = require('path'), 11 | importFresh = require('import-fresh'); 12 | 13 | var config = null; 14 | 15 | //These are the default config values for anything not specified in the app's config dir 16 | var defaults = {}; 17 | 18 | var individualKeyCache = {}; //when we load a specific key file, e.g. routes.json, store the content here 19 | 20 | exports.init = function(callback) { 21 | process.env.NODE_CONFIG_DIR = path.resolve(global.__appDir, 'config'); 22 | config = importFresh('config'); 23 | 24 | fs.readFile(path.join(__dirname, '/../defaults.json'), function (err, data) { 25 | if (err) { 26 | return callback(err); 27 | } 28 | defaults = JSON.parse(stripJsonComments(data.toString())); 29 | 30 | //We only ever want to check for a password in the cluster scenario, and then let the master 31 | //pass the password to the individual workers 32 | // 33 | //if process.env.decryptionKey is set, we're in single worker mode. 34 | //So even though we are a master, act more like a worker. 35 | if (cluster.isMaster && !exports.decryptionKey) { 36 | //Check if any of the config has encrypted data, in which case we need to prompt for a password 37 | if (security.containsEncryptedData(config)) { 38 | getPassword(function(err, result) { 39 | if (err) { 40 | return callback(err); 41 | } else { 42 | exports.decryptionKey = result; 43 | security.decryptObject(config, function (str) { 44 | //Decryption function 45 | return security.decrypt(str, result); 46 | }); 47 | return callback(); 48 | } 49 | }); 50 | } else { 51 | return callback(); 52 | } 53 | } else { 54 | //If we're a worker process, we expect that the master has set the decryptionKey as an env variable 55 | if (process.env.decryptionKey) { 56 | try { 57 | security.decryptObject(config, function (str) { 58 | //Decryption function 59 | return security.decrypt(str, process.env.decryptionKey); 60 | }); 61 | } catch (err) { 62 | return callback(new Error('Could not decrypt keys: ' + err.message)); 63 | } 64 | } 65 | return callback(); 66 | } 67 | }); 68 | 69 | }; 70 | 71 | exports.get = function(key) { 72 | //Load default config, merge in user config, and then the individual config files 73 | var val = defaults[key] || {}; 74 | val = config.util.extendDeep(val, config[key]); 75 | return config.util.extendDeep(val, loadFromIndividualConfigFile(key)); 76 | }; 77 | 78 | //For every requested config key, check if there's a json file by that name in the config dir. 79 | //If there is, load the contents and return it so that it can be merged in. 80 | function loadFromIndividualConfigFile(key) { 81 | key = _.replace(key, /\/|\\/g, ''); //forward and backslashes are unsafe when resolving filenames 82 | 83 | if (individualKeyCache[key]) { 84 | return individualKeyCache[key]; 85 | } else { 86 | var toLoad = path.resolve(global.__appDir, 'config/' + key + '.json'); 87 | 88 | var content = '{}'; 89 | try { 90 | content = fs.readFileSync(toLoad); 91 | } catch (err) { 92 | //file must not exists 93 | } 94 | 95 | var json = {}; 96 | try { 97 | json = JSON.parse(stripJsonComments(content.toString())); 98 | } catch (err) { 99 | console.warn('Error parsing JSON for %s', toLoad); 100 | } 101 | 102 | security.decryptObject(json, function (str) { 103 | //Decryption function 104 | return security.decrypt(str, process.env.decryptionKey); 105 | }); 106 | individualKeyCache[key] = json; 107 | 108 | return individualKeyCache[key]; 109 | } 110 | } 111 | 112 | 113 | function getPassword(callback) { 114 | //Check first if we have a config value for the key 115 | var key = exports.get('security').key; 116 | if (key) { 117 | return callback(null, key); 118 | } else if (process.env.decryptionKey) { //already set in env variable 119 | return callback(null, process.env.decryptionKey); 120 | } 121 | 122 | security.promptForPassword(callback); 123 | } 124 | 125 | -------------------------------------------------------------------------------- /services/middleware.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | /* 6 | * The express service takes care of creating the express apps, registering handlers, and then starting the server 7 | * listening on each configured port. 8 | */ 9 | var express = require('express'), 10 | di = require('../lib/di'), 11 | _ = require('lodash'); 12 | 13 | var app = null; 14 | 15 | exports.init = function(config, serviceLoader, callback) { 16 | app = express(); 17 | 18 | serviceLoader.inject('app', app); 19 | 20 | var cfg = config.get('express'); 21 | //this should catch middleware that exists in node modules instead of a /middleware directory 22 | _.forEach(cfg.middleware, function (middleware) { 23 | if (!serviceLoader.getConsumer('middleware', middleware)) { 24 | try { 25 | serviceLoader.loadConsumerModules('middleware', [middleware]); 26 | } catch (err) { 27 | return callback(err); 28 | } 29 | } 30 | }); 31 | 32 | //middleware 33 | serviceLoader.initConsumers('middleware', cfg.middleware || [], function initMiddlewareCallback(err) { 34 | return callback(err); 35 | }); 36 | }; 37 | 38 | exports.getApp = function() { 39 | return app; 40 | }; 41 | 42 | exports.getDependencies = function(serviceLoader) { 43 | var mods = serviceLoader.getConsumers('middleware'); 44 | var params = []; 45 | for (var i = 0; i < mods.length; i++) { 46 | params = params.concat(di.getParamNames(mods[i].init)); 47 | } 48 | var result = _.uniq(params); 49 | return _.without(result, 'app'); 50 | }; -------------------------------------------------------------------------------- /services/monitor.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var _ = require('lodash'), 6 | StatsD = require('node-statsd'), 7 | onHeaders = require('on-headers'); 8 | 9 | var client; 10 | var enabled = false; 11 | var methodsToExpose = ['increment', 'decrement', 'set', 'unique', 'gauge', 'histogram', 'timing']; 12 | 13 | exports.init = function(config, logger) { 14 | var cfg = config.get('monitor'); 15 | 16 | if (!cfg.host) { //have to have a host in order to monitor 17 | logger.info('Monitoring is disabled.'); 18 | } else { 19 | client = new StatsD(cfg); 20 | enabled = true; 21 | } 22 | 23 | //Wrap all the methods in sdc with either a call to sdc or a no-op if monitoring is disabled 24 | _.forEach(methodsToExpose, function(name) { 25 | if (enabled) { 26 | exports[name] = function() { 27 | client[name].apply(client, arguments); 28 | }; 29 | } else { 30 | exports[name] = _.noop; 31 | } 32 | }); 33 | }; 34 | 35 | exports.enabled = function() { 36 | return enabled; 37 | }; 38 | 39 | exports.stop = function() { 40 | if (enabled && client) { 41 | try { 42 | client.close(); 43 | } catch (err) { 44 | //probably not running; 45 | } 46 | } 47 | }; 48 | 49 | exports.express = function(prefix, genRoute) { 50 | if (!enabled) { 51 | return []; //make this a no-op if monitoring is disabled 52 | } 53 | return function (req, res, next) { 54 | 55 | var startTime = new Date().getTime(); 56 | 57 | onHeaders(res, function() { 58 | if (!req.route) { 59 | return; 60 | } 61 | 62 | var key = ''; 63 | if (genRoute) { 64 | var routeName = req.route.path; 65 | if (Object.prototype.toString.call(routeName) === '[object RegExp]') { 66 | // Might want to do some sanitation here? 67 | routeName = routeName.source; 68 | } 69 | 70 | if (routeName === '/') { 71 | routeName = 'root.'; 72 | } 73 | routeName = req.method + routeName; 74 | // eslint-disable-next-line lodash/prefer-lodash-method 75 | routeName = routeName.replace(/:/g, '').replace(/^\/|\/$/g, '').replace(/\//g, '.'); 76 | key = prefix + '.' + routeName + '.'; 77 | } else { 78 | key = prefix + '.'; 79 | } 80 | 81 | // Status Code 82 | var statusCode = res.statusCode || 'unknown_status'; 83 | client.increment(key + statusCode); 84 | 85 | // Response Time 86 | var duration = new Date().getTime() - startTime; 87 | client.timing(key + 'response_time', duration); 88 | }); 89 | 90 | if (next) { 91 | return next(); 92 | } 93 | }; 94 | }; 95 | -------------------------------------------------------------------------------- /services/postMiddleware.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | /* 6 | * post middleware runs after all the express handlers have been registered. 7 | * 8 | * defined in the config under the 'middleware$' key. 9 | * It's useful for things like error handlers. 10 | * 11 | * We always load the built-in error handler, middleware/_errors.js, last. 12 | * That way an app gets the opportunity to handle the error themselves first. 13 | */ 14 | 15 | var _ = require('lodash'); 16 | 17 | exports.init = function(config, serviceLoader, express, callback) { 18 | 19 | var cfg = config.get('express'); 20 | var postMiddleware = cfg['middleware$'] || []; 21 | postMiddleware = postMiddleware.concat('_errors'); 22 | //this should catch middleware that exists in node modules instead of a /middleware directory 23 | _.forEach(postMiddleware, function (middleware) { 24 | if (!serviceLoader.getConsumer('middleware', middleware)) { 25 | try { 26 | serviceLoader.loadConsumerModules('middleware', [middleware]); 27 | } catch (err) { 28 | return callback(err); 29 | } 30 | } 31 | }); 32 | serviceLoader.initConsumers('middleware', postMiddleware, function initPostHandlerCallback(err) { 33 | callback(err); 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /services/redis.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var _ = require('lodash'), 6 | redis = require('redis'); 7 | 8 | var client = null; 9 | 10 | exports.init = function (logger, config, callback) { 11 | 12 | var cfg = config.get('redis'); 13 | 14 | if (cfg.host && cfg.port) { 15 | logger.info('Redis service is enabled'); 16 | } else if (config.get('cache').type === 'redis') { 17 | //TODO: Should probably have a way to fail if someone is accessing the redis service directly 18 | //with config is missing, vs accessing it through cache service 19 | return callback(new Error('Must specify host and port for Redis')); 20 | } else { 21 | return callback(); 22 | } 23 | 24 | var reconnecting = false; //becomes true if we're attempting to reconnect 25 | client = redis.createClient(cfg.port, cfg.host, cfg.options); 26 | client.on('error', function (err) { 27 | //this gets called on every failed connect attempt 28 | 29 | if (callback) { 30 | client.end(); //stop trying to reconnect during server startup 31 | return callback(err); 32 | } 33 | }); 34 | 35 | client.on('reconnecting', function (data) { 36 | if (!reconnecting) { //ensures message only logged once 37 | logger.error('Disconnected from redis.'); 38 | reconnecting = true; 39 | } 40 | logger.debug('Reconnecting to redis, attempt #%s', data.attempt); 41 | }); 42 | 43 | client.on('connect', function () { 44 | reconnecting = false; 45 | if (!callback) { 46 | //must be reconnected, ignore 47 | logger.info('Reconnected to redis'); 48 | return; 49 | } 50 | 51 | logger.info('Connected to redis on %s:%s', cfg.host, cfg.port); 52 | var originCallback = callback; 53 | callback = null; //make sure it's not called if we disconnect 54 | return originCallback(); 55 | }); 56 | }; 57 | 58 | 59 | exports.cacheInterface = { 60 | 61 | get: function(key, callback) { 62 | client.get(key, function(err, result) { 63 | if (err) { 64 | return callback(err); 65 | } else { 66 | return callback(null, JSON.parse(result)); 67 | } 68 | }); 69 | }, 70 | 71 | set: function(key, val, ttl, callback) { 72 | //ttl is optional 73 | if (_.isFunction(ttl)) { 74 | callback = ttl; 75 | ttl = undefined; 76 | } 77 | 78 | //callback is optional 79 | callback = callback || _.noop; 80 | 81 | //redis doesn't handle JSON data, so stringify it ourselves 82 | val = JSON.stringify(val); 83 | 84 | client.set(key, val, function() { 85 | if (ttl) { 86 | //we also have to explicitly set the expiration in a separate call 87 | client.expire(key, ttl, function () { 88 | callback(); 89 | }); 90 | } else { 91 | return callback(); 92 | } 93 | }); 94 | }, 95 | 96 | stop: function() { 97 | client.quit(); 98 | }, 99 | 100 | getClient: function() { 101 | return client; 102 | } 103 | 104 | }; 105 | 106 | exports.getClient = function() { 107 | return client; 108 | }; 109 | -------------------------------------------------------------------------------- /services/stats.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var async = require('async'); 6 | var loader; 7 | 8 | exports.init = function (serviceLoader) { 9 | loader = serviceLoader; 10 | }; 11 | 12 | //Query all the services and invoke the stats method if it exists 13 | function collectStats(callback) { 14 | var serviceStats = {}; 15 | var services = loader.listServices(); 16 | async.each(services, function(service, callback) { 17 | var mod = loader.get(service); 18 | if (mod.stats) { //does it have a stats function? 19 | invokeStatsOnMod(mod, function(err, data) { 20 | serviceStats[service] = data; 21 | return callback(err); 22 | }); 23 | } else { 24 | return callback(); //nothing to do, no stats method exposed 25 | } 26 | 27 | }, function(err) { 28 | callback(err, serviceStats); 29 | }); 30 | 31 | } 32 | 33 | function invokeStatsOnMod(mod, callback) { 34 | mod.stats(callback); 35 | } 36 | 37 | exports.getStats = function (callback) { 38 | collectStats(callback); 39 | }; -------------------------------------------------------------------------------- /services/system.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var os = require('os'); 6 | var _ = require('lodash'); 7 | 8 | 9 | //this mod is only used to get stats about the system 10 | exports.init = _.noop; 11 | 12 | function getBlueOakVersion() { 13 | var pkg = require('../package.json'); 14 | return pkg.version; 15 | } 16 | 17 | exports.stats = function (callback) { 18 | var versions = { 19 | 'blueoak-server': getBlueOakVersion() 20 | }; 21 | 22 | _.assignIn(versions, process.versions); 23 | var stats = { 24 | os: { 25 | hostname: os.hostname(), 26 | arch: os.arch(), 27 | type: os.type(), 28 | release: os.release() 29 | }, 30 | versions: versions 31 | }; 32 | 33 | callback(undefined, stats); 34 | }; -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "mocha": true 5 | }, 6 | "globals": { 7 | "services": true 8 | } 9 | } -------------------------------------------------------------------------------- /test/integration/fixtures/manual-server-starter.js: -------------------------------------------------------------------------------- 1 | var testServer = require('../launchUtil'), 2 | readline = require('readline'); 3 | 4 | var fixtureName; 5 | if (process.argv.length < 3) { 6 | console.log('Provide the name of the fixture to start as an argument.'); 7 | console.log('e.g.:', process.argv[0], process.argv[1], 'server1'); 8 | process.exit(3); 9 | } else { 10 | fixtureName = process.argv[2]; 11 | } 12 | 13 | testServer.launch(fixtureName, function (err, child) { 14 | console.log(err || 'started ' + fixtureName); 15 | var rl = readline.createInterface({ 16 | input: process.stdin, 17 | output: process.stdout 18 | }); 19 | 20 | rl.question('Type anything to quit: ', function (answer) { 21 | rl.close(); 22 | testServer.finish(function () { 23 | console.log('ended'); 24 | process.exit(0); 25 | }); 26 | }); 27 | }); 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/integration/fixtures/server1/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "express": { 3 | "port": "5000" 4 | }, 5 | 6 | "cluster": { 7 | "maxWorkers": 1 8 | } 9 | } -------------------------------------------------------------------------------- /test/integration/fixtures/server1/handlers/handler1.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | 6 | module.exports.init = function(app) { 7 | app.get('/endpoint1', function(req, res) { 8 | res.status(200).json({name: 'endpoint1'}); 9 | }); 10 | 11 | app.post('/endpoint1', function(req, res) { 12 | res.status(200).json({name: 'endpoint1'}); 13 | }); 14 | 15 | app.put('/endpoint1', function(req, res) { 16 | res.status(200).json({name: 'endpoint1'}); 17 | }); 18 | 19 | app.delete('/endpoint1', function(req, res) { 20 | res.status(200).json({name: 'endpoint1'}); 21 | }); 22 | 23 | }; -------------------------------------------------------------------------------- /test/integration/fixtures/server10/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "express": { 3 | }, 4 | 5 | "cluster": { 6 | "maxWorkers": 1 7 | } 8 | } -------------------------------------------------------------------------------- /test/integration/fixtures/server10/handlers/test.handler.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | // eslint-disable-next-line lodash/prefer-noop 6 | exports.init = function() { 7 | //I should fail because there's already a logger service 8 | }; -------------------------------------------------------------------------------- /test/integration/fixtures/server11/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "express": { 3 | "port": "5000", 4 | "middleware": ["session", "body-parser", "dummy-middleware"], 5 | "middleware$": ["test-post-middleware"] 6 | }, 7 | 8 | "cluster": { 9 | "maxWorkers": 1 10 | }, 11 | 12 | "session": { 13 | "keys": ["sessionkey"] 14 | }, 15 | 16 | "body-parser": { 17 | "json": {} 18 | }, 19 | 20 | "logger": { 21 | "transports": [ 22 | { 23 | "package": "winston", 24 | "field": "transports.Console", 25 | "options": { 26 | "level": "debug", 27 | "colorize": true, 28 | "timestamp": true 29 | } 30 | } 31 | ] 32 | }, 33 | 34 | "swagger": { 35 | "validateResponseModels": "fail" 36 | } 37 | } -------------------------------------------------------------------------------- /test/integration/fixtures/server11/handlers/foo.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.pets5 = function(req, res, next) { 6 | res.json({name: 'pets5'}); 7 | }; -------------------------------------------------------------------------------- /test/integration/fixtures/server11/handlers/petstore.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.pets1 = function (req, res, next) { 6 | res.send({ 7 | name: 'pets1', 8 | id: 1 9 | }); 10 | }; 11 | 12 | exports.pets2 = function (req, res, next) { 13 | res.send({ 14 | name: 'pets2' 15 | }); 16 | }; 17 | 18 | exports.pets3 = function (req, res, next) { 19 | res.json({ 20 | name: 'pets3' 21 | }); 22 | }; 23 | 24 | exports.pets4 = function (req, res, next) { 25 | res.json({ 26 | name: 'pets4' 27 | }); 28 | }; 29 | 30 | exports.pets22 = function (req, res, next) { 31 | res.status(201).send(req.body); 32 | }; 33 | -------------------------------------------------------------------------------- /test/integration/fixtures/server11/node_modules/dummy-middleware/index.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line lodash/prefer-noop 2 | exports.init = function () { 3 | 4 | }; 5 | -------------------------------------------------------------------------------- /test/integration/fixtures/server11/node_modules/test-post-middleware/index.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line lodash/prefer-noop 2 | exports.init = function () { 3 | 4 | }; 5 | -------------------------------------------------------------------------------- /test/integration/fixtures/server11/swagger/petstore.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "1.0.0", 5 | "title": "Swagger Petstore", 6 | "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", 7 | "termsOfService": "http://helloreverb.com/terms/", 8 | "contact": { 9 | "name": "Wordnik API Team", 10 | "email": "foo@example.com", 11 | "url": "http://madskristensen.net" 12 | }, 13 | "license": { 14 | "name": "MIT", 15 | "url": "http://github.com/gruntjs/grunt/blob/master/LICENSE-MIT" 16 | } 17 | }, 18 | "host": "petstore.swagger.wordnik.com", 19 | "basePath": "/api", 20 | "schemes": [ 21 | "http" 22 | ], 23 | "consumes": [ 24 | "application/json" 25 | ], 26 | "produces": [ 27 | "application/json" 28 | ], 29 | "paths": { 30 | "/pets1": { 31 | "get": { 32 | "description": "Returns a user based on a single ID, if the user does not have access to the pet", 33 | "operationId": "pets1", 34 | "produces": [ 35 | "application/json", 36 | "application/xml", 37 | "text/xml", 38 | "text/html" 39 | ], 40 | "responses": { 41 | "200": { 42 | "description": "pet response", 43 | "schema": { 44 | "$ref": "#/definitions/pet" 45 | } 46 | }, 47 | "default": { 48 | "description": "unexpected error", 49 | "schema": { 50 | "$ref": "#/definitions/errorModel" 51 | } 52 | } 53 | } 54 | } 55 | }, 56 | "/pets3": { 57 | "get": { 58 | "x-middleware": "pets3", 59 | "description": "Returns a user based on a single ID, if the user does not have access to the pet", 60 | "operationId": "pets1", 61 | "produces": [ 62 | "application/json", 63 | "application/xml", 64 | "text/xml", 65 | "text/html" 66 | ], 67 | "responses": { 68 | "200": { 69 | "description": "pet response", 70 | "schema": { 71 | "$ref": "#/definitions/pet" 72 | } 73 | }, 74 | "default": { 75 | "description": "unexpected error", 76 | "schema": { 77 | "$ref": "#/definitions/errorModel" 78 | } 79 | } 80 | } 81 | } 82 | }, 83 | "/pets4": { 84 | "get": { 85 | "x-middleware": ["pets4"], 86 | "description": "Returns a user based on a single ID, if the user does not have access to the pet", 87 | "operationId": "pets1", 88 | "produces": [ 89 | "application/json", 90 | "application/xml", 91 | "text/xml", 92 | "text/html" 93 | ], 94 | "responses": { 95 | "200": { 96 | "description": "pet response", 97 | "schema": { 98 | "$ref": "#/definitions/pet" 99 | } 100 | }, 101 | "default": { 102 | "description": "unexpected error", 103 | "schema": { 104 | "$ref": "#/definitions/errorModel" 105 | } 106 | } 107 | } 108 | } 109 | }, 110 | "/pets5": { 111 | "get": { 112 | "x-middleware": ["foo.pets5"], 113 | "description": "Returns a user based on a single ID, if the user does not have access to the pet", 114 | "operationId": "pets1", 115 | "produces": [ 116 | "application/json", 117 | "application/xml", 118 | "text/xml", 119 | "text/html" 120 | ], 121 | "responses": { 122 | "200": { 123 | "description": "pet response", 124 | "schema": { 125 | "$ref": "#/definitions/pet" 126 | } 127 | }, 128 | "default": { 129 | "description": "unexpected error", 130 | "schema": { 131 | "$ref": "#/definitions/errorModel" 132 | } 133 | } 134 | } 135 | } 136 | } 137 | }, 138 | "definitions": { 139 | "pet": { 140 | "required": [ 141 | "id", 142 | "name" 143 | ], 144 | "properties": { 145 | "id": { 146 | "type": "integer", 147 | "format": "int64" 148 | }, 149 | "name": { 150 | "type": "string" 151 | }, 152 | "tag": { 153 | "type": "string" 154 | } 155 | } 156 | }, 157 | "newPet": { 158 | "allOf": [ 159 | { 160 | "$ref": "#/definitions/pet" 161 | }, 162 | { 163 | "required": [ 164 | "name" 165 | ], 166 | "properties": { 167 | "id": { 168 | "type": "integer", 169 | "format": "int64" 170 | } 171 | } 172 | } 173 | ] 174 | }, 175 | "errorModel": { 176 | "required": [ 177 | "code", 178 | "message" 179 | ], 180 | "properties": { 181 | "code": { 182 | "type": "integer", 183 | "format": "int32" 184 | }, 185 | "message": { 186 | "type": "string" 187 | } 188 | } 189 | } 190 | } 191 | } -------------------------------------------------------------------------------- /test/integration/fixtures/server11/swagger/petstore2.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "1.0.0", 5 | "title": "Swagger Petstore", 6 | "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", 7 | "termsOfService": "http://helloreverb.com/terms/", 8 | "contact": { 9 | "name": "Wordnik API Team", 10 | "email": "foo@example.com", 11 | "url": "http://madskristensen.net" 12 | }, 13 | "license": { 14 | "name": "MIT", 15 | "url": "http://github.com/gruntjs/grunt/blob/master/LICENSE-MIT" 16 | } 17 | }, 18 | "host": "petstore.swagger.wordnik.com", 19 | "basePath": "/api", 20 | "schemes": [ 21 | "http" 22 | ], 23 | "consumes": [ 24 | "application/json" 25 | ], 26 | "produces": [ 27 | "application/json" 28 | ], 29 | "paths": { 30 | "/pets2": { 31 | "x-handler": "petstore", 32 | "get": { 33 | "description": "Returns a user based on a single ID, if the user does not have access to the pet", 34 | "operationId": "pets2", 35 | "produces": [ 36 | "application/json", 37 | "application/xml", 38 | "text/xml", 39 | "text/html" 40 | ], 41 | "responses": { 42 | "200": { 43 | "description": "pet response", 44 | "schema": { 45 | "$ref": "#/definitions/pet" 46 | } 47 | }, 48 | "default": { 49 | "description": "unexpected error", 50 | "schema": { 51 | "$ref": "#/definitions/errorModel" 52 | } 53 | } 54 | } 55 | }, 56 | "post": { 57 | "description": "Adds a new pet", 58 | "operationId": "pets22", 59 | "parameters": [ 60 | { 61 | "in": "body", 62 | "name": "body", 63 | "description": "new pet object", 64 | "required": true, 65 | "schema": { 66 | "$ref": "#/definitions/pet" 67 | } 68 | } 69 | ], 70 | "responses": { 71 | "201": { 72 | "description": "pet added", 73 | "schema": { 74 | "$ref": "#/definitions/pet" 75 | } 76 | }, 77 | "422": { 78 | "description": "Request Validation Error" 79 | }, 80 | "default": { 81 | "description": "unexpected error", 82 | "schema": { 83 | "$ref": "#/definitions/errorModel" 84 | } 85 | } 86 | } 87 | } 88 | } 89 | }, 90 | "definitions": { 91 | "pet": { 92 | "required": [ 93 | "id", 94 | "name" 95 | ], 96 | "properties": { 97 | "id": { 98 | "type": "integer", 99 | "format": "int64" 100 | }, 101 | "name": { 102 | "type": "string" 103 | }, 104 | "tag": { 105 | "type": "string" 106 | } 107 | } 108 | }, 109 | "newPet": { 110 | "allOf": [ 111 | { 112 | "$ref": "#/definitions/pet" 113 | }, 114 | { 115 | "required": [ 116 | "name" 117 | ], 118 | "properties": { 119 | "id": { 120 | "type": "integer", 121 | "format": "int64" 122 | } 123 | } 124 | } 125 | ] 126 | }, 127 | "errorModel": { 128 | "required": [ 129 | "code", 130 | "message" 131 | ], 132 | "properties": { 133 | "code": { 134 | "type": "integer", 135 | "format": "int32" 136 | }, 137 | "message": { 138 | "type": "string" 139 | } 140 | } 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /test/integration/fixtures/server2/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "express": { 3 | "port": "5000" 4 | }, 5 | 6 | "cluster": { 7 | "maxWorkers": 1 8 | } 9 | } -------------------------------------------------------------------------------- /test/integration/fixtures/server2/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "get /endpoint2": { 3 | "handler": "handler2.get" 4 | }, 5 | "put /endpoint2": { 6 | "handler": "handler2.put" 7 | }, 8 | "post /endpoint2": { 9 | "handler": "handler2.post" 10 | }, 11 | "delete /endpoint2": { 12 | "handler": "handler2.delete" 13 | }, 14 | "all /endpoint3": { 15 | "handler": "handler2.all" 16 | }, 17 | "get /endpoint4": { 18 | "handler": "camelCase.get" 19 | } 20 | } -------------------------------------------------------------------------------- /test/integration/fixtures/server2/handlers/camelCase.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | 6 | module.exports.get = function(req, res) { 7 | console.log('GET!'); 8 | res.status(200).json({name: 'endpoint4'}); 9 | }; -------------------------------------------------------------------------------- /test/integration/fixtures/server2/handlers/handler2.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | 6 | module.exports.get = function(req, res) { 7 | res.status(200).json({name: 'endpoint2'}); 8 | }; 9 | 10 | module.exports.put = function(req, res) { 11 | res.status(200).json({name: 'endpoint2'}); 12 | }; 13 | 14 | module.exports.post = function(req, res) { 15 | res.status(200).json({name: 'endpoint2'}); 16 | }; 17 | 18 | module.exports.delete = function(req, res) { 19 | res.status(200).json({name: 'endpoint2'}); 20 | }; 21 | 22 | module.exports.all = function(req, res) { 23 | res.status(200).json({name: 'endpoint3'}); 24 | }; -------------------------------------------------------------------------------- /test/integration/fixtures/server3/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "express": { 3 | "port": "5000", 4 | "middleware": ["session", "bodyParser"] 5 | }, 6 | 7 | "cluster": { 8 | "maxWorkers": 1 9 | }, 10 | 11 | "session": { 12 | "keys": ["sessionkey"] 13 | }, 14 | 15 | "bodyParser": { 16 | "json": {} 17 | } 18 | } -------------------------------------------------------------------------------- /test/integration/fixtures/server3/handlers/handler.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | 6 | module.exports.init = function(app) { 7 | app.get('/session', function(req, res, next) { 8 | res.status(200).json(req.session.data); 9 | }); 10 | 11 | app.post('/session', function(req, res, next) { 12 | req.session.data = req.body; 13 | res.status(201).json(req.session.data); 14 | }); 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /test/integration/fixtures/server4/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "express": { 3 | "port": "5000", 4 | "middleware": ["session", "bodyParser"] 5 | }, 6 | 7 | "cluster": { 8 | "maxWorkers": 1 9 | }, 10 | 11 | "session": { 12 | "keys": ["sessionkey"] 13 | }, 14 | 15 | "bodyParser": { 16 | "json": {} 17 | }, 18 | 19 | "logger": { 20 | "transports": [ 21 | { 22 | "package": "winston", 23 | "field": "transports.Console", 24 | "options": { 25 | "level": "debug", 26 | "colorize": true, 27 | "timestamp": true 28 | } 29 | } 30 | ] 31 | } 32 | } -------------------------------------------------------------------------------- /test/integration/fixtures/server4/handlers/petstore.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.findPets = function(req, res, next) { 6 | next(); 7 | }; 8 | -------------------------------------------------------------------------------- /test/integration/fixtures/server4/handlers/petstore2.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | module.exports.findPets = function(req, res, next) { 6 | next(); 7 | }; 8 | -------------------------------------------------------------------------------- /test/integration/fixtures/server4/handlers/petstore3.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | module.exports = { 6 | 7 | // eslint-disable-next-line lodash/prefer-noop 8 | foo: function() { 9 | 10 | } 11 | 12 | }; -------------------------------------------------------------------------------- /test/integration/fixtures/server5/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "express": { 3 | "port": "5000", 4 | "middleware": ["session", "bodyParser"] 5 | }, 6 | 7 | "cluster": { 8 | "maxWorkers": 1 9 | }, 10 | 11 | "session": { 12 | "keys": ["sessionkey"] 13 | }, 14 | 15 | "bodyParser": { 16 | "json": {} 17 | }, 18 | 19 | "logger": { 20 | "transports": [ 21 | { 22 | "package": "winston", 23 | "field": "transports.Console", 24 | "options": { 25 | "level": "debug", 26 | "colorize": true, 27 | "timestamp": true 28 | } 29 | } 30 | ] 31 | } 32 | } -------------------------------------------------------------------------------- /test/integration/fixtures/server5/config/test-response-validation-errors.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": { 3 | "validateResponseModels": "error" 4 | } 5 | } -------------------------------------------------------------------------------- /test/integration/fixtures/server5/handlers/foo.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.pets5 = function(req, res, next) { 6 | res.json({name: 'pets5'}); 7 | }; -------------------------------------------------------------------------------- /test/integration/fixtures/server5/handlers/petstore.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.pets1 = function (req, res, next) { 6 | res.send({ 7 | name: 'pets1', 8 | id: 1 9 | }); 10 | }; 11 | 12 | exports.pets2 = function (req, res, next) { 13 | res.send({ 14 | name: 'pets2' 15 | }); 16 | }; 17 | 18 | exports.pets3 = function (req, res, next) { 19 | res.json({ 20 | name: 'pets3' 21 | }); 22 | }; 23 | 24 | exports.pets4 = function (req, res, next) { 25 | res.json({ 26 | name: 'pets4' 27 | }); 28 | }; 29 | 30 | exports.pets6 = function (req, res, next) { 31 | res.json({ 32 | name: 'Mr. Bigglesworth', 33 | isFurry: req.query.isFurry, 34 | isVaccinated: req.headers['isVaccinated'] 35 | }); 36 | }; 37 | 38 | exports.pets22 = function (req, res, next) { 39 | res.status(201).send(req.body); 40 | }; 41 | 42 | exports.petsPostNoContent = function (req, res) { 43 | res.status(204).send(); 44 | }; 45 | -------------------------------------------------------------------------------- /test/integration/fixtures/server5/swagger/petstore2.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "1.0.0", 5 | "title": "Swagger Petstore", 6 | "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", 7 | "termsOfService": "http://helloreverb.com/terms/", 8 | "contact": { 9 | "name": "Wordnik API Team", 10 | "email": "foo@example.com", 11 | "url": "http://madskristensen.net" 12 | }, 13 | "license": { 14 | "name": "MIT", 15 | "url": "http://github.com/gruntjs/grunt/blob/master/LICENSE-MIT" 16 | } 17 | }, 18 | "host": "petstore.swagger.wordnik.com", 19 | "basePath": "/api", 20 | "schemes": [ 21 | "http" 22 | ], 23 | "consumes": [ 24 | "application/json" 25 | ], 26 | "produces": [ 27 | "application/json" 28 | ], 29 | "paths": { 30 | "/pets2": { 31 | "x-bos-handler": "petstore", 32 | "get": { 33 | "description": "Returns a user based on a single ID, if the user does not have access to the pet", 34 | "operationId": "pets2", 35 | "produces": [ 36 | "application/json", 37 | "application/xml", 38 | "text/xml", 39 | "text/html" 40 | ], 41 | "responses": { 42 | "200": { 43 | "description": "pet response", 44 | "schema": { 45 | "$ref": "#/definitions/pet" 46 | } 47 | }, 48 | "default": { 49 | "description": "unexpected error", 50 | "schema": { 51 | "$ref": "#/definitions/errorModel" 52 | } 53 | } 54 | } 55 | }, 56 | "post": { 57 | "description": "Adds a new pet", 58 | "operationId": "pets22", 59 | "parameters": [ 60 | { 61 | "in": "body", 62 | "name": "body", 63 | "description": "new pet object", 64 | "required": true, 65 | "schema": { 66 | "$ref": "#/definitions/pet" 67 | } 68 | } 69 | ], 70 | "responses": { 71 | "201": { 72 | "description": "pet added", 73 | "schema": { 74 | "$ref": "#/definitions/pet" 75 | } 76 | }, 77 | "422": { 78 | "description": "Request Validation Error" 79 | }, 80 | "default": { 81 | "description": "unexpected error", 82 | "schema": { 83 | "$ref": "#/definitions/errorModel" 84 | } 85 | } 86 | } 87 | } 88 | } 89 | }, 90 | "definitions": { 91 | "pet": { 92 | "required": [ 93 | "id", 94 | "name" 95 | ], 96 | "properties": { 97 | "id": { 98 | "type": "integer", 99 | "format": "int64" 100 | }, 101 | "name": { 102 | "type": "string" 103 | }, 104 | "tag": { 105 | "type": "string" 106 | } 107 | } 108 | }, 109 | "newPet": { 110 | "allOf": [ 111 | { 112 | "$ref": "#/definitions/pet" 113 | }, 114 | { 115 | "required": [ 116 | "name" 117 | ], 118 | "properties": { 119 | "id": { 120 | "type": "integer", 121 | "format": "int64" 122 | } 123 | } 124 | } 125 | ] 126 | }, 127 | "errorModel": { 128 | "required": [ 129 | "code", 130 | "message" 131 | ], 132 | "properties": { 133 | "code": { 134 | "type": "integer", 135 | "format": "int32" 136 | }, 137 | "message": { 138 | "type": "string" 139 | } 140 | } 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /test/integration/fixtures/server6/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "express": { 3 | }, 4 | 5 | "cluster": { 6 | "maxWorkers": 1 7 | } 8 | } -------------------------------------------------------------------------------- /test/integration/fixtures/server6/services/logger.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | // eslint-disable-next-line lodash/prefer-noop 6 | exports.init = function() { 7 | //I should fail because there's already a logger service 8 | }; -------------------------------------------------------------------------------- /test/integration/fixtures/server7/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "express": { 3 | "port": "5000", 4 | "middleware": ["session", "body-parser"] 5 | }, 6 | 7 | "cluster": { 8 | "maxWorkers": 1 9 | }, 10 | 11 | "session": { 12 | "keys": ["sessionkey"] 13 | }, 14 | 15 | "body-parser": { 16 | "json": {} 17 | }, 18 | 19 | "logger": { 20 | "transports": [ 21 | { 22 | "package": "winston", 23 | "field": "transports.Console", 24 | "options": { 25 | "level": "debug", 26 | "colorize": true, 27 | "timestamp": true 28 | } 29 | } 30 | ] 31 | } 32 | } -------------------------------------------------------------------------------- /test/integration/fixtures/server7/handlers/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.foo = function(req, res, next) { 6 | res.json({ 7 | name: 'foo' 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /test/integration/fixtures/server7/handlers/petstore.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.getPetById = function(req, res, next) { 6 | res.json({ 7 | name: 'pets1' 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /test/integration/fixtures/server7/swagger/definitions/User.yaml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | name: 4 | type: string 5 | -------------------------------------------------------------------------------- /test/integration/fixtures/server7/swagger/definitions/index.yaml: -------------------------------------------------------------------------------- 1 | User: 2 | $ref: ./User.yaml 3 | -------------------------------------------------------------------------------- /test/integration/fixtures/server7/swagger/index.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | $ref: ./info/index.yaml 4 | paths: 5 | $ref: ./paths/index.yaml 6 | definitions: 7 | $ref: ./definitions/index.yaml 8 | -------------------------------------------------------------------------------- /test/integration/fixtures/server7/swagger/info/index.yaml: -------------------------------------------------------------------------------- 1 | version: 0.0.0 2 | title: Simple API 3 | -------------------------------------------------------------------------------- /test/integration/fixtures/server7/swagger/paths/bar.yaml: -------------------------------------------------------------------------------- 1 | get: 2 | responses: 3 | 200: 4 | description: OK 5 | schema: 6 | $ref: '../definitions/User.yaml' 7 | -------------------------------------------------------------------------------- /test/integration/fixtures/server7/swagger/paths/foo.yaml: -------------------------------------------------------------------------------- 1 | get: 2 | operationId: foo 3 | responses: 4 | 200: 5 | description: OK 6 | -------------------------------------------------------------------------------- /test/integration/fixtures/server7/swagger/paths/index.yaml: -------------------------------------------------------------------------------- 1 | /foo: 2 | $ref: ./foo.yaml 3 | /bar: 4 | $ref: ./bar.yaml 5 | -------------------------------------------------------------------------------- /test/integration/fixtures/server8/common/swagger/petstore2.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "1.0.0", 5 | "title": "Swagger Petstore", 6 | "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", 7 | "termsOfService": "http://helloreverb.com/terms/", 8 | "contact": { 9 | "name": "Wordnik API Team", 10 | "email": "foo@example.com", 11 | "url": "http://madskristensen.net" 12 | }, 13 | "license": { 14 | "name": "MIT", 15 | "url": "http://github.com/gruntjs/grunt/blob/master/LICENSE-MIT" 16 | } 17 | }, 18 | "host": "petstore.swagger.wordnik.com", 19 | "basePath": "/api", 20 | "schemes": [ 21 | "http" 22 | ], 23 | "consumes": [ 24 | "application/json" 25 | ], 26 | "produces": [ 27 | "application/json" 28 | ], 29 | "paths": { 30 | "/pets2": { 31 | "x-handler": "petstore", 32 | "get": { 33 | "description": "Returns a user based on a single ID, if the user does not have access to the pet", 34 | "operationId": "pets2", 35 | "produces": [ 36 | "application/json", 37 | "application/xml", 38 | "text/xml", 39 | "text/html" 40 | ], 41 | "responses": { 42 | "200": { 43 | "description": "pet response", 44 | "schema": { 45 | "$ref": "#/definitions/pet" 46 | } 47 | }, 48 | "default": { 49 | "description": "unexpected error", 50 | "schema": { 51 | "$ref": "#/definitions/errorModel" 52 | } 53 | } 54 | } 55 | }, 56 | "put": { 57 | "description": "upload a pet", 58 | "operationId": "pets2Upload", 59 | "consumes": ["multipart/form-data"], 60 | "parameters": [ 61 | { 62 | "in": "formData", 63 | "type": "file", 64 | "name": "pet" 65 | }, 66 | { 67 | "in": "formData", 68 | "type": "number", 69 | "name": "petId" 70 | } 71 | ], 72 | "produces": [ 73 | "application/json", 74 | "application/xml", 75 | "text/xml", 76 | "text/html" 77 | ], 78 | "responses": { 79 | "200": { 80 | "description": "pet response", 81 | "schema": { 82 | "$ref": "#/definitions/pet" 83 | } 84 | }, 85 | "default": { 86 | "description": "unexpected error", 87 | "schema": { 88 | "$ref": "#/definitions/errorModel" 89 | } 90 | } 91 | } 92 | }, 93 | "post": { 94 | "description": "save a pet", 95 | "operationId": "pets2Post", 96 | "parameters": [ 97 | { 98 | "in": "formData", 99 | "type": "string", 100 | "name": "petName" 101 | }, 102 | { 103 | "in": "formData", 104 | "type": "number", 105 | "name": "petId" 106 | } 107 | ], 108 | "produces": [ 109 | "application/json", 110 | "application/xml", 111 | "text/xml", 112 | "text/html" 113 | ], 114 | "responses": { 115 | "200": { 116 | "description": "pet response", 117 | "schema": { 118 | "$ref": "#/definitions/pet" 119 | } 120 | }, 121 | "default": { 122 | "description": "unexpected error", 123 | "schema": { 124 | "$ref": "#/definitions/errorModel" 125 | } 126 | } 127 | } 128 | } 129 | } 130 | }, 131 | "definitions": { 132 | "pet": { 133 | "required": [ 134 | "id", 135 | "name" 136 | ], 137 | "properties": { 138 | "id": { 139 | "type": "integer", 140 | "format": "int64" 141 | }, 142 | "name": { 143 | "type": "string" 144 | }, 145 | "tag": { 146 | "type": "string" 147 | } 148 | } 149 | }, 150 | "newPet": { 151 | "allOf": [ 152 | { 153 | "$ref": "#/definitions/pet" 154 | }, 155 | { 156 | "required": [ 157 | "name" 158 | ], 159 | "properties": { 160 | "id": { 161 | "type": "integer", 162 | "format": "int64" 163 | } 164 | } 165 | } 166 | ] 167 | }, 168 | "errorModel": { 169 | "required": [ 170 | "code", 171 | "message" 172 | ], 173 | "properties": { 174 | "code": { 175 | "type": "integer", 176 | "format": "int32" 177 | }, 178 | "message": { 179 | "type": "string" 180 | } 181 | } 182 | } 183 | } 184 | } -------------------------------------------------------------------------------- /test/integration/fixtures/server8/server/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "express": { 3 | "port": "5000", 4 | "middleware": ["session", "bodyParser"] 5 | }, 6 | 7 | "cluster": { 8 | "maxWorkers": 1 9 | }, 10 | 11 | "session": { 12 | "keys": ["sessionkey"] 13 | }, 14 | 15 | "bodyParser": { 16 | "json": {}, 17 | "urlencoded": {} 18 | }, 19 | 20 | "multer": { 21 | "storage": "multerMemoryStorage" 22 | }, 23 | 24 | "logger": { 25 | "transports": [ 26 | { 27 | "package": "winston", 28 | "field": "transports.Console", 29 | "options": { 30 | "level": "debug", 31 | "colorize": true, 32 | "timestamp": true 33 | } 34 | } 35 | ] 36 | } 37 | } -------------------------------------------------------------------------------- /test/integration/fixtures/server8/server/handlers/foo.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.pets5 = function(req, res, next) { 6 | res.json({name: 'pets5'}); 7 | }; -------------------------------------------------------------------------------- /test/integration/fixtures/server8/server/handlers/petstore.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.pets1 = function(req, res, next) { 6 | res.json({ 7 | name: 'pets1' 8 | }); 9 | }; 10 | 11 | exports.pets2 = function(req, res, next) { 12 | res.json({ 13 | name: 'pets2' 14 | }); 15 | }; 16 | 17 | exports.pets2Upload = function(req, res, next) { 18 | res.json({petId: Number(req.body.petId), file: req.files.pet[0]}); 19 | }; 20 | 21 | exports.pets2Post = function(req, res, next) { 22 | res.json({petId: Number(req.body.petId), name: req.body.petName}); 23 | }; 24 | 25 | exports.pets3 = function(req, res, next) { 26 | res.json({ 27 | name: 'pets3' 28 | }); 29 | }; 30 | 31 | exports.pets4 = function(req, res, next) { 32 | res.json({ 33 | name: 'pets4' 34 | }); 35 | }; 36 | 37 | -------------------------------------------------------------------------------- /test/integration/fixtures/server8/server/pet.txt: -------------------------------------------------------------------------------- 1 | Mr. Bigglesworth -------------------------------------------------------------------------------- /test/integration/fixtures/server9/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "express": { 3 | }, 4 | 5 | "cluster": { 6 | "maxWorkers": 1 7 | } 8 | } -------------------------------------------------------------------------------- /test/integration/fixtures/server9/services/test.service.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | // eslint-disable-next-line lodash/prefer-noop 6 | exports.init = function() { 7 | //I should fail because there's already a logger service 8 | }; -------------------------------------------------------------------------------- /test/integration/launchUtil.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var path = require('path'), 6 | child_process = require('child_process'); 7 | 8 | var lastLaunch, output; 9 | 10 | var spawner, execer; 11 | if (process.platform === 'win32') { 12 | spawner = child_process.exec; 13 | execer = 'node '; 14 | } else { 15 | spawner = child_process.execFile; 16 | execer = ''; 17 | } 18 | 19 | exports.launch = function (fixtureName, opts, done) { 20 | 21 | //opts is optional 22 | if (!done) { 23 | done = opts; 24 | opts = {}; 25 | } 26 | if (!opts.exec) { 27 | opts.exec = '../../bin/blueoak-server.js'; 28 | } 29 | 30 | var bosPath = path.resolve(__dirname, opts.exec); 31 | output = ''; 32 | lastLaunch = spawner(execer + bosPath, 33 | { 34 | 'cwd': path.resolve(__dirname, 'fixtures/' + fixtureName), 35 | 'env': opts.env 36 | }, 37 | function (err, stdout, stderr) { 38 | if (err && err.signal !== 'SIGINT') { 39 | console.warn(JSON.stringify(err, 0, 2), '\n' + stderr); 40 | } 41 | output += stdout + stderr; 42 | } 43 | ); 44 | setTimeout(function () { 45 | // key off the failure output from bin/blueoak-server.js or other Errors for errors 46 | output = /^([A-z]*Error|Startup failed):*/m.test(output) ? output : null; 47 | done(output, lastLaunch); 48 | }, 4000); 49 | }; 50 | 51 | exports.finish = function (done) { 52 | if (process.platform === 'win32') { 53 | child_process.exec('taskkill /PID ' + lastLaunch.pid + ' /T /F'); 54 | } 55 | else { 56 | lastLaunch.kill('SIGINT'); 57 | } 58 | 59 | if (output) { 60 | // there was an "error" on launch, just be done 61 | done(); 62 | } else { 63 | lastLaunch.on('close', function (code, signal) { 64 | done(); 65 | }); 66 | } 67 | }; 68 | 69 | -------------------------------------------------------------------------------- /test/integration/testDeclarativeRoutes.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var request = require('request'), 6 | assert = require('assert'), 7 | util = require('./launchUtil'); 8 | 9 | describe('SERVER2 - test declarative routes', function () { 10 | this.timeout(5000); 11 | before(function (done) { 12 | util.launch('server2', done); 13 | }); 14 | 15 | after(function (done) { 16 | util.finish(done); 17 | }); 18 | 19 | it('GET /endpoint2', function (done) { 20 | request('http://localhost:' + (process.env.PORT || 5000) + '/endpoint2', function(err, resp, body) { 21 | assert.equal(null, err); 22 | var json = JSON.parse(body); 23 | assert.equal('endpoint2', json.name); 24 | done(); 25 | }); 26 | }); 27 | 28 | it('POST /endpoint2', function (done) { 29 | request.post('http://localhost:' + (process.env.PORT || 5000) + '/endpoint2', function(err, resp, body) { 30 | assert.equal(null, err); 31 | var json = JSON.parse(body); 32 | assert.equal('endpoint2', json.name); 33 | done(); 34 | }); 35 | }); 36 | 37 | it('PUT /endpoint2', function (done) { 38 | request.put('http://localhost:' + (process.env.PORT || 5000) + '/endpoint2', function(err, resp, body) { 39 | assert.equal(null, err); 40 | var json = JSON.parse(body); 41 | assert.equal('endpoint2', json.name); 42 | done(); 43 | }); 44 | }); 45 | 46 | it('DELETE /endpoint2', function (done) { 47 | request.del('http://localhost:' + (process.env.PORT || 5000) + '/endpoint2', function(err, resp, body) { 48 | assert.equal(null, err); 49 | var json = JSON.parse(body); 50 | assert.equal('endpoint2', json.name); 51 | done(); 52 | }); 53 | }); 54 | 55 | 56 | //test the 'ALL' routes 57 | it('GET /endpoint3', function (done) { 58 | request('http://localhost:' + (process.env.PORT || 5000) + '/endpoint3', function(err, resp, body) { 59 | assert.equal(null, err); 60 | var json = JSON.parse(body); 61 | assert.equal('endpoint3', json.name); 62 | done(); 63 | }); 64 | }); 65 | 66 | it('POST /endpoint3', function (done) { 67 | request.post('http://localhost:' + (process.env.PORT || 5000) + '/endpoint3', function(err, resp, body) { 68 | assert.equal(null, err); 69 | var json = JSON.parse(body); 70 | assert.equal('endpoint3', json.name); 71 | done(); 72 | }); 73 | }); 74 | 75 | it('PUT /endpoint3', function (done) { 76 | request.put('http://localhost:' + (process.env.PORT || 5000) + '/endpoint3', function(err, resp, body) { 77 | assert.equal(null, err); 78 | var json = JSON.parse(body); 79 | assert.equal('endpoint3', json.name); 80 | done(); 81 | }); 82 | }); 83 | 84 | it('DELETE /endpoint3', function (done) { 85 | request.del('http://localhost:' + (process.env.PORT || 5000) + '/endpoint3', function(err, resp, body) { 86 | assert.equal(null, err); 87 | var json = JSON.parse(body); 88 | assert.equal('endpoint3', json.name); 89 | done(); 90 | }); 91 | }); 92 | 93 | it('GET /endpoint4', function (done) { 94 | request.get('http://localhost:' + (process.env.PORT || 5000) + '/endpoint4', function(err, resp, body) { 95 | assert.equal(null, err); 96 | var json = JSON.parse(body); 97 | assert.equal('endpoint4', json.name); 98 | done(); 99 | }); 100 | }); 101 | 102 | }); -------------------------------------------------------------------------------- /test/integration/testHandlers.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var request = require('request'), 6 | assert = require('assert'), 7 | util = require('./launchUtil'); 8 | 9 | describe('SERVER1 - test simple REST calls', function () { 10 | this.timeout(5000); 11 | 12 | before(function (done) { 13 | util.launch('server1', done); 14 | }); 15 | 16 | after(function (done) { 17 | util.finish(done); 18 | }); 19 | 20 | it('GET /endpoint1', function (done) { 21 | request('http://localhost:' + (process.env.PORT || 5000) + '/endpoint1', function(err, resp, body) { 22 | assert.equal(null, err); 23 | var json = JSON.parse(body); 24 | assert.equal('endpoint1', json.name); 25 | done(); 26 | }); 27 | }); 28 | 29 | it('POST /endpoint1', function (done) { 30 | request.post('http://localhost:' + (process.env.PORT || 5000) + '/endpoint1', function(err, resp, body) { 31 | assert.equal(null, err); 32 | var json = JSON.parse(body); 33 | assert.equal('endpoint1', json.name); 34 | done(); 35 | }); 36 | }); 37 | 38 | it('PUT /endpoint1', function (done) { 39 | request.put('http://localhost:' + (process.env.PORT || 5000) + '/endpoint1', function(err, resp, body) { 40 | assert.equal(null, err); 41 | var json = JSON.parse(body); 42 | assert.equal('endpoint1', json.name); 43 | done(); 44 | }); 45 | }); 46 | 47 | it('DELETE /endpoint1', function (done) { 48 | request.del('http://localhost:' + (process.env.PORT || 5000) + '/endpoint1', function(err, resp, body) { 49 | assert.equal(null, err); 50 | var json = JSON.parse(body); 51 | assert.equal('endpoint1', json.name); 52 | done(); 53 | }); 54 | }); 55 | 56 | }); -------------------------------------------------------------------------------- /test/integration/testLoader.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var _ = require('lodash'), 6 | assert = require('assert'), 7 | util = require('./launchUtil'), 8 | path = require('path'); 9 | 10 | describe('SERVER6 - duplicate service name should fail on startup', function () { 11 | this.timeout(5000); 12 | 13 | after(function (done) { 14 | util.finish(done); 15 | }); 16 | 17 | it('Launch server and check for failure', function (done) { 18 | util.launch('server6', function(output) { 19 | assert.ok(_.includes(output, 'already exists')); 20 | done(); 21 | }); 22 | }); 23 | }); 24 | 25 | describe('SERVER9 - service module with invalid name should fail on startup', function () { 26 | this.timeout(5000); 27 | 28 | after(function (done) { 29 | util.finish(done); 30 | }); 31 | 32 | it('Launch server and check for failure', function (done) { 33 | util.launch('server9', function(output) { 34 | assert.ok(_.includes(output, 'Names cannot contain periods')); 35 | done(); 36 | }); 37 | }); 38 | }); 39 | 40 | describe('SERVER10 - handler with invalid name should fail on startup', function () { 41 | this.timeout(5000); 42 | 43 | after(function (done) { 44 | util.finish(done); 45 | }); 46 | 47 | it('Launch server and check for failure', function (done) { 48 | util.launch('server10', function(output) { 49 | assert.ok(_.includes(output, 'Names cannot contain periods')); 50 | done(); 51 | }); 52 | }); 53 | }); 54 | 55 | describe('SERVER10 - handler with invalid name should fail on startup', function () { 56 | this.timeout(5000); 57 | 58 | after(function (done) { 59 | util.finish(done); 60 | }); 61 | 62 | it('Launch server and check for failure', function (done) { 63 | util.launch('server10', function(output) { 64 | assert.ok(_.includes(output, 'Names cannot contain periods')); 65 | done(); 66 | }); 67 | }); 68 | }); 69 | 70 | describe('SERVER11 - middleware should get loaded from node modules', function () { 71 | this.timeout(5000); 72 | 73 | after(function (done) { 74 | util.finish(done); 75 | }); 76 | 77 | it('Launch server and load middleware', function (done) { 78 | util.launch('server11', {appDir: path.resolve(__dirname, 'fixtures/server11')}, function(output) { 79 | assert.ok(output === null); 80 | done(); 81 | }); 82 | }); 83 | }); -------------------------------------------------------------------------------- /test/integration/testSession.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var request = require('request').defaults({ jar: true }); //need cookies enabled since this is cookie session 6 | 7 | var assert = require('assert'), 8 | util = require('./launchUtil'); 9 | 10 | describe('SERVER3 - test session support', function () { 11 | this.timeout(5000); 12 | before(function (done) { 13 | util.launch('server3', done); 14 | }); 15 | 16 | after(function (done) { 17 | util.finish(done); 18 | }); 19 | 20 | 21 | it('Should let me put and retrieve data from the session', function (done) { 22 | //the post sets session data 23 | request.post({ 24 | url: 'http://localhost:' + (process.env.PORT || 5000) + '/session', 25 | json: { 26 | foo: 'bar' 27 | } 28 | }, function (err, resp, body) { 29 | assert.equal(null, err); 30 | 31 | //get retrieves session data 32 | request.get('http://localhost:' + (process.env.PORT || 5000) + '/session', 33 | function (err, resp, body) { 34 | assert.equal(null, err); 35 | var data = JSON.parse(body); 36 | assert.equal('bar', data.foo); 37 | done(); 38 | } 39 | ); 40 | }); 41 | }); 42 | 43 | 44 | }); -------------------------------------------------------------------------------- /test/unit/data/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "curiousPeople": [ 3 | { 4 | "kind": "OtherPerson", 5 | "curiousPersonReqField": "hey!", 6 | "enthusiasticPersonReqField": "hola!" 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /test/unit/fixtures/config/config/cluster.json: -------------------------------------------------------------------------------- 1 | { 2 | //overrides what's in default.json 3 | "maxWorkers": 2 4 | } -------------------------------------------------------------------------------- /test/unit/fixtures/config/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | //override server default of 3000 3 | "express": { 4 | "port": 3001 5 | }, 6 | 7 | //override server default of -1 8 | "cluster": { 9 | "maxWorkers": 1 10 | } 11 | } -------------------------------------------------------------------------------- /test/unit/fixtures/config/timeout/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "express": { 3 | "httpServer": { 4 | "timeout": 111111, 5 | "keepAliveTimeout": 222222, 6 | "headersTimeout": 333333 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test1/service1.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | //uses sync init to set initialized to true 6 | var initialized = false; 7 | 8 | exports.init = function() { 9 | initialized = true; 10 | }; 11 | 12 | exports.isInitialized = function() { 13 | return initialized; 14 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test1/service2.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | //uses async init to set initialized to true 6 | var initialized = false; 7 | 8 | exports.init = function(callback) { 9 | initialized = true; 10 | callback(); 11 | }; 12 | 13 | exports.isInitialized = function() { 14 | return initialized; 15 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test10/service18.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var initialized = false; 6 | 7 | exports.init = function() { 8 | initialized = true; 9 | }; 10 | 11 | exports.isInitialized = function() { 12 | return initialized; 13 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test10/service19.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var initialized = false; 6 | 7 | exports.init = function() { 8 | initialized = true; 9 | }; 10 | 11 | exports.isInitialized = function() { 12 | return initialized; 13 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test11/consumers/dir1/consumer5.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.init = function(service21) { 6 | service21.add('consumer5'); 7 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test11/services/dir2/dir3/service22.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var initialized = false; 6 | 7 | exports.init = function(callback) { 8 | initialized = true; 9 | callback(); 10 | }; 11 | 12 | exports.isInitialized = function() { 13 | return initialized; 14 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test11/services/dir2/service21.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var initialized = false; 6 | var consumers = []; 7 | 8 | exports.init = function(callback) { 9 | initialized = true; 10 | callback(); 11 | }; 12 | 13 | exports.isInitialized = function() { 14 | return initialized; 15 | }; 16 | 17 | exports.add = function(name) { 18 | consumers.push(name); 19 | }; 20 | 21 | exports.get = function() { 22 | return consumers; 23 | }; 24 | -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test2/service3.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | //asynchronous 6 | var initialized = false; 7 | 8 | exports.init = function(callback) { 9 | 10 | //simulate an error 11 | return callback(new Error('crap!')); 12 | }; 13 | 14 | exports.isInitialized = function() { 15 | return initialized; 16 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test2/service4.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | //synchronous 6 | var initialized = false; 7 | 8 | exports.init = function() { 9 | throw new Error('crap, an error occured'); 10 | }; 11 | 12 | exports.isInitialized = function() { 13 | return initialized; 14 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test3/service5.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | //uses async init to set initialized to true 6 | var initialized = false; 7 | 8 | exports.init = function(callback) { 9 | initialized = true; 10 | callback(); 11 | }; 12 | 13 | exports.isInitialized = function() { 14 | return initialized; 15 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test3/service6.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | //uses async init to set initialized to true 6 | var initialized = false; 7 | 8 | exports.init = function(service5, callback) { 9 | if (service5.isInitialized()) { 10 | initialized = true; 11 | } 12 | callback(); 13 | }; 14 | 15 | exports.isInitialized = function() { 16 | return initialized; 17 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test3/service7.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | //uses async init to set initialized to true 6 | var initialized = false; 7 | 8 | exports.init = function(service6) { 9 | 10 | if (service6.isInitialized()) { 11 | initialized = true; 12 | } 13 | }; 14 | 15 | exports.isInitialized = function() { 16 | return initialized; 17 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test4/service8.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | //uses async init to set initialized to true 6 | var initialized = false; 7 | 8 | exports.init = function(blah, callback) { 9 | initialized = blah; 10 | callback(); 11 | }; 12 | 13 | exports.isInitialized = function() { 14 | return initialized; 15 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test4/service9.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | //uses async init to set initialized to true 6 | var initialized = false; 7 | 8 | exports.init = function(callback) { 9 | initialized = true; 10 | callback(); 11 | }; 12 | 13 | exports.isInitialized = function() { 14 | return initialized; 15 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test5/service10.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | //uses async init to set initialized to true 6 | var initialized = false; 7 | 8 | exports.init = function(service11, callback) { 9 | initialized = true; 10 | callback(); 11 | }; 12 | 13 | exports.isInitialized = function() { 14 | return initialized; 15 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test5/service11.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | //uses async init to set initialized to true 6 | var initialized = false; 7 | 8 | exports.init = function(service10, callback) { 9 | initialized = true; 10 | callback(); 11 | }; 12 | 13 | exports.isInitialized = function() { 14 | return initialized; 15 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test6/badConsumers/consumer1.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.init = function(service20, callback) { 6 | callback(new Error('bad consumer1 failed')); 7 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test6/badConsumers/consumer2.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.init = function(service20) { 6 | throw new Error('bad consumer2 failed'); 7 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test6/consumers/consumer1.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.init = function(service20, callback) { 6 | service20.add('consumer1'); 7 | callback(); 8 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test6/consumers/consumer2.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.init = function(service20) { 6 | service20.add('consumer2'); 7 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test6/otherConsumers/consumer1.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.init = function(service20, callback) { 6 | service20.add('otherConsumer1'); 7 | callback(); 8 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test6/services/service20.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | //uses async init to set initialized to true 6 | var initialized = false; 7 | 8 | var consumers = []; 9 | 10 | exports.init = function(callback) { 11 | initialized = true; 12 | callback(); 13 | }; 14 | 15 | exports.add = function(name) { 16 | consumers.push(name); 17 | }; 18 | 19 | exports.get = function() { 20 | return consumers; 21 | }; 22 | 23 | exports.isInitialized = function() { 24 | return initialized; 25 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test7/service12.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | //uses async init to set initialized to true 6 | var initialized = false; 7 | 8 | exports.init = function(service15, serviceLoader) { 9 | initialized = serviceLoader.get('service14').isInitialized() && service15.isInitialized(); 10 | }; 11 | 12 | exports.getDependencies = function(serviceLoader) { 13 | return ['service14']; 14 | }; 15 | 16 | exports.isInitialized = function() { 17 | return initialized; 18 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test7/service13.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | //uses async init to set initialized to true 6 | var initialized = false; 7 | 8 | exports.init = function(service15) { 9 | initialized = true; 10 | }; 11 | 12 | exports.isInitialized = function() { 13 | return initialized; 14 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test7/service14.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | //uses async init to set initialized to true 6 | var initialized = false; 7 | 8 | exports.init = function(service13) { 9 | initialized = service13.isInitialized(); 10 | }; 11 | 12 | exports.isInitialized = function() { 13 | return initialized; 14 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test7/service15.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | //uses async init to set initialized to true 6 | var initialized = false; 7 | 8 | exports.init = function() { 9 | initialized = true; 10 | }; 11 | 12 | exports.isInitialized = function() { 13 | return initialized; 14 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test8/service-one.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var initialized = false; 6 | 7 | exports.init = function(serviceLoader) { 8 | initialized = serviceLoader !== null; 9 | }; 10 | 11 | 12 | exports.isInitialized = function() { 13 | return initialized; 14 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/loader/test8/service15.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var initialized = false; 6 | 7 | exports.init = function(serviceOne) { 8 | initialized = serviceOne.isInitialized(); 9 | }; 10 | 11 | exports.isInitialized = function() { 12 | return initialized; 13 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/logger/logger.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var init = false; 6 | 7 | exports.init = function(winstonLogger) { 8 | init = true; 9 | }; 10 | 11 | exports.isInit = function() { 12 | return init; 13 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/logger/transports/transport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | 6 | var util = require('util'); 7 | var winston = require('winston'); 8 | var initOptions = null; 9 | 10 | var Transport = function(options) { 11 | initOptions = options; 12 | }; 13 | util.inherits(Transport, winston.Transport); 14 | 15 | // eslint-disable-next-line lodash/prefer-noop 16 | Transport.prototype.log = function() { 17 | 18 | }; 19 | 20 | exports.foo = { 21 | bar: Transport 22 | }; 23 | 24 | exports.getOptions = function() { 25 | return initOptions; 26 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/subrequire/test1/test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | // eslint-disable-next-line lodash/prefer-constant 6 | exports.test = function() { 7 | return 'test1'; 8 | }; -------------------------------------------------------------------------------- /test/unit/fixtures/subrequire/test2/node_modules/test/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.test = function() { 6 | return 'test2'; 7 | } -------------------------------------------------------------------------------- /test/unit/fixtures/subrequire/test2/node_modules/test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC" 11 | } 12 | -------------------------------------------------------------------------------- /test/unit/fixtures/subrequire/test3/node_modules/express/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.test = function() { 6 | return 'test3'; 7 | } -------------------------------------------------------------------------------- /test/unit/fixtures/subrequire/test3/node_modules/express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC" 11 | } 12 | -------------------------------------------------------------------------------- /test/unit/fixtures/subrequire/test4/node_modules/test/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.test = function() { 6 | return 'test4'; 7 | } -------------------------------------------------------------------------------- /test/unit/fixtures/subrequire/test4/node_modules/test/node_modules/express/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | exports.test = function() { 6 | return 'test4_express'; 7 | } -------------------------------------------------------------------------------- /test/unit/fixtures/subrequire/test4/node_modules/test/node_modules/express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC" 11 | } 12 | -------------------------------------------------------------------------------- /test/unit/fixtures/subrequire/test4/node_modules/test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC" 11 | } 12 | -------------------------------------------------------------------------------- /test/unit/testConfig.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var _ = require('lodash'), 6 | assert = require('assert'), 7 | path = require('path'), 8 | util = require('../../testlib/util'), 9 | config = require('../../services/config'); 10 | 11 | 12 | var origAppDir = global.__appDir; 13 | 14 | describe('Config test', function () { 15 | 16 | //The logger will try to read global.__appDir, so make sure it's set 17 | beforeEach(function() { 18 | global.__appDir = path.resolve(__dirname, 'fixtures/config'); 19 | }); 20 | 21 | //restore __appDir to its original value 22 | afterEach(function() { 23 | global.__appDir = origAppDir; 24 | }); 25 | 26 | it('Test config load order', function (done) { 27 | 28 | util.init(config, {}, _.noop, function() { 29 | assert.equal(config.get('express').port, 3001); //default.json overrides the built-in default of 3000 30 | assert.equal(config.get('monitor').port, 8125); //use the server default 31 | 32 | //server default is -1, but default.json overrides it to 1, but cluster.json overrides it to 2 33 | assert.equal(config.get('cluster').maxWorkers, 2); 34 | 35 | done(); 36 | }); 37 | }); 38 | 39 | }); 40 | 41 | describe('Node server timeout config test', function () { 42 | 43 | //The logger will try to read global.__appDir, so make sure it's set 44 | beforeEach(function() { 45 | global.__appDir = path.resolve(__dirname, 'fixtures/config/timeout'); 46 | }); 47 | 48 | //restore __appDir to its original value 49 | afterEach(function() { 50 | global.__appDir = origAppDir; 51 | }); 52 | 53 | it('Test timeout config', function (done) { 54 | 55 | util.init(config, {}, _.noop, function() { 56 | //timeout/default.json overrides default node timeout settings 57 | assert.equal(config.get('express').httpServer.timeout, 111111); 58 | assert.equal(config.get('express').httpServer.keepAliveTimeout, 222222); 59 | assert.equal(config.get('express').httpServer.headersTimeout, 333333); 60 | 61 | done(); 62 | }); 63 | }); 64 | 65 | }); 66 | 67 | -------------------------------------------------------------------------------- /test/unit/testDependencyCalc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var _ = require('lodash'), 6 | assert = require('assert'), 7 | depCalc = require('../../lib/dependencyCalc'); 8 | 9 | describe('Dependency Calculator', function () { 10 | 11 | it('should return empty array when no dependencies', function () { 12 | var result = depCalc.calcGroups(); 13 | assert.equal(result.length, 0, 'result is an empty array'); 14 | }); 15 | 16 | it('should correctly calculate the dependency groups', function () { 17 | depCalc.addNode('a'); 18 | depCalc.addNode('c', ['a']); 19 | depCalc.addNode('d', ['a']); 20 | depCalc.addNode('e', ['c', 'd']); 21 | depCalc.addNode('f', ['d']); 22 | 23 | var results = depCalc.calcGroups(); 24 | assert.equal(results.length, 3); 25 | 26 | //a 27 | assert.equal(results[0].length, 1); 28 | assert.equal(results[0][0], 'a'); 29 | 30 | //c & d 31 | assert.equal(results[1].length, 2); 32 | assert(_.includes(results[1], 'c')); 33 | assert(_.includes(results[1], 'd')); 34 | 35 | //e & f 36 | assert.equal(results[2].length, 2); 37 | assert(_.includes(results[2], 'e')); 38 | assert(_.includes(results[2], 'f')); 39 | }); 40 | 41 | it('should throw an error for circular dependencies', function () { 42 | depCalc.addNode('a', ['b']); 43 | depCalc.addNode('b', ['c']); 44 | depCalc.addNode('c', ['d']); 45 | depCalc.addNode('d', ['e']); 46 | depCalc.addNode('e', ['a']); 47 | assert.throws(function () { 48 | depCalc.calcGroups(); 49 | }, /Cycle.found/); 50 | }); 51 | 52 | it('should throw an error for unmet dependencies', function () { 53 | depCalc.addNode('a', ['d']); 54 | assert.throws(function () { 55 | depCalc.calcGroups(); 56 | }, /Unmet.dependency/); 57 | 58 | }); 59 | 60 | it('should reset itself after a calc', function () { 61 | depCalc.addNode('a', ['d']); 62 | 63 | //First do a circular dep calc 64 | assert.throws(function () { 65 | depCalc.calcGroups(); 66 | }); 67 | 68 | //Should be empty after the failure, so another calcGroups will return an empty list 69 | assert.equal(depCalc.calcGroups().length, 0); 70 | 71 | //Then let's do a non-failing one 72 | depCalc.addNode('a', []); 73 | assert.equal(depCalc.calcGroups().length, 1); 74 | 75 | //And do it again 76 | assert.equal(depCalc.calcGroups().length, 0); 77 | }); 78 | 79 | }); 80 | -------------------------------------------------------------------------------- /test/unit/testLogger.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var assert = require('assert'), 6 | path = require('path'), 7 | util = require('../../testlib/util'), 8 | _ = require('lodash'); 9 | 10 | var defaultCfg = { 11 | 'levels': {'silly': 0, 'debug': 1, 'verbose': 2, 'info': 3, 'warn': 4, 'error': 5}, 12 | 'colors': { 13 | 'silly': 'magenta', 14 | 'debug': 'cyan', 15 | 'verbose': 'blue', 16 | 'info': 'green', 17 | 'warn': 'yellow', 18 | 'error': 'red' 19 | }, 20 | 'transports': [ 21 | { 22 | 'package': 'winston', 23 | 'field': 'transports.Console', 24 | 'options': { 25 | 'level': 'debug', 26 | 'colorize': true 27 | } 28 | } 29 | ], 30 | 'showLocation': true 31 | }; 32 | 33 | var origAppDir = global.__appDir; 34 | 35 | describe('Logger test', function () { 36 | 37 | //The logger will try to read global.__appDir, so make sure it's set 38 | beforeEach(function () { 39 | global.__appDir = path.resolve(__dirname); 40 | }); 41 | 42 | //restore __appDir to its original value 43 | afterEach(function () { 44 | global.__appDir = origAppDir; 45 | }); 46 | 47 | it('Should be able to configure log levels', function (done) { 48 | var cfg = _.clone(defaultCfg); 49 | 50 | //override the log levels 51 | cfg.levels = { 52 | 'foo': 0, 53 | 'bar': 1 54 | }; 55 | 56 | util.injectCore('logger', {logger: cfg, cluster: {maxWorkers: 1}}, function (services) { 57 | assert.ok(!_.isUndefined(services.logger.foo)); 58 | assert.ok(!_.isUndefined(services.logger.bar)); 59 | done(); 60 | }); 61 | }); 62 | 63 | it('Should be able to load external logger config', function (done) { 64 | global.__appDir = path.resolve(__dirname, './fixtures/logger'); 65 | var cfg = _.clone(defaultCfg); 66 | 67 | util.injectCore('logger', {logger: cfg, cluster: {maxWorkers: 1}}, function (services) { 68 | //during init, the logger should have loaded the custom logger.js 69 | //after which isInit() will be true 70 | var customLoggerInit = require(global.__appDir + '/logger'); 71 | assert.ok(customLoggerInit.isInit()); 72 | done(); 73 | }); 74 | }); 75 | 76 | it('Should be able to load custom transports directory', function (done) { 77 | global.__appDir = path.resolve(__dirname, './fixtures/logger/transports'); 78 | var cfg = _.clone(defaultCfg); 79 | cfg.transports = [ 80 | { 81 | package: 'transport', 82 | field: 'foo.bar', 83 | options: {key: 'value'} 84 | } 85 | ]; 86 | util.injectCore('logger', {logger: cfg, cluster: {maxWorkers: 1}}, function (services) { 87 | //explicitly load the transport and verify that the options were 88 | //passed into it 89 | var transport = require(global.__appDir + '/transport'); 90 | assert.equal(transport.getOptions().key, 'value'); 91 | done(); 92 | }); 93 | }); 94 | 95 | it('Should fail loading custom transport that does not exist', function (done) { 96 | global.__appDir = path.resolve(__dirname, './fixtures/logger/transports'); 97 | var cfg = _.clone(defaultCfg); 98 | cfg.transports = [ 99 | { 100 | package: 'blah', 101 | field: 'foo.bar' 102 | } 103 | ]; 104 | try { 105 | util.injectCore('logger', {logger: cfg, cluster: {maxWorkers: 1}}, function (services) { 106 | assert.fail('Should have errored out initializing logger'); 107 | }); 108 | } catch (err) { 109 | done(); 110 | } 111 | 112 | }); 113 | 114 | }); 115 | 116 | -------------------------------------------------------------------------------- /test/unit/testRefCompiler.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'), 2 | assert = require('assert'), 3 | path = require('path'), 4 | fs = require('fs'), 5 | parser = require('swagger-parser'), 6 | logger = require('../../testlib/mocks/logger'), 7 | refCompiler = require('../../lib/swaggerRefCompiler'); 8 | 9 | var swaggerExampleDir = path.resolve(__dirname, '../../examples/swagger'); 10 | 11 | var config = { 12 | 'context': '/swagger', 13 | 'refCompiler': { 14 | 'api-v1': { 15 | 'baseSpecFile': 'api-v1.yaml', 16 | 'refDirs': [ 17 | 'public' 18 | ] 19 | } 20 | } 21 | }; 22 | 23 | function doRefCompilation(rootDir, logger, config) { 24 | global.__appDir = rootDir; 25 | refCompiler.compileSpecs(logger, config); 26 | } 27 | 28 | describe('Swagger spec building test', function () { 29 | 30 | before(function (callback) { 31 | doRefCompilation(swaggerExampleDir, logger, config); 32 | callback(); 33 | }); 34 | 35 | it('should have all definitions and parameters', function (done) { 36 | parser.parse(path.join(swaggerExampleDir, 'swagger/api-v1.yaml'), 'utf-8') 37 | .then(function(specs) { 38 | var definitionsCount = fs.readdirSync(path.join(swaggerExampleDir, 39 | 'swagger/public/definitions')).length; 40 | var parametersCount = fs.readdirSync(path.join(swaggerExampleDir, 41 | 'swagger/public/parameters')).length; 42 | assert.equal(_.keys(specs.definitions).length, definitionsCount); 43 | assert.equal(_.keys(specs.parameters).length, parametersCount); 44 | done(); 45 | }).catch(function (err) { 46 | done(err); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/unit/testSubRequire.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var assert = require('assert'), 6 | subrequire = require('../../lib/subRequire'), 7 | path = require('path'); 8 | 9 | /* uses the following load order 10 | * First try within the app's node_modules 11 | * Next, if this module is required by another module, load it relative to that other module 12 | * Finally, use the normal require call, which will look in the BO server's node_modules 13 | */ 14 | describe('Subrequire test', function () { 15 | 16 | 17 | it('Should load .js files directly', function () { 18 | var mod = subrequire(path.resolve(__dirname, './fixtures/subrequire/test1/test.js')); 19 | assert.equal(mod.__id, 'test'); 20 | assert.equal(mod.test(), 'test1'); 21 | }); 22 | 23 | it('Should load modules from app dir', function () { 24 | global.__appDir = path.resolve(__dirname, './fixtures/subrequire/test2'); 25 | var mod = subrequire('test'); 26 | assert.equal(mod.__id, 'test'); 27 | assert.equal(mod.test(), 'test2'); 28 | }); 29 | 30 | it('Should be able to load from the normal require path', function () { 31 | var mod = subrequire('express'); 32 | assert.equal(mod.__id, 'express'); 33 | }); 34 | 35 | it('Should load from app first', function () { 36 | global.__appDir = path.resolve(__dirname, './fixtures/subrequire/test3'); 37 | var mod = subrequire('express'); 38 | assert.equal(mod.__id, 'express'); 39 | assert.equal(mod.test(), 'test3'); 40 | }); 41 | 42 | it('Should load relative to another module', function () { 43 | global.__appDir = path.resolve(__dirname, './fixtures/subrequire/test4'); 44 | 45 | var mod = subrequire('test'); 46 | assert.equal(mod.__id, 'test'); 47 | assert.equal(mod.test(), 'test4'); 48 | 49 | mod = subrequire('express', 'test'); //load relative to test 50 | assert.equal(mod.__id, 'express'); 51 | assert.equal(mod.test(), 'test4_express'); 52 | 53 | }); 54 | 55 | 56 | }); 57 | -------------------------------------------------------------------------------- /test/unit/testSwaggerModelPolymorphism.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Globant 3 | * MIT Licensed 4 | */ 5 | var assert = require('assert'), 6 | swaggerUtil = require('../../lib/swaggerUtil'); 7 | 8 | describe('Test handling invalid OAI array definitions while preparing polymorphic validation', function () { 9 | 10 | var incompleteArrayDefinition = { 11 | type: 'array' 12 | }; 13 | var incompleteArrayDefinitionInBiggerThing = { 14 | type: 'object', 15 | properties: { 16 | 'TheIncompletelyDefinedArray': { 17 | type: 'array' 18 | } 19 | } 20 | }; 21 | 22 | it('Throws an error if an array definition is missing "items"', function () { 23 | assert.throws( 24 | function () { 25 | swaggerUtil.getObjectsWithDiscriminator(incompleteArrayDefinition); 26 | }, 27 | /Error: OpenAPI array definitions require an "items" property/); 28 | }); 29 | 30 | it('Throws an error with a model path if sub property is an array with missing "items"', function () { 31 | assert.throws( 32 | function () { 33 | swaggerUtil.getObjectsWithDiscriminator(incompleteArrayDefinitionInBiggerThing); 34 | }, 35 | /Error: OpenAPI array definitions require an "items" property; at model path: TheIncompletelyDefinedArray/); 36 | }); 37 | 38 | it('Throws an error with the top full model path if sub property is an array with missing "items"', function () { 39 | assert.throws( 40 | function () { 41 | swaggerUtil.getObjectsWithDiscriminator(incompleteArrayDefinitionInBiggerThing, 'MyModel'); 42 | }, 43 | /Error: OpenAPI array .+ at model path: MyModel.TheIncompletelyDefinedArray/); 44 | }); 45 | 46 | }); 47 | 48 | // TODO: add more testing of swaggerUtil.getObjectsWithDiscriminator 49 | -------------------------------------------------------------------------------- /test/unit/testTestUtil.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var assert = require('assert'), 6 | path = require('path'), 7 | util = require('../../testlib/util'), 8 | loggerService = require('../../services/logger'); 9 | 10 | describe('Test Util test', function () { 11 | 12 | var origAppDir; 13 | 14 | //The logger will try to read global.__appDir, so make sure it's set 15 | before(function () { 16 | origAppDir = global.__appDir; 17 | global.__appDir = __dirname; 18 | }); 19 | 20 | //restore __appDir to its original value 21 | after(function () { 22 | global.__appDir = origAppDir; 23 | }); 24 | 25 | it('Test that injectCore loads core modules', function (done) { 26 | global.__appDir = path.resolve(__dirname); 27 | 28 | util.injectCore('logger', {}, function (mods) { 29 | var logger = mods.logger; 30 | assert.equal(logger.init.toString(), loggerService.init.toString()); 31 | assert.notDeepEqual(logger, loggerService); // logger has been initialized, loggerService has not 32 | done(); 33 | }); 34 | 35 | }); 36 | 37 | it('Test that injectCore can reload core modules cleanly', function (done) { 38 | global.__appDir = path.resolve(__dirname, '../../examples/swagger'); 39 | util.injectCore(['config', 'swagger'], {}, function (mod1) { 40 | assert.equal(mod1.config.get('swagger').refCompiler.petstore.baseSpecFile, 'petstore.json'); 41 | assert.equal(mod1.swagger.getSpecNames().length, 3); 42 | 43 | global.__appDir = path.resolve(__dirname, '../../examples/redis'); 44 | util.injectCore(['config', 'swagger'], {}, function (mod2) { 45 | assert.equal(mod2.config.get('swagger').refCompiler, 'off'); 46 | assert.equal(mod2.swagger.getSpecNames().length, 0); 47 | done(); 48 | }); 49 | }); 50 | }); 51 | }); 52 | 53 | -------------------------------------------------------------------------------- /testlib/mocks/config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | // eslint-disable-next-line lodash/prefer-noop 6 | exports.get = function() { 7 | 8 | }; -------------------------------------------------------------------------------- /testlib/mocks/logger.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var _ = require('lodash'); 6 | 7 | _.forEach(['silly', 'debug', 'verbose', 'info', 'warn', 'error'], function (name) { 8 | exports[name] = _.noop; 9 | }); 10 | -------------------------------------------------------------------------------- /testlib/mocks/monitor.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2016 PointSource, LLC. 3 | * MIT Licensed 4 | */ 5 | var _ = require('lodash'); 6 | 7 | _.forEach(['increment', 'decrement', 'set', 'unique', 'gauge', 'histogram', 'timing', 'enabled'], function (name) { 8 | exports[name] = _.noop; 9 | }); 10 | 11 | exports.enabled = _.noop; 12 | exports.express = _.noop; -------------------------------------------------------------------------------- /tsd.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "v4", 3 | "repo": "DefinitelyTyped/DefinitelyTyped", 4 | "ref": "master", 5 | "path": "typings", 6 | "bundle": "typings/tsd.d.ts", 7 | "installed": { 8 | "async/async.d.ts": { 9 | "commit": "8b3b453a104bb2a949580bbb4e8c81c2a10138b3" 10 | }, 11 | "body-parser/body-parser.d.ts": { 12 | "commit": "8b3b453a104bb2a949580bbb4e8c81c2a10138b3" 13 | }, 14 | "express/express.d.ts": { 15 | "commit": "8b3b453a104bb2a949580bbb4e8c81c2a10138b3" 16 | }, 17 | "node/node.d.ts": { 18 | "commit": "8b3b453a104bb2a949580bbb4e8c81c2a10138b3" 19 | }, 20 | "serve-static/serve-static.d.ts": { 21 | "commit": "8b3b453a104bb2a949580bbb4e8c81c2a10138b3" 22 | }, 23 | "mime/mime.d.ts": { 24 | "commit": "8b3b453a104bb2a949580bbb4e8c81c2a10138b3" 25 | }, 26 | "form-data/form-data.d.ts": { 27 | "commit": "8b3b453a104bb2a949580bbb4e8c81c2a10138b3" 28 | }, 29 | "lodash/lodash.d.ts": { 30 | "commit": "8b3b453a104bb2a949580bbb4e8c81c2a10138b3" 31 | }, 32 | "mocha/mocha.d.ts": { 33 | "commit": "8b3b453a104bb2a949580bbb4e8c81c2a10138b3" 34 | }, 35 | "redis/redis.d.ts": { 36 | "commit": "8b3b453a104bb2a949580bbb4e8c81c2a10138b3" 37 | }, 38 | "request/request.d.ts": { 39 | "commit": "8b3b453a104bb2a949580bbb4e8c81c2a10138b3" 40 | }, 41 | "semver/semver.d.ts": { 42 | "commit": "8b3b453a104bb2a949580bbb4e8c81c2a10138b3" 43 | }, 44 | "verror/verror.d.ts": { 45 | "commit": "8b3b453a104bb2a949580bbb4e8c81c2a10138b3" 46 | }, 47 | "winston/winston.d.ts": { 48 | "commit": "8b3b453a104bb2a949580bbb4e8c81c2a10138b3" 49 | }, 50 | "assert/assert.d.ts": { 51 | "commit": "89f1449d19aa1010f61dd8c7a44502bed42da678" 52 | } 53 | } 54 | } 55 | --------------------------------------------------------------------------------