mod common;

use std::collections::VecDeque;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::sync::Arc;

use common::test_helper::{
    as_route_origin, as_v4_route_origin, indent_block, payloads_to_string,
    serial_result_detail_to_string, snapshot_hashes_to_string, test_report, v4_origin, v6_origin,
};

use rpki::data_model::resources::as_resources::Asn;
use rpki::rtr::cache::{
    CacheAvailability, Delta, RtrCacheBuilder, SerialResult, SessionIds, Snapshot,
    validate_payload_updates_for_rtr, validate_payloads_for_rtr,
};
use rpki::rtr::payload::{Aspa, Payload, RouterKey, Ski, Timing};
use rpki::rtr::store::RtrStore;

fn delta_to_string(delta: &Delta) -> String {
    format!(
        "serial: {}\nannounced:\n{}withdrawn:\n{}",
        delta.serial(),
        indent_block(&payloads_to_string(delta.announced()), 2),
        indent_block(&payloads_to_string(delta.withdrawn()), 2),
    )
}

fn deltas_window_to_string(deltas: &VecDeque<Arc<Delta>>) -> String {
    if deltas.is_empty() {
        return "  <empty>\n".to_string();
    }

    let mut out = String::new();
    for (idx, delta) in deltas.iter().enumerate() {
        out.push_str(&format!("delta[{}]:\n", idx));
        out.push_str(&indent_block(&delta_to_string(delta), 2));
    }
    out
}

fn get_deltas_since_input_to_string(
    cache_session_id: u16,
    cache_serial: u32,
    client_serial: u32,
) -> String {
    format!(
        "cache.session_id: {}\ncache.serial: {}\nclient_serial: {}\n",
        cache_session_id, cache_serial, client_serial
    )
}

fn snapshot_hashes_and_sorted_view_to_string(snapshot: &Snapshot) -> String {
    let payloads = snapshot.payloads_for_rtr();
    format!(
        "hashes:\n{}sorted payloads_for_rtr:\n{}",
        indent_block(&snapshot_hashes_to_string(snapshot), 2),
        indent_block(&payloads_to_string(&payloads), 2),
    )
}

fn serials_all(serial: u32) -> [u32; 3] {
    [serial; 3]
}

fn snapshots_all(snapshot: Snapshot) -> [Snapshot; 3] {
    [snapshot.clone(), snapshot.clone(), snapshot]
}

fn deltas_all(deltas: VecDeque<Arc<Delta>>) -> [VecDeque<Arc<Delta>>; 3] {
    [deltas.clone(), deltas.clone(), deltas]
}

///  Snapshot ?hash ?
///  payload  snapshot_hash / origins_hash ?
#[test]
fn snapshot_hash_is_stable_for_same_content_with_different_input_order() {
    let a = v4_origin(192, 0, 2, 0, 24, 24, 64496);
    let b = v4_origin(198, 51, 100, 0, 24, 24, 64497);
    let c = v6_origin(
        Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0),
        32,
        48,
        64498,
    );

    let s1_input = vec![
        Payload::RouteOrigin(a.clone()),
        Payload::RouteOrigin(b.clone()),
        Payload::RouteOrigin(c.clone()),
    ];
    let s2_input = vec![
        Payload::RouteOrigin(c),
        Payload::RouteOrigin(a),
        Payload::RouteOrigin(b),
    ];

    let s1 = Snapshot::from_payloads(s1_input.clone());
    let s2 = Snapshot::from_payloads(s2_input.clone());

    let input = format!(
        "s1  payloads:\n{}\ns2  payloads:\n{}",
        indent_block(&payloads_to_string(&s1_input), 2),
        indent_block(&payloads_to_string(&s2_input), 2),
    );

    let output = format!(
        "s1:\n{}\ns2:\n{}\n:\n  same_content: {}\n  same_origins: {}\n  snapshot_hash : {}\n  origins_hash : {}\n",
        indent_block(&snapshot_hashes_and_sorted_view_to_string(&s1), 2),
        indent_block(&snapshot_hashes_and_sorted_view_to_string(&s2), 2),
        s1.same_content(&s2),
        s1.same_origins(&s2),
        s1.snapshot_hash() == s2.snapshot_hash(),
        s1.origins_hash() == s2.origins_hash(),
    );

    test_report(
        "snapshot_hash_is_stable_for_same_content_with_different_input_order",
        "test purpose",
        &input,
        &output,
    );

    assert!(s1.same_content(&s2));
    assert!(s1.same_origins(&s2));
    assert_eq!(s1.snapshot_hash(), s2.snapshot_hash());
    assert_eq!(s1.origins_hash(), s2.origins_hash());
}

#[tokio::test]
async fn init_keeps_cache_running_when_file_loader_returns_no_data() {
    let dir = tempfile::tempdir().unwrap();
    let store = RtrStore::open(dir.path()).unwrap();

    let cache = rpki::rtr::cache::RtrCache::default()
        .init(&store, 16, false, Timing::new(600, 600, 7200), || {
            Ok(vec![])
        })
        .unwrap();

    assert!(!cache.is_data_available());
    assert_eq!(cache.serial_for_version(2), 0);
    assert!(cache.snapshot_for_version(2).payloads_for_rtr().is_empty());
}

