chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

1
vendor/wiremock/.cargo-checksum.json vendored Normal file
View File

@@ -0,0 +1 @@
{"files":{".cargo_vcs_info.json":"ea1c37b001120c78886d3b927fe537d59f5003ecb50a626c77e3fd35f1f346b3",".github/workflows/deny.yml":"ce3b2f04a22c0a96de5e1f778c7a27b948318b76e29bec674f38edd115a2f182",".github/workflows/general.yml":"8703fa92891427e1594e790b61c4c2e4e43d662745027fe44f0223aa95dc998e","CHANGELOG.md":"ead71f6ae6b3fe9c820fcdb6a3d52fa9a8f8a06afda06c5c6219ae5a944c60c5","CODE_OF_CONDUCT.md":"444ac5d3da580218e9c96e934cb3bc90d98f340c319559288a306b511cf5e731","Cargo.lock":"835db5804c918558713af13bb9797891d1dce115b93fdf39cd20a00cfe6d266e","Cargo.toml":"ee9fa93165c9cc168a6d82da14515a248c71bec45bbfe836b7112c809e0e6798","Cargo.toml.orig":"41fe031392a2ab9ffcbe54c3424c4076065f23031fc3bf2dc5ed5d8a267d0614","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3","README.md":"63eee602db5e9658e027ed234531e19a0dc39d358fc3ad7d9e8f2e2690d49b7d","deny.toml":"4a759b01c59cc90b73950a6e5a9d07db9ea6a2996e6633a91b413b83869c0b87","src/http.rs":"d8af56a2dc92d56adda90c12bc109009b8b6d1d86a7f05fb6526e99d2188bd41","src/lib.rs":"a1917bcfe3c773bf3aad012eec91b3e5a64274e1e9617caa79718de5724c9126","src/matchers.rs":"bc143ce2f9c3ce04c1f87056073db0a905fba4b555ecf84a0f224bae66e3f135","src/mock.rs":"d36b306689348a02488aa84ea2c90a8e284325ee6b34b15cd24e8ebef7d961cd","src/mock_server/bare_server.rs":"5e54aebfa60cf5b51faaba4c9894fdd9fc462e40606228dde9c313bf0003eb35","src/mock_server/builder.rs":"e522c02537f3baeeefe1d53677e4af9d24986346e6ef94ce19718252d7f63193","src/mock_server/exposed_server.rs":"08663d4fa10dc4f17cd814accaf84c6e63eda01bb5614936b895ed194f8ad45b","src/mock_server/hyper.rs":"8e4547c2aa228f252ee71aa7c6d41e6dec6ad1ee0f8f1464af0652f683b6889f","src/mock_server/mod.rs":"82cbb57c05c2cbb31413ee231ce987f8c98a54601de565d4efcf0847a67ea9da","src/mock_server/pool.rs":"5637589e1c189e13d049a92157084408af8964048d40b24aa8d420e1fe2d1efb","src/mock_set.rs":"52b74242726ec9fd4a731b17ba6e7dbcc801b4f3e7d0fa08b75688b03c478d6c","src/mounted_mock.rs":"fa4cf018a3773a31a1cd8be56ff1805d17437a2914bd3e59c200ec0892ac04d8","src/request.rs":"dc1ae7630b31eb6b6733f083fae622c86a1ea8a7a843d57bae0058a62b9dbe64","src/respond.rs":"554082121ce4c2992a0c90d0cbac1b776b3e18367bd4531cbe64ac09cf027ba3","src/response_template.rs":"b9abe51658ab00a8d3ed4138e9122a99214a6fb731611a8423927f1ad71e8e54","src/verification.rs":"4a0cb342acc7c9cd353f62699791126012247b1e951d473a895e8444e5e46b79","tests/mocks.rs":"dd6b133de4fd553f88647c1dca950026c479618819cc2cd813c3f22e5d86a4d4","tests/priority.rs":"5c3ffd06174b8c60bad3dd3cb0c056886ae0fb019c3e48fee5a4ac9098bce1de","tests/request_header_matching.rs":"d5163f483787cdcde4bf60b45c5b05e2b3906cfd8d5ac68f80a73c474f3548b8","tests/timeout.rs":"53726d1b603decb40eab889d0dfe9a51bc93fddc167c1ae0f71140671963ef71","tests/tokio.rs":"6d0c0b8373c30e6ff7cb0fccd42da9c73366e0dd9ec600faaef9cd94ebe16ce0"},"package":"08db1edfb05d9b3c1542e521aea074442088292f00b5f28e435c714a98f85031"}

6
vendor/wiremock/.cargo_vcs_info.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"git": {
"sha1": "6b193047bf2c5626da5dc5f3a23b58ab9bd3f130"
},
"path_in_vcs": ""
}

View File

@@ -0,0 +1,26 @@
name: Verify dependencies
on:
workflow_dispatch:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
deny-check:
name: cargo-deny check
runs-on: ubuntu-latest
strategy:
matrix:
checks:
- advisories
- licenses
steps:
- uses: actions/checkout@v4
- uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check ${{ matrix.checks }}
arguments: --all-features

View File

@@ -0,0 +1,76 @@
name: Rust
on:
pull_request:
push:
branches:
- main
env:
CARGO_TERM_COLOR: always
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Install Rust stable toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
- name: Test
run: |
cargo test
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Install Rust stable toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
- name: Format
run: |
cargo fmt --all -- --check
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Install Rust stable toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
- name: Lint
run: |
cargo clippy -- -D warnings
docs:
name: Docs
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Install Rust stable toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
- name: Document
run: |
cargo doc --no-deps --document-private-items
coverage:
runs-on: ubuntu-latest
env:
CARGO_TERM_COLOR: always
steps:
- uses: actions/checkout@v5
- name: Install Rust stable toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: Generate code coverage
run: cargo llvm-cov --all-features --workspace --codecov --output-path codecov.json
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: codecov.json
fail_ci_if_error: true

67
vendor/wiremock/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,67 @@
# Changelog
# Next
# `0.5.8`
- Features:
- Functions from `&Request` to `ResponseTemplate` can now be passed to `MockBuilder::respond_with`. You do not have to write a struct with a `Respond` implementation for simple manipulation of request data! (by [@RoGryza])
# `0.5.7`
- Fixes:
- `MockGuard` is now marked as `must_use`, ensuring that a compiler warning is raised if the guard for a scoped mock is not bound to a variable.
# `0.5.6`
- Features:
- Added support for **scoped** `Mock`s!
Using `MockServer::register_as_scoped` or `Mock::mount_as_scoped` you can now register `Mock`s that go "out of scope" when the returned RAII guard (`MockGuard`) is dropped.
Scoped `Mock`s are recommended for usage in test helper functions to ensure proper isolation - check the documentation for more details! (by [@LukeMathWalker])
# `0.5.5`
- Miscellaneous:
- Added the `http` module to re-export the types from `http-types` that are part of `wiremock`'s public API (by [@LukeMathWalker]).
# `0.5.4`
- Fixes:
- Handle URI authority properly in the mock server (by [@Tuetuopay])
# `0.5.3`
- Miscellaneous:
- Remove `textwrap` from the dependency tree (by [@apiraino])
# `0.5.2`
- Features:
- Support multi-valued headers (by [@beltram])
- Miscellaneous:
- Improve README (by [@apiraino])
# `0.5.1`
- Features:
- Capture the port in `hyper`'s `Request` into `wiremock::Request::url` (by [@beltram])
# `0.5.0`
- Breaking changes:
- Removed `MockServer::start_on`.
Use `MockServer::builder` and `MockServerBuilder::listener` to configure your `MockServer` to start on a pre-determined port (by [@LukeMathWalker]).
- `MockServer::verify` is now asynchronous (by [@LukeMathWalker]).
- Features:
- Added request recording to `MockServer`, enabled by default.
The recorded requests are used to display more meaningful error messages when assertions are not satisfied and can be retrieved using `MockServer::received_requests` ([by @LukeMathWalker]).
- Added `MockServerBuilder` to customise the configuration of a `MockServer`.
It can be used to bind a `MockServer` to an existing `TcpListener` as well as disabling request recording (by [@LukeMathWalker]).
- Added `matchers::body_json_schema` to verify the structure of the body attached to an incoming request (by [@Michael-J-Ward]).
[@Michael-J-Ward]: https://github.com/Michael-J-Ward
[@LukeMathWalker]: https://github.com/LukeMathWalker
[@beltram]: https://github.com/beltram
[@apiraino]: https://github.com/apiraino
[@Tuetuopay]: https://github.com/Tuetuopay
[@RoGryza]: https://github.com/RoGryza

76
vendor/wiremock/CODE_OF_CONDUCT.md vendored Normal file
View File

@@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at rust@lpalmieri.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

2113
vendor/wiremock/Cargo.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

144
vendor/wiremock/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,144 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2024"
name = "wiremock"
version = "0.6.5"
authors = ["Luca Palmieri <rust@lpalmieri.com>"]
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "HTTP mocking to test Rust applications."
documentation = "https://docs.rs/wiremock/"
readme = "README.md"
keywords = [
"test",
"http",
"mocking",
"mock",
"black-box",
]
categories = [
"development-tools",
"asynchronous",
]
license = "MIT/Apache-2.0"
repository = "https://github.com/LukeMathWalker/wiremock-rs"
[lib]
name = "wiremock"
path = "src/lib.rs"
[[test]]
name = "mocks"
path = "tests/mocks.rs"
[[test]]
name = "priority"
path = "tests/priority.rs"
[[test]]
name = "request_header_matching"
path = "tests/request_header_matching.rs"
[[test]]
name = "timeout"
path = "tests/timeout.rs"
[[test]]
name = "tokio"
path = "tests/tokio.rs"
[dependencies.assert-json-diff]
version = "2.0.2"
[dependencies.base64]
version = "0.22"
[dependencies.deadpool]
version = "0.12.2"
[dependencies.futures]
version = "0.3.31"
[dependencies.http]
version = "1.3"
[dependencies.http-body-util]
version = "0.1"
[dependencies.hyper]
version = "1.7"
features = ["full"]
[dependencies.hyper-util]
version = "0.1"
features = [
"tokio",
"server",
"http1",
"http2",
]
[dependencies.log]
version = "0.4"
[dependencies.once_cell]
version = "1"
[dependencies.regex]
version = "1"
[dependencies.serde]
version = "1"
[dependencies.serde_json]
version = "1"
[dependencies.tokio]
version = "1.47.1"
features = [
"rt",
"macros",
"net",
]
[dependencies.url]
version = "2.5"
[dev-dependencies.actix-rt]
version = "2.10.0"
[dev-dependencies.async-std]
version = "1.13.2"
features = [
"attributes",
"tokio1",
]
[dev-dependencies.reqwest]
version = "0.12.23"
features = ["json"]
[dev-dependencies.serde]
version = "1"
features = ["derive"]
[dev-dependencies.tokio]
version = "1.47.1"
features = [
"macros",
"rt-multi-thread",
]

201
vendor/wiremock/LICENSE-APACHE vendored Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

23
vendor/wiremock/LICENSE-MIT vendored Normal file
View File

@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

206
vendor/wiremock/README.md vendored Normal file
View File

