Update: 将子项目从 submodule 转为完整内容
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用 - 添加所有子项目的完整源代码 - 保留原始 .git 为 .git.bak 备份
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
use nomi_protocol::commands::ApprovalScope;
|
||||
use nomi_protocol::events::ToolCategory;
|
||||
use nomi_protocol::{ToolApprovalManager, ToolApprovalResult};
|
||||
use rstest::rstest;
|
||||
|
||||
#[rstest]
|
||||
#[case(ApprovalScope::Once, ToolCategory::Exec, "exec", false)]
|
||||
#[case(ApprovalScope::Always, ToolCategory::Edit, "edit", true)]
|
||||
#[tokio::test]
|
||||
async fn approve_resolves_request_and_updates_auto_approval(
|
||||
#[case] scope: ApprovalScope,
|
||||
#[case] category: ToolCategory,
|
||||
#[case] category_name: &str,
|
||||
#[case] should_auto_approve: bool,
|
||||
) {
|
||||
let manager = ToolApprovalManager::new();
|
||||
let rx = manager.request_approval("call-1", &category);
|
||||
|
||||
manager.approve("call-1", scope);
|
||||
|
||||
let result = rx.await.expect("approval result should arrive");
|
||||
assert!(matches!(result, ToolApprovalResult::Approved));
|
||||
assert_eq!(manager.is_auto_approved(category_name), should_auto_approve);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolve_preserves_denial_reason() {
|
||||
let manager = ToolApprovalManager::new();
|
||||
let rx = manager.request_approval("call-2", &ToolCategory::Exec);
|
||||
|
||||
manager.resolve(
|
||||
"call-2",
|
||||
ToolApprovalResult::Denied {
|
||||
reason: "policy violation".to_string(),
|
||||
},
|
||||
);
|
||||
|
||||
let result = rx.await.expect("denial result should arrive");
|
||||
assert!(matches!(
|
||||
result,
|
||||
ToolApprovalResult::Denied { reason } if reason == "policy violation"
|
||||
));
|
||||
assert!(!manager.is_auto_approved("exec"));
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
use nomi_protocol::events::{Capabilities, ProtocolEvent};
|
||||
|
||||
#[test]
|
||||
fn capabilities_serialize_with_all_fields() {
|
||||
let caps = Capabilities {
|
||||
tool_approval: true,
|
||||
thinking: true,
|
||||
effort: false,
|
||||
effort_levels: vec![],
|
||||
modes: vec!["default".into(), "auto_edit".into(), "yolo".into()],
|
||||
current_mode: "default".into(),
|
||||
mcp: true,
|
||||
};
|
||||
let event = ProtocolEvent::Ready {
|
||||
version: "0.2.0".into(),
|
||||
session_id: None,
|
||||
capabilities: caps,
|
||||
};
|
||||
let json = serde_json::to_string(&event).unwrap();
|
||||
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
|
||||
|
||||
assert_eq!(parsed["type"], "ready");
|
||||
assert_eq!(parsed["capabilities"]["thinking"], true);
|
||||
assert_eq!(parsed["capabilities"]["effort"], false);
|
||||
assert!(
|
||||
parsed["capabilities"]["effort_levels"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
);
|
||||
assert_eq!(parsed["capabilities"]["modes"].as_array().unwrap().len(), 3);
|
||||
assert_eq!(parsed["capabilities"]["current_mode"], "default");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_changed_event_serializes_correctly() {
|
||||
let caps = Capabilities {
|
||||
tool_approval: true,
|
||||
thinking: false,
|
||||
effort: true,
|
||||
effort_levels: vec!["low".into(), "medium".into(), "high".into()],
|
||||
modes: vec!["default".into(), "auto_edit".into(), "yolo".into()],
|
||||
current_mode: "default".into(),
|
||||
mcp: false,
|
||||
};
|
||||
let event = ProtocolEvent::ConfigChanged { capabilities: caps };
|
||||
let json = serde_json::to_string(&event).unwrap();
|
||||
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
|
||||
|
||||
assert_eq!(parsed["type"], "config_changed");
|
||||
assert_eq!(parsed["capabilities"]["effort_levels"][1], "medium");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn capabilities_with_effort_levels_roundtrip() {
|
||||
let caps = Capabilities {
|
||||
tool_approval: true,
|
||||
thinking: false,
|
||||
effort: true,
|
||||
effort_levels: vec!["low".into(), "medium".into(), "high".into()],
|
||||
modes: vec!["default".into(), "auto_edit".into(), "yolo".into()],
|
||||
current_mode: "default".into(),
|
||||
mcp: true,
|
||||
};
|
||||
let event = ProtocolEvent::Ready {
|
||||
version: "0.2.0".into(),
|
||||
session_id: Some("test-session".into()),
|
||||
capabilities: caps,
|
||||
};
|
||||
let json = serde_json::to_string(&event).unwrap();
|
||||
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
|
||||
|
||||
assert_eq!(parsed["capabilities"]["effort"], true);
|
||||
assert_eq!(
|
||||
parsed["capabilities"]["effort_levels"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.len(),
|
||||
3
|
||||
);
|
||||
assert_eq!(parsed["capabilities"]["effort_levels"][0], "low");
|
||||
assert_eq!(parsed["capabilities"]["effort_levels"][2], "high");
|
||||
assert_eq!(parsed["session_id"], "test-session");
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
use nomi_protocol::commands::{ApprovalScope, ProtocolCommand, SessionMode};
|
||||
use rstest::rstest;
|
||||
|
||||
#[rstest]
|
||||
#[case(
|
||||
r#"{"type":"message","msg_id":"m1","content":"Hello"}"#,
|
||||
ProtocolCommand::Message {
|
||||
msg_id: "m1".to_string(),
|
||||
content: "Hello".to_string(),
|
||||
files: vec![],
|
||||
}
|
||||
)]
|
||||
#[case(
|
||||
r#"{"type":"message","msg_id":"m2","content":"Read this","files":["/tmp/a.rs"]}"#,
|
||||
ProtocolCommand::Message {
|
||||
msg_id: "m2".to_string(),
|
||||
content: "Read this".to_string(),
|
||||
files: vec!["/tmp/a.rs".to_string()],
|
||||
}
|
||||
)]
|
||||
#[case(r#"{"type":"stop"}"#, ProtocolCommand::Stop)]
|
||||
#[case(
|
||||
r#"{"type":"init_history","text":"history"}"#,
|
||||
ProtocolCommand::InitHistory {
|
||||
text: "history".to_string(),
|
||||
}
|
||||
)]
|
||||
#[case(
|
||||
r#"{"type":"set_mode","mode":"default"}"#,
|
||||
ProtocolCommand::SetMode {
|
||||
mode: SessionMode::Default,
|
||||
}
|
||||
)]
|
||||
#[case(
|
||||
r#"{"type":"set_mode","mode":"auto_edit"}"#,
|
||||
ProtocolCommand::SetMode {
|
||||
mode: SessionMode::AutoEdit,
|
||||
}
|
||||
)]
|
||||
#[case(
|
||||
r#"{"type":"set_mode","mode":"yolo"}"#,
|
||||
ProtocolCommand::SetMode {
|
||||
mode: SessionMode::Yolo,
|
||||
}
|
||||
)]
|
||||
fn deserializes_protocol_commands(#[case] json: &str, #[case] expected: ProtocolCommand) {
|
||||
let cmd: ProtocolCommand = serde_json::from_str(json).expect("command should deserialize");
|
||||
assert_eq!(cmd, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(r#"{"type":"tool_approve","call_id":"c1"}"#, ApprovalScope::Once)]
|
||||
#[case(
|
||||
r#"{"type":"tool_approve","call_id":"c1","scope":"always"}"#,
|
||||
ApprovalScope::Always
|
||||
)]
|
||||
fn deserializes_tool_approve_scope(#[case] json: &str, #[case] expected_scope: ApprovalScope) {
|
||||
let cmd: ProtocolCommand = serde_json::from_str(json).expect("tool approve should deserialize");
|
||||
|
||||
match cmd {
|
||||
ProtocolCommand::ToolApprove { call_id, scope } => {
|
||||
assert_eq!(call_id, "c1");
|
||||
assert_eq!(scope, expected_scope);
|
||||
}
|
||||
other => panic!("expected ToolApprove, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(r#"{"type":"tool_deny","call_id":"c1"}"#, "")]
|
||||
#[case(
|
||||
r#"{"type":"tool_deny","call_id":"c1","reason":"not allowed"}"#,
|
||||
"not allowed"
|
||||
)]
|
||||
fn deserializes_tool_deny_reason(#[case] json: &str, #[case] expected_reason: &str) {
|
||||
let cmd: ProtocolCommand = serde_json::from_str(json).expect("tool deny should deserialize");
|
||||
|
||||
match cmd {
|
||||
ProtocolCommand::ToolDeny { call_id, reason } => {
|
||||
assert_eq!(call_id, "c1");
|
||||
assert_eq!(reason, expected_reason);
|
||||
}
|
||||
other => panic!("expected ToolDeny, got {other:?}"),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
use nomi_protocol::commands::ProtocolCommand;
|
||||
|
||||
#[test]
|
||||
fn parse_set_config_with_model() {
|
||||
let json = r#"{"type":"set_config","model":"claude-sonnet-4-5-20250514"}"#;
|
||||
let cmd: ProtocolCommand = serde_json::from_str(json).unwrap();
|
||||
match cmd {
|
||||
ProtocolCommand::SetConfig { model, .. } => {
|
||||
assert_eq!(model.as_deref(), Some("claude-sonnet-4-5-20250514"));
|
||||
}
|
||||
other => panic!("expected SetConfig, got: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_set_config_empty() {
|
||||
let json = r#"{"type":"set_config"}"#;
|
||||
let cmd: ProtocolCommand = serde_json::from_str(json).unwrap();
|
||||
match cmd {
|
||||
ProtocolCommand::SetConfig { model, .. } => {
|
||||
assert!(model.is_none());
|
||||
}
|
||||
other => panic!("expected SetConfig, got: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_set_config_null_model() {
|
||||
let json = r#"{"type":"set_config","model":null}"#;
|
||||
let cmd: ProtocolCommand = serde_json::from_str(json).unwrap();
|
||||
match cmd {
|
||||
ProtocolCommand::SetConfig { model, .. } => {
|
||||
assert!(model.is_none());
|
||||
}
|
||||
other => panic!("expected SetConfig, got: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_set_config_unknown_fields_ignored() {
|
||||
let json = r#"{"type":"set_config","model":"x","future_field":true,"nested":{"a":1}}"#;
|
||||
let cmd: ProtocolCommand = serde_json::from_str(json).unwrap();
|
||||
match cmd {
|
||||
ProtocolCommand::SetConfig { model, .. } => {
|
||||
assert_eq!(model.as_deref(), Some("x"));
|
||||
}
|
||||
other => panic!("expected SetConfig, got: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn existing_commands_still_parse() {
|
||||
// AC-7: Verify SetConfig addition doesn't break existing variants
|
||||
let message = r#"{"type":"message","msg_id":"m1","content":"hello"}"#;
|
||||
assert!(serde_json::from_str::<ProtocolCommand>(message).is_ok());
|
||||
|
||||
let stop = r#"{"type":"stop"}"#;
|
||||
assert!(serde_json::from_str::<ProtocolCommand>(stop).is_ok());
|
||||
|
||||
let approve = r#"{"type":"tool_approve","call_id":"c1"}"#;
|
||||
assert!(serde_json::from_str::<ProtocolCommand>(approve).is_ok());
|
||||
|
||||
let deny = r#"{"type":"tool_deny","call_id":"c1"}"#;
|
||||
assert!(serde_json::from_str::<ProtocolCommand>(deny).is_ok());
|
||||
|
||||
let init = r#"{"type":"init_history","text":"ctx"}"#;
|
||||
assert!(serde_json::from_str::<ProtocolCommand>(init).is_ok());
|
||||
|
||||
let mode = r#"{"type":"set_mode","mode":"yolo"}"#;
|
||||
assert!(serde_json::from_str::<ProtocolCommand>(mode).is_ok());
|
||||
}
|
||||
|
||||
// --- Cycle 2: Effort parsing tests ---
|
||||
|
||||
#[test]
|
||||
fn parse_set_config_with_effort() {
|
||||
let json = r#"{"type":"set_config","effort":"high"}"#;
|
||||
let cmd: ProtocolCommand = serde_json::from_str(json).unwrap();
|
||||
match cmd {
|
||||
ProtocolCommand::SetConfig { effort, model, .. } => {
|
||||
assert_eq!(effort.as_deref(), Some("high"));
|
||||
assert!(model.is_none());
|
||||
}
|
||||
other => panic!("expected SetConfig, got: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_set_config_with_null_effort() {
|
||||
let json = r#"{"type":"set_config","effort":null}"#;
|
||||
let cmd: ProtocolCommand = serde_json::from_str(json).unwrap();
|
||||
match cmd {
|
||||
ProtocolCommand::SetConfig { effort, .. } => {
|
||||
assert!(effort.is_none());
|
||||
}
|
||||
other => panic!("expected SetConfig, got: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
// --- Cycle 2: Thinking parsing tests ---
|
||||
|
||||
#[test]
|
||||
fn parse_set_config_with_thinking_enabled_and_budget() {
|
||||
let json = r#"{"type":"set_config","thinking":"enabled","thinking_budget":16000}"#;
|
||||
let cmd: ProtocolCommand = serde_json::from_str(json).unwrap();
|
||||
match cmd {
|
||||
ProtocolCommand::SetConfig {
|
||||
thinking,
|
||||
thinking_budget,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(thinking.as_deref(), Some("enabled"));
|
||||
assert_eq!(thinking_budget, Some(16000));
|
||||
}
|
||||
other => panic!("expected SetConfig, got: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_set_config_with_thinking_disabled() {
|
||||
let json = r#"{"type":"set_config","thinking":"disabled"}"#;
|
||||
let cmd: ProtocolCommand = serde_json::from_str(json).unwrap();
|
||||
match cmd {
|
||||
ProtocolCommand::SetConfig {
|
||||
thinking,
|
||||
thinking_budget,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(thinking.as_deref(), Some("disabled"));
|
||||
assert!(thinking_budget.is_none());
|
||||
}
|
||||
other => panic!("expected SetConfig, got: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_set_config_with_null_thinking() {
|
||||
let json = r#"{"type":"set_config","thinking":null}"#;
|
||||
let cmd: ProtocolCommand = serde_json::from_str(json).unwrap();
|
||||
match cmd {
|
||||
ProtocolCommand::SetConfig { thinking, .. } => {
|
||||
assert!(thinking.is_none());
|
||||
}
|
||||
other => panic!("expected SetConfig, got: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_set_config_thinking_enabled_no_budget() {
|
||||
let json = r#"{"type":"set_config","thinking":"enabled"}"#;
|
||||
let cmd: ProtocolCommand = serde_json::from_str(json).unwrap();
|
||||
match cmd {
|
||||
ProtocolCommand::SetConfig {
|
||||
thinking,
|
||||
thinking_budget,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(thinking.as_deref(), Some("enabled"));
|
||||
assert!(thinking_budget.is_none());
|
||||
}
|
||||
other => panic!("expected SetConfig, got: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
// --- Cycle 2: Combined fields test ---
|
||||
|
||||
#[test]
|
||||
fn parse_set_config_all_fields() {
|
||||
let json = r#"{"type":"set_config","model":"m","effort":"low","thinking":"disabled"}"#;
|
||||
let cmd: ProtocolCommand = serde_json::from_str(json).unwrap();
|
||||
match cmd {
|
||||
ProtocolCommand::SetConfig {
|
||||
model,
|
||||
effort,
|
||||
thinking,
|
||||
thinking_budget,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(model.as_deref(), Some("m"));
|
||||
assert_eq!(effort.as_deref(), Some("low"));
|
||||
assert_eq!(thinking.as_deref(), Some("disabled"));
|
||||
assert!(thinking_budget.is_none());
|
||||
}
|
||||
other => panic!("expected SetConfig, got: {other:?}"),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user