Skip to content

Commit a725cc8

Browse files
committed
test(coverage): batch 20 — tree_summarizer schemas, subconscious schemas (tinyhumansai#530)
Add 38 new tests across 2 modules: - tree_summarizer/schemas: catalog parity, all 5 functions, param helpers (read_required, read_optional, read_optional_timestamp), type_name, namespace_input - subconscious/schemas: catalog parity, all 10 functions, required input validation, field/field_req/field_opt helpers All 4261 tests pass.
1 parent 6c9be07 commit a725cc8

2 files changed

Lines changed: 328 additions & 0 deletions

File tree

‎src/openhuman/subconscious/schemas.rs‎

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,3 +479,163 @@ fn field_opt(name: &'static str, ty: TypeSchema, comment: &'static str) -> Field
479479
fn to_json<T: serde::Serialize>(outcome: RpcOutcome<T>) -> Result<Value, String> {
480480
outcome.into_cli_compatible_json()
481481
}
482+
483+
#[cfg(test)]
484+
mod tests {
485+
use super::*;
486+
487+
#[test]
488+
fn all_schemas_returns_ten() {
489+
assert_eq!(all_controller_schemas().len(), 10);
490+
}
491+
492+
#[test]
493+
fn all_controllers_returns_ten() {
494+
assert_eq!(all_registered_controllers().len(), 10);
495+
}
496+
497+
#[test]
498+
fn all_use_subconscious_namespace() {
499+
for s in all_controller_schemas() {
500+
assert_eq!(s.namespace, "subconscious");
501+
assert!(!s.description.is_empty());
502+
}
503+
}
504+
505+
#[test]
506+
fn schemas_and_controllers_match() {
507+
let s = all_controller_schemas();
508+
let c = all_registered_controllers();
509+
for (schema, ctrl) in s.iter().zip(c.iter()) {
510+
assert_eq!(schema.function, ctrl.schema.function);
511+
}
512+
}
513+
514+
#[test]
515+
fn known_functions_resolve() {
516+
for fn_name in [
517+
"status",
518+
"trigger",
519+
"tasks_list",
520+
"tasks_add",
521+
"tasks_update",
522+
"tasks_remove",
523+
"log_list",
524+
"escalations_list",
525+
"escalations_approve",
526+
"escalations_dismiss",
527+
] {
528+
let s = schemas(fn_name);
529+
assert_ne!(s.function, "unknown", "{fn_name} fell through");
530+
}
531+
}
532+
533+
#[test]
534+
fn unknown_function_returns_unknown() {
535+
let s = schemas("nonexistent");
536+
assert_eq!(s.function, "unknown");
537+
}
538+
539+
#[test]
540+
fn status_schema_has_no_inputs() {
541+
assert!(schemas("status").inputs.is_empty());
542+
}
543+
544+
#[test]
545+
fn trigger_schema_has_no_inputs() {
546+
assert!(schemas("trigger").inputs.is_empty());
547+
}
548+
549+
#[test]
550+
fn tasks_add_requires_title() {
551+
let s = schemas("tasks_add");
552+
let required: Vec<&str> = s
553+
.inputs
554+
.iter()
555+
.filter(|f| f.required)
556+
.map(|f| f.name)
557+
.collect();
558+
assert!(required.contains(&"title"));
559+
}
560+
561+
#[test]
562+
fn tasks_update_requires_task_id() {
563+
let s = schemas("tasks_update");
564+
let required: Vec<&str> = s
565+
.inputs
566+
.iter()
567+
.filter(|f| f.required)
568+
.map(|f| f.name)
569+
.collect();
570+
assert!(required.contains(&"task_id"));
571+
}
572+
573+
#[test]
574+
fn tasks_remove_requires_task_id() {
575+
let s = schemas("tasks_remove");
576+
let required: Vec<&str> = s
577+
.inputs
578+
.iter()
579+
.filter(|f| f.required)
580+
.map(|f| f.name)
581+
.collect();
582+
assert!(required.contains(&"task_id"));
583+
}
584+
585+
#[test]
586+
fn escalations_approve_requires_escalation_id() {
587+
let s = schemas("escalations_approve");
588+
assert!(s
589+
.inputs
590+
.iter()
591+
.any(|f| f.name == "escalation_id" && f.required));
592+
}
593+
594+
#[test]
595+
fn escalations_dismiss_requires_escalation_id() {
596+
let s = schemas("escalations_dismiss");
597+
assert!(s
598+
.inputs
599+
.iter()
600+
.any(|f| f.name == "escalation_id" && f.required));
601+
}
602+
603+
#[test]
604+
fn log_list_has_optional_inputs() {
605+
let s = schemas("log_list");
606+
for input in &s.inputs {
607+
assert!(
608+
!input.required,
609+
"log_list input '{}' should be optional",
610+
input.name
611+
);
612+
}
613+
}
614+
615+
#[test]
616+
fn tasks_list_has_optional_enabled_only() {
617+
let s = schemas("tasks_list");
618+
let enabled = s.inputs.iter().find(|f| f.name == "enabled_only");
619+
assert!(enabled.is_some_and(|f| !f.required));
620+
}
621+
622+
// ── Field helpers ──────────────────────────────────────────────
623+
624+
#[test]
625+
fn field_helper_is_required() {
626+
let f = field("name", TypeSchema::String, "desc");
627+
assert!(f.required);
628+
}
629+
630+
#[test]
631+
fn field_req_helper_is_required() {
632+
let f = field_req("name", TypeSchema::String, "desc");
633+
assert!(f.required);
634+
}
635+
636+
#[test]
637+
fn field_opt_helper_is_not_required() {
638+
let f = field_opt("name", TypeSchema::String, "desc");
639+
assert!(!f.required);
640+
}
641+
}

‎src/openhuman/tree_summarizer/schemas.rs‎

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,3 +289,171 @@ fn type_name(value: &Value) -> &'static str {
289289
Value::Object(_) => "object",
290290
}
291291
}
292+
293+
#[cfg(test)]
294+
mod tests {
295+
use super::*;
296+
use serde_json::json;
297+
298+
#[test]
299+
fn all_schemas_returns_five() {
300+
assert_eq!(all_controller_schemas().len(), 5);
301+
}
302+
303+
#[test]
304+
fn all_controllers_returns_five() {
305+
assert_eq!(all_registered_controllers().len(), 5);
306+
}
307+
308+
#[test]
309+
fn all_use_tree_summarizer_namespace() {
310+
for s in all_controller_schemas() {
311+
assert_eq!(s.namespace, "tree_summarizer");
312+
assert!(!s.description.is_empty());
313+
}
314+
}
315+
316+
#[test]
317+
fn schemas_and_controllers_match() {
318+
let s = all_controller_schemas();
319+
let c = all_registered_controllers();
320+
for (schema, ctrl) in s.iter().zip(c.iter()) {
321+
assert_eq!(schema.function, ctrl.schema.function);
322+
}
323+
}
324+
325+
#[test]
326+
fn known_functions_resolve() {
327+
for fn_name in ["ingest", "run", "query", "status", "rebuild"] {
328+
let s = schemas(fn_name);
329+
assert_ne!(s.function, "unknown", "{fn_name} fell through");
330+
}
331+
}
332+
333+
#[test]
334+
fn unknown_function_returns_unknown() {
335+
let s = schemas("nonexistent");
336+
assert_eq!(s.function, "unknown");
337+
}
338+
339+
#[test]
340+
fn ingest_requires_namespace_and_content() {
341+
let s = schemas("ingest");
342+
let required: Vec<&str> = s
343+
.inputs
344+
.iter()
345+
.filter(|f| f.required)
346+
.map(|f| f.name)
347+
.collect();
348+
assert!(required.contains(&"namespace"));
349+
assert!(required.contains(&"content"));
350+
}
351+
352+
#[test]
353+
fn query_requires_namespace() {
354+
let s = schemas("query");
355+
let required: Vec<&str> = s
356+
.inputs
357+
.iter()
358+
.filter(|f| f.required)
359+
.map(|f| f.name)
360+
.collect();
361+
assert!(required.contains(&"namespace"));
362+
}
363+
364+
#[test]
365+
fn status_requires_namespace() {
366+
let s = schemas("status");
367+
assert!(s.inputs.iter().any(|f| f.name == "namespace" && f.required));
368+
}
369+
370+
// ── Param helper tests ──────────────────────────────────────────
371+
372+
#[test]
373+
fn read_required_parses_string() {
374+
let mut m = Map::new();
375+
m.insert("key".into(), Value::String("val".into()));
376+
let result: String = read_required(&m, "key").unwrap();
377+
assert_eq!(result, "val");
378+
}
379+
380+
#[test]
381+
fn read_required_errors_on_missing() {
382+
let m = Map::new();
383+
let err = read_required::<String>(&m, "key").unwrap_err();
384+
assert!(err.contains("missing required"));
385+
}
386+
387+
#[test]
388+
fn read_optional_returns_none_for_missing() {
389+
let m = Map::new();
390+
let result: Option<String> = read_optional(&m, "key").unwrap();
391+
assert!(result.is_none());
392+
}
393+
394+
#[test]
395+
fn read_optional_returns_none_for_null() {
396+
let mut m = Map::new();
397+
m.insert("key".into(), Value::Null);
398+
let result: Option<String> = read_optional(&m, "key").unwrap();
399+
assert!(result.is_none());
400+
}
401+
402+
#[test]
403+
fn read_optional_returns_some_for_value() {
404+
let mut m = Map::new();
405+
m.insert("key".into(), Value::String("val".into()));
406+
let result: Option<String> = read_optional(&m, "key").unwrap();
407+
assert_eq!(result, Some("val".into()));
408+
}
409+
410+
#[test]
411+
fn read_optional_timestamp_valid_rfc3339() {
412+
let mut m = Map::new();
413+
m.insert("ts".into(), Value::String("2026-04-17T12:00:00Z".into()));
414+
let result = read_optional_timestamp(&m, "ts").unwrap();
415+
assert!(result.is_some());
416+
}
417+
418+
#[test]
419+
fn read_optional_timestamp_invalid_format() {
420+
let mut m = Map::new();
421+
m.insert("ts".into(), Value::String("not-a-date".into()));
422+
assert!(read_optional_timestamp(&m, "ts").is_err());
423+
}
424+
425+
#[test]
426+
fn read_optional_timestamp_non_string() {
427+
let mut m = Map::new();
428+
m.insert("ts".into(), json!(12345));
429+
assert!(read_optional_timestamp(&m, "ts").is_err());
430+
}
431+
432+
#[test]
433+
fn read_optional_timestamp_none_for_missing() {
434+
let m = Map::new();
435+
assert!(read_optional_timestamp(&m, "ts").unwrap().is_none());
436+
}
437+
438+
// ── type_name ───────────────────────────────────────────────────
439+
440+
#[test]
441+
fn type_name_covers_all_variants() {
442+
assert_eq!(type_name(&Value::Null), "null");
443+
assert_eq!(type_name(&Value::Bool(true)), "bool");
444+
assert_eq!(type_name(&json!(42)), "number");
445+
assert_eq!(type_name(&json!("s")), "string");
446+
assert_eq!(type_name(&json!([1])), "array");
447+
assert_eq!(type_name(&json!({})), "object");
448+
}
449+
450+
// ── namespace_input helper ───────────────────────────────────────
451+
452+
#[test]
453+
fn namespace_input_is_required_string() {
454+
let f = namespace_input("test");
455+
assert_eq!(f.name, "namespace");
456+
assert!(f.required);
457+
assert!(matches!(f.ty, TypeSchema::String));
458+
}
459+
}

0 commit comments

Comments
 (0)