@@ -0,0 +1,206 @@
<h1 align="center">wiremock</h1>
<div align="center">
<strong>
HTTP mocking to test Rust applications.
</strong>
</div>
<br />
<div align="center">
<!-- Crates version -->
<a href="https://crates.io/crates/wiremock">
<img src="https://img.shields.io/crates/v/wiremock.svg?style=flat-square"
alt="Crates.io version" />
</a>
<!-- Downloads -->
<a href="https://crates.io/crates/wiremock">
<img src="https://img.shields.io/crates/d/wiremock.svg?style=flat-square"
alt="Download" />
</a>
<!-- docs.rs docs -->
<a href="https://docs.rs/wiremock">
<img src="https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square"
alt="docs.rs docs" />
</a>
</div>
<br/>
`wiremock` provides HTTP mocking to perform black-box testing of Rust applications that
interact with third-party APIs.
It provides mocking of HTTP responses using request matching and response templating.
*The name `wiremock` is a reference to [`WireMock.Net`](https://github.com/WireMock-Net/WireMock.Net), a .NET port of the original [`Wiremock`](http://wiremock.org/) from Java.*
<div align="center">
<a style="display: inline" href="https://docs.rs/wiremock">Documentation</a>
<span style="display: inline"> - </span>
<a style="display: inline" href="https://crates.io/crates/wiremock">Crates.io</a>
</div>
# Table of Contents
0. [How to install](#how-to-install)
1. [Getting started](#getting-started)
2. [Matchers](#matchers)
3. [Spying](#spying)
4. [Responses](#spying)
5. [Test isolation](#test-isolation)
6. [Runtime compatibility](#runtime-compatibility)
7. [Efficiency](#efficiency)
8. [Prior art](#prior-art)
9. [Future evolution](#future-evolution)
10. [Related projects](#related-projects)
11. [License](#license)
## How to install
Add `wiremock` to your development dependencies by editing the `Cargo.toml` file:
```toml
[dev-dependencies]
# ...
wiremock = "0.6"
```
Or by running:
```bash
cargo add wiremock --dev
```
## Getting started
```rust
use wiremock::{MockServer, Mock, ResponseTemplate};
use wiremock::matchers::{method, path};
#[async_std::main]
async fn main() {
// Start a background HTTP server on a random local port
let mock_server = MockServer::start().await;
// Arrange the behaviour of the MockServer adding a Mock:
// when it receives a GET request on '/hello' it will respond with a 200.
Mock::given(method("GET"))
.and(path("/hello"))
.respond_with(ResponseTemplate::new(200))
// Mounting the mock on the mock server - it's now effective!
.mount(&mock_server)
.await;
// If we probe the MockServer using any HTTP client it behaves as expected.
let status = reqwest::get(format!("{}/hello", &mock_server.uri()))
.await
.unwrap()
.status();
assert_eq!(status.as_u16(), 200);
// If the request doesn't match any `Mock` mounted on our `MockServer` a 404 is returned.
let status = reqwest::get(format!("{}/missing", &mock_server.uri()))
.await
.unwrap()
.status();
assert_eq!(status.as_u16(), 404);
}
```
## Matchers
`wiremock` provides a set of matching strategies out of the box - check the [`matchers`] module
for a complete list.
You can define your own matchers using the [`Match`] trait, as well as using `Fn` closures.
Check [`Match`]'s documentation for more details and examples.
## Spying
`wiremock` empowers you to set expectations on the number of invocations to your [`Mock`]s -
check the [`expect`] method for more details.
Expectations can be used to verify that a side-effect has (or has not) taken place!
Expectations are automatically verified during the shutdown of each [`MockServer`] instance,
at the end of your test. A failed verification will trigger a panic.
By default, no expectations are set on your [`Mock`]s.
## Responses
`wiremock` lets you specify pre-determined responses using [`ResponseTemplate`] and
[`respond_with`].
You are also given the option to have [`Mock`]s return different responses based on the matched
[`Request`] using the [`Respond`] trait.
Check [`Respond`]'s documentation for more details and examples.
## Test isolation
Each instance of [`MockServer`] is fully isolated: [`start`] takes care of finding a random port
available on your local machine which is assigned to the new [`MockServer`].
To ensure full isolation and no cross-test interference, [`MockServer`]s shouldn't be
shared between tests. Instead, [`MockServer`]s should be created in the test where they are used.
When a [`MockServer`] instance goes out of scope (e.g. the test finishes), the corresponding
HTTP server running in the background is shut down to free up the port it was using.
## Runtime compatibility
`wiremock` can be used (and it is tested to work) with both [`async_std`] and [`tokio`] as
futures runtimes.
If you encounter any compatibility bug, please open an issue on our [GitHub repository].
## Efficiency
`wiremock` maintains a pool of mock servers in the background to minimise the number of
connections and the time spent starting up a new [`MockServer`].
Pooling reduces the likelihood of you having to tune your OS configurations (e.g. ulimit).
The pool is designed to be invisible: it makes your life easier and your tests faster. If you
end up having to worry about it, it's a bug: open an issue!
## Prior art
[`mockito`] and [`httpmock`] provide HTTP mocking for Rust.
Check the table below to see how `wiremock` compares to them across the following dimensions:
- Test execution strategy (do tests have to be executed sequentially or can they be executed in parallel?);
- How many APIs can I mock in a test?
- Out-of-the-box request matchers;
- Extensible request matching (i.e. you can define your own matchers);
- Sync/Async API;
- Spying (e.g. verify that a mock has/hasn't been called in a test);
- Standalone mode (i.e. can I launch an HTTP mock server outside of a test suite?).
| | Test execution strategy | How many APIs can I mock? | Out-of-the-box request matchers | Extensible request matching | API | Spying | Standalone mode |
|-----------|-------------------------|---------------------------|---------------------------------|-----------------------------|------------|--------|-----------------|
| mockito | ✔ Parallel | ✔ Unbounded | ✔ | ❌ | Async/Sync | ✔ | ❌ |
| httpmock | ✔ Parallel | ✔ Unbounded | ✔ | ✔ | Async/Sync | ✔ | ✔ |
| wiremock | ✔ Parallel | ✔ Unbounded | ✔ | ✔ | Async | ✔ | ❌ |
## Future evolution
More request matchers can be added to those provided out-of-the-box to handle common usecases.
## Related projects
* [`stubr`](https://github.com/beltram/stubr) for mounting [`Wiremock`](http://wiremock.org/) json stubs in a [`MockServer`]. Also works as a cli.
## License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
[`MockServer`]: https://docs.rs/wiremock/latest/wiremock/struct.MockServer.html
[`Mock`]: https://docs.rs/wiremock/latest/wiremock/struct.Mock.html
[`ResponseTemplate`]: https://docs.rs/wiremock/latest/wiremock/struct.ResponseTemplate.html
[`Request`]: https://docs.rs/wiremock/latest/wiremock/struct.Request.html
[`Match`]: https://docs.rs/wiremock/latest/wiremock/trait.Match.html
[`Respond`]: https://docs.rs/wiremock/latest/wiremock/trait.Respond.html
[`start`]: https://docs.rs/wiremock/latest/wiremock/struct.MockServer.html#method.start
[`expect`]: https://docs.rs/wiremock/latest/wiremock/struct.Mock.html#method.expect
[`respond_with`]: https://docs.rs/wiremock/latest/wiremock/struct.MockBuilder.html#method.respond_with
[`matchers`]: https://docs.rs/wiremock/latest/wiremock/matchers/index.html
[GitHub repository]: https://github.com/LukeMathWalker/wiremock-rs
[`mockito`]: https://docs.rs/mockito/
[`httpmock`]: https://docs.rs/httpmock/
[`async_std`]: https://docs.rs/async-std/
[`tokio`]: https://docs.rs/tokio/

21
vendor/wiremock/deny.toml vendored Normal file
View File

@@ -0,0 +1,21 @@
[licenses]
# List of explicitly allowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
allow = [
"MIT",
"Apache-2.0",
"Apache-2.0 WITH LLVM-exception",
"MPL-2.0",
"ISC",
"CC0-1.0",
"Unicode-DFS-2016",
"Unicode-3.0",
"BSD-3-Clause",
"BSL-1.0",
"OpenSSL",
"Zlib",
"CDLA-Permissive-2.0",
]
unused-allowed-license = "allow"
confidence-threshold = 0.8

3
vendor/wiremock/src/http.rs vendored Normal file
View File

@@ -0,0 +1,3 @@
//! Convenient re-exports of http types that are part of `wiremock`'s public API.
pub use http::{HeaderMap, HeaderName, HeaderValue, Method};
pub use url::Url;

164
vendor/wiremock/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,164 @@
#![allow(clippy::needless_doctest_main)]
//! `wiremock` provides HTTP mocking to perform black-box testing of Rust applications that
//! interact with third-party APIs.
//!
//! It provides mocking of HTTP responses using request matching and response templating.
//!
//! ## How to install
//!
//! Add `wiremock` to your development dependencies:
//! ```toml
//! [dev-dependencies]
//! # ...
//! wiremock = "0.5"
//! ```
//! If you are using [`cargo-edit`](https://github.com/killercup/cargo-edit), run
//! ```bash
//! cargo add wiremock --dev
//! ```
//!
//! ## Getting started
//!
//! ```rust
//! use wiremock::{MockServer, Mock, ResponseTemplate};
//! use wiremock::matchers::{method, path};
//!
//! #[async_std::main]
//! async fn main() {
//! // Start a background HTTP server on a random local port
//! let mock_server = MockServer::start().await;
//!
//! // Arrange the behaviour of the MockServer adding a Mock:
//! // when it receives a GET request on '/hello' it will respond with a 200.
//! Mock::given(method("GET"))
//! .and(path("/hello"))
//! .respond_with(ResponseTemplate::new(200))
//! // Mounting the mock on the mock server - it's now effective!
//! .mount(&mock_server)
//! .await;
//!
//! // If we probe the MockServer using any HTTP client it behaves as expected.
//! let status = reqwest::get(format!("{}/hello", &mock_server.uri()))
//! .await
//! .unwrap()
//! .status();
//! assert_eq!(status, 200);
//!
//! // If the request doesn't match any `Mock` mounted on our `MockServer` a 404 is returned.
//! let status = reqwest::get(format!("{}/missing", &mock_server.uri()))
//! .await
//! .unwrap()
//! .status();
//! assert_eq!(status, 404);
//! }
//! ```
//!
//! ## Matchers
//!
//! `wiremock` provides a set of matching strategies out of the box - check the [`matchers`] module
//! for a complete list.
//!
//! You can define your own matchers using the [`Match`] trait, as well as using `Fn` closures.
//! Check [`Match`]'s documentation for more details and examples.
//!
//! ## Spying
//!
//! `wiremock` empowers you to set expectations on the number of invocations to your [`Mock`]s -
//! check the [`expect`] method for more details.
//!
//! Expectations can be used to verify that a side-effect has (or has not) taken place!
//!
//! Expectations are automatically verified during the shutdown of each [`MockServer`] instance,
//! at the end of your test. A failed verification will trigger a panic.
//! By default, no expectations are set on your [`Mock`]s.
//!
//! ## Responses
//!
//! `wiremock` lets you specify pre-determined responses using [`ResponseTemplate`] and
//! [`respond_with`].
//!
//! You also given the option to have [`Mock`]s that return different responses based on the matched
//! [`Request`] using the [`Respond`] trait.
//! Check [`Respond`]'s documentation for more details and examples.
//!
//! ## Test isolation
//!
//! Each instance of [`MockServer`] is fully isolated: [`start`] takes care of finding a random port
//! available on your local machine which is assigned to the new [`MockServer`].
//!
//! To ensure full isolation and no cross-test interference, [`MockServer`]s shouldn't be
//! shared between tests. Instead, [`MockServer`]s should be created in the test where they are used.
//!
//! When a [`MockServer`] instance goes out of scope (e.g. the test finishes), the corresponding
//! HTTP server running in the background is shut down to free up the port it was using.
//!
//! ## Runtime compatibility
//!
//! `wiremock` can be used (and it is tested to work) with both [`async_std`] and [`tokio`] as
//! futures runtimes.
//! If you encounter any compatibility bug, please open an issue on our [GitHub repository].
//!
//! ## Efficiency
//!
//! `wiremock` maintains a pool of mock servers in the background to minimise the number of
//! connections and the time spent starting up a new [`MockServer`].
//! Pooling reduces the likelihood of you having to tune your OS configurations (e.g. ulimit).
//!
//! The pool is designed to be invisible: it makes your life easier and your tests faster. If you
//! end up having to worry about it, it's a bug: open an issue!
//!
//! ## Prior art
//!
//! [`mockito`] and [`httpmock`] provide HTTP mocking for Rust.
//!
//! Check the table below to see how `wiremock` compares to them across the following dimensions:
//! - Test execution strategy (do tests have to be executed sequentially or can they be executed in parallel?);
//! - How many APIs can I mock in a test?
//! - Out-of-the-box request matchers;
//! - Extensible request matching (i.e. you can define your own matchers);
//! - Sync/Async API;
//! - Spying (e.g. verify that a mock has/hasn't been called in a test);
//! - Standalone mode (i.e. can I launch an HTTP mock server outside of a test suite?).
//!
//! | | Test execution strategy | How many APIs can I mock? | Out-of-the-box request matchers | Extensible request matching | API | Spying | Standalone mode |
//! |-----------|-------------------------|---------------------------|---------------------------------|----------------------------|-------|----------|-----------------|
//! | mockito | ❌ Sequential | ❌ 1 | ✔ | ❌ | Sync | ✔ | ❌ |
//! | httpmock | ✔ Parallel | ✔ Unbounded | ✔ | ✔ | Async/Sync | ✔ | ✔ |
//! | wiremock | ✔ Parallel | ✔ Unbounded | ✔ | ✔ | Async | ✔ | ❌ |
//!
//!
//! ## Future evolution
//!
//! More request matchers can be added to those provided out-of-the-box to handle common usecases.
//!
//! ## Related projects
//!
//! * [`stubr`](https://github.com/beltram/stubr) for mounting [`Wiremock`](http://wiremock.org/) json stubs in a [`MockServer`]. Also works as a cli.
//!
//! [`MockServer`]: MockServer
//! [`start`]: MockServer::start
//! [`expect`]: Mock::expect
//! [`respond_with`]: MockBuilder::respond_with
//! [GitHub repository]: https://github.com/LukeMathWalker/wiremock-rs
//! [`mockito`]: https://docs.rs/mockito/
//! [`httpmock`]: https://docs.rs/httpmock/
//! [`async_std`]: https://docs.rs/async-std/
//! [`tokio`]: https://docs.rs/tokio/
pub mod http;
pub mod matchers;
mod mock;
mod mock_server;
mod mock_set;
mod mounted_mock;
mod request;
mod respond;
mod response_template;
mod verification;
pub type ErrorResponse = Box<dyn std::error::Error + Send + Sync + 'static>;
pub use mock::{Match, Mock, MockBuilder, Times};
pub use mock_server::{MockGuard, MockServer, MockServerBuilder};
pub use request::{BodyPrintLimit, Request};
pub use respond::Respond;
pub use response_template::ResponseTemplate;

1208
vendor/wiremock/src/matchers.rs vendored Normal file

File diff suppressed because it is too large Load Diff

825
vendor/wiremock/src/mock.rs vendored Normal file
View File

@@ -0,0 +1,825 @@
use crate::respond::{Respond, RespondErr};
use crate::{ErrorResponse, MockGuard, MockServer, Request, ResponseTemplate};
use std::fmt::{Debug, Formatter};
use std::ops::{
Range, RangeBounds, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive,
};
/// Anything that implements `Match` can be used to constrain when a [`Mock`] is activated.
///
/// `Match` can be used to extend the set of matchers provided out-of-the-box by `wiremock` to
/// cater to your specific testing needs:
/// ```rust
/// use wiremock::{Match, MockServer, Mock, Request, ResponseTemplate};
/// use wiremock::matchers::HeaderExactMatcher;
/// use std::convert::TryInto;
///
/// // Check that a header with the specified name exists and its value has an odd length.
/// pub struct OddHeaderMatcher(http::HeaderName);
///
/// impl Match for OddHeaderMatcher {
/// fn matches(&self, request: &Request) -> bool {
/// match request.headers.get(&self.0) {
/// // We are ignoring multi-valued headers for simplicity
/// Some(value) => value.to_str().unwrap_or_default().len() % 2 == 1,
/// None => false
/// }
/// }
/// }
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
///
/// Mock::given(OddHeaderMatcher("custom".try_into().unwrap()))
/// .respond_with(ResponseTemplate::new(200))
/// .mount(&mock_server)
/// .await;
///
/// let client = reqwest::Client::new();
///
/// // Even length
/// let status = client
/// .get(&mock_server.uri())
/// .header("custom", "even")
/// .send()
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 404);
///
/// // Odd length
/// let status = client
/// .get(&mock_server.uri())
/// .header("custom", "odd")
/// .send()
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 200);
/// }
/// ```
///
/// Anonymous functions that take a reference to a [`Request`] as input and return a boolean
/// as output automatically implement the `Match` trait.
///
/// The previous example could be rewritten as follows:
/// ```rust
/// use wiremock::{Match, MockServer, Mock, Request, ResponseTemplate};
/// use wiremock::matchers::HeaderExactMatcher;
/// use std::convert::TryInto;
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
///
/// let header_name = http::HeaderName::from_static("custom");
/// // Check that a header with the specified name exists and its value has an odd length.
/// let matcher = move |request: &Request| {
/// match request.headers.get(&header_name) {
/// Some(value) => value.to_str().unwrap_or_default().len() % 2 == 1,
/// None => false
/// }
/// };
///
/// Mock::given(matcher)
/// .respond_with(ResponseTemplate::new(200))
/// .mount(&mock_server)
/// .await;
///
/// let client = reqwest::Client::new();
///
/// // Even length
/// let status = client
/// .get(&mock_server.uri())
/// .header("custom", "even")
/// .send()
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 404);
///
/// // Odd length
/// let status = client
/// .get(&mock_server.uri())
/// .header("custom", "odd")
/// .send()
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 200);
/// }
/// ```
pub trait Match: Send + Sync {
/// Given a reference to a [`Request`], determine if it should match or not given
/// a specific criterion.
fn matches(&self, request: &Request) -> bool;
}
/// Wrapper around a `Match` trait object.
///
/// We need the wrapper to provide a (fake) implementation of `Debug`,
/// thus allowing us to pass this struct around as a `bastion` message.
/// This is because Rust's closures do not implement `Debug`.
///
/// We wouldn't need this if `bastion` didn't require `Debug` as a trait bound for its Message trait
/// or if Rust automatically implemented `Debug` for closures.
pub(crate) struct Matcher(Box<dyn Match>);
impl Match for Matcher {
fn matches(&self, request: &Request) -> bool {
self.0.matches(request)
}
}
impl Debug for Matcher {
fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result {
// Dummy `Debug` implementation to allow us to pass `Matcher` as a message in `bastion`.
// It's needed because closures do not implement `Debug` and we really want to enable
// closures as matchers from an API perspective.
// Might re-think this in the future.
Ok(())
}
}
/// Given a set of matchers, a `Mock` instructs an instance of [`MockServer`] to return a pre-determined response if the matching conditions are satisfied.
///
/// `Mock`s have to be mounted (or registered) with a [`MockServer`] to become effective.
/// You can use:
///
/// - [`MockServer::register`] or [`Mock::mount`] to activate a **global** `Mock`;
/// - [`MockServer::register_as_scoped`] or [`Mock::mount_as_scoped`] to activate a **scoped** `Mock`.
///
/// Check the respective documentations for more details (or look at the following examples!).
///
/// # Example (using [`register`]):
///
/// ```rust
/// use wiremock::{MockServer, Mock, ResponseTemplate};
/// use wiremock::matchers::method;
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
///
/// let response = ResponseTemplate::new(200);
///
/// let mock = Mock::given(method("GET")).respond_with(response.clone());
/// // Registering the mock with the mock server - it's now effective!
/// mock_server.register(mock).await;
///
/// // We won't register this mock instead.
/// let unregistered_mock = Mock::given(method("POST")).respond_with(response);
///
/// // Act
/// let status = reqwest::get(&mock_server.uri())
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 200);
///
/// // This would have matched `unregistered_mock`, but we haven't registered it!
/// // Hence it returns a 404, the default response when no mocks matched on the mock server.
/// let client = reqwest::Client::new();
/// let status = client.post(&mock_server.uri())
/// .send()
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 404);
/// }
/// ```
///
/// # Example (using [`mount`]):
///
/// If you prefer a fluent style, you can use the [`mount`] method on the `Mock` itself
/// instead of [`register`].
/// ```rust
/// use wiremock::{MockServer, Mock, ResponseTemplate};
/// use wiremock::matchers::method;
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
///
/// Mock::given(method("GET"))
/// .respond_with(ResponseTemplate::new(200))
/// .up_to_n_times(1)
/// // Mounting the mock on the mock server - it's now effective!
/// .mount(&mock_server)
/// .await;
///
/// // Act
/// let status = reqwest::get(&mock_server.uri())
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 200);
/// }
/// ```
///
/// # Example (using [`mount_as_scoped`]):
///
/// Sometimes you will need a `Mock` to be active within the scope of a function, but not any longer.
/// You can use [`Mock::mount_as_scoped`] to precisely control how long a `Mock` stays active.
///
/// ```rust
/// use wiremock::{MockServer, Mock, ResponseTemplate};
/// use wiremock::matchers::method;
///
/// async fn my_test_helper(mock_server: &MockServer) {
/// let mock_guard = Mock::given(method("GET"))
/// .respond_with(ResponseTemplate::new(200))
/// .expect(1)
/// .named("my_test_helper GET /")
/// .mount_as_scoped(mock_server)
/// .await;
///
/// reqwest::get(&mock_server.uri())
/// .await
/// .unwrap();
///
/// // `mock_guard` is dropped, expectations are verified!
/// }
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
/// my_test_helper(&mock_server).await;
///
/// // Act
///
/// // This would have returned 200 if the `Mock` in
/// // `my_test_helper` had not been scoped.
/// let status = reqwest::get(&mock_server.uri())
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 404);
/// }
/// ```
///
/// [`register`]: MockServer::register
/// [`mount`]: Mock::mount
/// [`mount_as_scoped`]: Mock::mount_as_scoped
#[must_use = "`Mock`s have to be mounted or registered with a `MockServer` to become effective"]
pub struct Mock {
pub(crate) matchers: Vec<Matcher>,
pub(crate) response: Result<Box<dyn Respond>, Box<dyn RespondErr>>,
/// Maximum number of times (inclusive) we should return a response from this Mock on
/// matching requests.
/// If `None`, there is no cap and we will respond to all incoming matching requests.
/// If `Some(max_n_matches)`, when `max_n_matches` matching incoming requests have been processed,
/// [`crate::mounted_mock::MountedMock::matches`] should start returning `false`, regardless of the incoming request.
pub(crate) max_n_matches: Option<u64>,
/// Allows prioritizing a Mock over another one.
/// `1` is the highest priority, `255` the lowest, default to `5`.
/// When priority is the same, it fallbacks to insertion order.
pub(crate) priority: u8,
/// The friendly mock name specified by the user.
/// Used in diagnostics and error messages if the mock expectations are not satisfied.
pub(crate) name: Option<String>,
/// The expectation is satisfied if the number of incoming requests falls within `expectation_range`.
pub(crate) expectation_range: Times,
}
/// A fluent builder to construct a [`Mock`] instance given matchers and a [`ResponseTemplate`].
#[derive(Debug)]
pub struct MockBuilder {
pub(crate) matchers: Vec<Matcher>,
}
impl Mock {
/// Start building a [`Mock`] specifying the first matcher.
///
/// It returns an instance of [`MockBuilder`].
pub fn given<M: 'static + Match>(matcher: M) -> MockBuilder {
MockBuilder {
matchers: vec![Matcher(Box::new(matcher))],
}
}
/// Specify an upper limit to the number of times you would like this [`Mock`] to respond to
/// incoming requests that satisfy the conditions imposed by your [`matchers`].
///
/// ### Example:
///
/// ```rust
/// use wiremock::{MockServer, Mock, ResponseTemplate};
/// use wiremock::matchers::method;
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
///
/// Mock::given(method("GET"))
/// .respond_with(ResponseTemplate::new(200))
/// // Default behaviour will have this Mock responding to any incoming request
/// // that satisfied our matcher (e.g. being a GET request).
/// // We can opt out of the default behaviour by setting a cap on the number of
/// // matching requests this Mock should respond to.
/// //
/// // In this case, once one matching request has been received, the mock will stop
/// // matching additional requests and you will receive a 404 if no other mock
/// // matches on those requests.
/// .up_to_n_times(1)
/// .mount(&mock_server)
/// .await;
///
/// // Act
///
/// // The first request matches, as expected.
/// let status = reqwest::get(&mock_server.uri())
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 200);
///
/// // The second request does NOT match given our `up_to_n_times(1)` setting.
/// let status = reqwest::get(&mock_server.uri())
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 404);
/// }
/// ```
///
/// [`matchers`]: crate::matchers
pub fn up_to_n_times(mut self, n: u64) -> Mock {
assert!(n > 0, "n must be strictly greater than 0!");
self.max_n_matches = Some(n);
self
}
/// Specify a priority for this [`Mock`].
/// Use this when you mount many [`Mock`] in a [`MockServer`]
/// and those mocks have interlaced request matching conditions
/// e.g. `mock A` accepts path `/abcd` and `mock B` a path regex `[a-z]{4}`
/// It is recommended to set the highest priority (1) for mocks with exact conditions (`mock A` in this case)
/// `1` is the highest priority, `255` the lowest, default to `5`
/// If two mocks have the same priority, priority is defined by insertion order (first one mounted has precedence over the others).
///
/// ### Example:
///
/// ```rust
/// use wiremock::{MockServer, Mock, ResponseTemplate};
/// use wiremock::matchers::{method, path, path_regex};
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
///
/// Mock::given(method("GET"))
/// .and(path("abcd"))
/// .respond_with(ResponseTemplate::new(200))
/// .with_priority(1) // highest priority
/// .mount(&mock_server)
/// .await;
///
/// Mock::given(method("GET"))
/// .and(path_regex("[a-z]{4}"))
/// .respond_with(ResponseTemplate::new(201))
/// .with_priority(2)
/// .mount(&mock_server)
/// .await;
///
/// // Act
///
/// // The request with highest priority, as expected.
/// let status = reqwest::get(&format!("{}/abcd", mock_server.uri()))
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 200);
/// }
/// ```
///
/// [`matchers`]: crate::matchers
pub fn with_priority(mut self, p: u8) -> Mock {
assert!(p > 0, "priority must be strictly greater than 0!");
self.priority = p;
self
}
/// Set an expectation on the number of times this [`Mock`] should match in the current
/// test case.
/// Expectations are verified when the [`MockServer`] is shutting down: if the expectation
/// is not satisfied, the [`MockServer`] will panic and the `error_message` is shown.
///
/// By default, no expectation is set for [`Mock`]s.
///
/// ### When is this useful?
///
/// `expect` can turn out handy when you'd like to verify that a certain side-effect has
/// (or has not!) taken place.
///
/// For example:
/// - check that a 3rd party notification API (e.g. email service) is called when an event
/// in your application is supposed to trigger a notification;
/// - check that a 3rd party API is NOT called when the response of a call is expected
/// to be retrieved from a cache (`.expect(0)`).
///
/// This technique is also called [spying](https://martinfowler.com/bliki/TestDouble.html).
///
/// ### Example:
///
/// ```rust
/// use wiremock::{MockServer, Mock, ResponseTemplate};
/// use wiremock::matchers::method;
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
///
/// Mock::given(method("GET"))
/// .respond_with(ResponseTemplate::new(200))
/// .up_to_n_times(2)
/// // We expect the mock to be called at least once.
/// // If that does not happen, the `MockServer` will panic on shutdown,
/// // causing the whole test to fail.
/// .expect(1..)
/// // We assign a name to the mock - it will be shown in error messages
/// // if our expectation is not verified!
/// .named("Root GET")
/// .mount(&mock_server)
/// .await;
///
/// // Act
/// let status = reqwest::get(&mock_server.uri())
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 200);
///
/// // Assert
/// // We made at least one matching request, the expectation is satisfied.
/// // The `MockServer` will shutdown peacefully, without panicking.
/// }
/// ```
pub fn expect<T: Into<Times>>(mut self, r: T) -> Self {
let range = r.into();
self.expectation_range = range;
self
}
/// Assign a name to your mock.
///
/// The mock name will be used in error messages (e.g. if the mock expectation
/// is not satisfied) and debug logs to help you identify what failed.
///
/// ### Example:
///
/// ```should_panic
/// use wiremock::{MockServer, Mock, ResponseTemplate};
/// use wiremock::matchers::method;
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
///
/// // We have two mocks in the same test - how do we find out
/// // which one failed when the test panics?
/// // Assigning a name to each mock with `named` gives us better error
/// // messages and makes it much easier to debug why a test is failing!
/// Mock::given(method("GET"))
/// .respond_with(ResponseTemplate::new(200))
/// .up_to_n_times(2)
/// .expect(1..)
/// // We assign a name to the mock - it will be shown in error messages
/// // if our expectation is not verified!
/// .named("Root GET")
/// .mount(&mock_server)
/// .await;
///
/// Mock::given(method("POST"))
/// .respond_with(ResponseTemplate::new(200))
/// .up_to_n_times(2)
/// .expect(1..)
/// // We assign a name to the mock - it will be shown in error messages
/// // if our expectation is not verified!
/// .named("Root POST")
/// .mount(&mock_server)
/// .await;
///
/// // Act
/// let status = reqwest::get(&mock_server.uri())
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 200);
///
/// // Assert
/// // We did not make a POST request, therefore the expectation on `Root POST`
/// // is not satisfied and the test will panic.
/// }
/// ```
pub fn named<T: Into<String>>(mut self, mock_name: T) -> Self {
self.name = Some(mock_name.into());
self
}
/// Mount a [`Mock`] on an instance of [`MockServer`].
/// The [`Mock`] will remain active until [`MockServer`] is shut down. If you want to control or limit how
/// long your [`Mock`] stays active, check out [`Mock::mount_as_scoped`].
///
/// Be careful! [`Mock`]s are not effective until they are [`mount`]ed or [`register`]ed on a [`MockServer`].
/// [`mount`] is an asynchronous method, make sure to `.await` it!
///
/// [`register`]: MockServer::register
/// [`mount`]: Mock::mount
pub async fn mount(self, server: &MockServer) {
server.register(self).await;
}
/// Mount a [`Mock`] as **scoped** on an instance of [`MockServer`].
///
/// When using [`mount`], your [`Mock`]s will be active until the [`MockServer`] is shut down.
/// When using `mount_as_scoped`, your [`Mock`]s will be active as long as the returned [`MockGuard`] is not dropped.
/// When the returned [`MockGuard`] is dropped, [`MockServer`] will verify that the expectations set on the scoped [`Mock`] were
/// verified - if not, it will panic.
///
/// `mount_as_scoped` is the ideal solution when you need a [`Mock`] within a test helper
/// but you do not want it to linger around after the end of the function execution.
///
/// # Limitations
///
/// When expectations of a scoped [`Mock`] are not verified, it will trigger a panic - just like a normal [`Mock`].
/// Due to [limitations](https://internals.rust-lang.org/t/should-drop-glue-use-track-caller/13682) in Rust's [`Drop`] trait,
/// the panic message will not include the filename and the line location
/// where the corresponding [`MockGuard`] was dropped - it will point into `wiremock`'s source code.
///
/// This can be an issue when you are using more than one scoped [`Mock`] in a single test - which of them panicked?
/// To improve your debugging experience it is strongly recommended to use [`Mock::named`] to assign a unique
/// identifier to your scoped [`Mock`]s, which will in turn be referenced in the panic message if their expectations are
/// not met.
///
/// # Example:
///
/// - The behaviour of the scoped mock is invisible outside of `my_test_helper`.
///
/// ```rust
/// use wiremock::{MockServer, Mock, ResponseTemplate};
/// use wiremock::matchers::method;
///
/// async fn my_test_helper(mock_server: &MockServer) {
/// let mock_guard = Mock::given(method("GET"))
/// .respond_with(ResponseTemplate::new(200))
/// .expect(1)
/// .named("my_test_helper GET /")
/// .mount_as_scoped(mock_server)
/// .await;
///
/// reqwest::get(&mock_server.uri())
/// .await
/// .unwrap();
///
/// // `mock_guard` is dropped, expectations are verified!
/// }
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
/// my_test_helper(&mock_server).await;
///
/// // Act
///
/// // This would have returned 200 if the `Mock` in
/// // `my_test_helper` had not been scoped.
/// let status = reqwest::get(&mock_server.uri())
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 404);
/// }
/// ```
///
/// - The expectations for the scoped mock are not verified, it panics at the end of `my_test_helper`.
///
/// ```rust,should_panic
/// use wiremock::{MockServer, Mock, ResponseTemplate};
/// use wiremock::matchers::method;
///
/// async fn my_test_helper(mock_server: &MockServer) {
/// let mock_guard = Mock::given(method("GET"))
/// .respond_with(ResponseTemplate::new(200))
/// .expect(1)
/// .named("my_test_helper GET /")
/// .mount_as_scoped(mock_server)
/// .await;
/// // `mock_guard` is dropped, expectations are NOT verified!
/// // Panic!
/// }
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
/// my_test_helper(&mock_server).await;
///
/// // Act
/// let status = reqwest::get(&mock_server.uri())
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 404);
/// }
/// ```
///
/// [`mount`]: Mock::mount
pub async fn mount_as_scoped(self, server: &MockServer) -> MockGuard {
server.register_as_scoped(self).await
}
/// Given a [`Request`] build an instance a [`ResponseTemplate`] using
/// the responder associated with the `Mock`.
pub(crate) fn response_template(
&self,
request: &Request,
) -> Result<ResponseTemplate, ErrorResponse> {
match &self.response {
Ok(responder) => Ok(responder.respond(request)),
Err(responder_err) => Err(responder_err.respond_err(request)),
}
}
}
impl MockBuilder {
/// Add another request matcher to the mock you are building.
///
/// **All** specified [`matchers`] must match for the overall [`Mock`] to match an incoming request.
///
/// [`matchers`]: crate::matchers
pub fn and<M: Match + 'static>(mut self, matcher: M) -> Self {
self.matchers.push(Matcher(Box::new(matcher)));
self
}
/// Establish what [`ResponseTemplate`] should be used to generate a response when an incoming
/// request matches.
///
/// `respond_with` finalises the `MockBuilder` and returns you a [`Mock`] instance, ready to
/// be [`register`]ed or [`mount`]ed on a [`MockServer`]!
///
/// [`register`]: MockServer::register
/// [`mount`]: Mock::mount
pub fn respond_with<R: Respond + 'static>(self, responder: R) -> Mock {
Mock {
matchers: self.matchers,
response: Ok(Box::new(responder)),
max_n_matches: None,
priority: 5,
name: None,
expectation_range: Times(TimesEnum::Unbounded(RangeFull)),
}
}
/// Instead of response with an HTTP reply, return a Rust error.
///
/// This can simulate lower level errors, e.g., a [`ConnectionReset`] IO Error.
///
/// [`ConnectionReset`]: std::io::ErrorKind::ConnectionReset
pub fn respond_with_err<R: RespondErr + 'static>(self, responder_err: R) -> Mock {
Mock {
matchers: self.matchers,
response: Err(Box::new(responder_err)),
max_n_matches: None,
priority: 5,
name: None,
expectation_range: Times(TimesEnum::Unbounded(RangeFull)),
}
}
}
/// Specify how many times we expect a [`Mock`] to match via [`expect`].
/// It is used to set expectations on the usage of a [`Mock`] in a test case.
///
/// You can either specify an exact value, e.g.
/// ```rust
/// use wiremock::Times;
///
/// let times: Times = 10.into();
/// ```
/// or a range
/// ```rust
/// use wiremock::Times;
///
/// // Between 10 and 15 (not included) times
/// let times: Times = (10..15).into();
/// // Between 10 and 15 (included) times
/// let times: Times = (10..=15).into();
/// // At least 10 times
/// let times: Times = (10..).into();
/// // Strictly less than 15 times
/// let times: Times = (..15).into();
/// // Strictly less than 16 times
/// let times: Times = (..=15).into();
/// ```
///
/// [`expect`]: Mock::expect
#[derive(Clone, Debug)]
pub struct Times(TimesEnum);
impl Times {
pub(crate) fn contains(&self, n_calls: u64) -> bool {
match &self.0 {
TimesEnum::Exact(e) => e == &n_calls,
TimesEnum::Unbounded(r) => r.contains(&n_calls),
TimesEnum::Range(r) => r.contains(&n_calls),
TimesEnum::RangeFrom(r) => r.contains(&n_calls),
TimesEnum::RangeTo(r) => r.contains(&n_calls),
TimesEnum::RangeToInclusive(r) => r.contains(&n_calls),
TimesEnum::RangeInclusive(r) => r.contains(&n_calls),
}
}
}
impl std::fmt::Display for Times {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.0 {
TimesEnum::Exact(e) => write!(f, "== {}", e),
TimesEnum::Unbounded(_) => write!(f, "0 <= x"),
TimesEnum::Range(r) => write!(f, "{} <= x < {}", r.start, r.end),
TimesEnum::RangeFrom(r) => write!(f, "{} <= x", r.start),
TimesEnum::RangeTo(r) => write!(f, "0 <= x < {}", r.end),
TimesEnum::RangeToInclusive(r) => write!(f, "0 <= x <= {}", r.end),
TimesEnum::RangeInclusive(r) => write!(f, "{} <= x <= {}", r.start(), r.end()),
}
}
}
// Implementation notes: this has gone through a couple of iterations before landing to
// what you see now.
//
// The original draft had Times itself as an enum with two variants (Exact and Range), with
// the Range variant generic over `R: RangeBounds<u64>`.
//
// We switched to a generic struct wrapper around a private `R: RangeBounds<u64>` when we realised
// that you would have had to specify a range type when creating the Exact variant
// (e.g. as you do for `Option` when creating a `None` variant).
//
// We achieved the same functionality with a struct wrapper, but exact values had to converted
// to ranges with a single element (e.g. 15 -> 15..16).
// Not the most expressive representation, but we would have lived with it.
//
// We changed once again when we started to update our `MockActor`: we are storing all `Mock`s
// in a vector. Being generic over `R`, the range type leaked into the overall `Mock` (and `MountedMock`)
// type, thus making those generic as well over `R`.
// To store them in a vector all mocks would have had to use the same range internally, which is
// obviously an unreasonable restrictions.
// At the same time, we can't have a Box<dyn RangeBounds<u64>> because `contains` is a generic
// method hence the requirements for object safety are not satisfied.
//
// Thus we ended up creating this master enum that wraps all range variants with the addition
// of the Exact variant.
// If you can do better, please submit a PR.
// We keep them enum private to the crate to allow for future refactoring.
#[derive(Clone, Debug)]
pub(crate) enum TimesEnum {
Exact(u64),
Unbounded(RangeFull),
Range(Range<u64>),
RangeFrom(RangeFrom<u64>),
RangeTo(RangeTo<u64>),
RangeToInclusive(RangeToInclusive<u64>),
RangeInclusive(RangeInclusive<u64>),
}
impl From<u64> for Times {
fn from(x: u64) -> Self {
Times(TimesEnum::Exact(x))
}
}
impl From<RangeFull> for Times {
fn from(x: RangeFull) -> Self {
Times(TimesEnum::Unbounded(x))
}
}
// A quick macro to help easing the implementation pain.
macro_rules! impl_from_for_range {
($type_name:ident) => {
impl From<$type_name<u64>> for Times {
fn from(r: $type_name<u64>) -> Self {
Times(TimesEnum::$type_name(r))
}
}
};
}
impl_from_for_range!(Range);
impl_from_for_range!(RangeTo);
impl_from_for_range!(RangeFrom);
impl_from_for_range!(RangeInclusive);
impl_from_for_range!(RangeToInclusive);

