openlark 0.15.0

飞书开放平台 Rust SDK - 企业级高覆盖率 API 客户端,极简依赖一条命令
Documentation
use fake::{Fake, Faker};
use lark_websocket_protobuf::pbbp2::{Frame, Header};
use open_lark::client::ws_client::frame_handler::FrameHandler;
use proptest::prelude::*;
use quickcheck::{quickcheck, TestResult};
use serde_json::Value;
use std::collections::HashMap;

/// WebSocket 帧处理器的属性测试
/// 
/// 这些测试验证帧处理器在各种边界条件和异常输入下的行为:
/// - 空载荷、极大载荷
/// - 畸形 JSON 数据
/// - 无效头部信息
/// - 错误的帧类型

#[cfg(test)]
mod frame_handler_properties {
    use super::*;

    /// 生成随机帧数据的策略
    fn arbitrary_frame() -> impl Strategy<Value = Frame> {
        (
            any::<u64>(),  // seq_id
            any::<u64>(),  // log_id
            0..100i32,     // service
            0..2i32,       // method (0=control, 1=data)
            prop::collection::vec(arbitrary_header(), 0..10), // headers
            prop::option::of(prop::collection::vec(any::<u8>(), 0..10000)), // payload
        ).prop_map(|(seq_id, log_id, service, method, headers, payload)| Frame {
            seq_id,
            log_id,
            service,
            method,
            headers,
            payload_encoding: None,
            payload_type: None,
            payload,
            log_id_new: None,
        })
    }

    /// 生成随机头部的策略
    fn arbitrary_header() -> impl Strategy<Value = Header> {
        (
            "[a-zA-Z0-9_]{1,50}",  // key
            ".*",                  // value (任意字符串)
        ).prop_map(|(key, value)| Header { key, value })
    }

    /// 属性测试:帧处理器不应该因为任意输入而崩溃
    proptest! {
        #[test]
        fn frame_handler_never_panics(frame in arbitrary_frame()) {
            // 创建简单的事件处理器和通道
            let (tx, _rx) = tokio::sync::mpsc::unbounded_channel();
            let handler = open_lark::event::dispatcher::EventDispatcherHandler::builder().build();
            
            // 在异步运行时中测试
            let rt = tokio::runtime::Runtime::new().unwrap();
            rt.block_on(async {
                // 这不应该崩溃,即使输入是畸形的
                let _result = FrameHandler::handle_frame(frame, &handler, &tx).await;
                // 我们不关心结果是什么,只要不崩溃即可
            });
        }
    }

    /// 属性测试:控制帧应该始终返回 None 或 Some(Frame),从不崩溃
    proptest! {
        #[test]
        fn control_frame_handling_is_safe(
            seq_id in any::<u64>(),
            headers in prop::collection::vec(arbitrary_header(), 0..10),
            payload in prop::option::of(prop::collection::vec(any::<u8>(), 0..1000))
        ) {
            let frame = Frame {
                seq_id,
                log_id: 0,
                service: 0,
                method: 0, // Control frame
                headers,
                payload_encoding: None,
                payload_type: None,
                payload,
                log_id_new: None,
            };

            let (tx, _rx) = tokio::sync::mpsc::unbounded_channel();
            let handler = open_lark::event::dispatcher::EventDispatcherHandler::builder().build();
            
            let rt = tokio::runtime::Runtime::new().unwrap();
            rt.block_on(async {
                let _result = FrameHandler::handle_frame(frame, &handler, &tx).await;
                // 控制帧处理应该总是安全的
            });
        }
    }

    /// 属性测试:数据帧处理应该能处理任意载荷
    proptest! {
        #[test]
        fn data_frame_handles_arbitrary_payloads(
            payload_bytes in prop::collection::vec(any::<u8>(), 0..5000)
        ) {
            let frame = Frame {
                seq_id: 1,
                log_id: 1,
                service: 1,
                method: 1, // Data frame
                headers: vec![
                    Header { key: "type".to_string(), value: "event".to_string() },
                    Header { key: "message_id".to_string(), value: "test_msg".to_string() },
                    Header { key: "trace_id".to_string(), value: "test_trace".to_string() },
                ],
                payload_encoding: None,
                payload_type: None,
                payload: Some(payload_bytes),
                log_id_new: None,
            };

            let (tx, _rx) = tokio::sync::mpsc::unbounded_channel();
            let handler = open_lark::event::dispatcher::EventDispatcherHandler::builder().build();
            
            let rt = tokio::runtime::Runtime::new().unwrap();
            rt.block_on(async {
                let _result = FrameHandler::handle_frame(frame, &handler, &tx).await;
                // 数据帧处理应该能处理任意载荷而不崩溃
            });
        }
    }

