///|
/// One step of a scripted transport. Constructed by callers (or tests) to
/// script a `FakeTransport`.
#warnings("-unused_constructor")
pub(all) enum FakeStep {
/// The next write must produce exactly these bytes.
Expect(Bytes)
/// The next read returns these bytes.
Recv(Bytes)
/// The next write is accepted without assertion and this response is
/// returned by the following read.
Respond(Bytes)
/// The next transport operation fails with this error.
Fail(LdapError)
/// The next read reports a closed connection.
Close
} derive(@debug.Debug)
///|
/// A fully scripted transport for deterministic, offline tests. It verifies
/// the request bytes written by the client and feeds back scripted response
/// bytes.
pub struct FakeTransport {
steps : Array[FakeStep]
mut index : Int
log : Array[Bytes]
mut connected : Bool
priv mut pending : Bytes?
}
///|
pub fn FakeTransport::new() -> FakeTransport {
{ steps: [], index: 0, log: [], connected: false, pending: None }
}
///|
pub fn FakeTransport::with_steps(steps : Array[FakeStep]) -> FakeTransport {
{ steps, index: 0, log: [], connected: false, pending: None }
}
///|
/// The request bytes received so far.
pub fn FakeTransport::log(self : FakeTransport) -> Array[Bytes] {
self.log
}
///|
pub fn FakeTransport::is_connected(self : FakeTransport) -> Bool {
self.connected
}
///|
pub fn FakeTransport::has_unconsumed_steps(self : FakeTransport) -> Bool {
self.index < self.steps.length() || self.pending is Some(_)
}
///|
fn FakeTransport::next_step(self : FakeTransport) -> FakeStep? {
if self.index >= self.steps.length() {
return None
}
let step = self.steps[self.index]
self.index = self.index + 1
Some(step)
}
///|
pub impl LdapTransport for FakeTransport with fn connect(self, _config) {
self.connected = true
Ok(())
}
///|
pub impl LdapTransport for FakeTransport with fn write(self, data) {
match self.next_step() {
None => Err(LdapError::ScriptMismatch("no step expected a write"))
Some(Respond(response)) => {
self.pending = Some(response)
self.log.push(data)
Ok(())
}
Some(Expect(expected)) =>
if expected == data {
self.log.push(data)
Ok(())
} else {
Err(
LdapError::ScriptMismatch(
"request bytes differ: got \{to_hex_upper(data[:])} want \{to_hex_upper(expected[:])}",
),
)
}
Some(Fail(err)) => Err(err)
Some(_) =>
Err(LdapError::ScriptMismatch("expected a read step, got a write"))
}
}
///|
pub impl LdapTransport for FakeTransport with fn read(self) {
match self.pending {
Some(data) => {
self.pending = None
Ok(data)
}
None =>
match self.next_step() {
None => Err(LdapError::PrematureClose)
Some(Recv(data)) => Ok(data)
Some(Respond(data)) => Ok(data)
Some(Close) => Err(LdapError::PrematureClose)
Some(Fail(err)) => Err(err)
Some(Expect(_)) =>
Err(LdapError::ScriptMismatch("expected a write step, got a read"))
}
}
}
///|
pub impl LdapTransport for FakeTransport with fn close(self) {
self.connected = false
}