View File

@@ -0,0 +1,329 @@
use crate::mock_server::hyper::run_server;
use crate::mock_set::MockId;
use crate::mock_set::MountedMockSet;
use crate::request::BodyPrintLimit;
use crate::{ErrorResponse, Request, mock::Mock, verification::VerificationOutcome};
use http_body_util::Full;
use hyper::body::Bytes;
use std::fmt::{Debug, Write};
use std::net::{SocketAddr, TcpListener, TcpStream};
use std::pin::pin;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use tokio::sync::Notify;
use tokio::sync::RwLock;
/// An HTTP web-server running in the background to behave as one of your dependencies using `Mock`s
/// for testing purposes.
///
/// `BareMockServer` is the actual mock server behind the publicly-exposed `MockServer`, which
/// is instead a thin facade over a `BareMockServer` retrieved from a pool - see `get_pooled_server`
/// for more details.
pub(crate) struct BareMockServer {
state: Arc<RwLock<MockServerState>>,
server_address: SocketAddr,
// When `_shutdown_trigger` gets dropped the listening server terminates gracefully.
_shutdown_trigger: tokio::sync::watch::Sender<()>,
}
/// The elements of [`BareMockServer`] that are affected by each incoming request.
/// By bundling them together, we can expose a unified `handle_request` that ensures
/// they are kept in sync without having to leak logic across multiple corners of the `wiremock`'s codebase.
pub(super) struct MockServerState {
mock_set: MountedMockSet,
received_requests: Option<Vec<Request>>,
body_print_limit: BodyPrintLimit,
}
impl MockServerState {
pub(super) async fn handle_request(
&mut self,
request: Request,
) -> Result<(hyper::Response<Full<Bytes>>, Option<tokio::time::Sleep>), ErrorResponse> {
// If request recording is enabled, record the incoming request
// by adding it to the `received_requests` stack
if let Some(received_requests) = &mut self.received_requests {
received_requests.push(request.clone());
}
self.mock_set.handle_request(request).await
}
}
impl BareMockServer {
/// Start a new instance of a `BareMockServer` listening on the specified
/// [`TcpListener`].
pub(super) async fn start(
listener: TcpListener,
request_recording: RequestRecording,
body_print_limit: BodyPrintLimit,
) -> Self {
let (shutdown_trigger, shutdown_receiver) = tokio::sync::watch::channel(());
let received_requests = match request_recording {
RequestRecording::Enabled => Some(Vec::new()),
RequestRecording::Disabled => None,
};
let state = Arc::new(RwLock::new(MockServerState {
mock_set: MountedMockSet::new(body_print_limit),
received_requests,
body_print_limit,
}));
let server_address = listener
.local_addr()
.expect("Failed to get server address.");
let server_state = state.clone();
std::thread::spawn(move || {
let server_future = run_server(listener, server_state, shutdown_receiver);
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Cannot build local tokio runtime");
runtime.block_on(server_future);
});
for _ in 0..40 {
if TcpStream::connect_timeout(&server_address, std::time::Duration::from_millis(25))
.is_ok()
{
break;
}
tokio::time::sleep(std::time::Duration::from_millis(25)).await;
}
Self {
state,
server_address,
_shutdown_trigger: shutdown_trigger,
}
}
/// Register a `Mock` on an instance of `BareMockServer`.
///
/// Be careful! `Mock`s are not effective until they are `mount`ed or `register`ed on a
/// `BareMockServer`.
pub(crate) async fn register(&self, mock: Mock) {
self.state.write().await.mock_set.register(mock);
}
/// Register a **scoped** `Mock` on an instance of `MockServer`.
///
/// When using `register`, your `Mock`s will be active until the `MockServer` is shut down.
/// When using `register_as_scoped`, your `Mock`s will be active as long as the returned `MockGuard` is not dropped.
/// When the returned `MockGuard` is dropped, `MockServer` will verify that the expectations set on the scoped `Mock` were
/// verified - if not, it will panic.
pub async fn register_as_scoped(&self, mock: Mock) -> MockGuard {
let (notify, mock_id) = self.state.write().await.mock_set.register(mock);
MockGuard {
notify,
mock_id,
server_state: self.state.clone(),
}
}
/// Drop all mounted `Mock`s from an instance of `BareMockServer`.
/// Delete all recorded requests.
///
/// It *must* be called if you plan to reuse a `BareMockServer` instance (i.e. in our
/// `MockServerPoolManager`).
pub(crate) async fn reset(&self) {
let mut state = self.state.write().await;
state.mock_set.reset();
if let Some(received_requests) = &mut state.received_requests {
received_requests.clear();
}
}
/// Verify that all mounted `Mock`s on this instance of `BareMockServer` have satisfied
/// their expectations on their number of invocations.
pub(crate) async fn verify(&self) -> VerificationOutcome {
let mock_set = &self.state.read().await.mock_set;
mock_set.verify_all()
}
/// Return the base uri of this running instance of `BareMockServer`, e.g. `http://127.0.0.1:4372`.
///
/// Use this method to compose uris when interacting with this instance of `BareMockServer` via
/// an HTTP client.
pub(crate) fn uri(&self) -> String {
format!("http://{}", self.server_address)
}
/// Return the socket address of this running instance of `BareMockServer`, e.g. `127.0.0.1:4372`.
///
/// Use this method to interact with the `BareMockServer` using `TcpStream`s.
pub(crate) fn address(&self) -> &SocketAddr {
&self.server_address
}
/// Return the body print limit of this running instance of `BareMockServer`.
pub(crate) async fn body_print_limit(&self) -> BodyPrintLimit {
self.state.read().await.body_print_limit
}
/// Return a vector with all the requests received by the `BareMockServer` since it started.
/// If no request has been served, it returns an empty vector.
///
/// If request recording was disabled, it returns `None`.
pub(crate) async fn received_requests(&self) -> Option<Vec<Request>> {
let state = self.state.read().await;
state.received_requests.clone()
}
}
impl Debug for BareMockServer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "BareMockServer {{ address: {} }}", self.address())
}
}
pub(super) enum RequestRecording {
Enabled,
Disabled,
}
/// You get a `MockGuard` when registering a **scoped** [`Mock`] using [`MockServer::register_as_scoped`](crate::MockServer::register_as_scoped)
/// or [`Mock::mount_as_scoped`].
///
/// When the [`MockGuard`] is dropped, the [`MockServer`](crate::MockServer) verifies that the expectations set on the
/// scoped [`Mock`] were verified - if not, it will panic.
///
/// # Limitations
///
/// When expectations of a scoped [`Mock`] are not verified, it will trigger a panic - just like a normal [`Mock`].
/// Due to [limitations](https://internals.rust-lang.org/t/should-drop-glue-use-track-caller/13682) in Rust's `Drop` trait,
/// the panic message will not include the filename and the line location
/// where the corresponding `MockGuard` was dropped - it will point into `wiremock`'s source code.
///
/// This can be an issue when you are using more than one scoped [`Mock`] in a single test - which of them panicked?
/// To improve your debugging experience it is strongly recommended to use [`Mock::named`] to assign a unique
/// identifier to your scoped [`Mock`]s, which will in turn be referenced in the panic message if their expectations are
/// not met.
#[must_use = "All *_scoped methods return a `MockGuard`.
This guard MUST be bound to a variable (e.g. _mock_guard), \
otherwise the mock will immediately be unmounted (and its expectations checked).
Check `wiremock`'s documentation on scoped mocks for more details."]
pub struct MockGuard {
mock_id: MockId,
server_state: Arc<RwLock<MockServerState>>,
notify: Arc<(Notify, AtomicBool)>,
}
impl MockGuard {
/// Return all the requests that have been matched by the corresponding
/// scoped [`Mock`] since it was mounted.
/// The requests are returned in the order they were received.
///
/// It returns an empty vector if no request has been matched.
pub async fn received_requests(&self) -> Vec<crate::Request> {
let state = self.server_state.read().await;
let (mounted_mock, _) = &state.mock_set[self.mock_id];
mounted_mock.received_requests()
}
/// This method doesn't return until the expectations set on the
/// corresponding scoped [`Mock`] are satisfied.
///
/// It can be useful when you are testing asynchronous flows (e.g. a
/// message queue consumer) and you don't have a good event that can be used
/// to trigger the verification of the expectations set on the scoped [`Mock`].
///
/// # Timeouts
///
/// There is no default timeout for this method, so it will end up waiting
/// **forever** if your expectations are never met. Probably not what you
/// want.
///
/// It is strongly recommended that you set your own timeout using the
/// appropriate timers from your chosen async runtime.
/// Since `wiremock` is runtime-agnostic, it cannot provide a default
/// timeout mechanism that would work for all users.
///
/// ```rust
/// use std::time::Duration;
/// use wiremock::{Mock, MockServer, ResponseTemplate};
/// use wiremock::matchers::method;
///
/// #[tokio::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
///
/// let response = ResponseTemplate::new(200);
/// let mock = Mock::given(method("GET")).respond_with(response);
/// let mock_guard = mock_server.register_as_scoped(mock).await;
///
/// // Act
/// let waiter = mock_guard.wait_until_satisfied();
/// // Here we wrap the waiter in a tokio timeout
/// let outcome = tokio::time::timeout(Duration::from_millis(10), waiter).await;
///
/// // Assert
/// assert!(outcome.is_err());
/// }
/// ```
pub async fn wait_until_satisfied(&self) {
let (notify, flag) = &*self.notify;
let mut notification = pin!(notify.notified());
// listen for events of satisfaction.
notification.as_mut().enable();
// check if satisfaction has previously been recorded
if flag.load(std::sync::atomic::Ordering::Acquire) {
return;
}
// await event
notification.await;
}
}
impl Drop for MockGuard {
fn drop(&mut self) {
let future = async move {
let MockGuard {
mock_id,
server_state,
..
} = self;
let mut state = server_state.write().await;
let report = state.mock_set.verify(*mock_id);
if !report.is_satisfied() {
let received_requests_message = if let Some(received_requests) =
&state.received_requests
{
if received_requests.is_empty() {
"The server did not receive any request.".into()
} else {
received_requests.iter().enumerate().fold(
"Received requests:\n".to_string(),
|mut message, (index, request)| {
_ = write!(message, "- Request #{}\n\t", index + 1,);
_ = request.print_with_limit(&mut message, state.body_print_limit);
message
},
)
}
} else {
"Enable request recording on the mock server to get the list of incoming requests as part of the panic message.".into()
};
let verifications_error = format!("- {}\n", report.error_message());
let error_message = format!(
"Verification failed for a scoped mock:\n{}\n{}",
verifications_error, received_requests_message
);
if std::thread::panicking() {
log::debug!("{}", &error_message);
} else {
panic!("{}", &error_message);
}
} else {
state.mock_set.deactivate(*mock_id);
}
};
futures::executor::block_on(future);
}
}

