pinakes-plugin-api: expand test coverage; fix merge conflicts
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I34e7c6d382ab7f4b6cf98ede9b7116056a6a6964
This commit is contained in:
parent
152356ce9f
commit
3abfe6a79b
9 changed files with 631 additions and 128 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -4904,6 +4904,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
|
"tokio",
|
||||||
"toml 0.9.11+spec-1.1.0",
|
"toml 0.9.11+spec-1.1.0",
|
||||||
"uuid",
|
"uuid",
|
||||||
"wit-bindgen 0.39.0",
|
"wit-bindgen 0.39.0",
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ resolver = "3"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
readme = true
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
# Async runtime
|
# Async runtime
|
||||||
|
|
|
||||||
|
|
@ -25,3 +25,6 @@ wit-bindgen = { workspace = true, optional = true }
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
wasm = ["wit-bindgen"]
|
wasm = ["wit-bindgen"]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros"] }
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ pub mod wasm;
|
||||||
|
|
||||||
pub use manifest::PluginManifest;
|
pub use manifest::PluginManifest;
|
||||||
pub use types::*;
|
pub use types::*;
|
||||||
|
pub use wasm::host_functions;
|
||||||
|
|
||||||
/// Plugin API version - plugins must match this version
|
/// Plugin API version - plugins must match this version
|
||||||
pub const PLUGIN_API_VERSION: &str = "1.0";
|
pub const PLUGIN_API_VERSION: &str = "1.0";
|
||||||
|
|
|
||||||
556
crates/pinakes-plugin-api/tests/api.rs
Normal file
556
crates/pinakes-plugin-api/tests/api.rs
Normal file
|
|
@ -0,0 +1,556 @@
|
||||||
|
use pinakes_plugin_api::{
|
||||||
|
Capabilities, EnvironmentCapability, Event, EventType,
|
||||||
|
ExtractedMetadata, FilesystemCapability, HealthStatus,
|
||||||
|
MediaTypeDefinition, NetworkCapability, Plugin, PluginContext,
|
||||||
|
PluginError, PluginMetadata, PluginResult, SearchIndexItem, SearchQuery,
|
||||||
|
SearchResult, SearchStats, ThumbnailFormat, ThumbnailInfo, ThumbnailOptions,
|
||||||
|
};
|
||||||
|
use pinakes_plugin_api::wasm::{HttpRequest, HttpResponse, LogLevel, LogMessage};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
struct TestPlugin {
|
||||||
|
initialized: bool,
|
||||||
|
shutdown: bool,
|
||||||
|
health_status: HealthStatus,
|
||||||
|
metadata: PluginMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestPlugin {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
initialized: false,
|
||||||
|
shutdown: false,
|
||||||
|
health_status: HealthStatus {
|
||||||
|
healthy: true,
|
||||||
|
message: Some("OK".to_string()),
|
||||||
|
metrics: HashMap::new(),
|
||||||
|
},
|
||||||
|
metadata: PluginMetadata {
|
||||||
|
id: "test-plugin".to_string(),
|
||||||
|
name: "Test Plugin".to_string(),
|
||||||
|
version: "1.0.0".to_string(),
|
||||||
|
author: "Test Author".to_string(),
|
||||||
|
description: "A test plugin".to_string(),
|
||||||
|
api_version: "1.0".to_string(),
|
||||||
|
capabilities_required: Capabilities::default(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Plugin for TestPlugin {
|
||||||
|
fn metadata(&self) -> &PluginMetadata {
|
||||||
|
&self.metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn initialize(&mut self, _context: PluginContext) -> PluginResult<()> {
|
||||||
|
self.initialized = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn shutdown(&mut self) -> PluginResult<()> {
|
||||||
|
self.shutdown = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn health_check(&self) -> PluginResult<HealthStatus> {
|
||||||
|
Ok(self.health_status.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_plugin_context_creation() {
|
||||||
|
let context = PluginContext {
|
||||||
|
data_dir: PathBuf::from("/data/test-plugin"),
|
||||||
|
cache_dir: PathBuf::from("/cache/test-plugin"),
|
||||||
|
config: HashMap::from([
|
||||||
|
("enabled".to_string(), serde_json::json!(true)),
|
||||||
|
("max_items".to_string(), serde_json::json!(100)),
|
||||||
|
]),
|
||||||
|
capabilities: Capabilities {
|
||||||
|
filesystem: FilesystemCapability {
|
||||||
|
read: vec![PathBuf::from("/data")],
|
||||||
|
write: vec![PathBuf::from("/data")],
|
||||||
|
},
|
||||||
|
network: NetworkCapability {
|
||||||
|
enabled: true,
|
||||||
|
allowed_domains: Some(vec!["api.example.com".to_string()]),
|
||||||
|
},
|
||||||
|
environment: EnvironmentCapability {
|
||||||
|
enabled: true,
|
||||||
|
allowed_vars: Some(vec!["API_KEY".to_string()]),
|
||||||
|
},
|
||||||
|
max_memory_bytes: Some(256 * 1024 * 1024),
|
||||||
|
max_cpu_time_ms: Some(30000),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(context.data_dir, PathBuf::from("/data/test-plugin"));
|
||||||
|
assert_eq!(context.cache_dir, PathBuf::from("/cache/test-plugin"));
|
||||||
|
assert_eq!(context.config.get("enabled").unwrap(), &serde_json::json!(true));
|
||||||
|
assert!(context.capabilities.network.enabled);
|
||||||
|
assert_eq!(
|
||||||
|
context.capabilities.max_memory_bytes,
|
||||||
|
Some(256 * 1024 * 1024)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_plugin_context_fields() {
|
||||||
|
let context = PluginContext {
|
||||||
|
data_dir: PathBuf::from("/custom/data"),
|
||||||
|
cache_dir: PathBuf::from("/custom/cache"),
|
||||||
|
config: HashMap::new(),
|
||||||
|
capabilities: Capabilities::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(context.data_dir, PathBuf::from("/custom/data"));
|
||||||
|
assert_eq!(context.cache_dir, PathBuf::from("/custom/cache"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_plugin_lifecycle() {
|
||||||
|
let mut plugin = TestPlugin::new();
|
||||||
|
|
||||||
|
assert!(!plugin.initialized);
|
||||||
|
assert!(!plugin.shutdown);
|
||||||
|
|
||||||
|
let context = PluginContext {
|
||||||
|
data_dir: PathBuf::from("/data"),
|
||||||
|
cache_dir: PathBuf::from("/cache"),
|
||||||
|
config: HashMap::new(),
|
||||||
|
capabilities: Capabilities::default(),
|
||||||
|
};
|
||||||
|
plugin.initialize(context).await.unwrap();
|
||||||
|
assert!(plugin.initialized);
|
||||||
|
|
||||||
|
let health = plugin.health_check().await.unwrap();
|
||||||
|
assert!(health.healthy);
|
||||||
|
assert_eq!(health.message, Some("OK".to_string()));
|
||||||
|
|
||||||
|
plugin.shutdown().await.unwrap();
|
||||||
|
assert!(plugin.shutdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_extracted_metadata_structure() {
|
||||||
|
let metadata = ExtractedMetadata {
|
||||||
|
title: Some("Test Document".to_string()),
|
||||||
|
description: Some("A test document".to_string()),
|
||||||
|
author: Some("John Doe".to_string()),
|
||||||
|
created_at: Some("2024-01-15T10:30:00Z".to_string()),
|
||||||
|
duration_secs: Some(120.5),
|
||||||
|
width: Some(1920),
|
||||||
|
height: Some(1080),
|
||||||
|
file_size_bytes: Some(1_500_000),
|
||||||
|
codec: Some("h264".to_string()),
|
||||||
|
bitrate_kbps: Some(5000),
|
||||||
|
custom_fields: HashMap::from([
|
||||||
|
("color_space".to_string(), serde_json::json!("sRGB")),
|
||||||
|
("orientation".to_string(), serde_json::json!(90)),
|
||||||
|
]),
|
||||||
|
tags: vec!["test".to_string(), "document".to_string()],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(metadata.title, Some("Test Document".to_string()));
|
||||||
|
assert_eq!(metadata.width, Some(1920));
|
||||||
|
assert_eq!(metadata.height, Some(1080));
|
||||||
|
assert_eq!(metadata.tags.len(), 2);
|
||||||
|
assert_eq!(metadata.custom_fields.get("color_space").unwrap(), "sRGB");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_search_query_serialization() {
|
||||||
|
let query = SearchQuery {
|
||||||
|
query_text: "nature landscape".to_string(),
|
||||||
|
filters: HashMap::from([
|
||||||
|
("type".to_string(), serde_json::json!("image")),
|
||||||
|
("year".to_string(), serde_json::json!(2023)),
|
||||||
|
]),
|
||||||
|
limit: 50,
|
||||||
|
offset: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&query).unwrap();
|
||||||
|
let deserialized: SearchQuery = serde_json::from_str(&serialized).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(deserialized.query_text, "nature landscape");
|
||||||
|
assert_eq!(deserialized.limit, 50);
|
||||||
|
assert_eq!(deserialized.offset, 0);
|
||||||
|
assert_eq!(deserialized.filters.get("type").unwrap(), "image");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_search_result_serialization() {
|
||||||
|
let result = SearchResult {
|
||||||
|
id: "media-123".to_string(),
|
||||||
|
score: 0.95,
|
||||||
|
highlights: vec!["matched <hit>content</hit>".to_string()],
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&result).unwrap();
|
||||||
|
let deserialized: SearchResult = serde_json::from_str(&serialized).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(deserialized.id, "media-123");
|
||||||
|
assert!((deserialized.score - 0.95).abs() < 0.001);
|
||||||
|
assert_eq!(deserialized.highlights.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_search_stats_serialization() {
|
||||||
|
let stats = SearchStats {
|
||||||
|
total_indexed: 10_000,
|
||||||
|
index_size_bytes: 500_000_000,
|
||||||
|
last_update: Some("2024-01-15T12:00:00Z".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&stats).unwrap();
|
||||||
|
let deserialized: SearchStats = serde_json::from_str(&serialized).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(deserialized.total_indexed, 10_000);
|
||||||
|
assert_eq!(deserialized.index_size_bytes, 500_000_000);
|
||||||
|
assert!(deserialized.last_update.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_thumbnail_options_serialization() {
|
||||||
|
let options = ThumbnailOptions {
|
||||||
|
width: 320,
|
||||||
|
height: 240,
|
||||||
|
quality: 85,
|
||||||
|
format: ThumbnailFormat::Jpeg,
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&options).unwrap();
|
||||||
|
let deserialized: ThumbnailOptions = serde_json::from_str(&serialized).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(deserialized.width, 320);
|
||||||
|
assert_eq!(deserialized.height, 240);
|
||||||
|
assert_eq!(deserialized.quality, 85);
|
||||||
|
assert!(matches!(deserialized.format, ThumbnailFormat::Jpeg));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_thumbnail_format_variants() {
|
||||||
|
for format in [ThumbnailFormat::Jpeg, ThumbnailFormat::Png, ThumbnailFormat::WebP] {
|
||||||
|
let options = ThumbnailOptions {
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
quality: 90,
|
||||||
|
format,
|
||||||
|
};
|
||||||
|
let serialized = serde_json::to_string(&options).unwrap();
|
||||||
|
let deserialized: ThumbnailOptions = serde_json::from_str(&serialized).unwrap();
|
||||||
|
assert!(matches!(deserialized.format, _));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_thumbnail_info_structure() {
|
||||||
|
let info = ThumbnailInfo {
|
||||||
|
path: PathBuf::from("/thumbnails/test.jpg"),
|
||||||
|
width: 320,
|
||||||
|
height: 240,
|
||||||
|
file_size_bytes: 45_000,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(info.path, PathBuf::from("/thumbnails/test.jpg"));
|
||||||
|
assert_eq!(info.width, 320);
|
||||||
|
assert_eq!(info.height, 240);
|
||||||
|
assert_eq!(info.file_size_bytes, 45_000);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_media_type_definition() {
|
||||||
|
let media_type = MediaTypeDefinition {
|
||||||
|
id: "custom-image format".to_string(),
|
||||||
|
name: "Custom Image Format".to_string(),
|
||||||
|
category: "image".to_string(),
|
||||||
|
extensions: vec!["cif".to_string(), "custom-img".to_string()],
|
||||||
|
mime_types: vec!["image/x-custom".to_string()],
|
||||||
|
icon: Some("image-cif".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(media_type.id, "custom-image format");
|
||||||
|
assert_eq!(media_type.category, "image");
|
||||||
|
assert_eq!(media_type.extensions.len(), 2);
|
||||||
|
assert!(media_type.icon.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_event_type_variants() {
|
||||||
|
let variants: Vec<EventType> = vec![
|
||||||
|
EventType::MediaImported,
|
||||||
|
EventType::MediaUpdated,
|
||||||
|
EventType::MediaDeleted,
|
||||||
|
EventType::MediaTagged,
|
||||||
|
EventType::MediaUntagged,
|
||||||
|
EventType::CollectionCreated,
|
||||||
|
EventType::CollectionUpdated,
|
||||||
|
EventType::CollectionDeleted,
|
||||||
|
EventType::ScanStarted,
|
||||||
|
EventType::ScanCompleted,
|
||||||
|
EventType::Custom("custom".to_string()),
|
||||||
|
];
|
||||||
|
|
||||||
|
for event_type in &variants {
|
||||||
|
let serialized = serde_json::to_string(event_type).unwrap();
|
||||||
|
let _deserialized: EventType = serde_json::from_str(&serialized).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_event_serialization() {
|
||||||
|
let event = Event {
|
||||||
|
event_type: EventType::MediaImported,
|
||||||
|
timestamp: "2024-01-15T10:00:00Z".to_string(),
|
||||||
|
data: HashMap::from([
|
||||||
|
("path".to_string(), serde_json::json!("/media/test.jpg")),
|
||||||
|
("size".to_string(), serde_json::json!(1024)),
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&event).unwrap();
|
||||||
|
let deserialized: Event = serde_json::from_str(&serialized).unwrap();
|
||||||
|
|
||||||
|
assert!(matches!(deserialized.event_type, EventType::MediaImported));
|
||||||
|
assert_eq!(deserialized.timestamp, "2024-01-15T10:00:00Z");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_http_request_serialization() {
|
||||||
|
let request = HttpRequest {
|
||||||
|
method: "GET".to_string(),
|
||||||
|
url: "https://api.example.com/data".to_string(),
|
||||||
|
headers: HashMap::from([
|
||||||
|
("Authorization".to_string(), "Bearer token".to_string()),
|
||||||
|
("Content-Type".to_string(), "application/json".to_string()),
|
||||||
|
]),
|
||||||
|
body: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&request).unwrap();
|
||||||
|
let deserialized: HttpRequest = serde_json::from_str(&serialized).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(deserialized.method, "GET");
|
||||||
|
assert_eq!(deserialized.url, "https://api.example.com/data");
|
||||||
|
assert!(deserialized.body.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_http_response_serialization() {
|
||||||
|
let response = HttpResponse {
|
||||||
|
status: 200,
|
||||||
|
headers: HashMap::from([("Content-Type".to_string(), "application/json".to_string())]),
|
||||||
|
body: b"{\"success\": true}".to_vec(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&response).unwrap();
|
||||||
|
let deserialized: HttpResponse = serde_json::from_str(&serialized).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(deserialized.status, 200);
|
||||||
|
assert_eq!(deserialized.body, b"{\"success\": true}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_log_message_serialization() {
|
||||||
|
let message = LogMessage {
|
||||||
|
level: LogLevel::Info,
|
||||||
|
target: "plugin::metadata".to_string(),
|
||||||
|
message: "Metadata extraction complete".to_string(),
|
||||||
|
fields: HashMap::from([
|
||||||
|
("file_count".to_string(), "42".to_string()),
|
||||||
|
("duration_ms".to_string(), "150".to_string()),
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&message).unwrap();
|
||||||
|
let deserialized: LogMessage = serde_json::from_str(&serialized).unwrap();
|
||||||
|
|
||||||
|
assert!(matches!(deserialized.level, LogLevel::Info));
|
||||||
|
assert_eq!(deserialized.target, "plugin::metadata");
|
||||||
|
assert_eq!(deserialized.message, "Metadata extraction complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_log_level_variants() {
|
||||||
|
let levels = [LogLevel::Trace, LogLevel::Debug, LogLevel::Info, LogLevel::Warn, LogLevel::Error];
|
||||||
|
|
||||||
|
for level in levels {
|
||||||
|
let serialized = serde_json::to_string(&level).unwrap();
|
||||||
|
let _deserialized: LogLevel = serde_json::from_str(&serialized).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_plugin_error_variants() {
|
||||||
|
let errors: Vec<PluginError> = vec![
|
||||||
|
PluginError::InitializationFailed("WASM load failed".to_string()),
|
||||||
|
PluginError::UnsupportedOperation("Custom search not implemented".to_string()),
|
||||||
|
PluginError::InvalidInput("Invalid file path".to_string()),
|
||||||
|
PluginError::IoError("File not found".to_string()),
|
||||||
|
PluginError::MetadataExtractionFailed("Parse error".to_string()),
|
||||||
|
PluginError::ThumbnailGenerationFailed("Format not supported".to_string()),
|
||||||
|
PluginError::SearchBackendError("Index corrupted".to_string()),
|
||||||
|
PluginError::PermissionDenied("Access denied to /data".to_string()),
|
||||||
|
PluginError::ResourceLimitExceeded("Memory limit exceeded".to_string()),
|
||||||
|
PluginError::Other("Unknown error".to_string()),
|
||||||
|
];
|
||||||
|
|
||||||
|
for error in errors {
|
||||||
|
let serialized = serde_json::to_string(&error).unwrap();
|
||||||
|
let deserialized: PluginError = serde_json::from_str(&serialized).unwrap();
|
||||||
|
assert_eq!(format!("{}", error), format!("{}", deserialized));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_search_index_item_serialization() {
|
||||||
|
let item = SearchIndexItem {
|
||||||
|
id: "media-456".to_string(),
|
||||||
|
title: Some("Summer Vacation".to_string()),
|
||||||
|
description: Some("Photos from summer vacation 2023".to_string()),
|
||||||
|
content: None,
|
||||||
|
tags: vec!["vacation".to_string(), "summer".to_string(), "photos".to_string()],
|
||||||
|
media_type: "image/jpeg".to_string(),
|
||||||
|
metadata: HashMap::from([
|
||||||
|
("camera".to_string(), serde_json::json!("Canon EOS R5")),
|
||||||
|
("location".to_string(), serde_json::json!("Beach")),
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&item).unwrap();
|
||||||
|
let deserialized: SearchIndexItem = serde_json::from_str(&serialized).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(deserialized.id, "media-456");
|
||||||
|
assert_eq!(deserialized.title, Some("Summer Vacation".to_string()));
|
||||||
|
assert_eq!(deserialized.tags.len(), 3);
|
||||||
|
assert_eq!(deserialized.media_type, "image/jpeg");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_health_status_variants() {
|
||||||
|
let healthy = HealthStatus {
|
||||||
|
healthy: true,
|
||||||
|
message: Some("All systems operational".to_string()),
|
||||||
|
metrics: HashMap::from([
|
||||||
|
("items_processed".to_string(), 1000.0),
|
||||||
|
("avg_process_time_ms".to_string(), 45.5),
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
assert!(healthy.healthy);
|
||||||
|
|
||||||
|
let unhealthy = HealthStatus {
|
||||||
|
healthy: false,
|
||||||
|
message: Some("Database connection failed".to_string()),
|
||||||
|
metrics: HashMap::new(),
|
||||||
|
};
|
||||||
|
assert!(!unhealthy.healthy);
|
||||||
|
assert_eq!(unhealthy.message, Some("Database connection failed".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_capabilities_merge() {
|
||||||
|
let mut caps = Capabilities::default();
|
||||||
|
assert!(!caps.network.enabled);
|
||||||
|
|
||||||
|
caps.network.enabled = true;
|
||||||
|
caps.max_memory_bytes = Some(512 * 1024 * 1024);
|
||||||
|
|
||||||
|
assert!(caps.network.enabled);
|
||||||
|
assert_eq!(caps.max_memory_bytes, Some(512 * 1024 * 1024));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_filesystem_capability_paths() {
|
||||||
|
let caps = FilesystemCapability {
|
||||||
|
read: vec![
|
||||||
|
PathBuf::from("/data"),
|
||||||
|
PathBuf::from("/media"),
|
||||||
|
PathBuf::from("/home/user/uploads"),
|
||||||
|
],
|
||||||
|
write: vec![PathBuf::from("/tmp/pinakes")],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(caps.read.len(), 3);
|
||||||
|
assert_eq!(caps.write.len(), 1);
|
||||||
|
assert!(caps.read.contains(&PathBuf::from("/data")));
|
||||||
|
assert!(caps.write.contains(&PathBuf::from("/tmp/pinakes")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_plugin_metadata_structure() {
|
||||||
|
let metadata = PluginMetadata {
|
||||||
|
id: "test-plugin".to_string(),
|
||||||
|
name: "Test Plugin".to_string(),
|
||||||
|
version: "1.0.0".to_string(),
|
||||||
|
author: "Test Author".to_string(),
|
||||||
|
description: "A test plugin for unit testing".to_string(),
|
||||||
|
api_version: "1.0".to_string(),
|
||||||
|
capabilities_required: Capabilities::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(metadata.id, "test-plugin");
|
||||||
|
assert_eq!(metadata.version, "1.0.0");
|
||||||
|
assert_eq!(metadata.api_version, "1.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_network_capability_defaults() {
|
||||||
|
let network = NetworkCapability::default();
|
||||||
|
assert!(!network.enabled);
|
||||||
|
assert!(network.allowed_domains.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_environment_capability_defaults() {
|
||||||
|
let env = EnvironmentCapability::default();
|
||||||
|
assert!(!env.enabled);
|
||||||
|
assert!(env.allowed_vars.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_extracted_metadata_default() {
|
||||||
|
let metadata = ExtractedMetadata::default();
|
||||||
|
|
||||||
|
assert!(metadata.title.is_none());
|
||||||
|
assert!(metadata.description.is_none());
|
||||||
|
assert!(metadata.author.is_none());
|
||||||
|
assert!(metadata.duration_secs.is_none());
|
||||||
|
assert!(metadata.width.is_none());
|
||||||
|
assert!(metadata.height.is_none());
|
||||||
|
assert!(metadata.custom_fields.is_empty());
|
||||||
|
assert!(metadata.tags.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_search_query_structure() {
|
||||||
|
let query = SearchQuery {
|
||||||
|
query_text: "test query".to_string(),
|
||||||
|
filters: HashMap::new(),
|
||||||
|
limit: 10,
|
||||||
|
offset: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(!query.query_text.is_empty());
|
||||||
|
assert_eq!(query.limit, 10);
|
||||||
|
assert_eq!(query.offset, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_thumbnail_options_structure() {
|
||||||
|
let options = ThumbnailOptions {
|
||||||
|
width: 640,
|
||||||
|
height: 480,
|
||||||
|
quality: 75,
|
||||||
|
format: ThumbnailFormat::Png,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(options.width, 640);
|
||||||
|
assert_eq!(options.height, 480);
|
||||||
|
assert_eq!(options.quality, 75);
|
||||||
|
assert!(matches!(options.format, ThumbnailFormat::Png));
|
||||||
|
}
|
||||||
|
|
@ -2,31 +2,6 @@
|
||||||
name = "heif-support"
|
name = "heif-support"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
api_version = "1.0"
|
api_version = "1.0"
|
||||||
author = "Pinakes Team"
|
|
||||||
description = "HEIF/HEIC image format support with metadata extraction and thumbnail generation"
|
|
||||||
homepage = "https://github.com/pinakes/pinakes"
|
|
||||||
license = "MIT OR Apache-2.0"
|
|
||||||
kind = ["media_type", "metadata_extractor", "thumbnail_generator"]
|
|
||||||
|
|
||||||
[plugin.binary]
|
|
||||||
wasm = "heif_support.wasm"
|
|
||||||
|
|
||||||
[capabilities]
|
|
||||||
max_memory_mb = 256
|
|
||||||
max_cpu_time_secs = 30
|
|
||||||
|
|
||||||
[capabilities.filesystem]
|
|
||||||
read = ["/tmp/pinakes-input"]
|
|
||||||
write = ["/tmp/pinakes-output"]
|
|
||||||
|
|
||||||
[config]
|
|
||||||
extract_exif = { type = "boolean", default = true, description = "Extract EXIF metadata from HEIF images" }
|
|
||||||
generate_thumbnails = { type = "boolean", default = true, description = "Generate thumbnails for HEIF images" }
|
|
||||||
thumbnail_quality = { type = "integer", default = 85, description = "JPEG quality for thumbnails (1-100)" }
|
|
||||||
[plugin]
|
|
||||||
name = "heif-support"
|
|
||||||
version = "1.0.0"
|
|
||||||
api_version = "1.0"
|
|
||||||
author = "Pinakes Contributors"
|
author = "Pinakes Contributors"
|
||||||
description = "HEIF/HEIC image format support for Pinakes"
|
description = "HEIF/HEIC image format support for Pinakes"
|
||||||
homepage = "https://github.com/notashelf/pinakes"
|
homepage = "https://github.com/notashelf/pinakes"
|
||||||
|
|
@ -42,21 +17,13 @@ max_memory_mb = 256
|
||||||
max_cpu_time_secs = 30
|
max_cpu_time_secs = 30
|
||||||
|
|
||||||
[capabilities.filesystem]
|
[capabilities.filesystem]
|
||||||
# Read access for processing images (use specific paths in production)
|
|
||||||
read = ["/media"]
|
read = ["/media"]
|
||||||
# Write access for thumbnail generation
|
|
||||||
write = ["/tmp/pinakes"]
|
write = ["/tmp/pinakes"]
|
||||||
|
|
||||||
# Plugin configuration
|
|
||||||
[config]
|
[config]
|
||||||
# Enable EXIF metadata extraction
|
|
||||||
extract_exif = true
|
extract_exif = true
|
||||||
# Enable thumbnail generation
|
|
||||||
generate_thumbnails = true
|
generate_thumbnails = true
|
||||||
# Thumbnail quality (1-100)
|
|
||||||
thumbnail_quality = 85
|
thumbnail_quality = 85
|
||||||
# Thumbnail format (jpeg, png, webp)
|
|
||||||
thumbnail_format = "jpeg"
|
thumbnail_format = "jpeg"
|
||||||
# Maximum image dimensions to process
|
|
||||||
max_width = 8192
|
max_width = 8192
|
||||||
max_height = 8192
|
max_height = 8192
|
||||||
|
|
|
||||||
|
|
@ -2,29 +2,8 @@
|
||||||
name = "markdown-metadata"
|
name = "markdown-metadata"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
api_version = "1.0"
|
api_version = "1.0"
|
||||||
author = "Pinakes Team"
|
|
||||||
description = "Extract metadata from Markdown files with YAML frontmatter"
|
|
||||||
homepage = "https://github.com/pinakes/pinakes"
|
|
||||||
license = "MIT OR Apache-2.0"
|
|
||||||
kind = ["metadata_extractor"]
|
|
||||||
|
|
||||||
[plugin.binary]
|
|
||||||
wasm = "markdown_metadata.wasm"
|
|
||||||
|
|
||||||
[capabilities]
|
|
||||||
# No filesystem or network access needed
|
|
||||||
# Plugin operates on provided content
|
|
||||||
|
|
||||||
[config]
|
|
||||||
extract_tags = { type = "boolean", default = true, description = "Extract tags from YAML frontmatter" }
|
|
||||||
parse_yaml = { type = "boolean", default = true, description = "Parse YAML frontmatter" }
|
|
||||||
max_file_size = { type = "integer", default = 10485760, description = "Maximum file size in bytes (10MB)" }
|
|
||||||
[plugin]
|
|
||||||
name = "markdown-metadata"
|
|
||||||
version = "1.0.0"
|
|
||||||
api_version = "1.0"
|
|
||||||
author = "Pinakes Contributors"
|
author = "Pinakes Contributors"
|
||||||
description = "Enhanced Markdown metadata extractor with frontmatter parsing"
|
description = "Extract metadata from Markdown files with YAML frontmatter"
|
||||||
homepage = "https://github.com/notashelf/pinakes"
|
homepage = "https://github.com/notashelf/pinakes"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
kind = ["metadata_extractor"]
|
kind = ["metadata_extractor"]
|
||||||
|
|
@ -39,13 +18,8 @@ network = false
|
||||||
read = []
|
read = []
|
||||||
write = []
|
write = []
|
||||||
|
|
||||||
# Plugin configuration
|
|
||||||
[config]
|
[config]
|
||||||
# Extract frontmatter tags as media tags
|
|
||||||
extract_tags = true
|
extract_tags = true
|
||||||
# Parse YAML frontmatter
|
|
||||||
parse_yaml = true
|
parse_yaml = true
|
||||||
# Parse TOML frontmatter
|
|
||||||
parse_toml = true
|
parse_toml = true
|
||||||
# Maximum file size to process (in bytes)
|
max_file_size = 10485760
|
||||||
max_file_size = 10485760 # 10MB
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue