use super::{
PluginComponentPath, PluginConfig, PluginIdentity, PluginProposalPolicy,
PluginProposalReviewMode, PluginRegistryConfig,
};
use crate::plugin::{
CURRENT_WIT_WORLD, MAX_PLUGIN_IDENTITY_BYTES, PluginCapabilities, PluginIdentityError,
PluginManifest, PluginRegistryError, PluginRuntimeLimits, WorkspacePathGrant,
policy::capability::tests::capabilities_strategy,
};
use proptest::prelude::*;
#[test]
fn plugin_registry_defaults_to_no_plugins() {
let registry = PluginRegistryConfig::default();
assert!(registry.plugins.is_empty());
assert_eq!(registry.enabled_plugins().count(), 0);
let validated = registry.validate().expect("empty registry should validate");
assert_eq!(validated.plugins().len(), 0);
assert_eq!(validated.enabled_plugins().count(), 0);
}
#[test]
fn plugin_registry_validation_rejects_empty_identities() {
let registry = PluginRegistryConfig {
plugins: vec![PluginConfig {
enabled: true,
component_path: "plugins/empty.wasm".into(),
..PluginConfig::default()
}],
};
assert_eq!(
registry.validate(),
Err(PluginRegistryError::InvalidIdentity {
identity: String::new(),
source: PluginIdentityError::Empty,
})
);
}
#[test]
fn plugin_identity_accepts_only_stable_ascii_grammar() {
for identity in [
"formatter",
"0formatter",
"com.example.formatter",
"fmt_v1-a",
] {
let validated = PluginIdentity::try_new(identity).expect("identity should be valid");
assert_eq!(validated.as_str(), identity);
assert_eq!(validated.into_string(), identity);
}
let overlong = format!("a{}", "b".repeat(MAX_PLUGIN_IDENTITY_BYTES));
for identity in [
"",
" Formatter",
"formatter/plugin",
"formatter plugin",
"Formatter",
"éditor",
".formatter",
"_formatter",
"-formatter",
"formatter\\plugin",
overlong.as_str(),
] {
assert!(
PluginIdentity::try_new(identity).is_err(),
"{identity:?} should not form a plugin identity"
);
}
}
#[test]
fn plugin_registry_validation_rejects_invalid_identities() {
for identity in [
" Formatter",
"formatter/plugin",
"formatter plugin",
"Formatter",
"éditor",
".formatter",
] {
let registry = PluginRegistryConfig {
plugins: vec![PluginConfig {
identity: identity.to_owned(),
enabled: true,
..PluginConfig::default()
}],
};
assert!(
matches!(
registry.validate(),
Err(PluginRegistryError::InvalidIdentity { .. })
),
"{identity:?} should fail registry validation"
);
}
}
#[test]
fn plugin_registry_validation_rejects_duplicate_identities_even_when_disabled() {
let registry = PluginRegistryConfig {
plugins: vec![
PluginConfig {
identity: String::from("formatter"),
enabled: true,
component_path: "plugins/formatter.wasm".into(),
..PluginConfig::default()
},
PluginConfig {
identity: String::from("formatter"),
enabled: false,
..PluginConfig::default()
},
],
};
assert_eq!(
registry.validate(),
Err(PluginRegistryError::DuplicateIdentity {
identity: identity("formatter")
})
);
}
#[test]
fn disabled_plugins_have_no_effective_manifest_grants() {
let config = PluginConfig {
identity: String::from("formatter"),
enabled: false,
capabilities: PluginCapabilities {
buffer_observe: true,
status_publish: true,
..PluginCapabilities::default()
},
..PluginConfig::default()
};
let manifest = PluginManifest::new_untrusted(
"formatter",
CURRENT_WIT_WORLD,
PluginCapabilities {
buffer_observe: true,
status_publish: true,
..PluginCapabilities::default()
},
);
assert_eq!(
config.granted_manifest_capabilities(&manifest),
PluginCapabilities::default()
);
}
#[test]
fn manifest_grants_are_intersection_of_request_and_config() {
let config = PluginConfig {
identity: String::from("formatter"),
enabled: true,
component_path: "plugins/formatter.wasm".into(),
capabilities: PluginCapabilities {
buffer_observe: true,
buffer_propose_edit: false,
workspace_observe: vec![WorkspacePathGrant::new("docs")],
status_publish: true,
..PluginCapabilities::default()
},
..PluginConfig::default()
};
let manifest = PluginManifest::new_untrusted(
"formatter",
CURRENT_WIT_WORLD,
PluginCapabilities {
buffer_observe: true,
buffer_propose_edit: true,
workspace_observe: vec![
WorkspacePathGrant::new("docs"),
WorkspacePathGrant::new("src"),
],
status_publish: false,
..PluginCapabilities::default()
},
);
let granted = config.granted_manifest_capabilities(&manifest);
assert!(granted.buffer_observe);
assert!(!granted.buffer_propose_edit);
assert_eq!(
granted.workspace_observe,
vec![WorkspacePathGrant::new("docs")]
);
assert!(!granted.status_publish);
}
#[test]
fn config_can_narrow_manifest_workspace_grants() {
let config = PluginConfig {
identity: String::from("formatter"),
enabled: true,
component_path: "plugins/formatter.wasm".into(),
capabilities: PluginCapabilities {
workspace_observe: vec![WorkspacePathGrant::new("docs/generated")],
workspace_artifact_write: vec![WorkspacePathGrant::new("docs/generated")],
..PluginCapabilities::default()
},
..PluginConfig::default()
};
let manifest = PluginManifest::new_untrusted(
"formatter",
CURRENT_WIT_WORLD,
PluginCapabilities {
workspace_observe: vec![WorkspacePathGrant::new("docs")],
workspace_artifact_write: vec![WorkspacePathGrant::all_workspace()],
..PluginCapabilities::default()
},
);
let granted = config.granted_manifest_capabilities(&manifest);
assert_eq!(
granted.workspace_observe,
vec![WorkspacePathGrant::new("docs/generated")]
);
assert_eq!(
granted.workspace_artifact_write,
vec![WorkspacePathGrant::new("docs/generated")]
);
}
#[test]
fn plugin_config_uses_current_wit_world_by_default() {
let config = PluginConfig::default();
assert_eq!(config.wit_world, CURRENT_WIT_WORLD);
assert!(config.supports_current_world());
assert_eq!(
config.proposal_policy,
PluginProposalPolicy {
buffer_edits: PluginProposalReviewMode::AutoApply,
}
);
}
#[test]
fn component_paths_reject_empty_absolute_and_dot_components() {
for path in [
"",
"/tmp/plugin.wasm",
"C:/plugin.wasm",
"plugins/../secret.wasm",
"plugins//formatter.wasm",
"plugins\\formatter.wasm",
"./plugin.wasm",
] {
assert!(
PluginComponentPath::try_new(path).is_err(),
"{path:?} should not form a runtime component path"
);
}
let path = PluginComponentPath::try_new("plugins/formatter.wasm")
.expect("normal relative component path should validate");
assert_eq!(
path.as_path(),
std::path::Path::new("plugins/formatter.wasm")
);
}
#[test]
fn component_path_debug_redacts_validated_path() {
let path = PluginComponentPath::try_new("plugins/secret-component.wasm")
.expect("component path should validate");
let debug = format!("{path:?}");
assert!(debug.contains("path_byte_len"));
assert!(!debug.contains("plugins/secret-component.wasm"));
assert!(!debug.contains("secret-component"));
}
#[test]
fn enabled_plugins_must_have_loadable_component_path_and_current_world() {
let invalid_path = PluginRegistryConfig {
plugins: vec![PluginConfig {
identity: String::from("formatter"),
enabled: true,
component_path: "../formatter.wasm".into(),
..PluginConfig::default()
}],
};
assert!(matches!(
invalid_path.validate(),
Err(PluginRegistryError::InvalidEnabledComponentPath { .. })
));
let unsupported_world = PluginRegistryConfig {
plugins: vec![PluginConfig {
identity: String::from("formatter"),
enabled: true,
component_path: "plugins/formatter.wasm".into(),
wit_world: String::from("alma:editor/plugin@9.9.9"),
..PluginConfig::default()
}],
};
assert!(matches!(
unsupported_world.validate(),
Err(PluginRegistryError::UnsupportedEnabledWitWorld { .. })
));
let disabled_unloadable = PluginRegistryConfig {
plugins: vec![PluginConfig {
identity: String::from("formatter"),
enabled: false,
..PluginConfig::default()
}],
};
assert!(disabled_unloadable.validate().is_ok());
}
#[test]
fn enabled_plugins_must_have_valid_runtime_limits() {
let registry = PluginRegistryConfig {
plugins: vec![PluginConfig {
identity: String::from("formatter"),
enabled: true,
component_path: "plugins/formatter.wasm".into(),
runtime_limits: PluginRuntimeLimits {
timeout_ms: 0,
..PluginRuntimeLimits::default()
},
..PluginConfig::default()
}],
};
assert!(matches!(
registry.validate(),
Err(PluginRegistryError::InvalidEnabledRuntimeLimits { .. })
));
}
#[test]
fn validated_registry_exposes_enabled_proof_view_only_for_loadable_entries() {
let registry = PluginRegistryConfig {
plugins: vec![
PluginConfig {
identity: String::from("formatter"),
enabled: true,
component_path: "plugins/formatter.wasm".into(),
capabilities: PluginCapabilities {
status_publish: true,
..PluginCapabilities::default()
},
..PluginConfig::default()
},
PluginConfig {
identity: String::from("disabled"),
enabled: false,
..PluginConfig::default()
},
],
}
.validate()
.expect("registry should validate");
let enabled = registry
.enabled_plugin(&identity("formatter"))
.expect("formatter should be loadable");
assert_eq!(enabled.identity().as_str(), "formatter");
assert_eq!(
enabled.component_path().as_path(),
std::path::Path::new("plugins/formatter.wasm")
);
assert_eq!(enabled.wit_world(), CURRENT_WIT_WORLD);
assert!(enabled.capabilities().status_publish);
assert_eq!(
enabled.runtime_limits().as_config(),
PluginRuntimeLimits::default()
);
assert_eq!(
enabled.proposal_policy().buffer_edit_review_mode(),
PluginProposalReviewMode::AutoApply
);
assert!(registry.enabled_plugin(&identity("disabled")).is_none());
assert_eq!(registry.enabled_plugins().count(), 1);
}
#[test]
fn enabled_plugins_expose_configured_proposal_policy() {
let registry = PluginRegistryConfig {
plugins: vec![PluginConfig {
identity: String::from("formatter"),
enabled: true,
component_path: "plugins/formatter.wasm".into(),
proposal_policy: PluginProposalPolicy {
buffer_edits: PluginProposalReviewMode::ManualReview,
},
..PluginConfig::default()
}],
}
.validate()
.expect("registry should validate");
let enabled = registry
.enabled_plugin(&identity("formatter"))
.expect("formatter should be loadable");
assert_eq!(
enabled.proposal_policy().buffer_edit_review_mode(),
PluginProposalReviewMode::ManualReview
);
}
fn identity(identity: &str) -> PluginIdentity {
PluginIdentity::try_new(identity).expect("test identity should validate")
}
fn valid_identity_strategy() -> impl Strategy<Value = String> {
"[a-z0-9][a-z0-9._-]{0,31}"
}
fn invalid_identity_strategy() -> impl Strategy<Value = String> {
prop_oneof![
Just(String::new()),
"[A-Z][a-z0-9._-]{0,8}",
"[._-][a-z0-9._-]{0,8}",
"[a-z0-9]{1,8}/[a-z0-9]{1,8}",
"[a-z0-9]{1,8} [a-z0-9]{1,8}",
"[a-z0-9]{1,8}\\\\[a-z0-9]{1,8}",
"[a-z0-9]{1,8}é[a-z0-9]{0,8}",
Just(format!("a{}", "b".repeat(MAX_PLUGIN_IDENTITY_BYTES))),
]
}
fn plugin_config_with_identity_strategy() -> impl Strategy<Value = PluginConfig> {
(
valid_identity_strategy(),
any::<bool>(),
capabilities_strategy(),
)
.prop_map(|(identity, enabled, capabilities)| PluginConfig {
identity,
enabled,
component_path: if enabled {
"plugins/plugin.wasm".into()
} else {
std::path::PathBuf::new()
},
capabilities,
..PluginConfig::default()
})
}
proptest! {
#[test]
fn valid_plugin_identities_round_trip(identity in valid_identity_strategy()) {
let validated = PluginIdentity::try_new(identity.clone())
.expect("generated identity should validate");
prop_assert_eq!(validated.as_str(), identity.as_str());
prop_assert_eq!(validated.into_string(), identity);
}
#[test]
fn invalid_plugin_identities_reject(identity in invalid_identity_strategy()) {
prop_assert!(PluginIdentity::try_new(identity).is_err());
}
#[test]
fn unique_valid_registry_identities_validate(
plugins in prop::collection::vec(plugin_config_with_identity_strategy(), 0..16),
) {
let mut seen = std::collections::HashSet::new();
prop_assume!(plugins.iter().all(|plugin| seen.insert(plugin.identity.clone())));
let registry = PluginRegistryConfig {
plugins: plugins.clone(),
};
let validated = registry.validate()
.expect("unique valid identities should validate");
let validated_configs: Vec<_> = validated
.plugins()
.map(|plugin| plugin.config().clone())
.collect();
prop_assert_eq!(validated_configs, plugins);
}
#[test]
fn duplicate_registry_identities_reject(
mut plugins in prop::collection::vec(plugin_config_with_identity_strategy(), 1..16),
duplicate_enabled in any::<bool>(),
) {
let mut seen = std::collections::HashSet::new();
prop_assume!(plugins.iter().all(|plugin| seen.insert(plugin.identity.clone())));
let duplicate_identity = plugins[0].identity.clone();
plugins.push(PluginConfig {
identity: duplicate_identity.clone(),
enabled: duplicate_enabled,
..PluginConfig::default()
});
let registry = PluginRegistryConfig { plugins };
let error = registry
.validate()
.expect_err("duplicate identity should reject");
prop_assert_eq!(
error,
PluginRegistryError::DuplicateIdentity {
identity: PluginIdentity::try_new(duplicate_identity)
.expect("generated identity should validate")
}
);
}
#[test]
fn invalid_registry_identity_rejects(
mut plugins in prop::collection::vec(plugin_config_with_identity_strategy(), 0..16),
empty_enabled in any::<bool>(),
invalid_identity in invalid_identity_strategy(),
) {
let mut seen = std::collections::HashSet::new();
prop_assume!(plugins.iter().all(|plugin| seen.insert(plugin.identity.clone())));
plugins.push(PluginConfig {
identity: invalid_identity.clone(),
enabled: empty_enabled,
..PluginConfig::default()
});
let registry = PluginRegistryConfig { plugins };
let invalid_identity_rejected = matches!(
registry.validate(),
Err(PluginRegistryError::InvalidIdentity { identity, .. }) if identity == invalid_identity
);
prop_assert!(invalid_identity_rejected);
}
#[test]
fn identity_or_world_mismatch_never_receives_effective_grants(
configured in capabilities_strategy(),
requested in capabilities_strategy(),
mismatch_identity in any::<bool>(),
) {
let config = PluginConfig {
identity: String::from("plugin"),
enabled: true,
capabilities: configured,
..PluginConfig::default()
};
let manifest = PluginManifest::new_untrusted(
if mismatch_identity {
String::from("other")
} else {
String::from("plugin")
},
if mismatch_identity {
crate::plugin::CURRENT_WIT_WORLD.to_owned()
} else {
String::from("alma:editor/plugin@9.9.9")
},
requested,
);
prop_assert_eq!(
config.granted_manifest_capabilities(&manifest),
PluginCapabilities::default()
);
}
#[test]
fn invalid_matching_identities_never_receive_effective_grants(
configured in capabilities_strategy(),
requested in capabilities_strategy(),
invalid_identity in invalid_identity_strategy(),
) {
let config = PluginConfig {
identity: invalid_identity.clone(),
enabled: true,
capabilities: configured,
..PluginConfig::default()
};
let manifest =
PluginManifest::new_untrusted(invalid_identity, CURRENT_WIT_WORLD, requested);
prop_assert_eq!(
config.granted_manifest_capabilities(&manifest),
PluginCapabilities::default()
);
}
#[test]
fn disabled_plugins_never_receive_effective_grants(
configured in capabilities_strategy(),
requested in capabilities_strategy(),
) {
let config = PluginConfig {
identity: String::from("plugin"),
enabled: false,
capabilities: configured,
..PluginConfig::default()
};
let manifest = PluginManifest::new_untrusted("plugin", CURRENT_WIT_WORLD, requested);
prop_assert_eq!(
config.granted_manifest_capabilities(&manifest),
PluginCapabilities::default()
);
}
}