View File

@@ -0,0 +1,121 @@
use crate::MockServer;
use crate::mock_server::bare_server::{BareMockServer, RequestRecording};
use crate::mock_server::exposed_server::InnerServer;
use crate::request::{BODY_PRINT_LIMIT, BodyPrintLimit};
use std::env;
use std::net::TcpListener;
/// A builder providing a fluent API to assemble a [`MockServer`] step-by-step.
/// Use [`MockServer::builder`] to get started.
pub struct MockServerBuilder {
listener: Option<TcpListener>,
record_incoming_requests: bool,
body_print_limit: BodyPrintLimit,
}
impl MockServerBuilder {
pub(super) fn new() -> Self {
let body_print_limit = match env::var("WIREMOCK_BODY_PRINT_LIMIT")
.ok()
.and_then(|x| x.parse::<usize>().ok())
{
Some(limit) => BodyPrintLimit::Limited(limit),
None => BodyPrintLimit::Limited(BODY_PRINT_LIMIT),
};
Self {
listener: None,
record_incoming_requests: true,
body_print_limit,
}
}
/// Each instance of [`MockServer`] is, by default, running on a random
/// port available on your local machine.
/// With `MockServerBuilder::listener` you can choose to start the `MockServer`
/// instance on a specific port you have already bound.
///
/// ### Example:
/// ```rust
/// use wiremock::MockServer;
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
/// let expected_server_address = listener
/// .local_addr()
/// .expect("Failed to get server address.");
///
/// // Act
/// let mock_server = MockServer::builder().listener(listener).start().await;
///
/// // Assert
/// assert_eq!(&expected_server_address, mock_server.address());
/// }
/// ```
pub fn listener(mut self, listener: TcpListener) -> Self {
self.listener = Some(listener);
self
}
/// By default, [`MockServer`] will record all incoming requests to display
/// more meaningful error messages when your expectations are not verified.
///
/// This can sometimes be undesirable (e.g. a long-lived server serving
/// high volumes of traffic) - you can disable request recording using
/// `MockServerBuilder::disable_request_recording`.
///
/// ### Example (Request recording disabled):
///
/// ```rust
/// use wiremock::MockServer;
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::builder().disable_request_recording().start().await;
///
/// // Act
/// let received_requests = mock_server.received_requests().await;
///
/// // Assert
/// assert!(received_requests.is_none());
/// }
/// ```
pub fn disable_request_recording(mut self) -> Self {
self.record_incoming_requests = false;
self
}
/// The mock server prints the requests it received when one or more mocks have expectations that have not been satisfied.
/// By default, the size of the printed body is limited.
///
/// You may want to change this if you're working with services with very large
/// bodies, or when printing wiremock output to a file where size matters
/// less than in a terminal window. You can configure this limit with
/// `MockServerBuilder::body_print_limit`.
pub fn body_print_limit(mut self, limit: BodyPrintLimit) -> Self {
self.body_print_limit = limit;
self
}
/// Finalise the builder to get an instance of a [`BareMockServer`].
pub(super) async fn build_bare(self) -> BareMockServer {
let listener = if let Some(listener) = self.listener {
listener
} else {
TcpListener::bind("127.0.0.1:0").expect("Failed to bind an OS port for a mock server.")
};
let recording = if self.record_incoming_requests {
RequestRecording::Enabled
} else {
RequestRecording::Disabled
};
BareMockServer::start(listener, recording, self.body_print_limit).await
}
/// Finalise the builder and launch the [`MockServer`] instance!
pub async fn start(self) -> MockServer {
MockServer::new(InnerServer::Bare(self.build_bare().await))
}
}

View File

@@ -0,0 +1,496 @@
use crate::mock_server::MockServerBuilder;
use crate::mock_server::bare_server::BareMockServer;
use crate::mock_server::pool::{PooledMockServer, get_pooled_mock_server};
use crate::{MockGuard, Request, mock::Mock, verification::VerificationOutcome};
use log::debug;
use std::fmt::{Debug, Write};
use std::net::SocketAddr;
use std::ops::Deref;
/// An HTTP web-server running in the background to behave as one of your dependencies using [`Mock`]s
/// for testing purposes.
///
/// Each instance of `MockServer` is fully isolated: [`MockServer::start`] takes care of finding a random port
/// available on your local machine which is assigned to the new `MockServer`.
///
/// You can use [`MockServer::builder`] if you need to specify custom configuration - e.g.
/// run on a specific port or disable request recording.
///
/// ## Best practices
///
/// You should use one instance of `MockServer` for each REST API that your application interacts
/// with and needs mocking for testing purposes.
///
/// To ensure full isolation and no cross-test interference, `MockServer`s shouldn't be
/// shared between tests. Instead, `MockServer`s should be created in the test where they are used.
///
/// When using a [`Mock`] within a test helper function, consider using [`MockServer::register_as_scoped`]
/// instead of [`MockServer::register`].
///
/// You can register as many [`Mock`]s as your scenario requires on a `MockServer`.
#[derive(Debug)]
pub struct MockServer(InnerServer);
/// `MockServer` is either a wrapper around a `BareMockServer` retrieved from an
/// object pool or a wrapper around an exclusive `BareMockServer`.
/// We use the pool when the user does not care about the port the mock server listens to, while
/// we provision a dedicated one if they specify their own `TcpListener` with `start_on`.
///
/// `InnerServer` implements `Deref<Target=BareMockServer>`, so we never actually have to match
/// on `InnerServer` in `MockServer` - the compiler does all the boring heavy-lifting for us.
#[derive(Debug)]
pub(super) enum InnerServer {
Bare(BareMockServer),
Pooled(PooledMockServer),
}
impl Deref for InnerServer {
type Target = BareMockServer;
fn deref(&self) -> &Self::Target {
match self {
InnerServer::Bare(b) => b,
InnerServer::Pooled(p) => p,
}
}
}
impl MockServer {
pub(super) fn new(server: InnerServer) -> Self {
Self(server)
}
/// You can use `MockServer::builder` if you need to specify custom configuration - e.g.
/// run on a specific port or disable request recording.
///
/// If this is not your case, use [`MockServer::start`].
pub fn builder() -> MockServerBuilder {
MockServerBuilder::new()
}
/// Start a new instance of a `MockServer` listening on a random port.
///
/// Each instance of `MockServer` is fully isolated: `start` takes care of finding a random port
/// available on your local machine which is assigned to the new `MockServer`.
///
/// You should use one instance of `MockServer` for each REST API that your application interacts
/// with and needs mocking for testing purposes.
///
/// ### Example:
/// ```rust
/// use wiremock::{MockServer, Mock, ResponseTemplate};
/// use wiremock::matchers::method;
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server_one = MockServer::start().await;
/// let mock_server_two = MockServer::start().await;
///
/// assert!(mock_server_one.address() != mock_server_two.address());
///
/// let mock = Mock::given(method("GET")).respond_with(ResponseTemplate::new(200));
/// // Registering the mock with the first mock server - it's now effective!
/// // But it *won't* be used by the second mock server!
/// mock_server_one.register(mock).await;
///
/// // Act
///
/// let status = reqwest::get(&mock_server_one.uri())
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 200);
///
/// // This would have matched our mock, but we haven't registered it for `mock_server_two`!
/// // Hence it returns a 404, the default response when no mocks matched on the mock server.
/// let status = reqwest::get(&mock_server_two.uri())
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 404);
/// }
/// ```
pub async fn start() -> Self {
Self(InnerServer::Pooled(get_pooled_mock_server().await))
}
/// Register a [`Mock`] on an instance of `MockServer`.
/// The [`Mock`] will remain active until `MockServer` is shut down. If you want to control or limit how
/// long your [`Mock`] stays active, check out [`MockServer::register_as_scoped`].
///
/// Be careful! [`Mock`]s are not effective until they are [`mount`]ed or `register`ed on a `MockServer`.
/// `register` is an asynchronous method, make sure to `.await` it!
///
/// ### Example:
/// ```rust
/// use wiremock::{MockServer, Mock, ResponseTemplate};
/// use wiremock::matchers::method;
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
///
/// let response = ResponseTemplate::new(200);
///
/// let mock = Mock::given(method("GET")).respond_with(response.clone());
/// // Registering the mock with the mock server - it's now effective!
/// mock_server.register(mock).await;
///
/// // We won't register this mock instead.
/// let unregistered_mock = Mock::given(method("GET")).respond_with(response);
///
/// // Act
/// let status = reqwest::get(&mock_server.uri())
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 200);
///
/// // This would have matched `unregistered_mock`, but we haven't registered it!
/// // Hence it returns a 404, the default response when no mocks matched on the mock server.
/// let client = reqwest::Client::new();
/// let status = client.post(&mock_server.uri())
/// .send()
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 404);
/// }
/// ```
///
/// [`mount`]: Mock::mount
pub async fn register(&self, mock: Mock) {
self.0.register(mock).await;
}
/// Register a **scoped** [`Mock`] on an instance of `MockServer`.
///
/// When using `register`, your [`Mock`]s will be active until the `MockServer` is shut down.
/// When using `register_as_scoped`, your [`Mock`]s will be active as long as the returned [`MockGuard`] is not dropped.
/// When the returned [`MockGuard`] is dropped, `MockServer` will verify that the expectations set on the scoped [`Mock`] were
/// verified - if not, it will panic.
///
/// `register_as_scoped` is the ideal solution when you need a [`Mock`] within a test helper
/// but you do not want it to linger around after the end of the function execution.
///
/// # Limitations
///
/// When expectations of a scoped [`Mock`] are not verified, it will trigger a panic - just like a normal [`Mock`].
/// Due to [limitations](https://internals.rust-lang.org/t/should-drop-glue-use-track-caller/13682) in Rust's `Drop` trait,
/// the panic message will not include the filename and the line location
/// where the corresponding [`MockGuard`] was dropped - it will point into `wiremock`'s source code.
///
/// This can be an issue when you are using more than one scoped [`Mock`] in a single test - which of them panicked?
/// To improve your debugging experience it is strongly recommended to use [`Mock::named`] to assign a unique
/// identifier to your scoped [`Mock`]s, which will in turn be referenced in the panic message if their expectations are
/// not met.
///
/// # Example:
///
/// - The behaviour of the scoped mock is invisible outside of `my_test_helper`.
///
/// ```rust
/// use wiremock::{MockServer, Mock, ResponseTemplate};
/// use wiremock::matchers::method;
///
/// async fn my_test_helper(mock_server: &MockServer) {
/// let mock = Mock::given(method("GET"))
/// .respond_with(ResponseTemplate::new(200))
/// .expect(1)
/// .named("my_test_helper GET /");
/// let mock_guard = mock_server.register_as_scoped(mock).await;
///
/// reqwest::get(&mock_server.uri())
/// .await
/// .unwrap();
///
/// // `mock_guard` is dropped, expectations are verified!
/// }
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
/// my_test_helper(&mock_server).await;
///
/// // Act
///
/// // This would have returned 200 if the `Mock` in
/// // `my_test_helper` had not been scoped.
/// let status = reqwest::get(&mock_server.uri())
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 404);
/// }
/// ```
///
/// - The expectations for the scoped mock are not verified, it panics at the end of `my_test_helper`.
///
/// ```rust,should_panic
/// use wiremock::{MockServer, Mock, ResponseTemplate};
/// use wiremock::matchers::method;
///
/// async fn my_test_helper(mock_server: &MockServer) {
/// let mock = Mock::given(method("GET"))
/// .respond_with(ResponseTemplate::new(200))
/// .expect(1)
/// .named("my_test_helper GET /");
/// let mock_guard = mock_server.register_as_scoped(mock).await;
/// // `mock_guard` is dropped, expectations are NOT verified!
/// // Panic!
/// }
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
/// my_test_helper(&mock_server).await;
///
/// // Act
/// let status = reqwest::get(&mock_server.uri())
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 404);
/// }
/// ```
pub async fn register_as_scoped(&self, mock: Mock) -> MockGuard {
self.0.register_as_scoped(mock).await
}
/// Drop all mounted [`Mock`]s from an instance of [`MockServer`].
/// It also deletes all recorded requests.
///
/// ### Example
///
/// ```rust
/// use wiremock::{MockServer, Mock, ResponseTemplate};
/// use wiremock::matchers::method;
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
///
/// let response = ResponseTemplate::new(200);
/// Mock::given(method("GET")).respond_with(response).mount(&mock_server).await;
///
/// // Act
/// let status = reqwest::get(&mock_server.uri())
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 200);
///
/// // Reset the server
/// mock_server.reset().await;
///
/// // This would have matched our mock, but we have dropped it resetting the server!
/// let client = reqwest::Client::new();
/// let status = client.post(&mock_server.uri())
/// .send()
/// .await
/// .unwrap()
/// .status();
/// assert_eq!(status, 404);
/// }
/// ```
///
/// ### Example (Recorded requests are reset)
///
/// ```rust
/// use wiremock::{MockServer, Mock, ResponseTemplate};
/// use wiremock::matchers::method;
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
///
/// // Act
/// reqwest::get(&mock_server.uri()).await.unwrap();
///
/// // We have recorded the incoming request
/// let received_requests = mock_server.received_requests().await.unwrap();
/// assert!(!received_requests.is_empty());
///
/// // Reset the server
/// mock_server.reset().await;
///
/// // All received requests have been forgotten after the call to `.reset`
/// let received_requests = mock_server.received_requests().await.unwrap();
/// assert!(received_requests.is_empty())
/// }
/// ```
pub async fn reset(&self) {
self.0.reset().await;
}
/// Verify that all mounted [`Mock`]s on this instance of `MockServer` have satisfied
/// their expectations on their number of invocations. Panics otherwise.
pub async fn verify(&self) {
debug!("Verify mock expectations.");
let body_print_limit = self.0.body_print_limit().await;
if let VerificationOutcome::Failure(failed_verifications) = self.0.verify().await {
let received_requests_message = if let Some(received_requests) =
self.0.received_requests().await
{
if received_requests.is_empty() {
"The server did not receive any request.".into()
} else {
received_requests.iter().enumerate().fold(
"Received requests:\n".to_string(),
|mut message, (index, request)| {
_ = write!(message, "- Request #{}\n\t", index + 1,);
_ = request.print_with_limit(&mut message, body_print_limit);
message
},
)
}
} else {
"Enable request recording on the mock server to get the list of incoming requests as part of the panic message.".into()
};
let verifications_errors: String =
failed_verifications.iter().fold(String::new(), |mut s, m| {
_ = writeln!(s, "- {}", m.error_message());
s
});
let error_message = format!(
"Verifications failed:\n{verifications_errors}\n{received_requests_message}",
);
if std::thread::panicking() {
debug!("{}", &error_message);
} else {
panic!("{}", &error_message);
}
}
}
/// Return the base uri of this running instance of `MockServer`, e.g. `http://127.0.0.1:4372`.
///
/// Use this method to compose uris when interacting with this instance of `MockServer` via
/// an HTTP client.
///
/// ### Example:
/// ```rust
/// use wiremock::MockServer;
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange - no mocks mounted
///
/// let mock_server = MockServer::start().await;
/// // Act
/// let uri = format!("{}/health_check", &mock_server.uri());
/// let status = reqwest::get(uri).await.unwrap().status();
///
/// // Assert - default response
/// assert_eq!(status, 404);
/// }
/// ```
pub fn uri(&self) -> String {
self.0.uri()
}
/// Return the socket address of this running instance of `MockServer`, e.g. `127.0.0.1:4372`.
///
/// Use this method to interact with the `MockServer` using [`TcpStream`]s.
///
/// ### Example:
/// ```rust
/// use wiremock::MockServer;
/// use std::net::TcpStream;
///
/// #[async_std::main]
/// async fn main() {
/// // Act - the server is started
/// let mock_server = MockServer::start().await;
///
/// // Assert - we can connect to it
/// assert!(TcpStream::connect(mock_server.address()).is_ok());
/// }
/// ```
///
/// [`TcpStream`]: std::net::TcpStream
pub fn address(&self) -> &SocketAddr {
self.0.address()
}
/// Return a vector with all the requests received by the `MockServer` since it started.
/// If no request has been served, it returns an empty vector.
///
/// If request recording has been disabled using [`MockServerBuilder::disable_request_recording`],
/// it returns `None`.
///
/// ### Example:
///
/// ```rust
/// use wiremock::MockServer;
/// use http::Method;
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
///
/// // Act
/// reqwest::get(&mock_server.uri()).await.unwrap();
///
/// // Assert
/// let received_requests = mock_server.received_requests().await.unwrap();
/// assert_eq!(received_requests.len(), 1);
///
/// let received_request = &received_requests[0];
/// assert_eq!(received_request.method, Method::GET);
/// assert_eq!(received_request.url.path(), "/");
/// assert!(received_request.body.is_empty());
/// }
/// ```
///
/// ### Example (No request served):
///
/// ```rust
/// use wiremock::MockServer;
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
///
/// // Assert
/// let received_requests = mock_server.received_requests().await.unwrap();
/// assert_eq!(received_requests.len(), 0);
/// }
/// ```
///
/// ### Example (Request recording disabled):
///
/// ```rust
/// use wiremock::MockServer;
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::builder().disable_request_recording().start().await;
///
/// // Assert
/// let received_requests = mock_server.received_requests().await;
/// assert!(received_requests.is_none());
/// }
/// ```
pub async fn received_requests(&self) -> Option<Vec<Request>> {
self.0.received_requests().await
}
}
impl Drop for MockServer {
// Clean up when the `MockServer` instance goes out of scope.
fn drop(&mut self) {
futures::executor::block_on(self.verify());
// The sender half of the channel, `shutdown_trigger`, gets dropped here
// Triggering the graceful shutdown of the server itself.
}
}

