From b9f8e6ddfded06080d21b77b003477373c393df4 Mon Sep 17 00:00:00 2001 From: Alexander Liesenfeld Date: Sat, 31 Aug 2024 22:35:33 +0200 Subject: [PATCH] Avoid copying Cargo.lock --- Makefile | 2 +- docs/website/generated/example_tests.json | 6 +++--- .../docs/record-and-playback/recording.mdx | 19 +++++++++++++------ .../src/content/docs/server/debugging.mdx | 11 ++++++++--- .../website/src/content/docs/server/https.mdx | 7 +++++-- .../src/content/docs/server/standalone.mdx | 16 ++++++++++------ .../{custom.md => custom.mdx} | 8 +++++++- src/api/adapter/remote.rs | 2 ++ src/common/http.rs | 1 + tools/Cargo.toml | 2 +- 10 files changed, 51 insertions(+), 23 deletions(-) rename docs/website/templates/matching_requests/{custom.md => custom.mdx} (51%) diff --git a/Makefile b/Makefile index f98794a..8430e27 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,7 @@ docs: cd tools && cargo run --bin extract_groups cd tools && cargo run --bin extract_example_tests rm -rf docs/website/generated && cp -r tools/target/generated docs/website/generated - cd docs/website && npm run generate-docs + cd docs/website && npm install && npm run generate-docs .PHONY: fmt diff --git a/docs/website/generated/example_tests.json b/docs/website/generated/example_tests.json index 81a8eea..9d7ce7f 100644 --- a/docs/website/generated/example_tests.json +++ b/docs/website/generated/example_tests.json @@ -1,7 +1,7 @@ { "record-forwarding-github": "#[cfg(all(feature = \"proxy\", feature = \"record\"))]\n#[test]\nfn record_github_api_with_forwarding_test() {\n // Let's create our mock server for the test\n let server = MockServer::start();\n\n // We configure our server to forward the request to the target\n // host instead of answering with a mocked response. The 'when'\n // variable lets you configure rules under which forwarding\n // should take place.\n server.forward_to(\"https://api.github.com\", |rule| {\n rule.filter(|when| {\n when.any_request(); // Ensure all requests are forwarded.\n });\n });\n\n let recording = server.record(|rule| {\n rule\n // Specify which headers to record.\n // Only the headers listed here will be captured and stored\n // as part of the recorded mock. This selective recording is\n // necessary because some headers may vary between requests\n // and could cause issues when replaying the mock later.\n // For instance, headers like 'Authorization' or 'Date' may\n // change with each request.\n .record_request_header(\"User-Agent\")\n .filter(|when| {\n when.any_request(); // Ensure all requests are recorded.\n });\n });\n\n // Now let's send an HTTP request to the mock server. The request\n // will be forwarded to the GitHub API, as we configured before.\n let client = Client::new();\n\n let response = client\n .get(server.url(\"/repos/torvalds/linux\"))\n // GitHub requires us to send a user agent header\n .header(\"User-Agent\", \"httpmock-test\")\n .send()\n .unwrap();\n\n // Since the request was forwarded, we should see a GitHub API response.\n assert_eq!(response.status().as_u16(), 200);\n assert_eq!(true, response.text().unwrap().contains(\"\\\"private\\\":false\"));\n\n // Save the recording to\n // \"target/httpmock/recordings/github-torvalds-scenario_.yaml\".\n recording\n .save(\"github-torvalds-scenario\")\n .expect(\"cannot store scenario on disk\");\n}", - "playback-forwarding-github": "#[cfg(all(feature = \"proxy\", feature = \"record\"))]\n#[test]\nfn playback_github_api() {\n // Start a mock server for the test\n let server = MockServer::start();\n\n // Configure the mock server to forward requests to the target\n // host (GitHub API) instead of responding with a mock. The 'rule'\n // parameter allows you to define conditions under which forwarding\n // should occur.\n server.forward_to(\"https://api.github.com\", |rule| {\n rule.filter(|when| {\n when.any_request(); // Forward all requests.\n });\n });\n\n // Set up recording to capture all forwarded requests and responses\n let recording = server.record(|rule| {\n rule.filter(|when| {\n when.any_request(); // Record all requests and responses.\n });\n });\n\n // Send an HTTP request to the mock server, which will be forwarded\n // to the GitHub API\n let client = Client::new();\n let response = client\n .get(server.url(\"/repos/torvalds/linux\"))\n // GitHub requires a User-Agent header\n .header(\"User-Agent\", \"httpmock-test\")\n .send()\n .unwrap();\n\n // Assert that the response from the forwarded request is as expected\n assert_eq!(response.status().as_u16(), 200);\n assert!(response.text().unwrap().contains(\"\\\"private\\\":false\"));\n\n // Save the recorded interactions to a file\n let target_path = recording\n .save(\"github-torvalds-scenario\")\n .expect(\"Failed to save the recording to disk\");\n\n // Start a new mock server instance for playback\n let playback_server = MockServer::start();\n\n // Load the recorded interactions into the new mock server\n playback_server.playback(target_path);\n\n // Send a request to the playback server and verify the response\n // matches the recorded data\n let response = client\n .get(playback_server.url(\"/repos/torvalds/linux\"))\n .send()\n .unwrap();\n assert_eq!(response.status().as_u16(), 200);\n assert!(response.text().unwrap().contains(\"\\\"private\\\":false\"));\n}", - "record-proxy-github": "#[cfg(all(feature = \"proxy\", feature = \"record\"))]\n#[test]\nfn record_with_proxy_test() {\n // Start a mock server to act as a proxy for the HTTP client\n let server = MockServer::start();\n\n // Configure the mock server to proxy all incoming requests\n server.proxy(|rule| {\n rule.filter(|when| {\n when.any_request(); // Intercept all requests\n });\n });\n\n // Set up recording on the mock server to capture all proxied\n // requests and responses\n let recording = server.record(|rule| {\n rule.filter(|when| {\n when.any_request(); // Record all requests\n });\n });\n\n // Create an HTTP client configured to route requests\n // through the mock proxy server\n let github_client = Client::builder()\n // Set the proxy URL to the mock server's URL\n .proxy(reqwest::Proxy::all(server.base_url()).unwrap())\n .build()\n .unwrap();\n\n // Send a GET request using the client, which will be proxied by the mock server\n let response = github_client.get(server.base_url()).send().unwrap();\n\n // Verify that the response matches the expected mock response\n assert_eq!(response.text().unwrap(), \"This is a mock response\");\n\n // Save the recorded HTTP interactions to a file for future reference or testing\n recording\n .save(\"my_scenario_name\")\n .expect(\"could not save the recording\");\n}", + "record-proxy-github": "#[cfg(all(feature = \"proxy\", feature = \"record\", feature = \"experimental\"))]\n#[test]\nfn record_with_proxy_test() {\n // Start a mock server to act as a proxy for the HTTP client\n let server = MockServer::start();\n\n // Configure the mock server to proxy all incoming requests\n server.proxy(|rule| {\n rule.filter(|when| {\n when.any_request(); // Intercept all requests\n });\n });\n\n // Set up recording on the mock server to capture all proxied\n // requests and responses\n let recording = server.record(|rule| {\n rule.filter(|when| {\n when.any_request(); // Record all requests\n });\n });\n\n // Create an HTTP client configured to route requests\n // through the mock proxy server\n let github_client = Client::builder()\n // Set the proxy URL to the mock server's URL\n .proxy(reqwest::Proxy::all(server.base_url()).unwrap())\n .build()\n .unwrap();\n\n // Send a GET request using the client, which will be proxied by the mock server\n let response = github_client.get(server.base_url()).send().unwrap();\n\n // Verify that the response matches the expected mock response\n assert_eq!(response.text().unwrap(), \"This is a mock response\");\n\n // Save the recorded HTTP interactions to a file for future reference or testing\n recording\n .save(\"my_scenario_name\")\n .expect(\"could not save the recording\");\n}", "forwarding-github": "#[cfg(feature = \"proxy\")]\n#[test]\nfn forward_to_github_test() {\n // Let's create our mock server for the test\n let server = MockServer::start();\n\n // We configure our server to forward the request to the target\n // host instead of answering with a mocked response. The 'when'\n // variable lets you configure rules under which forwarding\n // should take place.\n server.forward_to(\"https://api.github.com\", |rule| {\n rule.filter(|when| {\n when.any_request(); // Ensure all requests are forwarded.\n });\n });\n\n // Now let's send an HTTP request to the mock server. The request\n // will be forwarded to the GitHub API, as we configured before.\n let client = Client::new();\n\n let response = client\n .get(server.url(\"/repos/torvalds/linux\"))\n // GitHub requires us to send a user agent header\n .header(\"User-Agent\", \"httpmock-test\")\n .send()\n .unwrap();\n\n // Since the request was forwarded, we should see a GitHub API response.\n assert_eq!(response.status().as_u16(), 200);\n assert_eq!(true, response.text().unwrap().contains(\"\\\"private\\\":false\"));\n}", - "forwarding": "#[cfg(feature = \"proxy\")]\n#[test]\nfn forwarding_test() {\n // We will create this mock server to simulate a real service (e.g., GitHub, AWS, etc.).\n let target_server = MockServer::start();\n target_server.mock(|when, then| {\n when.any_request();\n then.status(200).body(\"Hi from fake GitHub!\");\n });\n\n // Let's create our mock server for the test\n let server = MockServer::start();\n\n // We configure our server to forward the request to the target host instead of\n // answering with a mocked response. The 'when' variable lets you configure\n // rules under which forwarding should take place.\n server.forward_to(target_server.base_url(), |rule| {\n rule.filter(|when| {\n when.any_request(); // We want all requests to be forwarded.\n });\n });\n\n // Now let's send an HTTP request to the mock server. The request will be forwarded\n // to the target host, as we configured before.\n let client = Client::new();\n\n // Since the request was forwarded, we should see the target host's response.\n let response = client.get(server.url(\"/get\")).send().unwrap();\n assert_eq!(response.status().as_u16(), 200);\n assert_eq!(response.text().unwrap(), \"Hi from fake GitHub!\");\n}" + "forwarding": "#[cfg(feature = \"proxy\")]\n#[test]\nfn forwarding_test() {\n // We will create this mock server to simulate a real service (e.g., GitHub, AWS, etc.).\n let target_server = MockServer::start();\n target_server.mock(|when, then| {\n when.any_request();\n then.status(200).body(\"Hi from fake GitHub!\");\n });\n\n // Let's create our mock server for the test\n let server = MockServer::start();\n\n // We configure our server to forward the request to the target host instead of\n // answering with a mocked response. The 'when' variable lets you configure\n // rules under which forwarding should take place.\n server.forward_to(target_server.base_url(), |rule| {\n rule.filter(|when| {\n when.any_request(); // We want all requests to be forwarded.\n });\n });\n\n // Now let's send an HTTP request to the mock server. The request will be forwarded\n // to the target host, as we configured before.\n let client = Client::new();\n\n // Since the request was forwarded, we should see the target host's response.\n let response = client.get(server.url(\"/get\")).send().unwrap();\n assert_eq!(response.status().as_u16(), 200);\n assert_eq!(response.text().unwrap(), \"Hi from fake GitHub!\");\n}", + "playback-forwarding-github": "#[cfg(all(feature = \"proxy\", feature = \"record\"))]\n#[test]\nfn playback_github_api() {\n // Start a mock server for the test\n let server = MockServer::start();\n\n // Configure the mock server to forward requests to the target\n // host (GitHub API) instead of responding with a mock. The 'rule'\n // parameter allows you to define conditions under which forwarding\n // should occur.\n server.forward_to(\"https://api.github.com\", |rule| {\n rule.filter(|when| {\n when.any_request(); // Forward all requests.\n });\n });\n\n // Set up recording to capture all forwarded requests and responses\n let recording = server.record(|rule| {\n rule.filter(|when| {\n when.any_request(); // Record all requests and responses.\n });\n });\n\n // Send an HTTP request to the mock server, which will be forwarded\n // to the GitHub API\n let client = Client::new();\n let response = client\n .get(server.url(\"/repos/torvalds/linux\"))\n // GitHub requires a User-Agent header\n .header(\"User-Agent\", \"httpmock-test\")\n .send()\n .unwrap();\n\n // Assert that the response from the forwarded request is as expected\n assert_eq!(response.status().as_u16(), 200);\n assert!(response.text().unwrap().contains(\"\\\"private\\\":false\"));\n\n // Save the recorded interactions to a file\n let target_path = recording\n .save(\"github-torvalds-scenario\")\n .expect(\"Failed to save the recording to disk\");\n\n // Start a new mock server instance for playback\n let playback_server = MockServer::start();\n\n // Load the recorded interactions into the new mock server\n playback_server.playback(target_path);\n\n // Send a request to the playback server and verify the response\n // matches the recorded data\n let response = client\n .get(playback_server.url(\"/repos/torvalds/linux\"))\n .send()\n .unwrap();\n assert_eq!(response.status().as_u16(), 200);\n assert!(response.text().unwrap().contains(\"\\\"private\\\":false\"));\n}" } \ No newline at end of file diff --git a/docs/website/src/content/docs/record-and-playback/recording.mdx b/docs/website/src/content/docs/record-and-playback/recording.mdx index d46252d..e8c1ba1 100644 --- a/docs/website/src/content/docs/record-and-playback/recording.mdx +++ b/docs/website/src/content/docs/record-and-playback/recording.mdx @@ -5,6 +5,7 @@ description: Explains how requests can be recorded. import codeExamples from "../../../../generated/example_tests.json" import { Code } from '@astrojs/starlight/components'; +import { Aside } from '@astrojs/starlight/components'; `httpmock` provides functionality to record both requests to third-party services and their responses. There are two strategies how you can achieve that: Forwarding and Proxy. @@ -34,8 +35,10 @@ server.forward_to("https://github.com", |rule| { }); ``` -> Hint: If no forwarding rule matches a request, the mock server reverts to its standard mocking strategy and attempts -to serve a configured mock response. + You can use the forwarding functionality to record requests sent to the remote service. @@ -48,8 +51,10 @@ the responses it returns. ## Proxy Strategy -> Note: This feature is currently **unstable** and is available only under the `experimental` feature -flag. There is **no guarantee** that it will be included in a future stable release. + The proxy feature in `httpmock`, while functional on its own, is particularly useful for recording in scenarios where modifying or injecting the base URL used by the client is not possible. @@ -73,8 +78,10 @@ The proxy server then tunnels or forwards the request to the target host, which When configured as a proxy, `httpmock` can intercept, record, and forward both requests and responses. -> Hint: If no proxy rule matches a request, the mock server reverts to its standard mocking strategy and attempts -to serve a configured mock response. + ### Full Example diff --git a/docs/website/src/content/docs/server/debugging.mdx b/docs/website/src/content/docs/server/debugging.mdx index 6d106d5..d2944fd 100644 --- a/docs/website/src/content/docs/server/debugging.mdx +++ b/docs/website/src/content/docs/server/debugging.mdx @@ -2,6 +2,7 @@ title: Debugging description: Describes what features are available to make debugging easier. --- +import { Aside } from '@astrojs/starlight/components'; ## Test Failure Output @@ -75,7 +76,11 @@ This output is useful for investigating issues, like figuring out why a request The debug log level is usually the most helpful, but you can use trace to get even more details. -> **Note**: To see the log output during test execution, add the `--nocapture` argument when running your tests. + -> **Hint**: If you're using the `env_logger` backend, set the `RUST_LOG` environment variable to `httpmock=debug` -to see `httpmock` logs. \ No newline at end of file + diff --git a/docs/website/src/content/docs/server/https.mdx b/docs/website/src/content/docs/server/https.mdx index a4f8676..4b70e3b 100644 --- a/docs/website/src/content/docs/server/https.mdx +++ b/docs/website/src/content/docs/server/https.mdx @@ -2,9 +2,12 @@ title: HTTPS description: Describes how the mock server can be configured to support HTTPS --- +import { Aside } from '@astrojs/starlight/components'; -> **Note**: This feature is currently **unstable** and there is no guarantee it will be included in a future stable release. -There is progress on stabilizing it, but at the moment, HTTPS **does not work with the proxy feature**. + By default, `httpmock` does not enable HTTPS support for testing. However, you can enable it on demand by using the Cargo feature `https`. When this feature is enabled, `httpmock` automatically uses HTTPS for all internal communication diff --git a/docs/website/src/content/docs/server/standalone.mdx b/docs/website/src/content/docs/server/standalone.mdx index 9ab0899..de1cfdb 100644 --- a/docs/website/src/content/docs/server/standalone.mdx +++ b/docs/website/src/content/docs/server/standalone.mdx @@ -2,6 +2,7 @@ title: Standalone Server description: Describes how to set up and use a standalone mock server. --- +import { Aside } from '@astrojs/starlight/components'; You can use `httpmock` to run a standalone mock server in a separate process, such as a Docker container. This setup allows the mock server to be accessible to multiple applications, not just within your Rust tests. @@ -82,9 +83,10 @@ This means that test functions may be blocked when connecting to the remote serv This is in contrast to tests that use a local mock server where parallel test execution is possible, because each test uses its own mock server. -> **Note**: Sequential execution is only enforced on a per-process basis. This means that if multiple test runs or -applications use the remote server simultaneously, interference may still occur. - + ## Usage Without Rust @@ -108,9 +110,11 @@ then: json_body: '{ "status" : "created" }' ``` -> **Note**: Defining mocks with YAML files is straightforward because the field names directly match the corresponding -methods in the Rust API, found in the [`When`](https://docs.rs/httpmock/latest/httpmock/struct.When.html) or -[`Then`](https://docs.rs/httpmock/latest/httpmock/struct.Then.html) data structures. + Please refer to [this example file](https://github.com/alexliesenfeld/httpmock/blob/master/tests/resources/static_yaml_mock.yaml), which includes many of the usable fields. \ No newline at end of file diff --git a/docs/website/templates/matching_requests/custom.md b/docs/website/templates/matching_requests/custom.mdx similarity index 51% rename from docs/website/templates/matching_requests/custom.md rename to docs/website/templates/matching_requests/custom.mdx index 35997f0..69dda4d 100644 --- a/docs/website/templates/matching_requests/custom.md +++ b/docs/website/templates/matching_requests/custom.mdx @@ -3,10 +3,16 @@ title: Request Matchers description: Using request matchers to specify which requests should respond. TODO --- +import { Aside } from '@astrojs/starlight/components'; + This section describes matcher functions that enable developers to implement custom matchers. These matchers execute user-defined code to determine if a request meets specific criteria. -> **Attention:** Custom matchers are **not available** when connecting to standalone mock servers (e.g., by using one of the `connect` methods, such as [`MockServer::connect`](https://docs.rs/httpmock/0.7.0/httpmock/struct.MockServer.html#method.connect)). + ## is_true {{{docs.when.is_true}}} diff --git a/src/api/adapter/remote.rs b/src/api/adapter/remote.rs index 8fecf89..3f079db 100644 --- a/src/api/adapter/remote.rs +++ b/src/api/adapter/remote.rs @@ -506,6 +506,7 @@ impl MockServerAdapter for RemoteMockServerAdapter { Ok(()) } + #[cfg(feature = "record")] async fn export_recording(&self, id: usize) -> Result, ServerAdapterError> { let request = Request::builder() .method("GET") @@ -531,6 +532,7 @@ impl MockServerAdapter for RemoteMockServerAdapter { Ok(Some(body)) } + #[cfg(feature = "record")] async fn create_mocks_from_recording<'a>( &self, recording_file_content: &'a str, diff --git a/src/common/http.rs b/src/common/http.rs index d3a4ac4..6501af9 100644 --- a/src/common/http.rs +++ b/src/common/http.rs @@ -2,6 +2,7 @@ use async_trait::async_trait; use bytes::Bytes; use http::{Request, Response}; use http_body_util::{BodyExt, Full}; +#[cfg(any(feature = "remote-https", feature = "https"))] use hyper_rustls::HttpsConnector; use hyper_util::{ client::legacy::{connect::HttpConnector, Client}, diff --git a/tools/Cargo.toml b/tools/Cargo.toml index 838dde3..60d415a 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2018" [dependencies] -httpmock = { path = ".." , features = ["full"]} +httpmock = { path = ".." , features = ["default", "standalone", "color", "cookies", "remote", "remote-https", "proxy", "https", "http2", "record", "experimental"]} serde_json = "1.0" syn = { version = "1.0", features = ["full"] } proc-macro2 = { version = "1.0", features = ["default", "span-locations"] }