use rsiprtp::sip::{Method, SipRequest, SipResponse};
use rsiprtp::transaction::client::invite::{
Action as InviteClientAction, State as InviteClientState,
};
use rsiprtp::transaction::client::non_invite::State as NonInviteClientState;
use rsiprtp::transaction::server::invite::State as InviteServerState;
use rsiprtp::transaction::server::non_invite::State as NonInviteServerState;
use rsiprtp::transaction::{
InviteClientTransaction, InviteServerTransaction, NonInviteClientTransaction,
NonInviteServerTransaction,
};
fn create_invite() -> SipRequest {
SipRequest::builder()
.method(Method::Invite)
.uri("sip:bob@example.com")
.via("192.168.1.1", 5060, "UDP", "z9hG4bKtest123")
.from("sip:alice@example.com", "fromtag123")
.to("sip:bob@example.com")
.call_id("call123@example.com")
.cseq(1)
.build()
.unwrap()
}
fn create_options() -> SipRequest {
SipRequest::builder()
.method(Method::Options)
.uri("sip:bob@example.com")
.via("192.168.1.1", 5060, "UDP", "z9hG4bKoptions")
.from("sip:alice@example.com", "fromtag456")
.to("sip:bob@example.com")
.call_id("options@example.com")
.cseq(1)
.build()
.unwrap()
}
fn create_response(request: &SipRequest, code: u16, reason: &str) -> SipResponse {
SipResponse::builder()
.status(code, reason)
.from_request(request)
.to_tag("totag123")
.build()
.unwrap()
}
#[test]
fn test_invite_client_transaction_complete_flow() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite.clone(), false).unwrap();
assert!(matches!(tx.state(), InviteClientState::Calling));
tx.poll_actions();
let ringing = create_response(&invite, 180, "Ringing");
tx.handle_response(ringing);
assert!(matches!(tx.state(), InviteClientState::Proceeding));
let ok = create_response(&invite, 200, "OK");
tx.handle_response(ok);
assert!(tx.is_terminated());
}
#[test]
fn test_invite_rejection_486_busy() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite.clone(), false).unwrap();
tx.poll_actions();
let busy = create_response(&invite, 486, "Busy Here");
tx.handle_response(busy);
assert!(matches!(tx.state(), InviteClientState::Completed));
let actions = tx.poll_actions();
let has_send_ack = actions
.iter()
.any(|a| matches!(a, InviteClientAction::Send(_)));
assert!(
has_send_ack,
"Transaction should generate ACK for error response"
);
}
#[test]
fn test_invite_rejection_404_not_found() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite.clone(), false).unwrap();
tx.poll_actions();
let not_found = create_response(&invite, 404, "Not Found");
tx.handle_response(not_found);
assert!(matches!(tx.state(), InviteClientState::Completed));
}
#[test]
fn test_invite_rejection_503_service_unavailable() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite.clone(), false).unwrap();
tx.poll_actions();
let unavailable = create_response(&invite, 503, "Service Unavailable");
tx.handle_response(unavailable);
assert!(matches!(tx.state(), InviteClientState::Completed));
}
#[test]
fn test_invite_server_transaction_receives_invite() {
let invite = create_invite();
let mut tx = InviteServerTransaction::new(invite.clone(), false).unwrap();
assert!(matches!(tx.state(), InviteServerState::Proceeding));
let ringing = create_response(&invite, 180, "Ringing");
tx.send_response(ringing);
assert!(matches!(tx.state(), InviteServerState::Proceeding));
let ok = create_response(&invite, 200, "OK");
tx.send_response(ok);
assert!(tx.is_terminated());
}
#[test]
fn test_invite_server_sends_error_response() {
let invite = create_invite();
let mut tx = InviteServerTransaction::new(invite.clone(), false).unwrap();
let busy = create_response(&invite, 486, "Busy Here");
tx.send_response(busy);
assert!(matches!(tx.state(), InviteServerState::Completed));
let ack = SipRequest::builder()
.method(Method::Ack)
.uri("sip:bob@example.com")
.via("192.168.1.1", 5060, "UDP", "z9hG4bKtest123")
.from("sip:alice@example.com", "fromtag123")
.to("sip:bob@example.com")
.to_tag("totag123")
.call_id("call123@example.com")
.cseq(1)
.build()
.unwrap();
tx.handle_request(ack);
assert!(matches!(tx.state(), InviteServerState::Confirmed));
}
#[test]
fn test_non_invite_options_request() {
let options = create_options();
let mut tx = NonInviteClientTransaction::new(options.clone(), false).unwrap();
assert!(matches!(tx.state(), NonInviteClientState::Trying));
tx.poll_actions();
let ok = create_response(&options, 200, "OK");
tx.handle_response(ok);
assert!(matches!(tx.state(), NonInviteClientState::Completed));
}
#[test]
fn test_non_invite_server_options() {
let options = create_options();
let mut tx = NonInviteServerTransaction::new(options.clone(), false).unwrap();
assert!(matches!(tx.state(), NonInviteServerState::Trying));
let ok = create_response(&options, 200, "OK");
tx.send_response(ok);
assert!(matches!(tx.state(), NonInviteServerState::Completed));
}
#[test]
fn test_multiple_provisional_responses() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite.clone(), false).unwrap();
tx.poll_actions();
let trying = create_response(&invite, 100, "Trying");
tx.handle_response(trying);
assert!(matches!(tx.state(), InviteClientState::Proceeding));
let ringing = create_response(&invite, 180, "Ringing");
tx.handle_response(ringing);
assert!(matches!(tx.state(), InviteClientState::Proceeding));
let progress = create_response(&invite, 183, "Session Progress");
tx.handle_response(progress);
assert!(matches!(tx.state(), InviteClientState::Proceeding));
let ok = create_response(&invite, 200, "OK");
tx.handle_response(ok);
assert!(tx.is_terminated());
}
#[test]
fn test_retransmitted_response_handling() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite.clone(), false).unwrap();
tx.poll_actions();
let ringing = create_response(&invite, 180, "Ringing");
tx.handle_response(ringing.clone());
assert!(matches!(tx.state(), InviteClientState::Proceeding));
tx.handle_response(ringing);
assert!(matches!(tx.state(), InviteClientState::Proceeding));
}