View File

@@ -0,0 +1,88 @@
use crate::mock_server::bare_server::MockServerState;
use hyper::service::service_fn;
use hyper_util::rt::TokioIo;
use std::sync::Arc;
use tokio::net::TcpListener;
use tokio::sync::RwLock;
/// Work around a lifetime error where, for some reason,
/// `Box<dyn std::error::Error + Send + Sync + 'static>` can't be converted to a
/// `Box<dyn std::error::Error + Send + Sync>`
struct ErrorLifetimeCast(Box<dyn std::error::Error + Send + Sync + 'static>);
impl From<ErrorLifetimeCast> for Box<dyn std::error::Error + Send + Sync> {
fn from(value: ErrorLifetimeCast) -> Self {
value.0
}
}
/// The actual HTTP server responding to incoming requests according to the specified mocks.
pub(super) async fn run_server(
listener: std::net::TcpListener,
server_state: Arc<RwLock<MockServerState>>,
mut shutdown_signal: tokio::sync::watch::Receiver<()>,
) {
listener
.set_nonblocking(true)
.expect("Cannot set non-blocking mode on TcpListener");
let listener = TcpListener::from_std(listener).expect("Cannot upgrade TcpListener");
let request_handler = move |request| {
let server_state = server_state.clone();
async move {
let wiremock_request = crate::Request::from_hyper(request).await;
let (response, delay) = server_state
.write()
.await
.handle_request(wiremock_request)
.await
.map_err(ErrorLifetimeCast)?;
// We do not wait for the delay within the handler otherwise we would be
// holding on to the write-side of the `RwLock` on `mock_set`.
// Holding on the lock while waiting prevents us from handling other requests until
// we have waited the whole duration specified in the delay.
// In particular, we cannot perform even perform read-only operation -
// e.g. check that mock assumptions have been verified.
// Using long delays in tests without handling the delay as we are doing here
// caused tests to hang (see https://github.com/seanmonstar/reqwest/issues/1147)
if let Some(delay) = delay {
delay.await;
}
Ok::<_, ErrorLifetimeCast>(response)
}
};
loop {
let (stream, _) = tokio::select! { biased;
accepted = listener.accept() => {
match accepted {
Ok(accepted) => accepted,
Err(_) => break,
}
},
_ = shutdown_signal.changed() => {
log::info!("Mock server shutting down");
break;
}
};
let io = TokioIo::new(stream);
let request_handler = request_handler.clone();
let mut shutdown_signal = shutdown_signal.clone();
tokio::task::spawn(async move {
let http_server =
hyper_util::server::conn::auto::Builder::new(hyper_util::rt::TokioExecutor::new());
let conn = http_server.serve_connection_with_upgrades(io, service_fn(request_handler));
tokio::pin!(conn);
loop {
tokio::select! {
_ = conn.as_mut() => break,
_ = shutdown_signal.changed() => conn.as_mut().graceful_shutdown(),
}
}
});
}
}

21
vendor/wiremock/src/mock_server/mod.rs vendored Normal file
View File

@@ -0,0 +1,21 @@
//! All bits and pieces concerning the HTTP mock server are in this module.
//!
//! `bare_server::BareMockServer` is the "front-end" to drive behaviour for the `hyper` HTTP
//! server running in the background, defined in the `hyper` sub-module.
//!
//! `bare_server::BareMockServer` is not exposed directly: crate users only get to interact with
//! `exposed_server::MockServer`.
//! `exposed_server::MockServer` is either a wrapper around a `BareMockServer` retrieved from an
//! object pool or a wrapper around an exclusive `BareMockServer`.
//! We use the pool when the user does not care about the port the mock server listens to, while
//! we provision a dedicated one if they specify their own `TcpListener` with `start_on`.
//! Check the `pool` submodule for more details on our pooling strategy.
mod bare_server;
mod builder;
mod exposed_server;
mod hyper;
mod pool;
pub use bare_server::MockGuard;
pub use builder::MockServerBuilder;
pub use exposed_server::MockServer;

73
vendor/wiremock/src/mock_server/pool.rs vendored Normal file
View File

@@ -0,0 +1,73 @@
use crate::MockServer;
use crate::mock_server::bare_server::BareMockServer;
use deadpool::managed::{Metrics, Object, Pool};
use once_cell::sync::Lazy;
use std::convert::Infallible;
/// A pool of `BareMockServer`s.
///
/// ## Design constraints
///
/// `wiremock`'s pooling is designed to be an invisible optimisation: users of the crate, if
/// we are successful, should never have to reason about it.
///
/// ## Motivation
///
/// Why are we pooling `BareMockServer`s?
/// Mostly to reduce the number of `TcpListener`s that are being opened and closed, therefore
/// mitigating risk of our users having to fight/raise OS limits for the maximum number of open
/// connections (e.g. ulimit on Linux).
///
/// It is also marginally faster to get a pooled `BareMockServer` than to create a new one, but
/// the absolute time is so small (<1 ms) that it does not make a material difference in a real
/// world test suite.
static MOCK_SERVER_POOL: Lazy<Pool<MockServerPoolManager>> = Lazy::new(|| {
// We are choosing an arbitrarily high max_size because we never want a test to "wait" for
// a `BareMockServer` instance to become available.
//
// We might expose in the future a way for a crate user to tune this value.
Pool::builder(MockServerPoolManager)
.max_size(1000)
.build()
.expect("Building a server pool is not expected to fail. Please report an issue")
});
pub(crate) type PooledMockServer = Object<MockServerPoolManager>;
/// Retrieve a `BareMockServer` from the pool.
/// The operation should never fail.
pub(crate) async fn get_pooled_mock_server() -> PooledMockServer {
MOCK_SERVER_POOL
.get()
.await
.expect("Failed to get a MockServer from the pool")
}
/// The `BareMockServer` pool manager.
///
/// It:
/// - creates a new `BareMockServer` if there is none to borrow from the pool;
/// - "cleans up" used `BareMockServer`s before making them available again for other tests to use.
#[derive(Debug)]
pub(crate) struct MockServerPoolManager;
impl deadpool::managed::Manager for MockServerPoolManager {
type Error = Infallible;
type Type = BareMockServer;
async fn create(&self) -> Result<BareMockServer, Infallible> {
// All servers in the pool use the default configuration
Ok(MockServer::builder().build_bare().await)
}
async fn recycle(
&self,
mock_server: &mut BareMockServer,
_metrics: &Metrics,
) -> deadpool::managed::RecycleResult<Infallible> {
// Remove all existing settings - we want to start clean when the mock server
// is picked up again from the pool.
mock_server.reset().await;
Ok(())
}
}

244
vendor/wiremock/src/mock_set.rs vendored Normal file
View File

@@ -0,0 +1,244 @@
use crate::request::BodyPrintLimit;
use crate::{
ErrorResponse,
mounted_mock::MountedMock,
verification::{VerificationOutcome, VerificationReport},
};
use crate::{Mock, Request};
use http_body_util::Full;
use hyper::body::Bytes;
use log::debug;
use std::{
ops::{Index, IndexMut},
sync::{Arc, atomic::AtomicBool},
};
use tokio::sync::Notify;
use tokio::time::{Sleep, sleep};
/// The collection of mocks used by a `MockServer` instance to match against
/// incoming requests.
///
/// New mocks are added to `MountedMockSet` every time [`MockServer::register`](crate::MockServer::register),
/// [`MockServer::register_as_scoped`](crate::MockServer::register_as_scoped) or
/// [`Mock::mount`](crate::Mock::mount) are called.
pub(crate) struct MountedMockSet {
mocks: Vec<(MountedMock, MountedMockState)>,
/// A counter that keeps track of how many times [`MountedMockSet::reset`] has been called.
/// It starts at `0` and gets incremented for each invocation.
///
/// We need `generation` to know if a [`MockId`] points to an [`MountedMock`] that has been
/// removed via [`MountedMockSet::reset`].
generation: u16,
body_print_limit: BodyPrintLimit,
}
/// A `MockId` is an opaque index that uniquely identifies an [`MountedMock`] inside an [`MountedMockSet`].
///
/// The only way to create a `MockId` is calling [`MountedMockSet::register`].
#[derive(Copy, Clone)]
pub(crate) struct MockId {
index: usize,
/// The generation of [`MountedMockSet`] when [`MountedMockSet::register`] was called.
/// It allows [`MountedMockSet`] to check that the [`MountedMock`] our [`MockId`] points to is still in
/// the set (i.e. the set has not been wiped by a [`MountedMockSet::reset`] call).
generation: u16,
}
impl MountedMockSet {
/// Create a new instance of `MountedMockSet`.
pub(crate) fn new(body_print_limit: BodyPrintLimit) -> MountedMockSet {
MountedMockSet {
mocks: vec![],
generation: 0,
body_print_limit,
}
}
pub(crate) async fn handle_request(
&mut self,
request: Request,
) -> Result<(hyper::Response<Full<Bytes>>, Option<Sleep>), ErrorResponse> {
debug!("Handling request.");
let mut response_template: Option<_> = None;
self.mocks.sort_by_key(|(m, _)| m.specification.priority);
for (mock, mock_state) in &mut self.mocks {
if *mock_state == MountedMockState::OutOfScope {
continue;
}
if mock.matches(&request) {
response_template = Some(mock.response_template(&request));
break;
}
}
if let Some(response_template) = response_template {
match response_template {
Ok(response_template) => {
let delay = response_template.delay().map(sleep);
Ok((response_template.generate_response(), delay))
}
Err(err) => Err(err),
}
} else {
let mut msg = "Got unexpected request:\n".to_string();
_ = request.print_with_limit(&mut msg, self.body_print_limit);
debug!("{}", msg);
let not_found_response = hyper::Response::builder()
.status(hyper::StatusCode::NOT_FOUND)
.body(Full::default())
.unwrap();
Ok((not_found_response, None))
}
}
pub(crate) fn register(&mut self, mock: Mock) -> (Arc<(Notify, AtomicBool)>, MockId) {
let n_registered_mocks = self.mocks.len();
let active_mock = MountedMock::new(mock, n_registered_mocks);
let notify = active_mock.notify();
self.mocks.push((active_mock, MountedMockState::InScope));
(
notify,
MockId {
index: self.mocks.len() - 1,
generation: self.generation,
},
)
}
pub(crate) fn reset(&mut self) {
self.mocks = vec![];
self.generation += 1;
}
/// Mark one of the mocks in the set as out of scope.
///
/// It will stop matching against incoming requests, regardless of its specification.
pub(crate) fn deactivate(&mut self, mock_id: MockId) {
let mock = &mut self[mock_id];
mock.1 = MountedMockState::OutOfScope;
}
/// Verify that expectations have been met for **all** [`MountedMock`]s in the set.
pub(crate) fn verify_all(&self) -> VerificationOutcome {
let failed_verifications: Vec<VerificationReport> = self
.mocks
.iter()
.filter(|(_, state)| *state == MountedMockState::InScope)
.map(|(m, _)| m.verify())
.filter(|verification_report| !verification_report.is_satisfied())
.collect();
if failed_verifications.is_empty() {
VerificationOutcome::Success
} else {
VerificationOutcome::Failure(failed_verifications)
}
}
/// Verify that expectations have been met for the [`MountedMock`] corresponding to the specified [`MockId`].
pub(crate) fn verify(&self, mock_id: MockId) -> VerificationReport {
let (mock, _) = &self[mock_id];
mock.verify()
}
}
impl IndexMut<MockId> for MountedMockSet {
fn index_mut(&mut self, index: MockId) -> &mut Self::Output {
if index.generation != self.generation {
panic!(
"The mock you are trying to access is no longer active. It has been deleted from the active set via `reset` - you should not hold on to a `MockId` after you call `reset`!."
)
}
&mut self.mocks[index.index]
}
}
impl Index<MockId> for MountedMockSet {
type Output = (MountedMock, MountedMockState);
fn index(&self, index: MockId) -> &Self::Output {
if index.generation != self.generation {
panic!(
"The mock you are trying to access is no longer active. It has been deleted from the active set via `reset` - you should not hold on to a `MockId` after you call `reset`!."
)
}
&self.mocks[index.index]
}
}
/// A [`MountedMock`] can either be global (i.e. registered using [`crate::MockServer::register`]) or
/// scoped (i.e. registered using [`crate::MockServer::register_as_scoped`]).
///
/// [`MountedMock`]s must currently be in scope to be matched against incoming requests.
/// Out of scope [`MountedMock`]s are skipped when trying to match an incoming request.
///
/// # Implementation Rationale
///
/// An alternative approach would be removing a [`MountedMock`] from the [`MountedMockSet`] when it goes
/// out of scope.
/// This would create an issue for the stability of [`MockId`]s: removing an element from the vector
/// of [`MountedMock`]s in [`MountedMockSet`] would invalidate the ids of all mocks registered after
/// the removed one.
///
/// Attaching a state to the mocks in the vector, instead, allows us to ensure id stability while
/// achieving the desired behaviour.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub(crate) enum MountedMockState {
InScope,
OutOfScope,
}
#[cfg(test)]
mod tests {
use crate::matchers::path;
use crate::mock_set::{MountedMockSet, MountedMockState};
use crate::request::BodyPrintLimit;
use crate::{Mock, ResponseTemplate};
fn test_mock_set() -> MountedMockSet {
MountedMockSet::new(BodyPrintLimit::Unlimited)
}
#[test]
fn generation_is_incremented_for_every_reset() {
let mut set = test_mock_set();
assert_eq!(set.generation, 0);
for i in 1..10 {
set.reset();
assert_eq!(set.generation, i);
}
}
#[test]
#[should_panic]
fn accessing_a_mock_id_after_a_reset_triggers_a_panic() {
// Assert
let mut set = test_mock_set();
let mock = Mock::given(path("/")).respond_with(ResponseTemplate::new(200));
let (_, mock_id) = set.register(mock);
// Act
set.reset();
// Assert
let _ = &set[mock_id];
}
#[test]
fn deactivating_a_mock_does_not_invalidate_other_ids() {
// Assert
let mut set = test_mock_set();
let first_mock = Mock::given(path("/")).respond_with(ResponseTemplate::new(200));
let second_mock = Mock::given(path("/hello")).respond_with(ResponseTemplate::new(500));
let (_, first_mock_id) = set.register(first_mock);
let (_, second_mock_id) = set.register(second_mock);
// Act
set.deactivate(first_mock_id);
// Assert
let first_mock = &set[first_mock_id];
assert_eq!(first_mock.1, MountedMockState::OutOfScope);
let second_mock = &set[second_mock_id];
assert_eq!(second_mock.1, MountedMockState::InScope);
}
}

