// A Transport that answers from a script instead of the network.
//
// This is public on purpose: it is how this library's own tests run without a
// network, and downstream code needs the same thing to test against Exa.
///|
/// The mock ran out of canned responses.
pub(all) suberror MockTransportExhausted {
MockTransportExhausted(String)
} derive(@debug.Debug)
///|
/// A `Transport` that returns pre-canned responses and records what it was
/// asked to send.
pub struct MockTransport {
queue : Array[HttpResponse]
seen : Array[HttpRequest]
mut position : Int
}
///|
/// A mock that answers with `responses` in order.
pub fn MockTransport::new(responses : Array[HttpResponse]) -> MockTransport {
{ queue: responses, seen: [], position: 0, }
}
///|
/// A mock that answers a single request with `body` as a JSON payload.
pub fn MockTransport::json(body : String, status? : Int = 200) -> MockTransport {
MockTransport::new([
{ status, headers: [("content-type", "application/json")], body, },
])
}
///|
/// Every request the mock has been asked to send, in order.
pub fn MockTransport::requests(self : MockTransport) -> Array[HttpRequest] {
self.seen
}
///|
/// The most recent request, or `None` if nothing was sent yet.
pub fn MockTransport::last_request(self : MockTransport) -> HttpRequest? {
if self.seen.is_empty() {
None
} else {
Some(self.seen[self.seen.length() - 1])
}
}
///|
/// The body of the most recent request, re-parsed as JSON. Handy for asserting
/// on what got serialised.
pub fn MockTransport::last_body(self : MockTransport) -> Json? {
self
.last_request()
.bind(fn(request) {
try @json.parse(request.body) catch {
_ => return None
} noraise {
json => Some(json)
}
})
}
///|
pub impl Transport for MockTransport with fn send(self, request) {
self.seen.push(request)
if self.position >= self.queue.length() {
raise MockTransportExhausted(
"no canned response left for \{request.meth} \{request.url}",
)
}
let response = self.queue[self.position]
self.position += 1
response
}