Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/cli/src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ pub(crate) enum NormalizedEvent {
HookMark(SessionEvent),
}

#[cfg(test)]
#[path = "../../tests/coverage/shared/events_tests.rs"]
mod tests;

impl NormalizedEvent {
// Extracts the routing session id regardless of normalized event kind. Keeping this on the
// enum lets the session manager group events before it needs to inspect lifecycle semantics.
Expand Down
47 changes: 47 additions & 0 deletions crates/cli/tests/coverage/shared/events_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

use serde_json::json;

use super::AgentKind;
use super::json_path::{string_at, string_at_any, value_at, value_at_any};

#[test]
fn agent_kinds_use_stable_runtime_metadata_names() {
assert_eq!(AgentKind::Codex.as_str(), "codex");
assert_eq!(AgentKind::ClaudeCode.as_str(), "claude-code");
assert_eq!(AgentKind::Hermes.as_str(), "hermes");
assert_eq!(AgentKind::Gateway.as_str(), "gateway");
}

#[test]
fn values_follow_nested_paths_and_return_owned_data() {
let payload = json!({"request": {"id": "request-1", "enabled": true}});

let extracted = value_at(&payload, &["request", "id"]);
assert_eq!(extracted, Some(json!("request-1")));
assert_eq!(value_at(&payload, &["request", "missing"]), None);
assert_eq!(
value_at_any(&payload, &[&["missing"], &["request", "enabled"]]),
Some(json!(true))
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}

#[test]
fn string_helpers_accept_scalars_and_skip_empty_or_structured_values() {
let payload = json!({
"empty": "",
"number": 42,
"enabled": false,
"object": {"id": "nested"}
});

assert_eq!(string_at(&payload, &["number"]), Some("42".into()));
assert_eq!(string_at(&payload, &["enabled"]), Some("false".into()));
assert_eq!(string_at(&payload, &["empty"]), None);
assert_eq!(string_at(&payload, &["object"]), None);
assert_eq!(
string_at_any(&payload, &[&["empty"], &["object"], &["object", "id"]]),
Some("nested".into())
);
}
48 changes: 48 additions & 0 deletions crates/cli/tests/coverage/shared/file_io_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,54 @@ fn lock_attempts_distinguish_contention_from_errors() {
fs2::FileExt::unlock(&waiter).unwrap();
}

#[test]
fn bounded_file_reads_stream_regular_content_and_reject_directories() {
let directory = tempdir().unwrap();
let path = directory.path().join("payload.json");
std::fs::write(&path, b"relay payload").unwrap();

assert_eq!(
crate::filesystem::bounded::read_bounded_regular_file(&path, "payload").unwrap(),
b"relay payload"
);

let mut chunks = Vec::new();
crate::filesystem::bounded::stream_bounded_regular_file(&path, "payload", |chunk| {
chunks.extend_from_slice(chunk);
})
.unwrap();
assert_eq!(chunks, b"relay payload");

let error = crate::filesystem::bounded::read_bounded_regular_file(directory.path(), "payload")
.unwrap_err();
assert!(error.contains("must be a regular file"), "{error}");
}

#[test]
fn backups_and_snapshots_restore_original_or_missing_files() {
let directory = tempdir().unwrap();
let path = directory.path().join("relay.toml");
std::fs::write(&path, b"original").unwrap();

backup(&path).unwrap();
std::fs::write(&path, b"changed").unwrap();
backup(&path).unwrap();
assert_eq!(std::fs::read(backup_path(&path)).unwrap(), b"original");
remove_backup(&path).unwrap();
remove_backup(&path).unwrap();

let existing = snapshot_optional_file(&path).unwrap();
std::fs::remove_file(&path).unwrap();
restore_file_snapshot(&existing).unwrap();
assert_eq!(std::fs::read(&path).unwrap(), b"changed");

let missing = directory.path().join("missing.toml");
let absent = snapshot_optional_file(&missing).unwrap();
std::fs::write(&missing, b"temporary").unwrap();
restore_file_snapshot(&absent).unwrap();
assert!(!missing.exists());
}

#[cfg(unix)]
#[test]
fn private_atomic_write_ignores_a_permissive_umask() {
Expand Down
30 changes: 30 additions & 0 deletions crates/cli/tests/coverage/shared/installer_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,36 @@ fn verified_hook_response_rejects_invalid_status_and_fail_open_http_errors() {
handle_hook_forward_status(reqwest::StatusCode::BAD_GATEWAY, String::new(), false).unwrap();
}

#[test]
fn hook_response_statuses_preserve_guardrail_rejections_and_fail_closed_errors() {
let rejection = handle_hook_forward_status(
reqwest::StatusCode::FORBIDDEN,
r#"{"error":{"type":"nemo_relay_guardrail_rejected","reason":"policy denied"}}"#.into(),
false,
)
.unwrap_err()
.to_string();
assert!(rejection.contains("policy denied"), "{rejection}");

let fallback = handle_hook_forward_status(
reqwest::StatusCode::BAD_REQUEST,
r#"{"error":{"type":"nemo_relay_guardrail_rejected","message":"fallback"}}"#.into(),
false,
)
.unwrap_err()
.to_string();
assert!(fallback.contains("fallback"), "{fallback}");

let error = handle_hook_forward_status(
reqwest::StatusCode::BAD_GATEWAY,
"not a guardrail response".into(),
true,
)
.unwrap_err()
.to_string();
assert!(error.contains("HTTP 502"), "{error}");
}

#[test]
fn windows_hook_decoder_rejects_unsafe_odd_and_trailing_argument_envelopes() {
const SEPARATOR: &str = " -NoLogo -NoProfile -NonInteractive -EncodedCommand ";
Expand Down
59 changes: 2 additions & 57 deletions crates/core/src/plugin/dynamic/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,60 +397,5 @@ impl Drop for PluginHostActivation {
}

#[cfg(test)]
mod tests {
use super::*;
use crate::plugin::{PLUGIN_HANDLERS, PLUGIN_MUTATION_OWNER, PluginMutationOwner};

struct PoisonedRegistryCleanup;

impl Drop for PoisonedRegistryCleanup {
fn drop(&mut self) {
PLUGIN_HANDLERS.clear_poison();
if let Ok(mut owner) = PLUGIN_MUTATION_OWNER.lock() {
*owner = PluginMutationOwner::Idle;
}
}
}

#[test]
fn unsafe_kind_deregistration_retains_runtime_and_owner() {
let _guard = crate::shared_runtime::runtime_owner_test_mutex()
.lock()
.unwrap_or_else(|error| error.into_inner());
let _cleanup = PoisonedRegistryCleanup;
let claim = acquire_plugin_host_lease().expect("fixture host should acquire the owner");
let owner_id = claim.owner_id();
let mut activation = PluginHostActivation {
active: true,
native: Some(NativePluginActivation::with_plugin_kind_for_test(
"fixture.poisoned",
)),
#[cfg(feature = "worker-grpc")]
worker: None,
claim: Some(claim),
};

std::thread::spawn(|| {
let _registry = PLUGIN_HANDLERS.write().unwrap();
panic!("poison plugin registry for teardown test");
})
.join()
.expect_err("fixture registry writer should panic");

let error = activation
.clear_inner()
.expect_err("an uncertain kind deregistration must retain the activation")
.to_string();
assert!(error.contains("plugin registry lock poisoned"), "{error}");
assert!(error.contains("activation owner were retained"), "{error}");
assert!(!activation.is_active());
assert_eq!(
*PLUGIN_MUTATION_OWNER.lock().unwrap(),
PluginMutationOwner::Host(owner_id)
);
assert!(matches!(
acquire_plugin_host_lease(),
Err(crate::plugin::PluginError::Conflict(_))
));
}
}
#[path = "../../../tests/unit/plugin_dynamic_host_tests.rs"]
mod tests;
23 changes: 23 additions & 0 deletions crates/core/tests/coverage/error_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,26 @@ fn test_error_debug() {
let debug = format!("{e:?}");
assert!(debug.contains("AlreadyExists"));
}

#[test]
fn upstream_failures_classify_retryability_and_render_status() {
use std::collections::BTreeMap;

let retryable = UpstreamFailure {
status: Some(503),
body: "temporarily unavailable".into(),
headers: BTreeMap::new(),
class: UpstreamFailureClass::RetryableStatus,
};
assert!(retryable.is_retryable());
assert!(retryable.to_string().contains("HTTP 503"));

let rejected = UpstreamFailure {
status: None,
body: "invalid API key".into(),
headers: BTreeMap::new(),
class: UpstreamFailureClass::Authentication,
};
assert!(!rejected.is_retryable());
assert!(rejected.to_string().contains("transport failure"));
}
Comment thread
willkill07 marked this conversation as resolved.
Resolved
116 changes: 116 additions & 0 deletions crates/core/tests/unit/plugin_dynamic_host_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

use super::*;
use crate::plugin::{PLUGIN_HANDLERS, PLUGIN_MUTATION_OWNER, PluginMutationOwner};
use serde_json::{Map, Value as Json};

struct PoisonedRegistryCleanup;

impl Drop for PoisonedRegistryCleanup {
fn drop(&mut self) {
PLUGIN_HANDLERS.clear_poison();
if let Ok(mut owner) = PLUGIN_MUTATION_OWNER.lock() {
*owner = PluginMutationOwner::Idle;
}
}
}

#[test]
fn unsafe_kind_deregistration_retains_runtime_and_owner() {
let _guard = crate::shared_runtime::runtime_owner_test_mutex()
.lock()
.unwrap_or_else(|error| error.into_inner());
let _cleanup = PoisonedRegistryCleanup;
let claim = acquire_plugin_host_lease().expect("fixture host should acquire the owner");
let owner_id = claim.owner_id();
let mut activation = PluginHostActivation {
active: true,
native: Some(NativePluginActivation::with_plugin_kind_for_test(
"fixture.poisoned",
)),
#[cfg(feature = "worker-grpc")]
worker: None,
claim: Some(claim),
};

std::thread::spawn(|| {
let _registry = PLUGIN_HANDLERS.write().unwrap();
panic!("poison plugin registry for teardown test");
})
.join()
.expect_err("fixture registry writer should panic");

let error = activation
.clear_inner()
.expect_err("an uncertain kind deregistration must retain the activation")
.to_string();
assert!(error.contains("plugin registry lock poisoned"), "{error}");
assert!(error.contains("activation owner were retained"), "{error}");
assert!(!activation.is_active());
assert_eq!(
*PLUGIN_MUTATION_OWNER.lock().unwrap(),
PluginMutationOwner::Host(owner_id)
);
assert!(matches!(
acquire_plugin_host_lease(),
Err(crate::plugin::PluginError::Conflict(_))
));
}

#[test]
fn dynamic_plugin_specs_require_unique_nonempty_input() {
let empty = validate_dynamic_plugin_specs(&[]).unwrap_err().to_string();
assert!(
empty.contains("requires at least one dynamic plugin"),
"{empty}"
);

let duplicate = DynamicPluginActivationSpec {
plugin_id: "fixture.duplicate".into(),
kind: DynamicPluginKind::RustDynamic,
manifest_ref: "relay-plugin.toml".into(),
environment_ref: None,
config: Map::new(),
};
let error = validate_dynamic_plugin_specs(&[duplicate.clone(), duplicate])
.unwrap_err()
.to_string();
assert!(error.contains("duplicate dynamic plugin id"), "{error}");
}

#[test]
fn plugin_error_context_preserves_each_error_class() {
use crate::plugin::PluginError;

let serialization = serde_json::from_str::<Json>("{").unwrap_err();
let errors = [
PluginError::InvalidConfig("invalid".into()),
PluginError::Conflict("conflict".into()),
PluginError::NotFound("missing".into()),
PluginError::Serialization(serialization),
PluginError::Internal("internal".into()),
PluginError::RegistrationFailed("registration".into()),
];

for error in errors {
let message = plugin_error_context("dynamic load", error).to_string();
assert!(message.contains("dynamic load"), "{message}");
}
}

#[test]
fn retained_runtime_errors_include_cleanup_details_when_available() {
let default_error = retained_runtime_error(Vec::new()).to_string();
assert!(
default_error.contains("teardown was incomplete"),
"{default_error}"
);

let detailed_error =
retained_runtime_error(vec!["registry remained active".into()]).to_string();
assert!(
detailed_error.contains("registry remained active"),
"{detailed_error}"
);
}
Loading
Loading