├── .env.sample ├── .gitignore ├── .rspec ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE.md ├── Makefile ├── README.md ├── Rakefile ├── config.ru ├── config └── database.yml ├── controllers ├── api_support_controller.rb ├── application_controller.rb ├── live_logs_controller.rb ├── mock_server_controller.rb ├── scripts_controller.rb └── upload_controller.rb ├── db ├── datamigration │ ├── migrate_mockdata.rb │ ├── migrate_replace_data.rb │ └── migrate_ruby_scripts.rb ├── migrations │ └── 1_create_mockdata.rb ├── mockserver.db ├── mockserver.db-shm ├── mockserver.db-wal └── mockserver_test.db ├── docker-compose.yml ├── helpers ├── application_api_helper.rb ├── application_helper.rb ├── cloned_data.rb ├── custom_error.rb ├── request_logger.rb ├── script_helper.rb ├── string_extension.rb └── wild_routes.rb ├── logs └── migrations.log ├── models ├── httprequestlog.rb ├── missed_request.rb ├── mockdata.rb ├── replacedata.rb └── rubyscript.rb ├── public ├── bootstrap │ ├── css │ │ ├── bootstrap-theme.css │ │ ├── bootstrap-theme.css.map │ │ ├── bootstrap-theme.min.css │ │ ├── bootstrap-theme.min.css.map │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ └── js │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ └── npm.js ├── css │ └── common.css ├── img │ ├── advanced_options.png │ ├── architecture.png │ ├── batch_clone.png │ ├── captcha.png │ ├── entertainment.png │ ├── home_screen.png │ ├── kids.png │ ├── mock_design.png │ ├── mocking_bird.jpg │ ├── movie.png │ ├── replace_screen.png │ ├── request_logs.png │ ├── search_results.png │ ├── search_screen.png │ └── sport.png ├── js │ ├── common.js │ ├── jquery.caret.js │ ├── jquery.validatejson.js │ └── liveLogs.js └── upload │ └── captcha.jpg ├── spec ├── create_mock_data_spec.rb ├── create_search_edit_spec.rb ├── create_spec.rb ├── helpers.rb ├── search_spec.rb ├── serve_mock_spec.rb └── spec_helper.rb ├── start-mock.sh ├── stop-mock.sh └── views ├── batch_clone.haml ├── create_mock_request.haml ├── create_mock_response.haml ├── create_ruby_script.haml ├── create_update_replace_data.haml ├── create_update_replace_data_ack.haml ├── http_request_logs.haml ├── layout.haml ├── missed_requests.haml ├── mock_deleted_ack.haml ├── mocking_bird.haml ├── release_train.haml ├── request_log_details.haml ├── search_mock.haml ├── search_replace.haml ├── search_results.haml ├── search_scripts.haml ├── set_mock_environment.haml ├── test_environment_ack.haml └── upload_image.haml /.env.sample: -------------------------------------------------------------------------------- 1 | POSTGRES_HOST=postgres 2 | POSTGRES_DB=postgres 3 | POSTGRES_USER=postgres 4 | POSTGRES_PASSWORD=postgres 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | public/upload/* 3 | logs/* 4 | .DS_Store 5 | .irb-history 6 | config 7 | .env 8 | pg_volume/ 9 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | --format documentation 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.7.4-buster 2 | 3 | RUN apt update -y 4 | 5 | RUN apt install -y git vim qt5-default libqt5webkit5 libqtwebkit-dev libqt5webkit5-dev lsof 6 | 7 | RUN gem install bundler 8 | 9 | COPY . /app 10 | 11 | WORKDIR /app 12 | 13 | RUN gem install bundler -v 1.13.7 14 | 15 | RUN bundle install 16 | 17 | CMD rackup 18 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'sinatra' 3 | gem 'sqlite3' 4 | gem 'activerecord' 5 | gem 'activesupport' 6 | gem 'activemodel' 7 | gem 'haml' 8 | gem 'httparty' 9 | gem 'sinatra-contrib' 10 | gem 'cucumber' 11 | gem 'rack' 12 | 13 | group :pg do 14 | gem 'pg' 15 | end 16 | 17 | group :test do 18 | gem 'rspec' 19 | gem 'capybara' 20 | gem 'capybara-webkit' 21 | gem 'database_cleaner' 22 | end 23 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activemodel (7.1.3.2) 5 | activesupport (= 7.1.3.2) 6 | activerecord (7.1.3.2) 7 | activemodel (= 7.1.3.2) 8 | activesupport (= 7.1.3.2) 9 | timeout (>= 0.4.0) 10 | activesupport (7.1.3.2) 11 | base64 12 | bigdecimal 13 | concurrent-ruby (~> 1.0, >= 1.0.2) 14 | connection_pool (>= 2.2.5) 15 | drb 16 | i18n (>= 1.6, < 2) 17 | minitest (>= 5.1) 18 | mutex_m 19 | tzinfo (~> 2.0) 20 | addressable (2.8.6) 21 | public_suffix (>= 2.0.2, < 6.0) 22 | base64 (0.2.0) 23 | bigdecimal (3.1.7) 24 | builder (3.2.4) 25 | capybara (3.40.0) 26 | addressable 27 | matrix 28 | mini_mime (>= 0.1.3) 29 | nokogiri (~> 1.11) 30 | rack (>= 1.6.0) 31 | rack-test (>= 0.6.3) 32 | regexp_parser (>= 1.5, < 3.0) 33 | xpath (~> 3.2) 34 | capybara-webkit (1.15.1) 35 | capybara (>= 2.3, < 4.0) 36 | json 37 | concurrent-ruby (1.2.3) 38 | connection_pool (2.4.1) 39 | cucumber (9.2.0) 40 | builder (~> 3.2) 41 | cucumber-ci-environment (> 9, < 11) 42 | cucumber-core (> 13, < 14) 43 | cucumber-cucumber-expressions (~> 17.0) 44 | cucumber-gherkin (> 24, < 28) 45 | cucumber-html-formatter (> 20.3, < 22) 46 | cucumber-messages (> 19, < 25) 47 | diff-lcs (~> 1.5) 48 | mini_mime (~> 1.1) 49 | multi_test (~> 1.1) 50 | sys-uname (~> 1.2) 51 | cucumber-ci-environment (10.0.1) 52 | cucumber-core (13.0.2) 53 | cucumber-gherkin (>= 27, < 28) 54 | cucumber-messages (>= 20, < 23) 55 | cucumber-tag-expressions (> 5, < 7) 56 | cucumber-cucumber-expressions (17.1.0) 57 | bigdecimal 58 | cucumber-gherkin (27.0.0) 59 | cucumber-messages (>= 19.1.4, < 23) 60 | cucumber-html-formatter (21.3.0) 61 | cucumber-messages (> 19, < 25) 62 | cucumber-messages (22.0.0) 63 | cucumber-tag-expressions (6.1.0) 64 | database_cleaner (2.0.2) 65 | database_cleaner-active_record (>= 2, < 3) 66 | database_cleaner-active_record (2.1.0) 67 | activerecord (>= 5.a) 68 | database_cleaner-core (~> 2.0.0) 69 | database_cleaner-core (2.0.1) 70 | diff-lcs (1.5.1) 71 | drb (2.2.1) 72 | ffi (1.16.3) 73 | haml (6.3.0) 74 | temple (>= 0.8.2) 75 | thor 76 | tilt 77 | httparty (0.21.0) 78 | mini_mime (>= 1.0.0) 79 | multi_xml (>= 0.5.2) 80 | i18n (1.14.4) 81 | concurrent-ruby (~> 1.0) 82 | json (2.7.1) 83 | matrix (0.4.2) 84 | mini_mime (1.1.5) 85 | minitest (5.22.3) 86 | multi_json (1.15.0) 87 | multi_test (1.1.0) 88 | multi_xml (0.6.0) 89 | mustermann (3.0.0) 90 | ruby2_keywords (~> 0.0.1) 91 | mutex_m (0.2.0) 92 | nokogiri (1.16.3-arm64-darwin) 93 | racc (~> 1.4) 94 | pg (1.5.6) 95 | public_suffix (5.0.4) 96 | racc (1.7.3) 97 | rack (3.0.10) 98 | rack-protection (4.0.0) 99 | base64 (>= 0.1.0) 100 | rack (>= 3.0.0, < 4) 101 | rack-session (2.0.0) 102 | rack (>= 3.0.0) 103 | rack-test (2.1.0) 104 | rack (>= 1.3) 105 | regexp_parser (2.9.0) 106 | rspec (3.13.0) 107 | rspec-core (~> 3.13.0) 108 | rspec-expectations (~> 3.13.0) 109 | rspec-mocks (~> 3.13.0) 110 | rspec-core (3.13.0) 111 | rspec-support (~> 3.13.0) 112 | rspec-expectations (3.13.0) 113 | diff-lcs (>= 1.2.0, < 2.0) 114 | rspec-support (~> 3.13.0) 115 | rspec-mocks (3.13.0) 116 | diff-lcs (>= 1.2.0, < 2.0) 117 | rspec-support (~> 3.13.0) 118 | rspec-support (3.13.1) 119 | ruby2_keywords (0.0.5) 120 | sinatra (4.0.0) 121 | mustermann (~> 3.0) 122 | rack (>= 3.0.0, < 4) 123 | rack-protection (= 4.0.0) 124 | rack-session (>= 2.0.0, < 3) 125 | tilt (~> 2.0) 126 | sinatra-contrib (4.0.0) 127 | multi_json (>= 0.0.2) 128 | mustermann (~> 3.0) 129 | rack-protection (= 4.0.0) 130 | sinatra (= 4.0.0) 131 | tilt (~> 2.0) 132 | sqlite3 (1.7.3-arm64-darwin) 133 | sys-uname (1.2.3) 134 | ffi (~> 1.1) 135 | temple (0.10.3) 136 | thor (1.3.1) 137 | tilt (2.3.0) 138 | timeout (0.4.1) 139 | tzinfo (2.0.6) 140 | concurrent-ruby (~> 1.0) 141 | xpath (3.2.0) 142 | nokogiri (~> 1.8) 143 | 144 | PLATFORMS 145 | arm64-darwin-22 146 | 147 | DEPENDENCIES 148 | activemodel 149 | activerecord 150 | activesupport 151 | capybara 152 | capybara-webkit 153 | cucumber 154 | database_cleaner 155 | haml 156 | httparty 157 | pg 158 | rack 159 | rspec 160 | sinatra 161 | sinatra-contrib 162 | sqlite3 163 | 164 | BUNDLED WITH 165 | 2.3.26 166 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2016 MVEMJSUN 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 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: build 2 | 3 | build: 4 | docker build -f Dockerfile -t mock_server . 5 | 6 | shell: 7 | docker run -p 9293:9293 -v $$(pwd):/app -ti mock_server bash 8 | 9 | run: 10 | docker run -p 9293:9293 -v $$(pwd):/app -t mock_server 11 | 12 | start-mock: 13 | docker run -p 9293:9293 -v $$(pwd):/app -t mock_server ./start-mock.sh 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Mocking Bird 2 | > An easy way to setup mock responses by specifying mock URL, HTTP verb, Response headers and Response body. 3 | 4 | ![](https://github.com/mvemjsun/mock_server/blob/master/public/img/architecture.png?raw=true) 5 | 6 | ### Quick Start 7 | 8 | Ensure ruby and `bundler` are installed. RVM is a good way to manage rubies on your machine. 9 | 10 | - git clone `https://github.com/mvemjsun/mock_server.git` 11 | - Run `bundle install --without=test pg` 12 | - There is an starter mock database inside the `/db` folder, you can use it to get started. 13 | - The sample db has one mocked url that serves a test url `/ping`. 14 | - Run `sh ./start-mock.sh` from the project root which starts the server on port 9292. 15 | - Visit `http://localhost:9292/mock/search` and search. To see the available mocks. 16 | - Visit `http://localhost:9292/mock/create` and create your mocks. 17 | - Direct your API requests to the mock server and have them served. 18 | 19 | Note: 20 | - To create a new mock db navigate to `/db` folder and delete the supplied sqlite db (`mockserver.db` file). 21 | - Run `rake db:migrate` from the project root. This will create an emty database file with no mocks. 22 | 23 | ### Summary 24 | 25 | The core idea behind this tool is having the ability to quickly and easily create mock responses for URLs that respond to HTTP verbs. It can help to 26 | test client devices against a mock server both manually and by using automated tests. All this is 27 | achieved by an easy to use user interface that allows a user to specify a URL to mock, set the return HTTP status, headers and last 28 | but not the least the response body. Mocking bird is slightly different from conventional mocking frameworks in that most of its features can be used even by 29 | non-programmers who have got a basic knowledge of HTTP structure (headers, status codes & body); **also mocks need not be programmed into a language specific implementation. 30 | Set up once and use across multiple clients that use differing technologies.** 31 | 32 | The requests to the mock server can also be logged into the mock database if the environment variable `REQUEST_LOGGING` has been defined. 33 | The logs can also be cleared using an api call (see API support section below) 34 | 35 | Images can be served using custom urls defined withing the mock server. Facility to upload the images is also 36 | provided. Mocking can becomes super easy if there are existing API endpoints that return data, existing API responses be cloned via the GET button on the home 37 | page and then modified (currently only GET requests are supported). 38 | 39 | The cloning feature can be used if there is existing data available that can be retrieved via HTTP GET requests, this can be quickly cloned into 40 | the mock database and then modified. 41 | 42 | The Implementation has been experimented and tested on OSX 10.10 and 10.11. User interface has been driven using recent versions of Safari (9.1) and Chrome (49.0). 43 | 44 | The tool has been kept lightweight so that it can be installed and run on a developers/testers machine easily and quickly without any major software 45 | or memory requirements. 46 | 47 | 48 | ### Features 49 | 50 | The tool can be used either as a standalone mock server on an individuals PC or setup as a team mock server. Its upto the team and user(s) to 51 | decide what suits their needs. 52 | 53 | #### Create Mock 54 | 55 | Create a mock by supplying relevant details on the form on the Home page. URL responses can be cloned if they require no \ 56 | client configuration. 57 | 58 | #### Search Mock 59 | Navigate to the search option and supply part of the mock name to search. 60 | 61 | #### Update Mock 62 | Edit an existing mock (search for it first). 63 | 64 | #### Clone in batch 65 | If you have a set of Rest URL's that require no client configuration. Then you can clone the URLs into the mock database using the 66 | batch clone option. 67 | 68 | #### Replace Data 69 | Replace data can be created to look for 'replace strings' either by exact match or by regular expressions. This is a final 70 | point in the request response time-line where the mock response that have been setup can still be modified before the 71 | response is sent back to the client. Replace data can be applied only to the response body. For example there could be 72 | a mock that has been set up to return the personal details of the user as 73 | 74 | ``` 75 | { 76 | "name" : "John", 77 | "middleName" : "Smith", 78 | "dob" : "1975-09-11", 79 | "postCode" : "TF12 6TR" 80 | } 81 | ``` 82 | 83 | We could set up a replace data(s) so that the string `"dob" : "1975-09-11"` is replaced by `"dob" : "1955-01-11"`. 84 | 85 | #### Upload Images 86 | Images can be uploaded in case you want to mock url's that end with image names. 87 | 88 | ### Possible use cases 89 | 90 | #### No existing data available 91 | Visit the /mock/create and create mock responses by entering response details manually 92 | 93 | #### Existing data available 94 | This option could be used when minimal test data is available. We have two ways to mock here. 95 | * Visit the /mock/create page and clone an individual request into the mock database via the GET button (Menu - Home) 96 | * If you have a set of URL's to hand that return data then use them to clone in batch using the /mock/clone/batch (Menu - Clone Many). This 97 | option will clone the data into the database that you can then edit search followed by selecting a result and editing it. 98 | 99 | #### Images 100 | * Images can be served if they are placed in /public/img directory and then the urls point to it like `http://xx.xx.xx.xx/img/captcha.png` 101 | where `xx.xx.xx.xx` is the ip address of the mock server. 102 | 103 | * To serve custom image URLs, first upload the image onto the mock server and then create a mock URL with correct content type (png or jpeg) 104 | . The Image file name at the end of the url must match the uploaded image name (case sensitive). For example if you want to serve the URL 105 | `get/me/a/cat.png` then upload the image with name `cat.png` while creating the mock URL. Note only urls that end with an image file name 106 | can be served. 107 | 108 | ### Wildcard in routes (experimental) 109 | * If a mock url is set up with a wildcard character `*` in it then the mock server will attempt to match against the "wild" route if no exact match is found. For example if a mock URL 110 | is set up as `/say/(.*)/to/(.*)` then this will match `/say/hello/to/tom` or `/say/hola/to/rafael`. 111 | 112 | * Similarly if a mock URL is set up as `/get/me/item/(.*)` will match `/get/me/item/2345`. 113 | 114 | When specifying wildcard in routes please ensure that any characters in the url that have a special meaning to the regex engine are escaped. 115 | For example if the url is `/get/me/a/book?id=98765` then you could have a wildcard route as `/get/me/a/book\?id=(.*)`. 116 | 117 | ### Basic Cookie support 118 | Mocks can be set up to return cookies. The cookie details should be entered ONE cookie in each line. The format is `cookieName cookieValue`. 119 | The cookie name should be followed by a space. If multiple cookies are required then enter each in its own line followed by a line-break. 120 | 121 | ```ruby 122 | userId 987656789 123 | token 7yser345abnjdlo12469sdfqws 124 | ssd yef32lvcdds 125 | ``` 126 | The above will return 3 cookies with names userId, token and ssd with above values. 127 | 128 | ### Scripting Support (Experimental) 129 | A mock url can optionally be set up with scripting support. The scripts have to be written in Ruby. The mock responses 130 | specify the name of the before and after scripts when they are being created/updated. These scripts should have been 131 | created using the scripts option from the menu. The script names should be one or more scripts names ending `.rb` 132 | delimited by a `,` (comma) character. 133 | 134 | The scripts are evaluated with the `before` and `after` Sinatra filters and are evaluated in the context of Sinatra 135 | request and responses. The scripts can for example be used to set up headers that need to be generated at run time or 136 | manipulate the response body before its sent back to the client. 137 | 138 | A word of CAUTION - Scripts are evaluated using ruby `eval` statement without any checks, so use them with caution. 139 | 140 | The mock_response build from the mock database is available in the instance variable `@mock_response`. 141 | Example script that adds a custom header `X-AfterScript-Time` and sets the response body could be set as 142 | 143 | ```ruby 144 | headers({"X-AfterScript-Time" => "#{Time.now}"}) 145 | @mock_response[:mock_data_response] = 'Hi Ya how are you' 146 | body @mock_response[:mock_data_response] 147 | ``` 148 | 149 | This uses the Sinatra's functions `headers` and passes it a header hash. Similarly the `body` function is used to set 150 | an altered body. 151 | 152 | ### API support 153 | * Mockdata in the database can be activated or deactivated using its id. 154 | 155 | ``` 156 | # To activate a mock url with Id = 1 157 | # http://localhost:9292/mock/api/activate/1 158 | # To deactivate a mock url with id = 1 159 | # http://localhost:9292/mock/api/deactivate/1 160 | ``` 161 | Note that activating a url will deactivate any active form of that url in that test environment. 162 | 163 | * Latency of responses can be set using 164 | ``` 165 | http://localhost:9292/latency/1 166 | OR 167 | http://localhost:9292/latency/3 168 | ``` 169 | This sets the global latency to 1 or 3 seconds for ALL mock responses. Please note that due to the blocking nature of the latency implementation 170 | at the moment, all server processing will be blocked while the latency is processed. The default latency is 0. 171 | 172 | To set the latency back to 0 issue the call `http://localhost:9292/latency/0` 173 | 174 | To set latency for individual url's you will have to use the 'Advanced options' and mention the name of a ruby script 175 | with a sleep statement in it. So for example 176 | you could have `sleep1.rb` to `sleep5.rb` with `sleep n` statements in them (where `n` is from 1 to 5) to cause an 177 | artificial delay in the response. 178 | 179 | * Replace data rows in the DB can be activated using the endpoint 180 | 181 | The below will activate & decativate the replace data row with an id of 1. Any other rows that have the same replace string will be deactivated 182 | ``` 183 | http://localhost:9292/mock/api/replace_data/activate/1 184 | http://localhost:9292/mock/api/replace_data/deactivate/1 185 | ``` 186 | 187 | * Reset mock url's served count. The below url will set the served counts to 0 for all the mock urls in the database. This could be ideally be done at the start of a test. 188 | 189 | ``` 190 | http://localhost:9292/mock/api/reset 191 | ``` 192 | 193 | * Retrieve recent data from `httpRequestLog` table 194 | ``` 195 | http://localhost:9292/mock/api/requestlog/recent 196 | ``` 197 | 198 | * Retrieve `httpRequestLog` table data within a time range 199 | ``` 200 | http://localhost:9292/mock/api/requestlog/range?from=2016-09-11 16:31:00&to=2016-09-11 16:32:11[&matching=] 201 | ``` 202 | matching query parameter is optional, could have a value like `matching=/account` 203 | 204 | * Delete all data from the `httpRequestLog` table 205 | ``` 206 | http://localhost:9292/mock/api/reset/requestlog 207 | ``` 208 | 209 | * Update all rows in the Replacedata table 210 | ``` 211 | http://localhost:9292/mock/api/update/replacedata?string=xxx&with=yyy 212 | ``` 213 | 214 | | API | Type |Description | 215 | | --- | --- | --- | 216 | | http://localhost:9292/mock/api/activate/1 | POST | Activate mock with id 1 | 217 | | http://localhost:9292/mock/api/deactivate/1 | POST | Deactivate mock with id 1 | 218 | | http://localhost:9292/latency/1 | POST | Set latency of response to 1 second | 219 | | http://localhost:9292/latency/2 | POST | Set latency of response to 2 seconds | 220 | | http://localhost:9292/mock/api/replace_data/activate/1 | POST | Set replace data mock 1 to active | 221 | | http://localhost:9292/mock/api/replace_data/deactivate/1 | POST | Set replace data mock 1 to Inactive | 222 | | http://localhost:9292/mock/api/reset | POST | Reset served counts for all the URLs to 0 | 223 | | http://localhost:9292/mock/api/requestlog/recent | GET | Return the recent logged requests | 224 | | http://localhost:9292/mock/api/requestlog/range?from=2016-09-11 16:31:00&to=2016-09-11 16:32:11[&matching=] | GET | Get recent log for a time range| 225 | | http://localhost:9292/mock/api/reset/requestlog | POST | Delete the request logs | 226 | | http://localhost:9292/mock/api/update/replacedata?string=xxx&with=yyy | POST | Update the replace data string to be replaced | 227 | 228 | ### Request log console 229 | The `Live Requests` tab on the web interface shows the requests being served by the mock server. 230 | ![](https://github.com/mvemjsun/mock_server/blob/master/public/img/request_logs.png?raw=true) 231 | 232 | ### Tests 233 | 234 | There is some coverage for the main features of the mock server around creating mocks and search. The tests are run using `RSpec` & `Capybara` webkit driver. 235 | To run the tests ensure that you set the environment variable `ENVIRONMENT` and set it to `test`. Run the rake migration to create the test database using 236 | the command `ENVIRONMENT='test' rake db:migrate`. Then start the mock server using `ENVIRONMENT='test' sh ./start-mock.sh` from the project root. 237 | 238 | The tests can then be run using the command `rspec` from the project root. 239 | 240 | ### Data migration 241 | You could potentially experiment using the mock server using sqlite and if there is a need you could migrate data 242 | from sqlite to another RDBMS such as Postgres. To do this you need to have the new DB already installed. 243 | The `database.yml` should contain the setup info for it such as 244 | 245 | ``` 246 | development_pg: 247 | adapter: postgresql 248 | encoding: unicode 249 | database: postgres 250 | pool: 5 251 | username: postgres 252 | password: postgres 253 | host: localhost 254 | ``` 255 | 256 | Which points to a database named `postgres`. Run the rake task `rake db:migrate` with environment variable `ENVIRONMENT` 257 | set to `development_pg` (your name might be different) i.e `ENVIRONMENT=DEVELOPMENT_PG rake db:migrate`. This will create the database tables 258 | needed. Following this we need to migrate the individual table data. Which is 3 main tables; `mockdata`, `replacedata` & `rubyscripts`. We do this 259 | by running the 3 migration scripts one by one from the `db/datamigration` directory. Ensure that the scripts have the correct old and new 260 | environment names from the `database.yml` file. Once the data has been migrated successfully change the `Rakefile` or 261 | the environment variable `ENVIRONMENT` to point to the correct db from the `database.yml` file. 262 | 263 | ### TODO's 264 | * Video mocking 265 | * Support more verbs when cloning (currently limited to GET) 266 | 267 | ### Caveat 268 | * The API URLs have to be unique across hosts as the mock server maintains only the mock url path and NOT the host part of the url. 269 | * The tool and framework has been setup to work against a single client and does not guarantee behaviour when used 270 | concurrently by more than one user. 271 | 272 | ### Web interface 273 | 274 | The tool/framework has an interface that lets you create and maintain mocking data. 275 | 276 | #### Home Screen 277 | ![](https://github.com/mvemjsun/mock_server/blob/master/public/img/home_screen.png?raw=true) 278 | 279 | #### Advanced Options 280 | ![](https://github.com/mvemjsun/mock_server/blob/master/public/img/advanced_options.png?raw=true) 281 | 282 | #### Replace Strings 283 | ![](https://github.com/mvemjsun/mock_server/blob/master/public/img/replace_screen.png?raw=true) 284 | 285 | #### Search 286 | ![](https://github.com/mvemjsun/mock_server/blob/master/public/img/search_screen.png?raw=true) 287 | 288 | #### Search Results 289 | ![](https://github.com/mvemjsun/mock_server/blob/master/public/img/search_results.png?raw=true) 290 | 291 | ## Copyright and License 292 | 293 | Copyright (c) 2016, mvemjsun. 294 | 295 | Mocking bird source code is licensed under the [MIT License](LICENSE.md). 296 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'active_record' 2 | require 'yaml' 3 | require 'logger' 4 | require 'cucumber/rake/task' 5 | require 'sqlite3' 6 | require 'zlib' 7 | 8 | ENV['ENVIRONMENT'] ||= 'development' 9 | 10 | namespace :db do 11 | def create_my_database 12 | p 'Create DB' 13 | end 14 | 15 | task :configure do 16 | p 'Task Reading configuration ...' 17 | @config = YAML.load_file('config/database.yml')[ENV['ENVIRONMENT']] 18 | end 19 | 20 | task :connect_to_db do 21 | p 'Task Establishing connection ...' 22 | p @config.inspect 23 | ActiveRecord::Base.establish_connection @config 24 | ActiveRecord::Base.logger = Logger.new(File.open('logs/migrations.log', 'a')) 25 | end 26 | 27 | task :migrate => [:configure, :connect_to_db] do 28 | p 'Task Migrate ...' 29 | ActiveRecord::Migration.verbose = true 30 | env_migration_version = ENV['VERSION'] || '1' 31 | p "Migration version #{env_migration_version}" 32 | ActiveRecord::MigrationContext.new("./db/migrations").migrate(env_migration_version.to_i) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | require "sinatra/cookies" 3 | require 'active_record' 4 | require 'sqlite3' 5 | require 'tilt/haml' 6 | require 'uri' 7 | require 'httparty' 8 | 9 | require 'logger' 10 | Logger.class_eval { alias :write :'<<' } 11 | $logger = ::Logger.new(::File.new("logs/app.log","a+")) 12 | 13 | $logger.level = Logger::DEBUG 14 | 15 | ENV['ENVIRONMENT'] ||= 'development' 16 | # ENV['ENVIRONMENT'] ||= 'development_pg' 17 | ENV['REQUEST_LOGGING']="1" 18 | ENV['TEST_ENV']='production' 19 | ENV['DEFAULT_CONTENT_TYPE'] = 'application/json;charset=UTF-8' 20 | # Once the delimiter is set then it can only be changed if all mock db mock_data_response_headers are updated to use the new header 21 | ENV['HEADER_DELIMITER'] = ":==:" 22 | ENV['REPLACE'] = "1" 23 | ENV['MAX_UPLOAD_SIZE'] = '500000' 24 | # Duration in seconds of how far to get the logs from the /mock/api/requestlog/recent 25 | ENV['RECENT_LOG_DURATION'] ||= '1800' 26 | # Integer setting the latency of responses, Global setting 27 | ENV['LATENCY'] ||= '0' 28 | ActiveRecord::Base.logger = Logger.new('logs/app.log') 29 | ActiveRecord::Base.logger.level = Logger::DEBUG 30 | 31 | db = YAML.load_file(File.expand_path('./config/database.yml'))[ENV['ENVIRONMENT']] 32 | ActiveRecord::Base.establish_connection db 33 | 34 | Dir.glob('./{helpers,controllers,models}/*.rb').each do |file| 35 | require file 36 | end 37 | 38 | # Needed for any API calls that may require authentication etc 39 | $user_id = ENV['MOCK_USERID'].nil? ? '' : ENV['MOCK_USERID'] 40 | $user_password = ENV['MOCK_PASSWORD'].nil? ? '' : ENV['MOCK_PASSWORD'] 41 | # 42 | # Load any existing routes with wildcards 43 | # 44 | $wild_routes = {} 45 | $wild_routes = WildRoutes.get_wild_routes_if_any 46 | 47 | map('/') { run ApplicationController} 48 | map('/mock') { run MockServerController} 49 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: sqlite3 3 | database: db/mockserver.db 4 | pool: 5 5 | timeout: 5000 6 | 7 | migration: 8 | adapter: sqlite3 9 | database: ../mockserver.db 10 | pool: 5 11 | timeout: 5000 12 | 13 | example_mysql: 14 | adapter: mysql2 15 | encoding: utf8 16 | database: my_db_name 17 | username: root 18 | password: my_password 19 | host: 127.0.0.1 20 | port: 3306 21 | 22 | test: 23 | adapter: sqlite3 24 | database: db/mockserver_test.db 25 | pool: 5 26 | timeout: 5000 27 | 28 | development_pg: 29 | adapter: postgresql 30 | encoding: unicode 31 | database: postgres 32 | pool: 5 33 | username: postgres 34 | password: postgres 35 | host: localhost 36 | 37 | development_docker: 38 | adapter: postgresql 39 | encoding: unicode 40 | database: postgres 41 | pool: 5 42 | username: postgres 43 | password: postgres 44 | host: pg_database 45 | -------------------------------------------------------------------------------- /controllers/api_support_controller.rb: -------------------------------------------------------------------------------- 1 | require_relative '../controllers/application_controller' 2 | 3 | class MockServerController < ApplicationController 4 | 5 | # 6 | # Gets the mock record details for the client. Does not return mock body to optimize response. 7 | # 8 | get '/api/:id' do 9 | mockData = Mockdata.select(:id, 10 | :mock_name, 11 | :mock_request_url, 12 | :mock_http_verb, 13 | :mock_data_response_headers, 14 | :mock_state, :mock_environment, 15 | :mock_content_type, 16 | :mock_served_times).where(id: params[:id]) 17 | if (params[:id].to_i.is_a? Fixnum) && (mockData.any?) 18 | content_type 'application/json' 19 | status 200 20 | return mockData.first.to_json 21 | else 22 | content_type 'text/plain' 23 | status 404 24 | body 'Not Found' 25 | end 26 | end 27 | 28 | # 29 | # Activate a mock URL using id 30 | # 31 | post '/api/activate/:id' do 32 | response = activate_mock_with_id 33 | status = response[:status] 34 | body = response[:body] 35 | end 36 | 37 | # 38 | # Deactivate a mock URL using id 39 | # 40 | post '/api/deactivate/:id' do 41 | response = deactivate_mock_with_id 42 | status = response[:status] 43 | body = response[:body] 44 | end 45 | 46 | # 47 | # Activate replace data 48 | # 49 | 50 | post '/api/replace_data/activate/:id' do 51 | status = Replacedata.new.activate_replace_mock_data(params['id']) 52 | content_type 'text/plain' 53 | if status 54 | status 200 55 | body = 'Activated successfully' 56 | else 57 | status 404 58 | body = 'Could not activate' 59 | end 60 | end 61 | 62 | # 63 | # Deactivate mock replace data 64 | # 65 | post '/api/replace_data/deactivate/:id' do 66 | status = Replacedata.new.deactivate_replace_mock_data(params['id']) 67 | content_type 'text/plain' 68 | if status 69 | status 200 70 | body = 'De-activated successfully' 71 | else 72 | status 404 73 | body = 'Could not De-activate' 74 | end 75 | end 76 | 77 | post '/api/reset' do 78 | Mockdata.new.reset_served_counts 79 | content_type 'text/plain' 80 | status 200 81 | body = 'Served counts reset.' 82 | end 83 | 84 | post '/api/reset/requestlog' do 85 | Httprequestlog.clear_request_log 86 | content_type 'text/plain' 87 | status 200 88 | body = 'Request log has been cleared.' 89 | end 90 | 91 | # 92 | # Get request logs for a given time range 93 | # the rage is specified in the `from` and `to` query parameters 94 | # @example /mock/api/requestlog/range?from=2016-09-26%2016:31:00&to=2016-09-26%2016:32:11 95 | # 96 | get '/api/requestlog/range' do 97 | if (params.has_key?('from') && params.has_key?('to')) 98 | if (valid_datetime_string?(params['from']) && valid_datetime_string?(params['to'])) 99 | if params.has_key? 'matching' 100 | matching_string = params['matching'] 101 | else 102 | matching_string = nil 103 | end 104 | response = Httprequestlog.get_request_log(start_datetime=params['from'], 105 | end_datetime=params['to'], 106 | matching=matching_string) 107 | content_type 'text/json' 108 | if JSON.parse(response).first.has_key? 'message' 109 | status 404 110 | else 111 | status 200 112 | end 113 | body = response 114 | else 115 | content_type 'text/json' 116 | status 400 117 | body = '{"message" : "Invalid dates supplied."}' 118 | end 119 | else 120 | content_type 'text/json' 121 | status 400 122 | body = '{"message" : "Both from and to date need to be supplied as query parameters."}' 123 | end 124 | 125 | end 126 | 127 | # 128 | # Get recent request logs 129 | # 130 | get '/api/requestlog/recent' do 131 | response = Httprequestlog.get_request_log 132 | content_type 'text/json' 133 | status 200 134 | body = response 135 | end 136 | 137 | # 138 | # Update the replace data replacing string 139 | # 140 | post '/api/update/replacedata' do 141 | if (params.has_key?('string') && params.has_key?('with')) 142 | Replacedata.update_replace_string(params['string'],params['with']) 143 | content_type 'text/plain' 144 | status 200 145 | body = 'Replace data updated' 146 | else 147 | content_type 'text/plain' 148 | status 402 149 | body = 'Please supply query parameters ?string=xxx&with=yyy' 150 | end 151 | end 152 | 153 | 154 | end -------------------------------------------------------------------------------- /controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | require "sinatra/base" 2 | require "sinatra/cookies" 3 | require 'haml' 4 | 5 | class ApplicationController < Sinatra::Base 6 | helpers ApplicationHelper 7 | helpers Sinatra::Cookies 8 | 9 | set :views, File.expand_path('../../views', __FILE__) 10 | set :public_folder, File.expand_path('../../public', __FILE__) 11 | set :bind, '0.0.0.0' 12 | Haml::Template.options[:escape_html] = false 13 | 14 | configure :production, :development do 15 | # enable :logging 16 | enable :session 17 | set :session_secret, 'av3rys3cr3tk3y' 18 | use Rack::CommonLogger, $logger 19 | end 20 | 21 | 22 | before do 23 | process_before_script 24 | end 25 | 26 | after do 27 | process_after_script 28 | ActiveRecord::Base.clear_active_connections! 29 | end 30 | 31 | get '/environment' do 32 | @title = 'Set mock environment' 33 | haml :set_mock_environment 34 | end 35 | 36 | post '/environment' do 37 | @title = 'Mock set successfully' 38 | env = %w{integration quality production} 39 | supplied_env = params[:mock_environment].downcase 40 | if env.include? supplied_env 41 | ENV['TEST_ENV'] = supplied_env 42 | haml_info = {success: true, message: "Test environment set to #{supplied_env}"} 43 | haml :test_environment_ack, :locals => haml_info 44 | else 45 | haml_info = {success: false, message: "Test environment cannot be #{supplied_env} only #{env} supported."} 46 | haml :test_environment_ack, :locals => haml_info 47 | end 48 | end 49 | 50 | post '/latency/:seconds' do 51 | if params['seconds'].is_i? 52 | ENV['LATENCY'] = params['seconds'] 53 | status 200 54 | body "Latency set to #{params['seconds']} second(s)." 55 | else 56 | status 400 57 | body 'Latency could not be set.' 58 | end 59 | end 60 | 61 | get '/bird' do 62 | @title = 'Mocking bird' 63 | haml :mocking_bird 64 | end 65 | 66 | get "/*" do 67 | @title = 'Mock GET' 68 | # Process the URL 69 | process_http_verb 70 | end 71 | 72 | post "/*" do 73 | @title = 'Mock POST' 74 | # Process the URL 75 | process_http_verb 76 | end 77 | 78 | put "/*" do 79 | @title = 'Mock PUT' 80 | # Process the URL 81 | process_http_verb 82 | end 83 | 84 | delete "/*" do 85 | @title = 'Mock DELETE' 86 | # Process the URL 87 | process_http_verb 88 | end 89 | 90 | options "/*" do 91 | @title = 'Mock OPTIONS' 92 | # Process the URL 93 | process_http_verb 94 | end 95 | end 96 | 97 | -------------------------------------------------------------------------------- /controllers/live_logs_controller.rb: -------------------------------------------------------------------------------- 1 | require_relative '../controllers/application_controller' 2 | 3 | class MockServerController < ApplicationController 4 | 5 | get '/livelogs' do 6 | @title = 'Live logs' 7 | haml :http_request_logs 8 | end 9 | 10 | get '/livelogs/detail/:id' do 11 | @title = 'HTTP Request details' 12 | request_details = Httprequestlog.get_log_details(params['id']) 13 | haml :request_log_details, locals: {request_details: request_details} 14 | end 15 | 16 | end -------------------------------------------------------------------------------- /controllers/mock_server_controller.rb: -------------------------------------------------------------------------------- 1 | require_relative '../controllers/application_controller' 2 | 3 | class MockServerController < ApplicationController 4 | 5 | helpers Sinatra::Cookies 6 | # 7 | # Display the mock data search form for the user to enter the part of the mock name 8 | # 9 | get '/search' do 10 | @title = 'Search' 11 | haml :search_mock 12 | end 13 | 14 | # 15 | # Display the missed requests log 16 | # 17 | get '/search/misses' do 18 | @title = 'Missed requests' 19 | missed_data = MissedRequest.order('created_at DESC').all 20 | haml :missed_requests, locals: {missed_data: missed_data} 21 | end 22 | 23 | # 24 | # Display the search results for a mock name (part) 25 | # 26 | get '/search/result' do 27 | @title = 'Search Result(s)' 28 | search_data = search_mock_data({mock_name: params[:search_mock_name].upcase, 29 | mock_request_url: params[:search_mock_request_url]}) 30 | haml :search_results, locals: {search_data: search_data} 31 | end 32 | 33 | # 34 | # Display the data for an existing mock. The data is retrieved using the mock internal database id. This is transferred 35 | # from the search results page 36 | # 37 | get "/update/:id" do 38 | @title = "Mock Update" 39 | mock_data = Mockdata.where(id: params[:id].to_i) 40 | haml :create_mock_request, locals: {mock_data: mock_data.first} 41 | end 42 | 43 | # 44 | # Delete a mock data, not available via a web form or page. Needs to be called via command line. 45 | # 46 | delete "/delete/:id" do 47 | @title = "Mock Deleted" 48 | mock_data = Mockdata.where(id: params[:id].to_i) 49 | data = mock_data.first 50 | if mock_data.any? 51 | mock_data.destroy(params[:id].to_i) 52 | haml :mock_deleted_ack, locals: {message: "Record deleted successfully", success: true} 53 | else 54 | haml :mock_deleted_ack, locals: {message: "Not Found", success: false} 55 | end 56 | end 57 | 58 | # 59 | # TODO Hack to delete a missed row, change to delete 60 | # Delete a missed request log data 61 | # 62 | post "/misses/delete/:id" do 63 | @title = "Mock misses Deleted" 64 | missed_data = MissedRequest.where(id: params[:id].to_i) 65 | data = missed_data.first 66 | if missed_data.any? 67 | missed_data.destroy(params[:id].to_i) 68 | missed_data = MissedRequest.order('created_at DESC').all 69 | haml :missed_requests, locals: {missed_data: missed_data} 70 | else 71 | haml :mock_deleted_ack, locals: {message: "Not Found", success: false} 72 | end 73 | end 74 | 75 | # 76 | # Display the mock create page for a user to create a new mock data for a URL. 77 | # 78 | ['/create', '/home'].each do |path| 79 | get path do 80 | @title = "Create mock response" 81 | haml :create_mock_request, locals: {mock_data: nil} 82 | end 83 | end 84 | 85 | # 86 | # Create the mock data when a form the create form is submit. Some validations are applied directly via the model for 87 | # mandatory fields. Only one URL of the same time can be active at the same time for a test environment. 88 | # 89 | post "/create" do 90 | @title = "Create mock - Acknowledge" 91 | 92 | errors = nil 93 | begin 94 | 95 | if params[:mock_request_url].include? '*' 96 | url_path = (params[:mock_request_url].strip).sub(/^\//, '') 97 | url_query=nil 98 | else 99 | # TODO Validate URI before processing !! 100 | url_path = URI::parse(params[:mock_request_url].strip).path.sub(/^\//, '') 101 | url_query = URI::parse(params[:mock_request_url].strip).query 102 | end 103 | 104 | if url_query 105 | url = url_path + '?' + url_query 106 | else 107 | url = url_path 108 | end 109 | 110 | if params[:id].length == 0 111 | # If New record being attempted to be created and a duplicate name/url/env is found 112 | mockdata = Mockdata.where(mock_name: params[:mock_name].upcase, 113 | mock_request_url: url, 114 | mock_http_verb: params[:mock_http_verb], 115 | mock_environment: params[:mock_environment] 116 | ) 117 | if mockdata.any? 118 | data = mockdata.first 119 | # Set id to nil so that the request is treated as a new record 120 | data.id = nil 121 | raise DuplicateNameAndURL, "Mock name and URL already exist with the name #{data.mock_name}. Search and edit or provide a different name." 122 | errors = true 123 | end 124 | else 125 | mockdata = Mockdata.where(id: params[:id].to_i) 126 | end 127 | 128 | mockdata_exist = Mockdata.where( 129 | mock_request_url: url, 130 | mock_environment: params[:mock_environment], 131 | mock_state: params[:mock_state].nil? ? false : true 132 | ) 133 | 134 | if mockdata.any? 135 | # errors = "Found record" 136 | 137 | data = mockdata.first 138 | data.mock_name= params[:mock_name] 139 | data.mock_request_url= url 140 | data.mock_http_verb= params[:mock_http_verb] 141 | data.mock_state= params[:mock_state].nil? ? false : true 142 | data.mock_http_status= params[:mock_http_status] 143 | data.mock_data_response_headers= params[:mock_data_response_headers] 144 | data.mock_data_response= params[:mock_data_response] 145 | data.mock_environment= params[:mock_environment] 146 | data.mock_content_type= params[:mock_content_type] 147 | data.mock_cookie = params[:mock_cookie] 148 | data.has_before_script = params[:has_before_script].nil? ? false : true 149 | data.before_script_name = params[:before_script_name].nil? ? nil : params[:before_script_name] 150 | data.has_after_script = params[:has_after_script].nil? ? false : true 151 | data.after_script_name = params[:after_script_name].nil? ? nil : params[:after_script_name] 152 | 153 | data.save! 154 | state = :updated 155 | else 156 | # errors = "Not Found record" 157 | data = Mockdata.new 158 | data.mock_name= params[:mock_name] 159 | data.mock_request_url= url 160 | data.mock_http_verb= params[:mock_http_verb] 161 | data.mock_state= params[:mock_state].nil? ? false : true 162 | data.mock_http_status= params[:mock_http_status] 163 | data.mock_data_response_headers= params[:mock_data_response_headers] 164 | data.mock_data_response= params[:mock_data_response] 165 | data.mock_environment= params[:mock_environment] 166 | data.mock_content_type= params[:mock_content_type] 167 | data.mock_cookie = params[:mock_cookie] 168 | data.has_before_script = params[:has_before_script].nil? ? false : true 169 | data.before_script_name = params[:before_script_name].nil? ? nil : params[:before_script_name] 170 | data.has_after_script = params[:has_after_script].nil? ? false : true 171 | data.after_script_name = params[:after_script_name].nil? ? nil : params[:after_script_name] 172 | data.mock_served_times= 0 173 | data.save! 174 | state = :created 175 | end 176 | # Refresh cache 177 | if url.index('*') 178 | $wild_routes = WildRoutes.get_wild_routes_if_any 179 | p 'Cache refreshed' 180 | p $wild_routes 181 | end 182 | rescue DuplicateNameAndURL => errors 183 | session[:errors] = [errors.message] 184 | rescue ActiveRecord::RecordNotUnique => errors 185 | session[:errors] = ["Only one URL can be active at a time. This URL is already ACTIVE with the name '#{(mockdata_exist.first.mock_name).upcase}' ."] 186 | rescue ActiveRecord::ActiveRecordError => errors 187 | session[:errors] = [errors.message] 188 | end 189 | 190 | # 191 | # Validate JSON 192 | # 193 | json_state = :valid 194 | if (data.mock_http_status.match(/^[^4-5]/)) && 195 | (params[:mock_content_type] == 'application/json;charset=UTF-8') 196 | if valid_json?(params[:mock_data_response]) 197 | json_state = :valid 198 | else 199 | json_state = :invalid 200 | end 201 | end 202 | 203 | if errors 204 | messages = errors 205 | haml :create_mock_request, locals: {mock_data: data} 206 | else 207 | haml :create_mock_response, locals: {messages: false, 208 | mock_name: params[:mock_name], 209 | mock_request_url: url, 210 | mock_http_verb: params[:mock_http_verb], 211 | mock_environment: params[:mock_environment], 212 | mock_content_type: params[:mock_content_type], 213 | mock_data_response_headers: params[:mock_data_response_headers], 214 | mock_http_status: params[:mock_http_status], 215 | mock_state: params[:mock_state], 216 | mock_record_state: state, 217 | json_state: json_state 218 | } 219 | end 220 | 221 | end 222 | 223 | # 224 | # Clone the mock data if URL is reachable already, supports only GET requests 225 | # will preset the mock body and headers.Build the 'mockdata' object with all columns if data found 226 | # 227 | get '/clone' do 228 | @title = 'Clone data' 229 | mock_data = {} 230 | msg = nil 231 | if params[:mock_request_url].length > 0 232 | begin 233 | clone_headers = extract_clone_request_headers 234 | response = HTTParty.get(params[:mock_request_url], headers: clone_headers) 235 | rescue => e 236 | # Ignore fatal URL responses 237 | msg = e.message 238 | end 239 | if (response) && 240 | (response.code.to_s.match(/^[1,2,3,404]/)) 241 | mock_data = extract_clone_response(response, 242 | params[:mock_request_url], 243 | params[:mock_name]) 244 | haml :create_mock_request, locals: {mock_data: mock_data} 245 | else 246 | mock_data = ClonedData.new 247 | mock_data.mock_data_response = msg if msg 248 | mock_data.mock_request_url = params[:mock_request_url] 249 | haml :create_mock_request, locals: {mock_data: mock_data} 250 | end 251 | else 252 | mock_data = Mockdata.new 253 | mock_data.mock_data_response = msg if msg 254 | mock_data.mock_request_url = params[:mock_request_url] if params[:mock_request_url].length > 0 255 | haml :create_mock_request, locals: {mock_data: mock_data} 256 | end 257 | end 258 | 259 | # 260 | # Display the form to create multiple mocks in one go, Supports only URLs that are GET type. 261 | # 262 | get '/clone/batch' do 263 | @title = 'Clone in batch' 264 | haml :batch_clone 265 | end 266 | 267 | # 268 | # Process the URLs to be cloned into the mock database. These can then be searched by name and modified. 269 | # 270 | post '/clone/batch' do 271 | @title = 'Clone in batch' 272 | response = process_batch_clone_request(params) 273 | bd = case response 274 | when :updated 275 | 'Updated' 276 | when :error_updating 277 | 'Error Updating' 278 | when :created 279 | 'Created' 280 | when :error_creating 281 | 'Error Creating' 282 | end 283 | content_type 'application/text' 284 | status 200 285 | body bd 286 | end 287 | 288 | # 289 | # Search any available replace data. 290 | # 291 | get '/replace/search' do 292 | @title = 'Maintain search strings' 293 | haml :search_replace, locals: {search_data: nil, search_message: nil} 294 | end 295 | 296 | get '/replace/search/results' do 297 | @title = 'Intelli replace - search results' 298 | search_data = search_replace_data({replace_name: params[:replace_name].upcase}) 299 | 300 | search_message = 'No data found' unless search_data 301 | haml :search_replace, locals: {search_data: search_data, search_message: search_message} 302 | end 303 | 304 | get '/replace/create_update' do 305 | @title = 'Create update replace data' 306 | haml :create_update_replace_data, locals: {replace_data: nil} 307 | end 308 | 309 | # 310 | # Create/Update the replace data. Replace data is used to silently replace mock responses with the replace strings that 311 | # we supply when creating the replace data. 312 | # 313 | post '/replace/create_update' do 314 | @title = 'Updated replace data' 315 | 316 | if params[:id].length == 0 317 | # Create 318 | response = create_update_replace_data({create: true}) 319 | state = :created 320 | else 321 | # Update 322 | response = create_update_replace_data({create: false}) 323 | state = :updated 324 | end 325 | 326 | if response[:error] 327 | session[:errors] = [response[:message]] 328 | haml :create_update_replace_data, locals: {replace_data: response[:replace_data]} 329 | else 330 | haml :create_update_replace_data_ack, locals: {replace_data: response[:replace_data], replace_record_state: state, messages: nil} 331 | end 332 | end 333 | 334 | # 335 | # Display replace data for update. The replace data id is the internal database id passed from the search page. 336 | # 337 | get '/replace/update/:id' do 338 | @title = "Replace string Update" 339 | replace_data = Replacedata.where(id: params[:id].to_i) 340 | if replace_data.any? 341 | haml :create_update_replace_data, locals: {replace_data: replace_data.first} 342 | else 343 | session[:errors] = [response[:message]] 344 | redirect '/mock/replace/search' 345 | end 346 | 347 | end 348 | 349 | 350 | end -------------------------------------------------------------------------------- /controllers/scripts_controller.rb: -------------------------------------------------------------------------------- 1 | require_relative '../controllers/application_controller' 2 | 3 | class MockServerController < ApplicationController 4 | 5 | get '/create/script' do 6 | @title = 'Create script' 7 | haml :create_ruby_script, locals: {rubyscript: nil, success_message: nil} 8 | end 9 | 10 | # 11 | # Create or update a processing script, if the script is an existing script then update else create a new one 12 | # 13 | post '/create/script' do 14 | @title = 'Script created' 15 | begin 16 | if params[:id].length == 0 17 | rubyScript = Rubyscript.new 18 | rubyScript.script_name = params[:script_name] 19 | rubyScript.script_body = params[:script_body] 20 | rubyScript.save! 21 | else 22 | rubyScript = Rubyscript.where(id: params[:id].to_i) 23 | if rubyScript.any? 24 | rubyScript.first.script_name = params[:script_name] 25 | rubyScript.first.script_body = params[:script_body] 26 | rubyScript.first.save! 27 | else 28 | end 29 | end 30 | rescue ActiveRecord::RecordNotUnique => errors 31 | session[:errors] = ['Script name is not unique.'] 32 | rescue ActiveRecord::ActiveRecordError => errors 33 | session[:errors] = [errors.message] 34 | end 35 | 36 | if rubyScript.blank? 37 | haml :create_ruby_script, locals: {rubyscript: nil, success_message: nil} 38 | else 39 | success_message = nil 40 | success_message = 'Script saved successfully' unless session[:errors] 41 | if params[:id].length == 0 42 | haml :create_ruby_script, locals: {rubyscript: rubyScript, success_message: success_message} 43 | else 44 | haml :create_ruby_script, locals: {rubyscript: rubyScript.first, success_message: success_message} 45 | end 46 | end 47 | end 48 | 49 | # 50 | # Update a script, routed here via the search scripts list 51 | # 52 | get '/script/update/:id' do 53 | @title = 'Update script' 54 | script_data = Rubyscript.where(id: params[:id].to_i) 55 | if script_data.any? 56 | haml :create_ruby_script, locals: {rubyscript: script_data.first, success_message: nil} 57 | else 58 | session[:errors] = [response[:message]] 59 | redirect '/mock/script/search' 60 | end 61 | end 62 | 63 | get '/script/search' do 64 | @title = 'Search scripts' 65 | haml :search_scripts, locals: {search_data: nil, search_message: nil} 66 | end 67 | 68 | get '/script/search/results' do 69 | @title = 'Search scripts - results' 70 | search_message = nil 71 | search_data = search_script_data({script_name: params[:script_name]}) 72 | search_message = 'No data found' unless search_data 73 | haml :search_scripts, locals: {search_data: search_data, search_message: search_message} 74 | end 75 | end -------------------------------------------------------------------------------- /controllers/upload_controller.rb: -------------------------------------------------------------------------------- 1 | require_relative '../controllers/application_controller' 2 | 3 | class MockServerController < ApplicationController 4 | 5 | get '/upload/image' do 6 | @title = 'Upload Image' 7 | haml :upload_image, locals: {upload_status: nil} 8 | end 9 | 10 | # 11 | # Process the uploaded files after it has been verified for its size , presence and type 12 | # else issue error message. 13 | # 14 | post '/upload/image' do 15 | @title = 'Upload Image' 16 | error = false 17 | 18 | if (params.has_key? 'image_file_name') 19 | msg = nil 20 | upload_file_name = params['image_file_name'][:filename] 21 | upload_temp_file = params['image_file_name'][:tempfile] 22 | upload_size = upload_temp_file.size 23 | allowed_size = ENV['MAX_UPLOAD_SIZE'].nil? ? 500000 : ENV['MAX_UPLOAD_SIZE'] 24 | actual_file_name_uploaded = upload_file_name.gsub(' ', '_') 25 | 26 | if upload_size.to_i > allowed_size.to_i 27 | msg = "Size of #{upload_file_name} is greater than max allowed file size. Upload failed." 28 | error = true 29 | end 30 | 31 | if params['image_file_name'][:type].match(/^image\//) 32 | else 33 | msg = 'Only image files can be uploaded here. Upload failed.' 34 | error = true 35 | end 36 | 37 | if !error 38 | begin 39 | File.open('public/upload/' + actual_file_name_uploaded, 'w') do |f| 40 | f.write(upload_temp_file.read) 41 | msg = "File name #{actual_file_name_uploaded} uploaded successfully." 42 | end 43 | rescue => e 44 | msg = "#{e.message}. Upload Failed." 45 | end 46 | end 47 | else 48 | msg = 'Filename must be supplied. Upload failed.' 49 | error = true 50 | end 51 | 52 | 53 | if error 54 | session[:errors] = [msg] 55 | haml :upload_image, locals: {upload_status: nil} 56 | else 57 | haml :upload_image, locals: {upload_status: "File #{actual_file_name_uploaded} uploaded successfully."} 58 | end 59 | end 60 | 61 | end -------------------------------------------------------------------------------- /db/datamigration/migrate_mockdata.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'active_record' 3 | 4 | module MockDataMigration 5 | class MockdataOld < ActiveRecord::Base 6 | self.table_name = 'mockdata' 7 | ENV['ENVIRONMENT'] = 'migration' 8 | config = YAML.load_file('../../config/database.yml')[ENV['ENVIRONMENT']] 9 | conn = establish_connection config 10 | end 11 | 12 | class MockdataNew < ActiveRecord::Base 13 | self.table_name = 'mockdata' 14 | ENV['ENVIRONMENT'] = 'development_pg' 15 | config = YAML.load_file('../../config/database.yml')[ENV['ENVIRONMENT']] 16 | conn = establish_connection config 17 | end 18 | 19 | def self.migrate 20 | MockdataOld.find_each do |old_data| 21 | data_hash = {} 22 | old_data.attributes.each do |col,val| 23 | data_hash[col.to_sym] = val 24 | end 25 | p "Processing data for row id #{data_hash[:id]}" 26 | MockdataNew.create!(data_hash) 27 | end 28 | end 29 | end 30 | 31 | MockDataMigration.migrate -------------------------------------------------------------------------------- /db/datamigration/migrate_replace_data.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'active_record' 3 | 4 | module ReplaceDataMigration 5 | class ReplacedataOld < ActiveRecord::Base 6 | self.table_name = 'replacedata' 7 | ENV['ENVIRONMENT'] = 'migration' 8 | config = YAML.load_file('../../config/database.yml')[ENV['ENVIRONMENT']] 9 | conn = establish_connection config 10 | end 11 | 12 | class ReplacedataNew < ActiveRecord::Base 13 | self.table_name = 'replacedata' 14 | ENV['ENVIRONMENT'] = 'development_pg' 15 | config = YAML.load_file('../../config/database.yml')[ENV['ENVIRONMENT']] 16 | conn = establish_connection config 17 | end 18 | 19 | def self.migrate 20 | ReplacedataOld.find_each do |old_data| 21 | data_hash = {} 22 | old_data.attributes.each do |col,val| 23 | data_hash[col.to_sym] = val 24 | end 25 | p "Processing data for row id #{data_hash[:id]}" 26 | ReplacedataNew.create!(data_hash) 27 | end 28 | end 29 | end 30 | 31 | ReplaceDataMigration.migrate -------------------------------------------------------------------------------- /db/datamigration/migrate_ruby_scripts.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'active_record' 3 | 4 | module RubyscriptDataMigration 5 | class RubyscriptOld < ActiveRecord::Base 6 | self.table_name = 'rubyscripts' 7 | ENV['ENVIRONMENT'] = 'migration' 8 | config = YAML.load_file('../../config/database.yml')[ENV['ENVIRONMENT']] 9 | conn = establish_connection config 10 | end 11 | 12 | class RubyscriptNew < ActiveRecord::Base 13 | self.table_name = 'rubyscripts' 14 | ENV['ENVIRONMENT'] = 'development_pg' 15 | config = YAML.load_file('../../config/database.yml')[ENV['ENVIRONMENT']] 16 | conn = establish_connection config 17 | end 18 | 19 | def self.migrate 20 | RubyscriptOld.find_each do |old_data| 21 | data_hash = {} 22 | old_data.attributes.each do |col,val| 23 | data_hash[col.to_sym] = val 24 | end 25 | p "Processing data for row id #{data_hash[:id]}" 26 | RubyscriptNew.create!(data_hash) 27 | end 28 | end 29 | end 30 | 31 | RubyscriptDataMigration.migrate -------------------------------------------------------------------------------- /db/migrations/1_create_mockdata.rb: -------------------------------------------------------------------------------- 1 | class CreateMockdata < ActiveRecord::Migration[5.0] 2 | 3 | def self.up 4 | 5 | create_table :mockdata do |t| 6 | t.string :mock_name 7 | t.string :mock_http_status 8 | t.text :mock_request_url 9 | t.text :mock_http_verb 10 | t.string :mock_data_response_headers 11 | t.text :mock_data_response, limit: 1000000 12 | t.boolean :mock_state 13 | t.string :mock_environment 14 | t.string :mock_content_type 15 | t.integer :mock_served_times 16 | t.boolean :has_before_script 17 | t.string :before_script_name 18 | t.boolean :has_after_script 19 | t.string :after_script_name 20 | t.string :profile_name 21 | t.text :mock_cookie 22 | t.timestamps 23 | end 24 | 25 | execute <<-SQL 26 | CREATE UNIQUE INDEX "unique_mock_data" 27 | ON "mockdata" ("mock_request_url","mock_http_verb", "mock_environment", "mock_state") 28 | WHERE "mock_state" = 't' 29 | SQL 30 | 31 | create_table :missed_requests do |t| 32 | t.string :url 33 | t.string :mock_http_verb 34 | t.string :mock_environment 35 | t.timestamps 36 | end 37 | 38 | create_table :replacedata do |t| 39 | t.string :replace_name 40 | t.string :replaced_string 41 | t.string :replacing_string 42 | t.boolean :is_regexp 43 | t.string :mock_environment 44 | t.boolean :replace_state 45 | end 46 | 47 | create_table :rubyscripts do |t| 48 | t.string :script_name 49 | t.text :script_body 50 | t.timestamps 51 | end 52 | 53 | create_table :httprequestlogs do |t| 54 | t.string :request_http_verb 55 | t.string :request_url 56 | t.string :request_query_string 57 | t.text :request_body, limit: 1000000 58 | t.text :request_headers 59 | t.string :request_environment 60 | t.timestamps 61 | end 62 | 63 | execute <<-SQL 64 | CREATE UNIQUE INDEX "unique_replace_data" 65 | ON "replacedata" ("replaced_string", "mock_environment", "replace_state") 66 | WHERE "replace_state" = 't' 67 | SQL 68 | 69 | end 70 | 71 | def self.down 72 | drop_table :mockdata 73 | drop_table :missed_requests 74 | drop_table :replacedata 75 | drop_table :rubyscripts 76 | drop_table :httprequestlogs 77 | end 78 | end -------------------------------------------------------------------------------- /db/mockserver.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/db/mockserver.db -------------------------------------------------------------------------------- /db/mockserver.db-shm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/db/mockserver.db-shm -------------------------------------------------------------------------------- /db/mockserver.db-wal: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/db/mockserver.db-wal -------------------------------------------------------------------------------- /db/mockserver_test.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/db/mockserver_test.db -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: '3' 3 | networks: 4 | internal_net: 5 | driver: bridge 6 | services: 7 | mock_server: 8 | build: 9 | context: "." 10 | dockerfile: Dockerfile 11 | environment: 12 | - ENVIRONMENT=development_docker 13 | ports: 14 | - 9293:9293 15 | volumes: 16 | - ".:/app" 17 | env_file: 18 | - ".env" 19 | networks: 20 | - internal_net 21 | depends_on: 22 | - pg_database 23 | pg_database: 24 | image: postgres:13.3 25 | env_file: 26 | - ".env" 27 | volumes: 28 | - "./pg_volume/data/:/var/lib/postgresql/data/" 29 | env_file: 30 | - .env 31 | networks: 32 | - internal_net 33 | -------------------------------------------------------------------------------- /helpers/application_api_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | 3 | # 4 | # Activate a mock URL using the id passed in the params 5 | # 6 | def activate_mock_with_id 7 | if params.has_key? 'id' 8 | if Mockdata.new.activate_mock_data(params['id']) 9 | return {status_code: 200, body: 'Activated successfully.'} 10 | else 11 | return {status_code: 404, body: 'Not Found.'} 12 | end 13 | end 14 | end 15 | 16 | # 17 | # Deactivate a mock URL using the id passed in the params 18 | # 19 | def deactivate_mock_with_id 20 | if params.has_key? 'id' 21 | mockData = Mockdata.where(id: params['id']) 22 | if mockData.any? 23 | mockData.first.mock_state = false 24 | mockData.first.save 25 | # Refresh wildcard cache 26 | $wild_routes = WildRoutes.get_wild_routes_if_any 27 | return {status_code: 200, body: 'Deactivated successfully.'} 28 | end 29 | else 30 | return {status_code: 404, body: 'Not Found'} 31 | end 32 | end 33 | 34 | end -------------------------------------------------------------------------------- /helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | 3 | def h(text) 4 | Rack::Utils.escape_html(text) 5 | end 6 | 7 | # 8 | # The main processing function that server the mock responses 9 | # Read the model for the given url in a test environment and serve it as a HTTP response 10 | # as specified in the mock with correct headers and status code. 11 | # 12 | # @param [String] url, The URL that is being mocked. 13 | # @return [Hash] response hash with keys :mock_http_status, :mock_data_response_headers, :mock_data_response [,:error] 14 | # 15 | 16 | def process_url(url, method='GET', env=ENV['TEST_ENV']) 17 | p "Processing URL #{url} / #{method} ..." 18 | return_data={} 19 | data = Mockdata.where(mock_request_url: url, mock_http_verb: method, mock_environment: env, mock_state: true) 20 | if data.any? 21 | return_data = get_mock_data(data.first) 22 | else 23 | wild_route_data = try_wildcard_route_mock_data(method, env) 24 | if wild_route_data 25 | return_data = wild_route_data 26 | else 27 | return_data[:error] = 'Not Found' unless return_data 28 | end 29 | end 30 | return return_data 31 | end 32 | 33 | def intelligent_response_replace(response_to_be_replaced) 34 | replace_data = Replacedata.where(mock_environment: ENV['TEST_ENV'], replace_state: true) 35 | replaced_response = response_to_be_replaced.dup 36 | replace_data.each do |row| 37 | # replaced_response ||= response_to_be_replaced.dup 38 | if row.is_regexp 39 | re = Regexp.new(row.replaced_string) 40 | replaced_response.gsub!(re, row.replacing_string) 41 | else 42 | replaced_response.gsub!(row.replaced_string, row.replacing_string) 43 | end 44 | end 45 | return replaced_response 46 | end 47 | 48 | # 49 | # Search for mock data either by name OR URL. Options key name should match the column name 50 | # @param [Hash] options with key :mock_name or :mock_request_url 51 | # 52 | def search_mock_data(options) 53 | # data = Mockdata.where(mock_name: options[:mock_name]) 54 | if options[:mock_request_url].length == 0 55 | data = Mockdata.where("mock_name LIKE ?", "%#{options[:mock_name]}%").order(:id) 56 | else 57 | data = Mockdata.where("mock_request_url LIKE ?", "%#{options[:mock_request_url]}%").order(:id) 58 | end 59 | 60 | if data.any? 61 | return data 62 | else 63 | return nil 64 | end 65 | end 66 | 67 | # 68 | # Search the replace data table and return results 69 | # 70 | def search_replace_data(options) 71 | data = Replacedata.where("replace_name LIKE ?", "%#{options[:replace_name]}%").order(:id) 72 | if data.any? 73 | return data 74 | else 75 | return nil 76 | end 77 | end 78 | 79 | # 80 | # Search the rubyscript table and return results 81 | # 82 | def search_script_data(options) 83 | data = Rubyscript.where("script_name LIKE ?", "%#{options[:script_name]}%").order(:id) 84 | if data.any? 85 | return data 86 | else 87 | return nil 88 | end 89 | end 90 | 91 | # 92 | # 93 | # 94 | def flash_messages 95 | session[:errors] 96 | end 97 | 98 | # 99 | # Build headers hash 100 | # @param [String] the headers string 101 | # @return [Hash] The headers hash 102 | # 103 | 104 | def build_headers(headers_string) 105 | headers_hash = {} 106 | headers_array = headers_string.split(/\r\n/) 107 | headers_array.each do |header_row| 108 | k, v = header_row.split(ENV['HEADER_DELIMITER']) 109 | headers_hash[k] = v 110 | end 111 | return headers_hash 112 | end 113 | 114 | # 115 | # Extract the HTTParty response for cloning the mock data set response hash to include all table columns. 116 | # The clone will only provide the body and the headers, rest set to nil. Assumes a valid HTTP response with success 117 | # 118 | def extract_clone_response(response, url, mock_name) 119 | 120 | mock_data = ClonedData.new 121 | mock_data.mock_name = mock_name 122 | mock_data.mock_http_status = response.code 123 | mock_data.mock_state = true 124 | mock_data.mock_environment = ENV['TEST_ENV'] 125 | mock_data.mock_content_type = ENV['DEFAULT_CONTENT_TYPE'] 126 | mock_data.mock_request_url = url 127 | hdr_string = 'X-Mock'+ENV['HEADER_DELIMITER']+'True' 128 | hdr_string = hdr_string + "\r\n" + 'Cache-Control' + ENV['HEADER_DELIMITER'] + 'max-age=0, no-cache' 129 | 130 | response.headers.each do |header, hdr_value| 131 | hdr_string = hdr_string + "\r\n" + header + ENV['HEADER_DELIMITER'] + hdr_value 132 | end 133 | mock_data.mock_data_response_headers = hdr_string 134 | mock_data.mock_data_response = response.body 135 | mock_data.id = nil 136 | return mock_data 137 | end 138 | 139 | # 140 | # JSON Validator 141 | # 142 | def valid_json?(json) 143 | begin 144 | JSON.parse(json) 145 | return true 146 | rescue JSON::ParserError => e 147 | return false 148 | end 149 | end 150 | 151 | # 152 | # When part of a batch clone request arrives it supplies the Mock Name, URL and Test environment 153 | # Check of the Mock name, URL, Environment has an active record, if so then update it else create 154 | # a new record in the mocking database 155 | # @params [Hash] params hash with keys :name, :url & :mock_request_environment 156 | # @return [Symbol] state with values :created, :error_creating, :updated, :error_updating 157 | # 158 | def process_batch_clone_request(params) 159 | 160 | url_path = URI::parse(params[:url]).path.sub(/^\//, '') 161 | url_query = URI::parse(params[:url]).query 162 | 163 | if url_query 164 | url = url_path + '?' + url_query 165 | else 166 | url = url_path 167 | end 168 | 169 | state = :created 170 | mockdata = Mockdata.where(mock_request_url: url, 171 | mock_name: params[:name].upcase, 172 | mock_environment: params[:mock_test_environment]) 173 | if mockdata.any? 174 | # errors = "Found record", then get new data to clone 175 | begin 176 | response = HTTParty.get(params[:url]) 177 | rescue => e 178 | # Ignore fatal URL responses 179 | end 180 | if (response) && 181 | (response.code.to_s.match(/^[1,2,3]/)) 182 | data = mockdata.first 183 | data.mock_name= params[:name] 184 | data.mock_request_url= url 185 | data.mock_http_status= response.code 186 | data.mock_data_response_headers= extract_clone_response(response, params[:url], params[:name]).mock_data_response_headers 187 | data.mock_data_response= response.body 188 | data.mock_environment= params[:mock_test_environment] 189 | data.mock_content_type= 'application/json;charset=UTF8' 190 | data.mock_http_verb='GET' 191 | data.save! 192 | state = :updated 193 | 194 | else 195 | state = :error_updating 196 | end 197 | else 198 | # New record need to be created 199 | begin 200 | response = HTTParty.get(params[:url]) 201 | rescue => e 202 | # Ignore fatal URL responses 203 | end 204 | 205 | begin 206 | if (response) && 207 | (response.code.to_s.match(/^[1,2,3]/)) 208 | data = Mockdata.new 209 | data.mock_name= params[:name] 210 | data.mock_request_url= url 211 | data.mock_http_status= response.code 212 | data.mock_data_response_headers= extract_clone_response(response, params[:url], params[:name]).mock_data_response_headers 213 | data.mock_data_response= response.body 214 | data.mock_environment= params[:mock_test_environment] 215 | data.mock_content_type= 'application/json;charset=UTF8' 216 | data.mock_state = true 217 | data.mock_http_verb='GET' 218 | data.save! 219 | state = :created 220 | else 221 | state = :error_creating 222 | end 223 | rescue => e 224 | p e.message 225 | state = :error_creating 226 | end 227 | end 228 | return state 229 | end 230 | 231 | # 232 | # Log the missed requests that could not be served 233 | # 234 | def log_missed_requests(request_object) 235 | missed_request = MissedRequest.new 236 | 237 | url_path = URI::parse(request_object.url).path.sub(/^\//, '') 238 | url_query = URI::parse(request_object.url).query 239 | 240 | if url_query 241 | url = url_path + '?' + url_query 242 | else 243 | url = url_path 244 | end 245 | missed_request.url = (url.nil? || url.size == 0) ? '/' : url 246 | missed_request.mock_environment = ENV['TEST_ENV'] 247 | missed_request.mock_http_verb = request_object.request_method 248 | missed_request.save! 249 | end 250 | 251 | # 252 | # Create or update the replace data strings and their replacements. 253 | # @params [Hash] keys :create with value true or false 254 | # @return [Hash] keys :error, :message, :replace_data 255 | # 256 | def create_update_replace_data(options) 257 | error = false 258 | return_data = {} 259 | if options[:create] 260 | data = Replacedata.where(replaced_string: params[:replaced_string], 261 | mock_environment: params[:mock_environment], 262 | replace_state: true) 263 | begin 264 | replaceData = Replacedata.new 265 | replaceData.replace_name = params[:replace_name].upcase 266 | replaceData.replaced_string = params[:replaced_string] 267 | replaceData.replacing_string = params[:replacing_string] 268 | replaceData.replace_state = params[:replace_state].nil? ? false : true 269 | replaceData.is_regexp = params[:is_regexp].nil? ? false : true 270 | replaceData.mock_environment = params[:mock_environment] 271 | replaceData.save! 272 | rescue ActiveRecord::RecordNotUnique => errors 273 | message = ["This replace is already ACTIVE with name '#{(data.first.replace_name).upcase}'."] 274 | error = true 275 | rescue ActiveRecord::ActiveRecordError => errors 276 | error = true 277 | message = [errors.message] 278 | end 279 | else 280 | data = Replacedata.where(id: params[:id]) 281 | if data.any? 282 | replaceData = data.first 283 | replaceData.replace_name = params[:replace_name].upcase 284 | replaceData.replaced_string = params[:replaced_string] 285 | replaceData.replacing_string = params[:replacing_string] 286 | replaceData.replace_state = params[:replace_state].nil? ? false : true 287 | replaceData.is_regexp = params[:is_regexp].nil? ? false : true 288 | replaceData.mock_environment = params[:mock_environment] 289 | replaceData.save! 290 | else 291 | error = true 292 | message = "Replace data with id #{params[:id]} not found." 293 | end 294 | 295 | end 296 | return_data[:error] = error 297 | return_data[:message] = message 298 | return_data[:replace_data] = replaceData 299 | return return_data 300 | end 301 | 302 | def process_http_verb 303 | log_incoming_request if ENV['REQUEST_LOGGING'] 304 | url = request.fullpath.sub!(/^\//, '') 305 | @mock_response = process_url(url, request.request_method, ENV['TEST_ENV']) 306 | 307 | if ENV['LATENCY'] 308 | sleep ENV['LATENCY'].to_i unless ENV['LATENCY'].to_i == 0 309 | end 310 | 311 | if @mock_response.has_key? :error 312 | log_missed_requests(request) 313 | content_type 'application/text' 314 | status 404 315 | body 'Not Found' 316 | else 317 | status_code = @mock_response[:mock_http_status].to_i 318 | status status_code 319 | content_type @mock_response[:mock_content_type] 320 | headers @mock_response[:mock_data_response_headers] 321 | cookies.merge! @mock_response[:mock_cookie] 322 | 323 | if @mock_response[:mock_content_type].match(/^image\//) 324 | send_file File.join('public/upload/', @mock_response[:image_file]) 325 | else 326 | body @mock_response[:mock_data_response] 327 | end 328 | end 329 | end 330 | 331 | #--------- 332 | private 333 | #--------- 334 | 335 | # 336 | # If there is no exact route match then try to see if there is a wildcard route match and return its data 337 | # @return [Hash] response hash with keys :mock_http_status, :mock_data_response_headers, :mock_data_response [,:error => 'Not Found'] 338 | # 339 | def try_wildcard_route_mock_data(method, env) 340 | wild_route_urls = $wild_routes.keys 341 | url_path = URI::parse(request.url).path.sub(/^\//, '') 342 | url_query = URI::parse(request.url).query 343 | if url_query 344 | url = url_path + '?' + url_query 345 | else 346 | url = url_path 347 | end 348 | $logger.info "Trying to find a wild route for request - #{request.url}..." 349 | matched_route = wild_route_urls.find do |route| 350 | $logger.info "Trying to match #{route}" 351 | begin 352 | Timeout::timeout(1) do 353 | match_data = Regexp.new(route).match "#{request.request_method.upcase}#{ENV['HEADER_DELIMITER']}#{url}" 354 | if match_data 355 | id = $wild_routes[route] 356 | if request.request_method.upcase == mock_row_http_verb(id) 357 | true 358 | end 359 | end 360 | end 361 | rescue => e 362 | $logger.error '*' * 80 363 | $logger.error "WARNING - timeout when matching route #{route} " 364 | $logger.error '*' * 80 365 | false 366 | end 367 | end 368 | 369 | p '%' * 80 370 | p "Matched route #{matched_route}" 371 | p '%' * 80 372 | 373 | if matched_route && matched_route.length > 0 374 | $logger.info '+' * 80 375 | $logger.info "Wild match for #{matched_route}" 376 | $logger.info '+' * 80 377 | route_id = $wild_routes[matched_route] 378 | data = Mockdata.where(id: route_id, 379 | mock_environment: env, 380 | mock_state: true, 381 | mock_http_verb: method) 382 | if data.any? 383 | return get_mock_data(data.first) 384 | else 385 | return {:error => 'Not Found. Are you trying to accessing a route that is not active ?'} 386 | end 387 | else 388 | $logger.info '+' * 80 389 | $logger.info "No wild match for #{url}" 390 | $logger.info '+' * 80 391 | return {:error => 'Not Found'} 392 | end 393 | end 394 | 395 | # 396 | # Refactored into a common routine for returning the mock data hash 397 | # @param [ActiveRecordData] row with mock data from the table 398 | # @return [Hash] response hash with keys :mock_http_status, :mock_data_response_headers, :mock_data_response, :id [,:error] 399 | # 400 | def get_mock_data(row) 401 | return_data = {} 402 | return_data[:mock_http_status] = row[:mock_http_status] 403 | response_body = row[:mock_data_response] 404 | 405 | if row[:mock_content_type].match(/^image\//) 406 | return_data[:image_file] = url.split('/').last 407 | return_data[:mock_data_response] = nil 408 | else 409 | if ENV['REPLACE'] 410 | return_data[:mock_data_response] = intelligent_response_replace(response_body) 411 | else 412 | return_data[:mock_data_response] = row[:mock_data_response] 413 | end 414 | end 415 | return_data[:mock_cookie] = extract_cookies(row[:mock_cookie]) 416 | return_data[:mock_data_response_headers] = build_headers row[:mock_data_response_headers] 417 | 418 | return_data[:mock_content_type] = row[:mock_content_type] 419 | return_data[:id] = row[:id] 420 | row.mock_served_times = row.mock_served_times + 1 421 | p ">>> Updating url served times for - #{url} to #{row.mock_served_times}" 422 | begin 423 | row.save! 424 | rescue => e 425 | p ">>> Retrying to update served times for - #{url} to #{row.mock_served_times}" 426 | sleep 0.5 427 | row.save! 428 | end 429 | return return_data 430 | end 431 | 432 | # 433 | # Extract the cookie name and value from the data, cookie name is followed by a space character followed by the 434 | # value of the cookie 435 | # @param [String] cookie_data 436 | # @return [Hash] Cookie info hash keyed by cookie name 437 | # 438 | def extract_cookies(mock_cookie) 439 | cookies = {} 440 | if mock_cookie 441 | cookie_data = mock_cookie.split(/\r\n/) 442 | cookie_data.each do |cookie_line| 443 | trimmed_line = cookie_line.gsub(/^\s*/, '') 444 | if trimmed_line.size > 0 445 | first_sp_index = trimmed_line.index("\s") 446 | if first_sp_index 447 | cookie_name = trimmed_line[0..first_sp_index-1] 448 | cookie_value = trimmed_line[first_sp_index..trimmed_line.length] 449 | cookies[cookie_name] = cookie_value 450 | else 451 | # Just name no value 452 | cookie_name = trimmed_line.chomp 453 | cookies[cookie_name] = '' 454 | end 455 | end 456 | end 457 | end 458 | return cookies 459 | end 460 | 461 | # 462 | # Extract the clone request headers and send back the hash 463 | # @return [Hash] 464 | # 465 | def extract_clone_request_headers 466 | return_hash = {} 467 | if params[:clone_headers] && params[:clone_headers].length > 0 468 | clone_request_headers = params[:clone_headers] 469 | header_lines = clone_request_headers.split(/\r\n/) 470 | header_lines.each do |header_row| 471 | if header_row.match(ENV['HEADER_DELIMITER']) # Has a valid delimited row 472 | x_hdr, x_val = header_row.split(ENV['HEADER_DELIMITER']) 473 | return_hash[x_hdr] = x_val 474 | end 475 | end 476 | end 477 | p return_hash 478 | return return_hash 479 | end 480 | 481 | # 482 | # Returns the HTTP verb for the mock data row id 483 | # @return [String] HTTP verb or nil 484 | # 485 | def mock_row_http_verb(id) 486 | data = Mockdata.where(id: id) 487 | if data.any? 488 | row = data.first 489 | return row[:mock_http_verb].upcase 490 | else 491 | return nil 492 | end 493 | end 494 | 495 | # 496 | # Validate date time in yyyy-mm-dd hh:mm:ss format 497 | # @param [String] date_time_string string in yyyy-mm-dd hh:mm:ss format 498 | # @return [Boolean] true or false 499 | # 500 | def valid_datetime_string?(datetime_string) 501 | regexp = /^(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01]) ((([01]\d|2[0-3]):)([0-5]\d):)([0-5]\d)$/ 502 | valid_date = true 503 | 504 | if regexp.match(datetime_string) 505 | begin 506 | Time.parse(datetime_string) 507 | rescue ArgumentError 508 | valid_date = false 509 | end 510 | else 511 | valid_date = false 512 | end 513 | return valid_date 514 | end 515 | 516 | end 517 | -------------------------------------------------------------------------------- /helpers/cloned_data.rb: -------------------------------------------------------------------------------- 1 | class ClonedData 2 | def method_missing(m, *args, &block) 3 | attr = m.to_s.sub('=','') 4 | attr_value = *args.last 5 | self.instance_variable_set("@#{attr}", (attr_value.nil? || attr_value.length) == 0 ? nil : attr_value.first) 6 | self.class.define_attr_accessor(attr) # Call a custom method to define the accessor 7 | end 8 | 9 | def self.define_attr_accessor(attr) 10 | attr_accessor attr 11 | end 12 | end -------------------------------------------------------------------------------- /helpers/custom_error.rb: -------------------------------------------------------------------------------- 1 | class DuplicateNameAndURL < StandardError 2 | 3 | end -------------------------------------------------------------------------------- /helpers/request_logger.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | 3 | def log_incoming_request 4 | if !request.env['PATH_INFO'].match(/^\/api\/requestlog/) 5 | httprequestslog = Httprequestlog.new 6 | httprequestslog.save_http_request(request) 7 | begin 8 | sleep 0.5 9 | httprequestslog.save! 10 | rescue ActiveRecord::StatementInvalid 11 | sleep 0.5 12 | httprequestslog.save! #Retry 13 | p 'Successfully retried Statement Invalid' 14 | rescue Exception 15 | sleep 0.5 16 | httprequestslog.save! #Retry 17 | p 'Successfully retried Exception' 18 | end 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /helpers/script_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | 3 | def process_before_script 4 | if has_before_script? 5 | process_script :before 6 | end 7 | end 8 | 9 | def process_after_script 10 | if has_after_script? 11 | process_script :after 12 | end 13 | end 14 | 15 | def has_before_script? 16 | row = get_current_request_db_row 17 | if row && row[:has_before_script] 18 | return true 19 | end 20 | end 21 | 22 | def has_after_script? 23 | row = get_current_request_db_row 24 | if row && row[:has_after_script] 25 | return true 26 | end 27 | end 28 | 29 | # 30 | # Get the current requests mock data from the database. 31 | # @param [None] 32 | # @return [Activerecord::Record] 33 | # 34 | def get_current_request_db_row 35 | if !defined? @current_request_db_data 36 | @current_request_db_data = nil 37 | url = request.fullpath.sub!(/^\//, '') 38 | query = Mockdata.where(mock_request_url: url, 39 | mock_http_verb: request.request_method, 40 | mock_environment: ENV['TEST_ENV'], 41 | mock_state: true) 42 | 43 | if query.any? 44 | @current_request_db_data = query.first 45 | else 46 | wild_try = try_wildcard_route_mock_data((request.request_method).upcase, ENV['TEST_ENV']) 47 | if wild_try.has_key? :error 48 | else 49 | @current_request_db_data = Mockdata.where(id: wild_try[:id]).first 50 | end 51 | end 52 | end 53 | return @current_request_db_data 54 | end 55 | 56 | # 57 | # Evaluate the before and after scripts 58 | # @param [Symbol] :before or :after 59 | # The scripts are evaluated using a simple ruby eval so its upto the user to know the risks 60 | # 61 | def process_script(type) 62 | begin 63 | case type 64 | when :before 65 | scripts = @current_request_db_data[:before_script_name].split(/,/) 66 | scripts.each do |script_name| 67 | row = Rubyscript.where(script_name: script_name.strip).first 68 | eval(row.script_body) unless row.blank? 69 | $logger.debug "Processed BEFORE script #{script_name}" 70 | end 71 | when :after 72 | scripts = @current_request_db_data[:after_script_name].split(/,/) 73 | scripts.each do |script_name| 74 | row = Rubyscript.where(script_name: script_name.strip).first 75 | eval(row.script_body) unless row.blank? 76 | $logger.debug "Processed AFTER #{script_name}" 77 | end 78 | end 79 | rescue => error 80 | $logger.error '------ SCRIPT ERROR ----------' 81 | $logger.error error.message 82 | $logger.error '------------------------------' 83 | 84 | end 85 | end 86 | 87 | end -------------------------------------------------------------------------------- /helpers/string_extension.rb: -------------------------------------------------------------------------------- 1 | class String 2 | def is_i? 3 | !!(self =~ /\A[-+]?[0-9]+\z/) 4 | end 5 | end -------------------------------------------------------------------------------- /helpers/wild_routes.rb: -------------------------------------------------------------------------------- 1 | class WildRoutes 2 | # 3 | # Reads the mock_data table and returns any routes that have the .* wildcard in it, keyed by the route url and 4 | # value is the internal id 5 | # @param None 6 | # @return [Hash] Key value (prefixed with the http verb) of route url and its id 7 | # @example return data {"POST:==:vod/(.*)/heartbeat"=>160,"PUT:==:watchlist/(.*)"=>195, "DELETE:==:watchlist/.*"=>196} 8 | # 9 | def self.get_wild_routes_if_any 10 | return_data = {} 11 | data = Mockdata.select('mock_request_url, id','mock_http_verb').where("mock_state = 't' and mock_request_url LIKE ?", "%*%") 12 | return return_data if data.blank? 13 | 14 | data.each do |data_row| 15 | return_data["#{data_row.mock_http_verb}#{ENV['HEADER_DELIMITER']}#{data_row.mock_request_url}"] = data_row.id 16 | end 17 | return return_data 18 | end 19 | end -------------------------------------------------------------------------------- /logs/migrations.log: -------------------------------------------------------------------------------- 1 | D, [2016-03-04T15:59:17.272530 #21881] DEBUG -- : ActiveRecord::SchemaMigration Load (0.1ms) SELECT "schema_migrations".* FROM "schema_migrations" 2 | D, [2016-03-04T15:59:17.278361 #21881] DEBUG -- : ActiveRecord::SchemaMigration Load (0.1ms) SELECT "schema_migrations".* FROM "schema_migrations" 3 | I, [2016-03-04T15:59:17.278506 #21881] INFO -- : Migrating to CreateMockdata (1) 4 | D, [2016-03-04T15:59:17.278937 #21881] DEBUG -- :  (0.1ms) begin transaction 5 | D, [2016-03-04T15:59:17.279510 #21881] DEBUG -- :  (0.4ms) DROP TABLE "mockdata" 6 | D, [2016-03-04T15:59:17.285016 #21881] DEBUG -- : SQL (0.4ms) DELETE FROM "schema_migrations" WHERE "schema_migrations"."version" = ? [["version", "1"]] 7 | D, [2016-03-04T15:59:17.288469 #21881] DEBUG -- :  (3.3ms) commit transaction 8 | D, [2016-03-04T16:04:13.006266 #21960] DEBUG -- : ActiveRecord::SchemaMigration Load (0.1ms) SELECT "schema_migrations".* FROM "schema_migrations" 9 | I, [2016-03-04T16:04:13.009824 #21960] INFO -- : Migrating to CreateMockdata (1) 10 | D, [2016-03-04T16:04:13.010325 #21960] DEBUG -- :  (0.1ms) begin transaction 11 | D, [2016-03-04T16:04:13.011777 #21960] DEBUG -- :  (0.4ms) CREATE TABLE "mockdata" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "mock_name" varchar, "mock_http_status" varchar, "mock_request_url" varchar, "mock_data_response_headers" varchar, "mock_data_response" text(1000000), "mock_state" boolean, "mock_environment" varchar, "created_at" datetime, "updated_at" datetime)  12 | D, [2016-03-04T16:04:13.011982 #21960] DEBUG -- :  (0.0ms) select sqlite_version(*) 13 | D, [2016-03-04T16:04:13.012658 #21960] DEBUG -- :  (0.4ms) CREATE UNIQUE INDEX "index_mockdata_on_mock_name_and_mock_environment" ON "mockdata" ("mock_name", "mock_environment") 14 | D, [2016-03-04T16:04:13.013018 #21960] DEBUG -- :  (0.1ms) SELECT sql 15 | FROM sqlite_master 16 | WHERE name='index_mockdata_on_mock_name_and_mock_environment' AND type='index' 17 | UNION ALL 18 | SELECT sql 19 | FROM sqlite_temp_master 20 | WHERE name='index_mockdata_on_mock_name_and_mock_environment' AND type='index' 21 | 22 | D, [2016-03-04T16:04:13.013282 #21960] DEBUG -- :  (0.1ms) CREATE UNIQUE INDEX "index_mockdata_on_mock_request_url_and_mock_environment" ON "mockdata" ("mock_request_url", "mock_environment") 23 | D, [2016-03-04T16:04:13.018593 #21960] DEBUG -- : SQL (0.2ms) INSERT INTO "schema_migrations" ("version") VALUES (?) [["version", "1"]] 24 | D, [2016-03-04T16:04:13.022761 #21960] DEBUG -- :  (3.9ms) commit transaction 25 | D, [2016-03-06T10:34:28.574273 #71640] DEBUG -- : ActiveRecord::SchemaMigration Load (0.1ms) SELECT "schema_migrations".* FROM "schema_migrations" 26 | D, [2016-03-06T10:34:28.578007 #71640] DEBUG -- : ActiveRecord::SchemaMigration Load (0.1ms) SELECT "schema_migrations".* FROM "schema_migrations" 27 | I, [2016-03-06T10:34:28.578083 #71640] INFO -- : Migrating to CreateMockdata (1) 28 | D, [2016-03-06T10:34:28.578466 #71640] DEBUG -- :  (0.0ms) begin transaction 29 | D, [2016-03-06T10:34:28.579807 #71640] DEBUG -- :  (1.1ms) DROP TABLE "mockdata" 30 | D, [2016-03-06T10:34:28.583599 #71640] DEBUG -- : SQL (0.2ms) DELETE FROM "schema_migrations" WHERE "schema_migrations"."version" = ? [["version", "1"]] 31 | D, [2016-03-06T10:34:28.587760 #71640] DEBUG -- :  (4.1ms) commit transaction 32 | D, [2016-03-06T10:34:51.581633 #71688] DEBUG -- : ActiveRecord::SchemaMigration Load (0.1ms) SELECT "schema_migrations".* FROM "schema_migrations" 33 | I, [2016-03-06T10:34:51.584034 #71688] INFO -- : Migrating to CreateMockdata (1) 34 | D, [2016-03-06T10:34:51.584389 #71688] DEBUG -- :  (0.0ms) begin transaction 35 | D, [2016-03-06T10:34:51.585886 #71688] DEBUG -- :  (0.3ms) CREATE TABLE "mockdata" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "mock_name" varchar, "mock_http_status" varchar, "mock_request_url" text, "mock_data_response_headers" varchar, "mock_data_response" text(1000000), "mock_state" boolean, "mock_environment" varchar, "mock_content_type" varchar, "created_at" datetime, "updated_at" datetime)  36 | D, [2016-03-06T10:34:51.586095 #71688] DEBUG -- :  (0.1ms) select sqlite_version(*) 37 | D, [2016-03-06T10:34:51.588210 #71688] DEBUG -- :  (0.9ms) CREATE UNIQUE INDEX "index_mockdata_on_mock_name_and_mock_environment" ON "mockdata" ("mock_name", "mock_environment") 38 | D, [2016-03-06T10:34:51.588701 #71688] DEBUG -- :  (0.1ms) SELECT sql 39 | FROM sqlite_master 40 | WHERE name='index_mockdata_on_mock_name_and_mock_environment' AND type='index' 41 | UNION ALL 42 | SELECT sql 43 | FROM sqlite_temp_master 44 | WHERE name='index_mockdata_on_mock_name_and_mock_environment' AND type='index' 45 | 46 | D, [2016-03-06T10:34:51.588958 #71688] DEBUG -- :  (0.1ms) CREATE UNIQUE INDEX "index_mockdata_on_mock_request_url_and_mock_environment" ON "mockdata" ("mock_request_url", "mock_environment") 47 | D, [2016-03-06T10:34:51.594034 #71688] DEBUG -- : SQL (0.1ms) INSERT INTO "schema_migrations" ("version") VALUES (?) [["version", "1"]] 48 | D, [2016-03-06T10:34:51.598327 #71688] DEBUG -- :  (4.1ms) commit transaction 49 | D, [2016-03-06T15:41:54.478079 #16530] DEBUG -- : ActiveRecord::SchemaMigration Load (0.1ms) SELECT "schema_migrations".* FROM "schema_migrations" 50 | D, [2016-03-06T15:41:54.482988 #16530] DEBUG -- : ActiveRecord::SchemaMigration Load (0.1ms) SELECT "schema_migrations".* FROM "schema_migrations" 51 | I, [2016-03-06T15:41:54.483132 #16530] INFO -- : Migrating to CreateMockdata (1) 52 | D, [2016-03-06T15:41:54.483533 #16530] DEBUG -- :  (0.0ms) begin transaction 53 | D, [2016-03-06T15:41:54.484124 #16530] DEBUG -- :  (0.4ms) DROP TABLE "mockdata" 54 | D, [2016-03-06T15:41:54.489097 #16530] DEBUG -- : SQL (0.2ms) DELETE FROM "schema_migrations" WHERE "schema_migrations"."version" = ? [["version", "1"]] 55 | D, [2016-03-06T15:41:54.492466 #16530] DEBUG -- :  (3.3ms) commit transaction 56 | D, [2016-03-06T15:42:06.033749 #16553] DEBUG -- : ActiveRecord::SchemaMigration Load (0.1ms) SELECT "schema_migrations".* FROM "schema_migrations" 57 | I, [2016-03-06T15:42:06.036282 #16553] INFO -- : Migrating to CreateMockdata (1) 58 | D, [2016-03-06T15:42:06.036670 #16553] DEBUG -- :  (0.0ms) begin transaction 59 | D, [2016-03-06T15:42:06.038092 #16553] DEBUG -- :  (0.4ms) CREATE TABLE "mockdata" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "mock_name" varchar, "mock_http_status" varchar, "mock_request_url" text, "mock_data_response_headers" varchar, "mock_data_response" text(1000000), "mock_state" boolean, "mock_environment" varchar, "mock_content_type" varchar, "created_at" datetime, "updated_at" datetime)  60 | D, [2016-03-06T15:42:06.038271 #16553] DEBUG -- :  (0.0ms) select sqlite_version(*) 61 | D, [2016-03-06T15:42:06.039177 #16553] DEBUG -- :  (0.7ms) CREATE UNIQUE INDEX "index_mockdata_on_mock_name_and_mock_environment" ON "mockdata" ("mock_name", "mock_environment") 62 | D, [2016-03-06T15:42:06.044001 #16553] DEBUG -- :  (4.6ms) rollback transaction 63 | D, [2016-03-06T15:44:41.207917 #16875] DEBUG -- : ActiveRecord::SchemaMigration Load (0.1ms) SELECT "schema_migrations".* FROM "schema_migrations" 64 | I, [2016-03-06T15:44:41.210333 #16875] INFO -- : Migrating to CreateMockdata (1) 65 | D, [2016-03-06T15:44:41.210697 #16875] DEBUG -- :  (0.0ms) begin transaction 66 | D, [2016-03-06T15:44:41.212029 #16875] DEBUG -- :  (0.3ms) CREATE TABLE "mockdata" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "mock_name" varchar, "mock_http_status" varchar, "mock_request_url" text, "mock_data_response_headers" varchar, "mock_data_response" text(1000000), "mock_state" boolean, "mock_environment" varchar, "mock_content_type" varchar, "created_at" datetime, "updated_at" datetime)  67 | D, [2016-03-06T15:44:41.212261 #16875] DEBUG -- :  (0.1ms) select sqlite_version(*) 68 | D, [2016-03-06T15:44:41.212783 #16875] DEBUG -- :  (0.3ms) CREATE UNIQUE INDEX "unique_data" ON "mockdata" ("mock_name", "mock_request_url", "mock_environment", "mock_state") 69 | D, [2016-03-06T15:44:41.218772 #16875] DEBUG -- : SQL (0.1ms) INSERT INTO "schema_migrations" ("version") VALUES (?) [["version", "1"]] 70 | D, [2016-03-06T15:44:41.222895 #16875] DEBUG -- :  (3.9ms) commit transaction 71 | -------------------------------------------------------------------------------- /models/httprequestlog.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'uri' 3 | 4 | class Httprequestlog < ActiveRecord::Base 5 | 6 | validates :request_http_verb, presence: true 7 | validates :request_url, presence: true 8 | validates :request_headers, presence: true 9 | validates :request_environment, presence: true 10 | 11 | # 12 | # Save the incoming http request, save only the headers and the body along with the request method and query string and url 13 | # @param [Hash] request sinatra request hash 14 | # @return nil 15 | # 16 | def save_http_request(request) 17 | self.request_http_verb = request.env['REQUEST_METHOD'] 18 | self.request_url = request.env['PATH_INFO'] 19 | self.request_query_string = request.env['QUERY_STRING'] 20 | 21 | output = '' 22 | request.env.each do |k, v| 23 | output << "#{k} => #{v} \n" unless k.match(/[rack,sinatra]/) 24 | end 25 | self.request_headers = output 26 | 27 | body_text = request.body.read 28 | if body_text && body_text.length > 0 29 | begin 30 | self.request_body = URI.decode(body_text) 31 | rescue 32 | # Hack for now to log body any way 33 | self.request_body = body_text 34 | end 35 | else 36 | self.request_body = '' 37 | end 38 | self.request_environment = ENV['TEST_ENV'] 39 | self.request_http_verb = self.request_http_verb.upcase 40 | self.created_at = Time.new.strftime('%Y-%m-%d %H:%M:%S') 41 | end 42 | 43 | # 44 | # Deletes all rows from the httprequestlogs table 45 | # 46 | def self.clear_request_log 47 | Httprequestlog.delete_all 48 | end 49 | 50 | # 51 | # Get the request logs from a start date time to the end date time. If no end datetime is specified the current 52 | # date time is assumed. If no start time is provided then time 10 minutes ago is taken. 53 | # @param [String,[String]] start_datetime and end_datetime in format ('%Y-%m-%d %H:%M:%S', ...) 54 | # @return [JSON] request log data in JSON format or empty JSON if no data 55 | # 56 | def self.get_request_log(start_datetime=(Time.now - (ENV['RECENT_LOG_DURATION'].to_i)).strftime('%Y-%m-%d %H:%M:%S'), 57 | end_datetime=Time.new.strftime('%Y-%m-%d %H:%M:%S'), 58 | matching=nil 59 | ) 60 | if matching.nil? 61 | data = where("created_at >= :start_datetime AND created_at <= :end_datetime", 62 | {start_datetime: start_datetime, end_datetime: end_datetime}).order('created_at DESC') 63 | else 64 | match_string = '%' + matching + '%' 65 | data = where("(created_at >= :start_datetime AND created_at <= :end_datetime) AND request_url like :match_string", 66 | {start_datetime: start_datetime, end_datetime: end_datetime, match_string: match_string}).order('created_at DESC') 67 | end 68 | 69 | if data.any? 70 | return data.to_json 71 | else 72 | return '[{"message" : "No request logs found"}]' 73 | end 74 | end 75 | 76 | # 77 | # Get the details of the logged HTTP request 78 | # @param [Fixnum] log_row_id Id of the database row for the logged http request 79 | # @return [Hash] The logged request row in JSON format 80 | # 81 | def self.get_log_details(log_row_id) 82 | data = where(id: log_row_id) 83 | if data.any? 84 | return data.first 85 | else 86 | return '{"message" : "Log does not exist."}' 87 | end 88 | end 89 | end -------------------------------------------------------------------------------- /models/missed_request.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | class MissedRequest < ActiveRecord::Base 3 | 4 | validates :url, presence: true 5 | validates :mock_http_verb, presence: true 6 | validates :mock_environment, presence: true 7 | 8 | before_save do 9 | self.mock_environment = ENV['TEST_ENV'] 10 | self.mock_http_verb = self.mock_http_verb.upcase 11 | end 12 | 13 | end -------------------------------------------------------------------------------- /models/mockdata.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | class Mockdata < ActiveRecord::Base 3 | 4 | validates :mock_name, presence: true 5 | validates :mock_http_status, presence: true, format: {with: /\A[12345]\d{2}\z/, message: '.Please enter a valid HTTP code.'} 6 | validates :mock_request_url, presence: true 7 | validates :mock_http_verb, presence: true 8 | validates :mock_data_response_headers, presence: true 9 | validate :validate_headers 10 | validate :mock_data_response_body 11 | validates :mock_content_type, presence: true 12 | validates :mock_environment, presence: true 13 | validate :validate_script_state 14 | validate :validate_script_name 15 | 16 | 17 | before_save do 18 | self.mock_name = self.mock_name.gsub(/\s+/, ' ').strip.upcase 19 | self.mock_http_verb = self.mock_http_verb.upcase 20 | self.mock_served_times = 0 if self.mock_served_times.nil? 21 | self.after_script_name = self.after_script_name.nil? ? nil : after_script_name 22 | self.before_script_name = self.before_script_name.nil? ? nil : before_script_name 23 | self.profile_name = self.profile_name.nil? ? '' : self.profile_name.gsub(/\s+/, ' ').strip.upcase 24 | end 25 | 26 | def mock_data_response_body 27 | if self.mock_http_status.match(/^[^4-5]/) && self.mock_data_response.size == 0 28 | errors.add(:mock_data_response, "can't be blank.") 29 | end 30 | end 31 | 32 | def validate_headers 33 | supplied_headers = self.mock_data_response_headers.split(/\r\n/) 34 | supplied_headers.each do |header_data| 35 | errors.add(mock_data_response_headers, " ** Header #{header_data} is not delimited correctly **") unless header_data.match(ENV['HEADER_DELIMITER']) 36 | end 37 | end 38 | 39 | def validate_script_name 40 | if (!self.before_script_name.nil? && self.before_script_name.length > 0) 41 | scripts = self.before_script_name.split(/,/) 42 | scripts.each do |script_name| 43 | if !script_name.match(/^\s*\w+\.rb\s*$/) 44 | errors.add(:before_script_name, "- Script name #{script_name} is invalid. ") 45 | end 46 | end 47 | end 48 | 49 | if (!self.after_script_name.nil? && self.after_script_name.length > 0) 50 | scripts = self.after_script_name.split(/,/) 51 | scripts.each do |script_name| 52 | if !script_name.match(/^\s*\w+\.rb\s*$/) 53 | errors.add(:after_script_name, "- Script name #{script_name} is invalid. ") 54 | end 55 | end 56 | end 57 | end 58 | 59 | def validate_script_state 60 | if self.has_before_script == true 61 | if (self.before_script_name.nil? || self.before_script_name.length == 0) 62 | errors.add(:before_script_name, '- Provide a before script name ending with .rb') 63 | end 64 | end 65 | 66 | if self.has_after_script == true 67 | if (self.after_script_name.nil? || self.after_script_name.length == 0) 68 | errors.add(:after_script_name, '- Provide a after script name ending with .rb') 69 | end 70 | end 71 | end 72 | 73 | def activate_mock_data(id) 74 | found = true 75 | env = ENV['TEST_ENV'] 76 | Mockdata.transaction do 77 | mock_data = Mockdata.where(id: id) 78 | if mock_data.any? 79 | mock_url = mock_data.first.mock_request_url 80 | mock_verb = mock_data.first.mock_http_verb 81 | Mockdata.where('mock_request_url = ? and mock_environment = ? and mock_http_verb= ?', mock_url,env,mock_verb).update_all(mock_state: 'f') 82 | Mockdata.where('id = ?', id).update_all(mock_state: 't') 83 | # Refresh wildcard cache 84 | $wild_routes = WildRoutes.get_wild_routes_if_any if mock_url.index('*') 85 | p $wild_routes 86 | else 87 | found = false 88 | end 89 | end 90 | return found 91 | end 92 | 93 | def reset_served_counts 94 | Mockdata.where('id > 0').update_all(mock_served_times: 0) 95 | end 96 | 97 | end -------------------------------------------------------------------------------- /models/replacedata.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | class Replacedata < ActiveRecord::Base 3 | 4 | validates :replace_name, presence: true 5 | validates :replaced_string, presence: true 6 | validates :replacing_string, presence: true 7 | validates :mock_environment, presence: true 8 | # validates :replace_state, presence: true 9 | # validates :is_regexp, presence: true 10 | 11 | before_save do 12 | self.replace_name = self.replace_name.upcase 13 | end 14 | 15 | 16 | # 17 | # Activate the replace data row of interest. Replace data does not have any unique constraints. If we activate a 18 | # replace data row any other row with the same replace string will be deactivated 19 | # @param [Fixnum] id Id of the replace data row 20 | # @return [Boolean] true or false 21 | # 22 | def activate_replace_mock_data(id) 23 | found = true 24 | Replacedata.transaction do 25 | replace_data = Replacedata.where(id: id) 26 | if replace_data.any? 27 | string_to_be_replaced = replace_data.first.replaced_string 28 | Replacedata.where('replaced_string = ?', string_to_be_replaced).update_all(replace_state: 'f') 29 | Replacedata.where('id = ?', id).update_all(replace_state: 't') 30 | else 31 | found = false 32 | end 33 | end 34 | return found 35 | end 36 | 37 | # 38 | # Set a Replace data mock status to OFF 39 | # @param [Fixnum] if of the replace mock data 40 | # @return [Boolean] True of False depending on if the mock id was found and updated 41 | # 42 | def deactivate_replace_mock_data(id) 43 | found = true 44 | Replacedata.transaction do 45 | replace_data = Replacedata.where(id: id) 46 | if replace_data.any? 47 | Replacedata.where('id = ?', id).update_all(replace_state: 'f') 48 | else 49 | found = false 50 | end 51 | end 52 | return found 53 | end 54 | 55 | # 56 | # Update the column replacing_string with the string supplied 57 | # @param [String] matching - String to be matched 58 | # @param [String] with - Replacement String 59 | # @example update_replace_string("127.0.0.1","196.2.34.67") will scan through all rows and replace any occurance of 60 | # the matching string "127.0.0.1" with "196.2.34.67" 61 | # 62 | def self.update_replace_string(matching,with) 63 | Replacedata.find_each do |replace_data| 64 | replace_data.replacing_string = replace_data.replacing_string.gsub(matching,with) 65 | replace_data.save! 66 | end 67 | end 68 | end -------------------------------------------------------------------------------- /models/rubyscript.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | class Rubyscript < ActiveRecord::Base 3 | validates :script_name, uniqueness: true, presence: true, format: { with: /\A[a-zA-Z]\S+.rb\z/,message: '.Please enter valid script name without spaces ending with .rb.' } 4 | 5 | end -------------------------------------------------------------------------------- /public/bootstrap/css/bootstrap-theme.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["less/theme.less","less/mixins/vendor-prefixes.less","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":";;;;AAmBA,YAAA,aAAA,UAAA,aAAA,aAAA,aAME,YAAA,EAAA,KAAA,EAAA,eC2CA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBDvCR,mBAAA,mBAAA,oBAAA,oBAAA,iBAAA,iBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBCsCA,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBDlCR,qBAAA,sBAAA,sBAAA,uBAAA,mBAAA,oBAAA,sBAAA,uBAAA,sBAAA,uBAAA,sBAAA,uBAAA,+BAAA,gCAAA,6BAAA,gCAAA,gCAAA,gCCiCA,mBAAA,KACQ,WAAA,KDlDV,mBAAA,oBAAA,iBAAA,oBAAA,oBAAA,oBAuBI,YAAA,KAyCF,YAAA,YAEE,iBAAA,KAKJ,aErEI,YAAA,EAAA,IAAA,EAAA,KACA,iBAAA,iDACA,iBAAA,4CAAA,iBAAA,qEAEA,iBAAA,+CCnBF,OAAA,+GH4CA,OAAA,0DACA,kBAAA,SAuC2C,aAAA,QAA2B,aAAA,KArCtE,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAgBN,aEtEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAiBN,aEvEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAkBN,UExEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,gBAAA,gBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,iBAAA,iBAEE,iBAAA,QACA,aAAA,QAMA,mBAAA,0BAAA,yBAAA,0BAAA,yBAAA,yBAAA,oBAAA,2BAAA,0BAAA,2BAAA,0BAAA,0BAAA,6BAAA,oCAAA,mCAAA,oCAAA,mCAAA,mCAME,iBAAA,QACA,iBAAA,KAmBN,aEzEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAoBN,YE1EI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,kBAAA,kBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,mBAAA,mBAEE,iBAAA,QACA,aAAA,QAMA,qBAAA,4BAAA,2BAAA,4BAAA,2BAAA,2BAAA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,+BAAA,sCAAA,qCAAA,sCAAA,qCAAA,qCAME,iBAAA,QACA,iBAAA,KA2BN,eAAA,WClCE,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBD2CV,0BAAA,0BE3FI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GF0FF,kBAAA,SAEF,yBAAA,+BAAA,+BEhGI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GFgGF,kBAAA,SASF,gBE7GI,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SH+HA,cAAA,ICjEA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBD6DV,sCAAA,oCE7GI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBD0EV,cAAA,iBAEE,YAAA,EAAA,IAAA,EAAA,sBAIF,gBEhII,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SHkJA,cAAA,IAHF,sCAAA,oCEhII,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBDgFV,8BAAA,iCAYI,YAAA,EAAA,KAAA,EAAA,gBAKJ,qBAAA,kBAAA,mBAGE,cAAA,EAqBF,yBAfI,mDAAA,yDAAA,yDAGE,MAAA,KE7JF,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,UFqKJ,OACE,YAAA,EAAA,IAAA,EAAA,qBC3HA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBDsIV,eEtLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAKF,YEvLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAMF,eExLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAOF,cEzLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAeF,UEjMI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFuMJ,cE3MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFwMJ,sBE5MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyMJ,mBE7MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0MJ,sBE9MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2MJ,qBE/MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF+MJ,sBElLI,iBAAA,yKACA,iBAAA,oKACA,iBAAA,iKFyLJ,YACE,cAAA,IC9KA,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBDgLV,wBAAA,8BAAA,8BAGE,YAAA,EAAA,KAAA,EAAA,QEnOE,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFiOF,aAAA,QALF,+BAAA,qCAAA,qCAQI,YAAA,KAUJ,OCnME,mBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,EAAA,IAAA,IAAA,gBD4MV,8BE5PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyPJ,8BE7PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0PJ,8BE9PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2PJ,2BE/PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF4PJ,8BEhQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF6PJ,6BEjQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFoQJ,MExQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFsQF,aAAA,QC3NA,mBAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA,qBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA"} -------------------------------------------------------------------------------- /public/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/public/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/public/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /public/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/public/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /public/bootstrap/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/public/bootstrap/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /public/bootstrap/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /public/css/common.css: -------------------------------------------------------------------------------- 1 | input[type="text"] { 2 | background-color: #ffffff; 3 | color: #D2691E; 4 | } 5 | 6 | textarea { 7 | color: #D2691E; 8 | resize: none; 9 | } 10 | 11 | #json_body { 12 | background-color: #ffffff; 13 | font-family: Courier; 14 | font-size: 90%; 15 | color: #D2691E; 16 | } 17 | 18 | #log_rows { 19 | background-color: #ffffff; 20 | font-family: Courier; 21 | font-size: 70%; 22 | color: #D2691E; 23 | } 24 | 25 | .pre_formatted { 26 | white-space: pre; 27 | } 28 | 29 | #script_body { 30 | background-color: #ffffff; 31 | font-family: Courier; 32 | font-size: 100%; 33 | color: #D2691E; 34 | } 35 | -------------------------------------------------------------------------------- /public/img/advanced_options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/public/img/advanced_options.png -------------------------------------------------------------------------------- /public/img/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/public/img/architecture.png -------------------------------------------------------------------------------- /public/img/batch_clone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/public/img/batch_clone.png -------------------------------------------------------------------------------- /public/img/captcha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/public/img/captcha.png -------------------------------------------------------------------------------- /public/img/entertainment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/public/img/entertainment.png -------------------------------------------------------------------------------- /public/img/home_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/public/img/home_screen.png -------------------------------------------------------------------------------- /public/img/kids.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/public/img/kids.png -------------------------------------------------------------------------------- /public/img/mock_design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/public/img/mock_design.png -------------------------------------------------------------------------------- /public/img/mocking_bird.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/public/img/mocking_bird.jpg -------------------------------------------------------------------------------- /public/img/movie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/public/img/movie.png -------------------------------------------------------------------------------- /public/img/replace_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/public/img/replace_screen.png -------------------------------------------------------------------------------- /public/img/request_logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/public/img/request_logs.png -------------------------------------------------------------------------------- /public/img/search_results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/public/img/search_results.png -------------------------------------------------------------------------------- /public/img/search_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/public/img/search_screen.png -------------------------------------------------------------------------------- /public/img/sport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/public/img/sport.png -------------------------------------------------------------------------------- /public/js/common.js: -------------------------------------------------------------------------------- 1 | function showHideAdvancedOptions() { 2 | advDiv = document.getElementById('advanced_options'); 3 | classname = advDiv.className; 4 | advButton = document.getElementById('advanced_button'); 5 | if (classname == 'hidden') { 6 | advDiv.setAttribute('class',''); 7 | advButton.textContent = 'Hide Advanced Options'; 8 | } else { 9 | advDiv.setAttribute('class','hidden'); 10 | advButton.textContent = 'Show Advanced Options'; 11 | } 12 | } 13 | 14 | function showCloneHeaders() { 15 | advDiv = document.getElementById('clone_headers_div'); 16 | classname = advDiv.className; 17 | advButton = document.getElementById('clone_headers_button'); 18 | if (classname == 'hidden') { 19 | advDiv.setAttribute('class',''); 20 | advButton.textContent = 'Hide Clone headers'; 21 | } else { 22 | advDiv.setAttribute('class','hidden'); 23 | advButton.textContent = 'Supply Clone headers'; 24 | } 25 | } 26 | 27 | function validateJsonBody(input_area) { 28 | var contentType = $("#id_mock_content_type").val(); 29 | 30 | if (contentType == "application/json;charset=UTF-8") { 31 | var jsonInput = $("#json_body"); 32 | 33 | if (jsonInput.val().length > 0) { 34 | jsonInput.validateJSON({ 35 | compress: false, 36 | reformat: true, 37 | 'onSuccess': function (json) { 38 | jsonInput.parent().removeClass('has-error').addClass('has-success'); 39 | }, 40 | 'onError': function (error) { 41 | jsonInput.parent().addClass('has-error'); 42 | xx = input_area.selectionStart; 43 | yy = input_area.selectionEnd = xx + 100; 44 | jsonInput.selectRange(xx, yy); 45 | } 46 | }); 47 | } 48 | } 49 | if (contentType == "text/xml") { 50 | 51 | } 52 | if (contentType == "text/html") { 53 | 54 | } 55 | } 56 | 57 | function httpGet() 58 | { 59 | var theUrl = document.getElementById('mock_request_url'); 60 | var urlInput = theUrl.value; 61 | var xmlHttp = new XMLHttpRequest(); 62 | xmlHttp.open( "GET", urlInput, false ); 63 | xmlHttp.send( null ); 64 | document.getElementById('mock_data_response').value = xmlHttp.responseText; 65 | } 66 | 67 | function urlChanged() { 68 | var theUrl = document.getElementById('mock_request_url'); 69 | var c = theUrl.value; 70 | theUrl.value = c; 71 | if (isURL(c) == false) { 72 | var getBtn = document.getElementById('get_button'); 73 | getBtn.disabled = true; 74 | } else { 75 | var getBtn = document.getElementById('get_button'); 76 | getBtn.disabled = false; 77 | } 78 | } 79 | 80 | function isURL(textval) { 81 | var urlregex = /^https?:\/\/.+/; 82 | return urlregex.test(textval); 83 | } 84 | 85 | function getCloningData() { 86 | $("#form_form").attr("action","/mock/clone"); 87 | $("#form_form").attr("method","get"); 88 | $("textarea#json_body").val(''); 89 | document.getElementById("form_form").submit(); 90 | } 91 | 92 | function addMoreRow() { 93 | var new_row = 94 | "\ 95 |
\ 96 |
\ 97 | \ 98 | \ 99 |
\ 100 |
\ 101 | \ 102 | \ 103 |
\ 104 |
\ 105 | \ 106 | \ 111 |
\ 112 | \ 113 |