#[tokio::test]
async fn init_restores_wraparound_delta_window_from_store() {
    let dir = tempfile::tempdir().unwrap();
    let store = RtrStore::open(dir.path()).unwrap();
    let session_ids = SessionIds::from_array([42, 43, 44]);
    let snapshot = Snapshot::from_payloads(vec![
        Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496)),
        Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497)),
        Payload::RouteOrigin(v4_origin(203, 0, 113, 0, 24, 24, 64498)),
    ]);

    let d_max = Delta::new(
        u32::MAX,
        vec![Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496))],
        vec![],
    );
    let d_zero = Delta::new(
        0,
        vec![Payload::RouteOrigin(v4_origin(
            198, 51, 100, 0, 24, 24, 64497,
        ))],
        vec![],
    );
    let d_one = Delta::new(
        1,
        vec![Payload::RouteOrigin(v4_origin(
            203, 0, 113, 0, 24, 24, 64498,
        ))],
        vec![],
    );

    let snapshots = snapshots_all(snapshot.clone());
    let session_ids = session_ids.as_array();

    store
        .save_cache_state_versioned(
            CacheAvailability::Ready,
            &snapshots,
            &session_ids,
            &serials_all(u32::MAX),
            &[None, None, Some(&d_max)],
            &[None, None, Some((u32::MAX, u32::MAX))],
            &[false, false, false],
        )
        .unwrap();
    store
        .save_cache_state_versioned(
            CacheAvailability::Ready,
            &snapshots,
            &session_ids,
            &serials_all(0),
            &[None, None, Some(&d_zero)],
            &[None, None, Some((u32::MAX, 0))],
            &[false, false, false],
        )
        .unwrap();
    store
        .save_cache_state_versioned(
            CacheAvailability::Ready,
            &snapshots,
            &session_ids,
            &serials_all(1),
            &[None, None, Some(&d_one)],
            &[None, None, Some((u32::MAX, 1))],
            &[false, false, false],
        )
        .unwrap();

    let cache = rpki::rtr::cache::RtrCache::default()
        .init(&store, 16, false, Timing::new(600, 600, 7200), || {
            Ok(Vec::new())
        })
        .unwrap();

    match cache.get_deltas_since_for_version(2, u32::MAX.wrapping_sub(1)) {
        SerialResult::Delta(delta) => {
            assert_eq!(delta.serial(), 1);
            assert_eq!(delta.announced().len(), 3);
        }
        _ => panic!("expected wraparound delta to be restored from store"),
    }
}

#[tokio::test]
async fn init_restore_respects_max_delta_limit() {
    let dir = tempfile::tempdir().unwrap();
    let store = RtrStore::open(dir.path()).unwrap();
    let session_ids = SessionIds::from_array([42, 43, 44]).as_array();

    let a = Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496));
    let b = Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497));
    let snapshot = Snapshot::from_payloads(vec![a.clone(), b.clone()]);
    let snapshots = snapshots_all(snapshot);

    let d17 = Delta::new(17, vec![a], vec![]);
    let d18 = Delta::new(18, vec![b], vec![]);

    store
        .save_cache_state_versioned(
            CacheAvailability::Ready,
            &snapshots,
            &session_ids,
            &serials_all(17),
            &[None, None, Some(&d17)],
            &[None, None, Some((17, 17))],
            &[false, false, false],
        )
        .unwrap();
    store
        .save_cache_state_versioned(
            CacheAvailability::Ready,
            &snapshots,
            &session_ids,
            &serials_all(18),
            &[None, None, Some(&d18)],
            &[None, None, Some((17, 18))],
            &[false, false, false],
        )
        .unwrap();

    let cache = rpki::rtr::cache::RtrCache::default()
        .init(&store, 1, false, Timing::new(600, 600, 7200), || {
            Ok(Vec::new())
        })
        .unwrap();

    match cache.get_deltas_since_for_version(2, 16) {
        SerialResult::ResetRequired => {}
        _ => panic!("expected ResetRequired for serial older than retained max_delta window"),
    }

    match cache.get_deltas_since_for_version(2, 17) {
        SerialResult::Delta(delta) => assert_eq!(delta.serial(), 18),
        _ => panic!("expected one retained delta from 17 -> 18"),
    }
}

#[tokio::test]
async fn update_prunes_delta_window_when_cumulative_delta_size_reaches_snapshot_size() {
    let dir = tempfile::tempdir().unwrap();
    let store = RtrStore::open(dir.path()).unwrap();
    let valid_spki = vec![
        0x30, 0x13, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
        0x05, 0x00, 0x03, 0x02, 0x00, 0x00,
    ];

    let initial_snapshot = Snapshot::from_payloads(vec![Payload::RouterKey(RouterKey::new(
        Ski::default(),
        Asn::from(64496u32),
        valid_spki.clone(),
    ))]);

    let mut cache = RtrCacheBuilder::new()
        .availability(CacheAvailability::Ready)
        .session_ids(SessionIds::from_array([42, 43, 44]))
        .serials(serials_all(1))
        .snapshots(snapshots_all(initial_snapshot))
        .max_delta(16)
        .prune_delta_by_snapshot_size(true)
        .timing(Timing::new(600, 600, 7200))
        .build();

    cache
        .update(
            vec![Payload::RouterKey(RouterKey::new(
                Ski::from_bytes([1u8; 20]),
                Asn::from(64496u32),
                valid_spki,
            ))],
            &store,
        )
        .unwrap();

    match cache.get_deltas_since_for_version(2, 1) {
        SerialResult::ResetRequired => {}
        _ => panic!(
            "expected delta window to be pruned when cumulative delta size exceeds snapshot size"
        ),
    }
}

///  Snapshot::diff() ?
/// old_snapshot ?new_snapshot  announced?withdrawn?
#[test]
fn snapshot_diff_reports_announced_and_withdrawn_correctly() {
    let old_a = v4_origin(192, 0, 2, 0, 24, 24, 64496);
    let old_b = v4_origin(198, 51, 100, 0, 24, 24, 64497);
    let new_c = v6_origin(
        Ipv6Addr::new(0x2001, 0xdb8, 0, 1, 0, 0, 0, 0),
        48,
        48,
        64499,
    );

    let old_input = vec![
        Payload::RouteOrigin(old_a.clone()),
        Payload::RouteOrigin(old_b.clone()),
    ];
    let new_input = vec![
        Payload::RouteOrigin(old_b),
        Payload::RouteOrigin(new_c.clone()),
    ];

    let old_snapshot = Snapshot::from_payloads(old_input.clone());
    let new_snapshot = Snapshot::from_payloads(new_input.clone());

    let (announced, withdrawn) = old_snapshot.diff(&new_snapshot);

    let input = format!(
        "old_snapshot :\n{}\nnew_snapshot :\n{}",
        indent_block(&payloads_to_string(&old_input), 2),
        indent_block(&payloads_to_string(&new_input), 2),
    );

    let output = format!(
        "announced:\n{}withdrawn:\n{}",
        indent_block(&payloads_to_string(&announced), 2),
        indent_block(&payloads_to_string(&withdrawn), 2),
    );

    test_report(
        "snapshot_diff_reports_announced_and_withdrawn_correctly",
        "test purpose",
        &input,
        &output,
    );

    assert_eq!(announced.len(), 1);
    assert_eq!(withdrawn.len(), 1);

    match &announced[0] {
        Payload::RouteOrigin(ro) => assert_eq!(ro, &new_c),
        _ => panic!("expected announced RouteOrigin"),
    }

    match &withdrawn[0] {
        Payload::RouteOrigin(ro) => assert_eq!(ro, &old_a),
        _ => panic!("expected withdrawn RouteOrigin"),
    }
}

