Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion app/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 50 additions & 0 deletions src/api/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,53 @@ pub fn websocket_url(http_or_https_base: &str) -> String {
};
format!("{}/socket.io/?EIO=4&transport=websocket", ws_base)
}

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

#[test]
fn converts_https_to_wss() {
let url = websocket_url("https://api.tinyhumans.ai");
assert_eq!(
url,
"wss://api.tinyhumans.ai/socket.io/?EIO=4&transport=websocket"
);
}

#[test]
fn converts_http_to_ws() {
let url = websocket_url("http://localhost:3000");
assert_eq!(
url,
"ws://localhost:3000/socket.io/?EIO=4&transport=websocket"
);
}

#[test]
fn passes_through_unknown_scheme() {
let url = websocket_url("ftp://example.com");
assert_eq!(
url,
"ftp://example.com/socket.io/?EIO=4&transport=websocket"
);
}

#[test]
fn strips_trailing_slash() {
let url = websocket_url("https://api.tinyhumans.ai/");
assert_eq!(
url,
"wss://api.tinyhumans.ai/socket.io/?EIO=4&transport=websocket"
);
}

#[test]
fn strips_multiple_trailing_slashes() {
let url = websocket_url("https://api.tinyhumans.ai///");
assert_eq!(
url,
"wss://api.tinyhumans.ai/socket.io/?EIO=4&transport=websocket"
);
}
}
123 changes: 123 additions & 0 deletions src/core/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,126 @@ pub struct AppState {
/// The current version of the OpenHuman core binary, usually from `CARGO_PKG_VERSION`.
pub core_version: String,
}

#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;

#[test]
fn invocation_result_ok_serializes_value() {
let result = InvocationResult::ok(json!({"key": "value"})).unwrap();
assert_eq!(result.value, json!({"key": "value"}));
assert!(result.logs.is_empty());
}

#[test]
fn invocation_result_with_logs() {
let result =
InvocationResult::with_logs(json!(42), vec!["log1".into(), "log2".into()]).unwrap();
assert_eq!(result.value, json!(42));
assert_eq!(result.logs.len(), 2);
}

#[test]
fn invocation_to_rpc_json_no_logs_returns_value_directly() {
let inv = InvocationResult {
value: json!({"data": true}),
logs: vec![],
};
let json = invocation_to_rpc_json(inv);
assert_eq!(json, json!({"data": true}));
}

#[test]
fn invocation_to_rpc_json_with_logs_wraps_in_envelope() {
let inv = InvocationResult {
value: json!({"data": true}),
logs: vec!["info".into()],
};
let json = invocation_to_rpc_json(inv);
assert!(json.get("result").is_some());
assert!(json.get("logs").is_some());
assert_eq!(json["result"], json!({"data": true}));
assert_eq!(json["logs"][0], "info");
}

#[test]
fn command_response_serde_roundtrip() {
let resp = CommandResponse {
result: "ok".to_string(),
logs: vec!["log1".into()],
};
let json = serde_json::to_string(&resp).unwrap();
let back: CommandResponse<String> = serde_json::from_str(&json).unwrap();
assert_eq!(back.result, "ok");
assert_eq!(back.logs.len(), 1);
}

#[test]
fn rpc_request_deserializes() {
let json = r#"{"jsonrpc":"2.0","id":1,"method":"test","params":{}}"#;
let req: RpcRequest = serde_json::from_str(json).unwrap();
assert_eq!(req.method, "test");
assert_eq!(req.id, json!(1));
}

#[test]
fn rpc_request_params_default_to_null() {
let json = r#"{"jsonrpc":"2.0","id":"abc","method":"foo"}"#;
let req: RpcRequest = serde_json::from_str(json).unwrap();
assert!(req.params.is_null());
}

#[test]
fn rpc_success_serializes() {
let resp = RpcSuccess {
jsonrpc: "2.0",
id: json!(42),
result: json!({"ok": true}),
};
let json = serde_json::to_string(&resp).unwrap();
assert!(json.contains("\"jsonrpc\":\"2.0\""));
assert!(json.contains("\"id\":42"));
}

#[test]
fn rpc_failure_serializes() {
let resp = RpcFailure {
jsonrpc: "2.0",
id: json!("req-1"),
error: RpcError {
code: -32601,
message: "Method not found".into(),
data: Some(json!({"detail": "unknown"})),
},
};
let json = serde_json::to_string(&resp).unwrap();
assert!(json.contains("-32601"));
assert!(json.contains("Method not found"));
}

#[test]
fn rpc_failure_serializes_without_data() {
let resp = RpcFailure {
jsonrpc: "2.0",
id: json!(null),
error: RpcError {
code: -32700,
message: "Parse error".into(),
data: None,
},
};
let json = serde_json::to_string(&resp).unwrap();
assert!(json.contains("-32700"));
}

