Unify remote compaction snapshot mocks around default endpoint behavior (#12050)

## Summary
- standardize remote compaction test mocking around one default behavior
in shared helpers
- make default remote compact mocks mirror production shape: keep
`message/user` + `message/developer`, drop assistant/tool artifacts,
then append a summary user message
- switch non-special `compact_remote` tests to the shared default mock
instead of ad-hoc JSON payloads

## Special-case tests that still use explicit mocks
- remote compaction error payload / HTTP failure behavior
- summary-only compact output behavior
- manual `/compact` with no prior user messages
- stale developer-instruction injection coverage

## Why
This removes inconsistent manual remote compaction fixtures and gives us
one source of truth for normal remote compact behavior, while preserving
explicit mocks only where tests intentionally cover non-default
behavior.
This commit is contained in:
Charley Cunningham
2026-02-17 18:18:47 -08:00
committed by GitHub
parent db4d2599b5
commit eb68767f2f
7 changed files with 655 additions and 40 deletions

View File

@@ -865,6 +865,88 @@ pub async fn mount_compact_json_once(server: &MockServer, body: serde_json::Valu
.await
}
/// Mount a `/responses/compact` mock that mirrors the default remote compaction shape:
/// keep user+developer messages from the request, drop assistant/tool artifacts, and append one
/// summary user message.
pub async fn mount_compact_user_history_with_summary_once(
server: &MockServer,
summary_text: &str,
) -> ResponseMock {
mount_compact_user_history_with_summary_sequence(server, vec![summary_text.to_string()]).await
}
/// Same as [`mount_compact_user_history_with_summary_once`], but for multiple compact calls.
/// Each incoming compact request receives the next summary text in order.
pub async fn mount_compact_user_history_with_summary_sequence(
server: &MockServer,
summary_texts: Vec<String>,
) -> ResponseMock {
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
#[derive(Debug)]
struct UserHistorySummaryResponder {
num_calls: AtomicUsize,
summary_texts: Vec<String>,
}
impl Respond for UserHistorySummaryResponder {
fn respond(&self, request: &wiremock::Request) -> ResponseTemplate {
let call_num = self.num_calls.fetch_add(1, Ordering::SeqCst);
let Some(summary_text) = self.summary_texts.get(call_num) else {
panic!("no summary text for compact request {call_num}");
};
let body_bytes = decode_body_bytes(
&request.body,
request
.headers
.get("content-encoding")
.and_then(|value| value.to_str().ok()),
);
let body_json: Value = serde_json::from_slice(&body_bytes)
.unwrap_or_else(|err| panic!("failed to parse compact request body: {err}"));
let mut output = body_json
.get("input")
.and_then(Value::as_array)
.cloned()
.unwrap_or_default()
.into_iter()
// Match current remote compaction behavior: keep user/developer messages and
// omit assistant/tool history entries.
.filter(|item| {
item.get("type").and_then(Value::as_str) == Some("message")
&& matches!(
item.get("role").and_then(Value::as_str),
Some("user") | Some("developer")
)
})
.collect::<Vec<Value>>();
// Append the synthetic summary message as the newest user item.
output.push(serde_json::json!({
"type": "message",
"role": "user",
"content": [{"type": "input_text", "text": summary_text}],
}));
ResponseTemplate::new(200)
.insert_header("content-type", "application/json")
.set_body_json(serde_json::json!({ "output": output }))
}
}
let num_calls = summary_texts.len();
let responder = UserHistorySummaryResponder {
num_calls: AtomicUsize::new(0),
summary_texts,
};
let (mock, response_mock) = compact_mock();
mock.respond_with(responder)
.up_to_n_times(num_calls as u64)
.expect(num_calls as u64)
.mount(server)
.await;
response_mock
}
pub async fn mount_compact_response_once(
server: &MockServer,
response: ResponseTemplate,