///  Snapshot::payloads_for_rtr() ?
///  IPv4 Prefix  IPv6 Prefix ?IPv4 announcement ?RTR ?
#[test]
fn snapshot_payloads_for_rtr_sorts_ipv4_before_ipv6_and_ipv4_announcements_descending() {
    let v4_low = v4_origin(192, 0, 2, 0, 24, 24, 64496);
    let v4_high = v4_origin(198, 51, 100, 0, 24, 24, 64497);
    let v6 = v6_origin(
        Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0),
        32,
        48,
        64498,
    );

    let input_payloads = vec![
        Payload::RouteOrigin(v6.clone()),
        Payload::RouteOrigin(v4_low.clone()),
        Payload::RouteOrigin(v4_high.clone()),
    ];

    let snapshot = Snapshot::from_payloads(input_payloads.clone());
    let output_payloads = snapshot.payloads_for_rtr();

    let input = format!(
        " payloads?Snapshot :\n{}",
        indent_block(&payloads_to_string(&input_payloads), 2),
    );

    let output = format!(
        "?payloads_for_rtr:\n{}",
        indent_block(&payloads_to_string(&output_payloads), 2),
    );

    test_report(
        "snapshot_payloads_for_rtr_sorts_ipv4_before_ipv6_and_ipv4_announcements_descending",
        "test purpose",
        &input,
        &output,
    );

    assert_eq!(output_payloads.len(), 3);

    let first = as_v4_route_origin(&output_payloads[0]);
    let second = as_v4_route_origin(&output_payloads[1]);

    assert_eq!(
        first.prefix().address.to_ipv4(),
        Some(Ipv4Addr::new(198, 51, 100, 0))
    );
    assert_eq!(
        second.prefix().address.to_ipv4(),
        Some(Ipv4Addr::new(192, 0, 2, 0))
    );

    let third = as_route_origin(&output_payloads[2]);
    assert!(third.prefix().address.is_ipv6());
    assert_eq!(
        third.prefix().address.to_ipv6(),
        Some(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0))
    );
}

///  Delta::new() ?announced ?withdrawn?
/// announced  announcement ithdrawn  withdrawal ?
#[test]
fn delta_new_sorts_announced_descending_and_withdrawn_ascending() {
    let announced_low = v4_origin(192, 0, 2, 0, 24, 24, 64496);
    let announced_high = v4_origin(198, 51, 100, 0, 24, 24, 64497);
    let withdrawn_high = v4_origin(203, 0, 113, 0, 24, 24, 64501);
    let withdrawn_low = v4_origin(10, 0, 0, 0, 24, 24, 64500);

    let input_announced = vec![
        Payload::RouteOrigin(announced_low),
        Payload::RouteOrigin(announced_high),
    ];
    let input_withdrawn = vec![
        Payload::RouteOrigin(withdrawn_high),
        Payload::RouteOrigin(withdrawn_low),
    ];

    let delta = Delta::new(101, input_announced.clone(), input_withdrawn.clone());

    let input = format!(
        "announced?\n{}withdrawn?\n{}",
        indent_block(&payloads_to_string(&input_announced), 2),
        indent_block(&payloads_to_string(&input_withdrawn), 2),
    );

    let output = indent_block(&delta_to_string(&delta), 2);

    test_report(
        "delta_new_sorts_announced_descending_and_withdrawn_ascending",
        "test purpose",
        &input,
        &output,
    );

    assert_eq!(delta.serial(), 101);
    assert_eq!(delta.announced().len(), 2);
    assert_eq!(delta.withdrawn().len(), 2);

    let a0 = as_v4_route_origin(&delta.announced()[0]);
    let a1 = as_v4_route_origin(&delta.announced()[1]);
    assert_eq!(
        a0.prefix().address.to_ipv4(),
        Some(Ipv4Addr::new(198, 51, 100, 0))
    );
    assert_eq!(
        a1.prefix().address.to_ipv4(),
        Some(Ipv4Addr::new(192, 0, 2, 0))
    );

    let w0 = as_v4_route_origin(&delta.withdrawn()[0]);
    let w1 = as_v4_route_origin(&delta.withdrawn()[1]);
    assert_eq!(
        w0.prefix().address.to_ipv4(),
        Some(Ipv4Addr::new(10, 0, 0, 0))
    );
    assert_eq!(
        w1.prefix().address.to_ipv4(),
        Some(Ipv4Addr::new(203, 0, 113, 0))
    );
}

///  serial ?serial
/// get_deltas_since() ?UpToDate?Delta ?ResetRequired?
#[test]
fn get_deltas_since_returns_up_to_date_when_client_serial_matches_current() {
    let cache = RtrCacheBuilder::new()
        .session_ids(SessionIds::from_array([42, 42, 42]))
        .serials(serials_all(100))
        .timing(Timing::default())
        .build();

    let result = cache.get_deltas_since_for_version(2, 100);

    let input =
        get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial_for_version(2), 100);
    let output = serial_result_detail_to_string(&result);

    test_report(
        "get_deltas_since_returns_up_to_date_when_client_serial_matches_current",
        "test purpose",
        &input,
        &output,
    );

    match result {
        SerialResult::UpToDate => {}
        _ => panic!("expected UpToDate"),
    }
}

