synpad 0.1.0

A full-featured Matrix chat client built with Dioxus
//! Headless test: login, load timeline, send a message, verify it appears.
//!
//! Usage:
//!   cargo run --bin test_sdk -- <homeserver> <username> <password> [room_id]
//!
//! Example:
//!   cargo run --bin test_sdk -- https://matrix.org myuser mypass "!abc123:matrix.org"
//!
//! If room_id is omitted, lists joined rooms and exits.

use anyhow::Result;
use matrix_sdk::ruma::events::room::message::RoomMessageEventContent;
use matrix_sdk::ruma::OwnedRoomId;
use matrix_sdk::Client;

#[tokio::main]
async fn main() -> Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| "test_sdk=info,matrix_sdk=warn".parse().unwrap()),
        )
        .init();

    let args: Vec<String> = std::env::args().collect();
    if args.len() < 4 {
        eprintln!("Usage: test_sdk <homeserver> <username> <password> [room_id]");
        eprintln!("Example: test_sdk https://matrix.org myuser mypass \"!abc:matrix.org\"");
        std::process::exit(1);
    }

    let homeserver = &args[1];
    let username = &args[2];
    let password = &args[3];
    let room_id_str = args.get(4).map(|s| s.as_str());

    // --- Step 1: Build client ---
    println!("\n[1/5] Building client for {homeserver}...");
    let data_dir = std::env::temp_dir().join("synpad-test-sdk");
    std::fs::create_dir_all(&data_dir)?;

    let client = Client::builder()
        .homeserver_url(homeserver)
        .sqlite_store(&data_dir, None)
        .build()
        .await?;
    println!("  OK - Client built (store: {})", data_dir.display());

    // --- Step 2: Login ---
    println!("\n[2/5] Logging in as {username}...");
    client
        .matrix_auth()
        .login_username(username, password)
        .initial_device_display_name("Netrix Test")
        .await?;

    let user_id = client.user_id().expect("No user ID after login");
    let device_id = client.device_id().expect("No device ID after login");
    println!("  OK - Logged in as {user_id} (device: {device_id})");

    // --- Step 3: Initial sync ---
    println!("\n[3/5] Running initial sync...");
    let sync_settings = matrix_sdk::config::SyncSettings::default()
        .timeout(std::time::Duration::from_secs(10));
    client.sync_once(sync_settings).await?;
    println!("  OK - Sync complete");

    // --- List rooms ---
    let joined = client.joined_rooms();
    println!("\n  Joined rooms ({}):", joined.len());
    for room in &joined {
        let name = match room.display_name().await {
            Ok(n) => n.to_string(),
            Err(_) => "(unknown)".to_string(),
        };
        let members = room.joined_members_count();
        println!("    {} - {} ({} members)", room.room_id(), name, members);
    }

    let Some(room_id_str) = room_id_str else {
        println!("\n  No room_id provided. Pass a room ID as 4th argument to test messaging.");
        println!("  Example: cargo run --bin test_sdk -- {homeserver} {username} <password> \"!roomid:server\"");
        cleanup(&client).await;
        return Ok(());
    };

    // --- Step 4: Load timeline ---
    let room_id: OwnedRoomId = room_id_str.try_into()?;
    let room = client
        .get_room(&room_id)
        .ok_or_else(|| anyhow::anyhow!("Room not found: {room_id}"))?;

    println!("\n[4/5] Loading timeline for {room_id}...");
    let options = matrix_sdk::room::MessagesOptions::backward();
    let response = room.messages(options).await?;
    println!(
        "  OK - Got {} events (pagination token: {})",
        response.chunk.len(),
        response.end.as_deref().unwrap_or("none")
    );

    // Parse and display recent messages
    for event in response.chunk.iter().rev().take(5) {
        let raw = event.raw();
        if let Ok(json) = serde_json::from_str::<serde_json::Value>(raw.json().get()) {
            let sender = json.get("sender").and_then(|v| v.as_str()).unwrap_or("?");
            let event_type = json.get("type").and_then(|v| v.as_str()).unwrap_or("?");
            if event_type == "m.room.message" {
                let body = json
                    .get("content")
                    .and_then(|c| c.get("body"))
                    .and_then(|b| b.as_str())
                    .unwrap_or("(no body)");
                println!("    [{sender}] {body}");
            } else {
                println!("    [{sender}] ({event_type})");
            }
        }
    }

    // --- Step 5: Send a test message ---
    let test_msg = format!(
        "Netrix headless test - {}",
        chrono::Local::now().format("%Y-%m-%d %H:%M:%S")
    );
    println!("\n[5/5] Sending test message: \"{test_msg}\"...");

    let content = RoomMessageEventContent::text_plain(&test_msg);
    let _send_response = room.send(content).await?;
    println!("  OK - Message sent!");

    // Sync again to verify message appears
    println!("\n  Syncing to verify...");
    let sync_settings = matrix_sdk::config::SyncSettings::default()
        .timeout(std::time::Duration::from_secs(5));
    client.sync_once(sync_settings).await?;

    // Reload timeline
    let options = matrix_sdk::room::MessagesOptions::backward();
    let response = room.messages(options).await?;
    let mut found = false;
    for event in &response.chunk {
        let raw = event.raw();
        if let Ok(json) = serde_json::from_str::<serde_json::Value>(raw.json().get()) {
            let body = json
                .get("content")
                .and_then(|c| c.get("body"))
                .and_then(|b| b.as_str())
                .unwrap_or("");
            if body == test_msg {
                found = true;
                break;
            }
        }
    }

    if found {
        println!("  VERIFIED - Test message found in timeline!");
    } else {
        println!("  WARNING - Test message not yet visible in timeline (may appear after next sync)");
    }

    // Cleanup
    cleanup(&client).await;

    println!("\n=== All tests passed! ===\n");
    Ok(())
}

async fn cleanup(client: &Client) {
    println!("\n  Logging out test device...");
    if let Err(e) = client.matrix_auth().logout().await {
        eprintln!("  Warning: logout failed: {e}");
    } else {
        println!("  OK - Logged out");
    }
}