99
vendor/wiremock/src/mounted_mock.rs vendored Normal file
View File

@@ -0,0 +1,99 @@
use std::sync::{Arc, atomic::AtomicBool};
use tokio::sync::Notify;
use crate::{
ErrorResponse, Match, Mock, Request, ResponseTemplate, verification::VerificationReport,
};
/// Given the behaviour specification as a [`Mock`], keep track of runtime information
/// concerning this mock - e.g. how many times it matched on a incoming request.
pub(crate) struct MountedMock {
pub(crate) specification: Mock,
n_matched_requests: u64,
/// The position occupied by this mock within the parent [`MountedMockSet`](crate::mock_set::MountedMockSet)
/// collection of `MountedMock`s.
///
/// E.g. `0` if this is the first mock that we try to match against an incoming request, `1`
/// if it is the second, etc.
position_in_set: usize,
// matched requests:
matched_requests: Vec<crate::Request>,
notify: Arc<(Notify, AtomicBool)>,
}
impl MountedMock {
pub(crate) fn new(specification: Mock, position_in_set: usize) -> Self {
Self {
specification,
n_matched_requests: 0,
position_in_set,
matched_requests: Vec::new(),
notify: Arc::new((Notify::new(), AtomicBool::new(false))),
}
}
/// This is NOT the same of `matches` from the `Match` trait!
/// Key difference: we are talking a mutable reference to `self` in order to capture
/// additional information (e.g. how many requests we matched so far) or change behaviour
/// after a certain threshold has been crossed (e.g. start returning `false` for all requests
/// once enough requests have been matched according to `max_n_matches`).
pub(crate) fn matches(&mut self, request: &Request) -> bool {
if Some(self.n_matched_requests) == self.specification.max_n_matches {
// Skip the actual check if we are already at our maximum of matched requests.
false
} else {
let matched = self
.specification
.matchers
.iter()
.all(|matcher| matcher.matches(request));
if matched {
// Increase match count
self.n_matched_requests += 1;
// Keep track of request
self.matched_requests.push(request.clone());
// notification of satisfaction
if self.verify().is_satisfied() {
// always set the satisfaction flag **before** raising the event
self.notify
.1
.store(true, std::sync::atomic::Ordering::Release);
self.notify.0.notify_waiters();
}
}
matched
}
}
/// Verify if this mock has verified the expectations set at creation time
/// over the number of invocations.
pub(crate) fn verify(&self) -> VerificationReport {
VerificationReport {
mock_name: self.specification.name.clone(),
n_matched_requests: self.n_matched_requests,
expectation_range: self.specification.expectation_range.clone(),
position_in_set: self.position_in_set,
}
}
pub(crate) fn response_template(
&self,
request: &Request,
) -> Result<ResponseTemplate, ErrorResponse> {
self.specification.response_template(request)
}
pub(crate) fn received_requests(&self) -> Vec<crate::Request> {
self.matched_requests.clone()
}
pub(crate) fn notify(&self) -> Arc<(Notify, AtomicBool)> {
self.notify.clone()
}
}

136
vendor/wiremock/src/request.rs vendored Normal file
View File

@@ -0,0 +1,136 @@
use std::fmt;
use http::{HeaderMap, Method};
use http_body_util::BodyExt;
use serde::de::DeserializeOwned;
use url::Url;
pub const BODY_PRINT_LIMIT: usize = 10_000;
/// Specifies limitations on printing request bodies when logging requests. For some mock servers
/// the bodies may be too large to reasonably print and it may be desirable to limit them.
#[derive(Debug, Copy, Clone)]
pub enum BodyPrintLimit {
/// Maximum length of a body to print in bytes.
Limited(usize),
/// There is no limit to the size of a body that may be printed.
Unlimited,
}
/// An incoming request to an instance of [`MockServer`].
///
/// Each matcher gets an immutable reference to a `Request` instance in the [`matches`] method
/// defined in the [`Match`] trait.
///
/// [`MockServer`]: crate::MockServer
/// [`matches`]: crate::Match::matches
/// [`Match`]: crate::Match
///
/// ### Implementation notes:
/// We can't use `http_types::Request` directly in our `Match::matches` signature:
/// it requires having mutable access to the request to extract the body (which gets
/// consumed when read!).
/// It would also require `matches` to be async, which is cumbersome due to the lack of async traits.
///
/// We introduce our `Request` type to perform this extraction once when the request
/// arrives in the mock serve, store the result and pass an immutable reference to it
/// to all our matchers.
#[derive(Debug, Clone)]
pub struct Request {
pub url: Url,
pub method: Method,
pub headers: HeaderMap,
pub body: Vec<u8>,
}
impl Request {
pub fn body_json<T: DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
serde_json::from_slice(&self.body)
}
pub(crate) async fn from_hyper(request: hyper::Request<hyper::body::Incoming>) -> Request {
let (parts, body) = request.into_parts();
let url = match parts.uri.authority() {
Some(_) => parts.uri.to_string(),
None => format!("http://localhost{}", parts.uri),
}
.parse()
.unwrap();
let body = body
.collect()
.await
.expect("Failed to read request body.")
.to_bytes();
Self {
url,
method: parts.method,
headers: parts.headers,
body: body.to_vec(),
}
}
pub(crate) fn print_with_limit(
&self,
mut buffer: impl fmt::Write,
body_print_limit: BodyPrintLimit,
) -> fmt::Result {
writeln!(buffer, "{} {}", self.method, self.url)?;
for name in self.headers.keys() {
let values = self
.headers
.get_all(name)
.iter()
.map(|value| String::from_utf8_lossy(value.as_bytes()))
.collect::<Vec<_>>();
let values = values.join(",");
writeln!(buffer, "{}: {}", name, values)?;
}
match body_print_limit {
BodyPrintLimit::Limited(limit) if self.body.len() > limit => {
let mut written = false;
for end_byte in limit..(limit + 4).max(self.body.len()) {
if let Ok(truncated) = std::str::from_utf8(&self.body[..end_byte]) {
written = true;
writeln!(buffer, "{}", truncated)?;
if end_byte < self.body.len() {
writeln!(
buffer,
"We truncated the body because it was too large: {} bytes (limit: {} bytes)",
self.body.len(),
limit
)?;
writeln!(
buffer,
"Increase this limit by setting `WIREMOCK_BODY_PRINT_LIMIT`, or calling `MockServerBuilder::body_print_limit` when building your MockServer instance"
)?;
}
break;
}
}
if !written {
writeln!(
buffer,
"Body is likely binary (invalid utf-8) size is {} bytes",
self.body.len()
)
} else {
Ok(())
}
}
_ => {
if let Ok(body) = std::str::from_utf8(&self.body) {
writeln!(buffer, "{}", body)
} else {
writeln!(
buffer,
"Body is likely binary (invalid utf-8) size is {} bytes",
self.body.len()
)
}
}
}
}
}

169
vendor/wiremock/src/respond.rs vendored Normal file
View File

@@ -0,0 +1,169 @@
use crate::{ErrorResponse, Request, ResponseTemplate};
/// Anything that implements `Respond` can be used to reply to an incoming request when a
/// [`Mock`] is activated.
///
/// ## Fixed responses
///
/// The simplest `Respond` is [`ResponseTemplate`]: no matter the request, it will
/// always return itself.
///
/// ```rust
/// use wiremock::{MockServer, Mock, ResponseTemplate};
/// use wiremock::matchers::method;
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
/// let correlation_id = "1311db4f-fe65-4cb2-b514-1bb47f781aa7";
/// let template = ResponseTemplate::new(200).insert_header(
/// "X-Correlation-ID",
/// correlation_id
/// );
/// Mock::given(method("GET"))
/// .respond_with(template)
/// .mount(&mock_server)
/// .await;
///
/// // Act
/// let response = reqwest::get(&mock_server.uri())
/// .await
/// .unwrap();
///
/// // Assert
/// assert_eq!(response.status(), 200);
/// assert_eq!(response.headers().get("X-Correlation-ID").unwrap().to_str().unwrap(), correlation_id);
/// }
/// ```
///
/// ## Dynamic responses
///
/// You can use `Respond`, though, to implement responses that depend on the data in
/// the request matched by a [`Mock`].
///
/// Functions from `Request` to `ResponseTemplate` implement `Respond`, so for simple cases you
/// can use a closure to build a response dynamically, for instance to echo the request body back:
///
/// ```rust
/// use wiremock::{Match, MockServer, Mock, Request, ResponseTemplate};
/// use wiremock::matchers::path;
///
/// #[async_std::main]
/// async fn main() {
/// let mock_server = MockServer::start().await;
/// let body = "Mock Server!".to_string();
///
/// Mock::given(path("/echo"))
/// .respond_with(|req: &Request| {
/// let body_string = String::from_utf8(req.body.clone()).unwrap();
/// ResponseTemplate::new(200).set_body_string(body_string)
/// })
/// .mount(&mock_server)
/// .await;
///
/// let client = reqwest::Client::new();
/// let response = client.post(format!("{}/echo", &mock_server.uri()))
/// .body(body.clone())
/// .send()
/// .await
/// .unwrap();
/// assert_eq!(response.status(), 200);
/// assert_eq!(response.text().await.unwrap(), body);
/// }
/// ```
///
/// For more complex cases you may want to implement `Respond` yourself. As an example, this is a
/// `Respond` that propagates back a request header in the response:
///
/// ```rust
/// use http::HeaderName;
/// use wiremock::{Match, MockServer, Mock, Request, ResponseTemplate, Respond};
/// use wiremock::matchers::path;
/// use std::convert::TryInto;
/// use std::str::FromStr;
///
/// /// Responds using the specified `ResponseTemplate`, but it dynamically populates the
/// /// `X-Correlation-Id` header from the request data.
/// pub struct CorrelationIdResponder(pub ResponseTemplate);
///
/// impl Respond for CorrelationIdResponder {
/// fn respond(&self, request: &Request) -> ResponseTemplate {
/// const HEADER: HeaderName = HeaderName::from_static("x-correlation-id");
/// let mut response_template = self.0.clone();
/// if let Some(correlation_id) = request.headers.get(&HEADER) {
/// response_template = response_template.insert_header(
/// HEADER,
/// correlation_id.to_owned()
/// );
/// }
/// response_template
/// }
/// }
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
/// let correlation_id = "1241-1245-1548-4567";
///
/// Mock::given(path("/hello"))
/// .respond_with(CorrelationIdResponder(ResponseTemplate::new(200)))
/// .mount(&mock_server)
/// .await;
///
/// let client = reqwest::Client::new();
/// let response = client
/// .get(format!("{}/hello", &mock_server.uri()))
/// .header("X-Correlation-Id", correlation_id)
/// .send()
/// .await
/// .unwrap();
/// assert_eq!(response.status(), 200);
/// assert_eq!(response.headers().get("X-Correlation-Id").unwrap().to_str().unwrap(), correlation_id);
/// }
/// ```
///
/// [`Mock`]: crate::Mock
/// [`ResponseTemplate`]: crate::ResponseTemplate
pub trait Respond: Send + Sync {
/// Given a reference to a [`Request`] return a [`ResponseTemplate`] that will be used
/// by the [`MockServer`] as blueprint for the response returned to the client.
///
/// [`Request`]: crate::Request
/// [`MockServer`]: crate::MockServer
/// [`ResponseTemplate`]: crate::ResponseTemplate
fn respond(&self, request: &Request) -> ResponseTemplate;
}
/// A `ResponseTemplate` is the simplest `Respond` implementation: it returns a clone of itself
/// no matter what the incoming request contains!
impl Respond for ResponseTemplate {
fn respond(&self, _request: &Request) -> ResponseTemplate {
self.clone()
}
}
impl<F> Respond for F
where
F: Send + Sync + Fn(&Request) -> ResponseTemplate,
{
fn respond(&self, request: &Request) -> ResponseTemplate {
(self)(request)
}
}
/// Like [`Respond`], but it only allows returning an error through a function.
pub trait RespondErr: Send + Sync {
fn respond_err(&self, request: &Request) -> ErrorResponse;
}
impl<F, Err> RespondErr for F
where
F: Send + Sync + Fn(&Request) -> Err,
Err: std::error::Error + Send + Sync + 'static,
{
fn respond_err(&self, request: &Request) -> ErrorResponse {
Box::new((self)(request))
}
}

331
vendor/wiremock/src/response_template.rs vendored Normal file
View File