///  serial  delta window ?/// get_deltas_since() ?ResetRequired?#[test]
fn get_deltas_since_returns_reset_required_when_client_serial_is_too_old() {
    let d1 = Arc::new(Delta::new(
        101,
        vec![Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496))],
        vec![],
    ));
    let d2 = Arc::new(Delta::new(
        102,
        vec![Payload::RouteOrigin(v4_origin(
            198, 51, 100, 0, 24, 24, 64497,
        ))],
        vec![],
    ));

    let mut deltas = VecDeque::new();
    deltas.push_back(d1);
    deltas.push_back(d2);

    let cache = RtrCacheBuilder::new()
        .session_ids(SessionIds::from_array([42, 42, 42]))
        .serials(serials_all(102))
        .timing(Timing::default())
        .deltas_by_version(deltas_all(deltas.clone()))
        .build();

    let result = cache.get_deltas_since_for_version(2, 99);

    let input = format!(
        "{}delta_window:\n{}",
        get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial_for_version(2), 99),
        indent_block(&deltas_window_to_string(&deltas), 2),
    );
    let output = serial_result_detail_to_string(&result);

    test_report(
        "get_deltas_since_returns_reset_required_when_client_serial_is_too_old",
        "test purpose",
        &input,
        &output,
    );

    match result {
        SerialResult::ResetRequired => {}
        _ => panic!("expected ResetRequired"),
    }
}

///  serial  delta window ?
/// get_deltas_since() ?delta ?
#[test]
fn get_deltas_since_returns_minimal_merged_delta() {
    let d1 = Arc::new(Delta::new(
        101,
        vec![Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496))],
        vec![],
    ));
    let d2 = Arc::new(Delta::new(
        102,
        vec![Payload::RouteOrigin(v4_origin(
            198, 51, 100, 0, 24, 24, 64497,
        ))],
        vec![],
    ));
    let d3 = Arc::new(Delta::new(
        103,
        vec![Payload::RouteOrigin(v4_origin(
            203, 0, 113, 0, 24, 24, 64498,
        ))],
        vec![],
    ));

    let final_snapshot = Snapshot::from_payloads(vec![
        Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496)),
        Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497)),
        Payload::RouteOrigin(v4_origin(203, 0, 113, 0, 24, 24, 64498)),
    ]);

    let mut deltas = VecDeque::new();
    deltas.push_back(d1);
    deltas.push_back(d2);
    deltas.push_back(d3);

    let cache = RtrCacheBuilder::new()
        .session_ids(SessionIds::from_array([42, 42, 42]))
        .serials(serials_all(103))
        .timing(Timing::default())
        .snapshots(snapshots_all(final_snapshot))
        .deltas_by_version(deltas_all(deltas.clone()))
        .build();

    let result = cache.get_deltas_since_for_version(2, 101);

    let input = format!(
        "{}delta_window:\n{}",
        get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial_for_version(2), 101),
        indent_block(&deltas_window_to_string(&deltas), 2),
    );
    let output = serial_result_detail_to_string(&result);

    test_report(
        "get_deltas_since_returns_minimal_merged_delta",
        "test purpose",
        &input,
        &output,
    );

    match result {
        SerialResult::Delta(delta) => {
            assert_eq!(delta.serial(), 103);
            assert_eq!(delta.announced().len(), 2);
            assert_eq!(delta.withdrawn().len(), 0);

            let a0 = as_v4_route_origin(&delta.announced()[0]);
            let a1 = as_v4_route_origin(&delta.announced()[1]);

            assert_eq!(
                a0.prefix().address.to_ipv4(),
                Some(Ipv4Addr::new(203, 0, 113, 0))
            );
            assert_eq!(
                a1.prefix().address.to_ipv4(),
                Some(Ipv4Addr::new(198, 51, 100, 0))
            );
        }
        _ => panic!("expected Delta"),
    }
}

///  serial  serial
///  ResetRequired?
#[test]
fn get_deltas_since_returns_reset_required_when_client_serial_is_in_future() {
    let cache = RtrCacheBuilder::new()
        .session_ids(SessionIds::from_array([42, 42, 42]))
        .serials(serials_all(100))
        .timing(Timing::default())
        .build();

    let result = cache.get_deltas_since_for_version(2, 101);

    let input =
        get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial_for_version(2), 101);
    let output = serial_result_detail_to_string(&result);

    test_report(
        "get_deltas_since_returns_reset_required_when_client_serial_is_in_future",
        "test purpose",
        &input,
        &output,
    );

    match result {
        SerialResult::ResetRequired => {}
        _ => panic!("expected ResetRequired"),
    }
}

#[test]
fn get_deltas_since_supports_incremental_updates_across_serial_wraparound() {
    let a = v4_origin(192, 0, 2, 0, 24, 24, 64496);
    let b = v4_origin(198, 51, 100, 0, 24, 24, 64497);

    let d_max = Arc::new(Delta::new(
        u32::MAX,
        vec![Payload::RouteOrigin(a.clone())],
        vec![],
    ));
    let d_zero = Arc::new(Delta::new(0, vec![Payload::RouteOrigin(b.clone())], vec![]));

    let mut deltas = VecDeque::new();
    deltas.push_back(d_max);
    deltas.push_back(d_zero);

    let final_snapshot = Snapshot::from_payloads(vec![
        Payload::RouteOrigin(a.clone()),
        Payload::RouteOrigin(b.clone()),
    ]);

    let cache = RtrCacheBuilder::new()
        .session_ids(SessionIds::from_array([42, 42, 42]))
        .serials(serials_all(0))
        .timing(Timing::default())
        .snapshots(snapshots_all(final_snapshot))
        .deltas_by_version(deltas_all(deltas.clone()))
        .build();

    let result = cache.get_deltas_since_for_version(2, u32::MAX.wrapping_sub(1));

    let input = format!(
        "{}delta_window:\n{}",
        get_deltas_since_input_to_string(
            cache.session_id_for_version(1),
            cache.serial_for_version(2),
            u32::MAX.wrapping_sub(1)
        ),
        indent_block(&deltas_window_to_string(&deltas), 2),
    );
    let output = serial_result_detail_to_string(&result);

    test_report(
        "get_deltas_since_supports_incremental_updates_across_serial_wraparound",
        "test purpose",
        &input,
        &output,
    );

    match result {
        SerialResult::Delta(delta) => {
            assert_eq!(delta.serial(), 0);
            assert_eq!(delta.announced().len(), 2);
            assert_eq!(delta.withdrawn().len(), 0);

            match &delta.announced()[0] {
                Payload::RouteOrigin(ro) => assert_eq!(ro, &b),
                _ => panic!("expected announced RouteOrigin"),
            }

            match &delta.announced()[1] {
                Payload::RouteOrigin(ro) => assert_eq!(ro, &a),
                _ => panic!("expected announced RouteOrigin"),
            }
        }
        _ => panic!("expected Delta"),
    }
}