#[test]
fn app_state_clone() {
let state = AppState {
core_version: "0.1.0".into(),
};
let cloned = state.clone();
assert_eq!(cloned.core_version, "0.1.0");
}
}
88 changes: 88 additions & 0 deletions src/openhuman/about_app/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,92 @@ mod tests {
"\"coming_soon\""
);
}

#[test]
fn category_all_has_10_variants() {
assert_eq!(CapabilityCategory::ALL.len(), 10);
}

#[test]
fn category_as_str_roundtrips_through_from_str() {
for cat in CapabilityCategory::ALL {
let s = cat.as_str();
let parsed: CapabilityCategory = s.parse().unwrap();
assert_eq!(parsed, cat);
}
}

#[test]
fn category_from_str_accepts_aliases() {
assert_eq!(
"local-ai".parse::<CapabilityCategory>().unwrap(),
CapabilityCategory::LocalAI
);
assert_eq!(
"local ai".parse::<CapabilityCategory>().unwrap(),
CapabilityCategory::LocalAI
);
assert_eq!(
"localai".parse::<CapabilityCategory>().unwrap(),
CapabilityCategory::LocalAI
);
assert_eq!(
"screen-intelligence".parse::<CapabilityCategory>().unwrap(),
CapabilityCategory::ScreenIntelligence
);
assert_eq!(
"screen intelligence".parse::<CapabilityCategory>().unwrap(),
CapabilityCategory::ScreenIntelligence
);
}

#[test]
fn category_from_str_is_case_insensitive() {
assert_eq!(
"CONVERSATION".parse::<CapabilityCategory>().unwrap(),
CapabilityCategory::Conversation
);
assert_eq!(
" Team ".parse::<CapabilityCategory>().unwrap(),
CapabilityCategory::Team
);
}

#[test]
fn category_from_str_rejects_unknown() {
let err = "bogus".parse::<CapabilityCategory>().unwrap_err();
assert!(err.contains("unknown capability category"));
assert!(err.contains("bogus"));
}

#[test]
fn status_as_str_covers_all_variants() {
assert_eq!(CapabilityStatus::Stable.as_str(), "stable");
assert_eq!(CapabilityStatus::Beta.as_str(), "beta");
assert_eq!(CapabilityStatus::ComingSoon.as_str(), "coming_soon");
assert_eq!(CapabilityStatus::Deprecated.as_str(), "deprecated");
}

#[test]
fn status_serde_roundtrip() {
for status in [
CapabilityStatus::Stable,
CapabilityStatus::Beta,
CapabilityStatus::ComingSoon,
CapabilityStatus::Deprecated,
] {
let json = serde_json::to_string(&status).unwrap();
let back: CapabilityStatus = serde_json::from_str(&json).unwrap();
assert_eq!(back, status);
}
}

#[test]
fn category_serde_roundtrip_all() {
for cat in CapabilityCategory::ALL {
let json = serde_json::to_string(&cat).unwrap();
let back: CapabilityCategory = serde_json::from_str(&json).unwrap();
assert_eq!(back, cat);
}
}
}
69 changes: 69 additions & 0 deletions src/openhuman/agent/harness/interrupt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,72 @@ pub fn check_interrupt(fence: &InterruptFence) -> Result<(), InterruptedError> {
Ok(())
}
}

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

#[test]
fn new_fence_is_not_interrupted() {
let fence = InterruptFence::new();
assert!(!fence.is_interrupted());
}

#[test]
fn trigger_sets_interrupted() {
let fence = InterruptFence::new();
fence.trigger();
assert!(fence.is_interrupted());
}

#[test]
fn reset_clears_interrupted() {
let fence = InterruptFence::new();
fence.trigger();
assert!(fence.is_interrupted());
fence.reset();
assert!(!fence.is_interrupted());
}

#[test]
fn flag_handle_shares_state() {
let fence = InterruptFence::new();
let handle = fence.flag_handle();
handle.store(true, std::sync::atomic::Ordering::Relaxed);
assert!(fence.is_interrupted());
}

#[test]
fn clone_shares_state() {
let fence = InterruptFence::new();
let clone = fence.clone();
fence.trigger();
assert!(clone.is_interrupted());
}

#[test]
fn default_is_not_interrupted() {
let fence = InterruptFence::default();
assert!(!fence.is_interrupted());
}

#[test]
fn check_interrupt_ok_when_not_triggered() {
let fence = InterruptFence::new();
assert!(check_interrupt(&fence).is_ok());
}

#[test]
fn check_interrupt_err_when_triggered() {
let fence = InterruptFence::new();
fence.trigger();
let err = check_interrupt(&fence).unwrap_err();
assert_eq!(err.to_string(), "operation interrupted by user");
}

#[test]
fn interrupted_error_display() {
let err = InterruptedError;
assert_eq!(format!("{err}"), "operation interrupted by user");
}
}
Loading
Loading