generated from nhcarrigan/template
feat(tools): set up proper CI (#2)
### Explanation _No response_ ### Issue _No response_ ### Attestations - [ ] I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/) - [ ] I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/). - [ ] My contribution complies with the [Contributor Covenant](https://docs.nhcarrigan.com/dev/covenant/). ### Dependencies - [ ] I have pinned the dependencies to a specific patch version. ### Style - [ ] I have run the linter and resolved any errors. - [ ] My pull request uses an appropriate title, matching the conventional commit standards. - [ ] My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request. ### Tests - [ ] My contribution adds new code, and I have added tests to cover it. - [ ] My contribution modifies existing code, and I have updated the tests to reflect these changes. - [ ] All new and existing tests pass locally with my changes. - [ ] Code coverage remains at or above the configured threshold. ### Documentation _No response_ ### Versioning _No response_ Reviewed-on: #2 Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com> Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
This commit was merged in pull request #2.
This commit is contained in:
+132
-14
@@ -1,8 +1,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CharacterState {
|
||||
#[default]
|
||||
Idle,
|
||||
Thinking,
|
||||
Typing,
|
||||
@@ -14,27 +15,17 @@ pub enum CharacterState {
|
||||
Error,
|
||||
}
|
||||
|
||||
impl Default for CharacterState {
|
||||
fn default() -> Self {
|
||||
CharacterState::Idle
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ConnectionStatus {
|
||||
#[default]
|
||||
Disconnected,
|
||||
Connecting,
|
||||
Connected,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl Default for ConnectionStatus {
|
||||
fn default() -> Self {
|
||||
ConnectionStatus::Disconnected
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TerminalLine {
|
||||
pub id: String,
|
||||
@@ -46,6 +37,7 @@ pub struct TerminalLine {
|
||||
pub tool_name: Option<String>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PermissionRequest {
|
||||
pub id: String,
|
||||
@@ -186,3 +178,129 @@ pub struct PermissionPromptEvent {
|
||||
pub tool_input: serde_json::Value,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_character_state_default() {
|
||||
let state = CharacterState::default();
|
||||
assert_eq!(state, CharacterState::Idle);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connection_status_default() {
|
||||
let status = ConnectionStatus::default();
|
||||
matches!(status, ConnectionStatus::Disconnected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_character_state_serialization() {
|
||||
let state = CharacterState::Thinking;
|
||||
let serialized = serde_json::to_string(&state).unwrap();
|
||||
assert_eq!(serialized, "\"thinking\"");
|
||||
|
||||
let deserialized: CharacterState = serde_json::from_str(&serialized).unwrap();
|
||||
assert_eq!(deserialized, CharacterState::Thinking);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_character_states_serialize() {
|
||||
let states = vec![
|
||||
(CharacterState::Idle, "\"idle\""),
|
||||
(CharacterState::Thinking, "\"thinking\""),
|
||||
(CharacterState::Typing, "\"typing\""),
|
||||
(CharacterState::Searching, "\"searching\""),
|
||||
(CharacterState::Coding, "\"coding\""),
|
||||
(CharacterState::Mcp, "\"mcp\""),
|
||||
(CharacterState::Permission, "\"permission\""),
|
||||
(CharacterState::Success, "\"success\""),
|
||||
(CharacterState::Error, "\"error\""),
|
||||
];
|
||||
|
||||
for (state, expected) in states {
|
||||
let serialized = serde_json::to_string(&state).unwrap();
|
||||
assert_eq!(serialized, expected, "Failed for state: {:?}", state);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_terminal_line_serialization() {
|
||||
let line = TerminalLine {
|
||||
id: "test-123".to_string(),
|
||||
line_type: "assistant".to_string(),
|
||||
content: "Hello, world!".to_string(),
|
||||
timestamp: "2024-01-01T00:00:00Z".to_string(),
|
||||
tool_name: None,
|
||||
};
|
||||
|
||||
let serialized = serde_json::to_string(&line).unwrap();
|
||||
assert!(serialized.contains("\"type\":\"assistant\""));
|
||||
assert!(serialized.contains("\"content\":\"Hello, world!\""));
|
||||
assert!(!serialized.contains("tool_name"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_terminal_line_with_tool_name() {
|
||||
let line = TerminalLine {
|
||||
id: "test-456".to_string(),
|
||||
line_type: "tool".to_string(),
|
||||
content: "Reading file...".to_string(),
|
||||
timestamp: "2024-01-01T00:00:00Z".to_string(),
|
||||
tool_name: Some("Read".to_string()),
|
||||
};
|
||||
|
||||
let serialized = serde_json::to_string(&line).unwrap();
|
||||
assert!(serialized.contains("\"tool_name\":\"Read\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content_block_text() {
|
||||
let block = ContentBlock::Text {
|
||||
text: "Hello!".to_string(),
|
||||
};
|
||||
|
||||
let serialized = serde_json::to_string(&block).unwrap();
|
||||
assert!(serialized.contains("\"type\":\"text\""));
|
||||
assert!(serialized.contains("\"text\":\"Hello!\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content_block_tool_use() {
|
||||
let block = ContentBlock::ToolUse {
|
||||
id: "tool-123".to_string(),
|
||||
name: "Read".to_string(),
|
||||
input: serde_json::json!({"file_path": "/test.txt"}),
|
||||
};
|
||||
|
||||
let serialized = serde_json::to_string(&block).unwrap();
|
||||
assert!(serialized.contains("\"type\":\"tool_use\""));
|
||||
assert!(serialized.contains("\"name\":\"Read\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_state_change_event() {
|
||||
let event = StateChangeEvent {
|
||||
state: CharacterState::Coding,
|
||||
tool_name: Some("Edit".to_string()),
|
||||
};
|
||||
|
||||
let serialized = serde_json::to_string(&event).unwrap();
|
||||
assert!(serialized.contains("\"state\":\"coding\""));
|
||||
assert!(serialized.contains("\"tool_name\":\"Edit\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_output_event() {
|
||||
let event = OutputEvent {
|
||||
line_type: "assistant".to_string(),
|
||||
content: "Test output".to_string(),
|
||||
tool_name: None,
|
||||
};
|
||||
|
||||
let serialized = serde_json::to_string(&event).unwrap();
|
||||
assert!(serialized.contains("\"line_type\":\"assistant\""));
|
||||
assert!(serialized.contains("\"content\":\"Test output\""));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,3 +472,127 @@ pub type SharedBridge = Arc<Mutex<WslBridge>>;
|
||||
pub fn create_shared_bridge() -> SharedBridge {
|
||||
Arc::new(Mutex::new(WslBridge::new()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_get_tool_state_search_tools() {
|
||||
assert!(matches!(get_tool_state("Read"), CharacterState::Searching));
|
||||
assert!(matches!(get_tool_state("Glob"), CharacterState::Searching));
|
||||
assert!(matches!(get_tool_state("Grep"), CharacterState::Searching));
|
||||
assert!(matches!(get_tool_state("WebSearch"), CharacterState::Searching));
|
||||
assert!(matches!(get_tool_state("WebFetch"), CharacterState::Searching));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_tool_state_coding_tools() {
|
||||
assert!(matches!(get_tool_state("Edit"), CharacterState::Coding));
|
||||
assert!(matches!(get_tool_state("Write"), CharacterState::Coding));
|
||||
assert!(matches!(get_tool_state("NotebookEdit"), CharacterState::Coding));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_tool_state_mcp_tools() {
|
||||
assert!(matches!(get_tool_state("mcp__github__create_issue"), CharacterState::Mcp));
|
||||
assert!(matches!(get_tool_state("mcp__notion__search"), CharacterState::Mcp));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_tool_state_task() {
|
||||
assert!(matches!(get_tool_state("Task"), CharacterState::Thinking));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_tool_state_unknown() {
|
||||
assert!(matches!(get_tool_state("SomeUnknownTool"), CharacterState::Typing));
|
||||
assert!(matches!(get_tool_state("Bash"), CharacterState::Typing));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_tool_description_read() {
|
||||
let input = serde_json::json!({"file_path": "/home/test/file.txt"});
|
||||
let desc = format_tool_description("Read", &input);
|
||||
assert_eq!(desc, "Reading file: /home/test/file.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_tool_description_read_no_path() {
|
||||
let input = serde_json::json!({});
|
||||
let desc = format_tool_description("Read", &input);
|
||||
assert_eq!(desc, "Reading file...");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_tool_description_glob() {
|
||||
let input = serde_json::json!({"pattern": "**/*.rs"});
|
||||
let desc = format_tool_description("Glob", &input);
|
||||
assert_eq!(desc, "Searching for files: **/*.rs");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_tool_description_grep() {
|
||||
let input = serde_json::json!({"pattern": "TODO"});
|
||||
let desc = format_tool_description("Grep", &input);
|
||||
assert_eq!(desc, "Searching for: TODO");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_tool_description_edit() {
|
||||
let input = serde_json::json!({"file_path": "/home/test/main.rs"});
|
||||
let desc = format_tool_description("Edit", &input);
|
||||
assert_eq!(desc, "Editing: /home/test/main.rs");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_tool_description_write() {
|
||||
let input = serde_json::json!({"file_path": "/home/test/new.txt"});
|
||||
let desc = format_tool_description("Write", &input);
|
||||
assert_eq!(desc, "Editing: /home/test/new.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_tool_description_bash_short() {
|
||||
let input = serde_json::json!({"command": "ls -la"});
|
||||
let desc = format_tool_description("Bash", &input);
|
||||
assert_eq!(desc, "Running: ls -la");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_tool_description_bash_long() {
|
||||
let long_cmd = "a".repeat(100);
|
||||
let input = serde_json::json!({"command": long_cmd});
|
||||
let desc = format_tool_description("Bash", &input);
|
||||
assert!(desc.starts_with("Running: "));
|
||||
assert!(desc.ends_with("..."));
|
||||
assert!(desc.len() < 70);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_tool_description_unknown() {
|
||||
let input = serde_json::json!({"some": "data"});
|
||||
let desc = format_tool_description("CustomTool", &input);
|
||||
assert_eq!(desc, "Using tool: CustomTool");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wsl_bridge_new() {
|
||||
let bridge = WslBridge::new();
|
||||
assert!(!bridge.is_running());
|
||||
assert_eq!(bridge.get_working_directory(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wsl_bridge_default() {
|
||||
let bridge = WslBridge::default();
|
||||
assert!(!bridge.is_running());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_shared_bridge() {
|
||||
let shared = create_shared_bridge();
|
||||
let bridge = shared.lock();
|
||||
assert!(!bridge.is_running());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user