|
|
@@ -1629,6 +1629,12 @@ fn build_plugin_manifest(
|
|
|
let permissions = build_manifest_permissions(&raw.permissions, &mut errors);
|
|
|
validate_command_entries(root, raw.hooks.pre_tool_use.iter(), "hook", &mut errors);
|
|
|
validate_command_entries(root, raw.hooks.post_tool_use.iter(), "hook", &mut errors);
|
|
|
+ validate_command_entries(
|
|
|
+ root,
|
|
|
+ raw.hooks.post_tool_use_failure.iter(),
|
|
|
+ "hook",
|
|
|
+ &mut errors,
|
|
|
+ );
|
|
|
validate_command_entries(
|
|
|
root,
|
|
|
raw.lifecycle.init.iter(),
|
|
|
@@ -2306,6 +2312,16 @@ mod tests {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
+ fn write_broken_failure_hook_plugin(root: &Path, name: &str) {
|
|
|
+ write_file(
|
|
|
+ root.join(MANIFEST_RELATIVE_PATH).as_path(),
|
|
|
+ format!(
|
|
|
+ "{{\n \"name\": \"{name}\",\n \"version\": \"1.0.0\",\n \"description\": \"broken plugin\",\n \"hooks\": {{\n \"PostToolUseFailure\": [\"./hooks/missing-failure.sh\"]\n }}\n}}"
|
|
|
+ )
|
|
|
+ .as_str(),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
fn write_lifecycle_plugin(root: &Path, name: &str, version: &str) -> PathBuf {
|
|
|
let log_path = root.join("lifecycle.log");
|
|
|
write_file(
|
|
|
@@ -3178,14 +3194,19 @@ mod tests {
|
|
|
|
|
|
#[test]
|
|
|
fn rejects_plugin_sources_with_missing_hook_paths() {
|
|
|
+ // given
|
|
|
let config_home = temp_dir("broken-home");
|
|
|
let source_root = temp_dir("broken-source");
|
|
|
write_broken_plugin(&source_root, "broken");
|
|
|
|
|
|
let manager = PluginManager::new(PluginManagerConfig::new(&config_home));
|
|
|
+
|
|
|
+ // when
|
|
|
let error = manager
|
|
|
.validate_plugin_source(source_root.to_str().expect("utf8 path"))
|
|
|
.expect_err("missing hook file should fail validation");
|
|
|
+
|
|
|
+ // then
|
|
|
assert!(error.to_string().contains("does not exist"));
|
|
|
|
|
|
let mut manager = PluginManager::new(PluginManagerConfig::new(&config_home));
|
|
|
@@ -3198,6 +3219,33 @@ mod tests {
|
|
|
let _ = fs::remove_dir_all(source_root);
|
|
|
}
|
|
|
|
|
|
+ #[test]
|
|
|
+ fn rejects_plugin_sources_with_missing_failure_hook_paths() {
|
|
|
+ // given
|
|
|
+ let config_home = temp_dir("broken-failure-home");
|
|
|
+ let source_root = temp_dir("broken-failure-source");
|
|
|
+ write_broken_failure_hook_plugin(&source_root, "broken-failure");
|
|
|
+
|
|
|
+ let manager = PluginManager::new(PluginManagerConfig::new(&config_home));
|
|
|
+
|
|
|
+ // when
|
|
|
+ let error = manager
|
|
|
+ .validate_plugin_source(source_root.to_str().expect("utf8 path"))
|
|
|
+ .expect_err("missing failure hook file should fail validation");
|
|
|
+
|
|
|
+ // then
|
|
|
+ assert!(error.to_string().contains("does not exist"));
|
|
|
+
|
|
|
+ let mut manager = PluginManager::new(PluginManagerConfig::new(&config_home));
|
|
|
+ let install_error = manager
|
|
|
+ .install(source_root.to_str().expect("utf8 path"))
|
|
|
+ .expect_err("install should reject invalid failure hook paths");
|
|
|
+ assert!(install_error.to_string().contains("does not exist"));
|
|
|
+
|
|
|
+ let _ = fs::remove_dir_all(config_home);
|
|
|
+ let _ = fs::remove_dir_all(source_root);
|
|
|
+ }
|
|
|
+
|
|
|
#[test]
|
|
|
fn plugin_registry_runs_initialize_and_shutdown_for_enabled_plugins() {
|
|
|
let config_home = temp_dir("lifecycle-home");
|