Something went wrong. Try again.
A lexicon-driven AppView for ATProto. happyview.dev
backfill firehose jetstream atproto appview oauth lexicon
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304// tests/plugin_executor.rs
use happyview::db::DatabaseBackend;use happyview::lexicon::LexiconRegistry;use happyview::plugin::{ ExecutionError, LoadedPlugin, PluginExecutor, PluginInfo, PluginRegistry, PluginSource, WasmRuntime,};use std::collections::HashMap;use std::sync::Arc;
type Secrets = HashMap<String, String>;
async fn create_test_executor() -> (PluginExecutor, Arc<PluginRegistry>) { // Create in-memory database sqlx::any::install_default_drivers(); let db = sqlx::AnyPool::connect("sqlite::memory:") .await .expect("Failed to create test database");
let runtime = Arc::new(WasmRuntime::new().expect("Failed to create runtime")); let registry = Arc::new(PluginRegistry::new()); let lexicons = Arc::new(LexiconRegistry::new()); let http_client = reqwest::Client::new();
let executor = PluginExecutor::new( runtime, registry.clone(), db, DatabaseBackend::Sqlite, http_client, lexicons, );
(executor, registry)}
fn load_test_plugin() -> LoadedPlugin { let wasm_bytes = std::fs::read( "tests/fixtures/test_plugin/target/wasm32-unknown-unknown/release/test_plugin.wasm", ) .expect( "Test plugin not built. Run: cd tests/fixtures/test_plugin && cargo build --target wasm32-unknown-unknown --release", );
LoadedPlugin { info: PluginInfo { id: "test".into(), name: "Test Plugin".into(), version: "1.0.0".into(), api_version: "1".into(), icon_url: None, required_secrets: vec![], auth_type: "oauth2".into(), config_schema: None, }, source: PluginSource::File { path: "tests/fixtures/test_plugin".into(), }, wasm_bytes, manifest: None, }}
#[tokio::test]async fn test_plugin_info() { let (executor, registry) = create_test_executor().await; let plugin = load_test_plugin(); registry.register(plugin).await;
let mut instance = executor .instantiate( "test", "user:did:plc:test", Secrets::new(), serde_json::Value::Null, ) .await .expect("Failed to instantiate");
let info = instance .call_plugin_info() .await .expect("Failed to get info");
assert_eq!(info.id, "test"); assert_eq!(info.name, "Test Plugin"); assert_eq!(info.version, "1.0.0");}
#[tokio::test]async fn test_get_authorize_url() { let (executor, registry) = create_test_executor().await; let plugin = load_test_plugin(); registry.register(plugin).await;
let mut instance = executor .instantiate("test", "state:123", Secrets::new(), serde_json::Value::Null) .await .expect("Failed to instantiate");
let url = instance .call_get_authorize_url( "state123", "https://app.example/callback", &serde_json::Value::Null, ) .await .expect("Failed to get URL");
assert!(url.starts_with("https://"));}
#[tokio::test]async fn test_handle_callback() { let (executor, registry) = create_test_executor().await; let plugin = load_test_plugin(); registry.register(plugin).await;
let mut instance = executor .instantiate( "test", "user:did:plc:test", Secrets::new(), serde_json::Value::Null, ) .await .expect("Failed to instantiate");
let mut params = HashMap::new(); params.insert("code".to_string(), "code123".to_string()); params.insert("state".to_string(), "state123".to_string());
let tokens = instance .call_handle_callback(¶ms, &serde_json::Value::Null) .await .expect("Failed to handle callback");
assert_eq!(tokens.access_token, "test-token"); assert_eq!(tokens.token_type, "Bearer");}
#[tokio::test]async fn test_refresh_tokens() { let (executor, registry) = create_test_executor().await; let plugin = load_test_plugin(); registry.register(plugin).await;
let mut instance = executor .instantiate( "test", "user:did:plc:test", Secrets::new(), serde_json::Value::Null, ) .await .expect("Failed to instantiate");
let tokens = instance .call_refresh_tokens("old-refresh-token", &serde_json::Value::Null) .await .expect("Failed to refresh tokens");
assert_eq!(tokens.access_token, "refreshed-token");}
#[tokio::test]async fn test_get_profile() { let (executor, registry) = create_test_executor().await; let plugin = load_test_plugin(); registry.register(plugin).await;
let mut instance = executor .instantiate( "test", "user:did:plc:test", Secrets::new(), serde_json::Value::Null, ) .await .expect("Failed to instantiate");
let profile = instance .call_get_profile("test-token", &serde_json::Value::Null) .await .expect("Failed to get profile");
assert_eq!(profile.account_id, "12345"); assert_eq!(profile.display_name, Some("Test User".into()));}
#[tokio::test]async fn test_sync_account() { let (executor, registry) = create_test_executor().await; let plugin = load_test_plugin(); registry.register(plugin).await;
let mut instance = executor .instantiate( "test", "user:did:plc:test", Secrets::new(), serde_json::Value::Null, ) .await .expect("Failed to instantiate");
let records = instance .call_sync_account("test-token", &serde_json::Value::Null) .await .expect("Failed to sync account");
assert!(records.is_empty()); // Test plugin returns empty array}
#[tokio::test]async fn test_plugin_not_found() { let (executor, _registry) = create_test_executor().await;
let result = executor .instantiate( "nonexistent", "scope", Secrets::new(), serde_json::Value::Null, ) .await;
assert!(matches!(result, Err(ExecutionError::PluginNotFound(_))));}
/// Test that host_get_secret returns a JSON envelope that plugins can parse./// Uses the real Steam plugin WASM which calls get_secret("API_KEY") in get_profile./// This test verifies the fix for the JSON envelope mismatch where the host/// returned raw bytes but the plugin expected {"ok": "value"}.#[tokio::test]async fn test_steam_get_secret_json_envelope() { let wasm_path = "tests/fixtures/steam.wasm"; if !std::path::Path::new(wasm_path).exists() { eprintln!("Skipping test: {} not found", wasm_path); return; }
let (executor, registry) = create_test_executor().await; let wasm_bytes = std::fs::read(wasm_path).expect("Failed to read steam.wasm");
let plugin = LoadedPlugin { info: PluginInfo { id: "steam".into(), name: "Steam".into(), version: "1.1.0".into(), api_version: "1".into(), icon_url: None, required_secrets: vec!["API_KEY".into()], auth_type: "openid".into(), config_schema: None, }, source: PluginSource::File { path: wasm_path.into(), }, wasm_bytes, manifest: None, };
registry.register(plugin).await;
let mut secrets = Secrets::new(); secrets.insert("API_KEY".to_string(), "test-fake-key".to_string());
let mut instance = executor .instantiate( "steam", "user:did:plc:test", secrets, serde_json::Value::Null, ) .await .expect("Failed to instantiate Steam plugin");
// get_profile calls host_get_secret("API_KEY") internally. // It will then try to call the Steam API with the fake key, which will fail // with an HTTP error — but the important thing is it does NOT fail with // MISSING_SECRET, which would mean host_get_secret's JSON envelope is broken. let result = instance .call_get_profile("76561198024344834", &serde_json::Value::Null) .await;
match result { Ok(_) => {} // Unlikely with a fake key, but fine Err(ExecutionError::PluginError { code, .. }) => { // HTTP_ERROR = Steam API rejected our fake key (expected) // INVALID_RESPONSE = Steam returned unexpected format (also fine) // MISSING_SECRET = host_get_secret envelope is broken (BAD!) assert_ne!( code, "MISSING_SECRET", "host_get_secret JSON envelope is broken — plugin couldn't parse the secret" ); } Err(e) => { panic!("Unexpected error type: {:?}", e); } }}