From 65f6e7bc00b6d34f0fbee33d9be2343f78d9c21e Mon Sep 17 00:00:00 2001 From: Mathieu Piton <27002047+mpiton@users.noreply.github.com> Date: Fri, 17 Jul 2026 17:04:17 +0200 Subject: [PATCH 1/2] feat(config): declare imgur and flickr credentials in the manifest plugin_api.rs already reads imgur_client_id and flickr_api_key via get_config, but the manifest never declared them, so the host had nothing to validate or persist. Declare both with empty defaults and bump to 1.1.0. Two tests pin the contract: manifest_config checks every key the plugin reads is declared, and the ABI smoke test now asserts a configured client id reaches the Imgur Client-ID header. MAT-134 --- CHANGELOG.md | 22 ++++++++++++++++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- plugin.toml | 4 +++- tests/manifest_config.rs | 14 ++++++++++++++ tests/wasm_smoke.rs | 22 ++++++++++++++++++++-- 6 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 tests/manifest_config.rs diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5810a99 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +# Changelog + +All notable changes to vortex-mod-gallery will be documented here. +Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). + +## [1.1.0] - 2026-07-17 + +### Added +- Declared `imgur_client_id` and `flickr_api_key` in the `[config]` manifest + section so the host can validate and persist them; the plugin already reads + both through `get_config` (MAT-134 R-01). +- Manifest test pinning every config key read by `plugin_api.rs` to a + `[config]` declaration, and an ABI smoke assertion proving a configured + Imgur client id travels from `get_config` into the `Client-ID` + Authorization header. + +## [1.0.0] - 2026-04-15 + +### Added +- Initial release: Imgur, Flickr, and generic HTML gallery extraction with + `can_handle`, `supports_playlist`, `extract_links`, `extract_generic`, and + `is_http_url` exports. diff --git a/Cargo.lock b/Cargo.lock index 6f3fa5b..1898fcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2238,7 +2238,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vortex-mod-gallery" -version = "1.0.0" +version = "1.1.0" dependencies = [ "extism", "extism-pdk", diff --git a/Cargo.toml b/Cargo.toml index 918b348..6853a78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vortex-mod-gallery" -version = "1.0.0" +version = "1.1.0" edition = "2021" description = "Gallery WASM plugin for Vortex — Imgur, Reddit, Flickr image galleries" license = "GPL-3.0" diff --git a/plugin.toml b/plugin.toml index f0919ed..20fedb8 100644 --- a/plugin.toml +++ b/plugin.toml @@ -1,6 +1,6 @@ [plugin] name = "vortex-mod-gallery" -version = "1.0.0" +version = "1.1.0" category = "crawler" author = "vortex-community" description = "Image galleries: Imgur, Reddit, Flickr, generic scraping" @@ -15,3 +15,5 @@ http = true [config] min_resolution = { type = "string", default = "800x600", description = "Filter by minimum image size (WxH)" } auto_name = { type = "boolean", default = true, description = "Auto-generate filenames from provider metadata" } +imgur_client_id = { type = "string", default = "", description = "Imgur API client ID (sent as 'Client-ID' authorization header)" } +flickr_api_key = { type = "string", default = "", description = "Flickr REST API key" } diff --git a/tests/manifest_config.rs b/tests/manifest_config.rs new file mode 100644 index 0000000..ca95aaf --- /dev/null +++ b/tests/manifest_config.rs @@ -0,0 +1,14 @@ +//! Guards the `[config]` contract: keys the plugin reads via `get_config` +//! must be declared in `plugin.toml` so the host can validate and expose them. + +const MANIFEST: &str = include_str!("../plugin.toml"); + +#[test] +fn manifest_declares_every_config_key_read_by_the_plugin() { + for key in ["min_resolution", "auto_name", "imgur_client_id", "flickr_api_key"] { + assert!( + MANIFEST.contains(key), + "plugin.toml [config] is missing '{key}', which plugin_api.rs reads via get_config" + ); + } +} diff --git a/tests/wasm_smoke.rs b/tests/wasm_smoke.rs index dac6bc1..edfc8f8 100644 --- a/tests/wasm_smoke.rs +++ b/tests/wasm_smoke.rs @@ -11,6 +11,7 @@ const GENERIC_URL: &str = "https://example.test/gallery/page.html"; const GENERIC_IMAGE_URL: &str = "https://example.test/gallery/sample.jpg"; const IMGUR_BODY: &str = r#"{"data":[{"id":"img1","title":"sample","link":"https://i.imgur.com/img1.jpg","width":1920,"height":1080}],"status":200,"success":true}"#; const GENERIC_BODY: &str = r#""#; +const SMOKE_CLIENT_ID: &str = "SMOKE_CLIENT"; fn wasm_path() -> PathBuf { let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(WASM_REL_PATH); @@ -38,6 +39,14 @@ fn stub_http_request() -> Function { .as_str() .ok_or_else(|| extism::Error::msg("http_request URL is missing"))?; let body = if url.contains("api.imgur.com") { + // The configured client id must travel from `get_config` + // all the way into the Imgur Authorization header. + let auth = request["headers"]["Authorization"].as_str().unwrap_or(""); + if auth != format!("Client-ID {SMOKE_CLIENT_ID}") { + return Err(extism::Error::msg(format!( + "imgur request carries wrong Authorization header: {auth:?}" + ))); + } IMGUR_BODY } else if url == GENERIC_URL { GENERIC_BODY @@ -60,8 +69,17 @@ fn stub_get_config() -> Function { [PTR], [PTR], UserData::<()>::default(), - |plugin, _inputs, outputs, _user_data: UserData<()>| { - let handle = plugin.memory_new("")?; + |plugin, inputs, outputs, _user_data: UserData<()>| { + let input = inputs[0] + .i64() + .ok_or_else(|| extism::Error::msg("get_config expected i64 input"))?; + let key: String = plugin.memory_get_val(&Val::I64(input))?; + let value = if key == "imgur_client_id" { + SMOKE_CLIENT_ID + } else { + "" + }; + let handle = plugin.memory_new(value)?; outputs[0] = Val::I64(handle.offset() as i64); Ok(()) }, From adbf1bcf6c8360ac4cebfb3c06b618f2bde474b8 Mon Sep 17 00:00:00 2001 From: Mathieu Piton <27002047+mpiton@users.noreply.github.com> Date: Fri, 17 Jul 2026 17:15:05 +0200 Subject: [PATCH 2/2] fix(test): scope manifest key check to the [config] section A key mentioned in a description string elsewhere in plugin.toml could satisfy the old full-file contains check. Match the key as the left-hand side of a declaration line after the [config] header, and let rustfmt expand the key array so cargo fmt --check passes. --- tests/manifest_config.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/manifest_config.rs b/tests/manifest_config.rs index ca95aaf..217be0c 100644 --- a/tests/manifest_config.rs +++ b/tests/manifest_config.rs @@ -5,9 +5,21 @@ const MANIFEST: &str = include_str!("../plugin.toml"); #[test] fn manifest_declares_every_config_key_read_by_the_plugin() { - for key in ["min_resolution", "auto_name", "imgur_client_id", "flickr_api_key"] { + let (_, config_section) = MANIFEST + .split_once("[config]") + .expect("plugin.toml must have a [config] section"); + for key in [ + "min_resolution", + "auto_name", + "imgur_client_id", + "flickr_api_key", + ] { + let declared = config_section.lines().any(|line| { + line.split_once('=') + .is_some_and(|(name, _)| name.trim() == key) + }); assert!( - MANIFEST.contains(key), + declared, "plugin.toml [config] is missing '{key}', which plugin_api.rs reads via get_config" ); }