use super::super::Client;
use super::{Binaries, Manifest};
fn fresh_base_dir() -> std::path::PathBuf {
let d = std::env::temp_dir().join(format!("oai-list-plugins-{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(&d).unwrap();
d
}
fn cleanup(d: &std::path::Path) {
let _ = std::fs::remove_dir_all(d);
}
fn client_for(base: &std::path::Path) -> Client {
Client::new(Some(base.to_path_buf()), None::<&str>, None::<&str>)
}
fn minimal_manifest_json() -> String {
serde_json::to_string(&Manifest {
description: "tiny test plugin".to_string(),
version: "0.1.0".to_string(),
author: None,
homepage: None,
license: None,
binaries: Binaries::default(),
viewer_zip: None,
viewer_routes: vec![],
mobile_ready: false,
})
.unwrap()
}
#[tokio::test]
async fn list_plugins_returns_empty_when_dir_missing() {
let base = fresh_base_dir();
let client = client_for(&base);
let plugins = client.list_plugins(0, 100).await;
assert!(plugins.is_empty(), "expected empty Vec, got {plugins:?}");
cleanup(&base);
}
#[tokio::test]
async fn list_plugins_returns_empty_when_dir_empty() {
let base = fresh_base_dir();
std::fs::create_dir_all(base.join("plugins")).unwrap();
let client = client_for(&base);
let plugins = client.list_plugins(0, 100).await;
assert!(plugins.is_empty(), "expected empty Vec, got {plugins:?}");
cleanup(&base);
}
#[tokio::test]
async fn list_plugins_parses_valid_manifest() {
let base = fresh_base_dir();
let plugins_dir = base.join("plugins");
std::fs::create_dir_all(&plugins_dir).unwrap();
let manifest_path = plugins_dir.join("psyops.json");
std::fs::write(&manifest_path, minimal_manifest_json()).unwrap();
let client = client_for(&base);
let plugins = client.list_plugins(0, 100).await;
assert_eq!(plugins.len(), 1);
let p = &plugins[0];
assert_eq!(p.name, "psyops");
assert_eq!(p.manifest.description, "tiny test plugin");
assert_eq!(p.manifest.version, "0.1.0");
assert_eq!(p.source, manifest_path.to_string_lossy());
cleanup(&base);
}
#[tokio::test]
async fn list_plugins_skips_invalid_files() {
let base = fresh_base_dir();
let plugins_dir = base.join("plugins");
std::fs::create_dir_all(&plugins_dir).unwrap();
std::fs::write(plugins_dir.join("a.json"), minimal_manifest_json()).unwrap();
std::fs::write(plugins_dir.join("b.json"), "\"not json\"").unwrap();
std::fs::write(plugins_dir.join("c.json"), r#"{"version":"1.0.0"}"#).unwrap();
std::fs::write(plugins_dir.join("noise.txt"), "ignore me").unwrap();
let client = client_for(&base);
let plugins = client.list_plugins(0, 100).await;
assert_eq!(plugins.len(), 1, "got {plugins:?}");
assert_eq!(plugins[0].name, "a");
cleanup(&base);
}
#[tokio::test]
async fn list_plugins_handles_multiple_valid_manifests() {
let base = fresh_base_dir();
let plugins_dir = base.join("plugins");
std::fs::create_dir_all(&plugins_dir).unwrap();
for stem in ["a", "b", "c"] {
std::fs::write(plugins_dir.join(format!("{stem}.json")), minimal_manifest_json()).unwrap();
}
let client = client_for(&base);
let mut plugins = client.list_plugins(0, 100).await;
plugins.sort_by(|x, y| x.name.cmp(&y.name));
let names: Vec<&str> = plugins.iter().map(|p| p.name.as_str()).collect();
assert_eq!(names, vec!["a", "b", "c"]);
cleanup(&base);
}
#[tokio::test]
async fn get_plugin_returns_some_when_manifest_exists() {
let base = fresh_base_dir();
let plugins_dir = base.join("plugins");
std::fs::create_dir_all(&plugins_dir).unwrap();
let manifest_path = plugins_dir.join("psyops.json");
std::fs::write(&manifest_path, minimal_manifest_json()).unwrap();
let client = client_for(&base);
let plugin = client.get_plugin("psyops").await;
let p = plugin.expect("expected Some(_)");
assert_eq!(p.name, "psyops");
assert_eq!(p.manifest.description, "tiny test plugin");
assert_eq!(p.manifest.version, "0.1.0");
assert_eq!(p.source, manifest_path.to_string_lossy());
cleanup(&base);
}
#[tokio::test]
async fn get_plugin_returns_none_when_dir_missing() {
let base = fresh_base_dir();
let client = client_for(&base);
assert!(client.get_plugin("psyops").await.is_none());
cleanup(&base);
}
#[tokio::test]
async fn get_plugin_returns_none_when_file_missing() {
let base = fresh_base_dir();
let plugins_dir = base.join("plugins");
std::fs::create_dir_all(&plugins_dir).unwrap();
std::fs::write(plugins_dir.join("other.json"), minimal_manifest_json()).unwrap();
let client = client_for(&base);
assert!(client.get_plugin("missing").await.is_none());
cleanup(&base);
}
#[tokio::test]
async fn get_plugin_returns_none_when_malformed() {
let base = fresh_base_dir();
let plugins_dir = base.join("plugins");
std::fs::create_dir_all(&plugins_dir).unwrap();
std::fs::write(plugins_dir.join("bad.json"), "\"not json\"").unwrap();
let client = client_for(&base);
assert!(client.get_plugin("bad").await.is_none());
cleanup(&base);
}
#[test]
fn plugin_binary_path_layout() {
let base = fresh_base_dir();
let client = client_for(&base);
let expected = client
.plugins_dir()
.join("my-plugin")
.join(if cfg!(windows) { "plugin.exe" } else { "plugin" });
assert_eq!(client.plugin_binary_path("my-plugin"), expected);
assert_eq!(client.plugin_dir("my-plugin"), client.plugins_dir().join("my-plugin"));
cleanup(&base);
}
#[tokio::test]
async fn resolve_plugin_returns_none_when_missing() {
let base = fresh_base_dir();
let client = client_for(&base);
assert!(client.resolve_plugin("nope").await.is_none());
cleanup(&base);
}
#[tokio::test]
async fn resolve_plugin_returns_canonical_nested_path_when_present() {
let base = fresh_base_dir();
let client = client_for(&base);
let target = client.plugin_binary_path("hello");
std::fs::create_dir_all(target.parent().unwrap()).unwrap();
std::fs::write(&target, b"\x7fELF or MZ; contents don't matter for is_file()").unwrap();
let resolved = client.resolve_plugin("hello").await;
assert_eq!(resolved.as_deref(), Some(target.as_path()));
cleanup(&base);
}
#[tokio::test]
async fn resolve_plugin_ignores_flat_layout() {
let base = fresh_base_dir();
let plugins_dir = base.join("plugins");
std::fs::create_dir_all(&plugins_dir).unwrap();
let flat = plugins_dir.join(if cfg!(windows) { "hello.exe" } else { "hello" });
std::fs::write(&flat, b"x").unwrap();
let client = client_for(&base);
assert!(client.resolve_plugin("hello").await.is_none());
cleanup(&base);
}
#[tokio::test]
async fn list_plugins_respects_offset_and_limit() {
let base = fresh_base_dir();
let plugins_dir = base.join("plugins");
std::fs::create_dir_all(&plugins_dir).unwrap();
for stem in ["a", "b", "c"] {
std::fs::write(plugins_dir.join(format!("{stem}.json")), minimal_manifest_json()).unwrap();
}
let client = client_for(&base);
assert_eq!(client.list_plugins(0, 100).await.len(), 3, "unbounded should return all 3");
assert_eq!(client.list_plugins(0, 1).await.len(), 1, "limit=1 should clip to 1");
assert_eq!(client.list_plugins(1, 1).await.len(), 1, "offset=1 limit=1 should return 1");
assert_eq!(client.list_plugins(2, 100).await.len(), 1, "offset=2 should leave 1 item");
assert_eq!(client.list_plugins(5, 100).await.len(), 0, "offset past end is empty");
assert_eq!(client.list_plugins(0, 0).await.len(), 0, "limit=0 is empty");
cleanup(&base);
}