Skip to content

Commit 6039ed1

Browse files
committed
test(coverage): batch 5.6 — local_ai/sentiment, migration/schemas, referral/schemas
- local_ai/sentiment.rs: 68.03% (negative-confidence clamp to zero, unknown valence fallback, all documented emotion/valence labels accepted, neutral() constructor invariants, empty-message early return) - migration/schemas.rs: 36.73% (controller registry parity, openclaw input shape, MigrateOpenClawParams defaults + round-trip, unknown function placeholder, to_json wrapping) - referral/schemas.rs: 37.18% (controller registry parity, claim input required/optional mapping, ReferralClaimParams camelCase alias + missing-code rejection, json_output + to_json helpers) 25 new deterministic tests. Refs tinyhumansai#530.
1 parent 569f9d1 commit 6039ed1

3 files changed

Lines changed: 212 additions & 0 deletions

File tree

‎src/openhuman/local_ai/sentiment.rs‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,53 @@ mod tests {
197197
assert_eq!(r.emotion, "neutral");
198198
assert_eq!(r.valence, "neutral");
199199
}
200+
201+
#[test]
202+
fn parse_clamps_negative_confidence_to_zero() {
203+
let r = parse_sentiment_response("joy positive -0.5");
204+
assert!(r.confidence >= 0.0 && r.confidence <= 1.0);
205+
assert!((r.confidence - 0.0).abs() < 0.01);
206+
}
207+
208+
#[test]
209+
fn parse_unknown_valence_falls_back_to_neutral() {
210+
let r = parse_sentiment_response("joy mixed 0.8");
211+
assert_eq!(r.emotion, "joy");
212+
assert_eq!(r.valence, "neutral");
213+
}
214+
215+
#[test]
216+
fn parse_accepts_all_documented_emotions() {
217+
for e in [
218+
"joy", "sadness", "anger", "surprise", "fear", "disgust", "neutral",
219+
] {
220+
let r = parse_sentiment_response(&format!("{e} positive 0.5"));
221+
assert_eq!(r.emotion, e, "emotion `{e}` should be accepted verbatim");
222+
}
223+
}
224+
225+
#[test]
226+
fn parse_accepts_all_documented_valences() {
227+
for v in ["positive", "negative", "neutral"] {
228+
let r = parse_sentiment_response(&format!("joy {v} 0.5"));
229+
assert_eq!(r.valence, v, "valence `{v}` should be accepted verbatim");
230+
}
231+
}
232+
233+
#[test]
234+
fn neutral_constructor_returns_documented_defaults() {
235+
let r = SentimentResult::neutral();
236+
assert_eq!(r.emotion, "neutral");
237+
assert_eq!(r.valence, "neutral");
238+
assert!((r.confidence - 1.0).abs() < 0.01);
239+
}
240+
241+
#[tokio::test]
242+
async fn local_ai_analyze_sentiment_returns_neutral_for_empty_message() {
243+
let config = Config::default();
244+
let outcome = local_ai_analyze_sentiment(&config, " ").await.unwrap();
245+
assert_eq!(outcome.value.emotion, "neutral");
246+
assert_eq!(outcome.value.valence, "neutral");
247+
assert!(outcome.logs.iter().any(|l| l.contains("empty message")));
248+
}
200249
}

