// Copyright (C) 2024 Huawei Device Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::collections::HashMap;
use std::sync::Arc;
use ylong_http_client::Headers;
use ylong_runtime::net::UnixDatagram;
use ylong_runtime::sync::mpsc::{unbounded_channel, UnboundedSender};
use ylong_runtime::sync::oneshot;
use crate::service::client::{Client, ClientEvent, MessageType};
use crate::task::notify::{NotifyData, SubscribeType, WaitingCause};
use crate::task::reason::Reason;
use crate::config::Version;
#[cfg(test)]
mod tests {
use super::*;
// Constants for testing
const TEST_PID: u64 = 12345;
const TEST_TID: u32 = 42;
// @tc.name: ut_client_message_type_variants
// @tc.desc: Test MessageType enum variants have correct values
// @tc.precon: MessageType enum is defined
// @tc.step: 1. Check each variant's discriminant value
// @tc.expect: Values match expected constants
// @tc.type: FUNC
// @tc.require: issue#ICODTG
// @tc.level: Level 0
#[test]
fn ut_client_message_type_variants_001() {
assert_eq!(MessageType::HttpResponse as u16, 0);
assert_eq!(MessageType::NotifyData as u16, 1);
assert_eq!(MessageType::Faults as u16, 2);
assert_eq!(MessageType::Waiting as u16, 3);
}
// @tc.name: ut_client_event_variants
// @tc.desc: Test ClientEvent enum variants can be constructed
// @tc.precon: ClientEvent enum is defined
// @tc.step: 1. Create each variant of ClientEvent
// @tc.expect: All variants can be instantiated
// @tc.type: FUNC
// @tc.require: issue#ICODTG
// @tc.level: Level 0
#[test]
fn ut_client_event_variants_001() {
let (tx, _rx) = oneshot::channel();
let headers = Headers::new();
let _ = ClientEvent::OpenChannel(TEST_PID, tx.clone());
let _ = ClientEvent::Subscribe(TEST_TID, TEST_PID, 1000, 2000, tx.clone());
let _ = ClientEvent::Unsubscribe(TEST_TID, tx.clone());
let _ = ClientEvent::TaskFinished(TEST_TID);
let _ = ClientEvent::Terminate(TEST_PID, tx.clone());
let _ = ClientEvent::SendResponse(TEST_TID, "HTTP/1.1".to_string(), 200, "OK".to_string(), headers);
let _ = ClientEvent::SendNotifyData(SubscribeType::Progress, create_test_notify_data());
let _ = ClientEvent::SendFaults(TEST_TID, SubscribeType::Complete, Reason::Success);
let _ = ClientEvent::SendWaitNotify(TEST_TID, WaitingCause::NetworkUnavailable);
let _ = ClientEvent::Shutdown;
}
// @tc.name: ut_client_constructor_socket_creation
// @tc.desc: Test UnixDatagram socket creation in Client constructor
// @tc.precon: Unix socket support is available
// @tc.step: 1. Call Client::constructor
// 2. Verify socket pair creation
// @tc.expect: Returns Some with valid sender and socket
// @tc.type: FUNC
// @tc.require: issue#ICODTG
// @tc.level: Level 1
#[test]
fn ut_client_constructor_socket_creation_001() {
let result = Client::constructor(TEST_PID);
assert!(result.is_some());
let (sender, socket) = result.unwrap();
// Verify sender works
assert!(sender.send(ClientEvent::TaskFinished(TEST_TID)).is_ok());
// Verify socket is valid
assert!(Arc::strong_count(&socket) >= 1);
}
// @tc.name: ut_client_run_shutdown_handling
// @tc.desc: Test Client shutdown event handling
// @tc.precon: Client is constructed and running
// @tc.step: 1. Create Client
// 2. Send shutdown event
// 3. Verify graceful shutdown
// @tc.expect: Client terminates gracefully
// @tc.type: FUNC
// @tc.require: issue#ICODTG
// @tc.level: Level 1
#[ylong_runtime::test]
async fn ut_client_run_shutdown_handling_001() {
let (sender, _receiver) = unbounded_channel::<ClientEvent>();
let (server_sock, client_sock) = UnixDatagram::pair().unwrap();
let client = Client {
pid: TEST_PID,
message_id: 1,
server_sock_fd: server_sock,
client_sock_fd: Arc::new(client_sock),
rx: _receiver,
};
// Test would need to be more sophisticated for async testing
// For now, we verify the structure can be created
assert_eq!(client.pid, TEST_PID);
assert_eq!(client.message_id, 1);
}
// Message format tests
mod message_format_tests {
use super::*;
// @tc.name: ut_client_message_format_magic_number
// @tc.desc: Test magic number is correctly placed in messages
// @tc.precon: Message creation functions are available
// @tc.step: 1. Create a test message
// 2. Verify magic number at start
// @tc.expect: Magic number 0x43434646 is first 4 bytes
// @tc.type: FUNC
// @tc.require: issue#ICODTG
// @tc.level: Level 1
#[test]
fn ut_client_message_format_magic_number_001() {
let magic = 0x43434646u32.to_le_bytes();
assert_eq!(magic, [0x46, 0x46, 0x43, 0x43]);
}
// @tc.name: ut_client_message_format_length_field
// @tc.desc: Test length field positioning in messages
// @tc.precon: Message structure is defined
// @tc.step: 1. Create test message
// 2. Verify length field at position 10
// @tc.expect: Length field correctly positioned
// @tc.type: FUNC
// @tc.require: issue#ICODTG
// @tc.level: Level 1
#[test]
fn ut_client_message_format_length_field_001() {
const POSITION: u32 = 10;
assert_eq!(POSITION, 10);
}
// @tc.name: ut_client_message_format_headers_max_size
// @tc.desc: Test maximum headers size constraint
// @tc.precon: HEADERS_MAX_SIZE is defined
// @tc.step: 1. Check HEADERS_MAX_SIZE value
// 2. Verify it's 8KB
// @tc.expect: Max size is 8192 bytes
// @tc.type: FUNC
// @tc.require: issue#ICODTG
// @tc.level: Level 1
#[test]
fn ut_client_message_format_headers_max_size_001() {
assert_eq!(super::HEADERS_MAX_SIZE, 8 * 1024);
}
}
// Protocol compliance tests
mod protocol_tests {
use super::*;
// @tc.name: ut_client_protocol_response_format
// @tc.desc: Test HTTP response message format compliance
// @tc.precon: Client can create response messages
// @tc.step: 1. Create test response data
// 2. Verify message structure
// @tc.expect: Message follows protocol specification
// @tc.type: FUNC
// @tc.require: issue#ICODTG
// @tc.level: Level 1
#[test]
fn ut_client_protocol_response_format_001() {
let mut message = Vec::<u8>::new();
// Magic number
message.extend_from_slice(&0x43434646u32.to_le_bytes());
// Message ID
message.extend_from_slice(&1u32.to_le_bytes());
// Message type (HTTP Response)
message.extend_from_slice(&(MessageType::HttpResponse as u16).to_le_bytes());
// Message body size (placeholder)
message.extend_from_slice(&0u16.to_le_bytes());
// Task ID
message.extend_from_slice(&TEST_TID.to_le_bytes());
// HTTP version
message.extend_from_slice(b"HTTP/1.1\0");
// Status code
message.extend_from_slice(&200u32.to_le_bytes());
// Reason phrase
message.extend_from_slice(b"OK\0");
// Headers (empty for this test)
// Update length field
let size = message.len() as u16;
let size_bytes = size.to_le_bytes();
message[10] = size_bytes[0];
message[11] = size_bytes[1];
assert!(message.len() > 20); // Basic sanity check
}
// @tc.name: ut_client_protocol_notify_data_format
// @tc.desc: Test notify data message format compliance
// @tc.precon: Client can create notify data messages
// @tc.step: 1. Create test notify data
// 2. Verify message structure
// @tc.expect: Message follows protocol specification
// @tc.type: FUNC
// @tc.require: issue#ICODTG
// @tc.level: Level 1
#[test]
fn ut_client_protocol_notify_data_format_001() {
let notify_data = create_test_notify_data();
let mut message = Vec::<u8>::new();
// Magic number
message.extend_from_slice(&0x43434646u32.to_le_bytes());
// Message ID
message.extend_from_slice(&1u32.to_le_bytes());
// Message type (Notify Data)
message.extend_from_slice(&(MessageType::NotifyData as u16).to_le_bytes());
// Message body size (placeholder)
message.extend_from_slice(&0u16.to_le_bytes());
// Subscribe type
message.extend_from_slice(&(SubscribeType::Progress as u32).to_le_bytes());
// Task ID
message.extend_from_slice(¬ify_data.task_id.to_le_bytes());
// State
message.extend_from_slice(&(0u32).to_le_bytes()); // Placeholder state
// Index
message.extend_from_slice(&0u32.to_le_bytes());
// Processed bytes
message.extend_from_slice(&0u64.to_le_bytes());
// Total processed
message.extend_from_slice(&0u64.to_le_bytes());
// Sizes length
message.extend_from_slice(&0u32.to_le_bytes());
// Extras count
message.extend_from_slice(&0u32.to_le_bytes());
// Action
message.extend_from_slice(&(0u32).to_le_bytes());
// Version
message.extend_from_slice(&(Version::API9 as u32).to_le_bytes());
// File status count
message.extend_from_slice(&0u32.to_le_bytes());
// Update length field
let size = message.len() as u16;
let size_bytes = size.to_le_bytes();
message[10] = size_bytes[0];
message[11] = size_bytes[1];
assert!(message.len() > 40); // Basic sanity check
}
}
// Error handling tests
mod error_handling_tests {
use super::*;
// @tc.name: ut_client_error_socket_creation_failure
// @tc.desc: Test handling of UnixDatagram creation failure
// @tc.precon: System resource limits reached
// @tc.step: 1. Attempt to create many sockets
// 2. Verify graceful handling
// @tc.expect: Constructor returns None when resources exhausted
// @tc.type: FUNC
// @tc.require: issue#ICODTG
// @tc.level: Level 3
#[test]
fn ut_client_error_socket_creation_failure_001() {
// This test is platform-dependent and may not be reliable
// In a real scenario, we'd use mocking or resource limits
let result = Client::constructor(TEST_PID);
// On most systems, this should succeed for a single client
assert!(result.is_some());
}
// @tc.name: ut_client_error_invalid_task_id
// @tc.desc: Test handling of invalid task IDs
// @tc.precon: Client is running
// @tc.step: 1. Send events with edge case task IDs
// 2. Verify no crashes
// @tc.expect: System handles gracefully
// @tc.type: FUNC
// @tc.require: issue#ICODTG
// @tc.level: Level 2
#[test]
fn ut_client_error_invalid_task_id_001() {
let (sender, receiver) = unbounded_channel::<ClientEvent>();
// Test with zero task ID
let _ = sender.send(ClientEvent::TaskFinished(0));
// Test with maximum u32 task ID
let _ = sender.send(ClientEvent::TaskFinished(u32::MAX));
// Verify no panics
drop(receiver);
}
// @tc.name: ut_client_error_empty_headers
// @tc.desc: Test handling of empty headers in response
// @tc.precon: Client can send responses
// @tc.step: 1. Create response with empty headers
// 2. Verify message creation
// @tc.expect: Message created successfully with empty headers
// @tc.type: FUNC
// @tc.require: issue#ICODTG
// @tc.level: Level 1
#[test]
fn ut_client_error_empty_headers_001() {
let empty_headers = Headers::new();
let mut response = Vec::<u8>::new();
response.extend_from_slice(&0x43434646u32.to_le_bytes());
response.extend_from_slice(&1u32.to_le_bytes());
response.extend_from_slice(&(MessageType::HttpResponse as u16).to_le_bytes());
response.extend_from_slice(&0u16.to_le_bytes()); // body size
response.extend_from_slice(&TEST_TID.to_le_bytes());
response.extend_from_slice(b"HTTP/1.1\0");
response.extend_from_slice(&200u32.to_le_bytes());
response.extend_from_slice(b"OK\0");
// Empty headers section
let size = response.len() as u16;
let size_bytes = size.to_le_bytes();
response[10] = size_bytes[0];
response[11] = size_bytes[1];
assert!(response.len() > 20);
}
// @tc.name: ut_client_error_oversized_headers
// @tc.desc: Test handling of oversized headers
// @tc.precon: Headers exceed HEADERS_MAX_SIZE
// @tc.step: 1. Create headers larger than 8KB
// 2. Verify truncation
// @tc.expect: Headers are truncated to max size
// @tc.type: FUNC
// @tc.require: issue#ICODTG
// @tc.level: Level 2
#[test]
fn ut_client_error_oversized_headers_001() {
let mut headers = Headers::new();
// Create oversized headers
let large_value = "x".repeat(9000);
headers.insert("X-Large-Header".to_string(), vec![large_value]);
let mut response = Vec::<u8>::new();
response.extend_from_slice(&0x43434646u32.to_le_bytes());
response.extend_from_slice(&1u32.to_le_bytes());
response.extend_from_slice(&(MessageType::HttpResponse as u16).to_le_bytes());
response.extend_from_slice(&0u16.to_le_bytes());
response.extend_from_slice(&TEST_TID.to_le_bytes());
response.extend_from_slice(b"HTTP/1.1\0");
response.extend_from_slice(&200u32.to_le_bytes());
response.extend_from_slice(b"OK\0");
// Add headers (should be truncated)
let mut buf_size = 0;
for (k, v) in &headers {
buf_size += k.as_bytes().len() + v.iter().map(|f| f.len()).sum::<usize>();
if buf_size > super::HEADERS_MAX_SIZE as usize {
break;
}
response.extend_from_slice(k.as_bytes());
response.push(b':');
for (i, sub_value) in v.iter().enumerate() {
if i != 0 {
response.push(b',');
}
response.extend_from_slice(sub_value.as_bytes());
}
response.push(b'\n');
}
let mut size = response.len() as u16;
if size > super::HEADERS_MAX_SIZE {
response.truncate(super::HEADERS_MAX_SIZE as usize);
size = super::HEADERS_MAX_SIZE;
}
let size_bytes = size.to_le_bytes();
response[10] = size_bytes[0];
response[11] = size_bytes[1];
assert!(response.len() <= super::HEADERS_MAX_SIZE as usize);
}
}
// Edge case tests
mod edge_case_tests {
use super::*;
// @tc.name: ut_client_edge_case_zero_values
// @tc.desc: Test handling of all zero values
// @tc.precon: Client is initialized
// @tc.step: 1. Send events with all zero values
// 2. Verify message creation
// @tc.expect: System handles zero values correctly
// @tc.type: FUNC
// @tc.require: issue#ICODTG
// @tc.level: Level 2
#[test]
fn ut_client_edge_case_zero_values_001() {
let notify_data = NotifyData {
task_id: 0,
progress: Default::default(),
action: Reason::Success,
version: Version::API9,
each_file_status: vec![],
};
let mut message = Vec::<u8>::new();
message.extend_from_slice(&0x43434646u32.to_le_bytes());
message.extend_from_slice(&0u32.to_le_bytes()); // message_id
message.extend_from_slice(&(MessageType::NotifyData as u16).to_le_bytes());
message.extend_from_slice(&0u16.to_le_bytes());
message.extend_from_slice(&(SubscribeType::Progress as u32).to_le_bytes());
message.extend_from_slice(&0u32.to_le_bytes()); // task_id
message.extend_from_slice(&0u32.to_le_bytes()); // state
message.extend_from_slice(&0u32.to_le_bytes()); // index
message.extend_from_slice(&0u64.to_le_bytes()); // processed
message.extend_from_slice(&0u64.to_le_bytes()); // total
message.extend_from_slice(&0u32.to_le_bytes()); // sizes len
message.extend_from_slice(&0u32.to_le_bytes()); // extras count
message.extend_from_slice(&(Reason::Success as u32).to_le_bytes());
message.extend_from_slice(&(Version::API9 as u32).to_le_bytes());
message.extend_from_slice(&0u32.to_le_bytes()); // file status count
let size = message.len() as u16;
let size_bytes = size.to_le_bytes();
message[10] = size_bytes[0];
message[11] = size_bytes[1];
assert!(message.len() > 30);
}
// @tc.name: ut_client_edge_case_max_values
// @tc.desc: Test handling of maximum values
// @tc.precon: Client is initialized
// @tc.step: 1. Send events with maximum values
// 2. Verify message creation
// @tc.expect: System handles max values correctly
// @tc.type: FUNC
// @tc.require: issue#ICODTG
// @tc.level: Level 2
#[test]
fn ut_client_edge_case_max_values_001() {
let max_u32 = u32::MAX;
let max_u64 = u64::MAX;
// Test with max values where appropriate
let mut message = Vec::<u8>::new();
message.extend_from_slice(&0x43434646u32.to_le_bytes());
message.extend_from_slice(&max_u32.to_le_bytes()); // message_id
message.extend_from_slice(&(MessageType::NotifyData as u16).to_le_bytes());
message.extend_from_slice(&0u16.to_le_bytes());
message.extend_from_slice(&(SubscribeType::Progress as u32).to_le_bytes());
message.extend_from_slice(&max_u32.to_le_bytes()); // task_id
let size = message.len() as u16;
let size_bytes = size.to_le_bytes();
message[10] = size_bytes[0];
message[11] = size_bytes[1];
// Just verify the message was created without panic
assert!(message.len() > 20);
}
// @tc.name: ut_client_edge_case_unicode_strings
// @tc.desc: Test handling of Unicode strings in headers
// @tc.precon: Unicode support is available
// @tc.step: 1. Create headers with Unicode content
// 2. Verify message creation
// @tc.expect: Unicode strings are handled correctly
// @tc.type: FUNC
// @tc.require: issue#ICODTG
// @tc.level: Level 2
#[test]
fn ut_client_edge_case_unicode_strings_001() {
let mut headers = Headers::new();
headers.insert("Content-Type".to_string(), vec!["text/plain; charset=utf-8".to_string()]);
headers.insert("X-Unicode-Header".to_string(), vec!["测试".to_string()]);
let mut response = Vec::<u8>::new();
response.extend_from_slice(&0x43434646u32.to_le_bytes());
response.extend_from_slice(&1u32.to_le_bytes());
response.extend_from_slice(&(MessageType::HttpResponse as u16).to_le_bytes());
response.extend_from_slice(&0u16.to_le_bytes());
response.extend_from_slice(&TEST_TID.to_le_bytes());
response.extend_from_slice(b"HTTP/1.1\0");
response.extend_from_slice(&200u32.to_le_bytes());
response.extend_from_slice(b"OK\0");
// Add Unicode headers
for (k, v) in &headers {
response.extend_from_slice(k.as_bytes());
response.push(b':');
for (i, sub_value) in v.iter().enumerate() {
if i != 0 {
response.push(b',');
}
response.extend_from_slice(sub_value.as_bytes());
}
response.push(b'\n');
}
let size = response.len() as u16;
let size_bytes = size.to_le_bytes();
response[10] = size_bytes[0];
response[11] = size_bytes[1];
assert!(response.len() > 50);
}
}
// Helper function to create test NotifyData
fn create_test_notify_data() -> NotifyData {
NotifyData {
task_id: TEST_TID,
progress: Default::default(),
action: Reason::Success,
version: Version::API9,
each_file_status: vec![],
}
}
}