#[test]
fn get_deltas_since_returns_reset_required_when_client_serial_is_too_old_across_wraparound() {
    let d_max = Arc::new(Delta::new(
        u32::MAX,
        vec![Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496))],
        vec![],
    ));
    let d_zero = Arc::new(Delta::new(
        0,
        vec![Payload::RouteOrigin(v4_origin(
            198, 51, 100, 0, 24, 24, 64497,
        ))],
        vec![],
    ));
    let d_one = Arc::new(Delta::new(
        1,
        vec![Payload::RouteOrigin(v4_origin(
            203, 0, 113, 0, 24, 24, 64498,
        ))],
        vec![],
    ));

    let mut deltas = VecDeque::new();
    deltas.push_back(d_max);
    deltas.push_back(d_zero);
    deltas.push_back(d_one);

    let cache = RtrCacheBuilder::new()
        .session_ids(SessionIds::from_array([42, 42, 42]))
        .serials(serials_all(1))
        .timing(Timing::default())
        .snapshots(snapshots_all(Snapshot::from_payloads(vec![
            Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496)),
            Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497)),
            Payload::RouteOrigin(v4_origin(203, 0, 113, 0, 24, 24, 64498)),
        ])))
        .deltas_by_version(deltas_all(deltas.clone()))
        .build();

    let client_serial = u32::MAX.wrapping_sub(2);
    let result = cache.get_deltas_since_for_version(2, client_serial);

    let input = format!(
        "{}delta_window:\n{}",
        get_deltas_since_input_to_string(
            cache.session_id_for_version(1),
            cache.serial_for_version(2),
            client_serial
        ),
        indent_block(&deltas_window_to_string(&deltas), 2),
    );
    let output = serial_result_detail_to_string(&result);

    test_report(
        "get_deltas_since_returns_reset_required_when_client_serial_is_too_old_across_wraparound",
        "test purpose",
        &input,
        &output,
    );

    match result {
        SerialResult::ResetRequired => {}
        _ => panic!("expected ResetRequired"),
    }
}

#[test]
fn get_deltas_since_returns_reset_required_when_client_serial_is_in_future_across_wraparound() {
    let cache = RtrCacheBuilder::new()
        .session_ids(SessionIds::from_array([42, 42, 42]))
        .serials(serials_all(u32::MAX))
        .timing(Timing::default())
        .build();

    let result = cache.get_deltas_since_for_version(2, 0);

    let input =
        get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial_for_version(2), 0);
    let output = serial_result_detail_to_string(&result);

    test_report(
        "get_deltas_since_returns_reset_required_when_client_serial_is_in_future_across_wraparound",
        "test purpose",
        &input,
        &output,
    );

    match result {
        SerialResult::ResetRequired => {}
        _ => panic!("expected ResetRequired"),
    }
}

///  update() ?payload ?
///  serial delta?
#[tokio::test]
async fn update_no_change_keeps_serial_and_produces_no_delta() {
    let old_a = v4_origin(192, 0, 2, 0, 24, 24, 64496);
    let old_b = v4_origin(198, 51, 100, 0, 24, 24, 64497);

    let old_input = vec![
        Payload::RouteOrigin(old_a.clone()),
        Payload::RouteOrigin(old_b.clone()),
    ];
    let snapshot = Snapshot::from_payloads(old_input.clone());

    let mut cache = RtrCacheBuilder::new()
        .session_ids(SessionIds::from_array([42, 42, 42]))
        .serials(serials_all(100))
        .timing(Timing::default())
        .snapshots(snapshots_all(snapshot.clone()))
        .build();

    let dir = tempfile::tempdir().unwrap();
    let store = RtrStore::open(dir.path()).unwrap();

    let new_payloads = vec![Payload::RouteOrigin(old_b), Payload::RouteOrigin(old_a)];

    cache.update(new_payloads.clone(), &store).unwrap();

    let current_snapshot = cache.snapshot_for_version(2);
    let result = cache.get_deltas_since_for_version(2, 100);

    let input = format!(
        "old_snapshot :\n{}new_payloads :\n{}",
        indent_block(&payloads_to_string(&old_input), 2),
        indent_block(&payloads_to_string(&new_payloads), 2),
    );

    let output = format!(
        "cache.serial_after_update: {}\ncurrent_snapshot:\n{}get_deltas_since(100):\n{}",
        cache.serial_for_version(2),
        indent_block(
            &snapshot_hashes_and_sorted_view_to_string(&current_snapshot),
            2
        ),
        indent_block(&serial_result_detail_to_string(&result), 2),
    );

    test_report(
        "update_no_change_keeps_serial_and_produces_no_delta",
        "test purpose",
        &input,
        &output,
    );

    assert_eq!(cache.serial_for_version(2), 100);
    assert!(cache.snapshot_for_version(2).same_content(&snapshot));

    match result {
        SerialResult::UpToDate => {}
        _ => panic!("expected UpToDate"),
    }
}

