use matcher

This commit is contained in:
Ahmed Ibrahim
2026-01-07 13:24:36 -08:00
parent 187924d761
commit eded1864b6
7 changed files with 280 additions and 57 deletions

View File

@@ -701,33 +701,6 @@ pub async fn start_mock_server() -> MockServer {
server
}
// todo(aibrahim): remove this and use our search matching patterns directly
/// Get all POST requests to `/responses` endpoints from the mock server.
/// Filters out GET requests (e.g., `/models`) .
pub async fn get_responses_requests(server: &MockServer) -> Vec<wiremock::Request> {
server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| req.method == "POST" && req.url.path().ends_with("/responses"))
.collect()
}
// todo(aibrahim): remove this and use our search matching patterns directly
/// Get request bodies as JSON values from POST requests to `/responses` endpoints.
/// Filters out GET requests (e.g., `/models`) .
pub async fn get_responses_request_bodies(server: &MockServer) -> Vec<Value> {
get_responses_requests(server)
.await
.into_iter()
.map(|req| {
req.body_json::<Value>()
.expect("request body to be valid JSON")
})
.collect()
}
#[derive(Clone)]
pub struct FunctionCallResponseMocks {
pub function_call: ResponseMock,

View File

@@ -23,10 +23,12 @@ use tempfile::TempDir;
use wiremock::MockServer;
use crate::load_default_config_for_test;
use crate::responses::get_responses_request_bodies;
use crate::responses::start_mock_server;
use crate::streaming_sse::StreamingSseServer;
use crate::wait_for_event;
use wiremock::Match;
use wiremock::matchers::method;
use wiremock::matchers::path_regex;
type ConfigMutator = dyn FnOnce(&mut Config) + Send;
type PreBuildHook = dyn FnOnce(&Path) + Send + 'static;
@@ -322,7 +324,19 @@ impl TestCodexHarness {
}
pub async fn request_bodies(&self) -> Vec<Value> {
get_responses_request_bodies(&self.server).await
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
self.server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.map(|req| {
req.body_json::<Value>()
.expect("request body to be valid JSON")
})
.collect()
}
pub async fn function_call_output_value(&self, call_id: &str) -> Value {

View File

@@ -31,7 +31,6 @@ use codex_protocol::user_input::UserInput;
use core_test_support::load_default_config_for_test;
use core_test_support::load_sse_fixture_with_id;
use core_test_support::responses::ev_completed_with_tokens;
use core_test_support::responses::get_responses_requests;
use core_test_support::responses::mount_sse_once;
use core_test_support::responses::mount_sse_once_match;
use core_test_support::responses::sse;
@@ -47,6 +46,7 @@ use std::io::Write;
use std::sync::Arc;
use tempfile::TempDir;
use uuid::Uuid;
use wiremock::Match;
use wiremock::Mock;
use wiremock::MockServer;
use wiremock::ResponseTemplate;
@@ -54,6 +54,7 @@ use wiremock::matchers::body_string_contains;
use wiremock::matchers::header_regex;
use wiremock::matchers::method;
use wiremock::matchers::path;
use wiremock::matchers::path_regex;
use wiremock::matchers::query_param;
/// Build minimal SSE stream with completed marker using the JSON fixture.
@@ -374,7 +375,15 @@ async fn includes_conversation_id_and_model_headers_in_request() {
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
// get request from the server
let requests = get_responses_requests(&server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let requests = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.collect::<Vec<_>>();
let request = requests
.first()
.expect("expected POST request to /responses");
@@ -500,7 +509,15 @@ async fn chatgpt_auth_sends_correct_request() {
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
// get request from the server
let requests = get_responses_requests(&server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let requests = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.collect::<Vec<_>>();
let request = requests
.first()
.expect("expected POST request to /responses");
@@ -1233,7 +1250,15 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() {
}
}
let requests = get_responses_requests(&server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let requests = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.collect::<Vec<_>>();
assert_eq!(requests.len(), 1, "expected a single POST request");
let body: serde_json::Value = requests[0]
.body_json()
@@ -1847,7 +1872,15 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() {
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
// Inspect the three captured requests.
let requests = get_responses_requests(&server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let requests = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.collect::<Vec<_>>();
assert_eq!(requests.len(), 3, "expected 3 requests (one per turn)");
// Replace full-array compare with tail-only raw JSON compare using a single hard-coded value.

View File

@@ -31,7 +31,6 @@ use core_test_support::responses::ev_assistant_message;
use core_test_support::responses::ev_completed;
use core_test_support::responses::ev_completed_with_tokens;
use core_test_support::responses::ev_function_call;
use core_test_support::responses::get_responses_requests;
use core_test_support::responses::mount_compact_json_once;
use core_test_support::responses::mount_sse_once;
use core_test_support::responses::mount_sse_once_match;
@@ -41,7 +40,10 @@ use core_test_support::responses::sse_failed;
use core_test_support::responses::start_mock_server;
use pretty_assertions::assert_eq;
use serde_json::json;
use wiremock::Match;
use wiremock::MockServer;
use wiremock::matchers::method;
use wiremock::matchers::path_regex;
// --- Test helpers -----------------------------------------------------------
pub(super) const FIRST_REPLY: &str = "FIRST_REPLY";
@@ -358,7 +360,15 @@ async fn manual_compact_uses_custom_prompt() {
assert_eq!(message, COMPACT_WARNING_MESSAGE);
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
let requests = get_responses_requests(&server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let requests = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.collect::<Vec<_>>();
let body = requests
.iter()
.find_map(|req| req.body_json::<serde_json::Value>().ok())
@@ -586,7 +596,15 @@ async fn multiple_auto_compact_per_task_runs_after_token_limit_hit() {
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
// collect the requests payloads from the model
let requests_payloads = get_responses_requests(&server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let requests_payloads = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.collect::<Vec<_>>();
let body = requests_payloads[0]
.body_json::<serde_json::Value>()
@@ -1111,7 +1129,15 @@ async fn auto_compact_runs_after_token_limit_hit() {
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
let requests = get_responses_requests(&server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let requests = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.collect::<Vec<_>>();
assert_eq!(
requests.len(),
4,
@@ -1897,7 +1923,15 @@ async fn auto_compact_allows_multiple_attempts_when_interleaved_with_other_turn_
"auto compact should not emit task lifecycle events"
);
let requests = get_responses_requests(&server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let requests = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.collect::<Vec<_>>();
let request_bodies: Vec<String> = requests
.into_iter()
.map(|request| String::from_utf8(request.body).unwrap_or_default())

View File

@@ -26,7 +26,6 @@ use codex_protocol::user_input::UserInput;
use core_test_support::load_default_config_for_test;
use core_test_support::responses::ev_assistant_message;
use core_test_support::responses::ev_completed;
use core_test_support::responses::get_responses_request_bodies;
use core_test_support::responses::mount_sse_once_match;
use core_test_support::responses::sse;
use core_test_support::wait_for_event;
@@ -35,7 +34,10 @@ use serde_json::Value;
use serde_json::json;
use std::sync::Arc;
use tempfile::TempDir;
use wiremock::Match;
use wiremock::MockServer;
use wiremock::matchers::method;
use wiremock::matchers::path_regex;
const AFTER_SECOND_RESUME: &str = "AFTER_SECOND_RESUME";
@@ -772,7 +774,19 @@ fn normalize_line_endings(value: &mut Value) {
}
async fn gather_request_bodies(server: &MockServer) -> Vec<Value> {
let mut bodies = get_responses_request_bodies(server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let mut bodies = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.map(|req| {
req.body_json::<Value>()
.expect("request body to be valid JSON")
})
.collect::<Vec<_>>();
for body in &mut bodies {
normalize_line_endings(body);
}

View File

@@ -23,7 +23,6 @@ use codex_core::review_format::render_review_output_text;
use codex_protocol::user_input::UserInput;
use core_test_support::load_default_config_for_test;
use core_test_support::load_sse_fixture_with_id_from_str;
use core_test_support::responses::get_responses_requests;
use core_test_support::skip_if_no_network;
use core_test_support::wait_for_event;
use pretty_assertions::assert_eq;
@@ -32,11 +31,13 @@ use std::sync::Arc;
use tempfile::TempDir;
use tokio::io::AsyncWriteExt as _;
use uuid::Uuid;
use wiremock::Match;
use wiremock::Mock;
use wiremock::MockServer;
use wiremock::ResponseTemplate;
use wiremock::matchers::method;
use wiremock::matchers::path;
use wiremock::matchers::path_regex;
/// Verify that submitting `Op::Review` spawns a child task and emits
/// EnteredReviewMode -> ExitedReviewMode(None) -> TaskComplete
@@ -426,7 +427,15 @@ async fn review_uses_custom_review_model_from_config() {
let _complete = wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
// Assert the request body model equals the configured review model
let requests = get_responses_requests(&server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let requests = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.collect::<Vec<_>>();
let request = requests
.first()
.expect("expected POST request to /responses");
@@ -547,7 +556,15 @@ async fn review_input_isolated_from_parent_history() {
let _complete = wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
// Assert the request `input` contains the environment context followed by the user review prompt.
let requests = get_responses_requests(&server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let requests = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.collect::<Vec<_>>();
let request = requests
.first()
.expect("expected POST request to /responses");
@@ -674,7 +691,15 @@ async fn review_history_surfaces_in_parent_session() {
// Inspect the second request (parent turn) input contents.
// Parent turns include session initial messages (user_instructions, environment_context).
// Critically, no messages from the review thread should appear.
let requests = get_responses_requests(&server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let requests = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.collect::<Vec<_>>();
assert_eq!(requests.len(), 2);
let body = requests[1].body_json::<serde_json::Value>().unwrap();
let input = body["input"].as_array().expect("input array");
@@ -792,7 +817,15 @@ async fn review_uses_overridden_cwd_for_base_branch_merge_base() {
let _entered = wait_for_event(&codex, |ev| matches!(ev, EventMsg::EnteredReviewMode(_))).await;
let _complete = wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
let requests = get_responses_requests(&server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let requests = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.collect::<Vec<_>>();
assert_eq!(requests.len(), 1);
let body = requests[0].body_json::<serde_json::Value>().unwrap();
let input = body["input"].as_array().expect("input array");

View File

@@ -20,7 +20,6 @@ use core_test_support::responses::ev_assistant_message;
use core_test_support::responses::ev_completed;
use core_test_support::responses::ev_function_call;
use core_test_support::responses::ev_response_created;
use core_test_support::responses::get_responses_request_bodies;
use core_test_support::responses::mount_sse_sequence;
use core_test_support::responses::sse;
use core_test_support::responses::start_mock_server;
@@ -38,6 +37,9 @@ use regex_lite::Regex;
use serde_json::Value;
use serde_json::json;
use tokio::time::Duration;
use wiremock::Match;
use wiremock::matchers::method;
use wiremock::matchers::path_regex;
fn extract_output_text(item: &Value) -> Option<&str> {
item.get("output").and_then(|value| match value {
@@ -1241,7 +1243,19 @@ async fn exec_command_reports_chunk_and_exit_metadata() -> Result<()> {
let requests = server.received_requests().await.expect("recorded requests");
assert!(!requests.is_empty(), "expected at least one POST request");
let bodies = get_responses_request_bodies(&server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let bodies = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.map(|req| {
req.body_json::<Value>()
.expect("request body to be valid JSON")
})
.collect::<Vec<_>>();
let outputs = collect_tool_outputs(&bodies)?;
let metadata = outputs
@@ -1345,7 +1359,19 @@ async fn unified_exec_respects_early_exit_notifications() -> Result<()> {
let requests = server.received_requests().await.expect("recorded requests");
assert!(!requests.is_empty(), "expected at least one POST request");
let bodies = get_responses_request_bodies(&server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let bodies = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.map(|req| {
req.body_json::<Value>()
.expect("request body to be valid JSON")
})
.collect::<Vec<_>>();
let outputs = collect_tool_outputs(&bodies)?;
let output = outputs
@@ -1470,7 +1496,19 @@ async fn write_stdin_returns_exit_metadata_and_clears_session() -> Result<()> {
let requests = server.received_requests().await.expect("recorded requests");
assert!(!requests.is_empty(), "expected at least one POST request");
let bodies = get_responses_request_bodies(&server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let bodies = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.map(|req| {
req.body_json::<Value>()
.expect("request body to be valid JSON")
})
.collect::<Vec<_>>();
let outputs = collect_tool_outputs(&bodies)?;
@@ -1824,7 +1862,19 @@ async fn unified_exec_reuses_session_via_stdin() -> Result<()> {
let requests = server.received_requests().await.expect("recorded requests");
assert!(!requests.is_empty(), "expected at least one POST request");
let bodies = get_responses_request_bodies(&server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let bodies = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.map(|req| {
req.body_json::<Value>()
.expect("request body to be valid JSON")
})
.collect::<Vec<_>>();
let outputs = collect_tool_outputs(&bodies)?;
@@ -1958,7 +2008,19 @@ PY
let requests = server.received_requests().await.expect("recorded requests");
assert!(!requests.is_empty(), "expected at least one POST request");
let bodies = get_responses_request_bodies(&server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let bodies = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.map(|req| {
req.body_json::<Value>()
.expect("request body to be valid JSON")
})
.collect::<Vec<_>>();
let outputs = collect_tool_outputs(&bodies)?;
@@ -2067,7 +2129,19 @@ async fn unified_exec_timeout_and_followup_poll() -> Result<()> {
let requests = server.received_requests().await.expect("recorded requests");
assert!(!requests.is_empty(), "expected at least one POST request");
let bodies = get_responses_request_bodies(&server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let bodies = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.map(|req| {
req.body_json::<Value>()
.expect("request body to be valid JSON")
})
.collect::<Vec<_>>();
let outputs = collect_tool_outputs(&bodies)?;
@@ -2153,7 +2227,19 @@ PY
let requests = server.received_requests().await.expect("recorded requests");
assert!(!requests.is_empty(), "expected at least one POST request");
let bodies = get_responses_request_bodies(&server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let bodies = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.map(|req| {
req.body_json::<Value>()
.expect("request body to be valid JSON")
})
.collect::<Vec<_>>();
let outputs = collect_tool_outputs(&bodies)?;
let large_output = outputs.get(call_id).expect("missing large output summary");
@@ -2230,7 +2316,19 @@ async fn unified_exec_runs_under_sandbox() -> Result<()> {
let requests = server.received_requests().await.expect("recorded requests");
assert!(!requests.is_empty(), "expected at least one POST request");
let bodies = get_responses_request_bodies(&server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let bodies = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.map(|req| {
req.body_json::<Value>()
.expect("request body to be valid JSON")
})
.collect::<Vec<_>>();
let outputs = collect_tool_outputs(&bodies)?;
let output = outputs.get(call_id).expect("missing output");
@@ -2328,7 +2426,19 @@ async fn unified_exec_python_prompt_under_seatbelt() -> Result<()> {
let requests = server.received_requests().await.expect("recorded requests");
assert!(!requests.is_empty(), "expected at least one POST request");
let bodies = get_responses_request_bodies(&server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let bodies = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.map(|req| {
req.body_json::<Value>()
.expect("request body to be valid JSON")
})
.collect::<Vec<_>>();
let outputs = collect_tool_outputs(&bodies)?;
let startup_output = outputs
@@ -2418,7 +2528,19 @@ async fn unified_exec_runs_on_all_platforms() -> Result<()> {
let requests = server.received_requests().await.expect("recorded requests");
assert!(!requests.is_empty(), "expected at least one POST request");
let bodies = get_responses_request_bodies(&server).await;
let method_matcher = method("POST");
let path_matcher = path_regex(".*/responses$");
let bodies = server
.received_requests()
.await
.expect("mock server should not fail")
.into_iter()
.filter(|req| method_matcher.matches(req) && path_matcher.matches(req))
.map(|req| {
req.body_json::<Value>()
.expect("request body to be valid JSON")
})
.collect::<Vec<_>>();
let outputs = collect_tool_outputs(&bodies)?;
let output = outputs.get(call_id).expect("missing output");