Files
MyAiDesk/nomifun-tauri/crates/backend/nomifun-office/tests/star_office_integration.rs
T
freedak f7a720204a Update: 将子项目从 submodule 转为完整内容
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用
- 添加所有子项目的完整源代码
- 保留原始 .git 为 .git.bak 备份
2026-07-04 19:20:46 +08:00

322 lines
11 KiB
Rust

use std::net::TcpListener;
use std::sync::Arc;
use nomifun_office::StarOfficeDetector;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
fn allocate_port() -> u16 {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
listener.local_addr().unwrap().port()
}
async fn mock_star_office_server(
port: u16,
health_ok: bool,
status_body: &'static str,
index_body: &'static str,
) -> tokio::task::JoinHandle<()> {
let listener = tokio::net::TcpListener::bind(format!("127.0.0.1:{port}"))
.await
.unwrap();
tokio::spawn(async move {
loop {
let (mut stream, _) = match listener.accept().await {
Ok(s) => s,
Err(_) => break,
};
let status_body = status_body.to_string();
let index_body = index_body.to_string();
tokio::spawn(async move {
let mut buf = vec![0u8; 4096];
let n = match stream.read(&mut buf).await {
Ok(n) => n,
Err(_) => return,
};
let request = String::from_utf8_lossy(&buf[..n]);
let path = request
.lines()
.next()
.and_then(|line| line.split_whitespace().nth(1))
.unwrap_or("/");
let (status_line, body) = match path {
"/health" => {
if health_ok {
("HTTP/1.1 200 OK", "ok".to_string())
} else {
("HTTP/1.1 503 Service Unavailable", "down".to_string())
}
}
"/status" => ("HTTP/1.1 200 OK", status_body),
"/" => ("HTTP/1.1 200 OK", index_body),
_ => ("HTTP/1.1 404 Not Found", "not found".to_string()),
};
let response = format!(
"{status_line}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}",
body.len()
);
let _ = stream.write_all(response.as_bytes()).await;
let _ = stream.shutdown().await;
});
}
})
}
// ---------------------------------------------------------------------------
// SO-1: No available service → returns None
// ---------------------------------------------------------------------------
#[tokio::test]
async fn so1_no_service_returns_none() {
let detector = StarOfficeDetector::new(reqwest::Client::new());
let port = allocate_port();
let url = format!("http://localhost:{port}");
let result = detector.detect_exact(Some(&url), true, Some(50)).await;
assert!(result.is_none());
}
// ---------------------------------------------------------------------------
// SO-2: With preferred URL, no service → returns None
// ---------------------------------------------------------------------------
#[tokio::test]
async fn so2_preferred_url_no_service() {
let detector = StarOfficeDetector::new(reqwest::Client::new());
let result = detector
.detect_exact(Some("http://localhost:59990"), true, Some(50))
.await;
assert!(result.is_none());
}
// ---------------------------------------------------------------------------
// SO-3: Cache behavior — second call hits cache
// ---------------------------------------------------------------------------
#[tokio::test]
async fn so3_cache_hit_returns_cached() {
let port = allocate_port();
let url = format!("http://localhost:{port}");
let detector = Arc::new(StarOfficeDetector::new(reqwest::Client::new()));
let _ = detector.detect_exact(Some(&url), false, Some(50)).await;
let t0 = tokio::time::Instant::now();
let result = detector.detect_exact(Some(&url), false, Some(50)).await;
let elapsed = t0.elapsed();
assert!(result.is_none());
assert!(
elapsed < std::time::Duration::from_millis(100),
"cached call should be fast, took {elapsed:?}"
);
}
// ---------------------------------------------------------------------------
// SO-4: Force ignores cache
// ---------------------------------------------------------------------------
#[tokio::test]
async fn so4_force_bypasses_cache() {
let port = allocate_port();
let url = format!("http://localhost:{port}");
let detector = StarOfficeDetector::new(reqwest::Client::new());
let _ = detector.detect_exact(Some(&url), false, Some(50)).await;
let result = detector.detect_exact(Some(&url), true, Some(50)).await;
assert!(result.is_none());
}
// ---------------------------------------------------------------------------
// SO-5: Detect available service (three-step health check)
// ---------------------------------------------------------------------------
#[tokio::test]
async fn so5_detect_available_service() {
let port = allocate_port();
let handle = mock_star_office_server(
port,
true,
r#"{"status": "idle"}"#,
"<html><head></head><body>Star Office dashboard with decorate room</body></html>",
)
.await;
let detector = StarOfficeDetector::new(reqwest::Client::new());
let url = format!("http://localhost:{port}");
let result = detector.detect_exact(Some(&url), true, Some(2000)).await;
assert_eq!(result, Some(format!("http://localhost:{port}")));
handle.abort();
}
// ---------------------------------------------------------------------------
// SO-6: Exclude OpenClaw misidentification
// ---------------------------------------------------------------------------
#[tokio::test]
async fn so6_exclude_openclaw() {
let port = allocate_port();
let handle = mock_star_office_server(
port,
true,
r#"{"status": "idle"}"#,
"<html><head></head><body>Star Office with openclaw control panel</body></html>",
)
.await;
let detector = StarOfficeDetector::new(reqwest::Client::new());
let url = format!("http://localhost:{port}");
let result = detector.detect_exact(Some(&url), true, Some(2000)).await;
assert!(result.is_none());
handle.abort();
}
// ---------------------------------------------------------------------------
// Health check step 1 fail: /health returns non-200
// ---------------------------------------------------------------------------
#[tokio::test]
async fn health_step1_fail_returns_none() {
let port = allocate_port();
let handle = mock_star_office_server(
port,
false,
r#"{"status": "idle"}"#,
"<html><body>Star Office</body></html>",
)
.await;
let detector = StarOfficeDetector::new(reqwest::Client::new());
let url = format!("http://localhost:{port}");
let result = detector.detect_exact(Some(&url), true, Some(2000)).await;
assert!(result.is_none());
handle.abort();
}
// ---------------------------------------------------------------------------
// Health check step 2 fail: /status has no status markers
// ---------------------------------------------------------------------------
#[tokio::test]
async fn health_step2_no_status_markers() {
let port = allocate_port();
let handle = mock_star_office_server(
port,
true,
"just some random text",
"<html><body>Star Office</body></html>",
)
.await;
let detector = StarOfficeDetector::new(reqwest::Client::new());
let url = format!("http://localhost:{port}");
let result = detector.detect_exact(Some(&url), true, Some(2000)).await;
assert!(result.is_none());
handle.abort();
}
// ---------------------------------------------------------------------------
// Health check step 3 fail: / has no feature keywords
// ---------------------------------------------------------------------------
#[tokio::test]
async fn health_step3_no_feature_keywords() {
let port = allocate_port();
let handle = mock_star_office_server(
port,
true,
r#"{"status": "idle"}"#,
"<html><body>Some other application</body></html>",
)
.await;
let detector = StarOfficeDetector::new(reqwest::Client::new());
let url = format!("http://localhost:{port}");
let result = detector.detect_exact(Some(&url), true, Some(2000)).await;
assert!(result.is_none());
handle.abort();
}
// ---------------------------------------------------------------------------
// Detect with different status markers
// ---------------------------------------------------------------------------
#[tokio::test]
async fn detect_with_writing_status() {
let port = allocate_port();
let handle = mock_star_office_server(
port,
true,
r#"{"status": "writing"}"#,
"<html><body>decorate room and asset sidebar</body></html>",
)
.await;
let detector = StarOfficeDetector::new(reqwest::Client::new());
let url = format!("http://localhost:{port}");
let result = detector.detect_exact(Some(&url), true, Some(2000)).await;
assert_eq!(result, Some(format!("http://localhost:{port}")));
handle.abort();
}
// ---------------------------------------------------------------------------
// Cache stores hit correctly
// ---------------------------------------------------------------------------
#[tokio::test]
async fn cache_stores_hit_after_success() {
let port = allocate_port();
let handle =
mock_star_office_server(port, true, r#"idle"#, "<html><body>star office dashboard</body></html>").await;
let detector = StarOfficeDetector::new(reqwest::Client::new());
let url = format!("http://localhost:{port}");
let result = detector.detect_exact(Some(&url), true, Some(2000)).await;
assert!(result.is_some());
handle.abort();
let cached = detector.detect_exact(Some(&url), false, Some(50)).await;
assert_eq!(cached, result);
}
// ---------------------------------------------------------------------------
// Cache miss TTL expires quickly
// ---------------------------------------------------------------------------
#[tokio::test]
async fn cache_miss_ttl_expires() {
let detector = StarOfficeDetector::new(reqwest::Client::new());
let port = allocate_port();
let url = format!("http://localhost:{port}");
let _ = detector.detect_exact(Some(&url), false, Some(50)).await;
tokio::time::sleep(std::time::Duration::from_millis(1600)).await;
let port2 = allocate_port();
let handle = mock_star_office_server(port2, true, "idle", "<html><body>star office</body></html>").await;
let url2 = format!("http://localhost:{port2}");
let result = detector.detect_exact(Some(&url2), false, Some(2000)).await;
assert_eq!(result, Some(format!("http://localhost:{port2}")));
handle.abort();
}