mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-30 15:03:57 +00:00
[codex] Harden threads schema parse contract tests (#797)
Co-authored-by: Jwalin Shah <jshah1331@gmail.com>
This commit is contained in:
co-authored by
Jwalin Shah
parent
e86406ea05
commit
22b7a8b18f
@@ -346,6 +346,7 @@ fn to_json<T: serde::Serialize>(outcome: crate::rpc::RpcOutcome<T>) -> Result<Va
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
const ALL_FUNCTIONS: &[&str] = &[
|
||||
"list",
|
||||
@@ -398,4 +399,175 @@ mod tests {
|
||||
assert_eq!(s.function, "unknown");
|
||||
assert_eq!(s.namespace, "threads");
|
||||
}
|
||||
|
||||
// ── parse::<T>(params) contract ─────────────────────────────────────
|
||||
|
||||
fn obj(value: Value) -> Map<String, Value> {
|
||||
match value {
|
||||
Value::Object(m) => m,
|
||||
_ => panic!("expected JSON object"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_upsert_accepts_snake_case_contract() {
|
||||
let p: UpsertConversationThreadRequest = parse(obj(json!({
|
||||
"id": "t1",
|
||||
"title": "Hello",
|
||||
"created_at": "2026-04-22T00:00:00Z",
|
||||
})))
|
||||
.expect("valid snake_case params parse");
|
||||
assert_eq!(p.id, "t1");
|
||||
assert_eq!(p.title, "Hello");
|
||||
assert_eq!(p.created_at, "2026-04-22T00:00:00Z");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_upsert_rejects_camel_case_created_at() {
|
||||
// Request params contract is snake_case; camelCase must not silently
|
||||
// succeed because `createdAt` leaves `created_at` missing and also
|
||||
// trips deny_unknown_fields.
|
||||
let err = parse::<UpsertConversationThreadRequest>(obj(json!({
|
||||
"id": "t1",
|
||||
"title": "Hello",
|
||||
"createdAt": "2026-04-22T00:00:00Z",
|
||||
})))
|
||||
.unwrap_err();
|
||||
assert!(err.starts_with("invalid params:"), "prefix: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_upsert_rejects_unknown_fields() {
|
||||
let err = parse::<UpsertConversationThreadRequest>(obj(json!({
|
||||
"id": "t1",
|
||||
"title": "Hello",
|
||||
"created_at": "2026-04-22T00:00:00Z",
|
||||
"extra": "nope",
|
||||
})))
|
||||
.unwrap_err();
|
||||
assert!(err.starts_with("invalid params:"), "prefix: {err}");
|
||||
assert!(err.contains("extra"), "field name in error: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_upsert_missing_required_field_errors() {
|
||||
let err = parse::<UpsertConversationThreadRequest>(obj(json!({
|
||||
"id": "t1",
|
||||
"title": "Hello",
|
||||
})))
|
||||
.unwrap_err();
|
||||
assert!(err.starts_with("invalid params:"), "prefix: {err}");
|
||||
assert!(err.contains("created_at"), "field name in error: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_messages_list_requires_thread_id() {
|
||||
let ok: ConversationMessagesRequest = parse(obj(json!({"thread_id": "t1"}))).unwrap();
|
||||
assert_eq!(ok.thread_id, "t1");
|
||||
|
||||
let err = parse::<ConversationMessagesRequest>(obj(json!({}))).unwrap_err();
|
||||
assert!(err.contains("thread_id"), "err: {err}");
|
||||
|
||||
// camelCase alias is not accepted under deny_unknown_fields.
|
||||
let err = parse::<ConversationMessagesRequest>(obj(json!({"threadId": "t1"}))).unwrap_err();
|
||||
assert!(err.starts_with("invalid params:"), "prefix: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_message_append_nested_message_requires_camel_case() {
|
||||
// Outer request is snake_case; nested ConversationMessageRecord is
|
||||
// camelCase by contract (messageType / createdAt). Assert both paths.
|
||||
let ok: AppendConversationMessageRequest = parse(obj(json!({
|
||||
"thread_id": "t1",
|
||||
"message": {
|
||||
"id": "m1",
|
||||
"content": "hi",
|
||||
"type": "text",
|
||||
"sender": "user",
|
||||
"createdAt": "2026-04-22T00:00:00Z",
|
||||
}
|
||||
})))
|
||||
.expect("valid nested camelCase message");
|
||||
assert_eq!(ok.thread_id, "t1");
|
||||
assert_eq!(ok.message.id, "m1");
|
||||
assert_eq!(ok.message.created_at, "2026-04-22T00:00:00Z");
|
||||
|
||||
let err = parse::<AppendConversationMessageRequest>(obj(json!({
|
||||
"thread_id": "t1",
|
||||
"message": {
|
||||
"id": "m1",
|
||||
"content": "hi",
|
||||
"type": "text",
|
||||
"sender": "user",
|
||||
"created_at": "2026-04-22T00:00:00Z",
|
||||
}
|
||||
})))
|
||||
.unwrap_err();
|
||||
assert!(
|
||||
err.contains("createdAt"),
|
||||
"err surfaces expected key: {err}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_generate_title_assistant_message_is_optional() {
|
||||
let without: GenerateConversationThreadTitleRequest =
|
||||
parse(obj(json!({"thread_id": "t1"}))).unwrap();
|
||||
assert_eq!(without.thread_id, "t1");
|
||||
assert_eq!(without.assistant_message, None);
|
||||
|
||||
let with: GenerateConversationThreadTitleRequest = parse(obj(json!({
|
||||
"thread_id": "t1",
|
||||
"assistant_message": "reply",
|
||||
})))
|
||||
.unwrap();
|
||||
assert_eq!(with.assistant_message.as_deref(), Some("reply"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_message_update_extra_metadata_optional_and_unknown_rejected() {
|
||||
let without: UpdateConversationMessageRequest = parse(obj(json!({
|
||||
"thread_id": "t1",
|
||||
"message_id": "m1",
|
||||
})))
|
||||
.unwrap();
|
||||
assert!(without.extra_metadata.is_none());
|
||||
|
||||
let with: UpdateConversationMessageRequest = parse(obj(json!({
|
||||
"thread_id": "t1",
|
||||
"message_id": "m1",
|
||||
"extra_metadata": {"k": "v"},
|
||||
})))
|
||||
.unwrap();
|
||||
assert_eq!(with.extra_metadata, Some(json!({"k": "v"})));
|
||||
|
||||
let err = parse::<UpdateConversationMessageRequest>(obj(json!({
|
||||
"thread_id": "t1",
|
||||
"message_id": "m1",
|
||||
"bogus": true,
|
||||
})))
|
||||
.unwrap_err();
|
||||
assert!(err.contains("bogus"), "err: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_delete_requires_thread_id_and_deleted_at() {
|
||||
let ok: DeleteConversationThreadRequest = parse(obj(json!({
|
||||
"thread_id": "t1",
|
||||
"deleted_at": "2026-04-22T00:00:00Z",
|
||||
})))
|
||||
.unwrap();
|
||||
assert_eq!(ok.thread_id, "t1");
|
||||
|
||||
let err =
|
||||
parse::<DeleteConversationThreadRequest>(obj(json!({"thread_id": "t1"}))).unwrap_err();
|
||||
assert!(err.contains("deleted_at"), "err: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_empty_request_rejects_any_field() {
|
||||
let _: EmptyRequest = parse(obj(json!({}))).unwrap();
|
||||
let err = parse::<EmptyRequest>(obj(json!({"x": 1}))).unwrap_err();
|
||||
assert!(err.starts_with("invalid params:"), "prefix: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user