///  update()  payload ?
/// ?serial?announced ?
#[tokio::test]
async fn update_add_only_increments_serial_and_generates_announced_delta() {
    let old_a = v4_origin(192, 0, 2, 0, 24, 24, 64496);
    let new_b = v4_origin(198, 51, 100, 0, 24, 24, 64497);

    let old_input = vec![Payload::RouteOrigin(old_a.clone())];
    let old_snapshot = Snapshot::from_payloads(old_input.clone());

    let mut cache = RtrCacheBuilder::new()
        .session_ids(SessionIds::from_array([42, 42, 42]))
        .serials(serials_all(100))
        .timing(Timing::default())
        .snapshots(snapshots_all(old_snapshot.clone()))
        .build();

    let dir = tempfile::tempdir().unwrap();
    let store = RtrStore::open(dir.path()).unwrap();

    let new_payloads = vec![
        Payload::RouteOrigin(old_a.clone()),
        Payload::RouteOrigin(new_b.clone()),
    ];

    cache.update(new_payloads.clone(), &store).unwrap();

    let current_snapshot = cache.snapshot_for_version(2);
    let result = cache.get_deltas_since_for_version(2, 100);

    let input = format!(
        "old_snapshot :\n{}new_payloads :\n{}",
        indent_block(&payloads_to_string(&old_input), 2),
        indent_block(&payloads_to_string(&new_payloads), 2),
    );

    let output = format!(
        "cache.serial_after_update: {}\ncurrent_snapshot:\n{}get_deltas_since(100):\n{}",
        cache.serial_for_version(2),
        indent_block(
            &snapshot_hashes_and_sorted_view_to_string(&current_snapshot),
            2
        ),
        indent_block(&serial_result_detail_to_string(&result), 2),
    );

    test_report(
        "update_add_only_increments_serial_and_generates_announced_delta",
        "test purpose",
        &input,
        &output,
    );

    assert_eq!(cache.serial_for_version(2), 101);

    match result {
        SerialResult::Delta(delta) => {
            assert_eq!(delta.serial(), 101);
            assert_eq!(delta.announced().len(), 1);
            assert_eq!(delta.withdrawn().len(), 0);

            match &delta.announced()[0] {
                Payload::RouteOrigin(ro) => assert_eq!(ro, &new_b),
                _ => panic!("expected announced RouteOrigin"),
            }
        }
        _ => panic!("expected Delta"),
    }
}

///  update()  payload ?
/// ?serial?withdrawn ?
#[tokio::test]
async fn update_remove_only_increments_serial_and_generates_withdrawn_delta() {
    let old_a = v4_origin(192, 0, 2, 0, 24, 24, 64496);
    let old_b = v4_origin(198, 51, 100, 0, 24, 24, 64497);

    let old_input = vec![
        Payload::RouteOrigin(old_a.clone()),
        Payload::RouteOrigin(old_b.clone()),
    ];
    let old_snapshot = Snapshot::from_payloads(old_input.clone());

    let mut cache = RtrCacheBuilder::new()
        .session_ids(SessionIds::from_array([42, 42, 42]))
        .serials(serials_all(100))
        .timing(Timing::default())
        .snapshots(snapshots_all(old_snapshot.clone()))
        .build();

    let dir = tempfile::tempdir().unwrap();
    let store = RtrStore::open(dir.path()).unwrap();

    let new_payloads = vec![Payload::RouteOrigin(old_b.clone())];

    cache.update(new_payloads.clone(), &store).unwrap();

    let current_snapshot = cache.snapshot_for_version(2);
    let result = cache.get_deltas_since_for_version(2, 100);

    let input = format!(
        "old_snapshot :\n{}new_payloads :\n{}",
        indent_block(&payloads_to_string(&old_input), 2),
        indent_block(&payloads_to_string(&new_payloads), 2),
    );

    let output = format!(
        "cache.serial_after_update: {}\ncurrent_snapshot:\n{}get_deltas_since(100):\n{}",
        cache.serial_for_version(2),
        indent_block(
            &snapshot_hashes_and_sorted_view_to_string(&current_snapshot),
            2
        ),
        indent_block(&serial_result_detail_to_string(&result), 2),
    );

    test_report(
        "update_remove_only_increments_serial_and_generates_withdrawn_delta",
        "test purpose",
        &input,
        &output,
    );

    assert_eq!(cache.serial_for_version(2), 101);

    match result {
        SerialResult::Delta(delta) => {
            assert_eq!(delta.serial(), 101);
            assert_eq!(delta.announced().len(), 0);
            assert_eq!(delta.withdrawn().len(), 1);

            match &delta.withdrawn()[0] {
                Payload::RouteOrigin(ro) => assert_eq!(ro, &old_a),
                _ => panic!("expected withdrawn RouteOrigin"),
            }
        }
        _ => panic!("expected Delta"),
    }
}

///  update()  payload
/// ?serial announced ?withdrawn?
#[tokio::test]
async fn update_add_and_remove_increments_serial_and_generates_both_sides() {
    let old_a = v4_origin(192, 0, 2, 0, 24, 24, 64496);
    let old_b = v4_origin(198, 51, 100, 0, 24, 24, 64497);
    let new_c = v6_origin(
        Ipv6Addr::new(0x2001, 0xdb8, 0, 1, 0, 0, 0, 0),
        48,
        48,
        64499,
    );

    let old_input = vec![
        Payload::RouteOrigin(old_a.clone()),
        Payload::RouteOrigin(old_b.clone()),
    ];
    let old_snapshot = Snapshot::from_payloads(old_input.clone());

    let mut cache = RtrCacheBuilder::new()
        .session_ids(SessionIds::from_array([42, 42, 42]))
        .serials(serials_all(100))
        .timing(Timing::default())
        .snapshots(snapshots_all(old_snapshot.clone()))
        .build();

    let dir = tempfile::tempdir().unwrap();
    let store = RtrStore::open(dir.path()).unwrap();

    let new_payloads = vec![
        Payload::RouteOrigin(old_b.clone()),
        Payload::RouteOrigin(new_c.clone()),
    ];

    cache.update(new_payloads.clone(), &store).unwrap();

    let current_snapshot = cache.snapshot_for_version(2);
    let result = cache.get_deltas_since_for_version(2, 100);

    let input = format!(
        "old_snapshot :\n{}new_payloads :\n{}",
        indent_block(&payloads_to_string(&old_input), 2),
        indent_block(&payloads_to_string(&new_payloads), 2),
    );

    let output = format!(
        "cache.serial_after_update: {}\ncurrent_snapshot:\n{}get_deltas_since(100):\n{}",
        cache.serial_for_version(2),
        indent_block(
            &snapshot_hashes_and_sorted_view_to_string(&current_snapshot),
            2
        ),
        indent_block(&serial_result_detail_to_string(&result), 2),
    );

    test_report(
        "update_add_and_remove_increments_serial_and_generates_both_sides",
        "test purpose",
        &input,
        &output,
    );

    assert_eq!(cache.serial_for_version(2), 101);

    match result {
        SerialResult::Delta(delta) => {
            assert_eq!(delta.serial(), 101);
            assert_eq!(delta.announced().len(), 1);
            assert_eq!(delta.withdrawn().len(), 1);

            match &delta.announced()[0] {
                Payload::RouteOrigin(ro) => assert_eq!(ro, &new_c),
                _ => panic!("expected announced RouteOrigin"),
            }

            match &delta.withdrawn()[0] {
                Payload::RouteOrigin(ro) => assert_eq!(ro, &old_a),
                _ => panic!("expected withdrawn RouteOrigin"),
            }
        }
        _ => panic!("expected Delta"),
    }
}