@@ -0,0 +1,331 @@
use http::{HeaderMap, HeaderName, HeaderValue, Response, StatusCode};
use http_body_util::Full;
use hyper::body::Bytes;
use serde::Serialize;
use std::convert::TryInto;
use std::time::Duration;
/// The blueprint for the response returned by a [`MockServer`] when a [`Mock`] matches on an incoming request.
///
/// [`Mock`]: crate::Mock
/// [`MockServer`]: crate::MockServer
#[derive(Clone, Debug)]
pub struct ResponseTemplate {
mime: String,
status_code: StatusCode,
headers: HeaderMap,
body: Option<Vec<u8>>,
delay: Option<Duration>,
}
// `wiremock` is a crate meant for testing - failures are most likely not handled/temporary mistakes.
// Hence we prefer to panic and provide an easier API than to use `Result`s thus pushing
// the burden of "correctness" (and conversions) on the user.
//
// All methods try to accept the widest possible set of inputs and then perform the fallible conversion
// internally, bailing if the fallible conversion fails.
//
// Same principle applies to allocation/cloning, freely used where convenient.
impl ResponseTemplate {
/// Start building a `ResponseTemplate` specifying the status code of the response.
pub fn new<S>(s: S) -> Self
where
S: TryInto<StatusCode>,
<S as TryInto<StatusCode>>::Error: std::fmt::Debug,
{
let status_code = s.try_into().expect("Failed to convert into status code.");
Self {
status_code,
headers: HeaderMap::new(),
mime: String::new(),
body: None,
delay: None,
}
}
/// Append a header `value` to list of headers with `key` as header name.
///
/// Unlike `insert_header`, this function will not override the contents of a header:
/// - if there are no header values with `key` as header name, it will insert one;
/// - if there are already some values with `key` as header name, it will append to the
/// existing list.
pub fn append_header<K, V>(mut self, key: K, value: V) -> Self
where
K: TryInto<HeaderName>,
<K as TryInto<HeaderName>>::Error: std::fmt::Debug,
V: TryInto<HeaderValue>,
<V as TryInto<HeaderValue>>::Error: std::fmt::Debug,
{
let key = key.try_into().expect("Failed to convert into header name.");
let value = value
.try_into()
.expect("Failed to convert into header value.");
self.headers.append(key, value);
self
}
/// Insert a header `value` with `key` as header name.
///
/// This function will override the contents of a header:
/// - if there are no header values with `key` as header name, it will insert one;
/// - if there are already some values with `key` as header name, it will drop them and
/// start a new list of header values, containing only `value`.
///
/// ### Example:
/// ```rust
/// use wiremock::{MockServer, Mock, ResponseTemplate};
/// use wiremock::matchers::method;
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
/// let correlation_id = "1311db4f-fe65-4cb2-b514-1bb47f781aa7";
/// let template = ResponseTemplate::new(200).insert_header(
/// "X-Correlation-ID",
/// correlation_id
/// );
/// Mock::given(method("GET"))
/// .respond_with(template)
/// .mount(&mock_server)
/// .await;
///
/// // Act
/// let res = reqwest::get(&mock_server.uri())
/// .await
/// .unwrap();
///
/// // Assert
/// assert_eq!(res.headers().get("X-Correlation-ID").unwrap().to_str().unwrap(), correlation_id);
/// }
/// ```
pub fn insert_header<K, V>(mut self, key: K, value: V) -> Self
where
K: TryInto<HeaderName>,
<K as TryInto<HeaderName>>::Error: std::fmt::Debug,
V: TryInto<HeaderValue>,
<V as TryInto<HeaderValue>>::Error: std::fmt::Debug,
{
let key = key.try_into().expect("Failed to convert into header name.");
let value = value
.try_into()
.expect("Failed to convert into header value.");
self.headers.insert(key, value);
self
}
/// Append multiple header key-value pairs.
///
/// Existing header values will not be overridden.
///
/// # Example
/// ```rust
/// use wiremock::{MockServer, Mock, ResponseTemplate};
/// use wiremock::matchers::method;
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
/// let headers = vec![
/// ("Set-Cookie", "name=value"),
/// ("Set-Cookie", "name2=value2; Domain=example.com"),
/// ];
/// let template = ResponseTemplate::new(200).append_headers(headers);
/// Mock::given(method("GET"))
/// .respond_with(template)
/// .mount(&mock_server)
/// .await;
///
/// // Act
/// let res = reqwest::get(&mock_server.uri())
/// .await
/// .unwrap();
///
/// // Assert
/// assert_eq!(res.headers().get_all("Set-Cookie").iter().count(), 2);
/// }
/// ```
pub fn append_headers<K, V, I>(mut self, headers: I) -> Self
where
K: TryInto<HeaderName>,
<K as TryInto<HeaderName>>::Error: std::fmt::Debug,
V: TryInto<HeaderValue>,
<V as TryInto<HeaderValue>>::Error: std::fmt::Debug,
I: IntoIterator<Item = (K, V)>,
{
let headers = headers.into_iter().map(|(key, value)| {
(
key.try_into().expect("Failed to convert into header name."),
value
.try_into()
.expect("Failed to convert into header value."),
)
});
// The `Extend<(HeaderName, T)>` impl uses `HeaderMap::append` internally: https://docs.rs/http/1.0.0/src/http/header/map.rs.html#1953
self.headers.extend(headers);
self
}
/// Set the response body with bytes.
///
/// It sets "Content-Type" to "application/octet-stream".
///
/// To set a body with bytes but a different "Content-Type"
/// [`set_body_raw`](#method.set_body_raw) can be used.
pub fn set_body_bytes<B>(mut self, body: B) -> Self
where
B: TryInto<Vec<u8>>,
<B as TryInto<Vec<u8>>>::Error: std::fmt::Debug,
{
let body = body.try_into().expect("Failed to convert into body.");
self.body = Some(body);
self
}
/// Set the response body from a JSON-serializable value.
///
/// It sets "Content-Type" to "application/json".
pub fn set_body_json<B: Serialize>(mut self, body: B) -> Self {
let body = serde_json::to_vec(&body).expect("Failed to convert into body.");
self.body = Some(body);
self.mime = "application/json".to_string();
self
}
/// Set the response body to a string.
///
/// It sets "Content-Type" to "text/plain".
pub fn set_body_string<T>(mut self, body: T) -> Self
where
T: TryInto<String>,
<T as TryInto<String>>::Error: std::fmt::Debug,
{
let body = body.try_into().expect("Failed to convert into body.");
self.body = Some(body.into_bytes());
self.mime = "text/plain".to_string();
self
}
/// Set a raw response body. The mime type needs to be set because the
/// raw body could be of any type.
///
/// ### Example:
/// ```rust
/// use wiremock::{MockServer, Mock, ResponseTemplate};
/// use wiremock::matchers::method;
///
/// mod external {
/// // This could be a method of a struct that is
/// // implemented in another crate and the struct
/// // does not implement Serialize.
/// pub fn body() -> Vec<u8>{
/// r#"{"hello": "world"}"#.as_bytes().to_owned()
/// }
/// }
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
/// let template = ResponseTemplate::new(200).set_body_raw(
/// external::body(),
/// "application/json"
/// );
/// Mock::given(method("GET"))
/// .respond_with(template)
/// .mount(&mock_server)
/// .await;
///
/// // Act
/// let mut res = reqwest::get(&mock_server.uri())
/// .await
/// .unwrap();
/// let content_type = res.headers().get(reqwest::header::CONTENT_TYPE).cloned();
/// let body = res.text()
/// .await
/// .unwrap();
///
/// // Assert
/// assert_eq!(body, r#"{"hello": "world"}"#);
/// assert_eq!(content_type, Some("application/json".parse().unwrap()));
/// }
/// ```
pub fn set_body_raw<B>(mut self, body: B, mime: &str) -> Self
where
B: TryInto<Vec<u8>>,
<B as TryInto<Vec<u8>>>::Error: std::fmt::Debug,
{
let body = body.try_into().expect("Failed to convert into body.");
self.body = Some(body);
self.mime = mime.to_string();
self
}
/// By default the [`MockServer`] tries to fulfill incoming requests as fast as possible.
///
/// You can use `set_delay` to introduce an artificial delay to simulate the behaviour of
/// a real server with a non-negligible latency.
///
/// In particular, you can use it to test the behaviour of your timeout policies.
///
/// ### Example:
/// ```rust
/// use wiremock::{MockServer, Mock, ResponseTemplate};
/// use wiremock::matchers::method;
/// use std::time::Duration;
/// use async_std::prelude::FutureExt;
///
/// #[async_std::main]
/// async fn main() {
/// // Arrange
/// let mock_server = MockServer::start().await;
/// let delay = Duration::from_secs(1);
/// let template = ResponseTemplate::new(200).set_delay(delay);
/// Mock::given(method("GET"))
/// .respond_with(template)
/// .mount(&mock_server)
/// .await;
///
/// // Act
/// let mut res = async_std::future::timeout(
/// // Shorter than the response delay!
/// delay / 3,
/// reqwest::get(&mock_server.uri())
/// )
/// .await;
///
/// // Assert - Timeout error!
/// assert!(res.is_err());
/// }
/// ```
///
/// [`MockServer`]: crate::mock_server::MockServer
pub fn set_delay(mut self, delay: Duration) -> Self {
self.delay = Some(delay);
self
}
/// Generate a response from the template.
pub(crate) fn generate_response(&self) -> Response<Full<Bytes>> {
let mut response = Response::builder().status(self.status_code);
let mut headers = self.headers.clone();
// Set content-type, if needed
if !self.mime.is_empty() {
headers.insert(http::header::CONTENT_TYPE, self.mime.parse().unwrap());
}
*response.headers_mut().unwrap() = headers;
let body = self.body.clone().unwrap_or_default();
response.body(body.into()).unwrap()
}
/// Retrieve the response delay.
pub(crate) fn delay(&self) -> &Option<Duration> {
&self.delay
}
}

47
vendor/wiremock/src/verification.rs vendored Normal file
View File

@@ -0,0 +1,47 @@
use crate::mock::Times;
/// A report returned by an `MountedMock` detailing what the user expectations were and
/// how many calls were actually received since the mock was mounted on the server.
#[derive(Clone)]
pub(crate) struct VerificationReport {
/// The mock name specified by the user.
pub(crate) mock_name: Option<String>,
/// What users specified
pub(crate) expectation_range: Times,
/// Actual number of received requests that matched the specification
pub(crate) n_matched_requests: u64,
/// The position occupied by the mock that generated the report within its parent
/// [`MountedMockSet`](crate::mock_set::MountedMockSet) collection of `MountedMock`s.
///
/// E.g. `0` if it is the first mock that we try to match against an incoming request, `1`
/// if it is the second, etc.
pub(crate) position_in_set: usize,
}
impl VerificationReport {
pub(crate) fn error_message(&self) -> String {
if let Some(ref mock_name) = self.mock_name {
format!(
"{}.\n\tExpected range of matching incoming requests: {}\n\tNumber of matched incoming requests: {}",
mock_name, self.expectation_range, self.n_matched_requests
)
} else {
format!(
"Mock #{}.\n\tExpected range of matching incoming requests: {}\n\tNumber of matched incoming requests: {}",
self.position_in_set, self.expectation_range, self.n_matched_requests
)
}
}
pub(crate) fn is_satisfied(&self) -> bool {
self.expectation_range.contains(self.n_matched_requests)
}
}
pub(crate) enum VerificationOutcome {
/// The expectations set on all active mocks were satisfied.
Success,
/// The expectations set for one or more of the active mocks were not satisfied.
/// All failed expectations are returned.
Failure(Vec<VerificationReport>),
}

View File

@@ -0,0 +1 @@
{"rustc_fingerprint":7411504112587450034,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.93.0-nightly (6647be936 2025-11-09)\nbinary: rustc\ncommit-hash: 6647be93640686a2a443a49f15c3390b68c8b5dd\ncommit-date: 2025-11-09\nhost: aarch64-apple-darwin\nrelease: 1.93.0-nightly\nLLVM version: 21.1.3\n","stderr":""}},"successes":{}}

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

495
vendor/wiremock/tests/mocks.rs vendored Normal file
View File

@@ -0,0 +1,495 @@
use futures::FutureExt;
use reqwest::StatusCode;
use serde::Serialize;
use serde_json::json;
use std::fmt::{Display, Formatter};
use std::io::ErrorKind;
use std::iter;
use std::net::TcpStream;
use std::time::Duration;
use wiremock::matchers::{PathExactMatcher, body_json, body_partial_json, method, path};
use wiremock::{Mock, MockServer, Request, ResponseTemplate};
#[async_std::test]
async fn new_starts_the_server() {
// Act
let mock_server = MockServer::start().await;
// Assert
assert!(TcpStream::connect(mock_server.address()).is_ok())
}
#[async_std::test]
async fn returns_404_if_nothing_matches() {
// Arrange - no mocks mounted
let mock_server = MockServer::start().await;
// Act
let status = reqwest::get(&mock_server.uri()).await.unwrap().status();
// Assert
assert_eq!(status, 404);
}
#[async_std::test]
#[should_panic]
async fn panics_if_the_expectation_is_not_satisfied() {
// Arrange
let mock_server = MockServer::start().await;
let response = ResponseTemplate::new(200);
Mock::given(method("GET"))
.respond_with(response)
.expect(1..)
.named("panics_if_the_expectation_is_not_satisfied expectation failed")
.mount(&mock_server)
.await;
// Act - we never call the mock
}
#[async_std::test]
#[should_panic(expected = "Verifications failed:
- Mock #0.
\tExpected range of matching incoming requests: 1 <= x
\tNumber of matched incoming requests: 0
The server did not receive any request.")]
async fn no_received_request_line_is_printed_in_the_panic_message_if_expectations_are_not_verified()
{
// Arrange
let mock_server = MockServer::start().await;
let response = ResponseTemplate::new(200);
Mock::given(method("GET"))
.respond_with(response)
.expect(1..)
.mount(&mock_server)
.await;
// Act - we never call the mock
}
#[async_std::test]
#[should_panic(expected = "Verifications failed:
- Mock #0.
\tExpected range of matching incoming requests: 1 <= x
\tNumber of matched incoming requests: 0
Received requests:
- Request #1
\tGET http://localhost/")]
async fn received_request_are_printed_as_panic_message_if_expectations_are_not_verified() {
// Arrange
let mock_server = MockServer::start().await;
let response = ResponseTemplate::new(200);
Mock::given(method("POST"))
.respond_with(response)
.expect(1..)
.mount(&mock_server)
.await;
// Act - we sent a request that does not match (GET)
reqwest::get(&mock_server.uri()).await.unwrap();
// Assert - verified on drop
}
#[async_std::test]
#[should_panic]
async fn panic_during_expectation_does_not_crash() {
// Arrange
let mock_server = MockServer::start().await;
let response = ResponseTemplate::new(200);
Mock::given(method("GET"))
.respond_with(response)
.expect(1..)
.named("panic_during_expectation_does_not_crash expectation failed")
.mount(&mock_server)
.await;
// Act - start a panic
panic!("forced panic")
}
#[async_std::test]
async fn simple_route_mock() {
// Arrange
let mock_server = MockServer::start().await;
let response = ResponseTemplate::new(200).set_body_bytes("world");
let mock = Mock::given(method("GET"))
.and(PathExactMatcher::new("hello"))
.respond_with(response);
mock_server.register(mock).await;
// Act
let response = reqwest::get(format!("{}/hello", &mock_server.uri()))
.await
.unwrap();
// Assert
assert_eq!(response.status(), 200);
assert_eq!(response.text().await.unwrap(), "world");
}
#[async_std::test]
async fn two_route_mocks() {
// Arrange
let mock_server = MockServer::start().await;
// First
let response = ResponseTemplate::new(200).set_body_bytes("aaa");
Mock::given(method("GET"))
.and(PathExactMatcher::new("first"))
.respond_with(response)
.named("/first")
.mount(&mock_server)
.await;
// Second
let response = ResponseTemplate::new(200).set_body_bytes("bbb");
Mock::given(method("GET"))
.and(PathExactMatcher::new("second"))
.respond_with(response)
.named("/second")
.mount(&mock_server)
.await;
// Act
let first_response = reqwest::get(format!("{}/first", &mock_server.uri()))
.await
.unwrap();
let second_response = reqwest::get(format!("{}/second", &mock_server.uri()))
.await
.unwrap();
// Assert
assert_eq!(first_response.status(), 200);
assert_eq!(second_response.status(), 200);
assert_eq!(first_response.text().await.unwrap(), "aaa");
assert_eq!(second_response.text().await.unwrap(), "bbb");
}
#[async_std::test]
async fn body_json_matches_independent_of_key_ordering() {
#[derive(Serialize)]
struct X {
b: u8,
a: u8,
}
// Arrange
let expected_body = json!({ "a": 1, "b": 2 });
let body = serde_json::to_string(&X { a: 1, b: 2 }).unwrap();
let mock_server = MockServer::start().await;
let response = ResponseTemplate::new(200);
let mock = Mock::given(method("POST"))
.and(body_json(expected_body))
.respond_with(response);
mock_server.register(mock).await;
// Act
let client = reqwest::Client::new();
let response = client
.post(mock_server.uri())
.body(body)
.send()
.await
.unwrap();
// Assert
assert_eq!(response.status(), 200);
}
#[async_std::test]
async fn body_json_partial_matches_a_part_of_response_json() {
// Arrange
let expected_body = json!({ "a": 1, "c": { "e": 2 } });
let body = json!({ "a": 1, "b": 2, "c": { "d": 1, "e": 2 } });
let mock_server = MockServer::start().await;
let response = ResponseTemplate::new(200);
let mock = Mock::given(method("POST"))
.and(body_partial_json(expected_body))
.respond_with(response);
mock_server.register(mock).await;
let client = reqwest::Client::new();
// Act
let response = client
.post(mock_server.uri())
.json(&body)
.send()
.await
.unwrap();
// Assert
assert_eq!(response.status(), StatusCode::OK);
}
#[should_panic(expected = "\
Wiremock can't match the path `abcd?` because it contains a `?`. You must use `wiremock::matchers::query_param` to match on query parameters (the part of the path after the `?`).")]
#[async_std::test]
async fn query_parameter_is_not_accepted_in_path() {
Mock::given(method("GET")).and(path("abcd?"));
}
#[should_panic(expected = "\
Wiremock can't match the path `https://domain.com/abcd` because it contains the host `domain.com`. You don't have to specify the host - wiremock knows it. Try replacing your path with `path(\"/abcd\")`")]
#[async_std::test]
async fn host_is_not_accepted_in_path() {
Mock::given(method("GET")).and(path("https://domain.com/abcd"));
}
#[async_std::test]
async fn use_mock_guard_to_verify_requests_from_mock() {
// Arrange
let mock_server = MockServer::start().await;
let first = mock_server
.register_as_scoped(
Mock::given(method("POST"))
.and(PathExactMatcher::new("first"))
.respond_with(ResponseTemplate::new(200)),
)
.await;
let second = mock_server
.register_as_scoped(
Mock::given(method("POST"))
.and(PathExactMatcher::new("second"))
.respond_with(ResponseTemplate::new(200)),
)
.await;
let client = reqwest::Client::new();
// Act
let uri = mock_server.uri();
let response = client
.post(format!("{uri}/first"))
.json(&json!({ "attempt": 1}))
.send()
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let response = client
.post(format!("{uri}/first"))
.json(&json!({ "attempt": 2}))
.send()
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let response = client
.post(format!("{uri}/second"))
.json(&json!({ "attempt": 99}))
.send()
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
// Assert
let all_requests_to_first = first.received_requests().await;
assert_eq!(all_requests_to_first.len(), 2);
let value: serde_json::Value = second.received_requests().await[0].body_json().unwrap();
assert_eq!(value, json!({"attempt": 99}));
}
#[async_std::test]
async fn use_mock_guard_to_await_satisfaction_readiness() {
// Arrange
let mock_server = MockServer::start().await;
let satisfy = mock_server
.register_as_scoped(
Mock::given(method("POST"))
.and(PathExactMatcher::new("satisfy"))
.respond_with(ResponseTemplate::new(200))
.expect(1),
)
.await;
let eventually_satisfy = mock_server
.register_as_scoped(
Mock::given(method("POST"))
.and(PathExactMatcher::new("eventually_satisfy"))
.respond_with(ResponseTemplate::new(200))
.expect(1),
)
.await;
// Act one
let uri = mock_server.uri();
let client = reqwest::Client::new();
let response = client.post(format!("{uri}/satisfy")).send().await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
// Assert
satisfy
.wait_until_satisfied()
.now_or_never()
.expect("should be satisfied immediately");
eventually_satisfy
.wait_until_satisfied()
.now_or_never()
.ok_or(())
.expect_err("should not be satisfied yet");
// Act two
async_std::task::spawn(async move {
async_std::task::sleep(Duration::from_millis(100)).await;
let client = reqwest::Client::new();
let response = client
.post(format!("{uri}/eventually_satisfy"))
.send()
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
});
// Assert
eventually_satisfy
.wait_until_satisfied()
.now_or_never()
.ok_or(())
.expect_err("should not be satisfied yet");
async_std::io::timeout(
Duration::from_millis(1000),
eventually_satisfy.wait_until_satisfied().map(Ok),
)
.await
.expect("should be satisfied");
}
#[async_std::test]
async fn debug_prints_mock_server_variants() {
let pooled_mock_server = MockServer::start().await;
let pooled_debug_str = format!("{:?}", pooled_mock_server);
assert!(pooled_debug_str.starts_with("MockServer(Pooled(Object {"));
assert!(
pooled_debug_str
.find(
format!(
"BareMockServer {{ address: {} }}",
pooled_mock_server.address()
)
.as_str()
)
.is_some()
);
let bare_mock_server = MockServer::builder().start().await;
assert_eq!(
format!(
"MockServer(Bare(BareMockServer {{ address: {} }}))",
bare_mock_server.address()
),
format!("{:?}", bare_mock_server)
);
}
#[tokio::test]
async fn io_err() {
// Act
let mock_server = MockServer::start().await;
let mock = Mock::given(method("GET")).respond_with_err(|_: &Request| {
std::io::Error::new(ErrorKind::ConnectionReset, "connection reset")
});
mock_server.register(mock).await;
// Assert
let err = reqwest::get(&mock_server.uri()).await.unwrap_err();
// We're skipping the original error since it can be either `error sending request` or
// `error sending request for url (http://127.0.0.1:<port>/)`
let actual_err: Vec<String> =
iter::successors(std::error::Error::source(&err), |err| err.source())
.map(|err| err.to_string())
.collect();
let expected_err = vec![
"client error (SendRequest)".to_string(),
"connection closed before message completed".to_string(),
];
assert_eq!(actual_err, expected_err);
}
#[tokio::test]
async fn custom_err() {
// Act
#[derive(Debug)]
struct CustomErr;
impl Display for CustomErr {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str("custom error")
}
}
impl std::error::Error for CustomErr {}
let mock_server = MockServer::start().await;
let mock = Mock::given(method("GET")).respond_with_err(|_: &Request| CustomErr);
mock_server.register(mock).await;
// Assert
let err = reqwest::get(&mock_server.uri()).await.unwrap_err();
// We're skipping the original error since it can be either `error sending request` or
// `error sending request for url (http://127.0.0.1:<port>/)`
let actual_err: Vec<String> =
iter::successors(std::error::Error::source(&err), |err| err.source())
.map(|err| err.to_string())
.collect();
let expected_err = vec![
"client error (SendRequest)".to_string(),
"connection closed before message completed".to_string(),
];
assert_eq!(actual_err, expected_err);
}
#[async_std::test]
async fn method_matcher_is_case_insensitive() {
// Arrange
let mock_server = MockServer::start().await;
let response = ResponseTemplate::new(200).set_body_bytes("world");
let mock = Mock::given(method("Get"))
.and(PathExactMatcher::new("hello"))
.respond_with(response);
mock_server.register(mock).await;
// Act
let response = reqwest::get(format!("{}/hello", &mock_server.uri()))
.await
.unwrap();
// Assert
assert_eq!(response.status(), 200);
assert_eq!(response.text().await.unwrap(), "world");
}
#[async_std::test]
async fn http_crate_method_can_be_used_directly() {
use http::Method;
// Arrange
let mock_server = MockServer::start().await;
let response = ResponseTemplate::new(200).set_body_bytes("world");
let mock = Mock::given(method(Method::GET))
.and(PathExactMatcher::new("hello"))
.respond_with(response);
mock_server.register(mock).await;
// Act
let response = reqwest::get(format!("{}/hello", &mock_server.uri()))
.await
.unwrap();
// Assert
assert_eq!(response.status(), 200);
assert_eq!(response.text().await.unwrap(), "world");
}

