videosdk-server-sdk 0.1.0

Rust server SDK for the VideoSDK v2 REST APIs
Documentation
//! Regression tests for response decoding.
//!
//! Two bug classes the Go SDK is immune to but Rust is not, unless guarded:
//!
//! 1. Go's `encoding/json` maps an explicit `null` to the zero value.
//!    `#[serde(default)]` alone only covers a *missing* key, so every defaulted
//!    field pairs it with `deserialize_with = "null_to_default"`.
//! 2. A closed Rust `enum` fails to deserialize a value the API adds later, and
//!    takes the whole response down with it. Response-carried enums are open
//!    string newtypes instead.

use serde_json::{json, Value};

use crate::{
    AutoCloseType, Participant, Region, Room, Session, SessionStatus, SipCall, SipTrunk, SipWebhook,
};

fn decode<T: serde::de::DeserializeOwned>(value: Value) -> T {
    serde_json::from_value(value).expect("the response should decode")
}

/* ------------------------- explicit nulls on defaults ------------------------- */

#[test]
fn a_null_string_decodes_as_empty() {
    let participant: Participant = decode(json!({"participantId": "p-1", "name": null}));
    assert_eq!(participant.name, "");

    // A missing key still defaults, as before.
    let participant: Participant = decode(json!({"participantId": "p-1"}));
    assert_eq!(participant.name, "");
}

#[test]
fn a_null_collection_decodes_as_empty() {
    let session: Session = decode(json!({
        "id": "s-1",
        "participants": null,
        "links": null,
        "region": null,
    }));
    assert!(session.participants.is_empty());
    assert!(session.links.is_empty());
    assert_eq!(session.region, "");

    let trunk: SipTrunk =
        decode(json!({"id": "gw-1", "numbers": null, "metadata": null, "tags": null}));
    assert!(trunk.numbers.is_empty());
    assert!(trunk.metadata.is_empty());
    assert!(trunk.tags.is_empty());
}

#[test]
fn a_null_bool_or_number_decodes_as_zero() {
    let room: Room = decode(json!({"id": "d-1", "roomId": null, "disabled": null}));
    assert_eq!(room.room_id, "");
    assert!(!room.disabled);
}

#[test]
fn nulls_on_ids_the_list_endpoints_remap() {
    let call: SipCall = decode(json!({"id": null, "timelog": null}));
    assert_eq!(call.id, "");
    assert!(call.timelog.is_empty());

    let webhook: SipWebhook = decode(json!({"id": null, "events": null}));
    assert_eq!(webhook.id, "");
    assert!(webhook.events.is_empty());
}

/* ---------------------- unknown values on response enums ---------------------- */

#[test]
fn an_unknown_session_status_does_not_fail_the_session() {
    let session: Session = decode(json!({"id": "s-1", "status": "terminated"}));
    assert_eq!(session.status.unwrap().as_str(), "terminated");

    // The known values still compare as expected.
    let session: Session = decode(json!({"id": "s-1", "status": "ended"}));
    assert_eq!(session.status, Some(SessionStatus::ENDED));
}

#[test]
fn an_unknown_auto_close_type_does_not_fail_the_room() {
    let room: Room = decode(json!({
        "id": "d-1", "roomId": "r-1",
        "autoCloseConfig": {"type": "session-archive", "duration": 60},
    }));
    let config = room.auto_close_config.unwrap();
    assert_eq!(config.kind.unwrap().as_str(), "session-archive");
    assert_eq!(config.duration, Some(60));

    let room: Room = decode(json!({
        "id": "d-1", "roomId": "r-1",
        "autoCloseConfig": {"type": "session-ends"},
    }));
    assert_eq!(
        room.auto_close_config.unwrap().kind,
        Some(AutoCloseType::SESSION_ENDS)
    );
}

#[test]
fn an_unknown_region_does_not_fail_the_room() {
    let room: Room = decode(json!({"id": "d-1", "roomId": "r-1", "geoFence": "mars001"}));
    assert_eq!(room.geo_fence.unwrap().as_str(), "mars001");

    let room: Room = decode(json!({"id": "d-1", "roomId": "r-1", "geoFence": "us001"}));
    assert_eq!(room.geo_fence, Some(Region::US001));
}

#[test]
fn an_unknown_call_status_and_transport_do_not_fail_the_call() {
    let call: SipCall = decode(json!({"id": "c-1", "status": "quantum-entangled"}));
    assert_eq!(call.status.unwrap().as_str(), "quantum-entangled");

    let trunk: SipTrunk =
        decode(json!({"id": "gw-1", "transport": "quic", "geoRegion": "mars001"}));
    assert_eq!(trunk.transport.unwrap().as_str(), "quic");
    assert_eq!(trunk.geo_region.unwrap().as_str(), "mars001");
}

/* -------------------------------- round trips --------------------------------- */

#[test]
fn an_unknown_value_serializes_back_out_unchanged() {
    let status = SessionStatus::from("terminated");
    assert_eq!(serde_json::to_value(&status).unwrap(), json!("terminated"));

    let kind = AutoCloseType::from("session-archive");
    assert_eq!(
        serde_json::to_value(&kind).unwrap(),
        json!("session-archive")
    );
}