/// ?prefix  announce withdraw
/// ?UpToDate?
#[test]
fn get_deltas_since_cancels_announce_then_withdraw_for_same_prefix() {
    let a = v4_origin(192, 0, 2, 0, 24, 24, 64496);

    let d1 = Arc::new(Delta::new(
        101,
        vec![Payload::RouteOrigin(a.clone())],
        vec![],
    ));
    let d2 = Arc::new(Delta::new(
        102,
        vec![],
        vec![Payload::RouteOrigin(a.clone())],
    ));

    let mut deltas = VecDeque::new();
    deltas.push_back(d1);
    deltas.push_back(d2);

    //  A ?
    let final_snapshot = Snapshot::empty();

    let cache = RtrCacheBuilder::new()
        .session_ids(SessionIds::from_array([42, 42, 42]))
        .serials(serials_all(102))
        .timing(Timing::default())
        .snapshots(snapshots_all(final_snapshot))
        .deltas_by_version(deltas_all(deltas.clone()))
        .build();

    let result = cache.get_deltas_since_for_version(2, 100);

    let input = format!(
        "{}delta_window:\n{}",
        get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial_for_version(2), 100),
        indent_block(&deltas_window_to_string(&deltas), 2),
    );
    let output = serial_result_detail_to_string(&result);

    test_report(
        "get_deltas_since_cancels_announce_then_withdraw_for_same_prefix",
        "test purpose",
        &input,
        &output,
    );

    match result {
        SerialResult::UpToDate => {}
        _ => panic!("expected UpToDate"),
    }
}

/// ?prefix  withdraw announce ?
/// ?UpToDate?
#[test]
fn get_deltas_since_cancels_withdraw_then_announce_for_same_prefix() {
    let a = v4_origin(192, 0, 2, 0, 24, 24, 64496);

    let d1 = Arc::new(Delta::new(
        101,
        vec![],
        vec![Payload::RouteOrigin(a.clone())],
    ));
    let d2 = Arc::new(Delta::new(
        102,
        vec![Payload::RouteOrigin(a.clone())],
        vec![],
    ));

    let mut deltas = VecDeque::new();
    deltas.push_back(d1);
    deltas.push_back(d2);

    // ?A?
    let final_snapshot = Snapshot::from_payloads(vec![Payload::RouteOrigin(a.clone())]);

    let cache = RtrCacheBuilder::new()
        .session_ids(SessionIds::from_array([42, 42, 42]))
        .serials(serials_all(102))
        .timing(Timing::default())
        .snapshots(snapshots_all(final_snapshot))
        .deltas_by_version(deltas_all(deltas.clone()))
        .build();

    let result = cache.get_deltas_since_for_version(2, 100);

    let input = format!(
        "{}delta_window:\n{}",
        get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial_for_version(2), 100),
        indent_block(&deltas_window_to_string(&deltas), 2),
    );
    let output = serial_result_detail_to_string(&result);

    test_report(
        "get_deltas_since_cancels_withdraw_then_announce_for_same_prefix",
        "test purpose",
        &input,
        &output,
    );

    match result {
        SerialResult::UpToDate => {}
        _ => panic!("expected UpToDate"),
    }
}

/// ?A ?B
/// ?delta?withdraw A + announce B?
#[test]
fn get_deltas_since_merges_replacement_into_withdraw_and_announce() {
    let a = v4_origin(192, 0, 2, 0, 24, 24, 64496);
    let b = v4_origin(192, 0, 2, 0, 24, 25, 64496);

    let d1 = Arc::new(Delta::new(
        101,
        vec![],
        vec![Payload::RouteOrigin(a.clone())],
    ));
    let d2 = Arc::new(Delta::new(
        102,
        vec![Payload::RouteOrigin(b.clone())],
        vec![],
    ));

    let mut deltas = VecDeque::new();
    deltas.push_back(d1);
    deltas.push_back(d2);

    let final_snapshot = Snapshot::from_payloads(vec![Payload::RouteOrigin(b.clone())]);

    let cache = RtrCacheBuilder::new()
        .session_ids(SessionIds::from_array([42, 42, 42]))
        .serials(serials_all(102))
        .timing(Timing::default())
        .snapshots(snapshots_all(final_snapshot))
        .deltas_by_version(deltas_all(deltas.clone()))
        .build();

    let result = cache.get_deltas_since_for_version(2, 100);

    let input = format!(
        "{}delta_window:\n{}",
        get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial_for_version(2), 100),
        indent_block(&deltas_window_to_string(&deltas), 2),
    );
    let output = serial_result_detail_to_string(&result);

    test_report(
        "get_deltas_since_merges_replacement_into_withdraw_and_announce",
        "test purpose",
        &input,
        &output,
    );

    match result {
        SerialResult::Delta(delta) => {
            assert_eq!(delta.serial(), 102);
            assert_eq!(delta.announced().len(), 1);
            assert_eq!(delta.withdrawn().len(), 1);

            match &delta.withdrawn()[0] {
                Payload::RouteOrigin(ro) => assert_eq!(ro, &a),
                _ => panic!("expected withdrawn RouteOrigin"),
            }

            match &delta.announced()[0] {
                Payload::RouteOrigin(ro) => assert_eq!(ro, &b),
                _ => panic!("expected announced RouteOrigin"),
            }
        }
        _ => panic!("expected Delta"),
    }
}

