blvm-node 0.1.2

Bitcoin Commons BLVM: Minimal Bitcoin node implementation using blvm-protocol and blvm-consensus
//! Tests for RPC batch request edge cases

use blvm_node::rpc::server::RpcServer;
use serde_json::{json, Value};

// Helper to create a test RPC server
fn create_test_server() -> RpcServer {
    RpcServer::new("127.0.0.1:0".parse().unwrap())
}

#[tokio::test]
async fn test_rpc_batch_empty_array() {
    let server = create_test_server();

    // Empty batch should return empty array
    let batch: Vec<Value> = vec![];
    let batch_json = serde_json::to_string(&batch).unwrap();

    // Process as batch (empty array)
    let result = process_batch_request(&server, &batch_json).await;
    assert!(result.is_ok());

    let response: Vec<Value> = serde_json::from_str(&result.unwrap()).unwrap();
    assert!(response.is_empty());
}

#[tokio::test]
async fn test_rpc_batch_single_request() {
    let server = create_test_server();

    // Single request in batch should work
    let batch = vec![json!({
        "jsonrpc": "2.0",
        "method": "getblockcount",
        "id": 1
    })];
    let batch_json = serde_json::to_string(&batch).unwrap();

    let result = process_batch_request(&server, &batch_json).await;
    // Should process successfully (even if method doesn't exist, should return error response)
    assert!(result.is_ok());
}

#[tokio::test]
async fn test_rpc_batch_mixed_valid_invalid() {
    let server = create_test_server();

    // Mix of valid and invalid requests
    let batch = vec![
        json!({
            "jsonrpc": "2.0",
            "method": "getblockcount",
            "id": 1
        }),
        json!({
            "jsonrpc": "2.0",
            "method": "nonexistent",
            "id": 2
        }),
        json!({
            "jsonrpc": "2.0",
            "invalid": "request",
            "id": 3
        }),
    ];
    let batch_json = serde_json::to_string(&batch).unwrap();

    let result = process_batch_request(&server, &batch_json).await;
    assert!(result.is_ok());

    let response: Vec<Value> = serde_json::from_str(&result.unwrap()).unwrap();
    assert_eq!(response.len(), 3); // Should have 3 responses
}

#[tokio::test]
async fn test_rpc_batch_duplicate_ids() {
    let server = create_test_server();

    // Multiple requests with same ID
    let batch = vec![
        json!({
            "jsonrpc": "2.0",
            "method": "getblockcount",
            "id": 1
        }),
        json!({
            "jsonrpc": "2.0",
            "method": "getblockcount",
            "id": 1
        }),
    ];
    let batch_json = serde_json::to_string(&batch).unwrap();

    let result = process_batch_request(&server, &batch_json).await;
    // Should handle duplicate IDs (each request gets its own response)
    assert!(result.is_ok());

    let response: Vec<Value> = serde_json::from_str(&result.unwrap()).unwrap();
    assert_eq!(response.len(), 2);
}

#[tokio::test]
async fn test_rpc_batch_missing_ids() {
    let server = create_test_server();

    // Requests without IDs (notification)
    let batch = vec![
        json!({
            "jsonrpc": "2.0",
            "method": "getblockcount"
        }),
        json!({
            "jsonrpc": "2.0",
            "method": "getblockcount",
            "id": 1
        }),
    ];
    let batch_json = serde_json::to_string(&batch).unwrap();

    let result = process_batch_request(&server, &batch_json).await;
    assert!(result.is_ok());

    let response: Vec<Value> = serde_json::from_str(&result.unwrap()).unwrap();
    // Notifications don't get responses, so should have 1 response
    assert_eq!(response.len(), 1);
}

#[tokio::test]
async fn test_rpc_batch_large_batch() {
    let server = create_test_server();

    // Large batch (100 requests)
    let mut batch = Vec::new();
    for i in 0..100 {
        batch.push(json!({
            "jsonrpc": "2.0",
            "method": "getblockcount",
            "id": i
        }));
    }
    let batch_json = serde_json::to_string(&batch).unwrap();

    let result = process_batch_request(&server, &batch_json).await;
    assert!(result.is_ok());

    let response: Vec<Value> = serde_json::from_str(&result.unwrap()).unwrap();
    assert_eq!(response.len(), 100);
}

#[tokio::test]
async fn test_rpc_batch_invalid_json() {
    let server = create_test_server();

    // Invalid JSON in batch
    let invalid_json = "[{ invalid json }]";

    let result = process_batch_request(&server, invalid_json).await;
    // Should handle gracefully
    assert!(result.is_ok() || result.is_err()); // Either is acceptable
}

#[tokio::test]
async fn test_rpc_batch_malformed_requests() {
    let server = create_test_server();

    // Malformed requests in batch
    let batch = vec![
        json!({}),                 // Empty object
        json!({"jsonrpc": "1.0"}), // Wrong version
        json!({"method": "test"}), // Missing jsonrpc
    ];
    let batch_json = serde_json::to_string(&batch).unwrap();

    let result = process_batch_request(&server, &batch_json).await;
    assert!(result.is_ok());

    let response: Vec<Value> = serde_json::from_str(&result.unwrap()).unwrap();
    // Should have error responses for malformed requests
    assert_eq!(response.len(), 3);
}

// Helper function to process batch requests
// This is a simplified version - actual implementation would use the RPC server's process_request method
async fn process_batch_request(_server: &RpcServer, body: &str) -> Result<String, String> {
    // Parse as JSON
    let value: Value = serde_json::from_str(body).map_err(|e| e.to_string())?;

    // Check if it's an array (batch) or object (single)
    if let Value::Array(requests) = value {
        let mut responses = Vec::new();
        for request in requests {
            // Process each request (simplified - actual would call server method)
            // Malformed requests should still get error responses
            let id = request.get("id").cloned();
            let has_jsonrpc = request.get("jsonrpc").and_then(|v| v.as_str()) == Some("2.0");

            if !has_jsonrpc || request.get("method").is_none() {
                // Invalid request - should return error
                responses.push(json!({
                    "jsonrpc": "2.0",
                    "error": {
                        "code": -32600,
                        "message": "Invalid Request"
                    },
                    "id": id
                }));
            } else if let Some(id) = id {
                // Valid request with ID
                responses.push(json!({
                    "jsonrpc": "2.0",
                    "error": {
                        "code": -32601,
                        "message": "Method not found"
                    },
                    "id": id
                }));
            }
            // Notifications (no id) don't get responses
        }
        Ok(serde_json::to_string(&responses).unwrap())
    } else {
        // Single request
        Ok(
            r#"{"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not found"},"id":null}"#
                .to_string(),
        )
    }
}