\ 114 |
" 115 | 116 | var first_row=$('div[id^="row_"]').last().attr('id'); 117 | var row_name_split_arr = (first_row.split("_")); 118 | var current_row_number = parseInt(row_name_split_arr[row_name_split_arr.length - 1]) + 1; 119 | var row = new_row.replace(/xx/g,current_row_number); 120 | 121 | $("#row_1").append(row) 122 | } 123 | 124 | function deleteRow(row) { 125 | var button_row = $(row).attr('row'); 126 | var row = parseInt(button_row); 127 | $("#row_"+row).empty(); 128 | $("#row_"+row).remove(); 129 | } 130 | 131 | function validateBatchCloneData() { 132 | mock_names = $('input[name^="mock_name_"]'); 133 | mock_urls = $('input[name^="mock_url_"]'); 134 | mock_envs = $('select[name^="mock_environment_"]'); 135 | var stateOK = true; 136 | 137 | mock_names.each(function(index) { 138 | if ($(this).val().trim().length == 0) { 139 | stateOK = false 140 | } 141 | }); 142 | 143 | mock_urls.each(function(index) { 144 | if ($(this).val().trim().length == 0) { 145 | stateOK = false 146 | } 147 | }); 148 | 149 | mock_envs.each(function(index) { 150 | if ($(this).val().trim().length == 0) { 151 | stateOK = false 152 | } 153 | }); 154 | 155 | if (stateOK) { 156 | var mock_row_states = $('label[id^="status_row_"]'); 157 | mock_row_states.each(function (index) { 158 | $(this).text('Processing ...'); 159 | }); 160 | $(':button').prop('disabled', true); 161 | } 162 | return stateOK; 163 | } 164 | 165 | function cloneRowOnSubmit(row) { 166 | if (validateBatchCloneData()) { 167 | 168 | mock_names = $('input[name^="mock_name_"]'); 169 | mock_urls = $('input[name^="mock_url_"]'); 170 | mock_envs = $('select[name^="mock_environment_"]'); 171 | mock_row_states = $('label[id^="status_row_"]'); 172 | 173 | var total_rows = mock_row_states.length; 174 | var count = 0; 175 | 176 | mock_names.each(function(index) { 177 | 178 | var mock_name = $(this).val(); 179 | var mock_url= mock_urls[index].value; 180 | var mock_env= mock_envs[index].value; 181 | var mock_row_status= mock_row_states[index]; 182 | 183 | $.ajax({ 184 | method: "POST", 185 | url: "/mock/clone/batch", 186 | data: { name: mock_name, url: mock_url, mock_test_environment: mock_env} 187 | }) 188 | .done(function (msg) { 189 | // alert("Data processed: " + msg); 190 | // mock_row_status.textContent = 'Done'; 191 | mock_row_status.textContent = msg; 192 | switch (msg) { 193 | case 'Updated': 194 | mock_row_status.setAttribute('class','label label-info'); 195 | break; 196 | 197 | case 'Error Updating': 198 | mock_row_status.setAttribute('class','label label-warning'); 199 | break; 200 | 201 | case 'Created': 202 | mock_row_status.setAttribute('class','label label-success'); 203 | break; 204 | 205 | case 'Error Creating': 206 | mock_row_status.setAttribute('class','label label-danger'); 207 | break; 208 | 209 | } 210 | count = count + 1; 211 | if (total_rows == count) { 212 | $(':button').prop('disabled', false); 213 | } 214 | }); 215 | }); 216 | 217 | 218 | } else { 219 | alert("Please supply all data. Mock Name and URL are required."); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /public/js/jquery.caret.js: -------------------------------------------------------------------------------- 1 | (function($, undefined) { 2 | 3 | var _input = document.createElement('input'); 4 | 5 | var _support = { 6 | setSelectionRange: ('setSelectionRange' in _input) || ('selectionStart' in _input), 7 | createTextRange: ('createTextRange' in _input) || ('selection' in document) 8 | }; 9 | 10 | var _rNewlineIE = /\r\n/g, 11 | _rCarriageReturn = /\r/g; 12 | 13 | var _getValue = function(input) { 14 | if (typeof(input.value) !== 'undefined') { 15 | return input.value; 16 | } 17 | return $(input).text(); 18 | }; 19 | 20 | var _setValue = function(input, value) { 21 | if (typeof(input.value) !== 'undefined') { 22 | input.value = value; 23 | } else { 24 | $(input).text(value); 25 | } 26 | }; 27 | 28 | var _getIndex = function(input, pos) { 29 | var norm = _getValue(input).replace(_rCarriageReturn, ''); 30 | var len = norm.length; 31 | 32 | if (typeof(pos) === 'undefined') { 33 | pos = len; 34 | } 35 | 36 | pos = Math.floor(pos); 37 | 38 | // Negative index counts backward from the end of the input/textarea's value 39 | if (pos < 0) { 40 | pos = len + pos; 41 | } 42 | 43 | // Enforce boundaries 44 | if (pos < 0) { pos = 0; } 45 | if (pos > len) { pos = len; } 46 | 47 | return pos; 48 | }; 49 | 50 | var _hasAttr = function(input, attrName) { 51 | return input.hasAttribute ? input.hasAttribute(attrName) : (typeof(input[attrName]) !== 'undefined'); 52 | }; 53 | 54 | /** 55 | * @class 56 | * @constructor 57 | */ 58 | var Range = function(start, end, length, text) { 59 | this.start = start || 0; 60 | this.end = end || 0; 61 | this.length = length || 0; 62 | this.text = text || ''; 63 | }; 64 | 65 | Range.prototype.toString = function() { 66 | return JSON.stringify(this, null, ' '); 67 | }; 68 | 69 | var _getCaretW3 = function(input) { 70 | return input.selectionStart; 71 | }; 72 | 73 | /** 74 | * @see http://stackoverflow.com/q/6943000/467582 75 | */ 76 | var _getCaretIE = function(input) { 77 | var caret, range, textInputRange, rawValue, len, endRange; 78 | 79 | // Yeah, you have to focus twice for IE 7 and 8. *cries* 80 | input.focus(); 81 | input.focus(); 82 | 83 | range = document.selection.createRange(); 84 | 85 | if (range && range.parentElement() === input) { 86 | rawValue = _getValue(input); 87 | 88 | len = rawValue.length; 89 | 90 | // Create a working TextRange that lives only in the input 91 | textInputRange = input.createTextRange(); 92 | textInputRange.moveToBookmark(range.getBookmark()); 93 | 94 | // Check if the start and end of the selection are at the very end 95 | // of the input, since moveStart/moveEnd doesn't return what we want 96 | // in those cases 97 | endRange = input.createTextRange(); 98 | endRange.collapse(false); 99 | 100 | if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) { 101 | caret = rawValue.replace(_rNewlineIE, '\n').length; 102 | } else { 103 | caret = -textInputRange.moveStart("character", -len); 104 | } 105 | 106 | return caret; 107 | } 108 | 109 | // NOTE: This occurs when you highlight part of a textarea and then click in the middle of the highlighted portion in IE 6-10. 110 | // There doesn't appear to be anything we can do about it. 111 | // alert("Your browser is incredibly stupid. I don't know what else to say."); 112 | // alert(range + '\n\n' + range.parentElement().tagName + '#' + range.parentElement().id); 113 | 114 | return 0; 115 | }; 116 | 117 | /** 118 | * Gets the position of the caret in the given input. 119 | * @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element 120 | * @returns {Number} 121 | * @see http://stackoverflow.com/questions/263743/how-to-get-cursor-position-in-textarea/263796#263796 122 | */ 123 | var _getCaret = function(input) { 124 | if (!input) { 125 | return undefined; 126 | } 127 | 128 | // Mozilla, et al. 129 | if (_support.setSelectionRange) { 130 | return _getCaretW3(input); 131 | } 132 | // IE 133 | else if (_support.createTextRange) { 134 | return _getCaretIE(input); 135 | } 136 | 137 | return undefined; 138 | }; 139 | 140 | var _setCaretW3 = function(input, pos) { 141 | input.setSelectionRange(pos, pos); 142 | }; 143 | 144 | var _setCaretIE = function(input, pos) { 145 | var range = input.createTextRange(); 146 | range.move('character', pos); 147 | range.select(); 148 | }; 149 | 150 | /** 151 | * Sets the position of the caret in the given input. 152 | * @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element 153 | * @param {Number} pos 154 | * @see http://parentnode.org/javascript/working-with-the-cursor-position/ 155 | */ 156 | var _setCaret = function(input, pos) { 157 | input.focus(); 158 | 159 | pos = _getIndex(input, pos); 160 | 161 | // Mozilla, et al. 162 | if (_support.setSelectionRange) { 163 | _setCaretW3(input, pos); 164 | } 165 | // IE 166 | else if (_support.createTextRange) { 167 | _setCaretIE(input, pos); 168 | } 169 | }; 170 | 171 | /** 172 | * Inserts the specified text at the current caret position in the given input. 173 | * @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element 174 | * @param {String} text 175 | * @see http://parentnode.org/javascript/working-with-the-cursor-position/ 176 | */ 177 | var _insertAtCaret = function(input, text) { 178 | var curPos = _getCaret(input); 179 | 180 | var oldValueNorm = _getValue(input).replace(_rCarriageReturn, ''); 181 | 182 | var newLength = +(curPos + text.length + (oldValueNorm.length - curPos)); 183 | var maxLength = +input.getAttribute('maxlength'); 184 | 185 | if(_hasAttr(input, 'maxlength') && newLength > maxLength) { 186 | var delta = text.length - (newLength - maxLength); 187 | text = text.substr(0, delta); 188 | } 189 | 190 | _setValue(input, oldValueNorm.substr(0, curPos) + text + oldValueNorm.substr(curPos)); 191 | 192 | _setCaret(input, curPos + text.length); 193 | }; 194 | 195 | var _getInputRangeW3 = function(input) { 196 | var range = new Range(); 197 | 198 | range.start = input.selectionStart; 199 | range.end = input.selectionEnd; 200 | 201 | var min = Math.min(range.start, range.end); 202 | var max = Math.max(range.start, range.end); 203 | 204 | range.length = max - min; 205 | range.text = _getValue(input).substring(min, max); 206 | 207 | return range; 208 | }; 209 | 210 | /** @see http://stackoverflow.com/a/3648244/467582 */ 211 | var _getInputRangeIE = function(input) { 212 | var range = new Range(); 213 | 214 | input.focus(); 215 | 216 | var selection = document.selection.createRange(); 217 | 218 | if (selection && selection.parentElement() === input) { 219 | var len, normalizedValue, textInputRange, endRange, start = 0, end = 0; 220 | var rawValue = _getValue(input); 221 | 222 | len = rawValue.length; 223 | normalizedValue = rawValue.replace(/\r\n/g, "\n"); 224 | 225 | // Create a working TextRange that lives only in the input 226 | textInputRange = input.createTextRange(); 227 | textInputRange.moveToBookmark(selection.getBookmark()); 228 | 229 | // Check if the start and end of the selection are at the very end 230 | // of the input, since moveStart/moveEnd doesn't return what we want 231 | // in those cases 232 | endRange = input.createTextRange(); 233 | endRange.collapse(false); 234 | 235 | if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) { 236 | start = end = len; 237 | } else { 238 | start = -textInputRange.moveStart("character", -len); 239 | start += normalizedValue.slice(0, start).split("\n").length - 1; 240 | 241 | if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) { 242 | end = len; 243 | } else { 244 | end = -textInputRange.moveEnd("character", -len); 245 | end += normalizedValue.slice(0, end).split("\n").length - 1; 246 | } 247 | } 248 | 249 | /// normalize newlines 250 | start -= (rawValue.substring(0, start).split('\r\n').length - 1); 251 | end -= (rawValue.substring(0, end).split('\r\n').length - 1); 252 | /// normalize newlines 253 | 254 | range.start = start; 255 | range.end = end; 256 | range.length = range.end - range.start; 257 | range.text = normalizedValue.substr(range.start, range.length); 258 | } 259 | 260 | return range; 261 | }; 262 | 263 | /** 264 | * Gets the selected text range of the given input. 265 | * @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element 266 | * @returns {Range} 267 | * @see http://stackoverflow.com/a/263796/467582 268 | * @see http://stackoverflow.com/a/2966703/467582 269 | */ 270 | var _getInputRange = function(input) { 271 | if (!input) { 272 | return undefined; 273 | } 274 | 275 | // Mozilla, et al. 276 | if (_support.setSelectionRange) { 277 | return _getInputRangeW3(input); 278 | } 279 | // IE 280 | else if (_support.createTextRange) { 281 | return _getInputRangeIE(input); 282 | } 283 | 284 | return undefined; 285 | }; 286 | 287 | var _setInputRangeW3 = function(input, startPos, endPos) { 288 | input.setSelectionRange(startPos, endPos); 289 | }; 290 | 291 | var _setInputRangeIE = function(input, startPos, endPos) { 292 | var tr = input.createTextRange(); 293 | tr.moveEnd('textedit', -1); 294 | tr.moveStart('character', startPos); 295 | tr.moveEnd('character', endPos - startPos); 296 | tr.select(); 297 | }; 298 | 299 | /** 300 | * Sets the selected text range of (i.e., highlights text in) the given input. 301 | * @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element 302 | * @param {Number} startPos Zero-based index 303 | * @param {Number} endPos Zero-based index 304 | * @see http://parentnode.org/javascript/working-with-the-cursor-position/ 305 | * @see http://stackoverflow.com/a/2966703/467582 306 | */ 307 | var _setInputRange = function(input, startPos, endPos) { 308 | startPos = _getIndex(input, startPos); 309 | endPos = _getIndex(input, endPos); 310 | 311 | // Mozilla, et al. 312 | if (_support.setSelectionRange) { 313 | _setInputRangeW3(input, startPos, endPos); 314 | } 315 | // IE 316 | else if (_support.createTextRange) { 317 | _setInputRangeIE(input, startPos, endPos); 318 | } 319 | }; 320 | 321 | /** 322 | * Replaces the currently selected text with the given string. 323 | * @param {HTMLInputElement|HTMLTextAreaElement} input input or textarea element 324 | * @param {String} text New text that will replace the currently selected text. 325 | * @see http://parentnode.org/javascript/working-with-the-cursor-position/ 326 | */ 327 | var _replaceInputRange = function(input, text) { 328 | var $input = $(input); 329 | 330 | var oldValue = $input.val(); 331 | var selection = _getInputRange(input); 332 | 333 | var newLength = +(selection.start + text.length + (oldValue.length - selection.end)); 334 | var maxLength = +$input.attr('maxlength'); 335 | 336 | if($input.is('[maxlength]') && newLength > maxLength) { 337 | var delta = text.length - (newLength - maxLength); 338 | text = text.substr(0, delta); 339 | } 340 | 341 | // Now that we know what the user selected, we can replace it 342 | var startText = oldValue.substr(0, selection.start); 343 | var endText = oldValue.substr(selection.end); 344 | 345 | $input.val(startText + text + endText); 346 | 347 | // Reset the selection 348 | var startPos = selection.start; 349 | var endPos = startPos + text.length; 350 | 351 | _setInputRange(input, selection.length ? startPos : endPos, endPos); 352 | }; 353 | 354 | var _selectAllW3 = function(elem) { 355 | var selection = window.getSelection(); 356 | var range = document.createRange(); 357 | range.selectNodeContents(elem); 358 | selection.removeAllRanges(); 359 | selection.addRange(range); 360 | }; 361 | 362 | var _selectAllIE = function(elem) { 363 | var range = document.body.createTextRange(); 364 | range.moveToElementText(elem); 365 | range.select(); 366 | }; 367 | 368 | /** 369 | * Select all text in the given element. 370 | * @param {HTMLElement} elem Any block or inline element other than a form element. 371 | */ 372 | var _selectAll = function(elem) { 373 | var $elem = $(elem); 374 | if ($elem.is('input, textarea') || elem.select) { 375 | $elem.select(); 376 | return; 377 | } 378 | 379 | // Mozilla, et al. 380 | if (_support.setSelectionRange) { 381 | _selectAllW3(elem); 382 | } 383 | // IE 384 | else if (_support.createTextRange) { 385 | _selectAllIE(elem); 386 | } 387 | }; 388 | 389 | var _deselectAll = function() { 390 | if (document.selection) { 391 | document.selection.empty(); 392 | } 393 | else if (window.getSelection) { 394 | window.getSelection().removeAllRanges(); 395 | } 396 | }; 397 | 398 | $.extend($.fn, { 399 | 400 | /** 401 | * Gets or sets the position of the caret or inserts text at the current caret position in an input or textarea element. 402 | * @returns {Number|jQuery} The current caret position if invoked as a getter (with no arguments) 403 | * or this jQuery object if invoked as a setter or inserter. 404 | * @see http://web.archive.org/web/20080704185920/http://parentnode.org/javascript/working-with-the-cursor-position/ 405 | * @since 1.0.0 406 | * @example 407 | *
408 |          *    // Get position
409 |          *    var pos = $('input:first').caret();
410 |          * 
411 | * @example 412 | *
413 |          *    // Set position
414 |          *    $('input:first').caret(15);
415 |          *    $('input:first').caret(-3);
416 |          * 
417 | * @example 418 | *
419 |          *    // Insert text at current position
420 |          *    $('input:first').caret('Some text');
421 |          * 
422 | */ 423 | caret: function() { 424 | var $inputs = this.filter('input, textarea'); 425 | 426 | // getCaret() 427 | if (arguments.length === 0) { 428 | var input = $inputs.get(0); 429 | return _getCaret(input); 430 | } 431 | // setCaret(position) 432 | else if (typeof arguments[0] === 'number') { 433 | var pos = arguments[0]; 434 | $inputs.each(function(_i, input) { 435 | _setCaret(input, pos); 436 | }); 437 | } 438 | // insertAtCaret(text) 439 | else { 440 | var text = arguments[0]; 441 | $inputs.each(function(_i, input) { 442 | _insertAtCaret(input, text); 443 | }); 444 | } 445 | 446 | return this; 447 | }, 448 | 449 | /** 450 | * Gets or sets the selection range or replaces the currently selected text in an input or textarea element. 451 | * @returns {Range|jQuery} The current selection range if invoked as a getter (with no arguments) 452 | * or this jQuery object if invoked as a setter or replacer. 453 | * @see http://stackoverflow.com/a/2966703/467582 454 | * @since 1.0.0 455 | * @example 456 | *
457 |          *    // Get selection range
458 |          *    var range = $('input:first').range();
459 |          * 
460 | * @example 461 | *
462 |          *    // Set selection range
463 |          *    $('input:first').range(15);
464 |          *    $('input:first').range(15, 20);
465 |          *    $('input:first').range(-3);
466 |          *    $('input:first').range(-8, -3);
467 |          * 
468 | * @example 469 | *
470 |          *    // Replace the currently selected text
471 |          *    $('input:first').range('Replacement text');
472 |          * 
473 | */ 474 | range: function() { 475 | var $inputs = this.filter('input, textarea'); 476 | 477 | // getRange() = { start: pos, end: pos } 478 | if (arguments.length === 0) { 479 | var input = $inputs.get(0); 480 | return _getInputRange(input); 481 | } 482 | // setRange(startPos, endPos) 483 | else if (typeof arguments[0] === 'number') { 484 | var startPos = arguments[0]; 485 | var endPos = arguments[1]; 486 | $inputs.each(function(_i, input) { 487 | _setInputRange(input, startPos, endPos); 488 | }); 489 | } 490 | // replaceRange(text) 491 | else { 492 | var text = arguments[0]; 493 | $inputs.each(function(_i, input) { 494 | _replaceInputRange(input, text); 495 | }); 496 | } 497 | 498 | return this; 499 | }, 500 | 501 | /** 502 | * Selects all text in each element of this jQuery object. 503 | * @returns {jQuery} This jQuery object 504 | * @see http://stackoverflow.com/a/11128179/467582 505 | * @since 1.5.0 506 | * @example 507 | *
508 |          *     // Select the contents of span elements when clicked
509 |          *     $('span').on('click', function() { $(this).highlight(); });
510 |          * 
511 | */ 512 | selectAll: function() { 513 | return this.each(function(_i, elem) { 514 | _selectAll(elem); 515 | }); 516 | } 517 | 518 | }); 519 | 520 | $.extend($, { 521 | /** 522 | * Deselects all text on the page. 523 | * @returns {jQuery} The jQuery function 524 | * @since 1.5.0 525 | * @example 526 | *
527 |          *     // Select some text
528 |          *     $('span').selectAll();
529 |          *
530 |          *     // Deselect the text
531 |          *     $.deselectAll();
532 |          * 
533 | */ 534 | deselectAll: function() { 535 | _deselectAll(); 536 | return this; 537 | } 538 | }); 539 | 540 | }(window.jQuery || window.Zepto || window.$)); -------------------------------------------------------------------------------- /public/js/liveLogs.js: -------------------------------------------------------------------------------- 1 | angular.module('liveLogs', []) 2 | 3 | .controller('liveLogsController', function($http) { 4 | var vm = this; 5 | vm.liveLogs = []; 6 | vm.i = 0; 7 | vm.message = "Waiting to get logs."; 8 | vm.refreshInterval = 5; 9 | 10 | vm.stopLogs = function stopRefresh() { 11 | advButton = document.getElementById('log_refresh_start_stop'); 12 | classname = advButton.getAttribute('name'); 13 | if (classname == 'stop_refresh') { 14 | clearInterval(vm.listener); 15 | advButton.setAttribute('name','start_refresh'); 16 | advButton.value = 'Resume'; 17 | } else { 18 | clearInterval(vm.listener); 19 | vm.listener = setInterval(logs, vm.refreshInterval * 1000); 20 | advButton.setAttribute('name','stop_refresh'); 21 | advButton.value = 'Pause'; 22 | } 23 | }; 24 | 25 | vm.setRefreshInterval = function changeInterval() { 26 | if (!isNaN(vm.refreshInterval) && vm.refreshInterval >= 5) { 27 | clearInterval(vm.listener); 28 | vm.listener = setInterval(logs, vm.refreshInterval * 1000); 29 | } 30 | }; 31 | 32 | function logs() { 33 | vm.liveLogs = []; 34 | $http.get('/mock/api/requestlog/recent') 35 | .then(function(response) { 36 | vm.liveLogs = []; 37 | var json = response.data[0]; 38 | if (json['message'] == null) { 39 | vm.message = ""; 40 | vm.i = 0; 41 | response.data.forEach(function(array_item){ 42 | vm.liveLogs.push( 43 | { 44 | serial_number: ++vm.i, 45 | id: array_item.id, 46 | request_http_verb: array_item.request_http_verb, 47 | request_url: array_item.request_url.substring(0,70), 48 | created_at: array_item.created_at, 49 | request_body: array_item.request_body.substring(0,20), 50 | request_query_string: array_item.request_query_string.substring(0,20) 51 | } 52 | ) 53 | }); 54 | } else { 55 | vm.message = "There is no RECENT activity recorded as yet."; 56 | } 57 | }); 58 | } 59 | 60 | vm.listener = setInterval(logs,vm.refreshInterval * 1000); 61 | }); -------------------------------------------------------------------------------- /public/upload/captcha.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvemjsun/mock_server/539ac48a18c7195d56404405dee5bd991ba21153/public/upload/captcha.jpg -------------------------------------------------------------------------------- /spec/create_mock_data_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative 'spec_helper' 2 | 3 | describe 'The mock/create page' do 4 | 5 | before :each do 6 | visit('/mock/create') 7 | end 8 | 9 | it 'allows user to create mock data without advanced options' do 10 | TestHelper.create_test_mock TestHelper.mockData 11 | TestHelper.submit_form_to_create_update_mock_data 12 | expect(page).to have_content('Mock Data - CREATED') 13 | end 14 | 15 | it 'does not allow to create mock data with same url enabled more than once in a test environment' do 16 | TestHelper.insert_mock_row_into_db 17 | TestHelper.create_test_mock TestHelper.mockData 18 | TestHelper.submit_form_to_create_update_mock_data 19 | expect(page).to have_content 'Only one URL can be active at a time.' 20 | end 21 | 22 | it 'should not allow a blank mock name' do 23 | TestHelper.create_test_mock TestHelper.mockData({mock_request_name: ''}) 24 | TestHelper.submit_form_to_create_update_mock_data 25 | expect(page).to have_content('Mock name can\'t be blank') 26 | end 27 | 28 | it 'should not allows an empty http status code' do 29 | TestHelper.create_test_mock TestHelper.mockData({mock_http_status: ''}) 30 | TestHelper.submit_form_to_create_update_mock_data 31 | expect(page).to have_content('Mock http status can\'t be blank') 32 | end 33 | 34 | it 'should not allow a blank mock url' do 35 | TestHelper.create_test_mock TestHelper.mockData({mock_request_url: ''}) 36 | TestHelper.submit_form_to_create_update_mock_data 37 | expect(page).to have_content('Mock request url can\'t be blank') 38 | end 39 | 40 | it 'should not an invalid HTTP status code like 900' do 41 | TestHelper.create_test_mock TestHelper.mockData({mock_http_status: '900'}) 42 | TestHelper.submit_form_to_create_update_mock_data 43 | expect(page).to have_content('Please enter a valid HTTP code') 44 | end 45 | 46 | it 'should not an invalid HTTP status code like abcd' do 47 | TestHelper.create_test_mock TestHelper.mockData({mock_http_status: 'abcd'}) 48 | TestHelper.submit_form_to_create_update_mock_data 49 | expect(page).to have_content('Please enter a valid HTTP code') 50 | end 51 | 52 | it 'should give a warning if a JSON content type response is not created with a valid JSON body' do 53 | TestHelper.create_test_mock TestHelper.mockData({mock_content_type: 'application/json;charset=UTF-8', 54 | mock_http_body: 'not a json'}) 55 | TestHelper.submit_form_to_create_update_mock_data 56 | expect(page).to have_content('Response Body is not a Valid JSON') 57 | end 58 | 59 | end 60 | -------------------------------------------------------------------------------- /spec/create_search_edit_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'The mock server should be able to search mock details' do 2 | 3 | before :each do 4 | data = { 5 | has_before_script: true, 6 | before_script_name: 'before_script_name.rb', 7 | has_after_script: true, 8 | after_script_name: 'after_script_name.rb', 9 | mock_cookie: 'cookie_name 987654321' 10 | } 11 | TestHelper.insert_mock_row_into_db(data) 12 | visit('/mock/search') 13 | fill_in('search_mock_request_url', with: 'a/test/url') 14 | click_button('Search') 15 | click_link('1', href: '/mock/update/1') 16 | click_button('Show Advanced Options') 17 | end 18 | 19 | it 'Should be able to retrieve details of an existing mock' do 20 | expect(find('#mock_name').value).to eq('TEST MOCK 1') 21 | expect(find('#mock_http_status').value).to eq('200') 22 | expect(find('#chk_mock_state').value).to eq('1') 23 | expect(find('#mock_request_url').value).to eq('a/test/url') 24 | expect(page).to have_select('sl_mock_http_verb', selected: 'GET') 25 | 26 | expect(page).to have_select('sl_mock_environment', selected: 'production') 27 | expect(page).to have_select('id_mock_content_type', selected: 'text/plain') 28 | expect(find('#mock_data_response_headers').value).to eq('x:==:y') 29 | 30 | expect(find('#chk_has_before_script').value).to eq('1') 31 | expect(find('#chk_has_after_script').value).to eq('1') 32 | expect(find('#before_script_name').value).to eq('before_script_name.rb') 33 | expect(find('#after_script_name').value).to eq('after_script_name.rb') 34 | expect(find('#mock_cookie').value).to eq('cookie_name 987654321') 35 | end 36 | 37 | it 'Should be able to update an attribute of the mock data' do 38 | fill_in('mock_http_status', with: 503) 39 | select 'POST', :from => 'sl_mock_http_verb' 40 | click_button('Create/Update Mock Data') 41 | expect(page).to have_content('Mock Data - UPDATED') 42 | 43 | click_link('Search', href: '/mock/search') 44 | fill_in('search_mock_request_url', with: 'a/test/url') 45 | click_button('Search') 46 | expect(page).to have_content('503') 47 | expect(page).to have_content('POST') 48 | end 49 | 50 | end -------------------------------------------------------------------------------- /spec/create_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative 'spec_helper' 2 | 3 | describe 'The mock/create page' do 4 | 5 | before :each do 6 | visit('/mock/create') 7 | end 8 | 9 | it 'has all the needed form fields' do 10 | 11 | expect(page).to have_field('mock_name') 12 | expect(page).to have_field('mock_http_status') 13 | expect(page).to have_field('chk_mock_state', {checked: false}) 14 | 15 | expect(page).to have_field('mock_request_url') 16 | expect(page).to have_button('clone_headers_button') 17 | expect(page).to have_button('get_button', {disabled: true}) 18 | 19 | expect(page).to have_select('sl_mock_http_verb') 20 | expect(page).to have_select('sl_mock_environment') 21 | expect(page).to have_select('id_mock_content_type') 22 | expect(page).to have_field('mock_data_response_headers') 23 | 24 | expect(page).to have_button('advanced_button') 25 | 26 | expect(page).to have_field('mock_data_response') 27 | 28 | expect(page).not_to have_unchecked_field('chk_has_before_script') 29 | expect(page).not_to have_field('before_script_name') 30 | 31 | expect(page).not_to have_unchecked_field('chk_has_after_script') 32 | expect(page).not_to have_field('after_script_name') 33 | 34 | expect(page).not_to have_field('mock_cookie') 35 | 36 | expect(page).to have_button('Create/Update Mock Data') 37 | end 38 | 39 | it 'shows the advanced options when the show advanced button is clicked' do 40 | 41 | click_button('Show Advanced Options') 42 | 43 | expect(page).to have_unchecked_field('chk_has_before_script') 44 | expect(page).to have_field('before_script_name') 45 | 46 | expect(page).to have_unchecked_field('chk_has_after_script') 47 | expect(page).to have_field('after_script_name') 48 | 49 | expect(page).to have_field('mock_cookie') 50 | end 51 | 52 | it 'should have the link to Mock Env page' do 53 | expect(page).to have_link('Mock Env') 54 | expect(page).to have_selector(:css, 'a[href="/environment"]') 55 | end 56 | 57 | it 'should have the link to Search' do 58 | expect(page).to have_link('Search') 59 | expect(page).to have_selector(:css, 'a[href="/mock/search"]') 60 | end 61 | 62 | it 'should have the link to clone in batch' do 63 | expect(page).to have_link('Clone Many') 64 | expect(page).to have_selector(:css, 'a[href="/mock/clone/batch"]') 65 | end 66 | 67 | it 'should have the link to search missed mock requests' do 68 | expect(page).to have_link('Misses') 69 | expect(page).to have_selector(:css, 'a[href="/mock/search/misses"]') 70 | end 71 | 72 | it 'should have the link to create replace data' do 73 | expect(page).to have_link('Replace') 74 | expect(page).to have_selector(:css, 'a[href="/mock/replace/create_update"]') 75 | end 76 | 77 | it 'should have the link to search replace data' do 78 | expect(page).to have_link('Replace Search') 79 | expect(page).to have_selector(:css, 'a[href="/mock/replace/search"]') 80 | end 81 | 82 | it 'should have the link to upload image' do 83 | expect(page).to have_link('Upload Image') 84 | expect(page).to have_selector(:css, 'a[href="/mock/upload/image"]') 85 | end 86 | 87 | it 'should have the link to create before and after ruby scripts' do 88 | expect(page).to have_link('Script') 89 | expect(page).to have_selector(:css, 'a[href="/mock/create/script"]') 90 | end 91 | 92 | it 'should have the link to search before and after ruby scripts' do 93 | expect(page).to have_link('Search Script') 94 | expect(page).to have_selector(:css, 'a[href="/mock/script/search"]') 95 | end 96 | end -------------------------------------------------------------------------------- /spec/helpers.rb: -------------------------------------------------------------------------------- 1 | module TestHelper 2 | 3 | extend Capybara::DSL 4 | 5 | def self.create_test_mock(data) 6 | fill_in('mock_name', with: data[:mock_request_name]) 7 | fill_in('mock_http_status', with: data[:mock_http_status]) 8 | check('chk_mock_state') if (data[:mock_enabled]) 9 | 10 | fill_in('Request URL To mock', with: data[:mock_request_url]) 11 | select(data[:mock_http_verb], :from => 'sl_mock_http_verb') 12 | select(data[:mock_test_environment], :from => 'sl_mock_environment') 13 | select(data[:mock_content_type], :from => 'id_mock_content_type') 14 | fill_in('json_body', with: data[:mock_http_body]) 15 | end 16 | 17 | def self.set_advanced_options(data) 18 | 19 | end 20 | 21 | def self.submit_form_to_create_update_mock_data 22 | click_button('Create/Update Mock Data') 23 | end 24 | 25 | def self.mockData(options={}) 26 | return {mock_request_name: 'Test Mock 2', 27 | mock_http_status: 200, 28 | mock_enabled: true, 29 | mock_request_url: 'a/test/url', 30 | mock_http_verb: 'GET', 31 | mock_test_environment: 'production', 32 | mock_content_type: 'text/plain', 33 | mock_http_body: 'mock body' 34 | }.merge! options 35 | end 36 | 37 | def self.insert_mock_row_into_db(options={}) 38 | data = { 39 | mock_name: 'Test mock 1', 40 | mock_http_status: 200, 41 | mock_request_url: 'a/test/url', 42 | mock_http_verb: 'GET', 43 | mock_data_response_headers: 'x:==:y', # Delimiter is :==: 44 | mock_state: true, 45 | mock_environment: 'production', 46 | mock_content_type: 'text/plain', 47 | mock_data_response: 'test', 48 | mock_served_times: 0, 49 | profile_name: '', 50 | } 51 | data.merge! options 52 | begin 53 | Mockdata.create(data) 54 | rescue => e 55 | p e.message 56 | raise 'Error creating mock data' 57 | end 58 | 59 | end 60 | 61 | def self.insert_row_into_replace_data(options={}) 62 | data = { 63 | replace_name: 'Replace Data', 64 | replaced_string: 'hello to me', 65 | replacing_string: 'hola to me', 66 | is_regexp: false, 67 | replace_state: true, 68 | mock_environment: 'production' 69 | } 70 | data.merge! options 71 | begin 72 | Replacedata.create(data) 73 | rescue => e 74 | p e.message 75 | raise 'Error creating replace data' 76 | end 77 | end 78 | end -------------------------------------------------------------------------------- /spec/search_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Mock server search feature' do 2 | 3 | before :each do 4 | visit('/mock/search') 5 | end 6 | 7 | it 'Should have have option to search a mock using URL' do 8 | expect(page).to have_css('input#search_mock_request_url') 9 | end 10 | 11 | it 'Should have have option to search a mock using Mock name' do 12 | expect(page).to have_css('input#search_mock_name') 13 | end 14 | 15 | it 'Should return search result(s) using mock name' do 16 | TestHelper.insert_mock_row_into_db({mock_name: 'REQUEST 1', mock_request_url: 'hello/world1', mock_data_response: 'Hi1'}) 17 | TestHelper.insert_mock_row_into_db({mock_name: 'REQUEST 2', mock_request_url: 'hello/world2', mock_data_response: 'Hi2'}) 18 | fill_in('search_mock_name', with: 'REQUEST') 19 | click_button('Search') 20 | expect(page).to have_content ('REQUEST 1') 21 | expect(page).to have_link('1', href: '/mock/update/1') 22 | expect(page).to have_content ('REQUEST 2') 23 | expect(page).to have_link('2', href: '/mock/update/2') 24 | end 25 | 26 | it 'Should return search result(s) using mock url' do 27 | TestHelper.insert_mock_row_into_db({mock_name: 'REQUEST 1', mock_request_url: 'hello/world1', mock_data_response: 'Hi1'}) 28 | TestHelper.insert_mock_row_into_db({mock_name: 'REQUEST 2', mock_request_url: 'hello/world2', mock_data_response: 'Hi2'}) 29 | fill_in('search_mock_request_url', with: 'hello') 30 | click_button('Search') 31 | expect(page).to have_content ('REQUEST 1') 32 | expect(page).to have_link('1', href: '/mock/update/1') 33 | expect(page).to have_content ('REQUEST 2') 34 | expect(page).to have_link('2', href: '/mock/update/2') 35 | end 36 | 37 | it 'Should allow to search and select details of a search result' do 38 | TestHelper.insert_mock_row_into_db({mock_name: 'REQUEST 1', mock_request_url: 'hello/world1', mock_data_response: 'Hi1'}) 39 | TestHelper.insert_mock_row_into_db({mock_name: 'REQUEST 2', mock_request_url: 'hello/world2', mock_data_response: 'Hi2'}) 40 | fill_in('search_mock_name', with: 'REQUEST') 41 | click_button('Search') 42 | click_link('1', href: '/mock/update/1') 43 | expect(page).to have_content('Mock Server - Create/Update mock data') 44 | expect(page).to have_css('textarea#json_body') 45 | end 46 | end -------------------------------------------------------------------------------- /spec/serve_mock_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative 'spec_helper' 2 | 3 | describe 'Mock server' do 4 | 5 | it 'should return http 200 for a known url' do 6 | TestHelper.insert_mock_row_into_db({mock_name: 'A MOCK GET REQUEST', mock_request_url: 'hello/world', mock_data_response: 'Hi'}) 7 | expect(HTTParty.get('http://localhost:9293/hello/world').code).to eq(200) 8 | end 9 | 10 | it 'should return http 404 for a unknown url' do 11 | expect(HTTParty.get('http://localhost:9293/hello/world').code).to eq(404) 12 | end 13 | 14 | it 'should return http 500 for for the mock set up with http status of 500' do 15 | TestHelper.insert_mock_row_into_db({mock_name: 'HTTP 500', mock_request_url: 'hello/world', mock_http_status: 500}) 16 | expect(HTTParty.get('http://localhost:9293/hello/world').code).to eq(500) 17 | end 18 | 19 | it 'should serve the correct response body' do 20 | TestHelper.insert_mock_row_into_db({mock_name: 'A MOCK GET REQUEST', mock_request_url: 'hello/world', mock_data_response: 'Hi'}) 21 | expect(HTTParty.get('http://localhost:9293/hello/world').body).to eq('Hi') 22 | end 23 | 24 | it 'should serve the correct text/plain content-type header' do 25 | TestHelper.insert_mock_row_into_db({mock_name: 'A MOCK GET REQUEST', mock_request_url: 'hello/world', mock_data_response: 'Hi'}) 26 | expect(HTTParty.get('http://localhost:9293/hello/world').headers["content-type"]).to eq('text/plain;charset=utf-8') 27 | end 28 | 29 | it 'should serve the correct application/json content-type header' do 30 | TestHelper.insert_mock_row_into_db({mock_name: 'A MOCK GET REQUEST', mock_request_url: 'hello/world', mock_data_response: '{}', mock_content_type: 'application/json;charset=utf-8'}) 31 | expect(HTTParty.get('http://localhost:9293/hello/world').headers["content-type"]).to eq('application/json;charset=utf-8') 32 | expect(HTTParty.get('http://localhost:9293/hello/world').headers['x']).to eq('y') 33 | end 34 | 35 | it 'should return http 404 when a url is disabled' do 36 | TestHelper.insert_mock_row_into_db({mock_name: 'A MOCK GET REQUEST', mock_request_url: 'hello/world',mock_state: false, mock_data_response: 'Hi'}) 37 | expect(HTTParty.get('http://localhost:9293/hello/world').code).to eq(404) 38 | end 39 | 40 | it 'should correctly replace the response body with the replace data' do 41 | TestHelper.insert_mock_row_into_db({mock_name: 'A MOCK GET REQUEST', mock_request_url: 'hello/world', mock_data_response: 'hello to me'}) 42 | TestHelper.insert_row_into_replace_data({replaced_string: 'hello to me', 43 | replacing_string: 'hola to me',}) 44 | expect(HTTParty.get('http://localhost:9293/hello/world').body).to eq('hola to me') 45 | end 46 | 47 | end -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'capybara' 2 | require 'capybara/rspec' 3 | require 'capybara/dsl' 4 | require 'capybara-webkit' 5 | require 'database_cleaner' 6 | require 'yaml' 7 | require 'active_record' 8 | require 'httparty' 9 | 10 | require_relative '../models/mockdata' 11 | require_relative '../models/replacedata' 12 | require_relative '../spec/helpers' 13 | 14 | Capybara.default_driver = :webkit 15 | Capybara.javascript_driver = :webkit 16 | Capybara.app_host='http://localhost:9293' 17 | 18 | Capybara::Webkit.configure do |config| 19 | config.debug = false 20 | config.allow_url('*') 21 | end 22 | 23 | ENV['ENVIRONMENT'] = 'test' 24 | ENV['HEADER_DELIMITER'] = ':==:' 25 | 26 | db = YAML.load_file(File.expand_path('./config/database.yml'))[ENV['ENVIRONMENT']] 27 | ActiveRecord::Base.establish_connection db 28 | 29 | # This file was generated by the `rspec --init` command. Conventionally, all 30 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 31 | # The generated `.rspec` file contains `--require spec_helper` which will cause 32 | # this file to always be loaded, without a need to explicitly require it in any 33 | # files. 34 | # 35 | # Given that it is always loaded, you are encouraged to keep this file as 36 | # light-weight as possible. Requiring heavyweight dependencies from this file 37 | # will add to the boot time of your test suite on EVERY test run, even for an 38 | # individual file that may not need all of that loaded. Instead, consider making 39 | # a separate helper file that requires the additional dependencies and performs 40 | # the additional setup, and require it from the spec files that actually need 41 | # it. 42 | # 43 | # The `.rspec` file also contains a few flags that are not defaults but that 44 | # users commonly want. 45 | # 46 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 47 | RSpec.configure do |config| 48 | # rspec-expectations config goes here. You can use an alternate 49 | # assertion/expectation library such as wrong or the stdlib/minitest 50 | # assertions if you prefer. 51 | config.expect_with :rspec do |expectations| 52 | # This option will default to `true` in RSpec 4. It makes the `description` 53 | # and `failure_message` of custom matchers include text for helper methods 54 | # defined using `chain`, e.g.: 55 | # be_bigger_than(2).and_smaller_than(4).description 56 | # # => "be bigger than 2 and smaller than 4" 57 | # ...rather than: 58 | # # => "be bigger than 2" 59 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 60 | end 61 | 62 | # rspec-mocks config goes here. You can use an alternate test double 63 | # library (such as bogus or mocha) by changing the `mock_with` option here. 64 | config.mock_with :rspec do |mocks| 65 | # Prevents you from mocking or stubbing a method that does not exist on 66 | # a real object. This is generally recommended, and will default to 67 | # `true` in RSpec 4. 68 | mocks.verify_partial_doubles = true 69 | end 70 | 71 | # The settings below are suggested to provide a good initial experience 72 | # with RSpec, but feel free to customize to your heart's content. 73 | =begin 74 | # These two settings work together to allow you to limit a spec run 75 | # to individual examples or groups you care about by tagging them with 76 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples 77 | # get run. 78 | config.filter_run :focus 79 | config.run_all_when_everything_filtered = true 80 | 81 | # Allows RSpec to persist some state between runs in order to support 82 | # the `--only-failures` and `--next-failure` CLI options. We recommend 83 | # you configure your source control system to ignore this file. 84 | config.example_status_persistence_file_path = "spec/examples.txt" 85 | 86 | # Limits the available syntax to the non-monkey patched syntax that is 87 | # recommended. For more details, see: 88 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ 89 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 90 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode 91 | config.disable_monkey_patching! 92 | 93 | # This setting enables warnings. It's recommended, but in some cases may 94 | # be too noisy due to issues in dependencies. 95 | config.warnings = true 96 | 97 | # Many RSpec users commonly either run the entire suite or an individual 98 | # file, and it's useful to allow more verbose output when running an 99 | # individual spec file. 100 | if config.files_to_run.one? 101 | # Use the documentation formatter for detailed output, 102 | # unless a formatter has already been configured 103 | # (e.g. via a command-line flag). 104 | config.default_formatter = 'doc' 105 | end 106 | 107 | # Print the 10 slowest examples and example groups at the 108 | # end of the spec run, to help surface which specs are running 109 | # particularly slow. 110 | config.profile_examples = 10 111 | 112 | # Run specs in random order to surface order dependencies. If you find an 113 | # order dependency and want to debug it, you can fix the order by providing 114 | # the seed, which is printed after each run. 115 | # --seed 1234 116 | config.order = :random 117 | 118 | # Seed global randomization in this process using the `--seed` CLI option. 119 | # Setting this allows you to use `--seed` to deterministically reproduce 120 | # test failures related to randomization by passing the same `--seed` value 121 | # as the one that triggered the failure. 122 | Kernel.srand config.seed 123 | =end 124 | config.before(:each) do 125 | config.include Capybara::DSL 126 | end 127 | 128 | config.before(:suite) do 129 | DatabaseCleaner.clean_with(:truncation) 130 | end 131 | 132 | config.before(:each) do 133 | DatabaseCleaner.strategy = :truncation 134 | end 135 | 136 | config.after(:each) do 137 | DatabaseCleaner.clean 138 | end 139 | 140 | end 141 | -------------------------------------------------------------------------------- /start-mock.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "Trying to start stop mock server ..." 3 | 4 | PID=`lsof -i:9292 | grep ruby | grep -v grep | awk '{print $2}'` 5 | 6 | if [[ -z "$PID" ]] 7 | then 8 | echo "No mock server running." 9 | else 10 | echo "Mock server is running with PID $PID. Will kill and restart." 11 | kill -9 $PID 12 | fi 13 | echo "Starting mock server now" 14 | sleep 2 15 | rackup > /dev/null 2>&1 & 16 | sleep 5 17 | 18 | PID=`lsof -i:9292 | grep ruby | grep -v grep | awk '{print $2}'` 19 | 20 | if [[ -z "$PID" ]] 21 | then 22 | echo "Failed to start mock server" 23 | exit 1 24 | else 25 | echo "Mock server started" 26 | fi 27 | -------------------------------------------------------------------------------- /stop-mock.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "Trying to stop mock server ..." 3 | 4 | PID=`lsof -i:9292 | grep ruby | grep -v grep | awk '{print $2}'` 5 | 6 | if [[ -z "$PID" ]] 7 | then 8 | echo "No mock server running." 9 | else 10 | echo "Mock server is running with PID $PID. Will kill now." 11 | kill -9 $PID 12 | fi 13 | 14 | sleep 3 15 | 16 | PID=`lsof -i:9292 | grep ruby | grep -v grep | awk '{print $2}'` 17 | if [[ -z "$PID" ]] 18 | then 19 | echo "Mock server stopped." 20 | else 21 | echo "Could not stop mock server." 22 | exit 1 23 | fi -------------------------------------------------------------------------------- /views/batch_clone.haml: -------------------------------------------------------------------------------- 1 | :javascript 2 | $('li').removeClass('active'); 3 | $('#tab-clone').addClass('active'); 4 | %div{class: 'page-header'} 5 | %h2 Mock Server - Clone multiple requests 6 | %blockquote 7 | %p{class: "lead text-info"} Supply Name, URL and Environment to clone into the mock server. This option is useful if you have a sequence of client URLs to hand that you want to clone and then modify response headers etc quickly via search and edit. 8 | 9 | %form{action: '/mock/clone/batch', method: 'post', id: 'form_clone_batch', class: 'form-inline'} 10 | %div{class: 'row col-md-12', id: 'row_1'} 11 | %div{class: 'form-group'} 12 | %label{class: 'control-label', for: 'mock_name_1'} Mock Request Name 13 | %input{type: 'text', class: 'form-control', name: 'mock_name_1', placeholder: 'Mock name'} 14 | %div{class: 'form-group'} 15 | %label{class: 'control-label', for: 'mock_url_1'} Mock URL 16 | %input{type: 'url', class: 'form-control', name: 'mock_url_1', placeholder: 'Mock URL'} 17 | %div{class: 'form-group'} 18 | %label{class: 'control-label',for: 'mock_environment_1'} Test Env 19 | %select{class: 'form-control',name: 'mock_environment_1'} 20 | %option{id: 'production'} production 21 | %option{id: 'integration'} integration 22 | %option{id: 'quality'} quality 23 | %button{class: "btn btn-primary", type: 'button', id: 'more_button_1', onclick: 'addMoreRow()'} + 24 | %div{class: 'form-group'} 25 | %label{class: 'control-label', for: 'status_row_1', id: 'status_row_1'} Pending 26 | %br 27 | %br 28 | %button(type="button" class="btn btn-primary btn-lg btn-block" onclick="cloneRowOnSubmit()" )Clone Data -------------------------------------------------------------------------------- /views/create_mock_request.haml: -------------------------------------------------------------------------------- 1 | %div{class: 'page-header'} 2 | %h2 Mock Server - Create/Update mock data 3 | %blockquote 4 | %p{class: "lead text-info"} Create mock responses by specifying the request URL's, response headers, status code and response body ! If you have an endpoint already active then just type in the URL and GET the URL data to automatically populate the status code, headers and the response body. 5 | 6 | %form{action: '/mock/create', method: 'post', id: 'form_form'} 7 | %div{class: 'row'} 8 | %div{class: 'form-group col-md-6'} 9 | %label{class: 'control-label', for: 'mock_name'} Mock Request Name 10 | %input{type: 'text', class: 'form-control', id: 'mock_name', name: 'mock_name', placeholder: 'Mock request name is required', value: "#{mock_data.nil? ? nil : mock_data.mock_name }", autocomplete: 'off'} 11 | %label{class: 'control-label'} 12 | %div{class: 'form-group col-md-3'} 13 | %label{class: 'control-label',for: 'mock_http_status'} Response HTTP Status Code 14 | %input{type: 'text', class: 'form-control', id: 'mock_http_status', name: 'mock_http_status', placeholder: '1xx, 2xx, 3xx, 4xx or 5xx', value: "#{mock_data.nil? ? nil : mock_data.mock_http_status }"} 15 | %div{class: 'form-group col-md-3'} 16 | %label{class: 'control-label',for: 'mock_state'} Mock State 17 | %div{class: 'checkbox'} 18 | %label 19 | %input{type: 'checkbox', id: 'chk_mock_state',name: 'mock_state', value: "#{mock_data.nil? ? nil : (mock_data.mock_state == true ? '1' : '0') }"} 20 | Enable/Disable Mock Response 21 | %div{class: 'row'} 22 | %div{class: 'form-group'} 23 | %div{class: 'col-md-8'} 24 | %label{class: 'control-label', for: 'mock_request_url'} Request URL To mock 25 | %input{type: 'text', class: 'form-control',id: 'mock_request_url', name: 'mock_request_url', placeholder: 'Request URL is required', onchange: 'urlChanged()', value: "#{mock_data.nil? ? nil : mock_data.mock_request_url }", autocomplete: 'off'} 26 | %div{class: 'col-md-2'} 27 | %label{class: 'control-label'} Clone request Hdrs 28 | %br 29 | %button{type: 'button', id: 'clone_headers_button' ,class: 'btn btn-primary', onclick: "showCloneHeaders()"} Supply Clone headers 30 | %div{class: 'col-md-2'} 31 | %label{class: 'control-label'} Clone existing data 32 | %br 33 | %button{type: 'button', id: 'get_button' ,class: 'btn btn-primary', onclick: "getCloningData()"} GET 34 | %div{class: 'row hidden' ,id: 'clone_headers_div'} 35 | %div{class: 'form-group col-md-3'} 36 | %label{class: 'control-label',for: 'clone_headers'} Clone request Header(s) delimited by #{ENV['HEADER_DELIMITER']} 37 | %textarea{class: 'form-control', id: 'clone_headers', name: 'clone_headers', rows: 4, placeholder: 'x-header :==: header_value (one in each line)'} 38 | %div{class: 'row'} 39 | %div{class: 'form-group col-md-2'} 40 | %label{class: 'control-label',for: 'mock_http_verb'} HTTP Verb 41 | %select{class: 'form-control', id: 'sl_mock_http_verb',name: 'mock_http_verb', value: "#{mock_data.nil? ? nil : mock_data.mock_http_verb }"} 42 | %option{id: 'GET'} GET 43 | %option{id: 'POST'} POST 44 | %option{id: 'PUT'} PUT 45 | %option{id: 'DELETE'} DELETE 46 | %option{id: 'OPTIONS'} OPTIONS 47 | %div{class: 'form-group col-md-2'} 48 | %label{class: 'control-label',for: 'mock_environment'} Test Environment 49 | %select{class: 'form-control', id: 'sl_mock_environment',name: 'mock_environment', value: "#{mock_data.nil? ? nil : mock_data.mock_environment }"} 50 | %option{id: 'production'} production 51 | %option{id: 'integration'} integration 52 | %option{id: 'quality'} quality 53 | %div{class: 'form-group col-md-2'} 54 | %label{class: 'control-label',for: 'mock_content_type'} Response Content Type 55 | %select{class: 'form-control', id: 'id_mock_content_type',name: 'mock_content_type', value: "#{mock_data.nil? ? nil : mock_data.mock_content_type }"} 56 | %option{id: 'app_json'} application/json;charset=UTF-8 57 | %option{id: 'text_xml'} text/xml 58 | %option{id: 'text_plain'} text/plain 59 | %option{id: 'text_html'} text/html 60 | %option{id: 'image_png'} image/png 61 | %option{id: 'image_jpeg'} image/jpeg 62 | %div{class: 'form-group col-md-6'} 63 | %label{class: 'control-label',for: 'mock_data_response_headers'} Other Delimited (#{ENV['HEADER_DELIMITER']})Response Header(s) 64 | %textarea{class: 'form-control', id: 'mock_data_response_headers',name: 'mock_data_response_headers', rows: 8}#{mock_data.nil? ? 'X-mocked'+ENV['HEADER_DELIMITER']+'True' + "\r\n"+'Cache-Control'+ENV['HEADER_DELIMITER']+'max-age=0, no-cache' : mock_data.mock_data_response_headers} 65 | %div{class: 'row'} 66 | %div{class: 'form-group col-md-12'} 67 | %button{type: 'button', id: 'advanced_button' ,class: 'btn btn-primary', onclick: 'showHideAdvancedOptions()'} Show Advanced Options 68 | %div{class: 'hidden', id: 'advanced_options'} 69 | %div{class: 'form-group col-md-2'} 70 | %label{class: 'control-label',for: 'has_before_script'} Before Script Status 71 | %div{class: 'checkbox'} 72 | %label 73 | %input{type: 'checkbox', id: 'chk_has_before_script',name: 'has_before_script', value: "#{mock_data.nil? ? nil : (mock_data.has_before_script == true ? '1' : '0') }"} 74 | Enable/Disable Before Script 75 | %div{class: 'form-group col-md-4'} 76 | %label{class: 'control-label', for: 'before_script_name'} @Before Script Name(s) delimited by comma 77 | %input{type: 'text', class: 'form-control',id: 'before_script_name', name: 'before_script_name', placeholder: 'script1.rb[, script2.rb]', value: "#{mock_data.nil? ? nil : mock_data.before_script_name }"} 78 | %div{class: 'form-group col-md-2'} 79 | %label{class: 'control-label',for: 'has_after_script'} After Script Status 80 | %div{class: 'checkbox'} 81 | %label 82 | %input{type: 'checkbox', id: 'chk_has_after_script',name: 'has_after_script', value: "#{mock_data.nil? ? nil : (mock_data.has_after_script == true ? '1' : '0') }"} 83 | Enable/Disable After Script 84 | %div{class: 'form-group col-md-4'} 85 | %label{class: 'control-label', for: 'after_script_name'} @After Script Name(s) delimited by comma 86 | %input{type: 'text', class: 'form-control',id: 'after_script_name', name: 'after_script_name', placeholder: 'script3.rb[, script4.rb]', value: "#{mock_data.nil? ? nil : mock_data.after_script_name }"} 87 | %div{class: 'form-group col-md-6'} 88 | %label{class: 'control-label',for: 'mock_cookie'} Cookie Data 89 | %textarea{class: 'form-control', id: 'mock_cookie', name: 'mock_cookie', rows: 4,placeholder: " (one in each line. Name followed by space.)"}#{mock_data.nil? ? '' : mock_data.mock_cookie} 90 | 91 | %div{class: 'row'} 92 | %div{class: 'form-group col-md-12'} 93 | %label{class: 'control-label',for: 'mock_data_response'} Response Body 94 | %textarea{id: 'json_body', class: 'form-control', name: 'mock_data_response', placeholder: 'Response Body', rows: 30, onchange: 'validateJsonBody(this)'}#{mock_data.nil? ? nil : h(mock_data.mock_data_response) } 95 | %div{class: 'row hidden'} 96 | %input{type: 'text', name: 'id', value: "#{mock_data.nil? ? nil : (mock_data.id) }"} 97 | %button(type="submit" class="btn btn-primary btn-lg btn-block")Create/Update Mock Data 98 | 99 | :javascript 100 | var mockStatuscheckbox = document.getElementById("chk_mock_state"); 101 | if (mockStatuscheckbox.value == '1') { 102 | document.getElementById('chk_mock_state').checked = true; 103 | } 104 | 105 | var mockBefScriptcheckbox = document.getElementById("chk_has_before_script"); 106 | if (mockBefScriptcheckbox.value == '1') { 107 | document.getElementById('chk_has_before_script').checked = true; 108 | } 109 | 110 | var mockAftScriptcheckbox = document.getElementById("chk_has_after_script"); 111 | if (mockAftScriptcheckbox.value == '1') { 112 | document.getElementById('chk_has_after_script').checked = true; 113 | } 114 | var slVerb = document.getElementById('sl_mock_http_verb').getAttribute('value'); 115 | var slVerbOption = document.getElementById(slVerb); 116 | slVerbOption.setAttribute("selected", "selected"); 117 | 118 | var slEnv = document.getElementById('sl_mock_environment').getAttribute('value'); 119 | var slOption = document.getElementById(slEnv); 120 | slOption.setAttribute("selected", "selected"); 121 | 122 | var slContentType = document.getElementById('id_mock_content_type').getAttribute('value'); 123 | var slcType = '' 124 | switch(slContentType) { 125 | case 'application/json;charset=UTF-8': 126 | slcType = document.getElementById('app_json'); 127 | break; 128 | case 'text/xml': 129 | slcType = document.getElementById('text_xml'); 130 | break; 131 | case 'text/plain': 132 | slcType = document.getElementById('text_plain'); 133 | break; 134 | case 'text/html': 135 | slcType = document.getElementById('text_html'); 136 | break; 137 | case 'image/png': 138 | slcType = document.getElementById('image_png'); 139 | break; 140 | case 'image/jpeg': 141 | slcType = document.getElementById('image_jpeg'); 142 | break; 143 | } 144 | slcType.setAttribute("selected", "selected"); 145 | -------------------------------------------------------------------------------- /views/create_mock_response.haml: -------------------------------------------------------------------------------- 1 | :javascript 2 | $('li').removeClass('active'); 3 | $('#tab-home').addClass('active'); 4 | - if messages 5 | %h3{class: 'text-center bg-danger'} #{messages} 6 | - else 7 | %h3{class: 'text-center bg-success'} Mock Data - #{mock_record_state == :created ? 'CREATED' : 'UPDATED'} 8 | %dl{class: 'dl-horizontal'} 9 | %dt Mock Name 10 | %dd #{mock_name} 11 | %dt Mock URL 12 | %dd #{mock_request_url} 13 | %dt HTTP Verb 14 | %dd #{mock_http_verb} 15 | %dt Mock Environment 16 | %dd #{mock_environment} 17 | %dt Mock Content Type 18 | %dd #{mock_content_type} 19 | %dt Mock HTTP Status 20 | %dd #{mock_http_status} 21 | %dt Mock Response Headers 22 | %dd #{mock_data_response_headers} 23 | %dt Mock State 24 | %dd 25 | - if mock_state 26 | ON 27 | - else 28 | OFF 29 | -if params[:mock_content_type] == 'application/json;charset=UTF-8' && json_state == :invalid 30 | %h3{class: "alert alert-warning" , role: "alert"} Response Body is not a Valid JSON. Please Check mock response body. -------------------------------------------------------------------------------- /views/create_ruby_script.haml: -------------------------------------------------------------------------------- 1 | :javascript 2 | $('li').removeClass('active'); 3 | $('#tab-ruby-script').addClass('active'); 4 | %div{class: 'page-header'} 5 | %h2 Mock Server - Create/Update Ruby Script 6 | %blockquote 7 | %p{class: "lead text-info"} Scripts can be used to process the incoming requests. The scripts are run just Before the request is received at the mock server or run After the default request processing has been done. Script names need to be valid ruby script_names ending in .rb . 8 | 9 | %div{class: "bg-success text text-center"} 10 | %strong #{success_message.nil? ? '' : success_message} 11 | 12 | %form{action: '/mock/create/script', method: 'post'} 13 | %div{class: 'row'} 14 | %div{class: 'form-group col-md-3'} 15 | %label{class: 'control-label',for: 'script_name'} Script name 16 | %input{type: 'text', class: 'form-control', name: 'script_name', placeholder: 'Script name without spaces', value: "#{rubyscript.nil? ? nil : rubyscript.script_name }"} 17 | 18 | %div{class: 'row'} 19 | %div{class: 'form-group col-md-12'} 20 | %label{class: 'control-label',for: 'script_body'} Ruby Script 21 | %textarea{id: 'script_body', class: 'form-control', name: 'script_body', placeholder: 'Ruby Script', rows: 30}#{rubyscript.nil? ? nil : rubyscript.script_body } 22 | %div{class: 'row hidden'} 23 | %input{type: 'text', name: 'id', value: "#{rubyscript.nil? ? nil : (rubyscript.id) }"} 24 | %div{class: 'row col-md-3'} 25 | %button(type="submit" class="btn btn-primary btn-lg btn-block")Create/Update Script -------------------------------------------------------------------------------- /views/create_update_replace_data.haml: -------------------------------------------------------------------------------- 1 | :javascript 2 | $('li').removeClass('active'); 3 | $('#tab-replace').addClass('active'); 4 | %div{class: 'page-header'} 5 | %h2 Mock Server - Create/Update replace Data 6 | %blockquote 7 | %p{class: "lead text-info"} Replace matching strings in the mock response silently. Search and edit the 'match' strings. When the mock server responds to a mock request it will silently 'replace any matching strings' in the response with the replacement string. 8 | 9 | %form{action: '/mock/replace/create_update', method: 'post', id: 'form_replace'} 10 | %div{class: 'row'} 11 | %div{class: 'form-group col-md-6'} 12 | %label{class: 'control-label', for: 'replace_name'} Replace Name 13 | %input{type: 'text', class: 'form-control', name: 'replace_name', placeholder: 'Replace name is required', value: "#{replace_data.nil? ? nil : replace_data.replace_name }", autocomplete: 'off'} 14 | %label{class: 'control-label'} 15 | 16 | %div{class: 'form-group col-md-6'} 17 | %label{class: 'control-label', for: 'replaced_string'} Text to be replaced 18 | %input{type: 'text', class: 'form-control', name: 'replaced_string', placeholder: 'Text required', value: "#{replace_data.nil? ? nil : replace_data.replaced_string }", autocomplete: 'off'} 19 | %label{class: 'control-label'} 20 | 21 | %div{class: 'form-group col-md-6'} 22 | %label{class: 'control-label', for: 'replacing_string'} Replaced text 23 | %input{type: 'text', class: 'form-control', name: 'replacing_string', placeholder: 'Text required', value: "#{replace_data.nil? ? nil : replace_data.replacing_string }", autocomplete: 'off'} 24 | %label{class: 'control-label'} 25 | 26 | %div{class: 'form-group col-md-3'} 27 | %label{class: 'control-label',for: 'replace_state'} Enable 28 | %div{class: 'checkbox'} 29 | %label 30 | %input{type: 'checkbox', id: 'chk_replace_state',name: 'replace_state', value: "#{replace_data.nil? ? nil : (replace_data.replace_state == true ? '1' : '0') }"} 31 | Enable/Disable 32 | %div{class: 'form-group col-md-3'} 33 | %label{class: 'control-label',for: 'is_regexp'} Regular Expression 34 | %div{class: 'checkbox'} 35 | %label 36 | %input{type: 'checkbox', id: 'chk_is_regexp',name: 'is_regexp', value: "#{replace_data.nil? ? nil : (replace_data.is_regexp == true ? '1' : '0') }"} 37 | Regexp On/Off 38 | %div{class: 'row'} 39 | %div{class: 'form-group col-md-3'} 40 | %label{class: 'control-label',for: 'mock_environment'} Test Environment 41 | %select{class: 'form-control', id: 'sl_mock_environment',name: 'mock_environment', value: "#{replace_data.nil? ? nil : replace_data.mock_environment }"} 42 | %option{id: 'integration'} integration 43 | %option{id: 'production'} production 44 | %option{id: 'quality'} quality 45 | %div{class: 'row hidden'} 46 | %input{type: 'text', name: 'id', value: "#{replace_data.nil? ? nil : (replace_data.id) }"} 47 | %button(type="submit" class="btn btn-primary btn-lg btn-block")Create/Update replace Data 48 | 49 | :javascript 50 | var replaceEnabled = document.getElementById("chk_replace_state"); 51 | var replaceRegexp = document.getElementById("chk_is_regexp"); 52 | if (replaceEnabled.value == '1') { 53 | document.getElementById('chk_replace_state').checked = true; 54 | } 55 | if (replaceRegexp.value == '1') { 56 | document.getElementById('chk_is_regexp').checked = true; 57 | } 58 | 59 | var slEnv = document.getElementById('sl_mock_environment').getAttribute('value'); 60 | var slOption = document.getElementById(slEnv); 61 | slOption.setAttribute("selected", "selected"); -------------------------------------------------------------------------------- /views/create_update_replace_data_ack.haml: -------------------------------------------------------------------------------- 1 | :javascript 2 | $('li').removeClass('active'); 3 | $('#tab-home').addClass('active'); 4 | - if messages 5 | %h3{class: 'text-center bg-danger'} #{messages} 6 | - else 7 | %h3{class: 'text-center bg-success'} Replace Data - #{replace_record_state == :created ? 'CREATED' : 'UPDATED'} 8 | %dl{class: 'dl-horizontal'} 9 | %dt Replace Name 10 | %dd #{replace_data.replace_name} 11 | %dt Replaced String 12 | %dd #{replace_data.replaced_string} 13 | %dt Replacing String 14 | %dd #{replace_data.replacing_string} 15 | %dt Environment 16 | %dd #{replace_data.mock_environment} 17 | %dt Regular Expression 18 | %dd 19 | - if replace_data.is_regexp 20 | ON 21 | - else 22 | OFF -------------------------------------------------------------------------------- /views/http_request_logs.haml: -------------------------------------------------------------------------------- 1 | %script{src: 'http://ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular.min.js'} 2 | %script{src: '/js/liveLogs.js'} 3 | :javascript 4 | $('li').removeClass('active'); 5 | $('#tab-liveLogs').addClass('active'); 6 | 7 | 8 | %div{class: 'page-header'} 9 | %h2 Mock Server - Live requests 10 | 11 | %div{"ng-app" => 'liveLogs', "ng-controller" => 'liveLogsController as main'} 12 | %p{class: 'bg-info text-center'} 13 | {{ main.message }} 14 | 15 | %form{class: 'form-inline'} 16 | %div{class: 'form-group'} 17 | %label{for: 'refreshTextBox'} Refresh Interval (min 5 sec). 18 | %input{type: "text", class: "form-control", placeholder: "5" , "ng-model"=> "main.refreshInterval", "ng-blur"=> "main.setRefreshInterval()", value:"{{ main.refreshInterval}}" } 19 | %input{class: "btn btn-primary" , type: "button", value: "Refresh", "ng-click" => "main.setRefreshInterval()"} 20 | %input{name: 'stop_refresh', class: "btn btn-primary stop_refresh" , type: "button", value: "Pause", "ng-click" => "main.stopLogs()", id: 'log_refresh_start_stop', onclick: 'startStopLogRefresh()'} 21 | 22 | %br 23 | %table{class: "table table-striped table-bordered "} 24 | %thead 25 | %tr 26 | %td # 27 | %td Method 28 | %td URL 29 | %td Query String 30 | %td Body 31 | %td Time 32 | %tbody 33 | %tr{id: 'log_rows', "ng-repeat" => "log in main.liveLogs"} 34 | %td 35 | %a{href: "/mock/livelogs/detail/{{ log.id }}"} 36 | {{ log.serial_number }} 37 | %td 38 | {{ log.request_http_verb }} 39 | %td 40 | {{ log.request_url }} 41 | %td 42 | {{ log.request_query_string }} 43 | %td 44 | {{ log.request_body }} 45 | %td 46 | {{ log.created_at }} -------------------------------------------------------------------------------- /views/layout.haml: -------------------------------------------------------------------------------- 1 | !!! 5 2 | %html 3 | %head 4 | %title= @title + '-' + ENV['TEST_ENV'] 5 | %meta{name: "viewport" ,content: "width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"} 6 | %link{ :href => "/bootstrap/css/bootstrap.min.css", :type => "text/css", :rel => "stylesheet" } 7 | %link{ :href => "/css/common.css", :type => "text/css", :rel => "stylesheet" } 8 | %script{src: '/js/common.js'} 9 | %script{src: 'https://code.jquery.com/jquery-2.2.1.min.js'} 10 | %script{src: '/bootstrap/js/bootstrap.js'} 11 | %script{src: 'https://code.jquery.com/ui/1.11.4/jquery-ui.min.js'} 12 | %script{src: '/js/jquery.caret.js'} 13 | %script{src: '/js/jquery.validatejson.js'} 14 | 15 | %body{onload: ' urlChanged()'} 16 | %div{class: 'container-fluid'} 17 | %ul{class: "nav nav-tabs"} 18 | %li{role: 'presentation', id: 'tab-home', class: 'active'} 19 | %a{href: '/mock/create'} Home 20 | %li{role: 'presentation', id: 'tab-env'} 21 | %a{href: '/environment'} Mock Env 22 | %li{role: 'presentation', id: 'tab-search'} 23 | %a{href: '/mock/search'} Search 24 | %li{role: 'presentation', id: 'tab-clone'} 25 | %a{href: '/mock/clone/batch'} Clone Many 26 | %li{role: 'presentation', id: 'tab-misses'} 27 | %a{href: '/mock/search/misses'} Misses 28 | %li{role: 'presentation', id: 'tab-replace'} 29 | %a{href: '/mock/replace/create_update'} Replace 30 | %li{role: 'presentation', id: 'tab-replace-search'} 31 | %a{href: '/mock/replace/search'} Replace Search 32 | %li{role: 'presentation', id: 'tab-upload-image'} 33 | %a{href: '/mock/upload/image'} Upload Image 34 | %li{role: 'presentation', id: 'tab-ruby-script'} 35 | %a{href: '/mock/create/script'} Script 36 | %li{role: 'presentation', id: 'tab-script-search'} 37 | %a{href: '/mock/script/search'} Search Script 38 | %li{role: 'presentation', id: 'tab-liveLogs'} 39 | %a{href: '/mock/livelogs'} Live Requests 40 | %li{role: 'presentation', id: 'tab-bird'} 41 | %a{href: '/mock/bird'} Design 42 | - if session.has_key? :errors 43 | %div{class: "alert alert-danger alert-dismissible" , role: "alert"} 44 | - flash_messages.each do |field, error_msg| 45 | %li 46 | %strong #{field.to_s.gsub('_',' ')} 47 | #{error_msg} 48 | =yield -------------------------------------------------------------------------------- /views/missed_requests.haml: -------------------------------------------------------------------------------- 1 | :javascript 2 | $('li').removeClass('active'); 3 | $('#tab-misses').addClass('active'); 4 | %div{class: 'page-header'} 5 | %h2 Mock Server - Missed Requests 6 | -if missed_data.any? 7 | %table{class: "table table-striped"} 8 | %thead 9 | %tr 10 | %th # 11 | %th Mock Environment 12 | %th HTTP Request 13 | %th Mock URL 14 | %th Date 15 | %tbody 16 | - missed_data.each do |row| 17 | %tr 18 | %td 19 | %form{action: "/mock/misses/delete/#{row.id}", method: 'post', id: "missed_id_#{row.id}"} 20 | %span{class: "glyphicon glyphicon-trash"} 21 | %a{:onclick => "document.getElementById('missed_id_#{row.id}').submit();"} 22 | #{row.id} 23 | %td #{row.mock_environment} 24 | %td #{row.mock_http_verb} 25 | %td #{row.url} 26 | %td #{row.updated_at} 27 | -else 28 | %h2{class: 'bg-warning text-center'} No Missed requests so far. Well done ! 29 | -------------------------------------------------------------------------------- /views/mock_deleted_ack.haml: -------------------------------------------------------------------------------- 1 | - if success 2 | %h2{class: 'bg-success text-center'} #{message} 3 | - else 4 | %h2{class: 'bg-danger text-center'} #{message} -------------------------------------------------------------------------------- /views/mocking_bird.haml: -------------------------------------------------------------------------------- 1 | :javascript 2 | $('li').removeClass('active'); 3 | $('#tab-bird').addClass('active'); 4 | %div{class: 'page-header'} 5 | %h2 Mock Server - Design 6 | %blockquote 7 | %p{class: "lead text-info"} 8 | Just mock and relax 9 | %div{class: 'col-md-6 col-sm-6'} 10 | %img{src: '/img/mock_design.png', class: "img-rounded"} 11 | %br 12 | %span{class: "glyphicon glyphicon-copyright-mark"} Copyright mvemjsun 13 | -------------------------------------------------------------------------------- /views/release_train.haml: -------------------------------------------------------------------------------- 1 | %h1 2 | iOS Release train 3 | %row 4 | %div{class: 'col-lg-3 col-md-6'} 5 | %div{class: 'panel panel-primary'} 6 | %div{class: 'panel-heading'} 7 | %div{class: 'row'} 8 | %div{class: 'text-center'} 9 | %div.h1 RT1 10 | %div{class: 'panel-default'} 11 | %div{class: 'panel-body'} 12 | %div{class: 'list-group'} 13 | %a{href: '#', class: 'list-group-item'} 14 | %i{class: 'glyphicon glyphicon-import'} 15 | Sprint 16 | %span{class: 'pull-right'} 17 | XX/XX/YY - YY/YY/YY 18 | %a{href: '#', class: 'list-group-item'} 19 | %i{class: 'glyphicon glyphicon-export'} 20 | Regression 21 | %span{class: 'pull-right'} 22 | XX/XX/YY - YY/YY/YY 23 | %a{href: '#', class: 'list-group-item'} 24 | %i{class: 'glyphicon glyphicon-ok'} 25 | Certification 26 | %span{class: 'pull-right'} 27 | XX/XX/YY - YY/YY/YY 28 | %a{href: '#', class: 'list-group-item'} 29 | %i{class: 'glyphicon glyphicon-send'} 30 | Release 31 | %span{class: 'pull-right'} 32 | XX/XX/YY 33 | %div{class: 'col-lg-3 col-md-6'} 34 | %div{class: 'panel panel-success'} 35 | %div{class: 'panel-heading'} 36 | %div{class: 'row'} 37 | %div{class: 'text-center'} 38 | %div.h1 RT2 39 | %div{class: 'panel-default'} 40 | %div{class: 'panel-body'} 41 | %div{class: 'list-group'} 42 | %a{href: '#', class: 'list-group-item'} 43 | %i{class: 'glyphicon glyphicon-import'} 44 | Sprint 45 | %span{class: 'pull-right'} 46 | XX/XX/YY - YY/YY/YY 47 | %a{href: '#', class: 'list-group-item'} 48 | %i{class: 'glyphicon glyphicon-export'} 49 | Regression 50 | %span{class: 'pull-right'} 51 | XX/XX/YY - YY/YY/YY 52 | %a{href: '#', class: 'list-group-item'} 53 | %i{class: 'glyphicon glyphicon-ok'} 54 | Certification 55 | %span{class: 'pull-right'} 56 | XX/XX/YY - YY/YY/YY 57 | %a{href: '#', class: 'list-group-item'} 58 | %i{class: 'glyphicon glyphicon-send'} 59 | Release 60 | %span{class: 'pull-right'} 61 | XX/XX/YY 62 | %div{class: 'col-lg-3 col-md-6'} 63 | %div{class: 'panel panel-info'} 64 | %div{class: 'panel-heading'} 65 | %div{class: 'row'} 66 | %div{class: 'text-center'} 67 | %div.h1 RT3 68 | %div{class: 'panel-default'} 69 | %div{class: 'panel-body'} 70 | %div{class: 'list-group'} 71 | %a{href: '#', class: 'list-group-item'} 72 | %i{class: 'glyphicon glyphicon-import'} 73 | Sprint 74 | %span{class: 'pull-right'} 75 | XX/XX/YY - YY/YY/YY 76 | %a{href: '#', class: 'list-group-item'} 77 | %i{class: 'glyphicon glyphicon-export'} 78 | Regression 79 | %span{class: 'pull-right'} 80 | XX/XX/YY - YY/YY/YY 81 | %a{href: '#', class: 'list-group-item'} 82 | %i{class: 'glyphicon glyphicon-ok'} 83 | Certification 84 | %span{class: 'pull-right'} 85 | XX/XX/YY - YY/YY/YY 86 | %a{href: '#', class: 'list-group-item'} 87 | %i{class: 'glyphicon glyphicon-send'} 88 | Release 89 | %span{class: 'pull-right'} 90 | XX/XX/YY 91 | %div{class: 'col-lg-3 col-md-6'} 92 | %div{class: 'panel panel-warning'} 93 | %div{class: 'panel-heading'} 94 | %div{class: 'row'} 95 | %div{class: 'text-center'} 96 | %div.h1 RT4 97 | %div{class: 'panel-default'} 98 | %div{class: 'panel-body'} 99 | %div{class: 'list-group'} 100 | %a{href: '#', class: 'list-group-item'} 101 | %i{class: 'glyphicon glyphicon-import'} 102 | Sprint 103 | %span{class: 'pull-right'} 104 | XX/XX/YY - YY/YY/YY 105 | %a{href: '#', class: 'list-group-item'} 106 | %i{class: 'glyphicon glyphicon-export'} 107 | Regression 108 | %span{class: 'pull-right'} 109 | XX/XX/YY - YY/YY/YY 110 | %a{href: '#', class: 'list-group-item'} 111 | %i{class: 'glyphicon glyphicon-ok'} 112 | Certification 113 | %span{class: 'pull-right'} 114 | XX/XX/YY - YY/YY/YY 115 | %a{href: '#', class: 'list-group-item'} 116 | %i{class: 'glyphicon glyphicon-send'} 117 | Release 118 | %span{class: 'pull-right'} 119 | XX/XX/YY -------------------------------------------------------------------------------- /views/request_log_details.haml: -------------------------------------------------------------------------------- 1 | :javascript 2 | $('li').removeClass('active'); 3 | $('#tab-liveLogs').addClass('active'); 4 | %div{class: 'page-header'} 5 | %h2 Mock Server - Request Log Details 6 | %div{class: 'col-md-8'} 7 | %table{class: "table table-bordered"} 8 | %thead 9 | %tr 10 | %th Request Attribute 11 | %th Value 12 | %tbody 13 | %tr 14 | %td HTTP Verb 15 | %td{id: 'log_rows'} #{h(request_details.request_http_verb)} 16 | %tr 17 | %td HTTP URL 18 | %td{id: 'log_rows'} #{h(request_details.request_url)} 19 | %tr 20 | %td Request Body 21 | %td{class: 'pre_formatted', id: 'log_rows'} #{h(request_details.request_body)} 22 | %tr 23 | %td Headers 24 | %td{class: 'pre_formatted', id: 'log_rows'} #{(request_details.request_headers)} 25 | %tr 26 | %td Query 27 | %td{id: 'log_rows'} #{h(request_details.request_query_string)} -------------------------------------------------------------------------------- /views/search_mock.haml: -------------------------------------------------------------------------------- 1 | :javascript 2 | $('li').removeClass('active'); 3 | $('#tab-search').addClass('active'); 4 | %div{class: 'page-header'} 5 | %h2 Mock Server - Search 6 | %blockquote 7 | %p{class: "lead text-info"} Search an existing mock using name OR URL 8 | 9 | %form{action: '/mock/search/result', method: 'get'} 10 | 11 | %div{class: 'row'} 12 | %div{class: 'form-group col-md-3'} 13 | %label{class: 'control-label',for: 'search_mock_name'} Mock Name 14 | %input{class: 'form-control', id: 'search_mock_name',name: 'search_mock_name', placeholder: 'Enter Part of Mock Name', autocomplete: 'off'} 15 | %div{class: 'form-group col-md-1'} 16 | OR 17 | %div{class: 'form-group col-md-3'} 18 | %label{class: 'control-label',for: 'search_mock_request_url'} URL 19 | %input{class: 'form-control',id: 'search_mock_request_url', name: 'search_mock_request_url', placeholder: 'Enter Part of URL', autocomplete: 'off'} 20 | %div{class: 'row col-md-3'} 21 | %button(type="submit" class="btn btn-primary btn-lg btn-block")Search -------------------------------------------------------------------------------- /views/search_replace.haml: -------------------------------------------------------------------------------- 1 | :javascript 2 | $('li').removeClass('active'); 3 | $('#tab-replace-search').addClass('active'); 4 | %div{class: 'page-header'} 5 | %h2 Mock Server - Intelli Replace search 6 | %blockquote 7 | %p{class: "lead text-info"} Search replace strings 8 | 9 | %form{action: '/mock/replace/search/results', method: 'get'} 10 | 11 | %div{class: 'row'} 12 | %div{class: 'form-group col-md-3'} 13 | %label{class: 'control-label',for: 'replace_name'} Replace Name 14 | %input{class: 'form-control', name: 'replace_name', placeholder: 'Enter Part of Name'} 15 | %div{class: 'row col-md-3'} 16 | %button(type="submit" class="btn btn-primary btn-lg btn-block")Search 17 | %br 18 | %br 19 | 20 | -if search_data 21 | %table{class: "table table-striped"} 22 | %thead 23 | %tr 24 | %th # 25 | %th Replace Name 26 | %th Text to replace 27 | %th Replaced with 28 | %th Environment 29 | %th Status 30 | %th Regexp 31 | %tbody 32 | - search_data.each do |row| 33 | -if row.replace_state == true 34 | %tr{class: 'success'} 35 | %td 36 | %span{class: "glyphicon glyphicon-edit"} 37 | %a{:href => "/mock/replace/update/#{row.id}"} 38 | #{row.id} 39 | %td #{row.replace_name} 40 | %td #{row.replaced_string} 41 | %td #{row.replacing_string} 42 | %td #{row.mock_environment} 43 | %td #{row.replace_state == true ? 'ON' : 'OFF'} 44 | %td #{row.is_regexp == true ? 'YES' : 'NO'} 45 | -else 46 | %tr 47 | %td 48 | %span{class: "glyphicon glyphicon-edit"} 49 | %a{:href => "/mock/replace/update/#{row.id}"} 50 | #{row.id} 51 | %td #{row.replace_name} 52 | %td #{row.replaced_string} 53 | %td #{row.replacing_string} 54 | %td #{row.mock_environment} 55 | %td #{row.replace_state == true ? 'ON' : 'OFF'} 56 | %td #{row.is_regexp == true ? 'YES' : 'NO'} 57 | -else 58 | %h2{class: 'bg-warning text-center'} #{search_message if search_message} -------------------------------------------------------------------------------- /views/search_results.haml: -------------------------------------------------------------------------------- 1 | :javascript 2 | $('li').removeClass('active'); 3 | $('#tab-search').addClass('active'); 4 | %div{class: 'page-header'} 5 | %h2 Mock Server - Search Results 6 | -if search_data 7 | %table{class: "table table-striped"} 8 | %thead 9 | %tr 10 | %th # 11 | %th Mock Name 12 | %th Mock Environment 13 | %th HTTP Verb 14 | %th HTTP Status 15 | %th Mock URL 16 | %th Status 17 | %th Updated 18 | %th Served 19 | %tbody 20 | - search_data.each do |row| 21 | -if row.mock_state == true 22 | %tr{class: 'success'} 23 | %td 24 | %span{class: "glyphicon glyphicon-edit"} 25 | %a{:href => "/mock/update/#{row.id}"} 26 | #{row.id} 27 | %td #{row.mock_name} 28 | %td #{row.mock_environment} 29 | %td #{row.mock_http_verb} 30 | %td #{row.mock_http_status} 31 | %td #{row.mock_request_url.length > 100 ? row.mock_request_url[0..100]+'...' : row.mock_request_url} 32 | %td #{row.mock_state == true ? 'ON' : 'OFF'} 33 | %td #{row.updated_at} 34 | %td #{row.mock_served_times} 35 | -else 36 | %tr 37 | %td 38 | %span{class: "glyphicon glyphicon-edit"} 39 | %a{:href => "/mock/update/#{row.id}"} 40 | #{row.id} 41 | %td #{row.mock_name} 42 | %td #{row.mock_environment} 43 | %td #{row.mock_http_verb} 44 | %td #{row.mock_http_status} 45 | %td #{row.mock_request_url.length > 100 ? row.mock_request_url[0..100]+'...' : row.mock_request_url} 46 | %td #{row.mock_state == true ? 'ON' : 'OFF'} 47 | %td #{row.updated_at} 48 | %td #{row.mock_served_times} 49 | -else 50 | %h2{class: 'bg-warning text-center'} Search returned no results ! -------------------------------------------------------------------------------- /views/search_scripts.haml: -------------------------------------------------------------------------------- 1 | :javascript 2 | $('li').removeClass('active'); 3 | $('#tab-script-search').addClass('active'); 4 | %div{class: 'page-header'} 5 | %h2 Mock Server - Search scripts 6 | %blockquote 7 | %p{class: 'lead text-info'} Search ruby scripts 8 | 9 | %form{action: '/mock/script/search/results', method: 'get'} 10 | 11 | %div{class: 'row'} 12 | %div{class: 'form-group col-md-3'} 13 | %label{class: 'control-label',for: 'script_name'} Script Name 14 | %input{class: 'form-control', name: 'script_name', placeholder: 'Enter Part of Name'} 15 | %div{class: 'row col-md-3'} 16 | %button(type="submit" class="btn btn-primary btn-lg btn-block")Search 17 | %br 18 | %br 19 | 20 | -if search_data 21 | %table{class: 'table table-striped'} 22 | %thead 23 | %tr 24 | %th # 25 | %th Script Name 26 | %th Created At 27 | %th Updated At 28 | %tbody 29 | - search_data.each do |row| 30 | %tr 31 | %td 32 | %span{class: "glyphicon glyphicon-edit"} 33 | %a{:href => "/mock/script/update/#{row.id}"} 34 | #{row.id} 35 | %td #{row.script_name} 36 | %td #{row.created_at} 37 | %td #{row.updated_at} 38 | -else 39 | %h2{class: 'bg-warning text-center'} #{search_message if search_message} -------------------------------------------------------------------------------- /views/set_mock_environment.haml: -------------------------------------------------------------------------------- 1 | :javascript 2 | $('li').removeClass('active'); 3 | $('#tab-env').addClass('active'); 4 | %div{class: 'page-header'} 5 | %h2 Mock Server - Set Test environment 6 | %blockquote 7 | %p{class: "lead text-info"} Mock server can respond to mock request for a specific test environment 8 | 9 | %form{action: '/environment', method: 'post'} 10 | 11 | %div{class: 'row'} 12 | %div{class: 'form-group col-md-3'} 13 | %h5{class: "alert alert-info"} Current test environment is #{ENV['TEST_ENV']} 14 | %label{class: 'control-label',for: 'mock_environment'} Set test Environment 15 | %select{class: 'form-control', name: 'mock_environment'} 16 | %option production 17 | %option integration 18 | %option quality 19 | %div{class: 'row col-md-3'} 20 | %button(type="submit" class="btn btn-primary btn-lg btn-block")Set Environment -------------------------------------------------------------------------------- /views/test_environment_ack.haml: -------------------------------------------------------------------------------- 1 | - if success 2 | %h2{class: 'bg-success text-center'} #{message} 3 | - else 4 | %h2{class: 'bg-danger text-center'} #{message} -------------------------------------------------------------------------------- /views/upload_image.haml: -------------------------------------------------------------------------------- 1 | :javascript 2 | $('li').removeClass('active'); 3 | $('#tab-upload-image').addClass('active'); 4 | %div{class: 'page-header'} 5 | %h2 Mock Server - Upload Image 6 | %blockquote 7 | %p{class: "lead text-info"} Images files can be uploaded and then served back from /upload/<img_name> or via a mocked URL. 8 | 9 | %form{action: '/mock/upload/image', method: 'post', enctype: 'multipart/form-data'} 10 | 11 | %div{class: 'row'} 12 | %div{class: 'form-group col-md-6'} 13 | %label{class: 'control-label',for: 'image_file_name'} Upload Image File (Max size #{ENV['MAX_UPLOAD_SIZE']} bytes) 14 | %input{class: 'form-control', name: 'image_file_name', placeholder: 'Image File', type: 'file'} 15 | %br 16 | #{upload_status.nil? ? nil : upload_status } 17 | %div{class: 'row col-md-6'} 18 | %button(type="submit" class="btn btn-primary btn-lg btn-block") Upload --------------------------------------------------------------------------------