Problem
The auction orchestrator's three timeout enforcement paths are untestable in unit tests:
- Deadline check in
select() loop — drops remaining requests when auction_start.elapsed() >= deadline
- Mediator skip — skips mediator when
remaining_ms == 0 after bidding phase
- Provider skip — skips provider launch when
effective_timeout == 0
The root cause is tight coupling to Fastly's concrete PendingRequest and select():
PendingRequest::new() is pub(super) — cannot be constructed outside the fastly crate
select() is a free function backed by Wasm host ABI calls
- The
AuctionProvider trait's request_bids() returns PendingRequest directly
This means the orchestrator's select loop can only run on Fastly Compute (or Viceroy), never in a plain cargo test.
Suggested approach
Introduce a trait that abstracts over "a set of in-flight requests you can wait on":
/// Result from waiting on a set of pending provider requests.
pub struct ProviderResult {
pub backend_name: String,
pub result: Result<fastly::Response, fastly::http::request::SendError>,
}
/// Abstracts over the mechanism for waiting on concurrent provider requests.
///
/// Production uses Fastly's `select()`; tests inject a mock.
pub trait PendingRequestSet {
/// True when there are no more requests to wait on.
fn is_empty(&self) -> bool;
/// Block until the next request completes and return it.
fn select_next(&mut self) -> ProviderResult;
}
The orchestrator's select loop changes from:
while !remaining.is_empty() {
let (result, rest) = select(remaining);
remaining = rest;
// ...
}
to:
while !pending.is_empty() {
let result = pending.select_next();
// ...
}
A production implementation wraps Vec<PendingRequest> and delegates to fastly::http::request::select(). A test implementation can return pre-canned responses with configurable delays (using std::thread::sleep or instant completion).
This unlocks tests like:
- Provider skip: set
timeout_ms: 0 on context, verify no requests are launched
- Deadline enforcement: mock 3 slow providers, verify the loop breaks after the deadline
- Mediator skip: inject provider responses that consume the full budget, verify mediator is skipped
Scope
- No behavior change — pure structural refactor
- Affected files:
orchestrator.rs, provider.rs (trait may need adjustment), new test mock
- The
AuctionProvider::request_bids() return type may also need abstracting, or the trait boundary can be drawn at the select loop level only (simpler)
Context
Flagged during review of #469 (auction timeout enforcement). See the TODO at crates/common/src/auction/orchestrator.rs:711.
Problem
The auction orchestrator's three timeout enforcement paths are untestable in unit tests:
select()loop — drops remaining requests whenauction_start.elapsed() >= deadlineremaining_ms == 0after bidding phaseeffective_timeout == 0The root cause is tight coupling to Fastly's concrete
PendingRequestandselect():PendingRequest::new()ispub(super)— cannot be constructed outside the fastly crateselect()is a free function backed by Wasm host ABI callsAuctionProvidertrait'srequest_bids()returnsPendingRequestdirectlyThis means the orchestrator's select loop can only run on Fastly Compute (or Viceroy), never in a plain
cargo test.Suggested approach
Introduce a trait that abstracts over "a set of in-flight requests you can wait on":
The orchestrator's select loop changes from:
to:
A production implementation wraps
Vec<PendingRequest>and delegates tofastly::http::request::select(). A test implementation can return pre-canned responses with configurable delays (usingstd::thread::sleepor instant completion).This unlocks tests like:
timeout_ms: 0on context, verify no requests are launchedScope
orchestrator.rs,provider.rs(trait may need adjustment), new test mockAuctionProvider::request_bids()return type may also need abstracting, or the trait boundary can be drawn at the select loop level only (simpler)Context
Flagged during review of #469 (auction timeout enforcement). See the TODO at
crates/common/src/auction/orchestrator.rs:711.