Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Your first scenario

Let’s write a complete test: two agents place, answer and tear down a call. We’ll build it line by line — every concept you need for most scenarios is here.

You’ll need baresip in your $PATH and two SIP accounts.

The whole script

Save this as first.rhai:

#![allow(unused)]
fn main() {
let dom = env("SIP_DOMAIN");

let a = agent("A", #{
    username: env("A_USER"),
    domain: dom,
    password: env("A_PASS"),
});
let b = agent("B", #{
    username: env("B_USER"),
    domain: dom,
    password: env("B_PASS"),
});

a.register();
b.register();
await_until(|| assert(a.registered).is_true(), "10s");
await_until(|| assert(b.registered).is_true(), "10s");

a.dial(b);
await_until(|| assert(b.state).equals(State::Ringing), "15s");
b.accept();
await_until(|| assert(a.state).equals(State::Established));

wait(3); // the call must stay up
a.hangup();
await_until(|| assert(a.state).equals(State::Idle), "10s");
}

Run it:

SIP_DOMAIN=example.com A_USER=alice A_PASS=… B_USER=bob B_PASS=… \
  ringo-flow run first.rhai

Line by line

Credentials from the environment. env("SIP_DOMAIN") reads a variable, so no secrets live in the script. Pass them as shown above, or from an --env-file.

Create the agents. agent(name, #{ … }) connects a headless baresip instance and returns a handle you drive with verbs. name is just a label used in the log. See the Agents reference for every config field.

Register, then wait for it. SIP is asynchronous: register() only starts registration. await_until(|| <assertion>, "10s") re-runs the assertion until it holds or the timeout elapses — never sleep and hope. assert(a.registered) reads the agent’s state; .is_true() checks it.

Place the call. a.dial(b) calls B at its address (you can also dial a number or SIP URI as a string). We then wait until B is ringingb.state is one of State::Idle / State::Ringing / State::Established.

Answer and connect. b.accept() answers; both sides become Established. await_until without a timeout uses the default (overridable with default_timeout(...)).

Hold, then hang up. wait(3) holds for three seconds — and fails if an established call drops in that window, so it doubles as a stability check. a.hangup() ends the call; we confirm both return to Idle.

What failure looks like

Assertions report expect … — actual …, and the exit code is non-zero if any assertion fails — so this runs cleanly in CI. Add -v to see every assertion, or --logs to dump each agent’s SIP signaling when something’s off.

Next