├── .codeclimate.yml ├── .editorconfig ├── .eslintignore ├── .gitattributes ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __tests__ ├── app.js └── endpoint.js ├── generators ├── app │ ├── index.js │ ├── promptingHelpers.js │ └── templates │ │ ├── LICENSE │ │ ├── README.md │ │ ├── _env │ │ ├── config.js │ │ ├── editorconfig │ │ ├── gitattributes │ │ ├── gitignore │ │ ├── index.js │ │ ├── manifest.js │ │ ├── nodemon.json │ │ ├── package.json │ │ ├── response-files │ │ └── gitkeep │ │ ├── server.js │ │ ├── server │ │ ├── api │ │ │ └── setup │ │ │ │ ├── index.js │ │ │ │ ├── lib │ │ │ │ ├── getContentDisposition.js │ │ │ │ └── getCustomResponseHeader.js │ │ │ │ ├── supportedMethod.js │ │ │ │ └── unsupportedMethods.js │ │ └── web │ │ │ ├── index.js │ │ │ ├── public.js │ │ │ ├── public │ │ │ └── assets │ │ │ │ └── css │ │ │ │ └── styles.css │ │ │ └── views │ │ │ ├── helpers │ │ │ ├── json.js │ │ │ ├── methods.js │ │ │ └── removeCurlyBraces.js │ │ │ ├── index.hbs │ │ │ ├── layout │ │ │ └── layout.hbs │ │ │ └── partials │ │ │ ├── footer.hbs │ │ │ └── header.hbs │ │ ├── test │ │ ├── config.js │ │ ├── index.js │ │ ├── manifest.js │ │ └── server │ │ │ ├── api │ │ │ ├── customResponseHeader.js │ │ │ ├── endpoint.js │ │ │ ├── fakeStatusCode.js │ │ │ ├── fileTypes.js │ │ │ └── fixtures │ │ │ │ ├── example.pdf │ │ │ │ ├── response.html │ │ │ │ ├── response.json │ │ │ │ └── response.txt │ │ │ └── web │ │ │ └── index.js │ │ └── yarn.lock └── endpoint │ ├── index.js │ ├── promptingHelpers.js │ └── templates │ ├── _endpoint.js │ ├── response.html │ ├── response.json │ └── response.txt ├── package-lock.json ├── package.json └── yarn.lock /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | duplication: 4 | enabled: true 5 | config: 6 | languages: 7 | - javascript 8 | eslint: 9 | enabled: true 10 | fixme: 11 | enabled: true 12 | ratings: 13 | paths: 14 | - "**.js" 15 | exclude_paths: 16 | - __tests__/ 17 | - generators/app/templates/ 18 | - generators/endpoint/templates/ 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/templates 2 | coverage 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | *.sublime* 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 6 4 | - 8 5 | - node 6 | after_script: 7 | - npm run coveralls 8 | addons: 9 | code_climate: 10 | repo_token: 1df4f6b88354e9ddfb84b7b06388d78ed12fb808747bca045225cfea3e2fb377 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | 6 | # [4.1.0](https://github.com/micromata/generator-http-fake-backend/compare/4.0.0...4.1.0) (2018-02-22) 7 | 8 | 9 | ### Features 10 | 11 | * Add possibility to define custom response header ([2bd6abb](https://github.com/micromata/generator-http-fake-backend/commit/2bd6abb)), closes [micromata/http-fake-backend#10](https://github.com/micromata/http-fake-backend/issues/10) 12 | 13 | 14 | 15 | 16 | # [4.0.0](https://github.com/micromata/generator-http-fake-backend/compare/v2.0.1...v4.0.0) (2018-02-19) 17 | 18 | *We moved from v2.0.1 to v4.0.0 and skipped v3.x to be on the same major level as the underlying [http-fake-backend](https://github.com/micromata/http-fake-backend) to avoid confusion.* 19 | 20 | ### Bug Fixes 21 | 22 | * **dependencies:** Fix linting errors caused by eslint update ([a51aad3](https://github.com/micromata/generator-http-fake-backend/commit/a51aad3)) 23 | * **dependencies:** Revert gulp-mocha to 3.x ([b005b7c](https://github.com/micromata/generator-http-fake-backend/commit/b005b7c)), closes [sindresorhus/gulp-mocha#159](https://github.com/sindresorhus/gulp-mocha/issues/159) 24 | * **dependencies:** update dependencies ([09fb881](https://github.com/micromata/generator-http-fake-backend/commit/09fb881)) 25 | * automatically install with npm if Yarn isn’t available ([1fe4844](https://github.com/micromata/generator-http-fake-backend/commit/1fe4844)), closes [#5](https://github.com/micromata/generator-http-fake-backend/issues/5) 26 | * Remove Gulp and replace Mocha with Jest ([#6](https://github.com/micromata/generator-http-fake-backend/issues/6)) ([475b466](https://github.com/micromata/generator-http-fake-backend/commit/475b466)) 27 | * Update dependencies ([4df3ba1](https://github.com/micromata/generator-http-fake-backend/commit/4df3ba1)) 28 | * Update dependencies of generated project ([60e1ace](https://github.com/micromata/generator-http-fake-backend/commit/60e1ace)) 29 | * update dependencies of the Yeoman generator ([f1ff18e](https://github.com/micromata/generator-http-fake-backend/commit/f1ff18e)) 30 | 31 | 32 | ### Chores 33 | 34 | * Update required minimum Node version to 6.0.0 ([6c32225](https://github.com/micromata/generator-http-fake-backend/commit/6c32225)) 35 | 36 | 37 | ### Features 38 | 39 | * Update fake backend to 4.0.2 ([05e234d](https://github.com/micromata/generator-http-fake-backend/commit/05e234d)), closes [#7](https://github.com/micromata/generator-http-fake-backend/issues/7) [#11](https://github.com/micromata/generator-http-fake-backend/issues/11) 40 | 41 | > #### http-fake-backend 4.0.2 42 | > 43 | > ##### Bug Fixes 44 | > 45 | > * update minimum node version in package.json 46 | > 47 | >*Change `engines.node` to `>=6.0.0` to reflect the minimum node version which is needed since http-fake-backend 4.0.0.* 48 | > 49 | > #### http-fake-backend 4.0.1 50 | > 51 | > ##### Bug Fixes 52 | > 53 | > * encoding of binary files send via endpoints 54 | > 55 | > #### http-fake-backend 4.0.0 56 | > 57 | > ##### Bug Fixes 58 | > 59 | > * **dependencies:** Apply changes of boom update 60 | > * **dependencies:** Update dependencies 61 | > 62 | > ##### Code Refactoring 63 | > 64 | > * Refactor existing codebase 65 | > 66 | > ##### Documentation 67 | > 68 | > * Update required minimum Node version in readme 69 | > 70 | > ##### Features 71 | > 72 | > * Add support for other response content-types, closes [#7](https://github.com/micromata/http-fake-backend/issues/7) 73 | > * Add support for sending files as response, closes [#11](https://github.com/micromata/http-fake-backend/issues/11) 74 | > 75 | > ##### BREAKING CHANGES 76 | > 77 | > * The transitive dependency punycode@2.1.0 needs Node version ">=6". 78 | > * The setup.js is divided to multiple files. 79 | Therefore you need to change the import of the setup in your endpoint files 80 | like the following: 81 | > 82 | > ```javascript 83 | > // before 84 | > const SetupEndpoint = require('./setup/setup.js'); 85 | > 86 | > // now 87 | > const SetupEndpoint = require('./setup/index.js'); 88 | > 89 | > // or: 90 | > const SetupEndpoint = require('./setup/'); 91 | > ``` 92 | 93 | 94 | ### BREAKING CHANGES 95 | 96 | * This project now needs Node 6 or greater. 97 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Micromata (Michael Kühnel), www.micromata.de 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Sorry, this project is unmaintained 😢 2 | Thanks to all contributors for your work! 3 | 4 | 5 | [![npm version](https://badge.fury.io/js/generator-http-fake-backend.svg)](http://badge.fury.io/js/generator-http-fake-backend) 6 | [![Build Status](https://travis-ci.org/micromata/generator-http-fake-backend.svg?branch=master)](https://travis-ci.org/micromata/generator-http-fake-backend) 7 | [![Coverage Status](https://coveralls.io/repos/github/micromata/generator-http-fake-backend/badge.svg?branch=master)](https://coveralls.io/github/micromata/generator-http-fake-backend?branch=master) 8 | [![Dependency Status](https://david-dm.org/micromata/generator-http-fake-backend.svg)](https://david-dm.org/micromata/generator-http-fake-backend) 9 | [![devDependency Status](https://david-dm.org/micromata/generator-http-fake-backend/dev-status.svg?theme=shields.io)](https://david-dm.org/micromata/generator-http-fake-backend#info=devDependencies) 10 | [![Unicorn](https://img.shields.io/badge/unicorn-approved-ff69b4.svg?style=flat)](https://www.youtube.com/watch?v=qRC4Vk6kisY) 11 | 12 | # Yeoman generator for http-fake-backend 13 | 14 | > Build a fake backend by providing the content of JSON files or JavaScript objects through configurable routes. 15 | 16 | ``` 17 | _-----_ 18 | | | .--------------------------. 19 | |--(o)--| | Welcome to the | 20 | `---------´ | phenomenal | 21 | ( _´U`_ ) | http-fake-backend | 22 | /___A___\ | generator! | 23 | | ~ | '--------------------------' 24 | __'.___.'__ 25 | ´ ` |° ´ Y ` 26 | ``` 27 | 28 | Please check the [README](https://github.com/micromata/http-fake-backend) of »http-fake-backend« to get detailed information about what it’s all about. 29 | 30 | This readme only contains the gist of it and mainly describes Yeoman specific things. 31 | 32 | ## Installation 33 | 34 | First, install [Yeoman](http://yeoman.io) and `generator-http-fake-backend` using npm (we assume you have pre-installed [Node.js](https://nodejs.org/) 6.0.0 or greater). 35 | 36 | ```bash 37 | # via Yarn 38 | yarn global add yo 39 | yarn global add generator-http-fake-backend 40 | 41 | # via npm 42 | npm install -g yo 43 | npm install -g generator-http-fake-backend 44 | ``` 45 | 46 | Then generate your fake backend server: 47 | 48 | ```bash 49 | yo http-fake-backend 50 | ``` 51 | 52 | ## Generating endpoints 53 | 54 | ```bash 55 | yo http-fake-backend:endpoint 56 | ``` 57 | 58 | ![Demo](https://cloud.githubusercontent.com/assets/441011/19894056/ce7c4ece-a04b-11e6-9cc0-a30429d98b6f.gif) 59 | 60 | Please see detailed info regarding how to adjust your endpoints over here: 61 | 62 | 63 | ## Start the server 64 | 65 | ``` 66 | npm run start:dev 67 | ``` 68 | 69 | This way the server uses `nodemon` to restart itself on changes. 70 | This way you dont have to restart the server in case you changed an endpoint. 71 | 72 | ## Getting To Know Yeoman 73 | 74 | Yeoman has a heart of gold. He’s a person with feelings and opinions, but he’s very easy to work with. If you think he’s too opinionated, he can be easily convinced. Feel free to [learn more about him](http://yeoman.io/). 75 | 76 | ## Related 77 | 78 | * [http-fake-backend](https://github.com/micromata/http-fake-backend) – Static version of the hapi server that this generator creates. 79 | 80 | ## License 81 | 82 | MIT © [Micromata](www.micromata.de) 83 | 84 | Please be aware of the licenses of the components we use in this project. 85 | Everything else that has been developed by the contributions to this project is under [MIT License](LICENSE). 86 | -------------------------------------------------------------------------------- /__tests__/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path'); 3 | const assert = require('yeoman-assert'); 4 | const helpers = require('yeoman-test'); 5 | const helper = require('../generators/app/promptingHelpers'); 6 | const chalk = require('chalk'); 7 | 8 | describe('generator-http-fake-backend → server with custom header', () => { 9 | beforeAll(done => { 10 | helpers.run(path.join(__dirname, '../generators/app')) 11 | .withPrompts({ 12 | serverPort: 8081, 13 | apiPrefix: '/api', 14 | customHeader: true, 15 | customHeaderName: 'HeaderName', 16 | customHeaderValue: 'HeaderValue' 17 | }) 18 | .on('end', done); 19 | }); 20 | 21 | describe('file creation', () => { 22 | it('should create dot files', () => { 23 | assert.file([ 24 | '.editorconfig', 25 | '.env', 26 | '.gitattributes', 27 | '.gitignore' 28 | ]); 29 | }); 30 | 31 | describe('.env', () => { 32 | it('should contain the prompted port number', () => { 33 | assert.fileContent('.env', /SERVER_PORT=8081/); 34 | }); 35 | it('should contain the prompted api url prefix', () => { 36 | assert.fileContent('.env', /API_PREFIX=\/api/); 37 | }); 38 | it('should contain the prompted custom header name', () => { 39 | assert.fileContent('.env', /CUSTOM_HEADER_NAME=HeaderName/); 40 | }); 41 | it('should contain the prompted custom header value', () => { 42 | assert.fileContent('.env', /CUSTOM_HEADER_VALUE=HeaderValue/); 43 | }); 44 | }); 45 | 46 | it('should create meta files', () => { 47 | assert.file([ 48 | 'LICENSE', 49 | 'nodemon.json', 50 | 'package.json', 51 | 'README.md', 52 | 'yarn.lock' 53 | ]); 54 | }); 55 | 56 | it('should create JS files in root directory', () => { 57 | assert.file([ 58 | 'config.js', 59 | 'index.js', 60 | 'manifest.js', 61 | 'server.js' 62 | ]); 63 | }); 64 | 65 | it('should create response-files directory', () => { 66 | assert.file([ 67 | 'response-files/.gitkeep' 68 | ]); 69 | }); 70 | 71 | it('should create server files', () => { 72 | assert.file([ 73 | 'server/api/setup/lib/getContentDisposition.js', 74 | 'server/api/setup/lib/getCustomResponseHeader.js', 75 | 'server/api/setup/index.js', 76 | 'server/api/setup/supportedMethod.js', 77 | 'server/api/setup/unsupportedMethods.js', 78 | 'server/web/index.js', 79 | 'server/web/public/assets/css/styles.css', 80 | 'server/web/public.js', 81 | 'server/web/views/helpers/json.js', 82 | 'server/web/views/helpers/methods.js', 83 | 'server/web/views/helpers/removeCurlyBraces.js', 84 | 'server/web/views/index.hbs', 85 | 'server/web/views/layout/layout.hbs', 86 | 'server/web/views/partials/footer.hbs', 87 | 'server/web/views/partials/header.hbs' 88 | ]); 89 | }); 90 | 91 | it('should create test files', () => { 92 | assert.file([ 93 | 'test/config.js', 94 | 'test/index.js', 95 | 'test/manifest.js', 96 | 'test/server/api/customResponseHeader.js', 97 | 'test/server/api/endpoint.js', 98 | 'test/server/api/fakeStatusCode.js', 99 | 'test/server/api/fileTypes.js', 100 | 'test/server/api/fixtures/example.pdf', 101 | 'test/server/api/fixtures/response.html', 102 | 'test/server/api/fixtures/response.json', 103 | 'test/server/api/fixtures/response.txt', 104 | 'test/server/web/index.js' 105 | ]); 106 | }); 107 | }); 108 | }); 109 | 110 | describe('generator-http-fake-backend → server without custom header', () => { 111 | beforeAll(done => { 112 | helpers.run(path.join(__dirname, '../generators/app')) 113 | .withPrompts({ 114 | serverPort: 8081, 115 | apiPrefix: '/api', 116 | customHeader: false 117 | }) 118 | .on('end', done); 119 | }); 120 | 121 | describe('.env', () => { 122 | it('should contain the prompted custom header name', () => { 123 | assert.fileContent('.env', /CUSTOM_HEADER_NAME=\n/); 124 | }); 125 | it('should contain the prompted custom header value', () => { 126 | assert.fileContent('.env', /CUSTOM_HEADER_VALUE=\n/); 127 | }); 128 | }); 129 | }); 130 | 131 | describe('generator-http-fake-backend → server → prompting helpers', () => { 132 | describe('→ validateApiPrefix()', () => { 133 | it('should accept a leading slash', () => { 134 | assert.equal(helper.validateApiPrefix('/api'), true); 135 | }); 136 | it('should fail with a trailing slash', () => { 137 | assert.equal(helper.validateApiPrefix('/api/'), chalk.red('please enter API prefix without trailing `/`.')); 138 | }); 139 | it('should fail when missing a leading slash', () => { 140 | assert.equal(helper.validateApiPrefix('api'), chalk.red('API prefix has to begin with a `/`.')); 141 | }); 142 | }); 143 | 144 | describe('→ validateCustomHeader()', () => { 145 | it('should accept a non empty string', () => { 146 | assert.equal(helper.validateCustomHeader('x-powered-by'), true); 147 | }); 148 | it('should fail with a empty string', () => { 149 | assert.equal(helper.validateCustomHeader(''), chalk.red('Can’t be an empty string.')); 150 | }); 151 | it('should fail with a empty string', () => { 152 | assert.equal(helper.validateCustomHeader(' '), chalk.red('Can’t be an empty string.')); 153 | }); 154 | }); 155 | }); 156 | -------------------------------------------------------------------------------- /__tests__/endpoint.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path'); 3 | const assert = require('yeoman-assert'); 4 | const helpers = require('yeoman-test'); 5 | const helper = require('../generators/endpoint/promptingHelpers'); 6 | const chalk = require('chalk'); 7 | 8 | describe('generator-http-fake-backend → endpoint', () => { 9 | beforeAll(() => { 10 | return helpers.run(path.join(__dirname, '../generators/endpoint')) 11 | .withOptions({someOption: true}) 12 | .withPrompts({ 13 | endpointName: 'endpoint', 14 | params: '/bar', 15 | method: 'GET', 16 | responseType: 'objectLiteral', 17 | response: '{ status: \'ok\' }', 18 | anotherUrl: false 19 | }) 20 | .toPromise(); 21 | }); 22 | 23 | it('should create endpoint.js', () => { 24 | assert.file([ 25 | 'server/api/endpoint.js' 26 | ]); 27 | }); 28 | 29 | describe('endpoint.js', () => { 30 | it('should contain the prompted name', () => { 31 | assert.fileContent('server/api/endpoint.js', /name: 'endpoint',/); 32 | }); 33 | it('should contain the prompted params', () => { 34 | assert.fileContent('server/api/endpoint.js', /params: '\/bar',/); 35 | }); 36 | it('should contain the prompted method', () => { 37 | assert.fileContent('server/api/endpoint.js', /method: 'GET',/); 38 | }); 39 | it('should contain the prompted response', () => { 40 | assert.fileContent('server/api/endpoint.js', /response: { status: 'ok' }/); 41 | }); 42 | it('should not contain a statuscode key', () => { 43 | assert.noFileContent('server/api/endpoint.js', /statusCode/); 44 | }); 45 | }); 46 | }); 47 | 48 | describe('generator-http-fake-backend → endpoint → JSON file', () => { 49 | beforeAll(() => { 50 | return helpers.run(path.join(__dirname, '../generators/endpoint')) 51 | .withOptions({someOption: true}) 52 | .withPrompts({ 53 | endpointName: 'endpoint', 54 | method: 'GET', 55 | responseType: 'fileContent', 56 | contentType: 'json', 57 | response: 'foo.json', 58 | statusCode: 204, 59 | anotherUrl: false 60 | }) 61 | .toPromise(); 62 | }); 63 | 64 | it('should create foo.json', () => { 65 | assert.file([ 66 | 'response-files/foo.json' 67 | ]); 68 | }); 69 | 70 | it('should create endpoint.js', () => { 71 | assert.file([ 72 | 'server/api/endpoint.js' 73 | ]); 74 | }); 75 | 76 | describe('foo.json', () => { 77 | it('should contain the content off the template', () => { 78 | assert.fileContent('response-files/foo.json', /{ "success": true }/); 79 | }); 80 | }); 81 | 82 | describe('endpoint.js', () => { 83 | it('should contain the prompted response', () => { 84 | assert.fileContent('server/api/endpoint.js', /response: '\/response-files\/foo.json'/); 85 | }); 86 | 87 | it('should not have a mimeType key ', () => { 88 | assert.noFileContent('server/api/endpoint.js', /mimeType:/); 89 | }); 90 | 91 | it('should not have a sendFile key ', () => { 92 | assert.noFileContent('server/api/endpoint.js', /sendFile:/); 93 | }); 94 | 95 | it('should contain the correct statuscode', () => { 96 | assert.fileContent('server/api/endpoint.js', /statusCode: 204/); 97 | }); 98 | 99 | it('should not contain the params key', () => { 100 | assert.noFileContent('server/api/endpoint.js', /params/); 101 | }); 102 | }); 103 | }); 104 | 105 | describe('generator-http-fake-backend → endpoint → Text file', () => { 106 | beforeAll(() => { 107 | return helpers.run(path.join(__dirname, '../generators/endpoint')) 108 | .withOptions({someOption: true}) 109 | .withPrompts({ 110 | endpointName: 'endpoint', 111 | method: 'GET', 112 | responseType: 'fileContent', 113 | contentType: 'txt', 114 | response: 'foo.txt', 115 | statusCode: 204, 116 | anotherUrl: false 117 | }) 118 | .toPromise(); 119 | }); 120 | 121 | it('should create foo.txt', () => { 122 | assert.file([ 123 | 'response-files/foo.txt' 124 | ]); 125 | }); 126 | 127 | it('should create endpoint.js', () => { 128 | assert.file([ 129 | 'server/api/endpoint.js' 130 | ]); 131 | }); 132 | 133 | describe('foo.txt', () => { 134 | it('should contain the content off the template', () => { 135 | assert.fileContent('response-files/foo.txt', /Success/); 136 | }); 137 | }); 138 | 139 | describe('endpoint.js', () => { 140 | it('should contain the prompted response', () => { 141 | assert.fileContent('server/api/endpoint.js', /response: '\/response-files\/foo.txt'/); 142 | }); 143 | 144 | it('should have the correct mimeType', () => { 145 | assert.fileContent('server/api/endpoint.js', /mimeType: 'text\/plain'/); 146 | }); 147 | 148 | it('should not have a sendFile key ', () => { 149 | assert.noFileContent('server/api/endpoint.js', /sendFile:/); 150 | }); 151 | }); 152 | }); 153 | 154 | describe('generator-http-fake-backend → endpoint → HTML file', () => { 155 | beforeAll(() => { 156 | return helpers.run(path.join(__dirname, '../generators/endpoint')) 157 | .withOptions({someOption: true}) 158 | .withPrompts({ 159 | endpointName: 'endpoint', 160 | method: 'GET', 161 | responseType: 'fileContent', 162 | contentType: 'html', 163 | response: 'foo.html', 164 | statusCode: 204, 165 | anotherUrl: false 166 | }) 167 | .toPromise(); 168 | }); 169 | 170 | it('should create foo.html', () => { 171 | assert.file([ 172 | 'response-files/foo.html' 173 | ]); 174 | }); 175 | 176 | it('should create endpoint.js', () => { 177 | assert.file([ 178 | 'server/api/endpoint.js' 179 | ]); 180 | }); 181 | 182 | describe('foo.html', () => { 183 | it('should contain the content off the template', () => { 184 | assert.fileContent('response-files/foo.html', /GitHub<\/a>/); 185 | }); 186 | }); 187 | 188 | describe('endpoint.js', () => { 189 | it('should contain the prompted response', () => { 190 | assert.fileContent('server/api/endpoint.js', /response: '\/response-files\/foo.html'/); 191 | }); 192 | 193 | it('should have the correct mimeType', () => { 194 | assert.fileContent('server/api/endpoint.js', /mimeType: 'text\/html'/); 195 | }); 196 | 197 | it('should not have a sendFile key ', () => { 198 | assert.noFileContent('server/api/endpoint.js', /sendFile:/); 199 | }); 200 | }); 201 | }); 202 | 203 | describe('generator-http-fake-backend → endpoint → send file', () => { 204 | beforeAll(() => { 205 | return helpers.run(path.join(__dirname, '../generators/endpoint')) 206 | .withOptions({someOption: true}) 207 | .withPrompts({ 208 | endpointName: 'endpoint', 209 | method: 'GET', 210 | responseType: 'fileAttachment', 211 | response: 'foo.pdf', 212 | statusCode: 204, 213 | anotherUrl: false 214 | }) 215 | .toPromise(); 216 | }); 217 | 218 | it('should create endpoint.js', () => { 219 | assert.file([ 220 | 'server/api/endpoint.js' 221 | ]); 222 | }); 223 | 224 | describe('endpoint.js', () => { 225 | it('should contain the prompted response', () => { 226 | assert.fileContent('server/api/endpoint.js', /response: '\/response-files\/foo.pdf'/); 227 | }); 228 | 229 | it('should not have a mimeType key', () => { 230 | assert.noFileContent('server/api/endpoint.js', /mimeType: 'text\/html'/); 231 | }); 232 | 233 | it('should have a sendFile key ', () => { 234 | assert.fileContent('server/api/endpoint.js', /sendFile: true/); 235 | }); 236 | }); 237 | }); 238 | 239 | describe('generator-http-fake-backend → endpoint → prompting helpers', () => { 240 | describe('→ filterResponseType()', () => { 241 | it('should return correct outputs', () => { 242 | assert.equal(helper.filterResponseType('The content of a file'), 'fileContent'); 243 | assert.equal(helper.filterResponseType('A file via Content-Disposition: attachment'), 'fileAttachment'); 244 | assert.equal(helper.filterResponseType('A JavaScript object literal as JSON'), 'objectLiteral'); 245 | assert.equal(helper.filterResponseType('An error object'), 'error'); 246 | }); 247 | }); 248 | 249 | describe('→ validateJsObject()', () => { 250 | it('should validate a string representation of an object', () => { 251 | assert.equal(helper.validateJsObject('{}'), true); 252 | }); 253 | it('should validate a string representation of an array', () => { 254 | assert.equal(helper.validateJsObject('[]'), true); 255 | }); 256 | it('should fail on a string', () => { 257 | assert.equal(helper.validateJsObject('foo'), chalk.red('Your input doesn’t look like an object or array at all.')); 258 | }); 259 | }); 260 | 261 | describe('→ validateJson()', () => { 262 | const failMsg = chalk.red('Please enter valid filename (*.json)'); 263 | 264 | it('should validate `foo.json`', () => { 265 | assert.equal(helper.validateJson('foo.json'), true); 266 | }); 267 | it('should fail `foo.txt`', () => { 268 | assert.equal(helper.validateJson('foo.txt'), failMsg); 269 | }); 270 | it('should fail when using forbidden chars', () => { 271 | assert.equal(helper.validateJson('^foo.json'), failMsg); 272 | }); 273 | }); 274 | 275 | describe('→ validateEndpoint()', () => { 276 | const failMsg = chalk.red('Please enter a valid name. This will be a part of the url.'); 277 | 278 | it('should validate `foo`', () => { 279 | assert.equal(helper.validateEndpoint('foo'), true); 280 | }); 281 | it('should fail when using a leading slash', () => { 282 | assert.equal(helper.validateEndpoint('/foo'), failMsg); 283 | }); 284 | it('should fail when using a trailing slash', () => { 285 | assert.equal(helper.validateEndpoint('foo/'), failMsg); 286 | }); 287 | it('should fail when using forbidden chars', () => { 288 | assert.equal(helper.validateEndpoint('f^oo'), failMsg); 289 | }); 290 | }); 291 | 292 | describe('→ validateParams()', () => { 293 | const failMsg = chalk.red('Please enter valid path parameters with a leading `/`. See http://hapijs.com/api#path-parameters'); 294 | 295 | it('should validate an empty string', () => { 296 | assert.equal(helper.validateParams(''), true); 297 | }); 298 | it('should validate `/foo/{id}`', () => { 299 | assert.equal(helper.validateParams('/foo/{id}'), true); 300 | }); 301 | it('should fail when missing a leading slash', () => { 302 | assert.equal(helper.validateParams('foo'), failMsg); 303 | }); 304 | it('should fail when using forbidden chars', () => { 305 | assert.equal(helper.validateParams('/foo^^'), failMsg); 306 | }); 307 | }); 308 | 309 | describe('→ validateErrorStatusCode()', () => { 310 | const failMsg = chalk.red('Please enter valid 4xx or 5xx status code supported by https://github.com/hapijs/boom'); 311 | 312 | it('should accept entering `400`', () => { 313 | assert.equal(helper.validateErrorStatusCode('400'), true); 314 | }); 315 | it('should fail when entering a `200`', () => { 316 | assert.equal(helper.validateErrorStatusCode('200'), failMsg); 317 | }); 318 | it('should fail when using any random string', () => { 319 | assert.equal(helper.validateErrorStatusCode('Hej there'), failMsg); 320 | }); 321 | }); 322 | 323 | describe('→ validateStatusCode()', () => { 324 | const failMsg = chalk.red('Please enter a number which reprents a valid HTTP status code'); 325 | 326 | it('should accept entering `400`', () => { 327 | assert.equal(helper.validateStatusCode('400'), true); 328 | }); 329 | it('should accept when entering a `200`', () => { 330 | assert.equal(helper.validateStatusCode('200'), true); 331 | }); 332 | it('should fail when entering a `2000`', () => { 333 | assert.equal(helper.validateStatusCode('2000'), failMsg); 334 | }); 335 | it('should fail when entering a `600`', () => { 336 | assert.equal(helper.validateStatusCode('600'), failMsg); 337 | }); 338 | it('should fail when using any random string', () => { 339 | assert.equal(helper.validateStatusCode('Hej there'), failMsg); 340 | }); 341 | }); 342 | }); 343 | -------------------------------------------------------------------------------- /generators/app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Generator = require('yeoman-generator'); 3 | const chalk = require('chalk'); 4 | const yosay = require('yosay'); 5 | const superb = require('superb'); 6 | const commandExists = require('command-exists').sync; 7 | const helper = require('./promptingHelpers'); 8 | 9 | module.exports = class extends Generator { 10 | prompting() { 11 | const done = this.async(); 12 | 13 | // Have Yeoman greet the user. 14 | this.log(yosay(` 15 | Welcome to the ${superb()} ${chalk.yellow('http-fake-backend')} generator! 16 | `)); 17 | 18 | const prompts = [ 19 | { 20 | type: 'input', 21 | name: 'serverPort', 22 | message: 'On which port should the server run?', 23 | default: '8081', 24 | store: true 25 | }, { 26 | type: 'input', 27 | name: 'apiPrefix', 28 | message: 'What should be the url prefix of the endpoints?', 29 | default: '/api', 30 | store: true, 31 | validate: helper.validateApiPrefix 32 | }, { 33 | type: 'confirm', 34 | name: 'customHeader', 35 | message: 'Would you like to add a custom response header (e.g. Authorization)?', 36 | default: false 37 | }, { 38 | type: 'input', 39 | name: 'customHeaderName', 40 | message: 'Custom header name:', 41 | validate: helper.validateCustomHeader, 42 | when(answers) { 43 | return answers.customHeader; 44 | } 45 | }, { 46 | type: 'input', 47 | name: 'customHeaderValue', 48 | message: 'Custom header value:', 49 | validate: helper.validateCustomHeader, 50 | when(answers) { 51 | return answers.customHeader; 52 | } 53 | } 54 | ]; 55 | 56 | this.prompt(prompts).then(props => { 57 | this.props = props; 58 | // To access props later use this.props.someOption; 59 | 60 | done(); 61 | }); 62 | } 63 | 64 | writing() { 65 | // Dotfiles 66 | this.fs.copy( 67 | this.templatePath('editorconfig'), 68 | this.destinationPath('.editorconfig') 69 | ); 70 | this.fs.copyTpl( 71 | this.templatePath('_env'), 72 | this.destinationPath('.env'), { 73 | serverPort: this.props.serverPort, 74 | apiPrefix: this.props.apiPrefix, 75 | customHeaderName: this.props.customHeaderName, 76 | customHeaderValue: this.props.customHeaderValue 77 | } 78 | ); 79 | this.fs.copy( 80 | this.templatePath('gitattributes'), 81 | this.destinationPath('.gitattributes') 82 | ); 83 | this.fs.copy( 84 | this.templatePath('gitignore'), 85 | this.destinationPath('.gitignore') 86 | ); 87 | 88 | // Meta Files 89 | this.fs.copy( 90 | this.templatePath('LICENSE'), 91 | this.destinationPath('LICENSE') 92 | ); 93 | this.fs.copy( 94 | this.templatePath('nodemon.json'), 95 | this.destinationPath('nodemon.json') 96 | ); 97 | this.fs.copy( 98 | this.templatePath('package.json'), 99 | this.destinationPath('package.json') 100 | ); 101 | this.fs.copy( 102 | this.templatePath('README.md'), 103 | this.destinationPath('README.md') 104 | ); 105 | this.fs.copy( 106 | this.templatePath('yarn.lock'), 107 | this.destinationPath('yarn.lock') 108 | ); 109 | 110 | // Root JS files 111 | this.fs.copy( 112 | this.templatePath('config.js'), 113 | this.destinationPath('config.js') 114 | ); 115 | this.fs.copy( 116 | this.templatePath('index.js'), 117 | this.destinationPath('index.js') 118 | ); 119 | 120 | this.fs.copy( 121 | this.templatePath('manifest.js'), 122 | this.destinationPath('manifest.js') 123 | ); 124 | 125 | this.fs.copy( 126 | this.templatePath('server.js'), 127 | this.destinationPath('server.js') 128 | ); 129 | 130 | // Directory for response files 131 | this.fs.copy( 132 | this.templatePath('response-files/gitkeep'), 133 | this.destinationPath('response-files/.gitkeep') 134 | ); 135 | 136 | // Server files 137 | this.fs.copy( 138 | this.templatePath('server'), 139 | this.destinationPath('server') 140 | ); 141 | 142 | // Test files 143 | this.fs.copy( 144 | this.templatePath('test'), 145 | this.destinationPath('test') 146 | ); 147 | } 148 | 149 | install() { 150 | const hasYarn = commandExists('yarn'); 151 | this.installDependencies({ 152 | npm: !hasYarn, 153 | bower: false, 154 | yarn: hasYarn 155 | }); 156 | } 157 | 158 | end() { 159 | this.log(yosay(` 160 | That’s it. Feel free to fire up the server with ${chalk.green('npm run start:dev')} 161 | or use our subgenerator to create endpoints: ${chalk.yellow('yo http-fake-backend:endpoint')} 162 | `, {maxLength: 40})); 163 | } 164 | }; 165 | -------------------------------------------------------------------------------- /generators/app/promptingHelpers.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const helper = {}; 3 | 4 | helper.validateApiPrefix = function (value) { 5 | const check = value.match(/^\/|\/$/g); 6 | let returnValue; 7 | 8 | if (check === null) { 9 | returnValue = chalk.red('API prefix has to begin with a `/`.'); 10 | } else if (check[1] === '/') { 11 | returnValue = chalk.red('please enter API prefix without trailing `/`.'); 12 | } else { 13 | returnValue = true; 14 | } 15 | return returnValue; 16 | }; 17 | 18 | helper.validateCustomHeader = function (value) { 19 | if (value.trim() === '') { 20 | return chalk.red('Can’t be an empty string.'); 21 | } 22 | return true; 23 | }; 24 | 25 | module.exports = helper; 26 | -------------------------------------------------------------------------------- /generators/app/templates/LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2016 Micromata (Michael Kühnel), www.micromata.de 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /generators/app/templates/README.md: -------------------------------------------------------------------------------- 1 | [![GitHub version](https://badge.fury.io/gh/micromata%2Fhttp-fake-backend.svg)](https://badge.fury.io/gh/micromata%2Fhttp-fake-backend) 2 | [![Build Status](https://travis-ci.org/micromata/http-fake-backend.svg?branch=master)](https://travis-ci.org/micromata/http-fake-backend) 3 | [![Coverage Status](https://coveralls.io/repos/github/micromata/http-fake-backend/badge.svg?branch=master)](https://coveralls.io/github/micromata/http-fake-backend?branch=master) 4 | [![Dependency Status](https://david-dm.org/micromata/http-fake-backend.svg)](https://david-dm.org/micromata/http-fake-backend) 5 | [![devDependency Status](https://david-dm.org/micromata/http-fake-backend/dev-status.svg?theme=shields.io)](https://david-dm.org/micromata/http-fake-backend#info=devDependencies) 6 | [![Unicorn](https://img.shields.io/badge/unicorn-approved-ff69b4.svg?style=flat)](https://www.youtube.com/watch?v=qRC4Vk6kisY) 7 | 8 | # http-fake-backend 9 | 10 | > Build a fake backend by providing the content of JSON files or JavaScript objects through configurable routes. 11 | 12 | *It actually can serve the content of other file types as well as sending the files itself as response.* 13 | 14 | Comes as a Node.js server. Useful for mocking, testing and developing independent of the »real« backend. 15 | 16 | ## Example 17 | Let’s say you need an endpoint like which should return: 18 | 19 | ``` 20 | { 21 | "response": "Yeah" 22 | } 23 | ``` 24 | 25 | It’s a matter of seconds to create this endpoint with help of this little hapi server. 26 | 27 | It might take a few seconds longer as setting up the well-made [JSON Server](https://github.com/typicode/json-server) but it’s way more flexible. 28 | 29 | ## Requirements 30 | 31 | - Node.js (v6.0.0 or greater) 32 | 33 | ## Install 34 | 35 | ```bash 36 | git clone https://github.com/micromata/http-fake-backend.git 37 | npm install 38 | ``` 39 | 40 | Or with help of [Yeoman](http://yeoman.io) 41 | 42 | ```bash 43 | npm install -g yo 44 | npm install -g generator-http-fake-backend 45 | ``` 46 | 47 | This comes in handy, because the Yeoman generator has a sub-generator to setup endpoints of your fake backend very convenient. See . 48 | 49 | ## Default Address 50 | The server runs at providing a page with links to all existing API endpoints. 51 | 52 | ## Start the server 53 | 54 | There are the following two options. 55 | 56 | ### During development 57 | 58 | ``` 59 | npm run start:dev 60 | ``` 61 | 62 | This way the server uses `nodemon` to restart itself on changes. 63 | 64 | 65 | ### Later (eg. for tests in CI) 66 | 67 | ``` 68 | npm start 69 | ``` 70 | 71 | Just starts the server via node. 72 | 73 | ## Configure endpoints 74 | 75 | Each endpoint needs a configuration file in `/server/api/` to define routes, http method and the response. 76 | 77 | ### Example configurations 78 | 79 | #### Simple Example 80 | 81 | `/server/api/simpleExample.js`: 82 | 83 | ```js 84 | module.exports = SetupEndpoint({ 85 | name: 'simpleExample', 86 | urls: [{ 87 | requests: [ 88 | { response: '/response-files/simpleExample.json' } 89 | ] 90 | }] 91 | }); 92 | ``` 93 | 94 | #### Advanced Example 95 | 96 | `/server/api/anotherExample.js`: 97 | 98 | ```js 99 | module.exports = SetupEndpoint({ 100 | name: 'anotherExample', 101 | urls: [{ 102 | params: '/read', 103 | requests: [{ 104 | method: 'GET', 105 | response: '/response-files/anotherExample.json' 106 | }] 107 | }, { 108 | params: '/update/{id}', 109 | requests: [{ 110 | method: ['PUT', 'PATCH'], 111 | response: { 112 | success: true 113 | } 114 | }, { 115 | method: 'DELETE', 116 | response: { 117 | deleted: true 118 | } 119 | }] 120 | }, ] 121 | }); 122 | ``` 123 | 124 | #### Serving different content types 125 | 126 | `/server/api/fileTypes.js`: 127 | 128 | ```js 129 | module.exports = SetupEndpoint({ 130 | name: 'fileTypes', 131 | urls: [{ 132 | params: '/json', 133 | requests: [{ 134 | response: '/response-files/simpleExample.json' 135 | }] 136 | }, { 137 | params: '/text', 138 | requests: [{ 139 | response: '/response-files/example.txt', 140 | mimeType: 'text/plain' 141 | }] 142 | }, { 143 | params: '/html', 144 | requests: [{ 145 | response: '/response-files/example.html', 146 | mimeType: 'text/html' 147 | }] 148 | }, { 149 | params: '/pdf', 150 | requests: [{ 151 | response: '/response-files/example.pdf', 152 | sendFile: true 153 | }] 154 | }] 155 | }); 156 | ``` 157 | 158 | #### Faking HTTP errors and status code 159 | 160 | `/server/api/fakingStatusCodes.js`: 161 | 162 | ```js 163 | module.exports = SetupEndpoint({ 164 | name: 'statusCodes', 165 | urls: [ 166 | { 167 | params: '/boomError', 168 | requests: [{ 169 | // Returns a 402 status code + error message provided by boom: 170 | // { 171 | // "error" : "Payment Required", 172 | // "message" : "Payment Required", 173 | // "statusCode" : 402 174 | // } 175 | statusCode: 402 176 | }] 177 | }, 178 | { 179 | params: '/customError', 180 | requests: [{ 181 | // Returns a HTTP status code 406 and a self defined response: 182 | response: { error: true }, 183 | statusCode: 406 184 | }] 185 | }, 186 | { 187 | params: '/regularResponse', 188 | requests: [{ 189 | // Returns a 401 error provided by boom 190 | // as defined on endpoint level 191 | response: '/response-files/anotherExample.json' 192 | }] 193 | } 194 | ], 195 | statusCode: 401 196 | }); 197 | ``` 198 | 199 | The configuration object in Detail: 200 | 201 | * `name` 202 | * Is used to set the endpoint. 203 | * `urls` 204 | * You need to add at least one url object. 205 | * `urls.params` 206 | * Optional 207 | * URL path parameters with fixed and/or variable path segments. 208 | * Example: 209 | * `params: '/update/{id}'` 210 | * See hapi docs. For example regarding optional [path parameters](http://hapijs.com/api#path-parameters). 211 | * `urls.requests` 212 | * You need to add at least one request object. 213 | * Multiple request objects are needed in case you like to serve different responses via different HTTP methods with the same URL. 214 | * `urls.requests.method` 215 | * optional. Uses `GET` when not defined. 216 | * `string`, or `array` of strings. 217 | * is used to define the http method(s) to which the endpoint will listen. 218 | * `urls.requests.response` 219 | * Could be a string pointing to a file: 220 | * `response: '/response-files/articles.json'` 221 | * Or just a JavaScript object: 222 | * `response: { success: true }` 223 | * `urls.requests.mimeType` 224 | * Optional (string). Defaults to `application/json`. 225 | * Is used to set the `content-type` response header. 226 | * `urls.requests.sendFile` 227 | * Optional (boolean). Defaults to `false`. 228 | * Sends the file as response instead of returning the file content. 229 | * `urls.requests.statusCode` 230 | * Optional (boolean). Defaults to `200` 231 | * The HTTP status code of the response. 232 | * Will return: 233 | * a status code with a self defined response if you provide a response property 234 | * a status code with a predefined error object provided by [boom](https://github.com/hapijs/boom) if you dont provide a response property for that request. 235 | * `statusCode` 236 | * Optional 237 | * Every subroute of this endpoint will return a HTTP error with the given status code provided by [boom](https://github.com/hapijs/boom). 238 | 239 | ## Configure server 240 | 241 | The main config is handled via a file named `.env` with the following content: 242 | 243 | ```dosini 244 | # NODE_ENV 245 | # Could be either `development` or `production` 246 | NODE_ENV=development 247 | 248 | # Port of the Server 249 | SERVER_PORT=8081 250 | 251 | # Port for running the tests 252 | TEST_PORT=9090 253 | 254 | # URL Prefix for the endpoints 255 | # eg. http://localhost:8081/api/foo 256 | API_PREFIX=/api 257 | 258 | # Custom response header 259 | #CUSTOM_HEADER_NAME=Authorization 260 | #CUSTOM_HEADER_VALUE=Bearer eyJhbGciOiJIUzUxMiJ9 261 | 262 | ``` 263 | 264 | 265 | ## Related 266 | 267 | * [Yeoman Generator](https://github.com/micromata/generator-http-fake-backend) – Easily generate your fake backend and use sub-generators to setup endpoints like :zap: 268 | 269 | ## License 270 | 271 | Please be aware of the licenses of the components we use in this project. 272 | Everything else that has been developed by the contributions to this project is under [MIT License](LICENSE). 273 | -------------------------------------------------------------------------------- /generators/app/templates/_env: -------------------------------------------------------------------------------- 1 | # NODE_ENV 2 | # Could be either `development` or `production` 3 | NODE_ENV=development 4 | 5 | # Port of the Server 6 | SERVER_PORT=<%= serverPort %> 7 | 8 | # Port for running the tests 9 | TEST_PORT=9090 10 | 11 | # URL Prefix for the endpoints 12 | # eg. http://localhost:8081/api/foo 13 | API_PREFIX=<%= apiPrefix %> 14 | 15 | # Custom response header 16 | CUSTOM_HEADER_NAME=<%= customHeaderName %> 17 | CUSTOM_HEADER_VALUE=<%= customHeaderValue %> 18 | -------------------------------------------------------------------------------- /generators/app/templates/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Confidence = require('confidence'); 4 | require('dotenv').load(); 5 | 6 | const criteria = { 7 | env: process.env.NODE_ENV 8 | }; 9 | 10 | 11 | const config = { 12 | $meta: 'General project wide config.', 13 | projectName: 'http-fake-backend', 14 | env: process.env.NODE_ENV, 15 | apiUrlPrefix: process.env.API_PREFIX, 16 | port: { 17 | web: { 18 | $filter: 'env', 19 | test: process.env.TEST_PORT, 20 | $default: process.env.SERVER_PORT 21 | } 22 | } 23 | }; 24 | 25 | 26 | const store = new Confidence.Store(config); 27 | 28 | 29 | exports.get = function (key) { 30 | 31 | return store.get(key, criteria); 32 | }; 33 | 34 | 35 | exports.meta = function (key) { 36 | 37 | return store.meta(key, criteria); 38 | }; 39 | -------------------------------------------------------------------------------- /generators/app/templates/editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 4 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | 15 | [*.json] 16 | indent_size = 2 17 | 18 | [*.md] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /generators/app/templates/gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /generators/app/templates/gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test/artifacts/* 3 | *.sublime-workspace 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /generators/app/templates/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Glue = require('glue'); 4 | const Manifest = require('./manifest'); 5 | 6 | 7 | const composeOptions = { 8 | relativeTo: __dirname 9 | }; 10 | 11 | 12 | module.exports = Glue.compose.bind(Glue, Manifest.get('/'), composeOptions); 13 | -------------------------------------------------------------------------------- /generators/app/templates/manifest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Confidence = require('confidence'); 4 | const Config = require('./config'); 5 | const Fs = require('fs'); 6 | const Path = require('path'); 7 | const GetCustomResponseHeader = require('./server/api/setup/lib/getCustomResponseHeader'); 8 | 9 | const criteria = { 10 | env: process.env.NODE_ENV 11 | }; 12 | 13 | const pathToEndpointConfigFiles = './server/api'; 14 | 15 | const manifest = { 16 | $meta: 'Hapi server config used by glue to compose the server.', 17 | server: { 18 | debug: { 19 | request: ['error'] 20 | }, 21 | connections: { 22 | routes: { 23 | security: true, 24 | cors: { 25 | origin: ['*'], 26 | credentials: true, 27 | additionalExposedHeaders: [GetCustomResponseHeader(process.env).name] 28 | } 29 | }, 30 | router: { 31 | stripTrailingSlash: true, 32 | isCaseSensitive: false 33 | } 34 | } 35 | }, 36 | connections: [{ 37 | port: Config.get('/port/web'), 38 | labels: ['web'] 39 | }], 40 | registrations: [ 41 | { plugin: 'vision' }, 42 | { 43 | plugin: { 44 | register: 'visionary', 45 | options: { 46 | layout: true, 47 | engines: { hbs: 'handlebars' }, 48 | path: './server/web/views', 49 | partialsPath: './server/web/views/partials', 50 | layoutPath: './server/web/views/layout', 51 | helpersPath: './server/web/views/helpers', 52 | isCached: { 53 | $filter: 'env', 54 | development: false, 55 | production: true 56 | } 57 | } 58 | } 59 | }, 60 | { 61 | plugin: { 62 | register: 'good', 63 | options: { 64 | ops: { 65 | interval: 15000 66 | }, 67 | reporters: { 68 | console: [{ 69 | module: 'good-squeeze', 70 | name: 'Squeeze', 71 | args: [{ 72 | log: '*', 73 | response: '*' 74 | }] 75 | }, { 76 | module: 'good-console', 77 | args: [{ format: 'YYYY-MM-DD/HH:mm:ss.SSS' }] 78 | }, 'stdout'] 79 | } 80 | } 81 | } 82 | }, 83 | { plugin: 'inert' }, 84 | { plugin: './server/web/index' }, 85 | { plugin: './server/web/public' } 86 | ] 87 | }; 88 | 89 | // Add plugins to manifest.registration for every endpoint in ./server/api 90 | Fs.readdirSync(pathToEndpointConfigFiles).map((file) => { 91 | 92 | return Path.join(pathToEndpointConfigFiles, file); 93 | }).filter((file) => { 94 | 95 | return Fs.statSync(file).isFile(); 96 | }).forEach((file) => { 97 | 98 | const plugin = { plugin: './server/api/' + Path.parse(file).name }; 99 | manifest.registrations.push(plugin); 100 | }); 101 | 102 | const store = new Confidence.Store(manifest); 103 | 104 | 105 | exports.get = function (key) { 106 | 107 | return store.get(key, criteria); 108 | }; 109 | 110 | 111 | exports.meta = function (key) { 112 | 113 | return store.meta(key, criteria); 114 | }; 115 | -------------------------------------------------------------------------------- /generators/app/templates/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["*.*"], 3 | "ext": "js json html" 4 | } 5 | -------------------------------------------------------------------------------- /generators/app/templates/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http-fake-backend", 3 | "version": "4.0.3", 4 | "description": "Build a fake backend by providing the content of JSON files or JavaScript objects through configurable routes.", 5 | "license": "MIT", 6 | "main": "index.js", 7 | "homepage": "https://github.com/micromata/http-fake-backend", 8 | "repository": "micromata/http-fake-backend", 9 | "author": "Michael Kühnel ", 10 | "private": true, 11 | "scripts": { 12 | "start": "node server.js", 13 | "start:dev": "nodemon server.js", 14 | "test": "lab -v -c -L", 15 | "security": "nsp check", 16 | "release": "npm run security && standard-version --tag-prefix", 17 | "release:patch": "npm run security && standard-version --tag-prefix --release-as patch", 18 | "release:minor": "npm run security && standard-version --tag-prefix --release-as minor", 19 | "release:major": "npm run security && standard-version --tag-prefix --release-as major", 20 | "coverage": "lab -v -c -r html -o ./test/artifacts/coverage.html && open ./test/artifacts/coverage.html", 21 | "coveralls": "lab -r lcov | ./node_modules/.bin/coveralls" 22 | }, 23 | "eslintConfig": { 24 | "extends": "eslint-config-hapi" 25 | }, 26 | "engines": { 27 | "node": ">=6.0.0" 28 | }, 29 | "dependencies": { 30 | "boom": "^5.2.0", 31 | "confidence": "^3.0.2", 32 | "dotenv": "^5.0.0", 33 | "glue": "^4.2.1", 34 | "good": "^7.3.0", 35 | "good-console": "^7.1.0", 36 | "good-squeeze": "^5.0.2", 37 | "handlebars": "^4.0.10", 38 | "hapi": "^16.6.2", 39 | "inert": "^4.2.1", 40 | "require-dir": "^1.0.0", 41 | "vision": "^4.1.1", 42 | "visionary": "^6.0.2" 43 | }, 44 | "devDependencies": { 45 | "code": "^4.1.0", 46 | "coveralls": "^3.0.0", 47 | "eslint": "^4.18.0", 48 | "eslint-config-hapi": "^11.1.0", 49 | "eslint-plugin-hapi": "^4.0.0", 50 | "lab": "^14.3.2", 51 | "nodemon": "^1.15.0", 52 | "nsp": "^3.2.1", 53 | "standard-version": "^4.3.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /generators/app/templates/response-files/gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micromata/generator-http-fake-backend/daa7b3eb0a209c4ec132122a336d42060f328737/generators/app/templates/response-files/gitkeep -------------------------------------------------------------------------------- /generators/app/templates/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Composer = require('./index'); 4 | const Config = require('./config'); 5 | 6 | Composer((err, server) => { 7 | 8 | if (err) { 9 | throw err; 10 | } 11 | 12 | server.start(() => { 13 | 14 | server.log('info', 'NODE_ENV: ' + Config.get('/env')); 15 | server.log('info', 'Server running at: ' + server.info.uri); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /generators/app/templates/server/api/setup/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Config = require('../../../config'); 4 | const UnsupportedMethods = require('./unsupportedMethods'); 5 | const SupportedMethod = require('./supportedMethod'); 6 | 7 | module.exports = function (settings) { 8 | 9 | const exportEndpoint = {}; 10 | const path = Config.get('/apiUrlPrefix') + '/' + settings.name; 11 | const urls = settings.urls; 12 | 13 | exportEndpoint.register = function (server, options, next) { 14 | 15 | const routes = []; 16 | 17 | const createRoutes = function (url) { 18 | 19 | const params = url.params || ''; 20 | 21 | url.requests.forEach((proposedRequest) => { 22 | 23 | const supportedMethod = SupportedMethod(server, proposedRequest, settings, params, path); 24 | routes.push(supportedMethod); 25 | }); 26 | 27 | const unsupportedMethods = UnsupportedMethods(settings, params, path); 28 | routes.push(unsupportedMethods); 29 | }; 30 | 31 | urls.forEach(createRoutes); 32 | server.route(routes); 33 | next(); 34 | }; 35 | 36 | exportEndpoint.register.attributes = { 37 | name: settings.name, 38 | path, 39 | urls 40 | }; 41 | 42 | return exportEndpoint; 43 | }; 44 | -------------------------------------------------------------------------------- /generators/app/templates/server/api/setup/lib/getContentDisposition.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (filePath) { 4 | 5 | const fileName = /\/([^\/]+\.\S+)$/g.exec(filePath); 6 | return 'attachment; filename=' + fileName[1]; 7 | }; 8 | -------------------------------------------------------------------------------- /generators/app/templates/server/api/setup/lib/getCustomResponseHeader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (env) { 4 | 5 | return { 6 | name: env.CUSTOM_HEADER_NAME || 'X-Powered-By', 7 | value: env.CUSTOM_HEADER_VALUE || 'https://hapijs.com' 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /generators/app/templates/server/api/setup/supportedMethod.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Boom = require('boom'); 4 | const Fs = require('fs'); 5 | const Path = require('path'); 6 | 7 | const GetContentDisposition = require('./lib/getContentDisposition'); 8 | const CustomResponseHeader = require('./lib/getCustomResponseHeader')(process.env); 9 | 10 | module.exports = function (server, proposedRequest, settings, params, path) { 11 | 12 | const method = proposedRequest.method || 'GET'; 13 | const sendFile = proposedRequest.sendFile | false; 14 | const isFile = typeof proposedRequest.response === 'string'; 15 | const mimeType = proposedRequest.mimeType || (sendFile ? 'application/octet-stream' : 'application/json'); 16 | 17 | return { 18 | method, 19 | path: path + params, 20 | handler: function (request, reply) { 21 | 22 | let response; 23 | 24 | if (proposedRequest.statusCode && !proposedRequest.response) { 25 | response = Boom.create(proposedRequest.statusCode); 26 | } 27 | else if (settings.statusCode && !proposedRequest.statusCode) { 28 | response = Boom.create(settings.statusCode); 29 | } 30 | else { 31 | server.log('info', 'Received payload:' + JSON.stringify(request.payload)); 32 | 33 | if (typeof proposedRequest.response === 'string') { 34 | const filePath = Path.normalize(Path.join(__dirname, '../../../', proposedRequest.response)); 35 | response = Fs.readFileSync(filePath); 36 | } 37 | else { 38 | response = proposedRequest.response; 39 | } 40 | 41 | } 42 | 43 | if (response.isBoom === true) { 44 | response.output.headers[CustomResponseHeader.name] = CustomResponseHeader.value; 45 | return reply(response); 46 | } 47 | 48 | if (sendFile && isFile) { 49 | return reply(response).code(proposedRequest.statusCode || 200).type(mimeType) 50 | .header('Content-Disposition', GetContentDisposition(proposedRequest.response)) 51 | .header(CustomResponseHeader.name, CustomResponseHeader.value); 52 | } 53 | return reply(response).code(proposedRequest.statusCode || 200).type(mimeType) 54 | .header(CustomResponseHeader.name, CustomResponseHeader.value); 55 | } 56 | }; 57 | }; 58 | -------------------------------------------------------------------------------- /generators/app/templates/server/api/setup/unsupportedMethods.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Boom = require('boom'); 4 | const CustomResponseHeader = require('./lib/getCustomResponseHeader')(process.env); 5 | 6 | module.exports = function (settings, params, path) { 7 | 8 | return { 9 | method: '*', 10 | path: path + params, 11 | handler: function (request, reply) { 12 | 13 | let response; 14 | 15 | if (settings.statusCode) { 16 | response = Boom.create(settings.statusCode); 17 | } 18 | else { 19 | response = Boom.methodNotAllowed(); 20 | } 21 | 22 | response.output.headers[CustomResponseHeader.name] = CustomResponseHeader.value; 23 | return reply(response); 24 | } 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /generators/app/templates/server/web/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const RequireDir = require('require-dir'); 4 | const apiDir = RequireDir('../api'); 5 | 6 | 7 | const getEndpoints = function (request, reply) { 8 | 9 | const endpoints = []; 10 | 11 | Object.keys(apiDir).forEach((key) => { 12 | 13 | endpoints.push(apiDir[key].register.attributes); 14 | }); 15 | 16 | return reply(endpoints); 17 | }; 18 | 19 | exports.register = function (server, options, next) { 20 | 21 | server.route({ 22 | method: 'GET', 23 | path: '/', 24 | config: { 25 | pre: [{ 26 | method: getEndpoints, 27 | assign: 'getEndpoints' 28 | }], 29 | handler: function (request, reply) { 30 | 31 | return reply.view('index', { 32 | title: 'endpoints / routes', 33 | endpoints: request.pre.getEndpoints 34 | }); 35 | } 36 | } 37 | }); 38 | 39 | next(); 40 | }; 41 | 42 | 43 | exports.register.attributes = { 44 | name: 'index', 45 | dependencies: 'visionary' 46 | }; 47 | -------------------------------------------------------------------------------- /generators/app/templates/server/web/public.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.register = function (server, options, next) { 4 | 5 | server.route({ 6 | method: 'GET', 7 | path: '/assets/{param*}', 8 | handler: { 9 | directory: { 10 | path: './server/web/public/assets', 11 | listing: true 12 | } 13 | } 14 | }); 15 | 16 | next(); 17 | }; 18 | 19 | 20 | exports.register.attributes = { 21 | name: 'public' 22 | }; 23 | -------------------------------------------------------------------------------- /generators/app/templates/server/web/public/assets/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; 3 | } 4 | h1 { font-weight: normal; } 5 | 6 | a:visited { color: inherit; } 7 | 8 | footer { 9 | border-top: 1px solid #dedede; 10 | padding-top: 1em; 11 | } 12 | 13 | .hidden { 14 | display: none; 15 | } 16 | 17 | .endpoints ul { padding-left: 0.6em; } 18 | .endpoints li { list-style-type: none; } 19 | .endpoints li:before { 20 | content: "–"; 21 | margin-right: 0.5em; 22 | } 23 | .endpoints .method { 24 | display: inline-block; 25 | font-family: "Courier New", Courier, monospace; 26 | } 27 | -------------------------------------------------------------------------------- /generators/app/templates/server/web/views/helpers/json.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Returns the given object as string representation. 5 | */ 6 | 7 | module.exports = function (object) { 8 | 9 | return JSON.stringify(object); 10 | }; 11 | -------------------------------------------------------------------------------- /generators/app/templates/server/web/views/helpers/methods.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Returns the method(s) or the default method when no method is defined. 5 | */ 6 | 7 | module.exports = function (requests) { 8 | 9 | const methods = []; 10 | requests.forEach((response) => { 11 | 12 | methods.push(response.method || 'GET'); 13 | }); 14 | return methods.join(','); 15 | }; 16 | -------------------------------------------------------------------------------- /generators/app/templates/server/web/views/helpers/removeCurlyBraces.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Returns the given params without curly braces. 5 | */ 6 | 7 | module.exports = function (params) { 8 | 9 | if (typeof params === 'string') { 10 | params = params.replace(/({|})/g, ''); 11 | } 12 | else { 13 | params = ''; 14 | } 15 | 16 | return params; 17 | }; 18 | -------------------------------------------------------------------------------- /generators/app/templates/server/web/views/index.hbs: -------------------------------------------------------------------------------- 1 | {{> header}} 2 | 3 |

