├── .gitignore ├── .npmignore ├── .nycrc ├── .prettierignore ├── .prettierrc ├── .travis.yml ├── CONTRIBUTING.md ├── HISTORY.md ├── LICENSE.txt ├── README.md ├── docs ├── DESIGN.md ├── README.md ├── classes │ ├── _context_.context.md │ ├── _errors_.yesnoerror.md │ ├── _filtering_collection_.filteredhttpcollection.md │ ├── _http_serializer_.requestserializer.md │ ├── _http_serializer_.responseserializer.md │ ├── _interceptor_.interceptor.md │ ├── _mock_response_.mockresponse.md │ ├── _recording_.recording.md │ └── _yesno_.yesno.md ├── enums │ └── _recording_.recordmode.md ├── interfaces │ ├── _context_.iinflightrequest.md │ ├── _context_.iredactprop.md │ ├── _context_.iresponseformatchingrequest.md │ ├── _file_.ifileoptions.md │ ├── _file_.ihttpmock.md │ ├── _file_.ipartialmockrequest.md │ ├── _file_.ipartialmockresponse.md │ ├── _file_.isavefile.md │ ├── _file_.isaveoptions.md │ ├── _filtering_collection_.ifiltered.md │ ├── _filtering_collection_.ifilteredhttpcollectionparams.md │ ├── _filtering_comparator_.icomparatormetadata.md │ ├── _filtering_matcher_.iserializedhttppartialdeepmatch.md │ ├── _filtering_matcher_.iserializedrequestresponsetomatch.md │ ├── _http_serializer_.clientrequestfull.md │ ├── _http_serializer_.icreaterecord.md │ ├── _http_serializer_.iheaders.md │ ├── _http_serializer_.iserializedhttp.md │ ├── _http_serializer_.iserializedrequest.md │ ├── _http_serializer_.iserializedrequestresponse.md │ ├── _http_serializer_.iserializedresponse.md │ ├── _interceptor_.clientrequesttracker.md │ ├── _interceptor_.iinterceptevent.md │ ├── _interceptor_.iinterceptevents.md │ ├── _interceptor_.iinterceptoptions.md │ ├── _interceptor_.iproxiedevent.md │ ├── _interceptor_.proxyrequestoptions.md │ ├── _interceptor_.registeredsocket.md │ ├── _recording_.irecordingoptions.md │ ├── _yesno_.irecordabletest.md │ └── _yesno_.iyesnointerceptingoptions.md └── modules │ ├── _consts_.md │ ├── _context_.md │ ├── _errors_.md │ ├── _file_.md │ ├── _filtering_collection_.md │ ├── _filtering_comparator_.md │ ├── _filtering_matcher_.md │ ├── _filtering_redact_.md │ ├── _http_serializer_.md │ ├── _index_.md │ ├── _interceptor_.md │ ├── _mock_response_.md │ ├── _recording_.md │ └── _yesno_.md ├── examples ├── mocks │ ├── recorded-tests-my-api-should-respond-401-for-an-invalid-token-yesno.json │ └── recorded-tests-my-api-should-respond-with-the-current-user-yesno.json ├── recorded-tests.spec.ts ├── spy-and-mock-tests.spec.ts └── src │ ├── api-client.ts │ └── api-server.ts ├── package.json ├── scripts └── cli.ts ├── src ├── @types │ ├── mitm │ │ ├── README.md │ │ ├── index.d.ts │ │ ├── package.json │ │ └── types-metadata.json │ └── readable-stream │ │ └── index.d.ts ├── consts.ts ├── context.ts ├── errors.ts ├── file.ts ├── filtering │ ├── collection.ts │ ├── comparator.ts │ ├── matcher.ts │ └── redact.ts ├── http-serializer.ts ├── index.ts ├── interceptor.ts ├── mock-response.ts ├── recording.ts └── yesno.ts ├── test ├── integration │ ├── mocks │ │ ├── mock-test-1-yesno.json │ │ └── mock-test-redact-yesno.json │ └── yesno.spec.ts ├── mocha.opts ├── setup.ts ├── test-client.ts ├── test-server.ts ├── tslint.json └── unit │ ├── context.spec.ts │ ├── file.spec.ts │ ├── filtering │ ├── collection.spec.ts │ ├── matcher.spec.ts │ ├── mocks │ │ └── two-requests.json │ └── redact.spec.ts │ ├── http-serializer.spec.ts │ ├── interceptor.spec.ts │ ├── mocks │ ├── mock-localhost-get-yesno.json │ ├── mock-test-yesno.json │ ├── mock-x-www-form-urlencoded.json │ └── recording-mock.json │ ├── recording.spec.ts │ └── yesno.spec.ts ├── tsconfig.json ├── tslint.json ├── yarn.lock └── yesno-Hero.png /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | dist/ 3 | 4 | test/integration/tmp 5 | test/unit/tmp 6 | .nyc_output/ 7 | .DS_Store 8 | node_modules 9 | package-lock.json 10 | coverage 11 | .gitprettierignore 12 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /* 2 | !/dist 3 | dist/**/*.map 4 | dist/test 5 | !LICENSE.txt 6 | !HISTORY.md 7 | !README.md 8 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "all": true, 3 | "branches": 75, 4 | "check-coverage": true, 5 | "extension": [".ts"], 6 | "exclude": ["**/*.d.ts"], 7 | "functions": 75, 8 | "include": ["src/**/*.ts"], 9 | "lines": 75, 10 | "per-file": false, 11 | "reporter": [ 12 | "html", 13 | "text", 14 | "text-summary" 15 | ], 16 | "require": [ 17 | "ts-node/register", 18 | "source-map-support/register" 19 | ], 20 | "statements": 75 21 | } 22 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | package.json 3 | .vscode 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true, 4 | "arrowParens": "always", 5 | "semi": true, 6 | "trailingComma": "all" 7 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "8" 5 | - "10" 6 | 7 | branches: 8 | only: 9 | - main 10 | 11 | install: 12 | # Fail if lockfile outdated. 13 | # https://yarnpkg.com/lang/en/docs/cli/install/#toc-yarn-install-frozen-lockfile 14 | - yarn install --frozen-lockfile 15 | 16 | script: 17 | - yarn --version 18 | - yarn run clean 19 | - yarn run compile 20 | - yarn run lint 21 | - yarn run tests 22 | # TODO: Add coverage 23 | # https://github.com/FormidableLabs/yesno/issues/5 24 | 25 | after_success: 26 | - yarn run codecov 27 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | Thanks for contributing! 5 | 6 | ## Development 7 | 8 | Install the project using `yarn` (which we've standardized on for development): 9 | 10 | ```sh 11 | $ yarn install 12 | ``` 13 | 14 | You can build and run everything with: 15 | 16 | ```sh 17 | $ yarn run compile 18 | $ yarn run check 19 | ``` 20 | 21 | If you are actively developing, you can run a watch in one terminal: 22 | 23 | ```sh 24 | $ yarn run watch 25 | ``` 26 | 27 | to build source files and then do whatever development you want in a separate terminal. 28 | 29 | ## Updating Generated typedoc 30 | 31 | To update the generated typedoc, execute: 32 | 33 | ```sh 34 | yarn run docs 35 | ``` 36 | 37 | ## Releasing a new version to NPM 38 | 39 | _Only for project administrators_. 40 | 41 | 1. Update `HISTORY.md`, following format for previous versions 42 | 2. Commit as "History for version NUMBER" 43 | 3. Run `npm version patch` (or `minor|major|VERSION`) to run tests and lint, regenerate docs, 44 | build published directories, then update `package.json` + add a git tag. 45 | 4. Run `npm publish` and publish to NPM if all is well. 46 | 5. Run `git push && git push --tags` 47 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | History 2 | ======= 3 | 4 | ## 0.0.7 5 | 6 | - Add missing docs 7 | - Allow intercepting only specified requests (#64) 8 | 9 | ## 0.0.6 10 | 11 | - Make the filter in `yesno.matching()` optional 12 | - Export GenericTest and Recording types 13 | - Improved test coverage and cross platform support 14 | - Fix bug where headers were not sent in response (#43) 15 | 16 | ## 0.0.5 17 | 18 | - Various bug fixes 19 | - Add "recording" api with `yesno.recording()` and `yesno.test()` 20 | - Add `collection.request()` and `collection.response()` 21 | - Add examples 22 | 23 | ## 0.0.4 24 | 25 | Publish with yarn 26 | 27 | ## 0.0.3 28 | 29 | Refine publish tooling 30 | 31 | [@ianwsperber]: https://github.com/ianwsperber 32 | [@ryan-roemer]: https://github.com/ryan-roemer 33 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Formidable Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /docs/DESIGN.md: -------------------------------------------------------------------------------- 1 | # YesNo Design 2 | 3 | This document is meant to describe the guiding principles and direction of yesno development. When adding new features or prioritizing the backlog, these ideas should help in decision making. 4 | 5 | ## Usage Types 6 | 7 | 1. `yesno.save, yesno.load` 8 | 1. `yesno.record, yesno.complete` 9 | 1. `yesno.test` 10 | 11 | Case 1 is the most basic and requires the most work by the user. It is intended as an escape hatch for users that have unique or specific needs that the default usage doesn't meet. Most users should be using cases 2 and 3 (where case 3 is just a convience testing wrapper around case 2). 12 | 13 | ### Differentiating from Nock 14 | 15 | Currently Nock can handle case 1 pretty well and we don't need to try to add value here. Nock does have nockback feature that can record and playback responses, but requires modification of the recorded response file to remove mocks. YesNo should focus on being easy to use for use cases 2 and 3 while allowing the flexibilty to handle most special cases through the rule system. 16 | 17 | ## mockRule Design 18 | 19 | A new mockRule method will be added to allow the user to define a set of rules that will be evaluated in the order that they are defined. This will let the user control how yesno will handle recording and responding. By default, yesno will record all network requests and mock them in the order in which they were recorded. Using mockRule, matching requests can be made live instead of mocked, or the mock response can be specified instead of using the recorded value. As soon as a rule matches a request, the rule is applied and processing stops. If the user wants the default action to be live requests, then they would just create a last rule that matches everything to be live. 20 | 21 | ## Depricated Features 22 | 23 | With the addition of mockRule and rule based processing of requests, the following legacy features will be depricated or changed. The deprecated features were confusing because they mixed request preflight behavior with intercepted response filtering. 24 | 25 | - yesno.matching() - matching can only be run after requests have been intercepted 26 | - yesno.matching().respond() - this is deprecated and replaced with yesno.mockRule().respond() 27 | - yesno.matching().ignore() - this is deprecated and replaced with yesno.mockRule().live() 28 | - yesno.matching().redact() - this redact call will only be applied to intercepted requests. To have redact apply to all requests, a mockRule.redact should be used. 29 | 30 | ## YesNo Interfaces 31 | 32 | - `yesno.mockRule` - set of user defined rules to be applied to all requests 33 | - `yesno.mockRule().record()` - matching requests will be recorded/mocked 34 | - `yesno.mockRule().live()` - matching requests will be proxied 35 | - `yesno.mockRule().respond()` - matching requests will be mocked with the defined response 36 | - `yesno.mockRule().redact()` - matching properties will be redacted 37 | - `yesno.matching()` - used after responses have been intercepted to filter the collection 38 | - `yesno.mockMode()` - allows the user to match mocks multiple times, defaults to `strict` (each recorded mock matches once) 39 | - `yesno.mockMatchingFunction()` - allows the user to override the default matching function, the callback gets passed: 40 | - the request data object 41 | - the collection of mocks 42 | - returns true on match 43 | 44 | ## Mock Matching logic 45 | 46 | ``` 47 | // strict mode (index based mocking, so no reusing mocks, no out of order mocks) 48 | yesno.mockMatchingFunction((currentRequestObj, listOfMocks, mockRuleArray, requestCounter) => { 49 | for each mock 50 | for each mockRule 51 | - does mockRule pattern match request? 52 | - if so apply rule 53 | - one of 54 | - live 55 | - record 56 | - use the i mock in the mock list (and basic check that it's matching) 57 | - respond 58 | - use an inline mock (can match multiple times) 59 | - increment i (let's not increment i going forward) 60 | - stop processing rule list 61 | 62 | Default is equivalent to yesno.mockMatchingFunction(yesno.mockMatching.STRICT) 63 | 64 | // matching mode (request signature based mocking, can reuse mocks, handles out of order mocks) 65 | yesno.mockMatchingFunction((currentRequestObj, listOfMocks, mockRuleArray, requestCounter) => { 66 | for each mock 67 | for each mockRule 68 | - does mockRule pattern match request? 69 | - if so apply rule 70 | - one of 71 | - live 72 | - record 73 | - for each mock 74 | - has mock already been used? 75 | - does mock have the same signature 76 | - if so 77 | - use mock 78 | - bump the "used" counter +1 79 | - respond 80 | - use an inline mock (can match multiple times) 81 | - increment i (let's not increment i going forward) 82 | - stop processing rule list 83 | 84 | ``` 85 | 86 | ## Example Use Cases 87 | 88 | Record and mock all requests (default use case) 89 | ``` 90 | const record = yesno.recording(filename); 91 | 92 | record.complete(); 93 | ``` 94 | 95 | Requests A, B and the order is non-deterministic 96 | ``` 97 | const record = yesno.recording(filename); 98 | 99 | record.complete(); 100 | ``` 101 | 102 | Requests A, B and only want to record/mock B 103 | ``` 104 | const record = yesno.recording(filename); 105 | yesno.mockRule(/B/).record(); 106 | yesno.mockRule(/.*/).live(); 107 | 108 | record.complete(); 109 | ``` 110 | 111 | Requests A, B, C... and only want B to be live 112 | ``` 113 | const record = yesno.recording(filename); 114 | yesno.mockRule(/B/).live(); 115 | 116 | record.complete(); 117 | ``` 118 | 119 | Requests A, B and want B to have mock value X 120 | ``` 121 | const record = yesno.recording(filename); 122 | yesno.mockRule(/B/).respond(X); 123 | 124 | record.complete(); 125 | ``` 126 | 127 | Redact `password` and `token` properties in requests to B 128 | ``` 129 | const record = yesno.recording(filename); 130 | yesno.mockRule(/B/).redact([password, token]); 131 | 132 | record.complete(); 133 | ``` 134 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | 2 | # yesno-http 3 | 4 | ## Index 5 | 6 | ### External modules 7 | 8 | * ["consts"](modules/_consts_.md) 9 | * ["context"](modules/_context_.md) 10 | * ["errors"](modules/_errors_.md) 11 | * ["file"](modules/_file_.md) 12 | * ["filtering/collection"](modules/_filtering_collection_.md) 13 | * ["filtering/comparator"](modules/_filtering_comparator_.md) 14 | * ["filtering/matcher"](modules/_filtering_matcher_.md) 15 | * ["filtering/redact"](modules/_filtering_redact_.md) 16 | * ["http-serializer"](modules/_http_serializer_.md) 17 | * ["index"](modules/_index_.md) 18 | * ["interceptor"](modules/_interceptor_.md) 19 | * ["mock-response"](modules/_mock_response_.md) 20 | * ["recording"](modules/_recording_.md) 21 | * ["yesno"](modules/_yesno_.md) 22 | 23 | --- 24 | 25 | -------------------------------------------------------------------------------- /docs/classes/_context_.context.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["context"](../modules/_context_.md) > [Context](../classes/_context_.context.md) 2 | 3 | # Class: Context 4 | 5 | Store the current execution context for YesNo by tracking requests & mocks. 6 | 7 | ## Hierarchy 8 | 9 | **Context** 10 | 11 | ## Index 12 | 13 | ### Properties 14 | 15 | * [autoRedact](_context_.context.md#autoredact) 16 | * [comparatorFn](_context_.context.md#comparatorfn) 17 | * [ignoresForMatchingRequests](_context_.context.md#ignoresformatchingrequests) 18 | * [inFlightRequests](_context_.context.md#inflightrequests) 19 | * [interceptedRequestsCompleted](_context_.context.md#interceptedrequestscompleted) 20 | * [loadedMocks](_context_.context.md#loadedmocks) 21 | * [mode](_context_.context.md#mode) 22 | * [responsesForMatchingRequests](_context_.context.md#responsesformatchingrequests) 23 | 24 | ### Methods 25 | 26 | * [addIgnoreForMatchingRequests](_context_.context.md#addignoreformatchingrequests) 27 | * [addResponseForMatchingRequests](_context_.context.md#addresponseformatchingrequests) 28 | * [clear](_context_.context.md#clear) 29 | * [getMatchingIntercepted](_context_.context.md#getmatchingintercepted) 30 | * [getMatchingMocks](_context_.context.md#getmatchingmocks) 31 | * [getResponseDefinedMatching](_context_.context.md#getresponsedefinedmatching) 32 | * [hasMatchingIgnore](_context_.context.md#hasmatchingignore) 33 | * [hasResponsesDefinedForMatchers](_context_.context.md#hasresponsesdefinedformatchers) 34 | 35 | --- 36 | 37 | ## Properties 38 | 39 | 40 | 41 | ### autoRedact 42 | 43 | **● autoRedact**: * [IRedactProp](../interfaces/_context_.iredactprop.md) | `null` 44 | * = null 45 | 46 | Setting to redact all incoming requests to match redacted mocks 47 | 48 | ___ 49 | 50 | 51 | ### comparatorFn 52 | 53 | **● comparatorFn**: *[ComparatorFn](../modules/_filtering_comparator_.md#comparatorfn)* = comparatorByUrl 54 | 55 | ___ 56 | 57 | 58 | ### ignoresForMatchingRequests 59 | 60 | **● ignoresForMatchingRequests**: *[IResponseForMatchingRequest](../interfaces/_context_.iresponseformatchingrequest.md)[]* = [] 61 | 62 | ___ 63 | 64 | 65 | ### inFlightRequests 66 | 67 | **● inFlightRequests**: *`Array`< [IInFlightRequest](../interfaces/_context_.iinflightrequest.md) | `null`>* = [] 68 | 69 | Proxied requests which have not yet responded. When completed the value is set to "null" but the index is preserved. 70 | 71 | ___ 72 | 73 | 74 | ### interceptedRequestsCompleted 75 | 76 | **● interceptedRequestsCompleted**: *[ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md)[]* = [] 77 | 78 | Completed serialized request-response objects. Used for: A. Assertions B. Saved to disk if in record mode 79 | 80 | ___ 81 | 82 | 83 | ### loadedMocks 84 | 85 | **● loadedMocks**: *[ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md)[]* = [] 86 | 87 | Serialized records loaded from disk. 88 | 89 | ___ 90 | 91 | 92 | ### mode 93 | 94 | **● mode**: *`Mode`* = Mode.Spy 95 | 96 | ___ 97 | 98 | 99 | ### responsesForMatchingRequests 100 | 101 | **● responsesForMatchingRequests**: *[IResponseForMatchingRequest](../interfaces/_context_.iresponseformatchingrequest.md)[]* = [] 102 | 103 | ___ 104 | 105 | ## Methods 106 | 107 | 108 | 109 | ### addIgnoreForMatchingRequests 110 | 111 | ▸ **addIgnoreForMatchingRequests**(matchingResponse: *[IResponseForMatchingRequest](../interfaces/_context_.iresponseformatchingrequest.md)*): `void` 112 | 113 | **Parameters:** 114 | 115 | | Name | Type | 116 | | ------ | ------ | 117 | | matchingResponse | [IResponseForMatchingRequest](../interfaces/_context_.iresponseformatchingrequest.md) | 118 | 119 | **Returns:** `void` 120 | 121 | ___ 122 | 123 | 124 | ### addResponseForMatchingRequests 125 | 126 | ▸ **addResponseForMatchingRequests**(matchingResponse: *[IResponseForMatchingRequest](../interfaces/_context_.iresponseformatchingrequest.md)*): `void` 127 | 128 | **Parameters:** 129 | 130 | | Name | Type | 131 | | ------ | ------ | 132 | | matchingResponse | [IResponseForMatchingRequest](../interfaces/_context_.iresponseformatchingrequest.md) | 133 | 134 | **Returns:** `void` 135 | 136 | ___ 137 | 138 | 139 | ### clear 140 | 141 | ▸ **clear**(): `void` 142 | 143 | **Returns:** `void` 144 | 145 | ___ 146 | 147 | 148 | ### getMatchingIntercepted 149 | 150 | ▸ **getMatchingIntercepted**(matcher: *[Matcher](../modules/_filtering_matcher_.md#matcher)*): [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md)[] 151 | 152 | **Parameters:** 153 | 154 | | Name | Type | 155 | | ------ | ------ | 156 | | matcher | [Matcher](../modules/_filtering_matcher_.md#matcher) | 157 | 158 | **Returns:** [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md)[] 159 | 160 | ___ 161 | 162 | 163 | ### getMatchingMocks 164 | 165 | ▸ **getMatchingMocks**(matcher: *[Matcher](../modules/_filtering_matcher_.md#matcher)*): [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md)[] 166 | 167 | **Parameters:** 168 | 169 | | Name | Type | 170 | | ------ | ------ | 171 | | matcher | [Matcher](../modules/_filtering_matcher_.md#matcher) | 172 | 173 | **Returns:** [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md)[] 174 | 175 | ___ 176 | 177 | 178 | ### getResponseDefinedMatching 179 | 180 | ▸ **getResponseDefinedMatching**(request: *[ISerializedRequest](../interfaces/_http_serializer_.iserializedrequest.md)*): [ISerializedResponse](../interfaces/_http_serializer_.iserializedresponse.md) | `undefined` 181 | 182 | **Parameters:** 183 | 184 | | Name | Type | 185 | | ------ | ------ | 186 | | request | [ISerializedRequest](../interfaces/_http_serializer_.iserializedrequest.md) | 187 | 188 | **Returns:** [ISerializedResponse](../interfaces/_http_serializer_.iserializedresponse.md) | `undefined` 189 | 190 | ___ 191 | 192 | 193 | ### hasMatchingIgnore 194 | 195 | ▸ **hasMatchingIgnore**(request: *[ISerializedRequest](../interfaces/_http_serializer_.iserializedrequest.md)*): `boolean` 196 | 197 | **Parameters:** 198 | 199 | | Name | Type | 200 | | ------ | ------ | 201 | | request | [ISerializedRequest](../interfaces/_http_serializer_.iserializedrequest.md) | 202 | 203 | **Returns:** `boolean` 204 | 205 | ___ 206 | 207 | 208 | ### hasResponsesDefinedForMatchers 209 | 210 | ▸ **hasResponsesDefinedForMatchers**(): `boolean` 211 | 212 | **Returns:** `boolean` 213 | 214 | ___ 215 | 216 | -------------------------------------------------------------------------------- /docs/classes/_errors_.yesnoerror.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["errors"](../modules/_errors_.md) > [YesNoError](../classes/_errors_.yesnoerror.md) 2 | 3 | # Class: YesNoError 4 | 5 | ## Hierarchy 6 | 7 | `Error` 8 | 9 | **↳ YesNoError** 10 | 11 | ## Index 12 | 13 | ### Constructors 14 | 15 | * [constructor](_errors_.yesnoerror.md#constructor) 16 | 17 | ### Properties 18 | 19 | * [message](_errors_.yesnoerror.md#message) 20 | * [name](_errors_.yesnoerror.md#name) 21 | * [stack](_errors_.yesnoerror.md#stack) 22 | * [Error](_errors_.yesnoerror.md#error) 23 | 24 | --- 25 | 26 | ## Constructors 27 | 28 | 29 | 30 | ### constructor 31 | 32 | ⊕ **new YesNoError**(message: *`string`*): [YesNoError](_errors_.yesnoerror.md) 33 | 34 | **Parameters:** 35 | 36 | | Name | Type | 37 | | ------ | ------ | 38 | | message | `string` | 39 | 40 | **Returns:** [YesNoError](_errors_.yesnoerror.md) 41 | 42 | ___ 43 | 44 | ## Properties 45 | 46 | 47 | 48 | ### message 49 | 50 | **● message**: *`string`* 51 | 52 | ___ 53 | 54 | 55 | ### name 56 | 57 | **● name**: *`string`* 58 | 59 | ___ 60 | 61 | 62 | ### `` stack 63 | 64 | **● stack**: * `undefined` | `string` 65 | * 66 | 67 | ___ 68 | 69 | 70 | ### `` Error 71 | 72 | **● Error**: *`ErrorConstructor`* 73 | 74 | ___ 75 | 76 | -------------------------------------------------------------------------------- /docs/classes/_filtering_collection_.filteredhttpcollection.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["filtering/collection"](../modules/_filtering_collection_.md) > [FilteredHttpCollection](../classes/_filtering_collection_.filteredhttpcollection.md) 2 | 3 | # Class: FilteredHttpCollection 4 | 5 | Represents a collection of HTTP requests which match the provided filter. 6 | 7 | Can filter both intercepted HTTP requests and loaded mocks. 8 | 9 | ## Hierarchy 10 | 11 | **FilteredHttpCollection** 12 | 13 | ## Implements 14 | 15 | * [IFiltered](../interfaces/_filtering_collection_.ifiltered.md) 16 | 17 | ## Index 18 | 19 | ### Constructors 20 | 21 | * [constructor](_filtering_collection_.filteredhttpcollection.md#constructor) 22 | 23 | ### Properties 24 | 25 | * [ctx](_filtering_collection_.filteredhttpcollection.md#ctx) 26 | * [matcher](_filtering_collection_.filteredhttpcollection.md#matcher) 27 | 28 | ### Methods 29 | 30 | * [ignore](_filtering_collection_.filteredhttpcollection.md#ignore) 31 | * [intercepted](_filtering_collection_.filteredhttpcollection.md#intercepted) 32 | * [mocks](_filtering_collection_.filteredhttpcollection.md#mocks) 33 | * [only](_filtering_collection_.filteredhttpcollection.md#only) 34 | * [redact](_filtering_collection_.filteredhttpcollection.md#redact) 35 | * [request](_filtering_collection_.filteredhttpcollection.md#request) 36 | * [respond](_filtering_collection_.filteredhttpcollection.md#respond) 37 | * [response](_filtering_collection_.filteredhttpcollection.md#response) 38 | 39 | --- 40 | 41 | ## Constructors 42 | 43 | 44 | 45 | ### constructor 46 | 47 | ⊕ **new FilteredHttpCollection**(__namedParameters: *`object`*): [FilteredHttpCollection](_filtering_collection_.filteredhttpcollection.md) 48 | 49 | **Parameters:** 50 | 51 | **__namedParameters: `object`** 52 | 53 | | Name | Type | 54 | | ------ | ------ | 55 | | context | [Context](_context_.context.md) | 56 | | matcher | `function` | [ISerializedHttpPartialDeepMatch](../interfaces/_filtering_matcher_.iserializedhttppartialdeepmatch.md)| 57 | 58 | **Returns:** [FilteredHttpCollection](_filtering_collection_.filteredhttpcollection.md) 59 | 60 | ___ 61 | 62 | ## Properties 63 | 64 | 65 | 66 | ### `` ctx 67 | 68 | **● ctx**: *[Context](_context_.context.md)* 69 | 70 | ___ 71 | 72 | 73 | ### `` matcher 74 | 75 | **● matcher**: * [ISerializedHttpPartialDeepMatch](../interfaces/_filtering_matcher_.iserializedhttppartialdeepmatch.md) | [MatchFn](../modules/_filtering_matcher_.md#matchfn) 76 | * 77 | 78 | ___ 79 | 80 | ## Methods 81 | 82 | 83 | 84 | ### ignore 85 | 86 | ▸ **ignore**(): `void` 87 | 88 | Ignore a mock for all matching requests. 89 | 90 | Matching requests defined here take _precedence_ over all mocks and will be proxied. 91 | 92 | **Returns:** `void` 93 | 94 | ___ 95 | 96 | 97 | ### intercepted 98 | 99 | ▸ **intercepted**(): [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md)[] 100 | 101 | Return all intercepted requests matching the current filter 102 | 103 | **Returns:** [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md)[] 104 | 105 | ___ 106 | 107 | 108 | ### mocks 109 | 110 | ▸ **mocks**(): [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md)[] 111 | 112 | Return all intercepted mocks matching the current filter 113 | 114 | **Returns:** [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md)[] 115 | 116 | ___ 117 | 118 | 119 | ### `` only 120 | 121 | ▸ **only**(): [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md) 122 | 123 | If the current filter only matches a single request, then return the single matching instance. Otherwise, throw an error. 124 | 125 | **Returns:** [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md) 126 | 127 | ___ 128 | 129 | 130 | ### redact 131 | 132 | ▸ **redact**(property: * `string` | `string`[]*, redactor?: *[Redactor](../modules/_filtering_redact_.md#redactor)*): `void` 133 | 134 | Redact given property/properties on all intercepted requests matching current filter. 135 | 136 | Useful whenever your HTTP requests contain sensitive details such as an API key that should not be saved to disc. 137 | 138 | **Parameters:** 139 | 140 | | Name | Type | Default value | Description | 141 | | ------ | ------ | ------ | ------ | 142 | | property | `string` | `string`[]| - | Nested path(s) for properties to redact from intercepted HTTP requests. Works for all properties on serialized HTTP request/response objects. Accepts dot notation for nested properties (eg \`response.body.foobars\[0\].id\`) | 143 | | `Default value` redactor | [Redactor](../modules/_filtering_redact_.md#redactor) | () => DEFAULT_REDACT_SYMBOL | Custom redactor. Defaults to replacing matching values with "**\***" | 144 | 145 | **Returns:** `void` 146 | 147 | ___ 148 | 149 | 150 | ### request 151 | 152 | ▸ **request**(): [ISerializedRequest](../interfaces/_http_serializer_.iserializedrequest.md) 153 | 154 | Return serialized request part of the _single_ matching intercepted HTTP request. 155 | 156 | Throws an exception if multiple requests were matched. 157 | 158 | **Returns:** [ISerializedRequest](../interfaces/_http_serializer_.iserializedrequest.md) 159 | 160 | ___ 161 | 162 | 163 | ### respond 164 | 165 | ▸ **respond**(response: *[PartialResponseForRequest](../modules/_filtering_collection_.md#partialresponseforrequest)*): `void` 166 | 167 | Provide a mock response for all matching requests. 168 | 169 | Use callback to dynamically generate response per request. 170 | 171 | Matching responses defined here take _precedence_ over mocks loaded normally. 172 | 173 | **Parameters:** 174 | 175 | | Name | Type | Description | 176 | | ------ | ------ | ------ | 177 | | response | [PartialResponseForRequest](../modules/_filtering_collection_.md#partialresponseforrequest) | Serialized HTTP response or callback | 178 | 179 | **Returns:** `void` 180 | 181 | ___ 182 | 183 | 184 | ### response 185 | 186 | ▸ **response**(): [ISerializedResponse](../interfaces/_http_serializer_.iserializedresponse.md) 187 | 188 | Return serialized response part of the _single_ matching intercepted HTTP request. 189 | 190 | Throws an exception if multiple requests were matched. 191 | 192 | **Returns:** [ISerializedResponse](../interfaces/_http_serializer_.iserializedresponse.md) 193 | 194 | ___ 195 | 196 | -------------------------------------------------------------------------------- /docs/classes/_interceptor_.interceptor.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["interceptor"](../modules/_interceptor_.md) > [Interceptor](../classes/_interceptor_.interceptor.md) 2 | 3 | # Class: Interceptor 4 | 5 | Intercept outbound HTTP requests and provide mock responses through an event API. 6 | 7 | Uses MITM library to spy on HTTP requests made in current NodeJS process. 8 | 9 | ## Hierarchy 10 | 11 | `EventEmitter` 12 | 13 | **↳ Interceptor** 14 | 15 | ## Implements 16 | 17 | * [IInterceptEvents](../interfaces/_interceptor_.iinterceptevents.md) 18 | 19 | ## Index 20 | 21 | ### Properties 22 | 23 | * [clientRequests](_interceptor_.interceptor.md#clientrequests) 24 | * [ignorePorts](_interceptor_.interceptor.md#ignoreports) 25 | * [mitm](_interceptor_.interceptor.md#mitm) 26 | * [origOnSocket](_interceptor_.interceptor.md#origonsocket) 27 | * [requestNumber](_interceptor_.interceptor.md#requestnumber) 28 | * [defaultMaxListeners](_interceptor_.interceptor.md#defaultmaxlisteners) 29 | 30 | ### Methods 31 | 32 | * [addListener](_interceptor_.interceptor.md#addlistener) 33 | * [disable](_interceptor_.interceptor.md#disable) 34 | * [emit](_interceptor_.interceptor.md#emit) 35 | * [enable](_interceptor_.interceptor.md#enable) 36 | * [eventNames](_interceptor_.interceptor.md#eventnames) 37 | * [getMaxListeners](_interceptor_.interceptor.md#getmaxlisteners) 38 | * [getRequestId](_interceptor_.interceptor.md#getrequestid) 39 | * [listenerCount](_interceptor_.interceptor.md#listenercount) 40 | * [listeners](_interceptor_.interceptor.md#listeners) 41 | * [mitmOnConnect](_interceptor_.interceptor.md#mitmonconnect) 42 | * [mitmOnRequest](_interceptor_.interceptor.md#mitmonrequest) 43 | * [off](_interceptor_.interceptor.md#off) 44 | * [on](_interceptor_.interceptor.md#on) 45 | * [once](_interceptor_.interceptor.md#once) 46 | * [prependListener](_interceptor_.interceptor.md#prependlistener) 47 | * [prependOnceListener](_interceptor_.interceptor.md#prependoncelistener) 48 | * [rawListeners](_interceptor_.interceptor.md#rawlisteners) 49 | * [removeAllListeners](_interceptor_.interceptor.md#removealllisteners) 50 | * [removeListener](_interceptor_.interceptor.md#removelistener) 51 | * [setMaxListeners](_interceptor_.interceptor.md#setmaxlisteners) 52 | * [trackSocketAndClientOptions](_interceptor_.interceptor.md#tracksocketandclientoptions) 53 | * [listenerCount](_interceptor_.interceptor.md#listenercount-1) 54 | 55 | --- 56 | 57 | ## Properties 58 | 59 | 60 | 61 | ### `` clientRequests 62 | 63 | **● clientRequests**: *[ClientRequestTracker](../interfaces/_interceptor_.clientrequesttracker.md)* 64 | 65 | ___ 66 | 67 | 68 | ### `` ignorePorts 69 | 70 | **● ignorePorts**: *`number`[]* = [] 71 | 72 | ___ 73 | 74 | 75 | ### ```` mitm 76 | 77 | **● mitm**: *`Mitm.Mitm`* 78 | 79 | ___ 80 | 81 | 82 | ### ```` origOnSocket 83 | 84 | **● origOnSocket**: * `undefined` | `function` 85 | * 86 | 87 | ___ 88 | 89 | 90 | ### requestNumber 91 | 92 | **● requestNumber**: *`number`* = 0 93 | 94 | ___ 95 | 96 | 97 | ### `` defaultMaxListeners 98 | 99 | **● defaultMaxListeners**: *`number`* 100 | 101 | ___ 102 | 103 | ## Methods 104 | 105 | 106 | 107 | ### addListener 108 | 109 | ▸ **addListener**(event: * `string` | `symbol`*, listener: *`function`*): `this` 110 | 111 | **Parameters:** 112 | 113 | | Name | Type | 114 | | ------ | ------ | 115 | | event | `string` | `symbol`| 116 | | listener | `function` | 117 | 118 | **Returns:** `this` 119 | 120 | ___ 121 | 122 | 123 | ### disable 124 | 125 | ▸ **disable**(): `void` 126 | 127 | Disables intercepting outbound HTTP requests. 128 | 129 | **Returns:** `void` 130 | 131 | ___ 132 | 133 | 134 | ### emit 135 | 136 | ▸ **emit**(event: * `string` | `symbol`*, ...args: *`any`[]*): `boolean` 137 | 138 | **Parameters:** 139 | 140 | | Name | Type | 141 | | ------ | ------ | 142 | | event | `string` | `symbol`| 143 | | `Rest` args | `any`[] | 144 | 145 | **Returns:** `boolean` 146 | 147 | ___ 148 | 149 | 150 | ### enable 151 | 152 | ▸ **enable**(options?: *[IInterceptOptions](../interfaces/_interceptor_.iinterceptoptions.md)*): `void` 153 | 154 | Enables intercepting all outbound HTTP requests. 155 | 156 | **Parameters:** 157 | 158 | | Name | Type | Default value | Description | 159 | | ------ | ------ | ------ | ------ | 160 | | `Default value` options | [IInterceptOptions](../interfaces/_interceptor_.iinterceptoptions.md) | {} | Intercept options | 161 | 162 | **Returns:** `void` 163 | 164 | ___ 165 | 166 | 167 | ### eventNames 168 | 169 | ▸ **eventNames**(): `Array`< `string` | `symbol`> 170 | 171 | **Returns:** `Array`< `string` | `symbol`> 172 | 173 | ___ 174 | 175 | 176 | ### getMaxListeners 177 | 178 | ▸ **getMaxListeners**(): `number` 179 | 180 | **Returns:** `number` 181 | 182 | ___ 183 | 184 | 185 | ### `` getRequestId 186 | 187 | ▸ **getRequestId**(interceptedRequest: *`IncomingMessage`*): `string` 188 | 189 | **Parameters:** 190 | 191 | | Name | Type | 192 | | ------ | ------ | 193 | | interceptedRequest | `IncomingMessage` | 194 | 195 | **Returns:** `string` 196 | 197 | ___ 198 | 199 | 200 | ### listenerCount 201 | 202 | ▸ **listenerCount**(type: * `string` | `symbol`*): `number` 203 | 204 | **Parameters:** 205 | 206 | | Name | Type | 207 | | ------ | ------ | 208 | | type | `string` | `symbol`| 209 | 210 | **Returns:** `number` 211 | 212 | ___ 213 | 214 | 215 | ### listeners 216 | 217 | ▸ **listeners**(event: * `string` | `symbol`*): `Function`[] 218 | 219 | **Parameters:** 220 | 221 | | Name | Type | 222 | | ------ | ------ | 223 | | event | `string` | `symbol`| 224 | 225 | **Returns:** `Function`[] 226 | 227 | ___ 228 | 229 | 230 | ### `` mitmOnConnect 231 | 232 | ▸ **mitmOnConnect**(socket: *`BypassableSocket`*, options: *[ProxyRequestOptions](../interfaces/_interceptor_.proxyrequestoptions.md)*): `void` 233 | 234 | Event handler for Mitm "connect" event. 235 | 236 | **Parameters:** 237 | 238 | | Name | Type | 239 | | ------ | ------ | 240 | | socket | `BypassableSocket` | 241 | | options | [ProxyRequestOptions](../interfaces/_interceptor_.proxyrequestoptions.md) | 242 | 243 | **Returns:** `void` 244 | 245 | ___ 246 | 247 | 248 | ### `` mitmOnRequest 249 | 250 | ▸ **mitmOnRequest**(interceptedRequest: *`IncomingMessage`*, interceptedResponse: *`ServerResponse`*): `void` 251 | 252 | Event handler for Mitm "request" event. 253 | 254 | Intercepted requests will be proxied if the `shouldProxy` option has been set. 255 | *__emits__*: 'intercept' when we intercept a request 256 | 257 | *__emits__*: 'proxied' when we have sent the response for a proxied response 258 | 259 | **Parameters:** 260 | 261 | | Name | Type | 262 | | ------ | ------ | 263 | | interceptedRequest | `IncomingMessage` | 264 | | interceptedResponse | `ServerResponse` | 265 | 266 | **Returns:** `void` 267 | 268 | ___ 269 | 270 | 271 | ### off 272 | 273 | ▸ **off**(event: * `string` | `symbol`*, listener: *`function`*): `this` 274 | 275 | **Parameters:** 276 | 277 | | Name | Type | 278 | | ------ | ------ | 279 | | event | `string` | `symbol`| 280 | | listener | `function` | 281 | 282 | **Returns:** `this` 283 | 284 | ___ 285 | 286 | 287 | ### on 288 | 289 | ▸ **on**(event: * `string` | `symbol`*, listener: *`function`*): `this` 290 | 291 | **Parameters:** 292 | 293 | | Name | Type | 294 | | ------ | ------ | 295 | | event | `string` | `symbol`| 296 | | listener | `function` | 297 | 298 | **Returns:** `this` 299 | 300 | ___ 301 | 302 | 303 | ### once 304 | 305 | ▸ **once**(event: * `string` | `symbol`*, listener: *`function`*): `this` 306 | 307 | **Parameters:** 308 | 309 | | Name | Type | 310 | | ------ | ------ | 311 | | event | `string` | `symbol`| 312 | | listener | `function` | 313 | 314 | **Returns:** `this` 315 | 316 | ___ 317 | 318 | 319 | ### prependListener 320 | 321 | ▸ **prependListener**(event: * `string` | `symbol`*, listener: *`function`*): `this` 322 | 323 | **Parameters:** 324 | 325 | | Name | Type | 326 | | ------ | ------ | 327 | | event | `string` | `symbol`| 328 | | listener | `function` | 329 | 330 | **Returns:** `this` 331 | 332 | ___ 333 | 334 | 335 | ### prependOnceListener 336 | 337 | ▸ **prependOnceListener**(event: * `string` | `symbol`*, listener: *`function`*): `this` 338 | 339 | **Parameters:** 340 | 341 | | Name | Type | 342 | | ------ | ------ | 343 | | event | `string` | `symbol`| 344 | | listener | `function` | 345 | 346 | **Returns:** `this` 347 | 348 | ___ 349 | 350 | 351 | ### rawListeners 352 | 353 | ▸ **rawListeners**(event: * `string` | `symbol`*): `Function`[] 354 | 355 | **Parameters:** 356 | 357 | | Name | Type | 358 | | ------ | ------ | 359 | | event | `string` | `symbol`| 360 | 361 | **Returns:** `Function`[] 362 | 363 | ___ 364 | 365 | 366 | ### removeAllListeners 367 | 368 | ▸ **removeAllListeners**(event?: * `string` | `symbol`*): `this` 369 | 370 | **Parameters:** 371 | 372 | | Name | Type | 373 | | ------ | ------ | 374 | | `Optional` event | `string` | `symbol`| 375 | 376 | **Returns:** `this` 377 | 378 | ___ 379 | 380 | 381 | ### removeListener 382 | 383 | ▸ **removeListener**(event: * `string` | `symbol`*, listener: *`function`*): `this` 384 | 385 | **Parameters:** 386 | 387 | | Name | Type | 388 | | ------ | ------ | 389 | | event | `string` | `symbol`| 390 | | listener | `function` | 391 | 392 | **Returns:** `this` 393 | 394 | ___ 395 | 396 | 397 | ### setMaxListeners 398 | 399 | ▸ **setMaxListeners**(n: *`number`*): `this` 400 | 401 | **Parameters:** 402 | 403 | | Name | Type | 404 | | ------ | ------ | 405 | | n | `number` | 406 | 407 | **Returns:** `this` 408 | 409 | ___ 410 | 411 | 412 | ### `` trackSocketAndClientOptions 413 | 414 | ▸ **trackSocketAndClientOptions**(socket: *[RegisteredSocket](../interfaces/_interceptor_.registeredsocket.md)*, clientOptions: *[ProxyRequestOptions](../interfaces/_interceptor_.proxyrequestoptions.md)*): `void` 415 | 416 | **Parameters:** 417 | 418 | | Name | Type | 419 | | ------ | ------ | 420 | | socket | [RegisteredSocket](../interfaces/_interceptor_.registeredsocket.md) | 421 | | clientOptions | [ProxyRequestOptions](../interfaces/_interceptor_.proxyrequestoptions.md) | 422 | 423 | **Returns:** `void` 424 | 425 | ___ 426 | 427 | 428 | ### `` listenerCount 429 | 430 | ▸ **listenerCount**(emitter: *`EventEmitter`*, event: * `string` | `symbol`*): `number` 431 | 432 | *__deprecated__*: since v4.0.0 433 | 434 | **Parameters:** 435 | 436 | | Name | Type | 437 | | ------ | ------ | 438 | | emitter | `EventEmitter` | 439 | | event | `string` | `symbol`| 440 | 441 | **Returns:** `number` 442 | 443 | ___ 444 | 445 | -------------------------------------------------------------------------------- /docs/classes/_mock_response_.mockresponse.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["mock-response"](../modules/_mock_response_.md) > [MockResponse](../classes/_mock_response_.mockresponse.md) 2 | 3 | # Class: MockResponse 4 | 5 | Encapsulates logic for sending a response for an intercepted HTTP request 6 | 7 | ## Hierarchy 8 | 9 | **MockResponse** 10 | 11 | ## Index 12 | 13 | ### Constructors 14 | 15 | * [constructor](_mock_response_.mockresponse.md#constructor) 16 | 17 | ### Properties 18 | 19 | * [ctx](_mock_response_.mockresponse.md#ctx) 20 | * [event](_mock_response_.mockresponse.md#event) 21 | 22 | ### Methods 23 | 24 | * [assertMockMatches](_mock_response_.mockresponse.md#assertmockmatches) 25 | * [getMockForIntecept](_mock_response_.mockresponse.md#getmockforintecept) 26 | * [send](_mock_response_.mockresponse.md#send) 27 | * [writeMockResponse](_mock_response_.mockresponse.md#writemockresponse) 28 | 29 | --- 30 | 31 | ## Constructors 32 | 33 | 34 | 35 | ### constructor 36 | 37 | ⊕ **new MockResponse**(event: *[IInterceptEvent](../interfaces/_interceptor_.iinterceptevent.md)*, ctx: *[Context](_context_.context.md)*): [MockResponse](_mock_response_.mockresponse.md) 38 | 39 | **Parameters:** 40 | 41 | | Name | Type | 42 | | ------ | ------ | 43 | | event | [IInterceptEvent](../interfaces/_interceptor_.iinterceptevent.md) | 44 | | ctx | [Context](_context_.context.md) | 45 | 46 | **Returns:** [MockResponse](_mock_response_.mockresponse.md) 47 | 48 | ___ 49 | 50 | ## Properties 51 | 52 | 53 | 54 | ### `` ctx 55 | 56 | **● ctx**: *[Context](_context_.context.md)* 57 | 58 | ___ 59 | 60 | 61 | ### `` event 62 | 63 | **● event**: *[IInterceptEvent](../interfaces/_interceptor_.iinterceptevent.md)* 64 | 65 | ___ 66 | 67 | ## Methods 68 | 69 | 70 | 71 | ### `` assertMockMatches 72 | 73 | ▸ **assertMockMatches**(__namedParameters: *`object`*): `void` 74 | 75 | **Parameters:** 76 | 77 | **__namedParameters: `object`** 78 | 79 | | Name | Type | 80 | | ------ | ------ | 81 | | mock | [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md) | 82 | | requestNumber | `number` | 83 | | serializedRequest | [ISerializedRequest](../interfaces/_http_serializer_.iserializedrequest.md) | 84 | 85 | **Returns:** `void` 86 | 87 | ___ 88 | 89 | 90 | ### `` getMockForIntecept 91 | 92 | ▸ **getMockForIntecept**(__namedParameters: *`object`*): [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md) 93 | 94 | **Parameters:** 95 | 96 | **__namedParameters: `object`** 97 | 98 | | Name | Type | 99 | | ------ | ------ | 100 | | requestNumber | `number` | 101 | 102 | **Returns:** [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md) 103 | 104 | ___ 105 | 106 | 107 | ### send 108 | 109 | ▸ **send**(): `Promise`< [ISerializedRequestResponse](../interfaces/_http_serializer_.iserializedrequestresponse.md) | `undefined`> 110 | 111 | Send a respond for our wrapped intercept event if able. 112 | 113 | Give precedence to matching responses in shared context (from `yesno.matching().respond()`). Else, if we're in mock mode, lookup the mock response. 114 | 115 | **Returns:** `Promise`< [ISerializedRequestResponse](../interfaces/_http_serializer_.iserializedrequestresponse.md) | `undefined`> 116 | The received request & sent response. Returns `undefined` if unable to respond 117 | 118 | ___ 119 | 120 | 121 | ### `` writeMockResponse 122 | 123 | ▸ **writeMockResponse**(response: *[ISerializedResponse](../interfaces/_http_serializer_.iserializedresponse.md)*, interceptedResponse: *`ServerResponse`*): `void` 124 | 125 | **Parameters:** 126 | 127 | | Name | Type | 128 | | ------ | ------ | 129 | | response | [ISerializedResponse](../interfaces/_http_serializer_.iserializedresponse.md) | 130 | | interceptedResponse | `ServerResponse` | 131 | 132 | **Returns:** `void` 133 | 134 | ___ 135 | 136 | -------------------------------------------------------------------------------- /docs/classes/_recording_.recording.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["recording"](../modules/_recording_.md) > [Recording](../classes/_recording_.recording.md) 2 | 3 | # Class: Recording 4 | 5 | Represents a single YesNo recording 6 | 7 | ## Hierarchy 8 | 9 | **Recording** 10 | 11 | ## Index 12 | 13 | ### Constructors 14 | 15 | * [constructor](_recording_.recording.md#constructor) 16 | 17 | ### Properties 18 | 19 | * [options](_recording_.recording.md#options) 20 | 21 | ### Methods 22 | 23 | * [complete](_recording_.recording.md#complete) 24 | 25 | --- 26 | 27 | ## Constructors 28 | 29 | 30 | 31 | ### constructor 32 | 33 | ⊕ **new Recording**(options: *[IRecordingOptions](../interfaces/_recording_.irecordingoptions.md)*): [Recording](_recording_.recording.md) 34 | 35 | **Parameters:** 36 | 37 | | Name | Type | 38 | | ------ | ------ | 39 | | options | [IRecordingOptions](../interfaces/_recording_.irecordingoptions.md) | 40 | 41 | **Returns:** [Recording](_recording_.recording.md) 42 | 43 | ___ 44 | 45 | ## Properties 46 | 47 | 48 | 49 | ### `` options 50 | 51 | **● options**: *[IRecordingOptions](../interfaces/_recording_.irecordingoptions.md)* 52 | 53 | ___ 54 | 55 | ## Methods 56 | 57 | 58 | 59 | ### complete 60 | 61 | ▸ **complete**(): `Promise`< `string` | `undefined`> 62 | 63 | Complete recording by saving all HTTP requests to disc if in record mode. No-op otherwise. 64 | 65 | **Returns:** `Promise`< `string` | `undefined`> 66 | 67 | ___ 68 | 69 | -------------------------------------------------------------------------------- /docs/enums/_recording_.recordmode.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["recording"](../modules/_recording_.md) > [RecordMode](../enums/_recording_.recordmode.md) 2 | 3 | # Enumeration: RecordMode 4 | 5 | ## Index 6 | 7 | ### Enumeration members 8 | 9 | * [Mock](_recording_.recordmode.md#mock) 10 | * [Record](_recording_.recordmode.md#record) 11 | * [Spy](_recording_.recordmode.md#spy) 12 | 13 | --- 14 | 15 | ## Enumeration members 16 | 17 | 18 | 19 | ### Mock 20 | 21 | **Mock**: = "mock" 22 | 23 | Intercept requests and respond with local mocks 24 | 25 | ___ 26 | 27 | 28 | ### Record 29 | 30 | **Record**: = "record" 31 | 32 | Save requests 33 | 34 | ___ 35 | 36 | 37 | ### Spy 38 | 39 | **Spy**: = "spy" 40 | 41 | Spy on request/response 42 | 43 | ___ 44 | 45 | -------------------------------------------------------------------------------- /docs/interfaces/_context_.iinflightrequest.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["context"](../modules/_context_.md) > [IInFlightRequest](../interfaces/_context_.iinflightrequest.md) 2 | 3 | # Interface: IInFlightRequest 4 | 5 | ## Hierarchy 6 | 7 | **IInFlightRequest** 8 | 9 | ## Index 10 | 11 | ### Properties 12 | 13 | * [requestSerializer](_context_.iinflightrequest.md#requestserializer) 14 | * [startTime](_context_.iinflightrequest.md#starttime) 15 | 16 | --- 17 | 18 | ## Properties 19 | 20 | 21 | 22 | ### requestSerializer 23 | 24 | **● requestSerializer**: *[RequestSerializer](../classes/_http_serializer_.requestserializer.md)* 25 | 26 | ___ 27 | 28 | 29 | ### startTime 30 | 31 | **● startTime**: *`number`* 32 | 33 | ___ 34 | 35 | -------------------------------------------------------------------------------- /docs/interfaces/_context_.iredactprop.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["context"](../modules/_context_.md) > [IRedactProp](../interfaces/_context_.iredactprop.md) 2 | 3 | # Interface: IRedactProp 4 | 5 | ## Hierarchy 6 | 7 | **IRedactProp** 8 | 9 | ## Index 10 | 11 | ### Properties 12 | 13 | * [property](_context_.iredactprop.md#property) 14 | * [redactor](_context_.iredactprop.md#redactor) 15 | 16 | --- 17 | 18 | ## Properties 19 | 20 | 21 | 22 | ### property 23 | 24 | **● property**: * `string` | `string`[] 25 | * 26 | 27 | ___ 28 | 29 | 30 | ### `` redactor 31 | 32 | **● redactor**: *[Redactor](../modules/_filtering_redact_.md#redactor)* 33 | 34 | ___ 35 | 36 | -------------------------------------------------------------------------------- /docs/interfaces/_context_.iresponseformatchingrequest.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["context"](../modules/_context_.md) > [IResponseForMatchingRequest](../interfaces/_context_.iresponseformatchingrequest.md) 2 | 3 | # Interface: IResponseForMatchingRequest 4 | 5 | ## Hierarchy 6 | 7 | **IResponseForMatchingRequest** 8 | 9 | ## Index 10 | 11 | ### Properties 12 | 13 | * [matcher](_context_.iresponseformatchingrequest.md#matcher) 14 | * [response](_context_.iresponseformatchingrequest.md#response) 15 | 16 | --- 17 | 18 | ## Properties 19 | 20 | 21 | 22 | ### matcher 23 | 24 | **● matcher**: *[Matcher](../modules/_filtering_matcher_.md#matcher)* 25 | 26 | ___ 27 | 28 | 29 | ### response 30 | 31 | **● response**: *[PartialResponseForRequest](../modules/_filtering_collection_.md#partialresponseforrequest)* 32 | 33 | ___ 34 | 35 | -------------------------------------------------------------------------------- /docs/interfaces/_file_.ifileoptions.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["file"](../modules/_file_.md) > [IFileOptions](../interfaces/_file_.ifileoptions.md) 2 | 3 | # Interface: IFileOptions 4 | 5 | ## Hierarchy 6 | 7 | **IFileOptions** 8 | 9 | ↳ [IRecordingOptions](_recording_.irecordingoptions.md) 10 | 11 | ## Index 12 | 13 | ### Properties 14 | 15 | * [filename](_file_.ifileoptions.md#filename) 16 | 17 | --- 18 | 19 | ## Properties 20 | 21 | 22 | 23 | ### filename 24 | 25 | **● filename**: *`string`* 26 | 27 | ___ 28 | 29 | -------------------------------------------------------------------------------- /docs/interfaces/_file_.ihttpmock.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["file"](../modules/_file_.md) > [IHttpMock](../interfaces/_file_.ihttpmock.md) 2 | 3 | # Interface: IHttpMock 4 | 5 | ## Hierarchy 6 | 7 | **IHttpMock** 8 | 9 | ## Index 10 | 11 | ### Properties 12 | 13 | * [request](_file_.ihttpmock.md#request) 14 | * [response](_file_.ihttpmock.md#response) 15 | 16 | --- 17 | 18 | ## Properties 19 | 20 | 21 | 22 | ### request 23 | 24 | **● request**: *[IPartialMockRequest](_file_.ipartialmockrequest.md)* 25 | 26 | ___ 27 | 28 | 29 | ### response 30 | 31 | **● response**: *[IPartialMockResponse](_file_.ipartialmockresponse.md)* 32 | 33 | ___ 34 | 35 | -------------------------------------------------------------------------------- /docs/interfaces/_file_.ipartialmockrequest.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["file"](../modules/_file_.md) > [IPartialMockRequest](../interfaces/_file_.ipartialmockrequest.md) 2 | 3 | # Interface: IPartialMockRequest 4 | 5 | ## Hierarchy 6 | 7 | **IPartialMockRequest** 8 | 9 | ## Index 10 | 11 | ### Properties 12 | 13 | * [body](_file_.ipartialmockrequest.md#body) 14 | * [headers](_file_.ipartialmockrequest.md#headers) 15 | * [host](_file_.ipartialmockrequest.md#host) 16 | * [method](_file_.ipartialmockrequest.md#method) 17 | * [path](_file_.ipartialmockrequest.md#path) 18 | * [port](_file_.ipartialmockrequest.md#port) 19 | * [protocol](_file_.ipartialmockrequest.md#protocol) 20 | 21 | --- 22 | 23 | ## Properties 24 | 25 | 26 | 27 | ### `` body 28 | 29 | **● body**: * `string` | `any` 30 | * 31 | 32 | ___ 33 | 34 | 35 | ### `` headers 36 | 37 | **● headers**: *[IHeaders](_http_serializer_.iheaders.md)* 38 | 39 | ___ 40 | 41 | 42 | ### host 43 | 44 | **● host**: *`string`* 45 | 46 | ___ 47 | 48 | 49 | ### method 50 | 51 | **● method**: *`string`* 52 | 53 | ___ 54 | 55 | 56 | ### `` path 57 | 58 | **● path**: * `undefined` | `string` 59 | * 60 | 61 | ___ 62 | 63 | 64 | ### `` port 65 | 66 | **● port**: * `undefined` | `number` 67 | * 68 | 69 | ___ 70 | 71 | 72 | ### protocol 73 | 74 | **● protocol**: * "http" | "https" 75 | * 76 | 77 | ___ 78 | 79 | -------------------------------------------------------------------------------- /docs/interfaces/_file_.ipartialmockresponse.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["file"](../modules/_file_.md) > [IPartialMockResponse](../interfaces/_file_.ipartialmockresponse.md) 2 | 3 | # Interface: IPartialMockResponse 4 | 5 | ## Hierarchy 6 | 7 | **IPartialMockResponse** 8 | 9 | ## Index 10 | 11 | ### Properties 12 | 13 | * [body](_file_.ipartialmockresponse.md#body) 14 | * [headers](_file_.ipartialmockresponse.md#headers) 15 | * [statusCode](_file_.ipartialmockresponse.md#statuscode) 16 | 17 | --- 18 | 19 | ## Properties 20 | 21 | 22 | 23 | ### `` body 24 | 25 | **● body**: * `string` | `any` 26 | * 27 | 28 | ___ 29 | 30 | 31 | ### `` headers 32 | 33 | **● headers**: *[IHeaders](_http_serializer_.iheaders.md)* 34 | 35 | ___ 36 | 37 | 38 | ### statusCode 39 | 40 | **● statusCode**: *`number`* 41 | 42 | ___ 43 | 44 | -------------------------------------------------------------------------------- /docs/interfaces/_file_.isavefile.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["file"](../modules/_file_.md) > [ISaveFile](../interfaces/_file_.isavefile.md) 2 | 3 | # Interface: ISaveFile 4 | 5 | ## Hierarchy 6 | 7 | **ISaveFile** 8 | 9 | ## Index 10 | 11 | ### Properties 12 | 13 | * [records](_file_.isavefile.md#records) 14 | 15 | --- 16 | 17 | ## Properties 18 | 19 | 20 | 21 | ### records 22 | 23 | **● records**: *[ISerializedHttp](_http_serializer_.iserializedhttp.md)[]* 24 | 25 | ___ 26 | 27 | -------------------------------------------------------------------------------- /docs/interfaces/_file_.isaveoptions.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["file"](../modules/_file_.md) > [ISaveOptions](../interfaces/_file_.isaveoptions.md) 2 | 3 | # Interface: ISaveOptions 4 | 5 | ## Hierarchy 6 | 7 | **ISaveOptions** 8 | 9 | ## Index 10 | 11 | ### Properties 12 | 13 | * [force](_file_.isaveoptions.md#force) 14 | * [records](_file_.isaveoptions.md#records) 15 | 16 | --- 17 | 18 | ## Properties 19 | 20 | 21 | 22 | ### `` force 23 | 24 | **● force**: * `undefined` | `false` | `true` 25 | * 26 | 27 | ___ 28 | 29 | 30 | ### `` records 31 | 32 | **● records**: *[ISerializedHttp](_http_serializer_.iserializedhttp.md)[]* 33 | 34 | ___ 35 | 36 | -------------------------------------------------------------------------------- /docs/interfaces/_filtering_collection_.ifiltered.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["filtering/collection"](../modules/_filtering_collection_.md) > [IFiltered](../interfaces/_filtering_collection_.ifiltered.md) 2 | 3 | # Interface: IFiltered 4 | 5 | ## Hierarchy 6 | 7 | **IFiltered** 8 | 9 | ## Implemented by 10 | 11 | * [FilteredHttpCollection](../classes/_filtering_collection_.filteredhttpcollection.md) 12 | * [YesNo](../classes/_yesno_.yesno.md) 13 | 14 | ## Index 15 | 16 | ### Properties 17 | 18 | * [intercepted](_filtering_collection_.ifiltered.md#intercepted) 19 | * [mocks](_filtering_collection_.ifiltered.md#mocks) 20 | * [redact](_filtering_collection_.ifiltered.md#redact) 21 | 22 | --- 23 | 24 | ## Properties 25 | 26 | 27 | 28 | ### intercepted 29 | 30 | **● intercepted**: *`function`* 31 | 32 | #### Type declaration 33 | ▸(): [ISerializedHttp](_http_serializer_.iserializedhttp.md)[] 34 | 35 | **Returns:** [ISerializedHttp](_http_serializer_.iserializedhttp.md)[] 36 | 37 | ___ 38 | 39 | 40 | ### mocks 41 | 42 | **● mocks**: *`function`* 43 | 44 | #### Type declaration 45 | ▸(): [ISerializedHttp](_http_serializer_.iserializedhttp.md)[] 46 | 47 | **Returns:** [ISerializedHttp](_http_serializer_.iserializedhttp.md)[] 48 | 49 | ___ 50 | 51 | 52 | ### redact 53 | 54 | **● redact**: *`function`* 55 | 56 | #### Type declaration 57 | ▸(property: * `string` | `string`[]*, symbol: *[Redactor](../modules/_filtering_redact_.md#redactor)*): `void` 58 | 59 | **Parameters:** 60 | 61 | | Name | Type | 62 | | ------ | ------ | 63 | | property | `string` | `string`[]| 64 | | symbol | [Redactor](../modules/_filtering_redact_.md#redactor) | 65 | 66 | **Returns:** `void` 67 | 68 | ___ 69 | 70 | -------------------------------------------------------------------------------- /docs/interfaces/_filtering_collection_.ifilteredhttpcollectionparams.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["filtering/collection"](../modules/_filtering_collection_.md) > [IFilteredHttpCollectionParams](../interfaces/_filtering_collection_.ifilteredhttpcollectionparams.md) 2 | 3 | # Interface: IFilteredHttpCollectionParams 4 | 5 | ## Hierarchy 6 | 7 | **IFilteredHttpCollectionParams** 8 | 9 | ## Index 10 | 11 | ### Properties 12 | 13 | * [context](_filtering_collection_.ifilteredhttpcollectionparams.md#context) 14 | * [matcher](_filtering_collection_.ifilteredhttpcollectionparams.md#matcher) 15 | 16 | --- 17 | 18 | ## Properties 19 | 20 | 21 | 22 | ### context 23 | 24 | **● context**: *[Context](../classes/_context_.context.md)* 25 | 26 | ___ 27 | 28 | 29 | ### `` matcher 30 | 31 | **● matcher**: *[Matcher](../modules/_filtering_matcher_.md#matcher)* 32 | 33 | ___ 34 | 35 | -------------------------------------------------------------------------------- /docs/interfaces/_filtering_comparator_.icomparatormetadata.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["filtering/comparator"](../modules/_filtering_comparator_.md) > [IComparatorMetadata](../interfaces/_filtering_comparator_.icomparatormetadata.md) 2 | 3 | # Interface: IComparatorMetadata 4 | 5 | ## Hierarchy 6 | 7 | **IComparatorMetadata** 8 | 9 | ## Index 10 | 11 | ### Properties 12 | 13 | * [requestIndex](_filtering_comparator_.icomparatormetadata.md#requestindex) 14 | 15 | --- 16 | 17 | ## Properties 18 | 19 | 20 | 21 | ### requestIndex 22 | 23 | **● requestIndex**: *`number`* 24 | 25 | ___ 26 | 27 | -------------------------------------------------------------------------------- /docs/interfaces/_filtering_matcher_.iserializedhttppartialdeepmatch.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["filtering/matcher"](../modules/_filtering_matcher_.md) > [ISerializedHttpPartialDeepMatch](../interfaces/_filtering_matcher_.iserializedhttppartialdeepmatch.md) 2 | 3 | # Interface: ISerializedHttpPartialDeepMatch 4 | 5 | ## Hierarchy 6 | 7 | **ISerializedHttpPartialDeepMatch** 8 | 9 | ## Index 10 | 11 | ### Properties 12 | 13 | * [request](_filtering_matcher_.iserializedhttppartialdeepmatch.md#request) 14 | * [response](_filtering_matcher_.iserializedhttppartialdeepmatch.md#response) 15 | * [url](_filtering_matcher_.iserializedhttppartialdeepmatch.md#url) 16 | 17 | --- 18 | 19 | ## Properties 20 | 21 | 22 | 23 | ### `` request 24 | 25 | **● request**: *[RequestQuery](../modules/_filtering_matcher_.md#requestquery)* 26 | 27 | ___ 28 | 29 | 30 | ### `` response 31 | 32 | **● response**: *[ResponseQuery](../modules/_filtering_matcher_.md#responsequery)* 33 | 34 | ___ 35 | 36 | 37 | ### `` url 38 | 39 | **● url**: * `string` | `RegExp` 40 | * 41 | 42 | ___ 43 | 44 | -------------------------------------------------------------------------------- /docs/interfaces/_filtering_matcher_.iserializedrequestresponsetomatch.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["filtering/matcher"](../modules/_filtering_matcher_.md) > [ISerializedRequestResponseToMatch](../interfaces/_filtering_matcher_.iserializedrequestresponsetomatch.md) 2 | 3 | # Interface: ISerializedRequestResponseToMatch 4 | 5 | ## Hierarchy 6 | 7 | **ISerializedRequestResponseToMatch** 8 | 9 | ## Index 10 | 11 | ### Properties 12 | 13 | * [request](_filtering_matcher_.iserializedrequestresponsetomatch.md#request) 14 | * [response](_filtering_matcher_.iserializedrequestresponsetomatch.md#response) 15 | 16 | --- 17 | 18 | ## Properties 19 | 20 | 21 | 22 | ### request 23 | 24 | **● request**: *[ISerializedRequest](_http_serializer_.iserializedrequest.md)* 25 | 26 | ___ 27 | 28 | 29 | ### `` response 30 | 31 | **● response**: *[ISerializedResponse](_http_serializer_.iserializedresponse.md)* 32 | 33 | ___ 34 | 35 | -------------------------------------------------------------------------------- /docs/interfaces/_http_serializer_.icreaterecord.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["http-serializer"](../modules/_http_serializer_.md) > [ICreateRecord](../interfaces/_http_serializer_.icreaterecord.md) 2 | 3 | # Interface: ICreateRecord 4 | 5 | ## Hierarchy 6 | 7 | **ICreateRecord** 8 | 9 | ## Index 10 | 11 | ### Properties 12 | 13 | * [duration](_http_serializer_.icreaterecord.md#duration) 14 | * [request](_http_serializer_.icreaterecord.md#request) 15 | * [response](_http_serializer_.icreaterecord.md#response) 16 | 17 | --- 18 | 19 | ## Properties 20 | 21 | 22 | 23 | ### duration 24 | 25 | **● duration**: *`number`* 26 | 27 | ___ 28 | 29 | 30 | ### request 31 | 32 | **● request**: *[ISerializedRequest](_http_serializer_.iserializedrequest.md)* 33 | 34 | ___ 35 | 36 | 37 | ### response 38 | 39 | **● response**: *[ISerializedResponse](_http_serializer_.iserializedresponse.md)* 40 | 41 | ___ 42 | 43 | -------------------------------------------------------------------------------- /docs/interfaces/_http_serializer_.iheaders.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["http-serializer"](../modules/_http_serializer_.md) > [IHeaders](../interfaces/_http_serializer_.iheaders.md) 2 | 3 | # Interface: IHeaders 4 | 5 | ## Hierarchy 6 | 7 | `object` 8 | 9 | **↳ IHeaders** 10 | 11 | ## Index 12 | 13 | --- 14 | 15 | -------------------------------------------------------------------------------- /docs/interfaces/_http_serializer_.iserializedhttp.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["http-serializer"](../modules/_http_serializer_.md) > [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md) 2 | 3 | # Interface: ISerializedHttp 4 | 5 | HTTP request/response serialized in a consistent format 6 | 7 | ## Hierarchy 8 | 9 | `object` & `object` 10 | 11 | **↳ ISerializedHttp** 12 | 13 | ## Index 14 | 15 | --- 16 | 17 | -------------------------------------------------------------------------------- /docs/interfaces/_http_serializer_.iserializedrequest.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["http-serializer"](../modules/_http_serializer_.md) > [ISerializedRequest](../interfaces/_http_serializer_.iserializedrequest.md) 2 | 3 | # Interface: ISerializedRequest 4 | 5 | HTTP response serialized in a consistent format 6 | 7 | ## Hierarchy 8 | 9 | `object` & `object` 10 | 11 | **↳ ISerializedRequest** 12 | 13 | ## Implemented by 14 | 15 | * [RequestSerializer](../classes/_http_serializer_.requestserializer.md) 16 | 17 | ## Index 18 | 19 | --- 20 | 21 | -------------------------------------------------------------------------------- /docs/interfaces/_http_serializer_.iserializedrequestresponse.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["http-serializer"](../modules/_http_serializer_.md) > [ISerializedRequestResponse](../interfaces/_http_serializer_.iserializedrequestresponse.md) 2 | 3 | # Interface: ISerializedRequestResponse 4 | 5 | HTTP request & response 6 | 7 | ## Hierarchy 8 | 9 | **ISerializedRequestResponse** 10 | 11 | ## Index 12 | 13 | ### Properties 14 | 15 | * [request](_http_serializer_.iserializedrequestresponse.md#request) 16 | * [response](_http_serializer_.iserializedrequestresponse.md#response) 17 | 18 | --- 19 | 20 | ## Properties 21 | 22 | 23 | 24 | ### request 25 | 26 | **● request**: *[ISerializedRequest](_http_serializer_.iserializedrequest.md)* 27 | 28 | ___ 29 | 30 | 31 | ### response 32 | 33 | **● response**: *[ISerializedResponse](_http_serializer_.iserializedresponse.md)* 34 | 35 | ___ 36 | 37 | -------------------------------------------------------------------------------- /docs/interfaces/_http_serializer_.iserializedresponse.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["http-serializer"](../modules/_http_serializer_.md) > [ISerializedResponse](../interfaces/_http_serializer_.iserializedresponse.md) 2 | 3 | # Interface: ISerializedResponse 4 | 5 | HTTP request serialized in a consistent format 6 | 7 | ## Hierarchy 8 | 9 | `object` 10 | 11 | **↳ ISerializedResponse** 12 | 13 | ## Implemented by 14 | 15 | * [ResponseSerializer](../classes/_http_serializer_.responseserializer.md) 16 | 17 | ## Index 18 | 19 | --- 20 | 21 | -------------------------------------------------------------------------------- /docs/interfaces/_interceptor_.clientrequesttracker.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["interceptor"](../modules/_interceptor_.md) > [ClientRequestTracker](../interfaces/_interceptor_.clientrequesttracker.md) 2 | 3 | # Interface: ClientRequestTracker 4 | 5 | ## Hierarchy 6 | 7 | **ClientRequestTracker** 8 | 9 | ## Indexable 10 | 11 | \[key: `string`\]: `object` 12 | 13 | clientOptions: `RequestOptions` 14 | 15 | `Optional` clientRequest: [ClientRequestFull](_http_serializer_.clientrequestfull.md) 16 | 17 | ## Index 18 | 19 | --- 20 | 21 | -------------------------------------------------------------------------------- /docs/interfaces/_interceptor_.iinterceptevent.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["interceptor"](../modules/_interceptor_.md) > [IInterceptEvent](../interfaces/_interceptor_.iinterceptevent.md) 2 | 3 | # Interface: IInterceptEvent 4 | 5 | Event emitted whenever we intercept an HTTP request 6 | 7 | ## Hierarchy 8 | 9 | **IInterceptEvent** 10 | 11 | ## Index 12 | 13 | ### Properties 14 | 15 | * [clientRequest](_interceptor_.iinterceptevent.md#clientrequest) 16 | * [interceptedRequest](_interceptor_.iinterceptevent.md#interceptedrequest) 17 | * [interceptedResponse](_interceptor_.iinterceptevent.md#interceptedresponse) 18 | * [proxy](_interceptor_.iinterceptevent.md#proxy) 19 | * [requestNumber](_interceptor_.iinterceptevent.md#requestnumber) 20 | * [requestSerializer](_interceptor_.iinterceptevent.md#requestserializer) 21 | 22 | --- 23 | 24 | ## Properties 25 | 26 | 27 | 28 | ### clientRequest 29 | 30 | **● clientRequest**: *`ClientRequest`* 31 | 32 | The client request which initiated the HTTP request 33 | 34 | ___ 35 | 36 | 37 | ### interceptedRequest 38 | 39 | **● interceptedRequest**: *`IncomingMessage`* 40 | 41 | Request arriving to our MITM proxy 42 | 43 | ___ 44 | 45 | 46 | ### interceptedResponse 47 | 48 | **● interceptedResponse**: *`ServerResponse`* 49 | 50 | Response from our MITM proxy 51 | 52 | ___ 53 | 54 | 55 | ### proxy 56 | 57 | **● proxy**: *`function`* 58 | 59 | Proxy the intercepted request to its original destination 60 | 61 | #### Type declaration 62 | ▸(): `void` 63 | 64 | **Returns:** `void` 65 | 66 | ___ 67 | 68 | 69 | ### requestNumber 70 | 71 | **● requestNumber**: *`number`* 72 | 73 | ___ 74 | 75 | 76 | ### requestSerializer 77 | 78 | **● requestSerializer**: *[RequestSerializer](../classes/_http_serializer_.requestserializer.md)* 79 | 80 | ___ 81 | 82 | -------------------------------------------------------------------------------- /docs/interfaces/_interceptor_.iinterceptevents.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["interceptor"](../modules/_interceptor_.md) > [IInterceptEvents](../interfaces/_interceptor_.iinterceptevents.md) 2 | 3 | # Interface: IInterceptEvents 4 | 5 | ## Hierarchy 6 | 7 | **IInterceptEvents** 8 | 9 | ## Implemented by 10 | 11 | * [Interceptor](../classes/_interceptor_.interceptor.md) 12 | 13 | ## Index 14 | 15 | ### Methods 16 | 17 | * [on](_interceptor_.iinterceptevents.md#on) 18 | 19 | --- 20 | 21 | ## Methods 22 | 23 | 24 | 25 | ### on 26 | 27 | ▸ **on**(event: *"intercept"*, listener: *`function`*): `this` 28 | 29 | ▸ **on**(event: *"proxied"*, listener: *`function`*): `this` 30 | 31 | **Parameters:** 32 | 33 | | Name | Type | 34 | | ------ | ------ | 35 | | event | "intercept" | 36 | | listener | `function` | 37 | 38 | **Returns:** `this` 39 | 40 | **Parameters:** 41 | 42 | | Name | Type | 43 | | ------ | ------ | 44 | | event | "proxied" | 45 | | listener | `function` | 46 | 47 | **Returns:** `this` 48 | 49 | ___ 50 | 51 | -------------------------------------------------------------------------------- /docs/interfaces/_interceptor_.iinterceptoptions.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["interceptor"](../modules/_interceptor_.md) > [IInterceptOptions](../interfaces/_interceptor_.iinterceptoptions.md) 2 | 3 | # Interface: IInterceptOptions 4 | 5 | Configure intercept 6 | 7 | ## Hierarchy 8 | 9 | **IInterceptOptions** 10 | 11 | ↳ [IYesNoInterceptingOptions](_yesno_.iyesnointerceptingoptions.md) 12 | 13 | ## Index 14 | 15 | ### Properties 16 | 17 | * [ignorePorts](_interceptor_.iinterceptoptions.md#ignoreports) 18 | 19 | --- 20 | 21 | ## Properties 22 | 23 | 24 | 25 | ### `` ignorePorts 26 | 27 | **● ignorePorts**: *`number`[]* 28 | 29 | Do not intercept outbound requests on these ports. 30 | 31 | By default MITM will intercept activity on any socket, HTTP or otherwise. If you need to ignore a port (eg for a database connection), provide that port number here. 32 | 33 | In practice YesNo normally runs after long running connections have been established, so this won't be a problem. 34 | 35 | ___ 36 | 37 | -------------------------------------------------------------------------------- /docs/interfaces/_interceptor_.iproxiedevent.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["interceptor"](../modules/_interceptor_.md) > [IProxiedEvent](../interfaces/_interceptor_.iproxiedevent.md) 2 | 3 | # Interface: IProxiedEvent 4 | 5 | Emit whenever we have proxied a request to its original destination 6 | 7 | ## Hierarchy 8 | 9 | **IProxiedEvent** 10 | 11 | ## Index 12 | 13 | ### Properties 14 | 15 | * [requestNumber](_interceptor_.iproxiedevent.md#requestnumber) 16 | * [requestSerializer](_interceptor_.iproxiedevent.md#requestserializer) 17 | * [responseSerializer](_interceptor_.iproxiedevent.md#responseserializer) 18 | 19 | --- 20 | 21 | ## Properties 22 | 23 | 24 | 25 | ### requestNumber 26 | 27 | **● requestNumber**: *`number`* 28 | 29 | ___ 30 | 31 | 32 | ### requestSerializer 33 | 34 | **● requestSerializer**: *[RequestSerializer](../classes/_http_serializer_.requestserializer.md)* 35 | 36 | ___ 37 | 38 | 39 | ### responseSerializer 40 | 41 | **● responseSerializer**: *[ResponseSerializer](../classes/_http_serializer_.responseserializer.md)* 42 | 43 | ___ 44 | 45 | -------------------------------------------------------------------------------- /docs/interfaces/_interceptor_.proxyrequestoptions.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["interceptor"](../modules/_interceptor_.md) > [ProxyRequestOptions](../interfaces/_interceptor_.proxyrequestoptions.md) 2 | 3 | # Interface: ProxyRequestOptions 4 | 5 | ## Hierarchy 6 | 7 | `RequestOptions` 8 | 9 | **↳ ProxyRequestOptions** 10 | 11 | ## Index 12 | 13 | ### Properties 14 | 15 | * [_defaultAgent](_interceptor_.proxyrequestoptions.md#_defaultagent) 16 | * [agent](_interceptor_.proxyrequestoptions.md#agent) 17 | * [auth](_interceptor_.proxyrequestoptions.md#auth) 18 | * [createConnection](_interceptor_.proxyrequestoptions.md#createconnection) 19 | * [defaultPort](_interceptor_.proxyrequestoptions.md#defaultport) 20 | * [family](_interceptor_.proxyrequestoptions.md#family) 21 | * [headers](_interceptor_.proxyrequestoptions.md#headers) 22 | * [host](_interceptor_.proxyrequestoptions.md#host) 23 | * [hostname](_interceptor_.proxyrequestoptions.md#hostname) 24 | * [localAddress](_interceptor_.proxyrequestoptions.md#localaddress) 25 | * [method](_interceptor_.proxyrequestoptions.md#method) 26 | * [path](_interceptor_.proxyrequestoptions.md#path) 27 | * [port](_interceptor_.proxyrequestoptions.md#port) 28 | * [protocol](_interceptor_.proxyrequestoptions.md#protocol) 29 | * [proxying](_interceptor_.proxyrequestoptions.md#proxying) 30 | * [socketPath](_interceptor_.proxyrequestoptions.md#socketpath) 31 | * [timeout](_interceptor_.proxyrequestoptions.md#timeout) 32 | 33 | --- 34 | 35 | ## Properties 36 | 37 | 38 | 39 | ### `` _defaultAgent 40 | 41 | **● _defaultAgent**: *`Agent`* 42 | 43 | ___ 44 | 45 | 46 | ### `` agent 47 | 48 | **● agent**: * `Agent` | `boolean` 49 | * 50 | 51 | ___ 52 | 53 | 54 | ### `` auth 55 | 56 | **● auth**: * `undefined` | `string` 57 | * 58 | 59 | ___ 60 | 61 | 62 | ### `` createConnection 63 | 64 | **● createConnection**: * `undefined` | `function` 65 | * 66 | 67 | ___ 68 | 69 | 70 | ### `` defaultPort 71 | 72 | **● defaultPort**: * `number` | `string` 73 | * 74 | 75 | ___ 76 | 77 | 78 | ### `` family 79 | 80 | **● family**: * `undefined` | `number` 81 | * 82 | 83 | ___ 84 | 85 | 86 | ### `` headers 87 | 88 | **● headers**: *`OutgoingHttpHeaders`* 89 | 90 | ___ 91 | 92 | 93 | ### `` host 94 | 95 | **● host**: * `undefined` | `string` 96 | * 97 | 98 | ___ 99 | 100 | 101 | ### `` hostname 102 | 103 | **● hostname**: * `undefined` | `string` 104 | * 105 | 106 | ___ 107 | 108 | 109 | ### `` localAddress 110 | 111 | **● localAddress**: * `undefined` | `string` 112 | * 113 | 114 | ___ 115 | 116 | 117 | ### `` method 118 | 119 | **● method**: * `undefined` | `string` 120 | * 121 | 122 | ___ 123 | 124 | 125 | ### `` path 126 | 127 | **● path**: * `undefined` | `string` 128 | * 129 | 130 | ___ 131 | 132 | 133 | ### `` port 134 | 135 | **● port**: * `number` | `string` 136 | * 137 | 138 | ___ 139 | 140 | 141 | ### `` protocol 142 | 143 | **● protocol**: * `undefined` | `string` 144 | * 145 | 146 | ___ 147 | 148 | 149 | ### `` proxying 150 | 151 | **● proxying**: * `undefined` | `false` | `true` 152 | * 153 | 154 | ___ 155 | 156 | 157 | ### `` socketPath 158 | 159 | **● socketPath**: * `undefined` | `string` 160 | * 161 | 162 | ___ 163 | 164 | 165 | ### `` timeout 166 | 167 | **● timeout**: * `undefined` | `number` 168 | * 169 | 170 | ___ 171 | 172 | -------------------------------------------------------------------------------- /docs/interfaces/_recording_.irecordingoptions.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["recording"](../modules/_recording_.md) > [IRecordingOptions](../interfaces/_recording_.irecordingoptions.md) 2 | 3 | # Interface: IRecordingOptions 4 | 5 | ## Hierarchy 6 | 7 | [IFileOptions](_file_.ifileoptions.md) 8 | 9 | **↳ IRecordingOptions** 10 | 11 | ## Index 12 | 13 | ### Properties 14 | 15 | * [filename](_recording_.irecordingoptions.md#filename) 16 | * [getRecordsToSave](_recording_.irecordingoptions.md#getrecordstosave) 17 | * [mode](_recording_.irecordingoptions.md#mode) 18 | 19 | --- 20 | 21 | ## Properties 22 | 23 | 24 | 25 | ### filename 26 | 27 | **● filename**: *`string`* 28 | 29 | ___ 30 | 31 | 32 | ### getRecordsToSave 33 | 34 | **● getRecordsToSave**: *`function`* 35 | 36 | Get all recorded HTTP requests we need to save to disc 37 | 38 | #### Type declaration 39 | ▸(): [ISerializedHttp](_http_serializer_.iserializedhttp.md)[] 40 | 41 | **Returns:** [ISerializedHttp](_http_serializer_.iserializedhttp.md)[] 42 | 43 | ___ 44 | 45 | 46 | ### mode 47 | 48 | **● mode**: *[RecordMode](../enums/_recording_.recordmode.md)* 49 | 50 | Current record mode. Determines whether or not we'll save to disc on completion. 51 | 52 | ___ 53 | 54 | -------------------------------------------------------------------------------- /docs/interfaces/_yesno_.irecordabletest.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["yesno"](../modules/_yesno_.md) > [IRecordableTest](../interfaces/_yesno_.irecordabletest.md) 2 | 3 | # Interface: IRecordableTest 4 | 5 | ## Hierarchy 6 | 7 | **IRecordableTest** 8 | 9 | ## Index 10 | 11 | ### Properties 12 | 13 | * [dir](_yesno_.irecordabletest.md#dir) 14 | * [it](_yesno_.irecordabletest.md#it) 15 | * [prefix](_yesno_.irecordabletest.md#prefix) 16 | * [test](_yesno_.irecordabletest.md#test) 17 | 18 | --- 19 | 20 | ## Properties 21 | 22 | 23 | 24 | ### dir 25 | 26 | **● dir**: *`string`* 27 | 28 | ___ 29 | 30 | 31 | ### `` it 32 | 33 | **● it**: *[GenericTestFunction](../modules/_yesno_.md#generictestfunction)* 34 | 35 | ___ 36 | 37 | 38 | ### `` prefix 39 | 40 | **● prefix**: * `undefined` | `string` 41 | * 42 | 43 | ___ 44 | 45 | 46 | ### `` test 47 | 48 | **● test**: *[GenericTestFunction](../modules/_yesno_.md#generictestfunction)* 49 | 50 | ___ 51 | 52 | -------------------------------------------------------------------------------- /docs/interfaces/_yesno_.iyesnointerceptingoptions.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["yesno"](../modules/_yesno_.md) > [IYesNoInterceptingOptions](../interfaces/_yesno_.iyesnointerceptingoptions.md) 2 | 3 | # Interface: IYesNoInterceptingOptions 4 | 5 | Options to configure intercept of HTTP requests 6 | 7 | ## Hierarchy 8 | 9 | [IInterceptOptions](_interceptor_.iinterceptoptions.md) 10 | 11 | **↳ IYesNoInterceptingOptions** 12 | 13 | ## Index 14 | 15 | ### Properties 16 | 17 | * [comparatorFn](_yesno_.iyesnointerceptingoptions.md#comparatorfn) 18 | * [ignorePorts](_yesno_.iyesnointerceptingoptions.md#ignoreports) 19 | 20 | --- 21 | 22 | ## Properties 23 | 24 | 25 | 26 | ### `` comparatorFn 27 | 28 | **● comparatorFn**: *[ComparatorFn](../modules/_filtering_comparator_.md#comparatorfn)* 29 | 30 | Comparator function used to determine whether an intercepted request matches a loaded mock. 31 | 32 | ___ 33 | 34 | 35 | ### `` ignorePorts 36 | 37 | **● ignorePorts**: *`number`[]* 38 | 39 | Do not intercept outbound requests on these ports. 40 | 41 | By default MITM will intercept activity on any socket, HTTP or otherwise. If you need to ignore a port (eg for a database connection), provide that port number here. 42 | 43 | In practice YesNo normally runs after long running connections have been established, so this won't be a problem. 44 | 45 | ___ 46 | 47 | -------------------------------------------------------------------------------- /docs/modules/_consts_.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["consts"](../modules/_consts_.md) 2 | 3 | # External module: "consts" 4 | 5 | ## Index 6 | 7 | ### Variables 8 | 9 | * [DEFAULT_PORT_HTTP](_consts_.md#default_port_http) 10 | * [DEFAULT_PORT_HTTPS](_consts_.md#default_port_https) 11 | * [DEFAULT_REDACT_SYMBOL](_consts_.md#default_redact_symbol) 12 | * [HEADER_CONTENT_TYPE](_consts_.md#header_content_type) 13 | * [MIME_TYPE_JSON](_consts_.md#mime_type_json) 14 | * [YESNO_INTERNAL_HTTP_HEADER](_consts_.md#yesno_internal_http_header) 15 | * [YESNO_RECORDING_MODE_ENV_VAR](_consts_.md#yesno_recording_mode_env_var) 16 | 17 | --- 18 | 19 | ## Variables 20 | 21 | 22 | 23 | ### `` DEFAULT_PORT_HTTP 24 | 25 | **● DEFAULT_PORT_HTTP**: *`number`* = 80 26 | 27 | Default port for outbound HTTP requests 28 | 29 | ___ 30 | 31 | 32 | ### `` DEFAULT_PORT_HTTPS 33 | 34 | **● DEFAULT_PORT_HTTPS**: *`number`* = 443 35 | 36 | Default port for outbound HTTPS requests 37 | 38 | ___ 39 | 40 | 41 | ### `` DEFAULT_REDACT_SYMBOL 42 | 43 | **● DEFAULT_REDACT_SYMBOL**: *`string`* = "*****" 44 | 45 | Default symbol to use when redacting fields from HTTP requests 46 | 47 | ___ 48 | 49 | 50 | ### `` HEADER_CONTENT_TYPE 51 | 52 | **● HEADER_CONTENT_TYPE**: *`string`* = "content-type" 53 | 54 | HTTP content type header 55 | 56 | ___ 57 | 58 | 59 | ### `` MIME_TYPE_JSON 60 | 61 | **● MIME_TYPE_JSON**: *`string`* = "application/json" 62 | 63 | JSON mime type 64 | 65 | ___ 66 | 67 | 68 | ### `` YESNO_INTERNAL_HTTP_HEADER 69 | 70 | **● YESNO_INTERNAL_HTTP_HEADER**: *`string`* = "x-yesno-internal-header-id" 71 | 72 | HTTP header to correlate requests made while intercepting 73 | 74 | ___ 75 | 76 | 77 | ### `` YESNO_RECORDING_MODE_ENV_VAR 78 | 79 | **● YESNO_RECORDING_MODE_ENV_VAR**: *`string`* = "YESNO_RECORDING_MODE" 80 | 81 | Environment variable controlling the current recording mode 82 | 83 | ___ 84 | 85 | -------------------------------------------------------------------------------- /docs/modules/_context_.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["context"](../modules/_context_.md) 2 | 3 | # External module: "context" 4 | 5 | ## Index 6 | 7 | ### Classes 8 | 9 | * [Context](../classes/_context_.context.md) 10 | 11 | ### Interfaces 12 | 13 | * [IInFlightRequest](../interfaces/_context_.iinflightrequest.md) 14 | * [IRedactProp](../interfaces/_context_.iredactprop.md) 15 | * [IResponseForMatchingRequest](../interfaces/_context_.iresponseformatchingrequest.md) 16 | 17 | --- 18 | 19 | -------------------------------------------------------------------------------- /docs/modules/_errors_.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["errors"](../modules/_errors_.md) 2 | 3 | # External module: "errors" 4 | 5 | ## Index 6 | 7 | ### Classes 8 | 9 | * [YesNoError](../classes/_errors_.yesnoerror.md) 10 | 11 | --- 12 | 13 | -------------------------------------------------------------------------------- /docs/modules/_file_.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["file"](../modules/_file_.md) 2 | 3 | # External module: "file" 4 | 5 | ## Index 6 | 7 | ### Interfaces 8 | 9 | * [IFileOptions](../interfaces/_file_.ifileoptions.md) 10 | * [IHttpMock](../interfaces/_file_.ihttpmock.md) 11 | * [IPartialMockRequest](../interfaces/_file_.ipartialmockrequest.md) 12 | * [IPartialMockResponse](../interfaces/_file_.ipartialmockresponse.md) 13 | * [ISaveFile](../interfaces/_file_.isavefile.md) 14 | * [ISaveOptions](../interfaces/_file_.isaveoptions.md) 15 | 16 | ### Variables 17 | 18 | * [debug](_file_.md#debug) 19 | 20 | ### Functions 21 | 22 | * [getMockFilename](_file_.md#getmockfilename) 23 | * [helpMessageMissingMock](_file_.md#helpmessagemissingmock) 24 | * [hydrateHttpMock](_file_.md#hydratehttpmock) 25 | * [load](_file_.md#load) 26 | * [save](_file_.md#save) 27 | 28 | --- 29 | 30 | ## Variables 31 | 32 | 33 | 34 | ### `` debug 35 | 36 | **● debug**: *`IDebugger`* = require('debug')('yesno:mocks') 37 | 38 | ___ 39 | 40 | ## Functions 41 | 42 | 43 | 44 | ### getMockFilename 45 | 46 | ▸ **getMockFilename**(name: *`string`*, dir: *`string`*): `string` 47 | 48 | Get the generated filename for a mock name. 49 | 50 | **Parameters:** 51 | 52 | | Name | Type | 53 | | ------ | ------ | 54 | | name | `string` | 55 | | dir | `string` | 56 | 57 | **Returns:** `string` 58 | 59 | ___ 60 | 61 | 62 | ### helpMessageMissingMock 63 | 64 | ▸ **helpMessageMissingMock**(filename: *`string`*): `string` 65 | 66 | **Parameters:** 67 | 68 | | Name | Type | 69 | | ------ | ------ | 70 | | filename | `string` | 71 | 72 | **Returns:** `string` 73 | 74 | ___ 75 | 76 | 77 | ### hydrateHttpMock 78 | 79 | ▸ **hydrateHttpMock**(mock: *[IHttpMock](../interfaces/_file_.ihttpmock.md)*): [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md) 80 | 81 | **Parameters:** 82 | 83 | | Name | Type | 84 | | ------ | ------ | 85 | | mock | [IHttpMock](../interfaces/_file_.ihttpmock.md) | 86 | 87 | **Returns:** [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md) 88 | 89 | ___ 90 | 91 | 92 | ### load 93 | 94 | ▸ **load**(__namedParameters: *`object`*): `Promise`<[ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md)[]> 95 | 96 | Read mocks from a specified file. 97 | *__throws__*: YesNoError If file is improperly formatted 98 | 99 | **Parameters:** 100 | 101 | **__namedParameters: `object`** 102 | 103 | | Name | Type | 104 | | ------ | ------ | 105 | | filename | `string` | 106 | 107 | **Returns:** `Promise`<[ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md)[]> 108 | 109 | ___ 110 | 111 | 112 | ### save 113 | 114 | ▸ **save**(__namedParameters: *`object`*): `Promise`<`string`> 115 | 116 | Save HTTP records to the specified file 117 | 118 | **Parameters:** 119 | 120 | **__namedParameters: `object`** 121 | 122 | | Name | Type | Default value | 123 | | ------ | ------ | ------ | 124 | | filename | `string` | - | 125 | | records | [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md)[] | [] | 126 | 127 | **Returns:** `Promise`<`string`> 128 | 129 | ___ 130 | 131 | -------------------------------------------------------------------------------- /docs/modules/_filtering_collection_.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["filtering/collection"](../modules/_filtering_collection_.md) 2 | 3 | # External module: "filtering/collection" 4 | 5 | ## Index 6 | 7 | ### Classes 8 | 9 | * [FilteredHttpCollection](../classes/_filtering_collection_.filteredhttpcollection.md) 10 | 11 | ### Interfaces 12 | 13 | * [IFiltered](../interfaces/_filtering_collection_.ifiltered.md) 14 | * [IFilteredHttpCollectionParams](../interfaces/_filtering_collection_.ifilteredhttpcollectionparams.md) 15 | 16 | ### Type aliases 17 | 18 | * [PartialResponse](_filtering_collection_.md#partialresponse) 19 | * [PartialResponseForRequest](_filtering_collection_.md#partialresponseforrequest) 20 | 21 | --- 22 | 23 | ## Type aliases 24 | 25 | 26 | 27 | ### PartialResponse 28 | 29 | **Ƭ PartialResponse**: * `Partial`<[ISerializedResponse](../interfaces/_http_serializer_.iserializedresponse.md)> & `object` 30 | * 31 | 32 | ___ 33 | 34 | 35 | ### PartialResponseForRequest 36 | 37 | **Ƭ PartialResponseForRequest**: * [PartialResponse](_filtering_collection_.md#partialresponse) | `function` 38 | * 39 | 40 | ___ 41 | 42 | -------------------------------------------------------------------------------- /docs/modules/_filtering_comparator_.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["filtering/comparator"](../modules/_filtering_comparator_.md) 2 | 3 | # External module: "filtering/comparator" 4 | 5 | ## Index 6 | 7 | ### Interfaces 8 | 9 | * [IComparatorMetadata](../interfaces/_filtering_comparator_.icomparatormetadata.md) 10 | 11 | ### Type aliases 12 | 13 | * [ComparatorFn](_filtering_comparator_.md#comparatorfn) 14 | 15 | ### Functions 16 | 17 | * [assertEqual](_filtering_comparator_.md#assertequal) 18 | * [byUrl](_filtering_comparator_.md#byurl) 19 | 20 | --- 21 | 22 | ## Type aliases 23 | 24 | 25 | 26 | ### ComparatorFn 27 | 28 | **Ƭ ComparatorFn**: *`function`* 29 | 30 | #### Type declaration 31 | ▸(intercepted: *[ISerializedRequest](../interfaces/_http_serializer_.iserializedrequest.md)*, mock: *[ISerializedRequest](../interfaces/_http_serializer_.iserializedrequest.md)*, metadata: *[IComparatorMetadata](../interfaces/_filtering_comparator_.icomparatormetadata.md)*): `boolean` 32 | 33 | **Parameters:** 34 | 35 | | Name | Type | 36 | | ------ | ------ | 37 | | intercepted | [ISerializedRequest](../interfaces/_http_serializer_.iserializedrequest.md) | 38 | | mock | [ISerializedRequest](../interfaces/_http_serializer_.iserializedrequest.md) | 39 | | metadata | [IComparatorMetadata](../interfaces/_filtering_comparator_.icomparatormetadata.md) | 40 | 41 | **Returns:** `boolean` 42 | 43 | ___ 44 | 45 | ## Functions 46 | 47 | 48 | 49 | ### assertEqual 50 | 51 | ▸ **assertEqual**<`T`>(unknown: *`T`*, known: *`T`*, message: *`string`*): `void` 52 | 53 | **Type parameters:** 54 | 55 | #### T 56 | **Parameters:** 57 | 58 | | Name | Type | 59 | | ------ | ------ | 60 | | unknown | `T` | 61 | | known | `T` | 62 | | message | `string` | 63 | 64 | **Returns:** `void` 65 | 66 | ___ 67 | 68 | 69 | ### `` byUrl 70 | 71 | ▸ **byUrl**(interceptedRequest: *[ISerializedRequest](../interfaces/_http_serializer_.iserializedrequest.md)*, mockRequest: *[ISerializedRequest](../interfaces/_http_serializer_.iserializedrequest.md)*, __namedParameters: *`object`*): `boolean` 72 | 73 | **Parameters:** 74 | 75 | **interceptedRequest: [ISerializedRequest](../interfaces/_http_serializer_.iserializedrequest.md)** 76 | 77 | **mockRequest: [ISerializedRequest](../interfaces/_http_serializer_.iserializedrequest.md)** 78 | 79 | **__namedParameters: `object`** 80 | 81 | | Name | Type | 82 | | ------ | ------ | 83 | | requestIndex | `number` | 84 | 85 | **Returns:** `boolean` 86 | 87 | ___ 88 | 89 | -------------------------------------------------------------------------------- /docs/modules/_filtering_matcher_.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["filtering/matcher"](../modules/_filtering_matcher_.md) 2 | 3 | # External module: "filtering/matcher" 4 | 5 | ## Index 6 | 7 | ### Interfaces 8 | 9 | * [ISerializedHttpPartialDeepMatch](../interfaces/_filtering_matcher_.iserializedhttppartialdeepmatch.md) 10 | * [ISerializedRequestResponseToMatch](../interfaces/_filtering_matcher_.iserializedrequestresponsetomatch.md) 11 | 12 | ### Type aliases 13 | 14 | * [MatchFn](_filtering_matcher_.md#matchfn) 15 | * [Matcher](_filtering_matcher_.md#matcher) 16 | * [RequestQuery](_filtering_matcher_.md#requestquery) 17 | * [ResponseQuery](_filtering_matcher_.md#responsequery) 18 | * [UnsafeMatchFn](_filtering_matcher_.md#unsafematchfn) 19 | 20 | ### Functions 21 | 22 | * [match](_filtering_matcher_.md#match) 23 | 24 | ### Object literals 25 | 26 | * [EMPTY_RESPONSE](_filtering_matcher_.md#empty_response) 27 | 28 | --- 29 | 30 | ## Type aliases 31 | 32 | 33 | 34 | ### MatchFn 35 | 36 | **Ƭ MatchFn**: *`function`* 37 | 38 | #### Type declaration 39 | ▸(serialized: *[ISerializedRequestResponse](../interfaces/_http_serializer_.iserializedrequestresponse.md)*): `boolean` 40 | 41 | **Parameters:** 42 | 43 | | Name | Type | 44 | | ------ | ------ | 45 | | serialized | [ISerializedRequestResponse](../interfaces/_http_serializer_.iserializedrequestresponse.md) | 46 | 47 | **Returns:** `boolean` 48 | 49 | ___ 50 | 51 | 52 | ### Matcher 53 | 54 | **Ƭ Matcher**: * [ISerializedHttpPartialDeepMatch](../interfaces/_filtering_matcher_.iserializedhttppartialdeepmatch.md) | [MatchFn](_filtering_matcher_.md#matchfn) 55 | * 56 | 57 | ___ 58 | 59 | 60 | ### RequestQuery 61 | 62 | **Ƭ RequestQuery**: *`object`* 63 | 64 | #### Type declaration 65 | 66 | ___ 67 | 68 | 69 | ### ResponseQuery 70 | 71 | **Ƭ ResponseQuery**: *`object`* 72 | 73 | #### Type declaration 74 | 75 | ___ 76 | 77 | 78 | ### UnsafeMatchFn 79 | 80 | **Ƭ UnsafeMatchFn**: *`function`* 81 | 82 | #### Type declaration 83 | ▸(serialized: *[ISerializedRequestResponseToMatch](../interfaces/_filtering_matcher_.iserializedrequestresponsetomatch.md)*): `boolean` 84 | 85 | **Parameters:** 86 | 87 | | Name | Type | 88 | | ------ | ------ | 89 | | serialized | [ISerializedRequestResponseToMatch](../interfaces/_filtering_matcher_.iserializedrequestresponsetomatch.md) | 90 | 91 | **Returns:** `boolean` 92 | 93 | ___ 94 | 95 | ## Functions 96 | 97 | 98 | 99 | ### match 100 | 101 | ▸ **match**(fnOrPartialMatch: * [ISerializedHttpPartialDeepMatch](../interfaces/_filtering_matcher_.iserializedhttppartialdeepmatch.md) | [MatchFn](_filtering_matcher_.md#matchfn)*): [UnsafeMatchFn](_filtering_matcher_.md#unsafematchfn) 102 | 103 | Curried function to determine whether a query matches an intercepted request. 104 | 105 | Query objects must be a deep partial match against the intercepted request. 106 | 107 | RegEx values are tested for match. 108 | 109 | **Parameters:** 110 | 111 | | Name | Type | 112 | | ------ | ------ | 113 | | fnOrPartialMatch | [ISerializedHttpPartialDeepMatch](../interfaces/_filtering_matcher_.iserializedhttppartialdeepmatch.md) | [MatchFn](_filtering_matcher_.md#matchfn)| 114 | 115 | **Returns:** [UnsafeMatchFn](_filtering_matcher_.md#unsafematchfn) 116 | 117 | ___ 118 | 119 | ## Object literals 120 | 121 | 122 | 123 | ### `` EMPTY_RESPONSE 124 | 125 | **EMPTY_RESPONSE**: *`object`* 126 | 127 | 128 | 129 | #### body 130 | 131 | **● body**: *`object`* 132 | 133 | #### Type declaration 134 | 135 | ___ 136 | 137 | 138 | #### headers 139 | 140 | **● headers**: *`object`* 141 | 142 | #### Type declaration 143 | 144 | ___ 145 | 146 | 147 | #### statusCode 148 | 149 | **● statusCode**: *`number`* = 0 150 | 151 | ___ 152 | 153 | ___ 154 | 155 | -------------------------------------------------------------------------------- /docs/modules/_filtering_redact_.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["filtering/redact"](../modules/_filtering_redact_.md) 2 | 3 | # External module: "filtering/redact" 4 | 5 | ## Index 6 | 7 | ### Type aliases 8 | 9 | * [Redactor](_filtering_redact_.md#redactor) 10 | 11 | ### Functions 12 | 13 | * [defaultRedactor](_filtering_redact_.md#defaultredactor) 14 | * [redact](_filtering_redact_.md#redact) 15 | 16 | --- 17 | 18 | ## Type aliases 19 | 20 | 21 | 22 | ### Redactor 23 | 24 | **Ƭ Redactor**: *`function`* 25 | 26 | #### Type declaration 27 | ▸(value: *`any`*, path: *`string`*): `string` 28 | 29 | **Parameters:** 30 | 31 | | Name | Type | 32 | | ------ | ------ | 33 | | value | `any` | 34 | | path | `string` | 35 | 36 | **Returns:** `string` 37 | 38 | ___ 39 | 40 | ## Functions 41 | 42 | 43 | 44 | ### defaultRedactor 45 | 46 | ▸ **defaultRedactor**(): `string` 47 | 48 | **Returns:** `string` 49 | 50 | ___ 51 | 52 | 53 | ### redact 54 | 55 | ▸ **redact**(record: *[ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md)*, properties: *`string`[]*, redactor?: *[Redactor](_filtering_redact_.md#redactor)*): [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md) 56 | 57 | Redact properties on the matching intercepted records. Note that header names are forced to lower case. 58 | 59 | If you want to redact a property in the saved mock file, run yesno.redact after a request has been made in spy mode to redact the specified properties in the intercepted requests to save. 60 | 61 | If you want incoming requests in mock mode to be redacted to match the saved mocks, run yesno.redact before any requests have been made to redact the specified properties on all intercepted requests. 62 | 63 | Use a `.` to reference a nested property 64 | *__todo__*: Benchmark & investigate alternatives 65 | 66 | **Parameters:** 67 | 68 | | Name | Type | Default value | 69 | | ------ | ------ | ------ | 70 | | record | [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md) | - | 71 | | properties | `string`[] | - | 72 | | `Default value` redactor | [Redactor](_filtering_redact_.md#redactor) | defaultRedactor | 73 | 74 | **Returns:** [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md) 75 | 76 | ___ 77 | 78 | -------------------------------------------------------------------------------- /docs/modules/_http_serializer_.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["http-serializer"](../modules/_http_serializer_.md) 2 | 3 | # External module: "http-serializer" 4 | 5 | ## Index 6 | 7 | ### Classes 8 | 9 | * [RequestSerializer](../classes/_http_serializer_.requestserializer.md) 10 | * [ResponseSerializer](../classes/_http_serializer_.responseserializer.md) 11 | 12 | ### Interfaces 13 | 14 | * [ClientRequestFull](../interfaces/_http_serializer_.clientrequestfull.md) 15 | * [ICreateRecord](../interfaces/_http_serializer_.icreaterecord.md) 16 | * [IHeaders](../interfaces/_http_serializer_.iheaders.md) 17 | * [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md) 18 | * [ISerializedRequest](../interfaces/_http_serializer_.iserializedrequest.md) 19 | * [ISerializedRequestResponse](../interfaces/_http_serializer_.iserializedrequestresponse.md) 20 | * [ISerializedResponse](../interfaces/_http_serializer_.iserializedresponse.md) 21 | 22 | ### Variables 23 | 24 | * [Headers](_http_serializer_.md#headers) 25 | * [SCHEMA_VERSION](_http_serializer_.md#schema_version) 26 | * [SerializedHttp](_http_serializer_.md#serializedhttp) 27 | * [SerializedHttpOptional](_http_serializer_.md#serializedhttpoptional) 28 | * [SerializedRequest](_http_serializer_.md#serializedrequest) 29 | * [SerializedRequestOptional](_http_serializer_.md#serializedrequestoptional) 30 | * [SerializedResponse](_http_serializer_.md#serializedresponse) 31 | * [debug](_http_serializer_.md#debug) 32 | 33 | ### Functions 34 | 35 | * [createRecord](_http_serializer_.md#createrecord) 36 | * [formatUrl](_http_serializer_.md#formaturl) 37 | * [serializeJSON](_http_serializer_.md#serializejson) 38 | * [validateSerializedHttpArray](_http_serializer_.md#validateserializedhttparray) 39 | 40 | --- 41 | 42 | ## Variables 43 | 44 | 45 | 46 | ### `` Headers 47 | 48 | **● Headers**: *`DictionaryType`<`StringType`, `UnionType`<( `NumberType` | `StringType` | `ArrayType`<`StringType`, `string`[], `string`[], `unknown`> | `UndefinedType`)[], `undefined` | `string` | `number` | `string`[], `undefined` | `string` | `number` | `string`[], `unknown`>, `object`, `object`, `unknown`>* = t.dictionary( 49 | t.string, 50 | t.union([t.number, t.string, t.array(t.string), t.undefined]), 51 | ) 52 | 53 | ___ 54 | 55 | 56 | ### `` SCHEMA_VERSION 57 | 58 | **● SCHEMA_VERSION**: *"1.0.0"* = "1.0.0" 59 | 60 | ___ 61 | 62 | 63 | ### `` SerializedHttp 64 | 65 | **● SerializedHttp**: *`IntersectionType`<`Object`, `object` & `object`, `object` & `object`, `unknown`>* = t.intersection([ 66 | SerializedHttpOptional, 67 | t.interface({ 68 | __id: t.readonly(t.string), 69 | __version: t.readonly(t.string), 70 | request: SerializedRequest, 71 | response: SerializedResponse, 72 | }), 73 | ]) 74 | 75 | ___ 76 | 77 | 78 | ### `` SerializedHttpOptional 79 | 80 | **● SerializedHttpOptional**: *`PartialType`<`object`, `object`, `object`, `unknown`>* = t.partial({ 81 | __duration: t.readonly(t.number), // Optional 82 | __timestamp: t.readonly(t.number), // Optional 83 | }) 84 | 85 | ___ 86 | 87 | 88 | ### `` SerializedRequest 89 | 90 | **● SerializedRequest**: *`IntersectionType`<`Object`, `object` & `object`, `object` & `object`, `unknown`>* = t.intersection([ 91 | t.interface({ 92 | headers: t.readonly(Headers), 93 | host: t.readonly(t.string), 94 | method: t.readonly(t.string), 95 | path: t.readonly(t.string), 96 | port: t.readonly(t.Integer), 97 | protocol: t.readonly(t.union([t.literal('http'), t.literal('https')])), 98 | }), 99 | SerializedRequestOptional, 100 | ]) 101 | 102 | ___ 103 | 104 | 105 | ### `` SerializedRequestOptional 106 | 107 | **● SerializedRequestOptional**: *`PartialType`<`object`, `object`, `object`, `unknown`>* = t.partial({ 108 | body: t.readonly(t.union([t.string, t.object])), // Optional 109 | query: t.readonly(t.string), // Optional 110 | }) 111 | 112 | ___ 113 | 114 | 115 | ### `` SerializedResponse 116 | 117 | **● SerializedResponse**: *`InterfaceType`<`object`, `object`, `object`, `unknown`>* = t.interface({ 118 | body: t.readonly(t.union([t.string, t.object])), 119 | headers: t.readonly(Headers), 120 | statusCode: t.readonly(t.Integer), 121 | }) 122 | 123 | ___ 124 | 125 | 126 | ### `` debug 127 | 128 | **● debug**: *`any`* = require('debug')('yesno:http-serializer') 129 | 130 | ___ 131 | 132 | ## Functions 133 | 134 | 135 | 136 | ### createRecord 137 | 138 | ▸ **createRecord**(__namedParameters: *`object`*): [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md) 139 | 140 | Create record for an HTTP request, which may be saved in a mock file. 141 | 142 | **Parameters:** 143 | 144 | **__namedParameters: `object`** 145 | 146 | | Name | Type | 147 | | ------ | ------ | 148 | | duration | `number` | 149 | | request | [ISerializedRequest](../interfaces/_http_serializer_.iserializedrequest.md) | 150 | | response | [ISerializedResponse](../interfaces/_http_serializer_.iserializedresponse.md) | 151 | 152 | **Returns:** [ISerializedHttp](../interfaces/_http_serializer_.iserializedhttp.md) 153 | 154 | ___ 155 | 156 | 157 | ### formatUrl 158 | 159 | ▸ **formatUrl**(request: *[ISerializedRequest](../interfaces/_http_serializer_.iserializedrequest.md)*, includePort?: *`boolean`*): `string` 160 | 161 | **Parameters:** 162 | 163 | | Name | Type | Default value | 164 | | ------ | ------ | ------ | 165 | | request | [ISerializedRequest](../interfaces/_http_serializer_.iserializedrequest.md) | - | 166 | | `Default value` includePort | `boolean` | false | 167 | 168 | **Returns:** `string` 169 | 170 | ___ 171 | 172 | 173 | ### serializeJSON 174 | 175 | ▸ **serializeJSON**(headers: *[IHeaders](../interfaces/_http_serializer_.iheaders.md)*, body?: * `undefined` | `string`*): `undefined` | `string` | `any` 176 | 177 | **Parameters:** 178 | 179 | | Name | Type | 180 | | ------ | ------ | 181 | | headers | [IHeaders](../interfaces/_http_serializer_.iheaders.md) | 182 | | `Optional` body | `undefined` | `string`| 183 | 184 | **Returns:** `undefined` | `string` | `any` 185 | 186 | ___ 187 | 188 | 189 | ### validateSerializedHttpArray 190 | 191 | ▸ **validateSerializedHttpArray**(records: *`any`[]*): `void` 192 | 193 | **Parameters:** 194 | 195 | | Name | Type | 196 | | ------ | ------ | 197 | | records | `any`[] | 198 | 199 | **Returns:** `void` 200 | 201 | ___ 202 | 203 | -------------------------------------------------------------------------------- /docs/modules/_index_.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["index"](../modules/_index_.md) 2 | 3 | # External module: "index" 4 | 5 | ## Index 6 | 7 | ### Variables 8 | 9 | * [yesno](_index_.md#yesno) 10 | 11 | --- 12 | 13 | ## Variables 14 | 15 | 16 | 17 | ### `` yesno 18 | 19 | **● yesno**: *[YesNo](../classes/_yesno_.yesno.md)* = new YesNo(new Context()) 20 | 21 | ___ 22 | 23 | -------------------------------------------------------------------------------- /docs/modules/_interceptor_.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["interceptor"](../modules/_interceptor_.md) 2 | 3 | # External module: "interceptor" 4 | 5 | ## Index 6 | 7 | ### Classes 8 | 9 | * [Interceptor](../classes/_interceptor_.interceptor.md) 10 | 11 | ### Interfaces 12 | 13 | * [ClientRequestTracker](../interfaces/_interceptor_.clientrequesttracker.md) 14 | * [IInterceptEvent](../interfaces/_interceptor_.iinterceptevent.md) 15 | * [IInterceptEvents](../interfaces/_interceptor_.iinterceptevents.md) 16 | * [IInterceptOptions](../interfaces/_interceptor_.iinterceptoptions.md) 17 | * [IProxiedEvent](../interfaces/_interceptor_.iproxiedevent.md) 18 | * [ProxyRequestOptions](../interfaces/_interceptor_.proxyrequestoptions.md) 19 | * [RegisteredSocket](../interfaces/_interceptor_.registeredsocket.md) 20 | 21 | ### Variables 22 | 23 | * [debug](_interceptor_.md#debug) 24 | 25 | --- 26 | 27 | ## Variables 28 | 29 | 30 | 31 | ### `` debug 32 | 33 | **● debug**: *`IDebugger`* = require('debug')('yesno:proxy') 34 | 35 | ___ 36 | 37 | -------------------------------------------------------------------------------- /docs/modules/_mock_response_.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["mock-response"](../modules/_mock_response_.md) 2 | 3 | # External module: "mock-response" 4 | 5 | ## Index 6 | 7 | ### Classes 8 | 9 | * [MockResponse](../classes/_mock_response_.mockresponse.md) 10 | 11 | ### Variables 12 | 13 | * [debug](_mock_response_.md#debug) 14 | 15 | --- 16 | 17 | ## Variables 18 | 19 | 20 | 21 | ### `` debug 22 | 23 | **● debug**: *`IDebugger`* = require('debug')('yesno:mock-response') 24 | 25 | ___ 26 | 27 | -------------------------------------------------------------------------------- /docs/modules/_recording_.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["recording"](../modules/_recording_.md) 2 | 3 | # External module: "recording" 4 | 5 | ## Index 6 | 7 | ### Enumerations 8 | 9 | * [RecordMode](../enums/_recording_.recordmode.md) 10 | 11 | ### Classes 12 | 13 | * [Recording](../classes/_recording_.recording.md) 14 | 15 | ### Interfaces 16 | 17 | * [IRecordingOptions](../interfaces/_recording_.irecordingoptions.md) 18 | 19 | ### Variables 20 | 21 | * [debug](_recording_.md#debug) 22 | 23 | --- 24 | 25 | ## Variables 26 | 27 | 28 | 29 | ### `` debug 30 | 31 | **● debug**: *`IDebugger`* = require('debug')('yesno:recording') 32 | 33 | ___ 34 | 35 | -------------------------------------------------------------------------------- /docs/modules/_yesno_.md: -------------------------------------------------------------------------------- 1 | [yesno-http](../README.md) > ["yesno"](../modules/_yesno_.md) 2 | 3 | # External module: "yesno" 4 | 5 | ## Index 6 | 7 | ### Classes 8 | 9 | * [YesNo](../classes/_yesno_.yesno.md) 10 | 11 | ### Interfaces 12 | 13 | * [IRecordableTest](../interfaces/_yesno_.irecordabletest.md) 14 | * [IYesNoInterceptingOptions](../interfaces/_yesno_.iyesnointerceptingoptions.md) 15 | 16 | ### Type aliases 17 | 18 | * [GenericTest](_yesno_.md#generictest) 19 | * [GenericTestFunction](_yesno_.md#generictestfunction) 20 | * [HttpFilter](_yesno_.md#httpfilter) 21 | 22 | ### Variables 23 | 24 | * [debug](_yesno_.md#debug) 25 | 26 | --- 27 | 28 | ## Type aliases 29 | 30 | 31 | 32 | ### GenericTest 33 | 34 | **Ƭ GenericTest**: *`function`* 35 | 36 | #### Type declaration 37 | ▸(...args: *`any`*): `Promise`<`any`> | `void` 38 | 39 | **Parameters:** 40 | 41 | | Name | Type | 42 | | ------ | ------ | 43 | | `Rest` args | `any` | 44 | 45 | **Returns:** `Promise`<`any`> | `void` 46 | 47 | ___ 48 | 49 | 50 | ### GenericTestFunction 51 | 52 | **Ƭ GenericTestFunction**: *`function`* 53 | 54 | #### Type declaration 55 | ▸(title: *`string`*, fn: *[GenericTest](_yesno_.md#generictest)*): `any` 56 | 57 | **Parameters:** 58 | 59 | | Name | Type | 60 | | ------ | ------ | 61 | | title | `string` | 62 | | fn | [GenericTest](_yesno_.md#generictest) | 63 | 64 | **Returns:** `any` 65 | 66 | ___ 67 | 68 | 69 | ### HttpFilter 70 | 71 | **Ƭ HttpFilter**: * `string` | `RegExp` | [ISerializedHttpPartialDeepMatch](../interfaces/_filtering_matcher_.iserializedhttppartialdeepmatch.md) | [MatchFn](_filtering_matcher_.md#matchfn) 72 | * 73 | 74 | ___ 75 | 76 | ## Variables 77 | 78 | 79 | 80 | ### `` debug 81 | 82 | **● debug**: *`IDebugger`* = require('debug')('yesno') 83 | 84 | ___ 85 | 86 | -------------------------------------------------------------------------------- /examples/mocks/recorded-tests-my-api-should-respond-401-for-an-invalid-token-yesno.json: -------------------------------------------------------------------------------- 1 | { 2 | "records": [ 3 | { 4 | "__duration": 3, 5 | "__id": "355ff617-3921-4209-9afb-e4c71d69acac", 6 | "__timestamp": 1543298161231, 7 | "__version": "1.0.0", 8 | "request": { 9 | "headers": { 10 | "authorization": "foobar", 11 | "host": "localhost:3000", 12 | "accept": "application/json" 13 | }, 14 | "host": "localhost", 15 | "method": "GET", 16 | "path": "/api/me", 17 | "port": 3000, 18 | "protocol": "http" 19 | }, 20 | "response": { 21 | "body": { 22 | "error": { 23 | "message": "Invalid API key" 24 | } 25 | }, 26 | "headers": { 27 | "x-powered-by": "Express", 28 | "content-type": "application/json; charset=utf-8", 29 | "content-length": "39", 30 | "etag": "W/\"27-8wHwtoK+dGN26Hnc3611Etra53E\"", 31 | "date": "Tue, 27 Nov 2018 05:56:01 GMT", 32 | "connection": "close" 33 | }, 34 | "statusCode": 401 35 | } 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /examples/mocks/recorded-tests-my-api-should-respond-with-the-current-user-yesno.json: -------------------------------------------------------------------------------- 1 | { 2 | "records": [ 3 | { 4 | "__duration": 19, 5 | "__id": "d14ec4dc-b7e0-4225-b8b0-d01dec0c29be", 6 | "__timestamp": 1543298161188, 7 | "__version": "1.0.0", 8 | "request": { 9 | "body": { 10 | "password": "example-password", 11 | "username": "example-username" 12 | }, 13 | "headers": { 14 | "host": "localhost:3000", 15 | "accept": "application/json", 16 | "content-type": "application/json", 17 | "content-length": 61 18 | }, 19 | "host": "localhost", 20 | "method": "POST", 21 | "path": "/api/login", 22 | "port": 3000, 23 | "protocol": "http" 24 | }, 25 | "response": { 26 | "body": { 27 | "data": { 28 | "token": "*****" 29 | } 30 | }, 31 | "headers": { 32 | "x-powered-by": "Express", 33 | "content-type": "application/json; charset=utf-8", 34 | "content-length": "57", 35 | "etag": "W/\"39-5iTPmQdaaC7DNfkzt/cHPyb/MRE\"", 36 | "date": "Tue, 27 Nov 2018 05:56:01 GMT", 37 | "connection": "close" 38 | }, 39 | "statusCode": 201 40 | } 41 | }, 42 | { 43 | "__duration": 9, 44 | "__id": "6e691aa5-3d7a-4897-9c2c-0277aca21b18", 45 | "__timestamp": 1543298161211, 46 | "__version": "1.0.0", 47 | "request": { 48 | "headers": { 49 | "authorization": "*****", 50 | "host": "localhost:3000", 51 | "accept": "application/json" 52 | }, 53 | "host": "localhost", 54 | "method": "GET", 55 | "path": "/api/me", 56 | "port": 3000, 57 | "protocol": "http" 58 | }, 59 | "response": { 60 | "body": { 61 | "data": { 62 | "id": 1, 63 | "password": "example-password", 64 | "username": "example-username" 65 | } 66 | }, 67 | "headers": { 68 | "x-powered-by": "Express", 69 | "content-type": "application/json; charset=utf-8", 70 | "content-length": "77", 71 | "etag": "W/\"4d-G0F5LRUo1DA+P5QyUGb5pd7Axtk\"", 72 | "date": "Tue, 27 Nov 2018 05:56:01 GMT", 73 | "connection": "close" 74 | }, 75 | "statusCode": 200 76 | } 77 | } 78 | ] 79 | } 80 | -------------------------------------------------------------------------------- /examples/recorded-tests.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import yesno from '../src'; 3 | import * as apiClient from './src/api-client'; 4 | 5 | describe('my api', () => { 6 | const itRecorded = yesno.test({ 7 | dir: `${__dirname}/mocks`, 8 | it, 9 | prefix: 'recorded-tests-my-api', 10 | }); 11 | 12 | describe('/api/me', () => { 13 | const username = 'example-username'; 14 | const password = 'example-password'; 15 | 16 | itRecorded('should respond with the current user', async () => { 17 | const token = await apiClient.login(username, password); 18 | const user = await apiClient.getCurrentUser(token); 19 | 20 | yesno.matching(/login/).redact(['response.body.data.token', 'request.body.token']); 21 | yesno.matching(/api\/me/).redact(['request.headers.authorization']); 22 | 23 | expect(yesno.intercepted()).to.have.lengthOf(2); 24 | expect(user).to.eql((yesno.matching(/api\/me/).response().body as any).data); 25 | }); 26 | 27 | itRecorded('should respond 401 for an invalid token', async () => { 28 | await expect(apiClient.getCurrentUser('foobar')).to.be.rejected; 29 | expect(yesno.matching(/me/).response()).to.have.nested.property('statusCode', 401); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /examples/spy-and-mock-tests.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { StatusCodeError } from 'request-promise/errors'; 3 | import yesno from '../src'; 4 | import * as apiClient from './src/api-client'; 5 | 6 | describe('my api', () => { 7 | beforeEach(() => yesno.spy({ ignorePorts: [9999] })); 8 | afterEach(() => yesno.restore()); 9 | 10 | describe('/api/me', () => { 11 | const username = 'example-username'; 12 | const password = 'example-password'; 13 | 14 | it('should respond with the current user', async () => { 15 | const token = await apiClient.login(username, password); 16 | const user = await apiClient.getCurrentUser(token); 17 | 18 | expect(yesno.intercepted()).to.have.lengthOf(2); 19 | expect(yesno.matching(/api\/me/).response().statusCode).to.eql(200); 20 | expect(user).to.eql((yesno.matching(/api\/me/).response().body as any).data); 21 | }); 22 | 23 | it('should respond 401 for an invalid token', async () => { 24 | await expect(apiClient.getCurrentUser('foobar')).to.be.rejected; 25 | expect(yesno.matching(/me/).response()).to.have.nested.property('statusCode', 401); 26 | }); 27 | 28 | it('should reject for a 500 response', async () => { 29 | yesno.mock([ 30 | { 31 | request: { 32 | host: 'localhost', 33 | method: 'GET', 34 | path: '/api/me', 35 | port: 3000, 36 | protocol: 'http', 37 | }, 38 | response: { 39 | body: 'Mock failure', 40 | statusCode: 500, 41 | }, 42 | }, 43 | ]); 44 | 45 | await expect(apiClient.getCurrentUser('any-token')).to.be.rejectedWith( 46 | '500 - "Mock failure"', 47 | ); 48 | expect(yesno.matching(/me/).response()).to.have.nested.property('statusCode', 500); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /examples/src/api-client.ts: -------------------------------------------------------------------------------- 1 | import rp from 'request-promise'; 2 | const debug = require('debug')('yesno:example-api-client'); 3 | 4 | const API_ENDPOINT = 'http://localhost:3000/api'; 5 | 6 | interface IUser { 7 | id: number; 8 | username: string; 9 | password: string; 10 | } 11 | 12 | export async function login(username: string, password: string): Promise { 13 | const { data } = await rp.post({ 14 | body: { 15 | password, 16 | username, 17 | }, 18 | json: true, 19 | uri: `${API_ENDPOINT}/login`, 20 | }); 21 | 22 | return data.token; 23 | } 24 | 25 | export async function getCurrentUser(token: string): Promise { 26 | const { data: user } = await rp.get({ 27 | headers: { 28 | authorization: token, 29 | }, 30 | json: true, 31 | uri: `${API_ENDPOINT}/me`, 32 | }); 33 | 34 | return user; 35 | } 36 | -------------------------------------------------------------------------------- /examples/src/api-server.ts: -------------------------------------------------------------------------------- 1 | import { json as jsonParser } from 'body-parser'; 2 | import express from 'express'; 3 | import { Server } from 'http'; 4 | const debug = require('debug')('yesno:example-api-server'); 5 | import { v4 as uuidV4 } from 'uuid'; 6 | 7 | export const VALID_USERNAME = 'example-username'; 8 | export const VALID_PASSWORD = 'example-password'; 9 | const usersByUsername: { [key: string]: object } = { 10 | [VALID_USERNAME]: { 11 | id: 1, 12 | password: VALID_PASSWORD, 13 | username: VALID_USERNAME, 14 | }, 15 | }; 16 | const tokens: { [key: string]: string } = {}; 17 | 18 | export const PORT = 3000; 19 | 20 | export interface ITestServer extends Server { 21 | getRequestCount: () => number; 22 | } 23 | 24 | export function start(port: number = PORT): Promise { 25 | let requestCount: number = 0; 26 | const app = express(); 27 | app.use(jsonParser()); 28 | 29 | app.use((req, res, next) => { 30 | debug(`Request: ${req.method} ${req.path}`); 31 | requestCount++; 32 | next(); 33 | }); 34 | 35 | app.post('/api/login', ({ body }: express.Request, res: express.Response) => { 36 | const { username, password } = body; 37 | if (!username || !password) { 38 | return res.status(400).send({ error: { message: 'Invalid request' } }); 39 | } 40 | 41 | if (username !== VALID_USERNAME || password !== VALID_PASSWORD) { 42 | return res.status(401).send({ error: { message: 'Incorrect username/password' } }); 43 | } 44 | 45 | const token = uuidV4(); 46 | tokens[token] = username; 47 | 48 | res.status(201).send({ data: { token } }); 49 | }); 50 | 51 | app.use('/api/*', (req, res, next) => { 52 | const token = req.headers.authorization; 53 | if (!token || !tokens[token]) { 54 | return res.status(401).send({ error: { message: 'Invalid API key' } }); 55 | } 56 | 57 | next(); 58 | }); 59 | 60 | app.get('/api/me', (req: express.Request, res: express.Response) => { 61 | const token = req.headers.authorization; 62 | 63 | if (!token) { 64 | return res.status(400).send({ error: { message: 'Missing token' } }); 65 | } 66 | 67 | const user = usersByUsername[tokens[token]]; 68 | 69 | if (!user) { 70 | debug('User missing'); 71 | return res.status(500).send({ error: { message: 'Failed to retrieve user for token' } }); 72 | } 73 | 74 | res.send({ data: user }); 75 | }); 76 | 77 | return new Promise((resolve) => { 78 | const server = app.listen(port, () => { 79 | debug('Test server running on port %d', port); 80 | (server as any).getRequestCount = () => requestCount; 81 | resolve(server as ITestServer); 82 | }); 83 | }); 84 | } 85 | 86 | if (!module.parent) { 87 | (async () => await start())(); 88 | } 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yesno-http", 3 | "version": "0.0.7", 4 | "description": "Easy HTTP testing", 5 | "main": "dist/src/index.js", 6 | "types": "dist/src/index.d.ts", 7 | "bin": { 8 | "yesno": "dist/scripts/cli.js" 9 | }, 10 | "engines": { 11 | "node": ">=8.0.0" 12 | }, 13 | "scripts": { 14 | "preversion": "npm run clean && npm run lint && npm run prettier && npm run tests", 15 | "version": "npm run compile && npm run docs", 16 | "clean": "rimraf dist", 17 | "compile": "tsc", 18 | "watch": "npm run compile -- -w", 19 | "test": "npm run lint && npm run coverage", 20 | "codecov": "nyc report --reporter=text-lcov -- mocha --timeout 2000 \"test/unit/**/*.spec.ts\" > coverage/coverage.lcov && codecov -t 5b8d8ce1-89c5-47c7-94e9-84f6d78afab3", 21 | "coverage": "nyc -- mocha --timeout 2000 \"test/unit/**/*.spec.ts\"", 22 | "tests": "npm run unit && npm run integration", 23 | "lint": "tslint \"src/**/*.ts\" \"test/**/*.ts\"", 24 | "prettier": "cat .gitignore .prettierignore > .gitprettierignore && prettier --write --config .prettierrc \"**/*.{ts,scss,json}\" --ignore-path .gitprettierignore", 25 | "unit": "mocha --timeout 2000 \"test/unit/**/*.spec.ts\"", 26 | "example-server": "./node_modules/.bin/ts-node ./examples/src/api-server.ts", 27 | "example-tests": "mocha --timeout 2000 \"./examples/*.spec.ts\"", 28 | "integration": "mocha --timeout 5000 \"test/integration/**/*.spec.ts\"", 29 | "check": "npm run lint && npm run tests", 30 | "docs": "node ./node_modules/typedoc/bin/typedoc --mdHideSources --readme none --theme markdown --out ./docs ./src" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "git+https://github.com/formidablelabs/yesno.git" 35 | }, 36 | "author": "Ian Walker-Sperber", 37 | "contributors": [ 38 | { 39 | "email": "mscottx88@gmail.com", 40 | "name": "Michael P. Scott" 41 | } 42 | ], 43 | "license": "MIT", 44 | "bugs": { 45 | "url": "https://github.com/formidablelabs/yesno/issues" 46 | }, 47 | "homepage": "https://github.com/formidablelabs/yesno#readme", 48 | "devDependencies": { 49 | "@types/chai": "^4.1.4", 50 | "@types/chai-as-promised": "^7.1.0", 51 | "@types/express": "^4.16.0", 52 | "@types/mocha": "^5.2.5", 53 | "@types/mock-require": "^2.0.0", 54 | "@types/node": "^10.11.4", 55 | "@types/request-promise": "^4.1.42", 56 | "@types/rimraf": "^2.0.2", 57 | "@types/sinon": "^5.0.1", 58 | "@types/sinon-chai": "^3.2.1", 59 | "body-parser": "^1.18.3", 60 | "chai": "^4.1.2", 61 | "chai-as-promised": "^7.1.1", 62 | "codecov": "^3.1.0", 63 | "express": "^4.16.3", 64 | "mocha": "^5.2.0", 65 | "mock-require": "^3.0.2", 66 | "nyc": "^13.1.0", 67 | "prettier": "^1.15.3", 68 | "request": "^2.88.0", 69 | "request-promise": "^4.2.2", 70 | "rimraf": "^2.6.2", 71 | "sinon": "^6.1.5", 72 | "sinon-chai": "^3.2.0", 73 | "source-map-support": "^0.5.8", 74 | "ts-node": "^7.0.1", 75 | "tslint": "^5.11.0", 76 | "typedoc": "^0.13.0", 77 | "typedoc-plugin-markdown": "^1.1.19", 78 | "typescript": "^3.0.1" 79 | }, 80 | "dependencies": { 81 | "@types/debug": "0.0.30", 82 | "@types/fs-extra": "^5.0.4", 83 | "@types/lodash": "^4.14.116", 84 | "@types/uuid": "^3.4.4", 85 | "@types/yargs": "^12.0.1", 86 | "debug": "^4.0.1", 87 | "fs-extra": "^7.0.1", 88 | "io-ts": "^1.3.4", 89 | "io-ts-reporters": "0.0.21", 90 | "lodash": "^4.17.11", 91 | "mitm": "^1.7.0", 92 | "readable-stream": "^3.0.6", 93 | "uuid": "^3.3.2", 94 | "yargs": "^12.0.2" 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /scripts/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import * as yargs from 'yargs'; 3 | import * as file from '../src/file'; 4 | import { createRecord, ISerializedHttp } from '../src/http-serializer'; 5 | 6 | function createMockRequestResponse(): ISerializedHttp { 7 | return createRecord({ 8 | duration: 0, 9 | request: { 10 | body: '{}', 11 | headers: {}, 12 | host: 'example.com', 13 | method: 'POST', 14 | path: '/', 15 | port: 443, 16 | protocol: 'https', 17 | }, 18 | response: { 19 | body: '', 20 | headers: { 21 | 'x-yesno': 'generated mock', 22 | }, 23 | statusCode: 501, 24 | }, 25 | }); 26 | } 27 | 28 | // tslint:disable-next-line:no-unused-expression 29 | yargs.command({ 30 | command: 'generate ', 31 | handler: (argv) => { 32 | const { filename } = argv; 33 | return file.save({ 34 | filename, 35 | records: [createMockRequestResponse()], 36 | }); 37 | }, 38 | }).argv; 39 | -------------------------------------------------------------------------------- /src/@types/mitm/README.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | > `npm install --save @types/mitm` 3 | 4 | # Summary 5 | This package contains type definitions for mitm v1.3.0 (https://github.com/moll/node-mitm). 6 | 7 | # Details 8 | Files were exported from https://www.github.com/DefinitelyTyped/DefinitelyTyped/tree/types-2.0/mitm 9 | 10 | Additional Details 11 | * Last updated: Mon, 21 Nov 2016 21:02:57 GMT 12 | * File structure: ModuleAugmentation 13 | * Library Dependencies: node 14 | * Module Dependencies: http, net 15 | * Global values: none 16 | 17 | # Credits 18 | These definitions were written by Alejandro Sánchez . 19 | -------------------------------------------------------------------------------- /src/@types/mitm/index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for mitm v1.3.0 2 | // Project: https://github.com/moll/node-mitm 3 | // Definitions by: Alejandro Sánchez 4 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 5 | 6 | /// 7 | 8 | declare module 'mitm' { 9 | import * as http from 'http'; 10 | import * as net from 'net'; 11 | 12 | namespace Mitm { 13 | interface SocketOptions { 14 | port: number; 15 | host?: string; 16 | localAddress?: string; 17 | localPort?: string; 18 | family?: number; 19 | allowHalfOpen?: boolean; 20 | } 21 | 22 | interface BypassableSocket extends net.Socket { 23 | bypass(): void; 24 | } 25 | 26 | type SocketConnectCallback = (socket: BypassableSocket, opts: SocketOptions) => void; 27 | 28 | type SocketConnectionCallback = (socket: net.Socket, opts: SocketOptions) => void; 29 | 30 | type HttpCallback = (request: http.IncomingMessage, response: http.ServerResponse) => void; 31 | 32 | type Event = 'connect' | 'connection' | 'request'; 33 | 34 | type Callback = SocketConnectCallback | SocketConnectionCallback | HttpCallback; 35 | 36 | interface Mitm { 37 | disable(): void; 38 | on(event: Event, callback: Callback): void; 39 | on(event: 'connect', callback: SocketConnectCallback): void; 40 | on(event: 'connection', callback: SocketConnectionCallback): void; 41 | on(event: 'request', callback: HttpCallback): void; 42 | } 43 | } 44 | 45 | function Mitm(): Mitm.Mitm; 46 | export = Mitm; 47 | } 48 | -------------------------------------------------------------------------------- /src/@types/mitm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_from": "@types/mitm", 3 | "_id": "@types/mitm@1.3.2", 4 | "_inBundle": false, 5 | "_integrity": "sha1-omkQqgznRrYKWK18on3OsYEZ5uw=", 6 | "_location": "/@types/mitm", 7 | "_phantomChildren": {}, 8 | "_requested": { 9 | "type": "tag", 10 | "registry": true, 11 | "raw": "@types/mitm", 12 | "name": "@types/mitm", 13 | "escapedName": "@types%2fmitm", 14 | "scope": "@types", 15 | "rawSpec": "", 16 | "saveSpec": null, 17 | "fetchSpec": "latest" 18 | }, 19 | "_requiredBy": [ 20 | "#USER", 21 | "/" 22 | ], 23 | "_resolved": "https://registry.npmjs.org/@types/mitm/-/mitm-1.3.2.tgz", 24 | "_shasum": "a26910aa0ce746b60a58ad7ca27dceb18119e6ec", 25 | "_spec": "@types/mitm", 26 | "_where": "/Users/ianwalker-sperber/Code/ian/yesno", 27 | "author": { 28 | "name": "Alejandro Sánchez", 29 | "email": "https://github.com/alejo90" 30 | }, 31 | "bundleDependencies": false, 32 | "dependencies": { 33 | "@types/node": "*" 34 | }, 35 | "deprecated": false, 36 | "description": "TypeScript definitions for mitm v1.3.0", 37 | "license": "MIT", 38 | "main": "", 39 | "name": "@types/mitm", 40 | "peerDependencies": {}, 41 | "repository": { 42 | "type": "git", 43 | "url": "https://www.github.com/DefinitelyTyped/DefinitelyTyped.git" 44 | }, 45 | "scripts": {}, 46 | "typesPublisherContentHash": "90d8a3f63390bfc281913aa21437c0b85cdb2d5a6be48fed8457828a6872269d", 47 | "typings": "index.d.ts", 48 | "version": "1.3.2" 49 | } 50 | -------------------------------------------------------------------------------- /src/@types/mitm/types-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": "Alejandro Sánchez ", 3 | "definitionFilename": "index.d.ts", 4 | "libraryDependencies": ["node"], 5 | "moduleDependencies": ["http", "net"], 6 | "libraryMajorVersion": 1, 7 | "libraryMinorVersion": 3, 8 | "libraryName": "mitm v1.3.0", 9 | "typingsPackageName": "mitm", 10 | "projectName": "https://github.com/moll/node-mitm", 11 | "sourceRepoURL": "https://www.github.com/DefinitelyTyped/DefinitelyTyped", 12 | "sourceBranch": "types-2.0", 13 | "kind": "ModuleAugmentation", 14 | "globals": [], 15 | "declaredModules": ["mitm"], 16 | "files": ["index.d.ts"], 17 | "hasPackageJson": false, 18 | "contentHash": "90d8a3f63390bfc281913aa21437c0b85cdb2d5a6be48fed8457828a6872269d" 19 | } 20 | -------------------------------------------------------------------------------- /src/@types/readable-stream/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import * as Stream from 'stream'; 4 | 5 | declare module 'readable-stream' { 6 | export namespace Stream {} 7 | } 8 | -------------------------------------------------------------------------------- /src/consts.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * HTTP header to correlate requests made while intercepting 3 | */ 4 | export const YESNO_INTERNAL_HTTP_HEADER: string = 'x-yesno-internal-header-id'; 5 | 6 | /** 7 | * JSON mime type 8 | */ 9 | export const MIME_TYPE_JSON: string = 'application/json'; 10 | 11 | /** 12 | * HTTP content type header 13 | */ 14 | export const HEADER_CONTENT_TYPE: string = 'content-type'; 15 | 16 | /** 17 | * Default symbol to use when redacting fields from HTTP requests 18 | */ 19 | export const DEFAULT_REDACT_SYMBOL: string = '*****'; 20 | 21 | /** 22 | * Default port for outbound HTTP requests 23 | */ 24 | export const DEFAULT_PORT_HTTP: number = 80; 25 | 26 | /** 27 | * Default port for outbound HTTPS requests 28 | */ 29 | export const DEFAULT_PORT_HTTPS: number = 443; 30 | 31 | /** 32 | * Environment variable controlling the current recording mode 33 | */ 34 | export const YESNO_RECORDING_MODE_ENV_VAR: string = 'YESNO_RECORDING_MODE'; 35 | -------------------------------------------------------------------------------- /src/context.ts: -------------------------------------------------------------------------------- 1 | import { PartialResponseForRequest } from './filtering/collection'; 2 | import { byUrl as comparatorByUrl, ComparatorFn } from './filtering/comparator'; 3 | import { match, Matcher } from './filtering/matcher'; 4 | import { Redactor } from './filtering/redact'; 5 | import { 6 | ISerializedHttp, 7 | ISerializedRequest, 8 | ISerializedResponse, 9 | RequestSerializer, 10 | } from './http-serializer'; 11 | import { RecordMode as Mode } from './recording'; 12 | 13 | export interface IRedactProp { 14 | property: string | string[]; 15 | redactor?: Redactor; 16 | } 17 | 18 | export interface IInFlightRequest { 19 | startTime: number; 20 | requestSerializer: RequestSerializer; 21 | } 22 | 23 | export interface IResponseForMatchingRequest { 24 | response: PartialResponseForRequest; 25 | matcher: Matcher; 26 | } 27 | 28 | /** 29 | * Store the current execution context for YesNo by tracking requests & mocks. 30 | */ 31 | export default class Context { 32 | public mode: Mode = Mode.Spy; 33 | 34 | /** 35 | * Setting to redact all incoming requests to match redacted mocks 36 | */ 37 | public autoRedact: IRedactProp | null = null; 38 | 39 | /** 40 | * Completed serialized request-response objects. Used for: 41 | * A. Assertions 42 | * B. Saved to disk if in record mode 43 | */ 44 | public interceptedRequestsCompleted: ISerializedHttp[] = []; 45 | 46 | /** 47 | * Serialized records loaded from disk. 48 | */ 49 | public loadedMocks: ISerializedHttp[] = []; 50 | 51 | /** 52 | * Proxied requests which have not yet responded. When completed 53 | * the value is set to "null" but the index is preserved. 54 | */ 55 | public inFlightRequests: Array = []; 56 | 57 | public ignoresForMatchingRequests: IResponseForMatchingRequest[] = []; 58 | 59 | public responsesForMatchingRequests: IResponseForMatchingRequest[] = []; 60 | 61 | public comparatorFn: ComparatorFn = comparatorByUrl; 62 | 63 | public clear() { 64 | this.ignoresForMatchingRequests = []; 65 | this.interceptedRequestsCompleted = []; 66 | this.inFlightRequests = []; 67 | this.loadedMocks = []; 68 | this.responsesForMatchingRequests = []; 69 | this.comparatorFn = comparatorByUrl; 70 | } 71 | 72 | public getMatchingMocks(matcher: Matcher): ISerializedHttp[] { 73 | return this.loadedMocks.filter(match(matcher)); 74 | } 75 | 76 | public getMatchingIntercepted(matcher: Matcher): ISerializedHttp[] { 77 | return this.interceptedRequestsCompleted.filter(match(matcher)); 78 | } 79 | 80 | public hasResponsesDefinedForMatchers(): boolean { 81 | return !!this.responsesForMatchingRequests.length; 82 | } 83 | 84 | public addResponseForMatchingRequests(matchingResponse: IResponseForMatchingRequest): void { 85 | this.responsesForMatchingRequests.push(matchingResponse); 86 | } 87 | 88 | public getResponseDefinedMatching(request: ISerializedRequest): ISerializedResponse | undefined { 89 | let firstMatchingResponse: ISerializedResponse | undefined; 90 | 91 | for (const { matcher, response } of this.responsesForMatchingRequests) { 92 | const doesMatch = match(matcher)({ request }); 93 | 94 | if (doesMatch) { 95 | firstMatchingResponse = { 96 | body: {}, 97 | headers: {}, 98 | ...(typeof response === 'function' ? response(request) : response), 99 | }; 100 | break; 101 | } 102 | } 103 | 104 | return firstMatchingResponse; 105 | } 106 | 107 | public addIgnoreForMatchingRequests(matchingResponse: IResponseForMatchingRequest): void { 108 | this.ignoresForMatchingRequests.push(matchingResponse); 109 | } 110 | 111 | public hasMatchingIgnore(request: ISerializedRequest): boolean { 112 | if (!this.ignoresForMatchingRequests.length) { 113 | return false; 114 | } 115 | 116 | for (const { matcher } of this.ignoresForMatchingRequests) { 117 | if (match(matcher)({ request })) { 118 | return true; 119 | } 120 | } 121 | 122 | return false; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/errors.ts: -------------------------------------------------------------------------------- 1 | export class YesNoError extends Error { 2 | constructor(message: string) { 3 | super(`YesNo: ${message}`); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/file.ts: -------------------------------------------------------------------------------- 1 | import { IDebugger } from 'debug'; 2 | import { ensureDir, readFile, writeFile } from 'fs-extra'; 3 | import * as _ from 'lodash'; 4 | import { EOL } from 'os'; 5 | import * as path from 'path'; 6 | 7 | import { 8 | DEFAULT_PORT_HTTP, 9 | DEFAULT_PORT_HTTPS, 10 | HEADER_CONTENT_TYPE, 11 | MIME_TYPE_JSON, 12 | } from './consts'; 13 | 14 | import { YesNoError } from './errors'; 15 | import { createRecord, IHeaders, ISerializedHttp } from './http-serializer'; 16 | const debug: IDebugger = require('debug')('yesno:mocks'); 17 | 18 | export interface ISaveFile { 19 | records: ISerializedHttp[]; 20 | } 21 | 22 | export interface ISaveOptions { 23 | force?: boolean; 24 | records?: ISerializedHttp[]; 25 | } 26 | 27 | export interface IFileOptions { 28 | filename: string; 29 | } 30 | 31 | export interface IPartialMockRequest { 32 | headers?: IHeaders; 33 | body?: string | object; 34 | port?: number; 35 | path?: string; 36 | method: string; 37 | protocol: 'http' | 'https'; 38 | host: string; 39 | } 40 | 41 | export interface IPartialMockResponse { 42 | body?: string | object; 43 | headers?: IHeaders; 44 | statusCode: number; 45 | } 46 | 47 | export interface IHttpMock { 48 | readonly request: IPartialMockRequest; 49 | readonly response: IPartialMockResponse; 50 | } 51 | 52 | /** 53 | * Read mocks from a specified file. 54 | * 55 | * @throws YesNoError If file is improperly formatted 56 | */ 57 | export async function load({ filename }: IFileOptions): Promise { 58 | debug('Loading mocks from', filename); 59 | 60 | let data: Buffer; 61 | try { 62 | data = await readFile(filename); 63 | } catch (e) { 64 | if ((e as any).code === 'ENOENT') { 65 | throw new YesNoError( 66 | `${helpMessageMissingMock(filename)}${EOL}${EOL}Original error: ${e.message}`, 67 | ); 68 | } 69 | 70 | throw e; 71 | } 72 | 73 | let obj: ISaveFile; 74 | const dataString: string = data.toString(); 75 | 76 | try { 77 | obj = JSON.parse(dataString); 78 | } catch (e) { 79 | throw new YesNoError(`Failed to parse JSON from ${filename}: ${e}`); 80 | } 81 | 82 | if (!obj.records) { 83 | throw new YesNoError('Invalid JSON format. Missing top level "records" key.'); 84 | } 85 | 86 | return obj.records; 87 | } 88 | 89 | /** 90 | * Save HTTP records to the specified file 91 | */ 92 | export async function save({ 93 | filename, 94 | records = [], 95 | }: ISaveOptions & IFileOptions): Promise { 96 | debug('Saving %d records to %s', records.length, filename); 97 | 98 | await ensureDir(path.dirname(filename)); 99 | 100 | const payload: ISaveFile = { records }; 101 | const contents = JSON.stringify(payload, null, 2); 102 | await writeFile(filename, contents); 103 | 104 | return filename; 105 | } 106 | 107 | function helpMessageMissingMock(filename: string): string { 108 | const { name } = path.parse(filename); 109 | // tslint:disable-next-line:max-line-length 110 | return `Mock file for "${name}" does not exist. If you are using itRecorded, you can run the test in record mode by setting the YESNO_RECORDING_MODE environment variable to "record".${EOL}${EOL}YESNO_RECORDING_MODE=record ${EOL}${EOL}Or to generate the missing file now you may run the command:${EOL}${EOL}./node_modules/.bin/yesno generate "${filename}"`; 111 | } 112 | 113 | export function hydrateHttpMock(mock: IHttpMock): ISerializedHttp { 114 | const { request, response } = mock; 115 | 116 | let responseHeaders = response.headers || {}; 117 | const responseBody: string | object = response.body || ''; 118 | 119 | if (_.isPlainObject(response.body) && !responseHeaders[HEADER_CONTENT_TYPE]) { 120 | debug('Adding missing header %s=%s', HEADER_CONTENT_TYPE, MIME_TYPE_JSON); 121 | responseHeaders = { ...responseHeaders, [HEADER_CONTENT_TYPE]: MIME_TYPE_JSON }; 122 | } 123 | 124 | return createRecord({ 125 | duration: 0, 126 | request: { 127 | // supply defaults for headers, path, and port 128 | headers: {}, 129 | path: '/', 130 | port: request.protocol === 'https' ? DEFAULT_PORT_HTTPS : DEFAULT_PORT_HTTP, 131 | ...request, 132 | }, 133 | response: { 134 | body: responseBody, 135 | headers: responseHeaders, 136 | statusCode: response.statusCode, 137 | }, 138 | }); 139 | } 140 | 141 | /** 142 | * Get the generated filename for a mock name. 143 | */ 144 | export function getMockFilename(name: string, dir: string): string { 145 | return path.join(dir, `${name.replace(/\s+/g, '-').toLowerCase()}-yesno.json`); 146 | } 147 | -------------------------------------------------------------------------------- /src/filtering/collection.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import { EOL } from 'os'; 3 | import { DEFAULT_REDACT_SYMBOL } from '../consts'; 4 | import Context from '../context'; 5 | import { YesNoError } from '../errors'; 6 | import { 7 | formatUrl, 8 | ISerializedHttp, 9 | ISerializedRequest, 10 | ISerializedResponse, 11 | } from '../http-serializer'; 12 | import { ISerializedHttpPartialDeepMatch, Matcher, MatchFn } from './matcher'; 13 | import { redact, Redactor } from './redact'; 14 | 15 | export interface IFiltered { 16 | redact: (property: string | string[], symbol: Redactor) => void; 17 | intercepted: () => ISerializedHttp[]; 18 | mocks: () => ISerializedHttp[]; 19 | } 20 | 21 | export interface IFilteredHttpCollectionParams { 22 | context: Context; 23 | matcher?: Matcher; 24 | } 25 | 26 | export type PartialResponse = Partial & { statusCode: number }; 27 | 28 | export type PartialResponseForRequest = 29 | | PartialResponse 30 | | ((request: ISerializedRequest) => PartialResponse); 31 | 32 | /** 33 | * Represents a collection of HTTP requests which match the provided filter. 34 | * 35 | * Can filter both intercepted HTTP requests and loaded mocks. 36 | */ 37 | export default class FilteredHttpCollection implements IFiltered { 38 | private readonly ctx: Context; 39 | private readonly matcher: ISerializedHttpPartialDeepMatch | MatchFn; 40 | 41 | constructor({ context, matcher = {} }: IFilteredHttpCollectionParams) { 42 | this.ctx = context; 43 | this.matcher = matcher; 44 | } 45 | 46 | /** 47 | * Return all intercepted requests matching the current filter 48 | */ 49 | public intercepted(): ISerializedHttp[] { 50 | return this.ctx.getMatchingIntercepted(this.matcher); 51 | } 52 | 53 | /** 54 | * Return all intercepted mocks matching the current filter 55 | */ 56 | public mocks(): ISerializedHttp[] { 57 | return this.ctx.getMatchingMocks(this.matcher); 58 | } 59 | 60 | /** 61 | * Ignore a mock for all matching requests. 62 | * 63 | * Matching requests defined here take _precedence_ over all mocks and will be proxied. 64 | */ 65 | public ignore(): void { 66 | const response = { statusCode: 0 }; // will be overridden by proxied response 67 | this.ctx.addIgnoreForMatchingRequests({ response, matcher: this.matcher }); 68 | } 69 | 70 | /** 71 | * Provide a mock response for all matching requests. 72 | * 73 | * Use callback to dynamically generate response per request. 74 | * 75 | * Matching responses defined here take _precedence_ over mocks loaded normally. 76 | * @param response Serialized HTTP response or callback 77 | */ 78 | public respond(response: PartialResponseForRequest): void { 79 | this.ctx.addResponseForMatchingRequests({ response, matcher: this.matcher }); 80 | } 81 | 82 | /** 83 | * Redact given property/properties on all intercepted requests matching current filter. 84 | * 85 | * Useful whenever your HTTP requests contain sensitive details such as an API key that should not 86 | * be saved to disc. 87 | * @param property Nested path(s) for properties to redact from intercepted HTTP requests. 88 | * Works for all properties on serialized HTTP request/response objects. 89 | * Accepts dot notation for nested properties (eg `response.body.foobars[0].id`) 90 | * @param redactor Custom redactor. Defaults to replacing matching values with "*****" 91 | */ 92 | public redact( 93 | property: string | string[], 94 | redactor: Redactor = () => DEFAULT_REDACT_SYMBOL, 95 | ): void { 96 | const properties: string[] = _.isArray(property) ? property : [property]; 97 | 98 | const redactedRecords = this.intercepted().map((intercepted) => 99 | redact(intercepted, properties, redactor), 100 | ); 101 | 102 | redactedRecords.forEach((redactedRecord) => { 103 | const i = this.ctx.interceptedRequestsCompleted.findIndex((req) => { 104 | return redactedRecord.__id === req.__id; 105 | }); 106 | this.ctx.interceptedRequestsCompleted[i] = redactedRecord; 107 | }); 108 | } 109 | 110 | /** 111 | * Return serialized request part of the _single_ matching intercepted HTTP request. 112 | * 113 | * Throws an exception if multiple requests were matched. 114 | */ 115 | public request(): ISerializedRequest { 116 | return this.only().request; 117 | } 118 | 119 | /** 120 | * Return serialized response part of the _single_ matching intercepted HTTP request. 121 | * 122 | * Throws an exception if multiple requests were matched. 123 | */ 124 | public response(): ISerializedResponse { 125 | return this.only().response; 126 | } 127 | 128 | /** 129 | * If the current filter only matches a single request, then return the single matching instance. 130 | * Otherwise, throw an error. 131 | */ 132 | private only(): ISerializedHttp { 133 | const intercepted = this.intercepted(); 134 | const all = this.ctx.interceptedRequestsCompleted; 135 | 136 | if (!intercepted.length) { 137 | const nonMatchingHint = all 138 | .map(({ request }, i) => ` ${i + 1}. ${request.method} ${formatUrl(request)}`) 139 | .slice(0, 6) 140 | .join(EOL); 141 | const queryHint = _.isObject(this.matcher) 142 | ? JSON.stringify( 143 | this.matcher, 144 | (key, value) => (value instanceof RegExp ? `RegExp(${value.toString()})` : value), 145 | 2, 146 | ) 147 | : 'Function'; 148 | const numNotShown = 149 | this.ctx.interceptedRequestsCompleted.length > 6 150 | ? this.ctx.interceptedRequestsCompleted.length - 6 151 | : 0; 152 | // tslint:disable-next-line:max-line-length 153 | const help = ` ${EOL}Query:${EOL}${queryHint}${EOL}${EOL}Non-matching intercepted:${EOL}${nonMatchingHint}${ 154 | numNotShown ? ` (+${numNotShown} more)` : '' 155 | }`; 156 | throw new YesNoError(`No matching intercepted requests ${help}`); 157 | } 158 | 159 | if (intercepted.length > 1) { 160 | throw new YesNoError(`Query unexpectedly matched multiple (${intercepted.length}) requests.`); 161 | } 162 | 163 | return intercepted[0]; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/filtering/comparator.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { YesNoError } from '../errors'; 3 | import { ISerializedRequest } from '../http-serializer'; 4 | 5 | export interface IComparatorMetadata { 6 | requestIndex: number; 7 | } 8 | 9 | export type ComparatorFn = ( 10 | intercepted: ISerializedRequest, 11 | mock: ISerializedRequest, 12 | metadata: IComparatorMetadata, 13 | ) => boolean; 14 | 15 | // @todo Do not bother using chai 16 | // @todo Change to return type `Either` 17 | export const byUrl: ComparatorFn = (interceptedRequest, mockRequest, { requestIndex }): boolean => { 18 | assertEqual( 19 | interceptedRequest.host, 20 | mockRequest.host, 21 | `Expected host "${mockRequest.host}" for request #${requestIndex}, received "${ 22 | interceptedRequest.host 23 | }"`, 24 | ); 25 | const { host } = interceptedRequest; 26 | 27 | assertEqual( 28 | interceptedRequest.method, 29 | mockRequest.method, 30 | `Expected request #${requestIndex} for ${host}${interceptedRequest.path} to HTTP method "${ 31 | mockRequest.method 32 | }", not "${interceptedRequest.method}"`, 33 | ); 34 | 35 | assertEqual( 36 | interceptedRequest.protocol, 37 | mockRequest.protocol, 38 | `Expected request #${requestIndex} for ${host} to use "${ 39 | mockRequest.protocol 40 | }" protocol, not "${interceptedRequest.protocol}"`, 41 | ); 42 | 43 | assertEqual( 44 | interceptedRequest.port, 45 | mockRequest.port, 46 | `Expected request #${requestIndex} for ${host} to be served on port "${ 47 | mockRequest.port 48 | }", not "${interceptedRequest.port}"`, 49 | ); 50 | 51 | const nickname = `${interceptedRequest.method} ${interceptedRequest.protocol}://${ 52 | interceptedRequest.host 53 | }:${interceptedRequest.port}`; 54 | 55 | assertEqual( 56 | interceptedRequest.path, 57 | mockRequest.path, 58 | `Expected request #${requestIndex} "${nickname}" to have path "${mockRequest.path}", not "${ 59 | interceptedRequest.path 60 | }"`, 61 | ); 62 | 63 | return true; 64 | }; 65 | 66 | function assertEqual(unknown: T, known: T, message: string): void { 67 | assert.strictEqual(unknown, known, new YesNoError(`Request does not match mock. ${message}`)); 68 | } 69 | -------------------------------------------------------------------------------- /src/filtering/matcher.ts: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import { 3 | formatUrl, 4 | ISerializedRequest, 5 | ISerializedRequestResponse, 6 | ISerializedResponse, 7 | } from '../http-serializer'; 8 | 9 | export type RequestQuery = { [P in keyof ISerializedRequest]?: ISerializedRequest[P] | RegExp }; 10 | export type ResponseQuery = { [P in keyof ISerializedResponse]?: ISerializedResponse[P] | RegExp }; 11 | 12 | export interface ISerializedHttpPartialDeepMatch { 13 | url?: string | RegExp; 14 | request?: RequestQuery; 15 | response?: ResponseQuery; 16 | } 17 | 18 | export interface ISerializedRequestResponseToMatch { 19 | request: ISerializedRequest; 20 | response?: ISerializedResponse; 21 | } 22 | 23 | export type MatchFn = (serialized: ISerializedRequestResponse) => boolean; 24 | 25 | export type UnsafeMatchFn = (serialized: ISerializedRequestResponseToMatch) => boolean; 26 | 27 | export type Matcher = ISerializedHttpPartialDeepMatch | MatchFn; 28 | 29 | export const EMPTY_RESPONSE = { body: {}, headers: {}, statusCode: 0 }; 30 | 31 | /** 32 | * Curried function to determine whether a query matches an intercepted request. 33 | * 34 | * Query objects must be a deep partial match against the intercepted request. 35 | * 36 | * RegEx values are tested for match. 37 | */ 38 | export function match(fnOrPartialMatch: ISerializedHttpPartialDeepMatch | MatchFn): UnsafeMatchFn { 39 | const equalityOrRegExpDeep = (reqResValue: any, queryValue: any): boolean => { 40 | if (queryValue instanceof RegExp) { 41 | return queryValue.test(reqResValue); 42 | } else if (_.isPlainObject(queryValue) || _.isArray(queryValue)) { 43 | // Add a depth limit? 44 | return _.isMatchWith(reqResValue, queryValue, equalityOrRegExpDeep); 45 | } else { 46 | return queryValue === reqResValue; 47 | } 48 | }; 49 | 50 | const matcher: MatchFn = _.isFunction(fnOrPartialMatch) 51 | ? fnOrPartialMatch 52 | : (serialized: ISerializedRequestResponse): boolean => { 53 | const query = fnOrPartialMatch as ISerializedHttpPartialDeepMatch; 54 | let isMatch = true; 55 | 56 | if (query.url) { 57 | const matchUrlNoPort = equalityOrRegExpDeep(formatUrl(serialized.request), query.url); 58 | const matchUrlPort = equalityOrRegExpDeep(formatUrl(serialized.request, true), query.url); 59 | isMatch = isMatch && (matchUrlNoPort || matchUrlPort); 60 | } 61 | 62 | if (query.request) { 63 | isMatch = 64 | isMatch && _.isMatchWith(serialized.request, query.request, equalityOrRegExpDeep); 65 | } 66 | 67 | if (query.response) { 68 | isMatch = 69 | isMatch && _.isMatchWith(serialized.response, query.response, equalityOrRegExpDeep); 70 | } 71 | 72 | return isMatch; 73 | }; 74 | 75 | return (serialized: ISerializedRequestResponseToMatch) => 76 | matcher({ 77 | request: serialized.request, 78 | // Response will be empty if matching against requests 79 | response: serialized.response || EMPTY_RESPONSE, 80 | }); 81 | } 82 | -------------------------------------------------------------------------------- /src/filtering/redact.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import { DEFAULT_REDACT_SYMBOL } from '../../src/consts'; 3 | import { ISerializedHttp } from '../http-serializer'; 4 | 5 | export type Redactor = ((value: any, path: string) => string); 6 | 7 | export function defaultRedactor(): string { 8 | return DEFAULT_REDACT_SYMBOL; 9 | } 10 | 11 | /** 12 | * Redact properties on the matching intercepted records. 13 | * Note that header names are forced to lower case. 14 | * 15 | * If you want to redact a property in the saved mock file, run yesno.redact after 16 | * a request has been made in spy mode to redact the specified properties in the 17 | * intercepted requests to save. 18 | * 19 | * If you want incoming requests in mock mode to be redacted to match the saved mocks, 20 | * run yesno.redact before any requests have been made to redact the specified 21 | * properties on all intercepted requests. 22 | * 23 | * Use a `.` to reference a nested property 24 | * @todo Benchmark & investigate alternatives 25 | * @param property Properties to redact 26 | * @param redactSymbol Symbol to use for redacted properties or function to compute. 27 | */ 28 | export function redact( 29 | record: ISerializedHttp, 30 | properties: string[], 31 | redactor: Redactor = defaultRedactor, 32 | ): ISerializedHttp { 33 | const redacted = _.cloneDeep(record); 34 | 35 | properties.forEach((property) => { 36 | if (property.startsWith('request.headers')) { 37 | property = property.toLowerCase(); 38 | } 39 | const currentValue = _.get(redacted, property); 40 | 41 | if (currentValue !== undefined) { 42 | _.set(redacted, property, redactor(currentValue, property)); 43 | } 44 | }); 45 | 46 | return redacted; 47 | } 48 | -------------------------------------------------------------------------------- /src/http-serializer.ts: -------------------------------------------------------------------------------- 1 | import { ClientRequest, IncomingMessage, RequestOptions } from 'http'; 2 | import * as t from 'io-ts'; 3 | import { reporter } from 'io-ts-reporters'; 4 | import * as _ from 'lodash'; 5 | import { Transform } from 'stream'; 6 | import uuid = require('uuid'); 7 | import { HEADER_CONTENT_TYPE, MIME_TYPE_JSON, YESNO_INTERNAL_HTTP_HEADER } from './consts'; 8 | import { YesNoError } from './errors'; 9 | const debug = require('debug')('yesno:http-serializer'); 10 | const SCHEMA_VERSION = '1.0.0'; 11 | /* tslint:disable:max-classes-per-file */ 12 | 13 | // @see http.OutgoingHttpHeaders 14 | const Headers = t.dictionary( 15 | t.string, 16 | t.union([t.number, t.string, t.array(t.string), t.undefined]), 17 | ); 18 | 19 | const SerializedRequestOptional = t.partial({ 20 | body: t.readonly(t.union([t.string, t.object])), // Optional 21 | query: t.readonly(t.string), // Optional 22 | }); 23 | 24 | const SerializedRequest = t.intersection([ 25 | t.interface({ 26 | headers: t.readonly(Headers), 27 | host: t.readonly(t.string), 28 | method: t.readonly(t.string), 29 | path: t.readonly(t.string), 30 | port: t.readonly(t.Integer), 31 | protocol: t.readonly(t.union([t.literal('http'), t.literal('https')])), 32 | }), 33 | SerializedRequestOptional, 34 | ]); 35 | 36 | const SerializedResponse = t.interface({ 37 | body: t.readonly(t.union([t.string, t.object])), 38 | headers: t.readonly(Headers), 39 | statusCode: t.readonly(t.Integer), 40 | }); 41 | 42 | const SerializedHttpOptional = t.partial({ 43 | __duration: t.readonly(t.number), // Optional 44 | __timestamp: t.readonly(t.number), // Optional 45 | }); 46 | 47 | const SerializedHttp = t.intersection([ 48 | SerializedHttpOptional, 49 | t.interface({ 50 | __id: t.readonly(t.string), 51 | __version: t.readonly(t.string), 52 | request: SerializedRequest, 53 | response: SerializedResponse, 54 | }), 55 | ]); 56 | 57 | /** 58 | * HTTP request/response serialized in a consistent format 59 | */ 60 | export interface ISerializedHttp extends t.TypeOf {} 61 | 62 | /** 63 | * HTTP request serialized in a consistent format 64 | */ 65 | export interface ISerializedResponse extends t.TypeOf {} 66 | 67 | /** 68 | * HTTP response serialized in a consistent format 69 | */ 70 | export interface ISerializedRequest extends t.TypeOf {} 71 | 72 | /** 73 | * HTTP request & response 74 | */ 75 | export interface ISerializedRequestResponse { 76 | request: ISerializedRequest; 77 | response: ISerializedResponse; 78 | } 79 | 80 | export interface IHeaders extends t.TypeOf {} 81 | 82 | // Some properties are not present in the TS definition 83 | export interface ClientRequestFull extends ClientRequest { 84 | agent?: { 85 | defaultPort?: number; 86 | }; 87 | path: string; 88 | } 89 | 90 | function serializeJSON(headers: IHeaders, body?: string): undefined | string | object { 91 | const isJSON = 92 | headers[HEADER_CONTENT_TYPE] && 93 | (headers[HEADER_CONTENT_TYPE] as string).includes(MIME_TYPE_JSON); 94 | 95 | if (isJSON) { 96 | try { 97 | body = JSON.parse(body as string); 98 | } catch (e) { 99 | // Don't throw, just log and continue with unparsed body 100 | debug('Failed to parse JSON body', body); 101 | } 102 | } 103 | 104 | return body; 105 | } 106 | 107 | export class RequestSerializer extends Transform implements ISerializedRequest { 108 | public body: string | undefined; 109 | public headers: IHeaders = {}; 110 | public host: string; 111 | public path: string; 112 | public method: string; 113 | public port: number; 114 | public protocol: 'http' | 'https'; 115 | /** 116 | * Query part _including_ `?` 117 | */ 118 | public query?: string; 119 | 120 | constructor( 121 | originalClientOpts: RequestOptions, 122 | originalClientReq: ClientRequest, 123 | interceptedServerReq: IncomingMessage, 124 | isHttps: boolean, 125 | ) { 126 | super(); 127 | 128 | // @see https://github.com/nodejs/node/blob/v10.11.0/lib/_http_client.js#L121 129 | const agent = (originalClientReq as ClientRequestFull).agent; 130 | const defaultPort = originalClientOpts.defaultPort || (agent && agent.defaultPort); 131 | const port: number | string = originalClientOpts.port || defaultPort || 80; 132 | this.port = typeof port === 'string' ? parseInt(port, 10) : port; 133 | 134 | // @see https://github.com/nodejs/node/blob/v10.11.0/lib/_http_client.js#L125 135 | const [path, query] = (originalClientReq as ClientRequestFull).path.split('?'); 136 | this.host = originalClientOpts.hostname || originalClientOpts.host || 'localhost'; 137 | this.method = (interceptedServerReq.method as string).toUpperCase(); 138 | this.path = path; 139 | this.query = query ? `?${query}` : query; 140 | this.protocol = isHttps ? 'https' : 'http'; 141 | this.headers = _.omit(originalClientReq.getHeaders(), YESNO_INTERNAL_HTTP_HEADER); 142 | } 143 | 144 | public _transform(chunk: Buffer, encoding: string, done: () => void) { 145 | this.body = this.body || ''; 146 | this.body += chunk.toString(); // @todo Do we really need to convert to string for each chunk? 147 | 148 | this.push(chunk); 149 | done(); 150 | } 151 | 152 | public serialize(): ISerializedRequest { 153 | return { 154 | body: serializeJSON(this.headers, this.body), 155 | headers: this.headers, 156 | host: this.host, 157 | method: this.method, 158 | path: this.path, 159 | port: this.port, 160 | protocol: this.protocol, 161 | query: this.query, 162 | }; 163 | } 164 | } 165 | 166 | export class ResponseSerializer extends Transform implements ISerializedResponse { 167 | public body: string | object; 168 | public headers: IHeaders = {}; 169 | public statusCode: number; 170 | 171 | constructor(clientResponse: IncomingMessage) { 172 | super(); 173 | 174 | this.body = ''; 175 | this.statusCode = clientResponse.statusCode as number; 176 | this.headers = clientResponse.headers; 177 | } 178 | 179 | public _transform(chunk: Buffer, encoding: string, done: () => void) { 180 | this.body += chunk.toString(); 181 | 182 | this.push(chunk); 183 | done(); 184 | } 185 | 186 | public serialize(): ISerializedResponse { 187 | return { 188 | body: serializeJSON(this.headers, this.body as string) as string | object, 189 | headers: this.headers, 190 | statusCode: this.statusCode, 191 | }; 192 | } 193 | } 194 | 195 | export function formatUrl(request: ISerializedRequest, includePort: boolean = false): string { 196 | const port = includePort ? `:${request.port}` : ''; 197 | const query = request.query || ''; 198 | return `${request.protocol}://${request.host}${port}${request.path}${query}`; 199 | } 200 | 201 | export interface ICreateRecord { 202 | request: ISerializedRequest; 203 | response: ISerializedResponse; 204 | duration: number; 205 | } 206 | 207 | /** 208 | * Create record for an HTTP request, which may be saved in a mock file. 209 | */ 210 | export function createRecord({ request, response, duration }: ICreateRecord): ISerializedHttp { 211 | return { 212 | __duration: duration, 213 | __id: uuid.v4(), 214 | __timestamp: Date.now(), 215 | __version: SCHEMA_VERSION, 216 | request, 217 | response, 218 | }; 219 | } 220 | 221 | export function validateSerializedHttpArray(records: object[]) { 222 | const result = t.array(SerializedHttp).decode(records); 223 | 224 | if (result.isLeft()) { 225 | const errs = reporter(result); 226 | throw new YesNoError(`Invalid serialized HTTP. (Errors: ${errs.join(', ')})`); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import Context from './context'; 2 | import { YesNo } from './yesno'; 3 | 4 | export { ISerializedHttpPartialDeepMatch } from './filtering/matcher'; 5 | export { IFileOptions, ISaveOptions } from './file'; 6 | export { IInterceptOptions } from './interceptor'; 7 | export { ISerializedHttp } from './http-serializer'; 8 | export { default as Recording, IRecordingOptions, RecordMode } from './recording'; 9 | export { GenericTest, GenericTestFunction } from './yesno'; 10 | 11 | export const yesno = new YesNo(new Context()); 12 | export default yesno; 13 | -------------------------------------------------------------------------------- /src/interceptor.ts: -------------------------------------------------------------------------------- 1 | import { IDebugger } from 'debug'; 2 | import { EventEmitter } from 'events'; 3 | import * as http from 'http'; 4 | import { ClientRequest } from 'http'; 5 | import * as https from 'https'; 6 | import * as _ from 'lodash'; 7 | import Mitm from 'mitm'; 8 | import { Socket } from 'net'; 9 | import * as readable from 'readable-stream'; 10 | import { v4 as uuidv4 } from 'uuid'; 11 | import { YESNO_INTERNAL_HTTP_HEADER } from './consts'; 12 | import { ClientRequestFull, RequestSerializer, ResponseSerializer } from './http-serializer'; 13 | 14 | const debug: IDebugger = require('debug')('yesno:proxy'); 15 | 16 | interface ClientRequestTracker { 17 | [key: string]: { 18 | clientOptions: http.RequestOptions; 19 | clientRequest?: ClientRequestFull; 20 | }; 21 | } 22 | 23 | interface RegisteredSocket extends Mitm.BypassableSocket { 24 | __yesno_req_id?: string; 25 | } 26 | 27 | interface ProxyRequestOptions extends http.RequestOptions { 28 | proxying?: boolean; 29 | } 30 | 31 | /** 32 | * Event emitted whenever we intercept an HTTP request 33 | */ 34 | export interface IInterceptEvent { 35 | /** 36 | * The client request which initiated the HTTP request 37 | */ 38 | clientRequest: http.ClientRequest; 39 | /** 40 | * Request arriving to our MITM proxy 41 | */ 42 | interceptedRequest: http.IncomingMessage; 43 | /** 44 | * Response from our MITM proxy 45 | */ 46 | interceptedResponse: http.ServerResponse; 47 | /** 48 | * Proxy the intercepted request to its original destination 49 | */ 50 | proxy: () => void; 51 | requestNumber: number; 52 | requestSerializer: RequestSerializer; 53 | } 54 | 55 | /** 56 | * Configure intercept 57 | */ 58 | export interface IInterceptOptions { 59 | /** 60 | * Do not intercept outbound requests on these ports. 61 | * 62 | * By default MITM will intercept activity on any socket, HTTP or otherwise. 63 | * If you need to ignore a port (eg for a database connection), provide that port number here. 64 | * 65 | * In practice YesNo normally runs after long running connections have been established, 66 | * so this won't be a problem. 67 | */ 68 | ignorePorts?: number[]; 69 | } 70 | 71 | /** 72 | * Emit whenever we have proxied a request to its original destination 73 | */ 74 | export interface IProxiedEvent { 75 | requestSerializer: RequestSerializer; 76 | responseSerializer: ResponseSerializer; 77 | requestNumber: number; 78 | } 79 | 80 | interface IInterceptEvents { 81 | on(event: 'intercept', listener: (event: IInterceptEvent) => void): this; 82 | on(event: 'proxied', listener: (event: IProxiedEvent) => void): this; 83 | } 84 | 85 | /** 86 | * Intercept outbound HTTP requests and provide mock responses through an event API. 87 | * 88 | * Uses MITM library to spy on HTTP requests made in current NodeJS process. 89 | */ 90 | export default class Interceptor extends EventEmitter implements IInterceptEvents { 91 | public requestNumber: number = 0; 92 | private clientRequests: ClientRequestTracker = {}; 93 | private mitm?: Mitm.Mitm; 94 | private origOnSocket?: (socket: Socket) => void; 95 | private ignorePorts: number[] = []; 96 | 97 | /** 98 | * Enables intercepting all outbound HTTP requests. 99 | * @param options Intercept options 100 | */ 101 | public enable(options: IInterceptOptions = {}): void { 102 | if (this.mitm || this.origOnSocket) { 103 | debug('Interceptor already enabled. Do nothing.'); 104 | return; 105 | } 106 | 107 | const self = this; 108 | this.mitm = Mitm(); 109 | this.ignorePorts = options.ignorePorts || []; 110 | this.origOnSocket = ClientRequest.prototype.onSocket; 111 | 112 | ClientRequest.prototype.onSocket = _.flowRight( 113 | this.origOnSocket, 114 | // tslint:disable-next-line:only-arrow-functions 115 | function(this: ClientRequestFull, socket: RegisteredSocket): RegisteredSocket { 116 | if (undefined !== socket.__yesno_req_id) { 117 | debug('New socket on client request', socket.__yesno_req_id); 118 | self.clientRequests[socket.__yesno_req_id].clientRequest = this; 119 | this.setHeader(YESNO_INTERNAL_HTTP_HEADER, socket.__yesno_req_id); 120 | } 121 | 122 | return socket; 123 | }, 124 | ); 125 | 126 | this.mitm.on('connect', this.mitmOnConnect.bind(this) as Mitm.SocketConnectCallback); 127 | this.mitm.on('request', this.mitmOnRequest.bind(this)); 128 | this.mitm.on('connection', (server) => { 129 | server.on('error', (err) => debug('Server error:', err)); 130 | }); 131 | } 132 | 133 | /** 134 | * Disables intercepting outbound HTTP requests. 135 | */ 136 | public disable() { 137 | if (!this.mitm || !this.origOnSocket) { 138 | debug('Interceptor already disabled. Do nothing.'); 139 | return; 140 | } 141 | 142 | ClientRequest.prototype.onSocket = this.origOnSocket; 143 | this.mitm.disable(); 144 | this.mitm = undefined; 145 | this.origOnSocket = undefined; 146 | } 147 | 148 | /** 149 | * Event handler for Mitm "connect" event. 150 | */ 151 | private mitmOnConnect(socket: Mitm.BypassableSocket, options: ProxyRequestOptions): void { 152 | debug('New socket connection'); 153 | const { port } = options; 154 | 155 | if (this.ignorePorts && -1 !== this.ignorePorts.indexOf(parseInt(String(port), 10))) { 156 | debug('Ignoring socket on port %d', port); 157 | socket.bypass(); 158 | return; 159 | } 160 | 161 | if (options.proxying) { 162 | debug('Bypassing intercept...'); 163 | socket.bypass(); 164 | } else { 165 | this.trackSocketAndClientOptions(socket as RegisteredSocket, options); 166 | debug('Tracking socket %s', (socket as RegisteredSocket).__yesno_req_id); 167 | } 168 | } 169 | 170 | /** 171 | * Event handler for Mitm "request" event. 172 | * 173 | * Intercepted requests will be proxied if the `shouldProxy` option has been set. 174 | * @emits 'intercept' when we intercept a request 175 | * @emits 'proxied' when we have sent the response for a proxied response 176 | */ 177 | private mitmOnRequest( 178 | interceptedRequest: http.IncomingMessage, 179 | interceptedResponse: http.ServerResponse, 180 | ): void { 181 | debug('mitm event:request'); 182 | 183 | const id = this.getRequestId(interceptedRequest); 184 | 185 | function debugReq(formatter: string, ...args: any[]): void { 186 | debug(`[id:${id}] ${formatter}`, ...args); 187 | } 188 | 189 | if (!this.clientRequests[id]) { 190 | debugReq(`Error: Missing client options for yesno req ${id}`); 191 | throw new Error(`Missing client options for yesno req ${id}`); 192 | } 193 | 194 | interceptedResponse.on('finish', () => { 195 | debugReq('Response finished'); 196 | }); 197 | 198 | const { clientOptions } = this.clientRequests[id]; 199 | const clientRequest = this.clientRequests[id].clientRequest as ClientRequestFull; 200 | const isHttps: boolean = (interceptedRequest.connection as any).encrypted; 201 | const requestSerializer = new RequestSerializer( 202 | clientOptions, 203 | clientRequest, 204 | interceptedRequest, 205 | isHttps, 206 | ); 207 | const requestNumber = this.requestNumber; 208 | this.requestNumber++; 209 | 210 | debugReq('Emitting "intercept"'); 211 | 212 | const proxy = () => { 213 | const request = isHttps ? https.request : http.request; 214 | const proxiedRequest: http.ClientRequest = request({ 215 | ...clientOptions, 216 | headers: _.omit(clientRequest.getHeaders(), YESNO_INTERNAL_HTTP_HEADER), 217 | path: (clientRequest as ClientRequestFull).path, 218 | proxying: true, 219 | } as ProxyRequestOptions); 220 | 221 | (readable as any).pipeline(interceptedRequest, requestSerializer, proxiedRequest); 222 | 223 | interceptedRequest.on('error', (e: any) => debugReq('Error on intercepted request:', e)); 224 | interceptedRequest.on('aborted', () => { 225 | debugReq('Intercepted request aborted'); 226 | proxiedRequest.abort(); 227 | }); 228 | 229 | proxiedRequest.on('timeout', (e: any) => debugReq('Proxied request timeout', e)); 230 | proxiedRequest.on('error', (e: any) => debugReq('Proxied request error', e)); 231 | proxiedRequest.on('aborted', () => debugReq('Proxied request aborted')); 232 | proxiedRequest.on('response', (proxiedResponse: http.IncomingMessage) => { 233 | const responseSerializer = new ResponseSerializer(proxiedResponse); 234 | debugReq('proxied response (%d)', proxiedResponse.statusCode); 235 | if (proxiedResponse.statusCode) { 236 | interceptedResponse.writeHead(proxiedResponse.statusCode, proxiedResponse.headers); 237 | } 238 | (readable as any).pipeline(proxiedResponse, responseSerializer, interceptedResponse); 239 | 240 | proxiedResponse.on('end', () => { 241 | debugReq('Emitting "proxied"'); 242 | this.emit('proxied', { 243 | requestNumber, 244 | requestSerializer, 245 | responseSerializer, 246 | }); 247 | }); 248 | }); 249 | }; 250 | 251 | // YesNo will listen for this event to mock the response 252 | this.emit('intercept', { 253 | clientRequest, 254 | interceptedRequest, 255 | interceptedResponse, 256 | proxy, 257 | requestNumber, 258 | requestSerializer, 259 | } as IInterceptEvent); 260 | } 261 | 262 | private trackSocketAndClientOptions( 263 | socket: RegisteredSocket, 264 | clientOptions: ProxyRequestOptions, 265 | ): void { 266 | socket.__yesno_req_id = uuidv4(); 267 | this.clientRequests[socket.__yesno_req_id] = { clientOptions }; 268 | } 269 | 270 | private getRequestId(interceptedRequest: http.IncomingMessage): string { 271 | const id = interceptedRequest.headers[YESNO_INTERNAL_HTTP_HEADER] as string | undefined; 272 | 273 | if (!id) { 274 | throw new Error('Missing internal header'); 275 | } 276 | 277 | return id; 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /src/mock-response.ts: -------------------------------------------------------------------------------- 1 | import { IDebugger } from 'debug'; 2 | import * as _ from 'lodash'; 3 | import * as readable from 'readable-stream'; 4 | 5 | import Context from './context'; 6 | import { YesNoError } from './errors'; 7 | import * as comparator from './filtering/comparator'; 8 | import { 9 | ISerializedHttp, 10 | ISerializedRequest, 11 | ISerializedRequestResponse, 12 | ISerializedResponse, 13 | } from './http-serializer'; 14 | import { IInterceptEvent } from './interceptor'; 15 | import { RecordMode as Mode } from './recording'; 16 | 17 | const debug: IDebugger = require('debug')('yesno:mock-response'); 18 | 19 | /** 20 | * Encapsulates logic for sending a response for an intercepted HTTP request 21 | */ 22 | export default class MockResponse { 23 | private event: IInterceptEvent; 24 | private ctx: Context; 25 | 26 | constructor(event: IInterceptEvent, ctx: Context) { 27 | this.ctx = ctx; 28 | this.event = event; 29 | } 30 | 31 | /** 32 | * Send a respond for our wrapped intercept event if able. 33 | * 34 | * Give precedence to matching responses in shared context (from `yesno.matching().respond()`). 35 | * Else, if we're in mock mode, lookup the mock response. 36 | * 37 | * @returns The received request & sent response. Returns `undefined` if unable to respond 38 | */ 39 | public async send(): Promise { 40 | const { 41 | interceptedRequest, 42 | interceptedResponse, 43 | requestSerializer, 44 | requestNumber, 45 | } = this.event; 46 | let response: ISerializedResponse | undefined; 47 | 48 | debug(`[#${requestNumber}] Mock response`); 49 | 50 | await (readable as any).pipeline(interceptedRequest, requestSerializer); 51 | const request = requestSerializer.serialize(); 52 | response = this.ctx.getResponseDefinedMatching(request); 53 | 54 | if (!response && this.ctx.mode === Mode.Mock) { 55 | const mock = this.getMockForIntecept(this.event); 56 | 57 | // Assertion must happen before promise - 58 | // mitm does not support promise rejections on "request" event 59 | this.assertMockMatches({ mock, serializedRequest: request, requestNumber }); 60 | 61 | response = mock.response; 62 | } 63 | 64 | if (response) { 65 | this.writeMockResponse(response, interceptedResponse); 66 | return { request, response }; 67 | } 68 | } 69 | 70 | private getMockForIntecept({ requestNumber }: IInterceptEvent): ISerializedHttp { 71 | const mock = this.ctx.loadedMocks[requestNumber]; 72 | 73 | if (!mock) { 74 | throw new YesNoError('No mock found for request'); 75 | } 76 | 77 | return mock; 78 | } 79 | 80 | private assertMockMatches({ 81 | mock, 82 | serializedRequest, 83 | requestNumber, 84 | }: { mock: ISerializedHttp; serializedRequest: ISerializedRequest } & Pick< 85 | IInterceptEvent, 86 | 'requestNumber' 87 | >): void { 88 | try { 89 | // determine how we'll compare the request and the mock 90 | const compareBy: comparator.ComparatorFn = this.ctx.comparatorFn; 91 | 92 | // the comparison function must throw an error to signal a mismatch 93 | compareBy(serializedRequest, mock.request, { requestIndex: requestNumber }); 94 | } catch (err) { 95 | // ensure any user-thrown error is wrapped in our YesNoError 96 | throw new YesNoError(err.message); 97 | } 98 | } 99 | 100 | private writeMockResponse( 101 | response: ISerializedResponse, 102 | interceptedResponse: IInterceptEvent['interceptedResponse'], 103 | ): void { 104 | const bodyString = _.isPlainObject(response.body) 105 | ? JSON.stringify(response.body) 106 | : (response.body as string); 107 | 108 | const responseHeaders = { ...response.headers }; 109 | if ( 110 | responseHeaders['content-length'] && 111 | parseInt(responseHeaders['content-length'] as string, 10) !== Buffer.byteLength(bodyString) 112 | ) { 113 | responseHeaders['content-length'] = Buffer.byteLength(bodyString); 114 | } 115 | 116 | interceptedResponse.writeHead(response.statusCode, responseHeaders); 117 | interceptedResponse.write(bodyString); 118 | interceptedResponse.end(); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/recording.ts: -------------------------------------------------------------------------------- 1 | import { IDebugger } from 'debug'; 2 | import { IFileOptions, save } from './file'; 3 | import { ISerializedHttp } from './http-serializer'; 4 | const debug: IDebugger = require('debug')('yesno:recording'); 5 | 6 | export enum RecordMode { 7 | /** 8 | * Intercept requests and respond with local mocks 9 | */ 10 | Mock = 'mock', 11 | /** 12 | * Spy on request/response 13 | */ 14 | Spy = 'spy', 15 | /** 16 | * Save requests 17 | */ 18 | Record = 'record', 19 | } 20 | 21 | export interface IRecordingOptions extends IFileOptions { 22 | /** 23 | * Get all recorded HTTP requests we need to save to disc 24 | */ 25 | getRecordsToSave: () => ISerializedHttp[]; 26 | /** 27 | * Current record mode. Determines whether or not we'll save to disc on completion. 28 | */ 29 | mode: RecordMode; 30 | } 31 | 32 | /** 33 | * Represents a single YesNo recording 34 | */ 35 | export default class Recording { 36 | private options: IRecordingOptions; 37 | 38 | constructor(options: IRecordingOptions) { 39 | this.options = options; 40 | } 41 | 42 | /** 43 | * Complete recording by saving all HTTP requests to disc if in record mode. 44 | * No-op otherwise. 45 | */ 46 | public complete(): Promise { 47 | if (this.options.mode === RecordMode.Record) { 48 | debug('Record mode, saving'); 49 | return save({ 50 | ...this.options, 51 | records: this.options.getRecordsToSave(), 52 | }); 53 | } 54 | 55 | debug('"%s" mode, no-op', this.options.mode); 56 | return Promise.resolve(undefined); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/integration/mocks/mock-test-1-yesno.json: -------------------------------------------------------------------------------- 1 | { 2 | "records": [ 3 | { 4 | "__id": "1", 5 | "__duration": 3, 6 | "__timestamp": 1539210298277, 7 | "__version": "1.0.0", 8 | "request": { 9 | "headers": { 10 | "x-status-code": 299, 11 | "x-timestamp": 1539210298272, 12 | "host": "localhost:3001" 13 | }, 14 | "host": "localhost", 15 | "method": "GET", 16 | "path": "/get", 17 | "port": 3001, 18 | "protocol": "http", 19 | "query": "foo=bar" 20 | }, 21 | "response": { 22 | "body": "Mock Body 1", 23 | "headers": { 24 | "x-powered-by": "Express", 25 | "content-type": "application/json; charset=utf-8", 26 | "content-length": "126", 27 | "etag": "W/\"7e-URVWNJGNVqk8miEYbmWEsYLGGmo\"", 28 | "date": "Wed, 10 Oct 2018 22:24:58 GMT", 29 | "connection": "close" 30 | }, 31 | "statusCode": 299 32 | }, 33 | "url": "http://localhost:3001/get" 34 | }, 35 | { 36 | "__id": "2", 37 | "__duration": 3, 38 | "__timestamp": 1539210298282, 39 | "__version": "1.0.0", 40 | "request": { 41 | "body": "{\"foo\":\"bar\"}", 42 | "headers": { 43 | "host": "localhost:3001", 44 | "accept": "application/json", 45 | "content-type": "application/json", 46 | "content-length": 13 47 | }, 48 | "host": "localhost", 49 | "method": "POST", 50 | "path": "/post", 51 | "port": 3001, 52 | "protocol": "http" 53 | }, 54 | "response": { 55 | "body": "Mock Body 2", 56 | "headers": { 57 | "x-powered-by": "Express", 58 | "content-type": "application/json; charset=utf-8", 59 | "content-length": "163", 60 | "etag": "W/\"a3-jHqZr7r0l0zBQzwvS/zMEYPWTEo\"", 61 | "date": "Wed, 10 Oct 2018 22:24:58 GMT", 62 | "connection": "close" 63 | }, 64 | "statusCode": 200 65 | }, 66 | "url": "http://localhost:3001/post" 67 | } 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /test/integration/mocks/mock-test-redact-yesno.json: -------------------------------------------------------------------------------- 1 | { 2 | "records": [ 3 | { 4 | "__duration": 41, 5 | "__id": "1", 6 | "__timestamp": 1594314369237, 7 | "__version": "1.0.0", 8 | "request": { 9 | "body": { 10 | "password": "*****", 11 | "username": "hulkhoganhero" 12 | }, 13 | "headers": { 14 | "x-status-code": 200, 15 | "host": "localhost:3001", 16 | "accept": "application/json", 17 | "content-type": "application/json", 18 | "content-length": 48 19 | }, 20 | "host": "localhost", 21 | "method": "POST", 22 | "path": "/post", 23 | "port": 3001, 24 | "protocol": "http" 25 | }, 26 | "response": { 27 | "body": { 28 | "headers": { 29 | "x-status-code": "200", 30 | "host": "localhost:3001", 31 | "accept": "application/json", 32 | "content-type": "application/json", 33 | "content-length": "48", 34 | "connection": "close" 35 | }, 36 | "body": { 37 | "password": "secret", 38 | "username": "hulkhoganhero" 39 | }, 40 | "method": "POST", 41 | "path": "/post" 42 | }, 43 | "headers": { 44 | "x-powered-by": "Express", 45 | "x-test-server-header": "foo", 46 | "content-type": "application/json; charset=utf-8", 47 | "content-length": "251", 48 | "etag": "W/\"fb-kMrRfN5TqT3Sw6VGbMDQrSt8J4E\"", 49 | "date": "Thu, 09 Jul 2020 17:06:09 GMT", 50 | "connection": "close" 51 | }, 52 | "statusCode": 200 53 | } 54 | } 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /test/integration/yesno.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import * as http from 'http'; 3 | import * as https from 'https'; 4 | import * as _ from 'lodash'; 5 | import * as path from 'path'; 6 | import rp from 'request-promise'; 7 | import yesno from '../../src'; 8 | import * as testServer from '../test-server'; 9 | 10 | describe('yesno', () => { 11 | const TEST_HEADER_VALUE = 'foo'; 12 | const TEST_BODY_VALUE = 'fiz'; 13 | const tmpDir = path.join(__dirname, 'tmp'); 14 | let server: testServer.ITestServer; 15 | 16 | before(async () => { 17 | server = await testServer.start(); 18 | }); 19 | 20 | beforeEach(() => { 21 | yesno.spy(); 22 | }); 23 | 24 | afterEach(() => { 25 | yesno.clear(); 26 | }); 27 | 28 | after(() => { 29 | yesno.restore(); 30 | server.close(); 31 | }); 32 | 33 | describe.skip('#save', () => { 34 | it('should create records locally', async () => { 35 | const filename = `${tmpDir}/record-test-1-yesno.json`; 36 | const now = Date.now(); 37 | 38 | await rp.get({ 39 | headers: { 40 | 'x-status-code': 299, 41 | 'x-timestamp': now, 42 | }, 43 | uri: 'http://localhost:3001/get?foo=bar', 44 | }); 45 | await rp.post({ 46 | body: { 47 | foo: 'bar', 48 | }, 49 | json: true, 50 | uri: 'http://localhost:3001/post', 51 | }); 52 | 53 | expect(yesno.intercepted()).to.have.length(2); 54 | await yesno.save({ filename }); 55 | 56 | const mocks = await yesno.load({ filename }); 57 | expect(mocks[0]).to.have.nested.property('request.headers.x-timestamp', now); 58 | expect(mocks[0]).to.have.nested.property('request.host', 'localhost'); 59 | expect(mocks[0]).to.have.nested.property('request.path', '/get'); 60 | expect(mocks[0]).to.have.nested.property('request.query', '?foo=bar'); 61 | expect(mocks[0]).to.have.nested.property('request.method', 'GET'); 62 | expect(mocks[0]).to.have.nested.property('response.statusCode', 299); 63 | expect(mocks[0]).to.have.nested.property('response.headers'); 64 | expect(mocks[0]).to.have.nested.property('response.body'); 65 | }); 66 | }); 67 | 68 | describe('#intercepted', () => { 69 | it('should allow querying for the various requests made', async () => { 70 | yesno.spy(); 71 | 72 | await rp.get({ 73 | headers: { 74 | 'x-foo': 'bar', 75 | }, 76 | uri: 'http://localhost:3001/get', 77 | }); 78 | 79 | await expect( 80 | rp.post({ 81 | headers: { 82 | 'x-status-code': 500, 83 | }, 84 | json: true, 85 | uri: 'http://localhost:3001/post', 86 | }), 87 | ).to.be.rejected; 88 | 89 | expect(yesno.intercepted(), 'Returns all intercepted requests').to.have.lengthOf(2); 90 | expect(yesno.matching(/\/get/).intercepted(), 'Match URL by RegExp').to.have.lengthOf(1); 91 | expect( 92 | yesno.matching({ response: { statusCode: 500 } }).intercepted(), 93 | 'Match by a response property', 94 | ).to.have.lengthOf(1); 95 | expect( 96 | yesno.matching({ request: { headers: { 'x-foo': 'bar' } } }).intercepted(), 97 | 'Match by a nested request property', 98 | ).to.have.lengthOf(1); 99 | expect( 100 | yesno.matching({ request: { headers: { 'x-foo': 'bar', 'x-fiz': 'baz' } } }).intercepted(), 101 | 'All properties must match ', 102 | ).to.have.lengthOf(0); 103 | }); 104 | 105 | it('should treat JSON request or response bodies as objects', async () => { 106 | await rp.post({ 107 | body: { 108 | nested: { 109 | foobar: 'fizbaz', 110 | }, 111 | }, 112 | json: true, 113 | uri: 'http://localhost:3001/post', 114 | }); 115 | 116 | await rp.post({ 117 | body: '{ "foo": "bar" }', 118 | uri: 'http://localhost:3001/post', 119 | }); 120 | 121 | const interceptedJSON = yesno.intercepted()[0]; 122 | const interceptedNotJSON = yesno.intercepted()[1]; 123 | expect(interceptedJSON).to.have.nested.property('request.body.nested.foobar', 'fizbaz'); 124 | expect(interceptedNotJSON, 'Non-json left as a string').to.have.nested.property( 125 | 'request.body', 126 | '{ "foo": "bar" }', 127 | ); 128 | }); 129 | }); 130 | 131 | describe('#redact', () => { 132 | it('should allow redacting a single nested property', async () => { 133 | await expect( 134 | rp.post({ 135 | body: { 136 | password: 'secret', 137 | username: 'hulkhoganhero', 138 | }, 139 | headers: { 140 | 'x-status-code': 500, 141 | }, 142 | json: true, 143 | uri: 'http://localhost:3001/post', 144 | }), 145 | ).to.be.rejected; 146 | 147 | const toRedact = 'request.body.password'; 148 | const intercepted = yesno.intercepted(); 149 | expect(intercepted[0]).to.have.nested.property(toRedact, 'secret'); 150 | yesno.redact(toRedact); 151 | 152 | expect(yesno.intercepted()[0]).to.have.nested.property(toRedact, '*****'); 153 | expect( 154 | _.omit(yesno.intercepted()[0], toRedact), 155 | 'Non matching properties are unchanged', 156 | ).to.eql(_.omit(intercepted[0], toRedact)); 157 | expect(intercepted[0], 'The intercepted requests are not mutated').to.have.nested.property( 158 | toRedact, 159 | 'secret', 160 | ); 161 | }); 162 | 163 | it('should allow redacting intercepted requests as well', async () => { 164 | // run redact before the request to redact the intercepted request 165 | const toRedact = 'request.body.password'; 166 | yesno.redact(toRedact); 167 | 168 | const mocks = await yesno.load({ 169 | filename: `${path.join(__dirname, 'mocks')}/mock-test-redact-yesno.json`, 170 | }); 171 | yesno.mock(mocks); 172 | 173 | await rp.post({ 174 | body: { 175 | password: 'secret', 176 | username: 'hulkhoganhero', 177 | }, 178 | headers: { 179 | 'x-status-code': 200, 180 | }, 181 | json: true, 182 | uri: 'http://localhost:3001/post', 183 | }); 184 | 185 | const intercepted = yesno.intercepted(); 186 | expect(intercepted[0]).to.have.nested.property(toRedact, '*****'); 187 | }); 188 | }); 189 | 190 | describe('mock mode', () => { 191 | it('should play back the requests from disk', async () => { 192 | const mocks = await yesno.load({ 193 | filename: `${path.join(__dirname, 'mocks')}/mock-test-1-yesno.json`, 194 | }); 195 | yesno.mock(mocks); 196 | 197 | const now = Date.now(); 198 | 199 | const response1 = await rp.get({ 200 | headers: { 201 | 'x-status-code': 299, 202 | 'x-timestamp': now, 203 | }, 204 | uri: 'http://localhost:3001/get?foo=bar', 205 | }); 206 | const response2 = await rp.post({ 207 | body: { 208 | foo: 'bar', 209 | }, 210 | json: true, 211 | uri: 'http://localhost:3001/post', 212 | }); 213 | 214 | expect(response1).to.eql(mocks[0].response.body); 215 | expect(response2).to.eql(mocks[1].response.body); 216 | }); 217 | }); 218 | 219 | it('should send get to test server', async () => { 220 | const response: rp.RequestPromise = await rp.get({ 221 | headers: { 222 | 'x-test-header': TEST_HEADER_VALUE, 223 | }, 224 | json: true, 225 | qs: { 226 | fiz: 'baz', 227 | }, 228 | uri: 'http://localhost:3001/get', 229 | }); 230 | 231 | expect(response, 'Missing response').to.be.ok; 232 | expect(response).to.have.nested.property('headers.x-test-header', TEST_HEADER_VALUE); 233 | }); 234 | 235 | it('should proxy HTTP GET requests', async () => { 236 | const response: rp.RequestPromise = await rp.get({ 237 | headers: { 238 | 'x-test-header': TEST_HEADER_VALUE, 239 | }, 240 | json: true, 241 | uri: 'http://postman-echo.com/get', 242 | }); 243 | 244 | expect(response, 'Missing response').to.be.ok; 245 | expect(response).to.have.nested.property('headers.x-test-header', TEST_HEADER_VALUE); 246 | }); 247 | 248 | it('should proxy HTTP POST requests', async () => { 249 | const response: rp.RequestPromise = await rp.post({ 250 | body: { 251 | test: TEST_BODY_VALUE, 252 | }, 253 | headers: { 254 | 'x-test-header': TEST_HEADER_VALUE, 255 | }, 256 | json: true, 257 | uri: 'https://postman-echo.com/post', 258 | }); 259 | 260 | expect(response, 'Missing response').to.be.ok; 261 | expect(response).to.have.nested.property('headers.x-test-header', TEST_HEADER_VALUE); 262 | expect(response).to.have.nested.property('json.test', TEST_BODY_VALUE); 263 | }); 264 | 265 | it('should mock HTTPS requests', (done) => { 266 | const request: http.ClientRequest = https.request({ 267 | headers: { 268 | accept: 'application/json', 269 | connection: 'close', 270 | 'content-type': 'application/json', 271 | host: 'postman-echo.com', 272 | 'x-test-header': 'foo', 273 | }, 274 | host: 'postman-echo.com', 275 | path: '/get', 276 | }); 277 | 278 | request.on('error', (e) => { 279 | // console.error(`problem with request: ${e.message}`); 280 | }); 281 | 282 | request.on('end', (uh) => { 283 | // console.log('End?', uh); 284 | }); 285 | 286 | request.on('response', (res) => { 287 | // console.log('Response'); 288 | // console.log(`STATUS: ${res.statusCode}`); 289 | // console.log(`HEADERS: ${JSON.stringify(res.headers)}`); 290 | res.setEncoding('utf8'); 291 | res.on('data', (chunk: Buffer) => { 292 | // console.log(`BODY: ${chunk}`); 293 | }); 294 | res.on('end', () => { 295 | // console.log('No more data in response.'); 296 | done(); 297 | }); 298 | }); 299 | 300 | request.end(); 301 | }); 302 | }); 303 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --colors 2 | --require ts-node/register 3 | --require source-map-support/register 4 | --require test/setup.ts 5 | --recursive 6 | -------------------------------------------------------------------------------- /test/setup.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import chaiAsPromised from 'chai-as-promised'; 3 | import sinonChai = require('sinon-chai'); 4 | 5 | chai.use(sinonChai); 6 | chai.use(chaiAsPromised as any); 7 | -------------------------------------------------------------------------------- /test/test-client.ts: -------------------------------------------------------------------------------- 1 | import rp from 'request-promise'; 2 | 3 | export function getRequest(options: object = {}): rp.RequestPromise { 4 | return rp({ 5 | method: 'GET', 6 | uri: 'http://localhost:3001/get', 7 | ...options, 8 | }); 9 | } 10 | 11 | export function postRequest(options: object = {}): rp.RequestPromise { 12 | return rp({ 13 | method: 'POST', 14 | uri: 'http://localhost:3001/post', 15 | ...options, 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /test/test-server.ts: -------------------------------------------------------------------------------- 1 | import { json as jsonParser } from 'body-parser'; 2 | import express from 'express'; 3 | import { Server } from 'http'; 4 | const debug = require('debug')('yesno:test-server'); 5 | 6 | export const PORT = 3001; 7 | 8 | export const URI_ENDPOINT_GET = `http://localhost:${PORT}/get`; 9 | export const URI_ENDPOINT_POST = `http://localhost:${PORT}/post`; 10 | 11 | export interface ITestServer extends Server { 12 | getRequestCount: () => number; 13 | } 14 | 15 | export function start(port: number = PORT): Promise { 16 | let requestCount: number = 0; 17 | const app = express(); 18 | app.use(jsonParser()); 19 | 20 | app.use((req, res, next) => { 21 | requestCount++; 22 | next(); 23 | }); 24 | 25 | app.get('/get', ({ headers }: express.Request, res: express.Response) => { 26 | debug('Received GET request'); 27 | res.status(headers['x-status-code'] ? parseInt(headers['x-status-code'] as string, 10) : 200); 28 | res.setHeader('x-test-server-header', 'foo'); 29 | res.send({ headers, source: 'server', method: 'GET', path: '/get' }); 30 | }); 31 | 32 | app.post('/post', ({ headers, body }: express.Request, res: express.Response) => { 33 | debug('Received POST request'); 34 | res.status(headers['x-status-code'] ? parseInt(headers['x-status-code'] as string, 10) : 200); 35 | res.setHeader('x-test-server-header', 'foo'); 36 | res.send({ headers, body, method: 'POST', path: '/post' }); 37 | }); 38 | 39 | return new Promise((resolve) => { 40 | const server = app.listen(port, () => { 41 | debug('Test server running on port %d', port); 42 | (server as any).getRequestCount = () => requestCount; 43 | resolve(server as ITestServer); 44 | }); 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /test/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../tslint.json"], 3 | "rules": { 4 | "max-line-length": [true, 150], 5 | "no-unused-expression": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/unit/context.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { SinonSandbox as Sandbox } from 'sinon'; 3 | import * as sinon from 'sinon'; 4 | 5 | import Context from '../../src/context'; 6 | import { ISerializedHttp } from '../../src/http-serializer'; 7 | 8 | describe('context', () => { 9 | const sandbox: Sandbox = sinon.createSandbox(); 10 | 11 | afterEach(() => { 12 | sandbox.restore(); 13 | }); 14 | 15 | let instance: Context; 16 | beforeEach(() => { 17 | instance = new Context(); 18 | }); 19 | 20 | describe('constructor', () => { 21 | it('returns a configured instance of Context', () => { 22 | expect(instance).instanceOf(Context); 23 | }); 24 | }); 25 | 26 | describe('clear', () => { 27 | it('resets internal arrays and state variables', () => { 28 | // add some stuff to see it go away 29 | instance.interceptedRequestsCompleted = [{}, {}] as [ISerializedHttp, ISerializedHttp]; 30 | instance.loadedMocks = [{}, {}] as [ISerializedHttp, ISerializedHttp]; 31 | instance.inFlightRequests = [null, null]; 32 | 33 | instance.clear(); 34 | 35 | expect(instance.interceptedRequestsCompleted).deep.equals([]); 36 | expect(instance.inFlightRequests).deep.equals([]); 37 | expect(instance.loadedMocks).deep.equals([]); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/unit/filtering/collection.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import yesno from '../../../src'; 3 | import Context from '../../../src/context'; 4 | import FilteredHttpCollection from '../../../src/filtering/collection'; 5 | 6 | describe('FilteredHttpCollection', () => { 7 | describe('#redacted', () => { 8 | it('should effect the provided array', async () => { 9 | const context = new Context(); 10 | context.interceptedRequestsCompleted = await yesno.load({ 11 | filename: `${__dirname}/mocks/two-requests.json`, 12 | }); 13 | expect(context.interceptedRequestsCompleted[0]).to.have.nested.property( 14 | 'request.path', 15 | '/fiz', 16 | ); 17 | expect(context.interceptedRequestsCompleted[1]).to.have.nested.property( 18 | 'request.path', 19 | '/foo', 20 | ); 21 | 22 | const collection = new FilteredHttpCollection({ context, matcher: { url: /foo/ } }); 23 | collection.redact(['request.path']); 24 | 25 | expect(context.interceptedRequestsCompleted[0]).to.have.nested.property( 26 | 'request.path', 27 | '/fiz', 28 | ); 29 | expect(context.interceptedRequestsCompleted[1]).to.have.nested.property( 30 | 'request.path', 31 | '*****', 32 | ); 33 | }); 34 | }); 35 | 36 | describe('#intercepted', () => { 37 | it('should return all intercepted requests in the collection that match the filter'); 38 | }); 39 | 40 | describe('#mocks', () => { 41 | it('should return all mocks in the collection that match the filter'); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/unit/filtering/matcher.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { match } from '../../../src/filtering/matcher'; 3 | import { ISerializedHttp } from '../../../src/http-serializer'; 4 | 5 | describe('matcher', () => { 6 | const serialized = (): ISerializedHttp => ({ 7 | __duration: 0, 8 | __id: 'id', 9 | __timestamp: 0, 10 | __version: 'mock', 11 | request: { 12 | body: { foo: { bar: false } }, 13 | headers: { 14 | 'content-type': 'application/json', 15 | 'x-test-header': 'fizbaz', 16 | }, 17 | host: 'api.foobar.com', 18 | method: 'POST', 19 | path: '/my/path', 20 | port: 443, 21 | protocol: 'https', 22 | query: '?foo=bar', 23 | }, 24 | response: { 25 | body: 'foobar', 26 | headers: { 27 | 'content-type': 'text/plain', 28 | }, 29 | statusCode: 200, 30 | }, 31 | }); 32 | 33 | it('should match a request by an exact URL match', () => { 34 | expect(match({ url: 'https://api.foobar.com/my/path?foo=bar' })(serialized())).to.be.true; 35 | expect( 36 | match({ url: 'https://api.foobar.com:443/my/path?foo=bar' })(serialized()), 37 | 'Optionally include port', 38 | ).to.be.true; 39 | expect(match({ url: 'https://api.fizbaz.com/my/path?foo=bar' })(serialized())).to.be.false; 40 | expect(match({ url: 'https://api.foobar.com/my/path' })(serialized())).to.be.false; 41 | }); 42 | 43 | it('should match a request by a regex URL match', () => { 44 | expect(match({ url: /api\.foobar\.com\/my/ })(serialized())).to.be.true; 45 | expect(match({ url: /api\.foobar\.com:443\/my/ })(serialized()), 'Optionally include port').to 46 | .be.true; 47 | expect(match({ url: /^api/ })(serialized())).to.be.false; 48 | }); 49 | 50 | it('should match a request by a deep partial object match', () => { 51 | const s = serialized(); 52 | 53 | expect(match({ request: { method: 'POST' } })(s), 'Request').to.be.true; 54 | expect(match({ request: { method: 'GET' } })(s), 'Request').to.be.false; 55 | expect( 56 | match({ request: { method: 'POST' }, response: { statusCode: 200 } })(s), 57 | 'Request & response', 58 | ).to.be.true; 59 | expect( 60 | match({ request: { method: 'POST' }, response: { statusCode: 500 } })(s), 61 | 'Request & response no match', 62 | ).to.be.false; 63 | expect( 64 | match({ request: { method: 'POST', body: { foo: { bar: false } } } })(s), 65 | 'Deep partial match', 66 | ).to.be.true; 67 | expect( 68 | match({ request: { method: 'POST', body: { foo: { bar: true } } } })(s), 69 | 'Deep partial no match', 70 | ).to.be.false; 71 | expect( 72 | match({ url: /foobar/, request: { method: 'POST', body: { foo: { bar: false } } } })(s), 73 | 'URL Regex & deep partial match', 74 | ).to.be.true; 75 | expect( 76 | match({ url: /fizbaz/, request: { method: 'POST', body: { foo: { bar: false } } } })(s), 77 | 'URL Regex & deep partial no match', 78 | ).to.be.false; 79 | }); 80 | 81 | it('should match against arrays in JSON bodies', () => { 82 | const s = serialized(); 83 | (s.request.body as any) = { users: [{ id: 1, i: 0 }, { id: 2, i: 1 }] }; 84 | 85 | expect( 86 | match({ request: { body: { users: [{ id: 1, i: 0 }, { id: 2, i: 1 }] } } })(s), 87 | 'Array exact match', 88 | ).to.be.true; 89 | expect( 90 | match({ request: { body: { users: [{ id: 1 }, { id: 2 }] } } })(s), 91 | 'Partial match of all values in array', 92 | ).to.be.true; 93 | expect(match({ request: { body: { users: [{ id: 1, i: 0 }] } } })(s), 'Array shortened matches') 94 | .to.be.true; 95 | expect(match({ request: { body: { users: [] } } })(s), 'Array empty matches').to.be.true; 96 | expect( 97 | match({ request: { body: { users: [{ id: 1, i: 1 }] } } })(s), 98 | 'Array wrong value mismatch', 99 | ).to.be.false; 100 | expect( 101 | match({ request: { body: { users: [{ id: 2, i: 1 }] } } })(s), 102 | 'Array missing element mismatch', 103 | ).to.be.false; 104 | expect(match({ request: { body: {} } })(s)).to.be.true; 105 | }); 106 | 107 | it('should test leaf nodes which are regular expressions'); 108 | 109 | it('should supporting using a callback', () => { 110 | expect(match(() => true)(serialized())).to.be.true; 111 | expect(match(() => false)(serialized())).to.be.false; 112 | expect(match((toMatch) => toMatch.request.path === '/my/path')(serialized())).to.be.true; 113 | expect(match((toMatch) => toMatch.request.path !== '/my/path')(serialized())).to.be.false; 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /test/unit/filtering/mocks/two-requests.json: -------------------------------------------------------------------------------- 1 | { 2 | "records": [ 3 | { 4 | "__duration": 2, 5 | "__id": "069390a8-adb9-4434-b4e5-e0751ae283b4", 6 | "__timestamp": 1542869578952, 7 | "__version": "1.0.0", 8 | "request": { 9 | "headers": { 10 | "host": "example" 11 | }, 12 | "host": "example", 13 | "method": "GET", 14 | "path": "/fiz", 15 | "port": 80, 16 | "protocol": "https" 17 | }, 18 | "response": { 19 | "body": { 20 | "fiz": "baz" 21 | }, 22 | "headers": { 23 | "content-type": "application/json; charset=utf-8" 24 | }, 25 | "statusCode": 200 26 | } 27 | }, 28 | { 29 | "__duration": 2, 30 | "__id": "069390a8-adb9-4434-b4e5-e0751ae283b5", 31 | "__timestamp": 1542869578952, 32 | "__version": "1.0.0", 33 | "request": { 34 | "headers": { 35 | "host": "example" 36 | }, 37 | "host": "example", 38 | "method": "POST", 39 | "path": "/foo", 40 | "port": 80, 41 | "protocol": "https" 42 | }, 43 | "response": { 44 | "body": { 45 | "foo": "bar" 46 | }, 47 | "headers": { 48 | "content-type": "application/json; charset=utf-8" 49 | }, 50 | "statusCode": 200 51 | } 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /test/unit/filtering/redact.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import _ from 'lodash'; 3 | import * as sinon from 'sinon'; 4 | import { DEFAULT_REDACT_SYMBOL } from '../../../src/consts'; 5 | 6 | import { redact } from '../../../src/filtering/redact'; 7 | import { ISerializedHttp } from '../../../src/http-serializer'; 8 | 9 | describe('redact#redact', () => { 10 | const serialized = (): ISerializedHttp => ({ 11 | __duration: 0, 12 | __id: 'id', 13 | __timestamp: 0, 14 | __version: 'mock', 15 | request: { 16 | body: { foo: { bar: false } }, 17 | headers: { 18 | 'content-type': 'application/json', 19 | 'x-test-header': 'fizbaz', 20 | }, 21 | host: 'api.foobar.com', 22 | method: 'POST', 23 | path: '/my/path', 24 | port: 443, 25 | protocol: 'https', 26 | query: '?foo=bar', 27 | }, 28 | response: { 29 | body: 'foobar', 30 | headers: { 31 | 'content-type': 'text/plain', 32 | }, 33 | statusCode: 200, 34 | }, 35 | }); 36 | 37 | it('should allow redacting multiple properties', () => { 38 | const original = serialized(); 39 | const redacted = redact(original, ['response.body', 'request.headers.x-test-header', 'foobar']); 40 | 41 | expect(redacted, 'No mutation').to.not.eql(original); 42 | expect(redacted).to.have.nested.property('response.body', DEFAULT_REDACT_SYMBOL); 43 | expect(redacted).to.have.nested.property( 44 | 'request.headers.x-test-header', 45 | DEFAULT_REDACT_SYMBOL, 46 | ); 47 | expect(redacted, 'Missing properties have no effect').to.not.have.property('foobar'); 48 | }); 49 | 50 | it('should ignore case when redacting headers', () => { 51 | const original = serialized(); 52 | const redacted = redact(original, ['request.headers.X-test-header']); 53 | 54 | expect(redacted, 'No mutation').to.not.eql(original); 55 | expect(redacted).to.have.nested.property( 56 | 'request.headers.x-test-header', 57 | DEFAULT_REDACT_SYMBOL, 58 | ); 59 | }); 60 | 61 | it('should support providing a custom redactor', () => { 62 | const mockRedactSymbol = 'REDACTED!'; 63 | const mockRedactor = sinon.mock().returns(mockRedactSymbol); 64 | 65 | const redacted = redact(serialized(), ['request.body.foo.bar'], mockRedactor); 66 | 67 | expect(mockRedactor.args).to.have.lengthOf(1); 68 | expect(mockRedactor.args[0]).to.eql([false, 'request.body.foo.bar']); 69 | expect(redacted).to.have.nested.property('request.body.foo.bar', mockRedactSymbol); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /test/unit/http-serializer.spec.ts: -------------------------------------------------------------------------------- 1 | describe('http-serializer', () => { 2 | describe('RequestSerializer', () => { 3 | describe('constructor', () => { 4 | it('should set defaults'); 5 | }); 6 | 7 | describe('#serialize', () => { 8 | it('should return a SerializedRequest representing the current request'); 9 | it('should convert JSON bodies to objects'); 10 | it('should degrade to string if JSON parsing fails'); 11 | }); 12 | }); 13 | 14 | describe('ResponseSerializer', () => { 15 | describe('constructor', () => { 16 | it('should set defaults'); 17 | }); 18 | 19 | describe('#serialize', () => { 20 | it('should return a SerializedRequest representing the current request'); 21 | it('should convert JSON bodies to objects'); 22 | it('should degrade to string if JSON parsing fails'); 23 | }); 24 | }); 25 | 26 | describe('#formatUrl', () => { 27 | it('should create a url without port or query params'); 28 | }); 29 | 30 | describe('#createRecord', () => { 31 | it('should create a record with additional metadata'); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/unit/interceptor.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import * as http from 'http'; 3 | import { ISerializedRequest, RequestSerializer } from '../../src/http-serializer'; 4 | import Interceptor, { IInterceptEvent } from '../../src/interceptor'; 5 | import * as testClient from '../test-client'; 6 | import * as testServer from '../test-server'; 7 | 8 | describe('Interceptor', () => { 9 | let server: testServer.ITestServer; 10 | let interceptor: Interceptor; 11 | 12 | before(async () => { 13 | server = await testServer.start(); 14 | }); 15 | 16 | afterEach(() => { 17 | if (interceptor) { 18 | interceptor.disable(); 19 | } 20 | }); 21 | 22 | after(() => { 23 | server.close(); 24 | }); 25 | 26 | describe('#enable', () => { 27 | it('should allow us to proxy via the intercept event', async () => { 28 | interceptor = new Interceptor(); 29 | interceptor.enable(); 30 | 31 | const serverCount = server.getRequestCount(); 32 | let intercepted = 0; 33 | interceptor.on('intercept', ({ proxy }) => { 34 | intercepted++; 35 | proxy(); 36 | }); 37 | 38 | const body = { foo: 'bar' }; 39 | const response = await testClient.postRequest({ json: true, body }); 40 | 41 | expect(server.getRequestCount(), 'Server received request').to.eql(serverCount + 1); 42 | expect(intercepted, 'Request intercepted').to.eql(1); 43 | expect(response, 'Echo response is correct').to.deep.include({ 44 | body, 45 | method: 'POST', 46 | path: '/post', 47 | }); 48 | }); 49 | 50 | it('should allow ignoring ports', async () => { 51 | interceptor = new Interceptor(); 52 | interceptor.enable({ ignorePorts: [testServer.PORT] }); 53 | 54 | const serverCount = server.getRequestCount(); 55 | let intercepted = 0; 56 | interceptor.on('intercept', () => intercepted++); 57 | 58 | const body = { foo: 'bar' }; 59 | await testClient.postRequest({ json: true, body }); 60 | 61 | expect(server.getRequestCount(), 'Server got request').to.eql(serverCount + 1); 62 | expect(intercepted, 'No requests were intercepted').to.eql(0); 63 | }); 64 | 65 | it('should be configurable by port'); 66 | it('should abort the proxied request if the intercepted request is aborted'); 67 | it('should emit an error on the intercepted request if the proxied request errors'); 68 | it('should timeout the intercepted request if the proxied request times out'); 69 | }); 70 | 71 | describe('#disable', () => { 72 | it('should disable HTTP request intercepting', async () => { 73 | interceptor = new Interceptor(); 74 | interceptor.enable(); 75 | 76 | const serverCount = server.getRequestCount(); 77 | let intercepted = 0; 78 | interceptor.on('intercept', ({ proxy }) => { 79 | intercepted++; 80 | proxy(); 81 | }); 82 | 83 | await testClient.getRequest(); // 1 84 | await testClient.getRequest(); // 2 85 | 86 | interceptor.disable(); 87 | await testClient.getRequest(); // Not intercepted 88 | 89 | expect(server.getRequestCount(), 'Server got 3 requests').to.eql(serverCount + 3); 90 | expect(intercepted, 'Only intercepted first 2').to.eql(2); 91 | }); 92 | }); 93 | 94 | describe('event "intercept"', () => { 95 | it('should should provide us a reference to the originating ClientRequest', async () => { 96 | interceptor = new Interceptor(); 97 | interceptor.enable(); 98 | 99 | let interceptEvent: IInterceptEvent | undefined; 100 | interceptor.on('intercept', (e: IInterceptEvent) => { 101 | interceptEvent = e; 102 | e.proxy(); 103 | }); 104 | 105 | const body = { foo: 'bar' }; 106 | await testClient.postRequest({ json: true, body }); 107 | 108 | expect(interceptEvent).to.be.ok; 109 | expect(interceptEvent) 110 | .to.have.property('clientRequest') 111 | .instanceof(http.ClientRequest); 112 | expect(interceptEvent) 113 | .to.have.property('interceptedRequest') 114 | .instanceof(http.IncomingMessage); 115 | expect(interceptEvent) 116 | .to.have.property('interceptedResponse') 117 | .instanceof(http.ServerResponse); 118 | expect(interceptEvent) 119 | .to.have.property('requestSerializer') 120 | .instanceof(RequestSerializer); 121 | expect(interceptEvent).to.have.property('requestNumber', 0); 122 | }); 123 | }); 124 | 125 | describe('event "proxied"', () => { 126 | it('should emit a "proxied" event when a request is proxied', async () => { 127 | return new Promise(async (resolve) => { 128 | interceptor = new Interceptor(); 129 | interceptor.enable(); 130 | 131 | let didIntercept = false; 132 | 133 | interceptor.on('intercept', ({ proxy }: IInterceptEvent) => { 134 | didIntercept = true; 135 | 136 | proxy(); 137 | }); 138 | 139 | interceptor.on('proxied', () => { 140 | expect(didIntercept).to.eql(true); // Intercept must happen first 141 | 142 | resolve(); 143 | }); 144 | 145 | await testClient.getRequest(); 146 | }); 147 | }); 148 | }); 149 | }); 150 | -------------------------------------------------------------------------------- /test/unit/mocks/mock-localhost-get-yesno.json: -------------------------------------------------------------------------------- 1 | { 2 | "records": [ 3 | { 4 | "__duration": 0, 5 | "__id": "1ae8735f-7ac2-4b4b-91b5-72c554d3f52e", 6 | "__timestamp": 1540404357697, 7 | "__version": "1.0.0", 8 | "request": { 9 | "headers": {}, 10 | "host": "localhost", 11 | "method": "GET", 12 | "path": "/get", 13 | "port": 3001, 14 | "protocol": "http" 15 | }, 16 | "response": { 17 | "body": "", 18 | "headers": { 19 | "x-yesno": "generated mock" 20 | }, 21 | "statusCode": 200 22 | }, 23 | "url": "https://localhost:3000/" 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /test/unit/mocks/mock-test-yesno.json: -------------------------------------------------------------------------------- 1 | { 2 | "records": [ 3 | { 4 | "__duration": 0, 5 | "__id": "3814c497-cded-4e4d-8dff-3fde6feebcc9", 6 | "__timestamp": 1540332352920, 7 | "__version": "1.0.0", 8 | "request": { 9 | "body": "", 10 | "headers": {}, 11 | "host": "example.com", 12 | "method": "POST", 13 | "path": "/my/path", 14 | "port": 80, 15 | "protocol": "http" 16 | }, 17 | "response": { 18 | "body": "mock body", 19 | "headers": { 20 | "content-type": "text/plain", 21 | "x-yesno-mock": "hello" 22 | }, 23 | "statusCode": 200 24 | }, 25 | "url": "http://example.com:80/my/path" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /test/unit/mocks/mock-x-www-form-urlencoded.json: -------------------------------------------------------------------------------- 1 | { 2 | "records": [ 3 | { 4 | "__duration": 0, 5 | "__id": "3814c497-cded-4e4d-8dff-3fde6feebcc9", 6 | "__timestamp": 1540332352920, 7 | "__version": "1.0.0", 8 | "request": { 9 | "body": "email=example%40example.com&description=YesNo%20test", 10 | "headers": { 11 | "accept": "application/json", 12 | "content-type": "application/x-www-form-urlencoded", 13 | "content-length": 59, 14 | "host": "example.com" 15 | }, 16 | "host": "example.com", 17 | "method": "POST", 18 | "path": "/my/path", 19 | "port": 443, 20 | "protocol": "https" 21 | }, 22 | "response": { 23 | "body": { 24 | "foo": "bar" 25 | }, 26 | "headers": { 27 | "server": "nginx", 28 | "date": "Mon, 11 Mar 2019 15:58:15 GMT", 29 | "content-type": "application/json", 30 | "content-length": "999999999", 31 | "connection": "keep-alive" 32 | }, 33 | "statusCode": 200 34 | }, 35 | "url": "https://example.com:443/my/path" 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /test/unit/mocks/recording-mock.json: -------------------------------------------------------------------------------- 1 | { 2 | "records": [ 3 | { 4 | "__duration": 2, 5 | "__id": "069390a8-adb9-4434-b4e5-e0751ae283b4", 6 | "__timestamp": 1542869578952, 7 | "__version": "1.0.0", 8 | "request": { 9 | "headers": { 10 | "host": "localhost:3001" 11 | }, 12 | "host": "localhost", 13 | "method": "GET", 14 | "path": "/get", 15 | "port": 3001, 16 | "protocol": "http" 17 | }, 18 | "response": { 19 | "body": "{\"headers\":{\"host\":\"localhost:3001\",\"connection\":\"close\"},\"method\":\"GET\",\"path\":\"/get\"}", 20 | "headers": { 21 | "x-powered-by": "Express", 22 | "content-type": "application/json; charset=utf-8", 23 | "content-length": "87", 24 | "etag": "W/\"57-Z4ae1p59epSpYq2PcHrfwQR3giw\"", 25 | "date": "Thu, 22 Nov 2018 06:52:58 GMT", 26 | "connection": "close" 27 | }, 28 | "statusCode": 200 29 | } 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /test/unit/recording.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { SinonSandbox as Sandbox, SinonStub as Stub } from 'sinon'; 3 | import * as sinon from 'sinon'; 4 | 5 | import * as file from '../../src/file'; 6 | import { ISerializedHttp } from '../../src/http-serializer'; 7 | import Recording, { IRecordingOptions, RecordMode } from '../../src/recording'; 8 | 9 | describe('recording', () => { 10 | const sandbox: Sandbox = sinon.createSandbox(); 11 | 12 | afterEach(() => { 13 | sandbox.restore(); 14 | }); 15 | 16 | const mockRecordMode: RecordMode = RecordMode.Record; 17 | const mockOptions: IRecordingOptions = { mode: mockRecordMode } as IRecordingOptions; 18 | 19 | let stubGetRecordsToSave: Stub; 20 | 21 | let instance: Recording; 22 | beforeEach(() => { 23 | stubGetRecordsToSave = sandbox.stub(); 24 | 25 | Object.assign(mockOptions, { getRecordsToSave: stubGetRecordsToSave }); 26 | instance = new Recording(mockOptions); 27 | }); 28 | 29 | describe('constructor', () => { 30 | it('returns a configured instance of Recording', () => { 31 | expect(instance).instanceOf(Recording); 32 | }); 33 | }); 34 | 35 | describe('complete', () => { 36 | const mockFileName: string = 'some-file-name'; 37 | const mockRecords: ISerializedHttp[] = []; 38 | 39 | let stubSave: Stub; 40 | 41 | beforeEach(() => { 42 | stubSave = sandbox.stub(file, 'save'); 43 | }); 44 | 45 | it('invokes the file save method if mode is Record', async () => { 46 | stubGetRecordsToSave.returns(mockRecords); 47 | stubSave.resolves(mockFileName); 48 | 49 | const results: string | undefined = await instance.complete(); 50 | expect(results).equals(mockFileName); 51 | 52 | expect(stubGetRecordsToSave).calledOnce.and.calledWithExactly(); 53 | 54 | expect(stubSave).calledOnce.and.calledWithMatch({ 55 | getRecordsToSave: sinon.match.func, 56 | mode: mockRecordMode, 57 | records: mockRecords, 58 | }); 59 | }); 60 | 61 | it('does not invoke the file save method if mode is not Record', async () => { 62 | const options = { mode: RecordMode.Spy } as IRecordingOptions; 63 | Object.assign(options, { getRecordsToSave: stubGetRecordsToSave }); 64 | 65 | instance = new Recording(options); 66 | 67 | const results: string | undefined = await instance.complete(); 68 | expect(results).undefined; 69 | 70 | expect(stubGetRecordsToSave).not.called; 71 | expect(stubSave).not.called; 72 | }); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "ES2018", 5 | "lib": ["dom", "es2018"], 6 | "module": "commonjs", 7 | "strict": true, 8 | "paths": { 9 | "*": ["src/@types/*"] 10 | }, 11 | "outDir": "dist", 12 | "removeComments": false, 13 | "sourceMap": true, 14 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 15 | /* Enable all strict type-checking options. */ 16 | // "lib": [], /* Specify library files to be included in the compilation. */ 17 | // "allowJs": true, /* Allow javascript files to be compiled. */ 18 | // "checkJs": true, /* Report errors in .js files. */ 19 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 20 | "declaration": true /* Generates corresponding '.d.ts' file. */, 21 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 22 | // "outFile": "./", /* Concatenate and emit output to single file. */ 23 | // "outDir": "./", /* Redirect output structure to the directory. */ 24 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 25 | // "composite": true, /* Enable project compilation */ 26 | // "removeComments": true, /* Do not emit comments to output. */ 27 | // "noEmit": true, /* Do not emit outputs. */ 28 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 29 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 30 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 31 | 32 | /* Strict Type-Checking Options */ 33 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 34 | // "strictNullChecks": true, /* Enable strict null checks. */ 35 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 36 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 37 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 38 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 39 | 40 | /* Additional Checks */ 41 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 42 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 43 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 44 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 45 | 46 | /* Module Resolution Options */ 47 | "baseUrl": "./" /* Base directory to resolve non-absolute module names. */, 48 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 49 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 50 | // "typeRoots": [], /* List of folders to include type definitions from. */ 51 | // "types": [], /* Type declaration files to be included in compilation. */ 52 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 53 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 54 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 55 | 56 | /* Source Map Options */ 57 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 58 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 59 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 60 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 61 | 62 | /* Experimental Options */ 63 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 64 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 65 | }, 66 | "include": ["scripts/**/*.ts", "src/**/*.ts"], 67 | "exclude": ["node_modules", "**/*.d.ts"] 68 | } 69 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "jsRules": {}, 5 | "linterOptions": { 6 | "exclude": ["**/*.d.ts"] 7 | }, 8 | "nodePath": "./node_modules", 9 | "rules": { 10 | "interface-name": false, 11 | "max-line-length": [true, 100], 12 | "no-var-requires": false, 13 | "object-literal-key-quotes": [true, "as-needed"], 14 | "quotemark": [true, "single", "avoid-escape", "avoid-template"] 15 | }, 16 | "rulesDirectory": [] 17 | } 18 | -------------------------------------------------------------------------------- /yesno-Hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/yesno/307fac9353ebb07bcd8e963a9ffec15344a975b1/yesno-Hero.png --------------------------------------------------------------------------------