nvpn 4.1.9

CLI and daemon for Nostr VPN private mesh networks
Documentation
use super::*;

#[test]
fn payment_outbox_retry_is_bounded_and_logs_only_state_changes() {
    let start = Instant::now();
    let mut retry = PaidExitPaymentOutboxRetry::new(start);

    assert!(retry.due(start));
    assert!(retry.record_flush(start, 1, 0));
    assert!(!retry.due(start + Duration::from_secs(PAID_EXIT_PAYMENT_OUTBOX_RETRY_SECS - 1)));
    let next = start + Duration::from_secs(PAID_EXIT_PAYMENT_OUTBOX_RETRY_SECS);
    assert!(retry.due(next));
    assert!(!retry.record_flush(next, 1, 0));
    assert!(retry.record_flush(
        next + Duration::from_secs(PAID_EXIT_PAYMENT_OUTBOX_RETRY_SECS),
        0,
        0
    ));
}
use nostr_sdk::prelude::{Keys, ToBech32};
use nostr_vpn_core::paid_route_store::{
    OpenPaidRouteBuyerSessionRequest, PaidRouteLifecycleStatus,
};
use nostr_vpn_core::paid_routes::{
    PaidExitConfig, PaidRouteChannelTerms, PaidRouteIpSupport, signed_paid_exit_offer_from_config,
};

#[test]
fn manual_unacknowledged_session_fails_and_falls_back_to_direct() {
    let seller = Keys::generate();
    let seller_npub = seller.public_key().to_bech32().expect("seller npub");
    let mint = "https://mint.example";
    let offer_config = PaidExitConfig {
        enabled: true,
        channel: PaidRouteChannelTerms {
            accepted_mints: vec![mint.to_string()],
            max_channel_capacity_sat: 100,
            channel_expiry_secs: 600,
            free_probe_units: 1_048_576,
            ..PaidRouteChannelTerms::default()
        },
        ip_support: PaidRouteIpSupport {
            ipv4: true,
            ..PaidRouteIpSupport::default()
        },
        ..PaidExitConfig::default()
    };
    let signed =
        signed_paid_exit_offer_from_config("manual-timeout", &seller, &offer_config, None, 100)
            .expect("signed offer");
    let directory = std::env::temp_dir().join(format!(
        "nvpn-manual-paid-exit-timeout-{}",
        std::process::id()
    ));
    std::fs::create_dir_all(&directory).expect("create test directory");
    let config_path = directory.join("config.toml");
    let store_path = paid_route_store_file_path(&config_path);
    let mut app = AppConfig::generated();
    let buyer_npub = app
        .nostr_keys()
        .expect("buyer keys")
        .public_key()
        .to_bech32()
        .expect("buyer npub");

    let session = update_paid_route_store(&store_path, |store| {
        store.upsert_wallet_mint(mint, "test", None, 100);
        store.upsert_signed_offer(signed, Vec::new(), 100)?;
        let session = store.open_buyer_session(OpenPaidRouteBuyerSessionRequest {
            offer_selector: "manual-timeout".to_string(),
            buyer_npub,
            mint_url: Some(mint.to_string()),
            channel_capacity_sat: Some(20),
            initial_paid_msat: 0,
            now_unix: 100,
        })?;
        store.begin_buyer_session_open_attempt(&session.session_id, 100)?;
        Ok(session)
    })
    .expect("create selected buyer session");
    app.select_public_paid_exit_node(&seller_npub)
        .expect("select manual seller");
    app.save(&config_path).expect("save manual selection");

    let early = reconcile_selected_paid_exit_session(
        &mut app,
        &config_path,
        &seller.public_key().to_hex(),
        129,
    )
    .expect("early reconciliation");
    assert!(!early.selected_session_timed_out);
    assert_eq!(
        app.internet_source,
        nostr_vpn_core::config::InternetSource::PaidManual
    );

    let timed_out = reconcile_selected_paid_exit_session(
        &mut app,
        &config_path,
        &seller.public_key().to_hex(),
        130,
    )
    .expect("timeout reconciliation");
    assert!(timed_out.selected_session_timed_out);
    assert_eq!(timed_out.selected_session_id, session.session_id);
    assert_eq!(
        app.internet_source,
        nostr_vpn_core::config::InternetSource::Direct
    );
    assert_eq!(
        AppConfig::load(&config_path)
            .expect("saved direct fallback")
            .internet_source,
        nostr_vpn_core::config::InternetSource::Direct
    );
    let store = load_paid_route_store(&store_path).expect("reloaded failed session");
    assert_eq!(
        store.channels[&session.channel_id].status,
        PaidRouteLifecycleStatus::Failed
    );
    assert!(
        store.channels[&session.channel_id]
            .error
            .contains("acknowledge")
    );

    let _ = std::fs::remove_dir_all(directory);
}