mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-29 14:02:19 +00:00
473 lines
14 KiB
Rust
473 lines
14 KiB
Rust
use serde_json::Map;
|
|
|
|
use super::*;
|
|
use crate::core::{ControllerSchema, FieldSchema, TypeSchema};
|
|
|
|
fn schema(
|
|
namespace: &'static str,
|
|
function: &'static str,
|
|
inputs: Vec<FieldSchema>,
|
|
) -> ControllerSchema {
|
|
ControllerSchema {
|
|
namespace,
|
|
function,
|
|
description: "test",
|
|
inputs,
|
|
outputs: vec![],
|
|
}
|
|
}
|
|
|
|
fn noop_handler(_params: Map<String, Value>) -> ControllerFuture {
|
|
Box::pin(async { Ok(Value::Null) })
|
|
}
|
|
|
|
#[test]
|
|
fn validate_registry_rejects_duplicate_namespace_function() {
|
|
let declared = vec![schema("dup", "fn", vec![]), schema("dup", "fn", vec![])];
|
|
let registered = vec![
|
|
RegisteredController {
|
|
schema: declared[0].clone(),
|
|
handler: noop_handler,
|
|
},
|
|
RegisteredController {
|
|
schema: declared[1].clone(),
|
|
handler: noop_handler,
|
|
},
|
|
];
|
|
|
|
let err = validate_registry(®istered, &declared).expect_err("expected duplicate error");
|
|
assert!(err.contains("duplicate declared controller `dup.fn`"));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_registry_rejects_duplicate_required_inputs() {
|
|
let declared = vec![schema(
|
|
"doctor",
|
|
"models",
|
|
vec![
|
|
FieldSchema {
|
|
name: "use_cache",
|
|
ty: TypeSchema::Bool,
|
|
comment: "x",
|
|
required: true,
|
|
},
|
|
FieldSchema {
|
|
name: "use_cache",
|
|
ty: TypeSchema::Bool,
|
|
comment: "x",
|
|
required: true,
|
|
},
|
|
],
|
|
)];
|
|
let registered = vec![RegisteredController {
|
|
schema: declared[0].clone(),
|
|
handler: noop_handler,
|
|
}];
|
|
|
|
let err = validate_registry(®istered, &declared).expect_err("expected duplicate input");
|
|
assert!(err.contains("duplicate required input `use_cache` in `doctor.models`"));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_registry_accepts_valid_registry() {
|
|
let declared = vec![
|
|
schema("ns1", "fn1", vec![]),
|
|
schema("ns1", "fn2", vec![]),
|
|
schema("ns2", "fn1", vec![]),
|
|
];
|
|
let registered = declared
|
|
.iter()
|
|
.map(|s| RegisteredController {
|
|
schema: s.clone(),
|
|
handler: noop_handler,
|
|
})
|
|
.collect::<Vec<_>>();
|
|
assert!(validate_registry(®istered, &declared).is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn rpc_method_name_formats_correctly() {
|
|
let s = schema("memory", "doc_put", vec![]);
|
|
assert_eq!(rpc_method_name(&s), "openhuman.memory_doc_put");
|
|
}
|
|
|
|
#[test]
|
|
fn registered_controller_rpc_method_name() {
|
|
let s = schema("billing", "get_balance", vec![]);
|
|
let rc = RegisteredController {
|
|
schema: s,
|
|
handler: noop_handler,
|
|
};
|
|
assert_eq!(rc.rpc_method_name(), "openhuman.billing_get_balance");
|
|
}
|
|
|
|
#[test]
|
|
fn namespace_description_known_namespaces() {
|
|
assert!(namespace_description("memory").is_some());
|
|
assert!(namespace_description("memory_tree").is_some());
|
|
assert!(namespace_description("redirect_links").is_some());
|
|
assert!(namespace_description("billing").is_some());
|
|
assert!(namespace_description("config").is_some());
|
|
assert!(namespace_description("health").is_some());
|
|
assert!(namespace_description("security").is_some());
|
|
assert!(namespace_description("tool_registry").is_some());
|
|
assert!(namespace_description("voice").is_some());
|
|
assert!(namespace_description("webhooks").is_some());
|
|
assert!(namespace_description("notification").is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn namespace_description_unknown_returns_none() {
|
|
assert!(namespace_description("nonexistent_xyz").is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn validate_params_accepts_valid_params() {
|
|
let s = schema(
|
|
"test",
|
|
"fn",
|
|
vec![FieldSchema {
|
|
name: "key",
|
|
ty: TypeSchema::String,
|
|
comment: "a key",
|
|
required: true,
|
|
}],
|
|
);
|
|
let mut params = Map::new();
|
|
params.insert("key".into(), Value::String("value".into()));
|
|
assert!(validate_params(&s, ¶ms).is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn validate_params_rejects_missing_required() {
|
|
let s = schema(
|
|
"test",
|
|
"fn",
|
|
vec![FieldSchema {
|
|
name: "key",
|
|
ty: TypeSchema::String,
|
|
comment: "a key",
|
|
required: true,
|
|
}],
|
|
);
|
|
let params = Map::new();
|
|
let err = validate_params(&s, ¶ms).unwrap_err();
|
|
assert!(err.contains("missing required param 'key'"));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_params_rejects_unknown_param() {
|
|
let s = schema("test", "fn", vec![]);
|
|
let mut params = Map::new();
|
|
params.insert("unknown".into(), Value::Null);
|
|
let err = validate_params(&s, ¶ms).unwrap_err();
|
|
assert!(err.contains("unknown param 'unknown'"));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_params_accepts_empty_for_no_required() {
|
|
let s = schema("test", "fn", vec![]);
|
|
assert!(validate_params(&s, &Map::new()).is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn all_registered_controllers_is_nonempty() {
|
|
let controllers = all_registered_controllers();
|
|
assert!(
|
|
controllers.len() > 50,
|
|
"expected many controllers, got {}",
|
|
controllers.len()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn all_controller_schemas_matches_registered_count() {
|
|
let schemas = all_controller_schemas();
|
|
let controllers = all_registered_controllers();
|
|
assert_eq!(schemas.len(), controllers.len());
|
|
}
|
|
|
|
#[test]
|
|
fn schema_for_rpc_method_finds_known_method() {
|
|
let schema = schema_for_rpc_method("openhuman.health_snapshot");
|
|
assert!(schema.is_some(), "health.snapshot should be findable");
|
|
let s = schema.unwrap();
|
|
assert_eq!(s.namespace, "health");
|
|
assert_eq!(s.function, "snapshot");
|
|
}
|
|
|
|
#[test]
|
|
fn schema_for_rpc_method_finds_security_policy_info() {
|
|
let schema = schema_for_rpc_method("openhuman.security_policy_info");
|
|
assert!(schema.is_some(), "security.policy_info should be findable");
|
|
let s = schema.unwrap();
|
|
assert_eq!(s.namespace, "security");
|
|
assert_eq!(s.function, "policy_info");
|
|
}
|
|
|
|
#[test]
|
|
fn schema_for_rpc_method_returns_none_for_unknown() {
|
|
assert!(schema_for_rpc_method("openhuman.nonexistent_method_xyz").is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn rpc_method_from_parts_finds_known() {
|
|
let method = rpc_method_from_parts("health", "snapshot");
|
|
assert_eq!(method.as_deref(), Some("openhuman.health_snapshot"));
|
|
}
|
|
|
|
#[test]
|
|
fn rpc_method_from_parts_returns_none_for_unknown() {
|
|
assert!(rpc_method_from_parts("fake", "method").is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn no_duplicate_rpc_methods_in_registry() {
|
|
let controllers = all_registered_controllers();
|
|
let mut methods: Vec<String> = controllers.iter().map(|c| c.rpc_method_name()).collect();
|
|
let original_len = methods.len();
|
|
methods.sort();
|
|
methods.dedup();
|
|
assert_eq!(
|
|
methods.len(),
|
|
original_len,
|
|
"duplicate RPC methods found in registry"
|
|
);
|
|
}
|
|
|
|
// --- validate_params edge cases -----------------------------------------
|
|
|
|
#[test]
|
|
fn validate_params_accepts_missing_optional_param() {
|
|
let s = schema(
|
|
"test",
|
|
"fn",
|
|
vec![FieldSchema {
|
|
name: "filter",
|
|
ty: TypeSchema::String,
|
|
comment: "optional filter",
|
|
required: false,
|
|
}],
|
|
);
|
|
assert!(validate_params(&s, &Map::new()).is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn validate_params_accepts_optional_param_when_present() {
|
|
let s = schema(
|
|
"test",
|
|
"fn",
|
|
vec![FieldSchema {
|
|
name: "filter",
|
|
ty: TypeSchema::String,
|
|
comment: "",
|
|
required: false,
|
|
}],
|
|
);
|
|
let mut p = Map::new();
|
|
p.insert("filter".into(), Value::String("abc".into()));
|
|
assert!(validate_params(&s, &p).is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn validate_params_missing_required_error_includes_comment() {
|
|
// The comment text helps callers (esp. the CLI/UI) understand what
|
|
// the missing field is for — lock this in so error messages don't
|
|
// regress to bare field names.
|
|
let s = schema(
|
|
"memory",
|
|
"doc_put",
|
|
vec![FieldSchema {
|
|
name: "namespace",
|
|
ty: TypeSchema::String,
|
|
comment: "namespace to write into",
|
|
required: true,
|
|
}],
|
|
);
|
|
let err = validate_params(&s, &Map::new()).unwrap_err();
|
|
assert!(err.contains("missing required param 'namespace'"));
|
|
assert!(err.contains("namespace to write into"));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_params_unknown_error_includes_namespace_and_function() {
|
|
let s = schema("billing", "top_up", vec![]);
|
|
let mut p = Map::new();
|
|
p.insert("typo".into(), Value::Null);
|
|
let err = validate_params(&s, &p).unwrap_err();
|
|
assert!(err.contains("unknown param 'typo'"));
|
|
assert!(err.contains("billing.top_up"));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_params_reports_missing_required_before_unknown() {
|
|
// If a call both omits a required param AND has an unknown one,
|
|
// the missing-required error fires first (it's strictly more
|
|
// actionable for callers).
|
|
let s = schema(
|
|
"test",
|
|
"fn",
|
|
vec![FieldSchema {
|
|
name: "key",
|
|
ty: TypeSchema::String,
|
|
comment: "",
|
|
required: true,
|
|
}],
|
|
);
|
|
let mut p = Map::new();
|
|
p.insert("unknown".into(), Value::Null);
|
|
let err = validate_params(&s, &p).unwrap_err();
|
|
assert!(err.contains("missing required param 'key'"), "got: {err}");
|
|
}
|
|
|
|
#[test]
|
|
fn validate_params_null_for_required_is_acceptable() {
|
|
// JSON-RPC semantics: `null` is a valid value for an optional field
|
|
// sent explicitly. For a required field, presence (not value) is
|
|
// what we check — null does satisfy the "key present" check.
|
|
// Handlers enforce stronger type contracts downstream.
|
|
let s = schema(
|
|
"test",
|
|
"fn",
|
|
vec![FieldSchema {
|
|
name: "key",
|
|
ty: TypeSchema::String,
|
|
comment: "",
|
|
required: true,
|
|
}],
|
|
);
|
|
let mut p = Map::new();
|
|
p.insert("key".into(), Value::Null);
|
|
assert!(validate_params(&s, &p).is_ok());
|
|
}
|
|
|
|
// --- validate_registry edge cases ---------------------------------------
|
|
|
|
#[test]
|
|
fn validate_registry_rejects_empty_namespace() {
|
|
let declared = vec![schema("", "fn", vec![])];
|
|
let registered = vec![RegisteredController {
|
|
schema: declared[0].clone(),
|
|
handler: noop_handler,
|
|
}];
|
|
let err = validate_registry(®istered, &declared).unwrap_err();
|
|
assert!(err.contains("namespace must not be empty"));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_registry_rejects_empty_function() {
|
|
let declared = vec![schema("ns", "", vec![])];
|
|
let registered = vec![RegisteredController {
|
|
schema: declared[0].clone(),
|
|
handler: noop_handler,
|
|
}];
|
|
let err = validate_registry(®istered, &declared).unwrap_err();
|
|
assert!(err.contains("function must not be empty"));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_registry_rejects_whitespace_only_namespace() {
|
|
// `trim().is_empty()` is the invariant — a namespace of " " must
|
|
// be rejected to prevent `openhuman. _fn` nonsense RPC method names.
|
|
let declared = vec![schema(" ", "fn", vec![])];
|
|
let registered = vec![RegisteredController {
|
|
schema: declared[0].clone(),
|
|
handler: noop_handler,
|
|
}];
|
|
let err = validate_registry(®istered, &declared).unwrap_err();
|
|
assert!(err.contains("namespace must not be empty"));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_registry_rejects_declared_without_registered() {
|
|
let declared = vec![schema("a", "b", vec![])];
|
|
let registered: Vec<RegisteredController> = vec![];
|
|
let err = validate_registry(®istered, &declared).unwrap_err();
|
|
assert!(err.contains("declared controller `a.b` has no registered handler"));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_registry_rejects_registered_without_declared() {
|
|
let declared: Vec<ControllerSchema> = vec![];
|
|
let registered = vec![RegisteredController {
|
|
schema: schema("a", "b", vec![]),
|
|
handler: noop_handler,
|
|
}];
|
|
let err = validate_registry(®istered, &declared).unwrap_err();
|
|
assert!(err.contains("registered controller `a.b` has no declared schema"));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_registry_rejects_duplicate_registered_controllers() {
|
|
let s = schema("a", "b", vec![]);
|
|
let declared = vec![s.clone()];
|
|
let registered = vec![
|
|
RegisteredController {
|
|
schema: s.clone(),
|
|
handler: noop_handler,
|
|
},
|
|
RegisteredController {
|
|
schema: s,
|
|
handler: noop_handler,
|
|
},
|
|
];
|
|
let err = validate_registry(®istered, &declared).unwrap_err();
|
|
assert!(err.contains("duplicate registered controller `a.b`"));
|
|
}
|
|
|
|
// --- try_invoke_registered_rpc routing ---------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn try_invoke_registered_rpc_returns_none_for_unknown_method() {
|
|
let out = try_invoke_registered_rpc("openhuman.not_a_real_method_xyz_123", Map::new()).await;
|
|
assert!(out.is_none(), "unknown methods must return None");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn try_invoke_registered_rpc_returns_some_for_known_method() {
|
|
// `openhuman.health_snapshot` is registered at startup and takes no
|
|
// required params — it must route and produce Some(_).
|
|
let out = try_invoke_registered_rpc("openhuman.health_snapshot", Map::new()).await;
|
|
assert!(out.is_some(), "known method must route");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn try_invoke_registered_rpc_routes_security_policy_info() {
|
|
let out = try_invoke_registered_rpc("openhuman.security_policy_info", Map::new())
|
|
.await
|
|
.expect("security policy info should be registered")
|
|
.expect("security policy info should succeed");
|
|
|
|
assert!(
|
|
out.get("result").is_some() || out.get("autonomy").is_some(),
|
|
"security policy info should return policy payload: {out}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rpc_method_name_handles_multi_underscore_function() {
|
|
// Functions often contain underscores — the RPC method name must
|
|
// preserve them verbatim, separated from the namespace with `_`.
|
|
let s = schema("team", "change_member_role", vec![]);
|
|
assert_eq!(rpc_method_name(&s), "openhuman.team_change_member_role");
|
|
}
|
|
|
|
#[test]
|
|
fn every_registered_controller_has_matching_declared_schema() {
|
|
// Global invariant: the registry is consistent by construction.
|
|
// This test re-asserts the contract to catch drift.
|
|
use std::collections::BTreeSet;
|
|
let registered: BTreeSet<String> = all_registered_controllers()
|
|
.into_iter()
|
|
.map(|c| format!("{}.{}", c.schema.namespace, c.schema.function))
|
|
.collect();
|
|
let declared: BTreeSet<String> = all_controller_schemas()
|
|
.into_iter()
|
|
.map(|s| format!("{}.{}", s.namespace, s.function))
|
|
.collect();
|
|
assert_eq!(
|
|
registered, declared,
|
|
"registry/schema sets must be identical"
|
|
);
|
|
}
|