89
vendor/wiremock/tests/priority.rs vendored Normal file
View File

@@ -0,0 +1,89 @@
use wiremock::{
Mock, MockServer, ResponseTemplate,
matchers::{method, path, path_regex},
};
#[async_std::test]
async fn should_prioritize_mock_with_highest_priority() {
// Arrange
let mock_server = MockServer::start().await;
let exact = Mock::given(method("GET"))
.and(path("abcd"))
.respond_with(ResponseTemplate::new(200))
.with_priority(2);
mock_server.register(exact).await;
let regex = Mock::given(method("GET"))
.and(path_regex("[a-z]{4}"))
.respond_with(ResponseTemplate::new(201))
.with_priority(1);
mock_server.register(regex).await;
// Act
let should_match = reqwest::get(format!("{}/abcd", mock_server.uri()))
.await
.unwrap();
// Assert
assert_eq!(should_match.status(), 201);
}
#[async_std::test]
async fn should_not_prioritize_mock_with_lower_priority() {
// Arrange
let mock_server = MockServer::start().await;
let exact = Mock::given(method("GET"))
.and(path("abcd"))
.respond_with(ResponseTemplate::new(200))
.with_priority(u8::MAX);
mock_server.register(exact).await;
let regex = Mock::given(method("GET"))
.and(path_regex("[a-z]{4}"))
.respond_with(ResponseTemplate::new(201));
mock_server.register(regex).await;
// Act
let should_match = reqwest::get(format!("{}/abcd", mock_server.uri()))
.await
.unwrap();
// Assert
assert_eq!(should_match.status(), 201);
}
#[async_std::test]
async fn by_default_should_use_insertion_order() {
// Arrange
let mock_server = MockServer::start().await;
let exact = Mock::given(method("GET"))
.and(path("abcd"))
.respond_with(ResponseTemplate::new(200));
let regex = Mock::given(method("GET"))
.and(path_regex("[a-z]{4}"))
.respond_with(ResponseTemplate::new(201));
mock_server.register(exact).await;
mock_server.register(regex).await;
// Act
let should_match = reqwest::get(format!("{}/abcd", mock_server.uri()))
.await
.unwrap();
// Assert
assert_eq!(should_match.status(), 200);
// Insert mocks in the opposite order
// Arrange
let mock_server = MockServer::start().await;
let exact = Mock::given(method("GET"))
.and(path("abcd"))
.respond_with(ResponseTemplate::new(200));
let regex = Mock::given(method("GET"))
.and(path_regex("[a-z]{4}"))
.respond_with(ResponseTemplate::new(201));
mock_server.register(regex).await;
mock_server.register(exact).await;
// Act
let should_match = reqwest::get(format!("{}/abcd", mock_server.uri()))
.await
.unwrap();
// Assert
assert_eq!(should_match.status(), 201);
}

View File

@@ -0,0 +1,357 @@
use wiremock::matchers::{basic_auth, bearer_token, header, header_regex, headers, method};
use wiremock::{Mock, MockServer, ResponseTemplate};
#[async_std::test]
async fn should_match_simple_request_header() {
// Arrange
let mock_server = MockServer::start().await;
let mock = Mock::given(method("GET"))
.and(header("content-type", "application/json"))
.respond_with(ResponseTemplate::new(200));
mock_server.register(mock).await;
// Act
let client = reqwest::Client::new();
let should_match = client
.get(mock_server.uri())
.header("content-type", "application/json")
.send()
.await
.unwrap();
// Assert
assert_eq!(should_match.status(), 200);
}
#[async_std::test]
async fn should_not_match_simple_request_header_upon_wrong_key() {
// Arrange
let mock_server = MockServer::start().await;
let mock = Mock::given(method("GET"))
.and(header("content-type", "application/json"))
.respond_with(ResponseTemplate::new(200));
mock_server.register(mock).await;
// Act
let client = reqwest::Client::new();
let should_fail_wrong_key = client
.get(mock_server.uri())
.header("accept", "application/json")
.send()
.await
.unwrap();
// Assert
assert_eq!(should_fail_wrong_key.status(), 404);
}
#[async_std::test]
async fn should_not_match_simple_request_header_upon_wrong_value() {
// Arrange
let mock_server = MockServer::start().await;
let mock = Mock::given(method("GET"))
.and(header("content-type", "application/json"))
.respond_with(ResponseTemplate::new(200));
mock_server.register(mock).await;
// Act
let client = reqwest::Client::new();
let should_fail_wrong_value = client
.get(mock_server.uri())
.header("content-type", "application/xml")
.send()
.await
.unwrap();
// Assert
assert_eq!(should_fail_wrong_value.status(), 404);
}
#[async_std::test]
async fn should_match_multi_request_header() {
// Arrange
let mock_server = MockServer::start().await;
let header_matcher = headers("cache-control", vec!["no-cache", "no-store"]);
let mock = Mock::given(method("GET"))
.and(header_matcher)
.respond_with(ResponseTemplate::new(200));
mock_server.register(mock).await;
// Act
let client = reqwest::Client::new();
let should_match = client
.get(mock_server.uri())
.header("cache-control", "no-cache, no-store")
.send()
.await
.unwrap();
// Assert
assert_eq!(should_match.status(), 200);
}
#[tokio::test]
async fn should_match_multi_request_header_x() {
// Arrange
let mock_server = MockServer::start().await;
let header_matcher = headers("cache-control", vec!["no-cache", "no-store"]);
let mock = Mock::given(method("GET"))
.and(header_matcher)
.respond_with(ResponseTemplate::new(200))
.expect(1);
mock_server.register(mock).await;
// Act
let should_match = reqwest::Client::new()
.get(mock_server.uri())
// TODO: use a dedicated headers when upgrade reqwest v0.12
.header("cache-control", "no-cache")
.header("cache-control", "no-store")
.send()
.await
.unwrap();
// Assert
assert_eq!(should_match.status(), 200);
}
#[async_std::test]
async fn should_not_match_multi_request_header_upon_wrong_values() {
// Arrange
let mock_server = MockServer::start().await;
let header_matcher = headers("cache-control", vec!["no-cache", "no-store"]);
let mock = Mock::given(method("GET"))
.and(header_matcher)
.respond_with(ResponseTemplate::new(200));
mock_server.register(mock).await;
// Act
let client = reqwest::Client::new();
let should_fail_wrong_values = client
.get(mock_server.uri())
.header("cache-control", "no-cache, no-transform")
.send()
.await
.unwrap();
// Assert
assert_eq!(should_fail_wrong_values.status(), 404);
}
#[async_std::test]
async fn should_not_match_multi_request_header_upon_incomplete_values() {
// Arrange
let mock_server = MockServer::start().await;
let header_matcher = headers("cache-control", vec!["no-cache", "no-store"]);
let mock = Mock::given(method("GET"))
.and(header_matcher)
.respond_with(ResponseTemplate::new(200));
mock_server.register(mock).await;
// Act
let client = reqwest::Client::new();
let should_fail_incomplete_values = client
.get(mock_server.uri())
.header("cache-control", "no-cache")
.send()
.await
.unwrap();
// Assert
assert_eq!(should_fail_incomplete_values.status(), 404);
}
#[async_std::test]
async fn should_match_regex_single_header_value() {
// Arrange
let mock_server = MockServer::start().await;
let mock = Mock::given(method("GET"))
.and(header_regex("cache-control", r"no-(cache|store)"))
.respond_with(ResponseTemplate::new(200));
mock_server.register(mock).await;
let client = reqwest::Client::new();
// Act
let should_match = client
.get(mock_server.uri())
.header("cache-control", "no-cache")
.send()
.await
.unwrap();
// Assert
assert_eq!(should_match.status(), 200);
}
#[async_std::test]
async fn should_match_regex_multiple_header_values() {
// Arrange
let mock_server = MockServer::start().await;
let mock = Mock::given(method("GET"))
.and(header_regex("cache-control", r"no-(cache|store)"))
.respond_with(ResponseTemplate::new(200));
mock_server.register(mock).await;
let client = reqwest::Client::new();
// Act
let should_match = client
.get(mock_server.uri())
.header("cache-control", "no-cache")
.header("cache-control", "no-store")
.send()
.await
.unwrap();
// Assert
assert_eq!(should_match.status(), 200);
}
#[async_std::test]
async fn should_not_match_regex_with_wrong_header_value() {
// Arrange
let mock_server = MockServer::start().await;
let mock = Mock::given(method("GET"))
.and(header_regex("cache-control", r"no-(cache|store)"))
.respond_with(ResponseTemplate::new(200));
mock_server.register(mock).await;
let client = reqwest::Client::new();
// Act
let should_fail_wrong_value = client
.get(mock_server.uri())
.header("cache-control", "no-junk")
.send()
.await
.unwrap();
// Assert
assert_eq!(should_fail_wrong_value.status(), 404);
}
#[async_std::test]
async fn should_not_match_regex_with_at_least_one_wrong_header_value() {
// Arrange
let mock_server = MockServer::start().await;
let mock = Mock::given(method("GET"))
.and(header_regex("cache-control", r"no-(cache|store)"))
.respond_with(ResponseTemplate::new(200));
mock_server.register(mock).await;
let client = reqwest::Client::new();
// Act
let should_fail_wrong_value = client
.get(mock_server.uri())
.header("cache-control", "no-cache")
.header("cache-control", "no-junk")
.send()
.await
.unwrap();
// Assert
assert_eq!(should_fail_wrong_value.status(), 404);
}
#[async_std::test]
async fn should_not_match_regex_with_no_values_for_header() {
// Arrange
let mock_server = MockServer::start().await;
let mock = Mock::given(method("GET"))
.and(header_regex("cache-control", r"no-(cache|store)"))
.respond_with(ResponseTemplate::new(200));
mock_server.register(mock).await;
// Act
let should_fail_wrong_value = reqwest::get(mock_server.uri()).await.unwrap();
// Assert
assert_eq!(should_fail_wrong_value.status(), 404);
}
#[async_std::test]
async fn should_match_basic_auth_header() {
// Arrange
let mock_server = MockServer::start().await;
let mock = Mock::given(method("GET"))
.and(basic_auth("Aladdin", "open sesame"))
.respond_with(ResponseTemplate::new(200));
mock_server.register(mock).await;
let client = reqwest::Client::new();
// Act
let should_match = client
.get(mock_server.uri())
.header("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==")
.send()
.await
.unwrap();
// Assert
assert_eq!(should_match.status(), 200);
}
#[async_std::test]
async fn should_not_match_bad_basic_auth_header() {
// Arrange
let mock_server = MockServer::start().await;
let mock = Mock::given(method("GET"))
.and(basic_auth("Aladdin", "close sesame"))
.respond_with(ResponseTemplate::new(200));
mock_server.register(mock).await;
let client = reqwest::Client::new();
// Act
let should_not_match = client
.get(mock_server.uri())
.header("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==")
.send()
.await
.unwrap();
// Assert
assert_eq!(should_not_match.status(), 404);
}
#[async_std::test]
async fn should_match_bearer_token_header() {
// Arrange
let mock_server = MockServer::start().await;
let mock = Mock::given(method("GET"))
.and(bearer_token("delightful"))
.respond_with(ResponseTemplate::new(200));
mock_server.register(mock).await;
let client = reqwest::Client::new();
// Act
let should_match = client
.get(mock_server.uri())
.header("Authorization", "Bearer delightful")
.send()
.await
.unwrap();
// Assert
assert_eq!(should_match.status(), 200);
}
#[async_std::test]
async fn should_not_match_bearer_token_header() {
// Arrange
let mock_server = MockServer::start().await;
let mock = Mock::given(method("GET"))
.and(bearer_token("expired"))
.respond_with(ResponseTemplate::new(200));
mock_server.register(mock).await;
let client = reqwest::Client::new();
// Act
let should_not_match = client
.get(mock_server.uri())
.header("Authorization", "Bearer delightful")
.send()
.await
.unwrap();
// Assert
assert_eq!(should_not_match.status(), 404);
}

36
vendor/wiremock/tests/timeout.rs vendored Normal file
View File

@@ -0,0 +1,36 @@
use reqwest::Client;
use wiremock::matchers::any;
use wiremock::{Mock, MockServer, ResponseTemplate};
async fn test_body() {
// Arrange
let mock_server = MockServer::start().await;
let response = ResponseTemplate::new(200).set_delay(std::time::Duration::from_secs(60));
Mock::given(any())
.respond_with(response)
.mount(&mock_server)
.await;
// Act
let outcome = Client::builder()
.timeout(std::time::Duration::from_secs(1))
.build()
.unwrap()
.get(&mock_server.uri())
.send()
.await;
// Assert
assert!(outcome.is_err());
}
#[actix_rt::test]
async fn request_times_out_if_the_server_takes_too_long_with_actix() {
test_body().await
}
#[tokio::test]
async fn request_times_out_if_the_server_takes_too_long_with_tokio() {
test_body().await
}

59
vendor/wiremock/tests/tokio.rs vendored Normal file
View File

@@ -0,0 +1,59 @@
use reqwest::Client;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
// regression tests for https://github.com/LukeMathWalker/wiremock-rs/issues/7
// running both tests will _sometimes_ trigger a hang if the runtimes aren't separated correctly
#[tokio::test]
async fn hello_reqwest() {
let mock_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/"))
.respond_with(ResponseTemplate::new(200))
.mount(&mock_server)
.await;
let resp = Client::new().get(&mock_server.uri()).send().await.unwrap();
assert_eq!(resp.status(), 200);
}
#[actix_rt::test]
async fn hello_reqwest_actix() {
let mock_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/"))
.respond_with(ResponseTemplate::new(200))
.mount(&mock_server)
.await;
let resp = Client::new().get(&mock_server.uri()).send().await.unwrap();
assert_eq!(resp.status(), 200);
}
#[tokio::test]
async fn hello_reqwest_http2() {
let mock_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/"))
.respond_with(ResponseTemplate::new(200))
.mount(&mock_server)
.await;
let resp = Client::builder()
.http2_prior_knowledge()
.build()
.expect("http client")
.get(&mock_server.uri())
.send()
.await
.expect("response");
assert_eq!(resp.status(), 200);
assert_eq!(resp.version(), reqwest::Version::HTTP_2);
}