///  delta ?
/// ?
#[test]
fn get_deltas_since_merges_multiple_deltas_to_final_minimal_view() {
    let a = v4_origin(192, 0, 2, 0, 24, 24, 64496);
    let b = v4_origin(198, 51, 100, 0, 24, 24, 64497);
    let c = v4_origin(203, 0, 113, 0, 24, 24, 64498);

    // 100 -> 101 : +A
    let d1 = Arc::new(Delta::new(
        101,
        vec![Payload::RouteOrigin(a.clone())],
        vec![],
    ));
    // 101 -> 102 : -A +B
    let d2 = Arc::new(Delta::new(
        102,
        vec![Payload::RouteOrigin(b.clone())],
        vec![Payload::RouteOrigin(a.clone())],
    ));
    // 102 -> 103 : -B +C
    let d3 = Arc::new(Delta::new(
        103,
        vec![Payload::RouteOrigin(c.clone())],
        vec![Payload::RouteOrigin(b.clone())],
    ));

    let mut deltas = VecDeque::new();
    deltas.push_back(d1);
    deltas.push_back(d2);
    deltas.push_back(d3);

    let final_snapshot = Snapshot::from_payloads(vec![Payload::RouteOrigin(c.clone())]);

    let cache = RtrCacheBuilder::new()
        .session_ids(SessionIds::from_array([42, 42, 42]))
        .serials(serials_all(103))
        .timing(Timing::default())
        .snapshots(snapshots_all(final_snapshot))
        .deltas_by_version(deltas_all(deltas.clone()))
        .build();

    // ?serial=100 A/B ?+C
    let result = cache.get_deltas_since_for_version(2, 100);

    let input = format!(
        "{}delta_window:\n{}",
        get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial_for_version(2), 100),
        indent_block(&deltas_window_to_string(&deltas), 2),
    );
    let output = serial_result_detail_to_string(&result);

    test_report(
        "get_deltas_since_merges_multiple_deltas_to_final_minimal_view",
        "test purpose",
        &input,
        &output,
    );

    match result {
        SerialResult::Delta(delta) => {
            assert_eq!(delta.serial(), 103);
            assert_eq!(delta.announced().len(), 1);
            assert_eq!(delta.withdrawn().len(), 0);

            match &delta.announced()[0] {
                Payload::RouteOrigin(ro) => assert_eq!(ro, &c),
                _ => panic!("expected announced RouteOrigin"),
            }
        }
        _ => panic!("expected Delta"),
    }
}

#[test]
fn snapshot_from_payloads_unions_aspas_by_customer() {
    let first = Payload::Aspa(Aspa::new(Asn::from(64496u32), vec![Asn::from(64497u32)]));
    let second = Payload::Aspa(Aspa::new(
        Asn::from(64496u32),
        vec![Asn::from(64498u32), Asn::from(64497u32)],
    ));

    let snapshot = Snapshot::from_payloads(vec![first, second]);
    let aspas = snapshot.aspas().iter().collect::<Vec<_>>();

    assert_eq!(aspas.len(), 1);
    assert_eq!(aspas[0].customer_asn(), Asn::from(64496u32));
    assert_eq!(
        aspas[0].provider_asns(),
        &[Asn::from(64497u32), Asn::from(64498u32)]
    );
}

#[test]
fn snapshot_diff_replaces_aspa_with_single_announcement() {
    let old_snapshot = Snapshot::from_payloads(vec![Payload::Aspa(Aspa::new(
        Asn::from(64496u32),
        vec![Asn::from(64497u32)],
    ))]);
    let new_snapshot = Snapshot::from_payloads(vec![Payload::Aspa(Aspa::new(
        Asn::from(64496u32),
        vec![Asn::from(64498u32)],
    ))]);

    let (announced, withdrawn) = old_snapshot.diff(&new_snapshot);

    assert_eq!(announced.len(), 1);
    assert!(withdrawn.is_empty());

    match &announced[0] {
        Payload::Aspa(aspa) => {
            assert_eq!(aspa.customer_asn(), Asn::from(64496u32));
            assert_eq!(aspa.provider_asns(), &[Asn::from(64498u32)]);
        }
        _ => panic!("expected announced ASPA"),
    }
}

#[test]
fn get_deltas_since_merges_aspa_replacement_into_single_announcement() {
    let old = Aspa::new(Asn::from(64496u32), vec![Asn::from(64497u32)]);
    let new = Aspa::new(Asn::from(64496u32), vec![Asn::from(64498u32)]);

    let d1 = Arc::new(Delta::new(101, vec![], vec![Payload::Aspa(old.clone())]));
    let d2 = Arc::new(Delta::new(102, vec![Payload::Aspa(new.clone())], vec![]));

    let mut deltas = VecDeque::new();
    deltas.push_back(d1);
    deltas.push_back(d2);

    let cache = RtrCacheBuilder::new()
        .session_ids(SessionIds::from_array([42, 42, 42]))
        .serials(serials_all(102))
        .timing(Timing::default())
        .snapshots(snapshots_all(Snapshot::from_payloads(vec![Payload::Aspa(new.clone())])))
        .deltas_by_version(deltas_all(deltas))
        .build();

    let result = cache.get_deltas_since_for_version(2, 100);

    match result {
        SerialResult::Delta(delta) => {
            assert_eq!(delta.announced().len(), 1);
            assert!(delta.withdrawn().is_empty());

            match &delta.announced()[0] {
                Payload::Aspa(aspa) => assert_eq!(aspa, &new),
                _ => panic!("expected announced ASPA"),
            }
        }
        _ => panic!("expected Delta"),
    }
}

#[test]
fn validate_payloads_for_rtr_rejects_unsorted_snapshot_payloads() {
    let low = Payload::RouteOrigin(v4_origin(192, 0, 2, 0, 24, 24, 64496));
    let high = Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497));

    let err = validate_payloads_for_rtr(&[low, high], true).unwrap_err();
    assert!(err.to_string().contains("RTR payload ordering violation"));
}

#[test]
fn validate_payload_updates_for_rtr_rejects_unsorted_aspa_updates() {
    let withdraw = (
        false,
        Payload::Aspa(Aspa::new(Asn::from(64497u32), vec![Asn::from(64500u32)])),
    );
    let announce = (
        true,
        Payload::Aspa(Aspa::new(Asn::from(64496u32), vec![Asn::from(64499u32)])),
    );

    let err = validate_payload_updates_for_rtr(&[withdraw, announce]).unwrap_err();
    assert!(err.to_string().contains("withdraw ASPA"));
    assert!(err.to_string().contains("announce ASPA"));
}