‎src/openhuman/migration/schemas.rs‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,71 @@ fn handle_migrate_openclaw(params: Map<String, Value>) -> ControllerFuture {
8585
fn to_json<T: serde::Serialize>(outcome: RpcOutcome<T>) -> Result<Value, String> {
8686
outcome.into_cli_compatible_json()
8787
}
88+
89+
#[cfg(test)]
90+
mod tests {
91+
use super::*;
92+
use serde_json::json;
93+
94+
#[test]
95+
fn all_controller_schemas_advertises_openclaw_only() {
96+
let names: Vec<_> = all_controller_schemas()
97+
.into_iter()
98+
.map(|s| s.function)
99+
.collect();
100+
assert_eq!(names, vec!["openclaw"]);
101+
}
102+
103+
#[test]
104+
fn all_registered_controllers_has_one_handler() {
105+
let ctrl = all_registered_controllers();
106+
assert_eq!(ctrl.len(), 1);
107+
assert_eq!(ctrl[0].schema.function, "openclaw");
108+
}
109+
110+
#[test]
111+
fn openclaw_schema_describes_optional_source_and_dry_run() {
112+
let s = schemas("openclaw");
113+
assert_eq!(s.namespace, "migrate");
114+
assert_eq!(s.function, "openclaw");
115+
let names: Vec<_> = s.inputs.iter().map(|f| f.name).collect();
116+
assert!(names.contains(&"source_workspace"));
117+
assert!(names.contains(&"dry_run"));
118+
for f in &s.inputs {
119+
assert!(!f.required, "input `{}` must be optional", f.name);
120+
}
121+
assert_eq!(s.outputs[0].name, "report");
122+
}
123+
124+
#[test]
125+
fn unknown_function_returns_unknown_placeholder() {
126+
let s = schemas("bogus");
127+
assert_eq!(s.function, "unknown");
128+
assert_eq!(s.namespace, "migrate");
129+
assert_eq!(s.outputs[0].name, "error");
130+
}
131+
132+
#[test]
133+
fn migrate_openclaw_params_tolerates_empty_object() {
134+
let params: MigrateOpenClawParams = serde_json::from_value(json!({})).unwrap();
135+
assert!(params.source_workspace.is_none());
136+
assert!(params.dry_run.is_none());
137+
}
138+
139+
#[test]
140+
fn migrate_openclaw_params_parses_both_fields() {
141+
let params: MigrateOpenClawParams = serde_json::from_value(json!({
142+
"source_workspace": "/tmp/old",
143+
"dry_run": false
144+
}))
145+
.unwrap();
146+
assert_eq!(params.source_workspace.as_deref(), Some("/tmp/old"));
147+
assert_eq!(params.dry_run, Some(false));
148+
}
149+
150+
#[test]
151+
fn to_json_wraps_rpc_outcome_result_envelope() {
152+
let v = to_json(RpcOutcome::single_log(json!({"done": true}), "done")).unwrap();
153+
assert!(v.get("logs").is_some() || v.get("result").is_some());
154+
}
155+
}

‎src/openhuman/referral/schemas.rs‎

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,98 @@ fn json_output(name: &'static str, comment: &'static str) -> FieldSchema {
123123
required: true,
124124
}
125125
}
126+
127+
#[cfg(test)]
128+
mod tests {
129+
use super::*;
130+
use serde_json::json;
131+
132+
#[test]
133+
fn all_referral_controller_schemas_advertises_stats_and_claim() {
134+
let names: Vec<_> = all_referral_controller_schemas()
135+
.into_iter()
136+
.map(|s| s.function)
137+
.collect();
138+
assert_eq!(names, vec!["get_stats", "claim"]);
139+
}
140+
141+
#[test]
142+
fn all_referral_registered_controllers_matches_schema_count() {
143+
assert_eq!(
144+
all_referral_registered_controllers().len(),
145+
all_referral_controller_schemas().len()
146+
);
147+
}
148+
149+
#[test]
150+
fn get_stats_schema_has_no_inputs_and_required_output() {
151+
let s = referral_schemas("referral_get_stats");
152+
assert_eq!(s.namespace, "referral");
153+
assert!(s.inputs.is_empty());
154+
assert!(s.outputs.iter().all(|f| f.required));
155+
}
156+
157+
#[test]
158+
fn claim_schema_requires_code_and_has_optional_fingerprint() {
159+
let s = referral_schemas("referral_claim");
160+
let code = s.inputs.iter().find(|f| f.name == "code").unwrap();
161+
assert!(code.required);
162+
let fp = s
163+
.inputs
164+
.iter()
165+
.find(|f| f.name == "deviceFingerprint")
166+
.unwrap();
167+
assert!(!fp.required);
168+
}
169+
170+
#[test]
171+
fn unknown_function_returns_unknown_placeholder() {
172+
let s = referral_schemas("no_such");
173+
assert_eq!(s.function, "unknown");
174+
assert_eq!(s.namespace, "referral");
175+
}
176+
177+
#[test]
178+
fn claim_params_parse_camel_case_device_fingerprint() {
179+
let p: ReferralClaimParams = serde_json::from_value(json!({
180+
"code": "ABC123",
181+
"deviceFingerprint": "fp-xyz"
182+
}))
183+
.unwrap();
184+
assert_eq!(p.code, "ABC123");
185+
assert_eq!(p.device_fingerprint.as_deref(), Some("fp-xyz"));
186+
}
187+
188+
#[test]
189+
fn claim_params_tolerate_missing_device_fingerprint() {
190+
let p: ReferralClaimParams = serde_json::from_value(json!({"code": "ABC"})).unwrap();
191+
assert!(p.device_fingerprint.is_none());
192+
}
193+
194+
#[test]
195+
fn claim_params_require_code() {
196+
let err = serde_json::from_value::<ReferralClaimParams>(json!({})).unwrap_err();
197+
assert!(err.to_string().contains("code"));
198+
}
199+
200+
#[test]
201+
fn deserialize_params_reports_invalid_params_prefix_on_bad_types() {
202+
let mut m = Map::new();
203+
m.insert("code".into(), json!(42));
204+
let err = deserialize_params::<ReferralClaimParams>(m).unwrap_err();
205+
assert!(err.starts_with("invalid params"));
206+
}
207+
208+
#[test]
209+
fn json_output_builds_required_json_field() {
210+
let f = json_output("x", "c");
211+
assert!(f.required);
212+
assert!(matches!(f.ty, TypeSchema::Json));
213+
}
214+
215+
#[test]
216+
fn to_json_wraps_result_and_logs() {
217+
let v = to_json(RpcOutcome::single_log(json!({"ok": true}), "log")).unwrap();
218+
assert!(v.get("result").is_some() || v.get("logs").is_some());
219+
}
220+
}

0 commit comments

Comments
 (0)