Endpoints / Routes

4 | 5 | 6 | 7 | {{#if endpoints}} 8 |
20 | {{else}} 21 |

No endpoints configured yet …

22 | {{/if}} 23 | 24 | {{> footer}} 25 | -------------------------------------------------------------------------------- /generators/app/templates/server/web/views/layout/layout.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | http-fake-backend{{#if title}} - {{title}}{{/if}} 7 | 8 | 9 | 10 | {{{content}}} 11 | 12 | 13 | -------------------------------------------------------------------------------- /generators/app/templates/server/web/views/partials/footer.hbs: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /generators/app/templates/server/web/views/partials/header.hbs: -------------------------------------------------------------------------------- 1 |
2 | http-fake-backend 3 |
4 | -------------------------------------------------------------------------------- /generators/app/templates/test/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Lab = require('lab'); 4 | const Code = require('code'); 5 | const Config = require('../config'); 6 | 7 | 8 | const lab = exports.lab = Lab.script(); 9 | 10 | 11 | lab.experiment('Config', () => { 12 | 13 | lab.test('it gets config data', (done) => { 14 | 15 | Code.expect(Config.get('/')).to.be.an.object(); 16 | done(); 17 | }); 18 | 19 | 20 | lab.test('it gets config meta data', (done) => { 21 | 22 | Code.expect(Config.meta('/')).to.match(/general project wide config/i); 23 | done(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /generators/app/templates/test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Lab = require('lab'); 4 | const Code = require('code'); 5 | const Composer = require('../index'); 6 | 7 | 8 | const lab = exports.lab = Lab.script(); 9 | 10 | 11 | lab.experiment('App', () => { 12 | 13 | lab.test('it composes a server', (done) => { 14 | 15 | Composer((err, composedServer) => { 16 | 17 | Code.expect(composedServer).to.be.an.object(); 18 | done(err); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /generators/app/templates/test/manifest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Lab = require('lab'); 4 | const Code = require('code'); 5 | const Manifest = require('../manifest'); 6 | 7 | 8 | const lab = exports.lab = Lab.script(); 9 | 10 | 11 | lab.experiment('Manifest', () => { 12 | 13 | lab.test('it gets manifest data', (done) => { 14 | 15 | Code.expect(Manifest.get('/')).to.be.an.object(); 16 | done(); 17 | }); 18 | 19 | 20 | lab.test('it gets manifest meta data', (done) => { 21 | 22 | Code.expect(Manifest.meta('/')).to.match(/hapi server config used by glue to compose the server/i); 23 | done(); 24 | }); 25 | 26 | lab.test('it gets the correct custom response header', (done) => { 27 | 28 | Code.expect(Manifest.get('/').server.connections.routes.cors.additionalExposedHeaders).to.equal(['X-Powered-By']); 29 | done(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /generators/app/templates/test/server/api/customResponseHeader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Lab = require('lab'); 4 | const Code = require('code'); 5 | const Config = require('../../../config'); 6 | const Hapi = require('hapi'); 7 | const SetupEndpoint = require('../../../server/api/setup/'); 8 | const GetCustomResponseHeader = require('../../../server/api/setup/lib/getCustomResponseHeader'); 9 | 10 | const apiUrlPrefix = Config.get('/apiUrlPrefix'); 11 | 12 | const Endpoint = SetupEndpoint({ 13 | name: 'customResponseHeader', 14 | urls: [ 15 | { 16 | params: '/regularResponse', 17 | requests: [ 18 | { response: '/test/server/api/fixtures/response.json' } 19 | ] 20 | }, 21 | { 22 | params: '/fileResponse', 23 | requests: [{ 24 | response: '/test/server/api/fixtures/example.pdf', 25 | sendFile: true, 26 | mimeType: 'application/pdf' 27 | }] 28 | }, 29 | { 30 | params: '/boomError', 31 | requests: [{ statusCode: 402 }] 32 | } 33 | ] 34 | }); 35 | 36 | const lab = exports.lab = Lab.script(); 37 | let request; 38 | let server; 39 | 40 | lab.beforeEach((done) => { 41 | 42 | const plugins = [Endpoint]; 43 | server = new Hapi.Server(); 44 | server.connection({ port: Config.get('/port/web') }); 45 | server.register(plugins, (err) => { 46 | 47 | if (err) { 48 | return done(err); 49 | } 50 | 51 | done(); 52 | }); 53 | }); 54 | 55 | 56 | lab.experiment('Custom response header', () => { 57 | 58 | lab.beforeEach((done) => { 59 | 60 | 61 | done(); 62 | }); 63 | 64 | lab.test('should be read from .env file', (done) => { 65 | 66 | const env = Object.assign({}, process.env); 67 | 68 | env.CUSTOM_HEADER_NAME = 'Authorization'; 69 | env.CUSTOM_HEADER_VALUE = 'Bearer eyJhbGciOiJIUzUxMiJ9'; 70 | 71 | Code.expect(GetCustomResponseHeader(env)).to.equal({ 72 | name: 'Authorization', 73 | value: 'Bearer eyJhbGciOiJIUzUxMiJ9' 74 | }); 75 | 76 | done(); 77 | }); 78 | 79 | lab.test('should have a fallback if not defined in .env file', (done) => { 80 | 81 | 82 | Code.expect(GetCustomResponseHeader(process.env)).to.equal({ 83 | name: 'X-Powered-By', 84 | value: 'https://hapijs.com' 85 | }); 86 | 87 | done(); 88 | }); 89 | 90 | lab.test('regular responses should have the defined response header', (done) => { 91 | 92 | request = { 93 | method: 'GET', 94 | url: apiUrlPrefix + '/customResponseHeader/regularResponse' 95 | }; 96 | 97 | server.inject(request, (response) => { 98 | 99 | Code.expect(response.headers['x-powered-by']).to.equal('https://hapijs.com'); 100 | 101 | done(); 102 | }); 103 | }); 104 | 105 | lab.test('file responses should have the defined response header', (done) => { 106 | 107 | request = { 108 | method: 'GET', 109 | url: apiUrlPrefix + '/customResponseHeader/fileResponse' 110 | }; 111 | 112 | server.inject(request, (response) => { 113 | 114 | Code.expect(response.headers['x-powered-by']).to.equal('https://hapijs.com'); 115 | 116 | done(); 117 | }); 118 | }); 119 | 120 | lab.test('boom errors should have the defined response header', (done) => { 121 | 122 | request = { 123 | method: 'GET', 124 | url: apiUrlPrefix + '/customResponseHeader/boomError' 125 | }; 126 | 127 | server.inject(request, (response) => { 128 | 129 | Code.expect(response.headers['x-powered-by']).to.equal('https://hapijs.com'); 130 | 131 | done(); 132 | }); 133 | }); 134 | 135 | lab.test('unallowed methods of regular responses should have the defined response header', (done) => { 136 | 137 | request = { 138 | method: 'POST', 139 | url: apiUrlPrefix + '/customResponseHeader/regularResponse' 140 | }; 141 | 142 | server.inject(request, (response) => { 143 | 144 | Code.expect(response.headers['x-powered-by']).to.equal('https://hapijs.com'); 145 | 146 | done(); 147 | }); 148 | }); 149 | 150 | lab.test('unallowed methods of boom errors should have the defined response header', (done) => { 151 | 152 | request = { 153 | method: 'POST', 154 | url: apiUrlPrefix + '/customResponseHeader/boomError' 155 | }; 156 | 157 | server.inject(request, (response) => { 158 | 159 | Code.expect(response.headers['x-powered-by']).to.equal('https://hapijs.com'); 160 | 161 | done(); 162 | }); 163 | }); 164 | 165 | }); 166 | -------------------------------------------------------------------------------- /generators/app/templates/test/server/api/endpoint.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Lab = require('lab'); 4 | const Code = require('code'); 5 | const Config = require('../../../config'); 6 | const Hapi = require('hapi'); 7 | const SetupEndpoint = require('../../../server/api/setup/'); 8 | 9 | const apiUrlPrefix = Config.get('/apiUrlPrefix'); 10 | 11 | const Endpoint = SetupEndpoint({ 12 | name: 'endpoint', 13 | urls: [ 14 | { 15 | requests: [ 16 | { response: '/test/server/api/fixtures/response.json' } 17 | ] 18 | }, 19 | { 20 | params: '/object', 21 | requests: [{ 22 | response: { 23 | javascript: 'object' 24 | } 25 | }] 26 | }, 27 | { 28 | params: '/read', 29 | requests: [{ 30 | method: 'GET', 31 | response: '/test/server/api/fixtures/response.json' 32 | }] 33 | }, 34 | { 35 | params: '/update/{id}', 36 | requests: [{ 37 | method: 'PUT', 38 | response: '/test/server/api/fixtures/response.json' 39 | }, { 40 | method: 'PATCH', 41 | response: { 42 | success: true 43 | } 44 | }] 45 | }, 46 | { 47 | params: '/delete/{id}', 48 | requests: [{ 49 | method: 'DELETE', 50 | response: '/test/server/api/fixtures/response.json' 51 | }] 52 | }, 53 | { 54 | params: '/multipleMethods', 55 | requests: [{ 56 | method: ['PUT', 'PATCH'], 57 | response: { 58 | success: true 59 | } 60 | }] 61 | } 62 | ] 63 | }); 64 | 65 | const lab = exports.lab = Lab.script(); 66 | let request; 67 | let server; 68 | 69 | lab.beforeEach((done) => { 70 | 71 | const plugins = [Endpoint]; 72 | server = new Hapi.Server(); 73 | server.connection({ port: Config.get('/port/web') }); 74 | server.register(plugins, (err) => { 75 | 76 | if (err) { 77 | return done(err); 78 | } 79 | 80 | done(); 81 | }); 82 | }); 83 | 84 | 85 | lab.experiment('Setup endpoints', () => { 86 | 87 | lab.beforeEach((done) => { 88 | 89 | 90 | done(); 91 | }); 92 | 93 | 94 | lab.test('returns 404 for unknown route', (done) => { 95 | 96 | request = { 97 | method: 'POST', 98 | url: apiUrlPrefix + '/baz' 99 | }; 100 | 101 | server.inject(request, (response) => { 102 | 103 | Code.expect(response.statusCode).to.equal(404); 104 | 105 | done(); 106 | }); 107 | }); 108 | 109 | lab.test('returns 405: Method Not Allowed for undefined methods', (done) => { 110 | 111 | request = { 112 | method: 'POST', 113 | url: apiUrlPrefix + '/endpoint/read' 114 | }; 115 | 116 | server.inject(request, (response) => { 117 | 118 | Code.expect(response.statusCode).to.equal(405); 119 | Code.expect(response.result).to.equal({ 120 | statusCode: 405, 121 | error: 'Method Not Allowed', 122 | message: 'Method Not Allowed' 123 | }); 124 | 125 | done(); 126 | }); 127 | }); 128 | 129 | lab.test('params and method are optional', (done) => { 130 | 131 | request = { 132 | method: 'GET', 133 | url: apiUrlPrefix + '/endpoint' 134 | }; 135 | 136 | server.inject(request, (response) => { 137 | 138 | Code.expect(response.statusCode).to.equal(200); 139 | Code.expect(JSON.parse(response.result)).to.equal({ response: '🐷' }); 140 | 141 | done(); 142 | }); 143 | }); 144 | 145 | lab.test('returns correct json from JavaScript object', (done) => { 146 | 147 | request = { 148 | method: 'GET', 149 | url: apiUrlPrefix + '/endpoint/object' 150 | }; 151 | 152 | server.inject(request, (response) => { 153 | 154 | Code.expect(response.statusCode).to.equal(200); 155 | Code.expect(response.result).to.equal({ javascript: 'object' }); 156 | 157 | done(); 158 | }); 159 | }); 160 | 161 | lab.test('returns correct json from JSON template', (done) => { 162 | 163 | request = { 164 | method: 'GET', 165 | url: apiUrlPrefix + '/endpoint/read' 166 | }; 167 | 168 | server.inject(request, (response) => { 169 | 170 | Code.expect(response.statusCode).to.equal(200); 171 | Code.expect(JSON.parse(response.result)).to.equal({ response: '🐷' }); 172 | 173 | done(); 174 | }); 175 | }); 176 | 177 | lab.test('PUT returns correct json', (done) => { 178 | 179 | request = { 180 | method: 'PUT', 181 | url: apiUrlPrefix + '/endpoint/update/foo' 182 | }; 183 | 184 | server.inject(request, (response) => { 185 | 186 | Code.expect(response.statusCode).to.equal(200); 187 | Code.expect(JSON.parse(response.result)).to.equal({ response: '🐷' }); 188 | 189 | done(); 190 | }); 191 | }); 192 | 193 | lab.test('PATCH on same route returns different json', (done) => { 194 | 195 | request = { 196 | method: 'PATCH', 197 | url: apiUrlPrefix + '/endpoint/update/foo' 198 | }; 199 | 200 | server.inject(request, (response) => { 201 | 202 | Code.expect(response.statusCode).to.equal(200); 203 | Code.expect(response.result).to.equal({ success: true }); 204 | 205 | done(); 206 | }); 207 | }); 208 | 209 | lab.test('DELETE returns correct json', (done) => { 210 | 211 | request = { 212 | method: 'DELETE', 213 | url: apiUrlPrefix + '/endpoint/delete/foo' 214 | }; 215 | 216 | server.inject(request, (response) => { 217 | 218 | Code.expect(response.statusCode).to.equal(200); 219 | Code.expect(JSON.parse(response.result)).to.equal({ response: '🐷' }); 220 | 221 | done(); 222 | }); 223 | }); 224 | 225 | lab.test('correct json for multiple Methods', (done) => { 226 | 227 | const putRequest = { 228 | method: 'PUT', 229 | url: apiUrlPrefix + '/endpoint/multipleMethods' 230 | }; 231 | 232 | const patchRequest = { 233 | method: 'PATCH', 234 | url: apiUrlPrefix + '/endpoint/multipleMethods' 235 | }; 236 | 237 | const postRequest = { 238 | method: 'POST', 239 | url: apiUrlPrefix + '/endpoint/multipleMethods' 240 | }; 241 | 242 | server.inject(putRequest, (response) => { 243 | 244 | Code.expect(response.statusCode).to.equal(200); 245 | Code.expect(response.result).to.equal({ success: true }); 246 | }); 247 | 248 | server.inject(patchRequest, (response) => { 249 | 250 | Code.expect(response.statusCode).to.equal(200); 251 | Code.expect(response.result).to.equal({ success: true }); 252 | }); 253 | 254 | server.inject(postRequest, (response) => { 255 | 256 | Code.expect(response.statusCode).to.equal(405); 257 | Code.expect(response.result).to.equal({ 258 | statusCode: 405, 259 | error: 'Method Not Allowed', 260 | message: 'Method Not Allowed' 261 | }); 262 | 263 | done(); 264 | }); 265 | 266 | 267 | }); 268 | 269 | }); 270 | -------------------------------------------------------------------------------- /generators/app/templates/test/server/api/fakeStatusCode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Lab = require('lab'); 4 | const Code = require('code'); 5 | const Config = require('../../../config'); 6 | const Hapi = require('hapi'); 7 | const SetupEndpoint = require('../../../server/api/setup/'); 8 | 9 | const apiUrlPrefix = Config.get('/apiUrlPrefix'); 10 | 11 | const EndpointLevel = SetupEndpoint({ 12 | name: 'endpoint', 13 | urls: [{ 14 | requests: [ 15 | { response: '/test/server/api/fixtures/response.json' } 16 | ] 17 | }], 18 | statusCode: 401 19 | }); 20 | 21 | const RequestLevel = SetupEndpoint({ 22 | name: 'request', 23 | urls: [ 24 | { 25 | params: '/boomError', 26 | requests: [{ statusCode: 402 }] 27 | }, 28 | { 29 | params: '/customError', 30 | requests: [ 31 | { 32 | response: { error: true }, 33 | statusCode: 406 34 | } 35 | ] 36 | } 37 | ] 38 | }); 39 | 40 | const RequestLevelBeatsEndpointLevel = SetupEndpoint({ 41 | name: 'requestBeatsEndpoint', 42 | urls: [ 43 | { 44 | params: '/boomError', 45 | requests: [{ statusCode: 402 }] 46 | }, 47 | { 48 | params: '/customError', 49 | requests: [ 50 | { 51 | response: { error: true }, 52 | statusCode: 406 53 | } 54 | ] 55 | } 56 | ], 57 | statusCode: 401 58 | }); 59 | 60 | const lab = exports.lab = Lab.script(); 61 | let request; 62 | let server; 63 | 64 | lab.beforeEach((done) => { 65 | 66 | const plugins = [ 67 | EndpointLevel, 68 | RequestLevel, 69 | RequestLevelBeatsEndpointLevel 70 | ]; 71 | server = new Hapi.Server(); 72 | server.connection({ port: Config.get('/port/web') }); 73 | server.register(plugins, (err) => { 74 | 75 | if (err) { 76 | return done(err); 77 | } 78 | 79 | done(); 80 | }); 81 | }); 82 | 83 | 84 | lab.experiment('Fake status codes', () => { 85 | 86 | lab.experiment('on endpoint level', () => { 87 | 88 | lab.beforeEach((done) => { 89 | 90 | done(); 91 | }); 92 | 93 | lab.test('returns correct error provided by boom', (done) => { 94 | 95 | request = { 96 | method: 'GET', 97 | url: apiUrlPrefix + '/endpoint' 98 | }; 99 | 100 | server.inject(request, (response) => { 101 | 102 | Code.expect(response.statusCode).to.equal(401); 103 | 104 | done(); 105 | }); 106 | }); 107 | 108 | lab.test('undefined method returns correct error provided by boom', (done) => { 109 | 110 | request = { 111 | method: 'PUT', 112 | url: apiUrlPrefix + '/endpoint' 113 | }; 114 | 115 | server.inject(request, (response) => { 116 | 117 | Code.expect(response.statusCode).to.equal(401); 118 | 119 | done(); 120 | }); 121 | }); 122 | }); 123 | 124 | lab.experiment('on request level', () => { 125 | 126 | lab.test('returns correct error provided by boom', (done) => { 127 | 128 | request = { 129 | method: 'GET', 130 | url: apiUrlPrefix + '/request/boomError' 131 | }; 132 | 133 | server.inject(request, (response) => { 134 | 135 | Code.expect(response.statusCode).to.equal(402); 136 | 137 | done(); 138 | }); 139 | }); 140 | 141 | lab.test('returns correct faked status code with regular response', (done) => { 142 | 143 | request = { 144 | method: 'GET', 145 | url: apiUrlPrefix + '/request/customError' 146 | }; 147 | 148 | server.inject(request, (response) => { 149 | 150 | Code.expect(response.statusCode).to.equal(406); 151 | Code.expect(response.result).to.equal({ error: true }); 152 | 153 | done(); 154 | }); 155 | }); 156 | }); 157 | 158 | lab.experiment('request level beats endpoint level', () => { 159 | 160 | lab.test('returns correct error provided by boom', (done) => { 161 | 162 | request = { 163 | method: 'GET', 164 | url: apiUrlPrefix + '/requestBeatsEndpoint/boomError' 165 | }; 166 | 167 | server.inject(request, (response) => { 168 | 169 | Code.expect(response.statusCode).to.equal(402); 170 | 171 | done(); 172 | }); 173 | }); 174 | 175 | lab.test('returns correct faked status code with regular response', (done) => { 176 | 177 | request = { 178 | method: 'GET', 179 | url: apiUrlPrefix + '/requestBeatsEndpoint/customError' 180 | }; 181 | 182 | server.inject(request, (response) => { 183 | 184 | Code.expect(response.statusCode).to.equal(406); 185 | Code.expect(response.result).to.equal({ error: true }); 186 | 187 | done(); 188 | }); 189 | }); 190 | }); 191 | }); 192 | -------------------------------------------------------------------------------- /generators/app/templates/test/server/api/fileTypes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Lab = require('lab'); 4 | const Code = require('code'); 5 | const Config = require('../../../config'); 6 | const Hapi = require('hapi'); 7 | const Fs = require('fs'); 8 | const Path = require('path'); 9 | const SetupEndpoint = require('../../../server/api/setup/'); 10 | 11 | const apiUrlPrefix = Config.get('/apiUrlPrefix'); 12 | 13 | const Endpoint = SetupEndpoint({ 14 | name: 'fileTypes', 15 | urls: [{ 16 | params: '/json', 17 | requests: [{ 18 | response: '/test/server/api/fixtures/response.json' 19 | }] 20 | }, { 21 | params: '/json/download', 22 | requests: [{ 23 | response: '/test/server/api/fixtures/response.json', 24 | sendFile: true 25 | }] 26 | }, { 27 | params: '/text', 28 | requests: [{ 29 | response: '/test/server/api/fixtures/response.txt', 30 | mimeType: 'text/plain' 31 | }] 32 | }, { 33 | params: '/html', 34 | requests: [{ 35 | response: '/test/server/api/fixtures/response.html', 36 | statusCode: 201, 37 | mimeType: 'text/html' 38 | }] 39 | }, { 40 | params: '/pdf', 41 | requests: [{ 42 | response: '/response-files/example.pdf', 43 | sendFile: true 44 | }] 45 | }, { 46 | params: '/pdf/tweaked', 47 | requests: [{ 48 | response: '/response-files/example.pdf', 49 | sendFile: true, 50 | statusCode: 201, 51 | mimeType: 'application/pdf' 52 | }] 53 | }] 54 | }); 55 | 56 | const lab = exports.lab = Lab.script(); 57 | let request; 58 | let server; 59 | 60 | lab.beforeEach((done) => { 61 | 62 | const plugins = [Endpoint]; 63 | server = new Hapi.Server(); 64 | server.connection({ port: Config.get('/port/web') }); 65 | server.register(plugins, (err) => { 66 | 67 | if (err) { 68 | return done(err); 69 | } 70 | 71 | done(); 72 | }); 73 | }); 74 | 75 | 76 | lab.experiment('Different file types', () => { 77 | 78 | lab.beforeEach((done) => { 79 | 80 | 81 | done(); 82 | }); 83 | 84 | 85 | lab.experiment('send file contents ', () => { 86 | 87 | lab.test('content-type header defaults to `application/json`', (done) => { 88 | 89 | request = { 90 | method: 'GET', 91 | url: apiUrlPrefix + '/fileTypes/json' 92 | }; 93 | 94 | server.inject(request, (response) => { 95 | 96 | Code.expect(response.headers['content-type']).to.equal('application/json; charset=utf-8'); 97 | Code.expect(JSON.parse(response.result)).to.equal({ response: '🐷' }); 98 | 99 | done(); 100 | }); 101 | }); 102 | 103 | lab.test('return text files with the defined content-type header`', (done) => { 104 | 105 | request = { 106 | method: 'GET', 107 | url: apiUrlPrefix + '/fileTypes/text' 108 | }; 109 | 110 | server.inject(request, (response) => { 111 | 112 | Code.expect(response.headers['content-type']).to.equal('text/plain; charset=utf-8'); 113 | Code.expect(response.result).to.equal('This is just a plain old text file ✅\n'); 114 | 115 | done(); 116 | }); 117 | }); 118 | 119 | lab.test('return with the defined content-type header and custom status code`', (done) => { 120 | 121 | request = { 122 | method: 'GET', 123 | url: apiUrlPrefix + '/fileTypes/html' 124 | }; 125 | 126 | server.inject(request, (response) => { 127 | 128 | Code.expect(response.headers['content-type']).to.equal('text/html; charset=utf-8'); 129 | Code.expect(response.result).to.equal('GitHub 💖\n'); 130 | Code.expect(response.statusCode).to.equal(201); 131 | 132 | done(); 133 | }); 134 | }); 135 | 136 | }); 137 | 138 | 139 | 140 | lab.experiment('send files ', () => { 141 | 142 | lab.test('ascii files have correctly encoded content', (done) => { 143 | 144 | request = { 145 | method: 'GET', 146 | url: apiUrlPrefix + '/fileTypes/json/download' 147 | }; 148 | 149 | server.inject(request, (response) => { 150 | 151 | Code.expect(JSON.parse(response.result)).to.equal({ response: '🐷' }); 152 | 153 | done(); 154 | }); 155 | }); 156 | 157 | lab.test('binary files have correctly encoded content', (done) => { 158 | 159 | request = { 160 | method: 'GET', 161 | url: apiUrlPrefix + '/fileTypes/pdf' 162 | }; 163 | 164 | const fixtureContent = Fs.readFileSync(Path.normalize(Path.join(__dirname, '/fixtures/example.pdf'))).toString(); 165 | 166 | server.inject(request, (response) => { 167 | 168 | Code.expect(response.result).to.equal(fixtureContent); 169 | 170 | done(); 171 | }); 172 | }); 173 | 174 | lab.test('send files with the default content-type and the correct name`', (done) => { 175 | 176 | request = { 177 | method: 'GET', 178 | url: apiUrlPrefix + '/fileTypes/pdf' 179 | }; 180 | 181 | server.inject(request, (response) => { 182 | 183 | Code.expect(response.headers['content-type']).to.equal('application/octet-stream'); 184 | Code.expect(response.headers['content-disposition']).to.equal('attachment; filename=example.pdf'); 185 | Code.expect(response.statusCode).to.equal(200); 186 | 187 | done(); 188 | }); 189 | }); 190 | 191 | lab.test('send files with a defined content-type and a custom status code`', (done) => { 192 | 193 | request = { 194 | method: 'GET', 195 | url: apiUrlPrefix + '/fileTypes/pdf/tweaked' 196 | }; 197 | 198 | server.inject(request, (response) => { 199 | 200 | Code.expect(response.headers['content-type']).to.equal('application/pdf'); 201 | Code.expect(response.headers['content-disposition']).to.equal('attachment; filename=example.pdf'); 202 | Code.expect(response.statusCode).to.equal(201); 203 | 204 | done(); 205 | }); 206 | }); 207 | 208 | }); 209 | 210 | 211 | }); 212 | -------------------------------------------------------------------------------- /generators/app/templates/test/server/api/fixtures/example.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micromata/generator-http-fake-backend/daa7b3eb0a209c4ec132122a336d42060f328737/generators/app/templates/test/server/api/fixtures/example.pdf -------------------------------------------------------------------------------- /generators/app/templates/test/server/api/fixtures/response.html: -------------------------------------------------------------------------------- 1 | GitHub 💖 2 | -------------------------------------------------------------------------------- /generators/app/templates/test/server/api/fixtures/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": "🐷" 3 | } 4 | -------------------------------------------------------------------------------- /generators/app/templates/test/server/api/fixtures/response.txt: -------------------------------------------------------------------------------- 1 | This is just a plain old text file ✅ 2 | -------------------------------------------------------------------------------- /generators/app/templates/test/server/web/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Lab = require('lab'); 4 | const Code = require('code'); 5 | const Config = require('../../../config'); 6 | const Hapi = require('hapi'); 7 | const Vision = require('vision'); 8 | const Inert = require('inert'); 9 | const HomePlugin = require('../../../server/web/index'); 10 | 11 | 12 | const lab = exports.lab = Lab.script(); 13 | let request; 14 | let server; 15 | 16 | 17 | lab.beforeEach((done) => { 18 | 19 | const plugins = [Vision, Inert, HomePlugin]; 20 | server = new Hapi.Server(); 21 | server.connection({ port: Config.get('/port/web') }); 22 | server.register(plugins, (err) => { 23 | 24 | if (err) { 25 | return done(err); 26 | } 27 | 28 | server.views({ 29 | engines: { hbs: require('handlebars') }, 30 | layout: true, 31 | path: './server/web/views', 32 | partialsPath: './server/web/views/partials', 33 | layoutPath: './server/web/views/layout', 34 | helpersPath: './server/web/views/helpers', 35 | isCached: false 36 | }); 37 | 38 | done(); 39 | }); 40 | }); 41 | 42 | 43 | lab.experiment('Home Page View', () => { 44 | 45 | lab.beforeEach((done) => { 46 | 47 | request = { 48 | method: 'GET', 49 | url: '/' 50 | }; 51 | 52 | done(); 53 | }); 54 | 55 | 56 | lab.test('home page renders properly', (done) => { 57 | 58 | server.inject(request, (response) => { 59 | 60 | Code.expect(response.result).to.match(/http-fake-backend - endpoints \/ routes<\/title>/i); 61 | Code.expect(response.statusCode).to.equal(200); 62 | 63 | done(); 64 | }); 65 | }); 66 | 67 | lab.test('endpoints are delivered to the view', (done) => { 68 | 69 | server.inject(request, (response) => { 70 | 71 | const endpointsInView = response.result.match(/<code>(.+)<\/code>/)[1]; 72 | const endpointsInController = JSON.stringify(response.request.preResponses.getEndpoints.source); 73 | 74 | Code.expect(endpointsInController).to.equal(endpointsInView); 75 | 76 | done(); 77 | }); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /generators/endpoint/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Generator = require('yeoman-generator'); 3 | const chalk = require('chalk'); 4 | const yosay = require('yosay'); 5 | const superb = require('superb'); 6 | const titleCase = require('title-case'); 7 | const helper = require('./promptingHelpers'); 8 | 9 | module.exports = class extends Generator { 10 | prompting() { 11 | const done = this.async(); 12 | 13 | // Have Yeoman greet the user. 14 | this.log(yosay(`${titleCase(superb())}, let’s create an endpoint …`)); 15 | 16 | this.endpoint = { 17 | name: null, 18 | urls: [] 19 | }; 20 | 21 | const prompts = [ 22 | { 23 | type: 'input', 24 | name: 'endpointName', 25 | message: 'What should be the name of the endpoint?', 26 | when: () => { 27 | return !this.endpoint.urls.length; 28 | }, 29 | validate: helper.validateEndpoint 30 | }, 31 | { 32 | type: 'input', 33 | name: 'params', 34 | message: 'Please enter URL path params (optional)', 35 | validate: helper.validateParams 36 | }, { 37 | type: 'list', 38 | name: 'method', 39 | message: 'What should be the accepted method for this request?', 40 | choices: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'] 41 | }, { 42 | type: 'list', 43 | name: 'responseType', 44 | message: 'What would you like to return?', 45 | choices: [ 46 | 'The content of a file', 47 | 'A JavaScript object literal as JSON', 48 | 'A file via Content-Disposition: attachment', 49 | 'An error object' 50 | ], 51 | filter: helper.filterResponseType 52 | }, { 53 | type: 'list', 54 | name: 'contentType', 55 | message: 'Which content type should it have?', 56 | choices: [ 57 | 'application/json', 58 | 'text/plain', 59 | 'text/html' 60 | ], 61 | filter: helper.filterContentType, 62 | when(answers) { 63 | return answers.responseType === 'fileContent'; 64 | } 65 | }, { 66 | type: 'input', 67 | name: 'response', 68 | message: 'Please enter the name of the JSON file', 69 | default: 'your-filename.json', 70 | when(answers) { 71 | return answers.contentType === 'json'; 72 | }, 73 | validate: helper.validateJson 74 | }, { 75 | type: 'input', 76 | name: 'response', 77 | message: 'Please enter the name of the text file', 78 | default: 'your-filename.txt', 79 | when(answers) { 80 | return answers.contentType === 'txt'; 81 | }, 82 | validate: helper.validateText 83 | }, { 84 | type: 'input', 85 | name: 'response', 86 | message: 'Please enter the name of the HTML file', 87 | default: 'your-filename.html', 88 | when(answers) { 89 | return answers.contentType === 'html'; 90 | }, 91 | validate: helper.validateHtml 92 | }, { 93 | type: 'input', 94 | name: 'response', 95 | message: 'Please enter a JavaScript object literal or array', 96 | default: '{ status: \'ok\' }', 97 | when(answers) { 98 | return answers.responseType === 'objectLiteral'; 99 | }, 100 | validate: helper.validateJsObject 101 | }, { 102 | type: 'input', 103 | name: 'response', 104 | message: 'Please enter the name of the file', 105 | when(answers) { 106 | return answers.responseType === 'fileAttachment'; 107 | }, 108 | validate: helper.validateFile 109 | }, { 110 | type: 'input', 111 | name: 'statusCode', 112 | message: 'Please enter a valid HTTP error status code', 113 | default: '400', 114 | when(answers) { 115 | return answers.responseType === 'error'; 116 | }, 117 | validate: helper.validateErrorStatusCode 118 | }, { 119 | type: 'input', 120 | name: 'statusCode', 121 | message: 'Please enter a valid HTTP status code', 122 | default: '200', 123 | when(answers) { 124 | return answers.responseType !== 'error'; 125 | }, 126 | validate: helper.validateStatusCode 127 | }, { 128 | type: 'confirm', 129 | name: 'anotherUrl', 130 | message: 'Should this endpoint have additional path params (just hit enter for YES)?', 131 | default: true 132 | } 133 | ]; 134 | 135 | prompting(this); 136 | 137 | function prompting(that) { 138 | that.prompt(prompts).then(props => { 139 | that.props = props; 140 | 141 | if (!that.endpoint.name) { 142 | that.endpoint.name = that.props.endpointName; 143 | } 144 | 145 | that.endpoint.urls.push({ 146 | params: that.props.params, 147 | requests: [{ 148 | method: that.props.method, 149 | responseType: that.props.responseType, 150 | contentType: that.props.contentType, 151 | response: that.props.response, 152 | statusCode: that.props.statusCode 153 | }] 154 | }); 155 | 156 | if (props.anotherUrl) { 157 | prompting(that); 158 | } else { 159 | done(); 160 | } 161 | }); 162 | } 163 | } 164 | 165 | writing() { 166 | this.fs.copyTpl( 167 | this.templatePath('_endpoint.js'), 168 | this.destinationPath('server/api/' + this.endpoint.name + '.js'), { 169 | endpoint: this.endpoint 170 | } 171 | ); 172 | 173 | this.endpoint.urls.forEach(url => { 174 | if (url.requests[0].responseType === 'fileContent') { 175 | this.fs.copy( 176 | this.templatePath(`response.${url.requests[0].contentType}`), 177 | this.destinationPath('response-files/' + url.requests[0].response) 178 | ); 179 | } 180 | }); 181 | } 182 | 183 | end() { 184 | this.log(yosay(` 185 | That’s it. Feel free to fire up the server with ${chalk.green('npm run start:dev')} 186 | `)); 187 | } 188 | }; 189 | -------------------------------------------------------------------------------- /generators/endpoint/promptingHelpers.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const helper = {}; 3 | 4 | helper.filterResponseType = function (value) { 5 | let responseType; 6 | switch (value) { 7 | case 'The content of a file': 8 | responseType = 'fileContent'; 9 | break; 10 | case 'A file via Content-Disposition: attachment': 11 | responseType = 'fileAttachment'; 12 | break; 13 | case 'A JavaScript object literal as JSON': 14 | responseType = 'objectLiteral'; 15 | break; 16 | case 'An error object': 17 | responseType = 'error'; 18 | break; 19 | // No Default 20 | } 21 | return responseType; 22 | }; 23 | 24 | helper.filterContentType = function (value) { 25 | let contentType; 26 | switch (value) { 27 | case 'application/json': 28 | contentType = 'json'; 29 | break; 30 | case 'text/plain': 31 | contentType = 'txt'; 32 | break; 33 | case 'text/html': 34 | contentType = 'html'; 35 | break; 36 | // No Default 37 | } 38 | return contentType; 39 | }; 40 | 41 | helper.validateJsObject = function (value) { 42 | let returnvalue = chalk.red('Your input doesn’t look like an object or array at all.'); 43 | const isArray = value.match(/^\[/) && value.match(/]$/); 44 | const isObject = value.match(/^{/) && value.match(/\}$/); 45 | if (isArray || isObject) { 46 | returnvalue = true; 47 | } 48 | return returnvalue; 49 | }; 50 | 51 | helper.validateJson = function (value) { 52 | let returnvalue = chalk.red('Please enter valid filename (*.json)'); 53 | const fileExt = value.match(/\w\.json$/); 54 | const validChars = value.match(/[^a-zA-Z0-9(),!.~$&'\-_*+;=:@]+/g); 55 | if (fileExt && !validChars) { 56 | returnvalue = true; 57 | } 58 | return returnvalue; 59 | }; 60 | 61 | helper.validateText = function (value) { 62 | let returnvalue = chalk.red('Please enter valid filename (*.txt)'); 63 | const fileExt = value.match(/\w\.txt$/); 64 | const validChars = value.match(/[^a-zA-Z0-9(),!.~$&'\-_*+;=:@]+/g); 65 | if (fileExt && !validChars) { 66 | returnvalue = true; 67 | } 68 | return returnvalue; 69 | }; 70 | 71 | helper.validateHtml = function (value) { 72 | let returnvalue = chalk.red('Please enter valid filename (*.html)'); 73 | const fileExt = value.match(/\w\.html$/); 74 | const validChars = value.match(/[^a-zA-Z0-9(),!.~$&'\-_*+;=:@]+/g); 75 | if (fileExt && !validChars) { 76 | returnvalue = true; 77 | } 78 | return returnvalue; 79 | }; 80 | 81 | helper.validateFile = function (value) { 82 | let returnvalue = chalk.red('Please enter valid filename'); 83 | const fileExt = value.match(/\w\.\w{2,4}$/); 84 | const validChars = value.match(/[^a-zA-Z0-9(),!.~$&'\-_*+;=:@]+/g); 85 | if (fileExt && !validChars) { 86 | returnvalue = true; 87 | } 88 | return returnvalue; 89 | }; 90 | 91 | helper.validateEndpoint = function (value) { 92 | let returnvalue = chalk.red('Please enter a valid name. This will be a part of the url.'); 93 | const validChars = value.match(/[^a-zA-Z0-9(),!.~$&'\-_*+;=:@]+/g); 94 | if (!validChars && value) { 95 | returnvalue = true; 96 | } 97 | return returnvalue; 98 | }; 99 | 100 | helper.validateParams = function (value) { 101 | let returnvalue = chalk.red('Please enter valid path parameters with a leading `/`. See http://hapijs.com/api#path-parameters'); 102 | const validChars = value.match(/[^a-zA-Z0-9()/,!.~$&'\-_*+;=:@{}]+/g); 103 | const leadingSlash = value.match(/^\//); 104 | if ((!validChars && leadingSlash) || value === '') { 105 | returnvalue = true; 106 | } 107 | return returnvalue; 108 | }; 109 | 110 | helper.validateErrorStatusCode = function (value) { 111 | let returnvalue = chalk.red('Please enter valid 4xx or 5xx status code supported by https://github.com/hapijs/boom'); 112 | const validStatusCodes = [ 113 | 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 410, 411, 412, 413, 414, 415, 416, 417, 422, 423, 428, 429, 451, 500, 501, 502, 503, 504 114 | ]; 115 | 116 | if (validStatusCodes.indexOf(Number(value)) !== -1) { 117 | returnvalue = true; 118 | } 119 | return returnvalue; 120 | }; 121 | 122 | helper.validateStatusCode = function (value) { 123 | let returnvalue = chalk.red('Please enter a number which reprents a valid HTTP status code'); 124 | const validStatusCode = value.match(/^[1-5][0-9][0-9]/); 125 | 126 | if (validStatusCode && value.length === 3) { 127 | returnvalue = true; 128 | } 129 | return returnvalue; 130 | }; 131 | 132 | module.exports = helper; 133 | -------------------------------------------------------------------------------- /generators/endpoint/templates/_endpoint.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const SetupEndpoint = require('./setup/'); 4 | 5 | module.exports = SetupEndpoint({ 6 | name: '<%= endpoint.name %>', 7 | urls: [<% endpoint.urls.forEach(function(url, index){ %> 8 | {<% if (url.params){ %> 9 | params: '<%= url.params %>',<% } %> 10 | requests: [{ 11 | method: '<%= url.requests[0].method %>',<% if (url.requests[0].responseType === 'fileContent'){ %> 12 | response: '/response-files/<%= url.requests[0].response %>'<% if (url.requests[0].contentType === 'txt'){ %>, 13 | mimeType: 'text/plain'<% } else if (url.requests[0].contentType === 'html'){ %>, 14 | mimeType: 'text/html'<% } %><% } else if (url.requests[0].responseType === 'objectLiteral'){ %> 15 | response: <%- url.requests[0].response %><% } else if (url.requests[0].responseType === 'fileAttachment'){ %> 16 | response: '/response-files/<%= url.requests[0].response %>', 17 | sendFile: true<% } %><% if (url.requests[0].statusCode !== '200' && url.requests[0].responseType !== 'error'){ %>,<% } %><% if (url.requests[0].statusCode !== '200'){ %> 18 | statusCode: <%- url.requests[0].statusCode %><% } %> 19 | }] 20 | }<% if (index + 1 !== endpoint.urls.length) { %>,<% } %><% }) %> 21 | ] 22 | }); 23 | -------------------------------------------------------------------------------- /generators/endpoint/templates/response.html: -------------------------------------------------------------------------------- 1 | <a href="https://github.com">GitHub</a> 2 | -------------------------------------------------------------------------------- /generators/endpoint/templates/response.json: -------------------------------------------------------------------------------- 1 | { "success": true } 2 | -------------------------------------------------------------------------------- /generators/endpoint/templates/response.txt: -------------------------------------------------------------------------------- 1 | Success 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-http-fake-backend", 3 | "version": "4.1.0", 4 | "description": "Build a fake backend by providing the content of JSON files or JavaScript objects through configurable routes.", 5 | "homepage": "https://github.com/micromata/generator-http-fake-backend", 6 | "author": { 7 | "name": "Michael Kühnel", 8 | "email": "m.kuehnel@micromata.de", 9 | "url": "http://micromata.de" 10 | }, 11 | "engines": { 12 | "node": ">=6.0.0" 13 | }, 14 | "files": [ 15 | "generators" 16 | ], 17 | "main": "generators/index.js", 18 | "scripts": { 19 | "test": "jest", 20 | "test:watch": "jest --watch", 21 | "security": "nsp check", 22 | "prepublishOnly": "npm run security", 23 | "lint": "eslint . --fix", 24 | "pretest": "npm run lint", 25 | "coveralls": "cat ./coverage/lcov.info | coveralls", 26 | "release": "npm run security && standard-version --tag-prefix", 27 | "release:patch": "npm run security && standard-version --tag-prefix --release-as patch", 28 | "release:minor": "npm run security && standard-version --tag-prefix --release-as minor", 29 | "release:major": "npm run security && standard-version --tag-prefix --release-as major" 30 | }, 31 | "keywords": [ 32 | "fake", 33 | "api", 34 | "http", 35 | "backend", 36 | "yeoman-generator", 37 | "micromata", 38 | "server", 39 | "rest", 40 | "restful", 41 | "data", 42 | "mocks", 43 | "mocking", 44 | "mock" 45 | ], 46 | "dependencies": { 47 | "chalk": "2.3.1", 48 | "command-exists": "^1.2.2", 49 | "nsp": "^3.2.1", 50 | "superb": "2.0.0", 51 | "title-case": "^2.1.1", 52 | "yeoman-generator": "^2.0.2", 53 | "yosay": "^2.0.1" 54 | }, 55 | "devDependencies": { 56 | "codeclimate-test-reporter": "^0.5.0", 57 | "coveralls": "^3.0.0", 58 | "eslint": "^4.18.0", 59 | "eslint-config-xo-space": "^0.18.0", 60 | "jest": "^22.3.0", 61 | "jest-cli": "^22.3.0", 62 | "standard-version": "^4.3.0", 63 | "yeoman-assert": "^3.1.0", 64 | "yeoman-test": "1.7.0" 65 | }, 66 | "eslintConfig": { 67 | "extends": "xo-space/esnext", 68 | "env": { 69 | "jest": true 70 | } 71 | }, 72 | "jest": { 73 | "testEnvironment": "node", 74 | "testPathIgnorePatterns": [ 75 | "<rootDir>/generators/app/templates" 76 | ], 77 | "collectCoverage": true, 78 | "coverageDirectory": "coverage", 79 | "collectCoverageFrom": [ 80 | "generators/app/**/*.js", 81 | "!generators/app/templates/**/*.js" 82 | ], 83 | "coverageReporters": [ 84 | "lcov", 85 | "html", 86 | "text", 87 | "text-summary" 88 | ] 89 | }, 90 | "repository": "micromata/generator-http-fake-backend", 91 | "license": "MIT" 92 | } 93 | --------------------------------------------------------------------------------