    /// QuickCheck 测试:帧序列化和反序列化的一致性
    #[quickcheck]
    fn frame_serialization_roundtrip(seq_id: u64, service: i32, method: u8) -> TestResult {
        if method > 1 { return TestResult::discard(); }
        
        let original_frame = Frame {
            seq_id,
            log_id: 0,
            service,
            method: method as i32,
            headers: vec![],
            payload_encoding: None,
            payload_type: None,
            payload: Some(vec![1, 2, 3]),
            log_id_new: None,
        };

        // 这里我们测试帧的基本属性保持一致
        TestResult::from_bool(
            original_frame.seq_id == seq_id &&
            original_frame.service == service &&
            original_frame.method == method as i32
        )
    }

    /// 边界条件测试:极大载荷处理
    #[tokio::test]
    async fn test_extremely_large_payload() {
        let large_payload = vec![0u8; 1024 * 1024]; // 1MB payload
        let frame = Frame {
            seq_id: 1,
            log_id: 1,
            service: 1,
            method: 1,
            headers: vec![
                Header { key: "type".to_string(), value: "event".to_string() },
            ],
            payload_encoding: None,
            payload_type: None,
            payload: Some(large_payload),
            log_id_new: None,
        };

        let (tx, _rx) = tokio::sync::mpsc::unbounded_channel();
        let handler = open_lark::event::dispatcher::EventDispatcherHandler::new();
        
        // 应该能处理大载荷而不崩溃
        let _result = FrameHandler::handle_frame(frame, &handler, &tx).await;
    }

    /// 边界条件测试:空载荷处理
    #[tokio::test]
    async fn test_empty_payload() {
        let frame = Frame {
            seq_id: 1,
            log_id: 1,
            service: 1,
            method: 1,
            headers: vec![
                Header { key: "type".to_string(), value: "event".to_string() },
            ],
            payload_encoding: None,
            payload_type: None,
            payload: None,
        };

        let (tx, _rx) = tokio::sync::mpsc::unbounded_channel();
        let handler = open_lark::event::dispatcher::EventDispatcherHandler::new();
        
        let result = FrameHandler::handle_frame(frame, &handler, &tx).await;
        // 空载荷的数据帧应该返回 None(被正确拒绝)
        assert!(result.is_none());
    }

    /// 畸形 JSON 处理测试
    #[tokio::test]
    async fn test_malformed_json_in_pong() {
        let malformed_json = b"{invalid json}";
        let frame = Frame {
            seq_id: 1,
            log_id: 1,
            service: 1,
            method: 0, // Control frame
            headers: vec![
                Header { key: "type".to_string(), value: "pong".to_string() },
            ],
            payload_encoding: None,
            payload_type: None,
            payload: Some(malformed_json.to_vec()),
            log_id_new: None,
        };

        let (tx, _rx) = tokio::sync::mpsc::unbounded_channel();
        let handler = open_lark::event::dispatcher::EventDispatcherHandler::new();
        
        let result = FrameHandler::handle_frame(frame, &handler, &tx).await;
        // 畸形 JSON 应该被优雅处理,返回 None
        assert!(result.is_none());
    }

    /// 测试无效头部组合
    #[tokio::test]
    async fn test_invalid_header_combinations() {
        let frame = Frame {
            seq_id: 1,
            log_id: 1,
            service: 1,
            method: 1,
            headers: vec![
                // 缺少必要的头部信息
                Header { key: "invalid_type".to_string(), value: "unknown".to_string() },
            ],
            payload_encoding: None,
            payload_type: None,
            payload: Some(b"some data".to_vec()),
            log_id_new: None,
        };

        let (tx, _rx) = tokio::sync::mpsc::unbounded_channel();
        let handler = open_lark::event::dispatcher::EventDispatcherHandler::new();
        
        let result = FrameHandler::handle_frame(frame, &handler, &tx).await;
        // 无效头部应该被优雅处理
        assert!(result.is_none());
    }
}

/// 用于生成测试数据的辅助函数
mod test_generators {
    use super::*;

    /// 生成有效的事件载荷
    pub fn generate_valid_event_payload() -> Vec<u8> {
        let event = serde_json::json!({
            "event": {
                "sender": {
                    "sender_id": {
                        "open_id": "test_open_id"
                    }
                },
                "message": {
                    "content": "{\"text\":\"test message\"}"
                }
            }
        });
        serde_json::to_vec(&event).unwrap()
    }

    /// 生成无效的事件载荷
    pub fn generate_invalid_event_payloads() -> Vec<Vec<u8>> {
        vec![
            // 空 JSON
            b"{}".to_vec(),
            // 不完整的 JSON
            b"{\"event\":}".to_vec(),
            // 非 JSON 数据
            b"not json at all".to_vec(),
            // 包含 null 字节
            vec![123, 34, 116, 101, 115, 116, 34, 58, 0, 125],
            // 极长的字符串
            format!("{{\"data\":\"{}\"}}", "x".repeat(100000)).into_bytes(),
        ]
    }
}