Update: 将子项目从 submodule 转为完整内容
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用 - 添加所有子项目的完整源代码 - 保留原始 .git 为 .git.bak 备份
This commit is contained in:
@@ -0,0 +1,602 @@
|
||||
//! Integration tests for contribution resolution (test-plan CR-1..CR-10).
|
||||
//!
|
||||
//! These are black-box tests that exercise the public resolver APIs with
|
||||
//! realistic extension manifests and file system fixtures.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use nomifun_extension::types::*;
|
||||
use nomifun_extension::{resolve_all_contributions, resolve_extension_contributions, resolve_i18n_for_all};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn make_loaded_extension(name: &str, dir: &str, contributes: ExtContributes) -> LoadedExtension {
|
||||
LoadedExtension {
|
||||
manifest: ExtensionManifest {
|
||||
name: name.to_owned(),
|
||||
version: "1.0.0".to_owned(),
|
||||
display_name: None,
|
||||
description: None,
|
||||
author: None,
|
||||
license: None,
|
||||
homepage: None,
|
||||
icon: None,
|
||||
engine: None,
|
||||
api_version: None,
|
||||
dependencies: HashMap::new(),
|
||||
entry_point: None,
|
||||
permissions: None,
|
||||
contributes: Some(contributes),
|
||||
lifecycle: None,
|
||||
i18n: None,
|
||||
},
|
||||
directory: dir.to_owned(),
|
||||
source: ExtensionSource::Local,
|
||||
state: ExtensionState {
|
||||
name: name.to_owned(),
|
||||
version: "1.0.0".to_owned(),
|
||||
enabled: true,
|
||||
installed_at: None,
|
||||
last_activated_at: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn make_loaded_extension_with_i18n(name: &str, dir: &str, i18n: I18nConfig) -> LoadedExtension {
|
||||
LoadedExtension {
|
||||
manifest: ExtensionManifest {
|
||||
name: name.to_owned(),
|
||||
version: "1.0.0".to_owned(),
|
||||
display_name: None,
|
||||
description: None,
|
||||
author: None,
|
||||
license: None,
|
||||
homepage: None,
|
||||
icon: None,
|
||||
engine: None,
|
||||
api_version: None,
|
||||
dependencies: HashMap::new(),
|
||||
entry_point: None,
|
||||
permissions: None,
|
||||
contributes: None,
|
||||
lifecycle: None,
|
||||
i18n: Some(i18n),
|
||||
},
|
||||
directory: dir.to_owned(),
|
||||
source: ExtensionSource::Local,
|
||||
state: ExtensionState {
|
||||
name: name.to_owned(),
|
||||
version: "1.0.0".to_owned(),
|
||||
enabled: true,
|
||||
installed_at: None,
|
||||
last_activated_at: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CR-1: ACP Adapter resolution
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn cr1_acp_adapter_resolved_with_env_and_avatar() {
|
||||
unsafe { std::env::set_var("_CR1_API_KEY", "test-key-123") };
|
||||
|
||||
let mut env = HashMap::new();
|
||||
env.insert("API_KEY".into(), "${_CR1_API_KEY}".into());
|
||||
|
||||
let contributes = ExtContributes {
|
||||
acp_adapters: vec![ExtAcpAdapter {
|
||||
id: "claude-adapter".into(),
|
||||
name: "Claude Adapter".into(),
|
||||
description: Some("Claude via ACP".into()),
|
||||
cli_command: Some("claude".into()),
|
||||
default_cli_path: None,
|
||||
acp_args: vec!["--dangerously-skip-permissions".into()],
|
||||
env,
|
||||
avatar: Some("icons/claude.png".into()),
|
||||
auth_required: Some(true),
|
||||
supports_streaming: Some(true),
|
||||
connection_type: Some("stdio".into()),
|
||||
endpoint: None,
|
||||
models: vec!["claude-sonnet-4-20250514".into()],
|
||||
yolo_mode: Some(serde_json::json!({
|
||||
"type": "session"
|
||||
})),
|
||||
health_check: None,
|
||||
api_key_fields: vec![],
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let ext = make_loaded_extension("claude-ext", "/ext/claude-ext", contributes);
|
||||
let result = resolve_extension_contributions(&ext);
|
||||
|
||||
assert_eq!(result.acp_adapters.len(), 1);
|
||||
let adapter = &result.acp_adapters[0];
|
||||
assert_eq!(adapter.extension_name, "claude-ext");
|
||||
assert_eq!(adapter.id, "claude-adapter");
|
||||
assert_eq!(adapter.cli_command.as_deref(), Some("claude"));
|
||||
assert_eq!(adapter.env["API_KEY"], "test-key-123");
|
||||
assert!(adapter.avatar.as_ref().unwrap().contains("icons/claude.png"));
|
||||
|
||||
unsafe { std::env::remove_var("_CR1_API_KEY") };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CR-2: MCP Server resolution
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn cr2_mcp_server_resolved_as_opaque_config() {
|
||||
let contributes = ExtContributes {
|
||||
mcp_servers: vec![ExtMcpServer {
|
||||
id: "sqlite-mcp".into(),
|
||||
name: "SQLite MCP".into(),
|
||||
description: Some("SQLite via MCP".into()),
|
||||
config: serde_json::json!({
|
||||
"command": "npx",
|
||||
"args": ["-y", "@anthropic/mcp-server-sqlite"],
|
||||
"transport": "stdio"
|
||||
}),
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let ext = make_loaded_extension("sqlite-ext", "/ext/sqlite-ext", contributes);
|
||||
let result = resolve_extension_contributions(&ext);
|
||||
|
||||
assert_eq!(result.mcp_servers.len(), 1);
|
||||
let server = &result.mcp_servers[0];
|
||||
assert_eq!(server.extension_name, "sqlite-ext");
|
||||
assert_eq!(server.id, "sqlite-mcp");
|
||||
assert_eq!(server.config["command"], "npx");
|
||||
assert_eq!(server.config["transport"], "stdio");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CR-3: Assistant resolution with @file: reference
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn cr3_assistant_file_reference_resolved() {
|
||||
let dir = std::env::temp_dir().join("cr3_assistant_resolve");
|
||||
let prompts = dir.join("prompts");
|
||||
std::fs::create_dir_all(&prompts).unwrap();
|
||||
std::fs::write(prompts.join("system.md"), "You are a helpful coding assistant.").unwrap();
|
||||
|
||||
let contributes = ExtContributes {
|
||||
assistants: vec![ExtAssistant {
|
||||
id: "code-helper".into(),
|
||||
name: "Code Helper".into(),
|
||||
description: Some("AI coding assistant".into()),
|
||||
system_prompt: Some("@file:prompts/system.md".into()),
|
||||
icon: Some("icons/code.png".into()),
|
||||
context: None,
|
||||
preset_agent_type: Some("gemini".into()),
|
||||
enabled_skills: vec!["code-review".into()],
|
||||
prompts: vec!["Review this patch".into()],
|
||||
models: vec!["gemini-2.0-flash".into()],
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let ext = make_loaded_extension("helper-ext", &dir.to_string_lossy(), contributes);
|
||||
let result = resolve_extension_contributions(&ext);
|
||||
|
||||
assert_eq!(result.assistants.len(), 1);
|
||||
let assistant = &result.assistants[0];
|
||||
assert_eq!(assistant.extension_name, "helper-ext");
|
||||
assert_eq!(
|
||||
assistant.system_prompt.as_deref(),
|
||||
Some("You are a helpful coding assistant.")
|
||||
);
|
||||
assert_eq!(assistant.preset_agent_type.as_deref(), Some("gemini"));
|
||||
assert_eq!(assistant.enabled_skills, vec!["code-review"]);
|
||||
assert_eq!(assistant.prompts, vec!["Review this patch"]);
|
||||
assert_eq!(assistant.models, vec!["gemini-2.0-flash"]);
|
||||
|
||||
std::fs::remove_dir_all(&dir).unwrap();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CR-4: Agent resolution with @file: reference
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn cr4_agent_file_reference_resolved() {
|
||||
let dir = std::env::temp_dir().join("cr4_agent_resolve");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
std::fs::write(dir.join("agent_ctx.md"), "Agent context loaded from file.").unwrap();
|
||||
|
||||
let contributes = ExtContributes {
|
||||
agents: vec![ExtAgent {
|
||||
id: "auto-agent".into(),
|
||||
name: "Auto Agent".into(),
|
||||
description: Some("Autonomous coding agent".into()),
|
||||
agent_type: Some("claude".into()),
|
||||
context: Some("@file:agent_ctx.md".into()),
|
||||
icon: None,
|
||||
enabled_skills: vec!["ship-it".into()],
|
||||
prompts: vec!["Fix the build".into()],
|
||||
models: vec!["claude-sonnet-4".into()],
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let ext = make_loaded_extension("agent-ext", &dir.to_string_lossy(), contributes);
|
||||
let result = resolve_extension_contributions(&ext);
|
||||
|
||||
assert_eq!(result.agents.len(), 1);
|
||||
let agent = &result.agents[0];
|
||||
assert_eq!(agent.extension_name, "agent-ext");
|
||||
assert_eq!(agent.agent_type.as_deref(), Some("claude"));
|
||||
assert_eq!(agent.context.as_deref(), Some("Agent context loaded from file."));
|
||||
assert_eq!(agent.enabled_skills, vec!["ship-it"]);
|
||||
assert_eq!(agent.prompts, vec!["Fix the build"]);
|
||||
assert_eq!(agent.models, vec!["claude-sonnet-4"]);
|
||||
|
||||
std::fs::remove_dir_all(&dir).unwrap();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CR-5: Skill resolution
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn cr5_skill_resolved_with_path() {
|
||||
let dir = std::env::temp_dir().join("cr5_skill_resolved_with_path");
|
||||
std::fs::create_dir_all(dir.join("skills")).unwrap();
|
||||
std::fs::write(dir.join("skills/code-review.md"), "# review").unwrap();
|
||||
|
||||
let contributes = ExtContributes {
|
||||
skills: vec![ExtSkill {
|
||||
name: "code-review".into(),
|
||||
description: Some("Review code for quality".into()),
|
||||
path: Some("skills/code-review.md".into()),
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let ext = make_loaded_extension("skill-ext", &dir.to_string_lossy(), contributes);
|
||||
let result = resolve_extension_contributions(&ext);
|
||||
|
||||
assert_eq!(result.skills.len(), 1);
|
||||
let skill = &result.skills[0];
|
||||
assert_eq!(skill.extension_name, "skill-ext");
|
||||
assert_eq!(skill.name, "code-review");
|
||||
assert!(skill.path.as_ref().unwrap().contains("skills/code-review"));
|
||||
|
||||
std::fs::remove_dir_all(&dir).unwrap();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CR-6: Theme resolution (CSS content loaded)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn cr6_theme_css_content_loaded() {
|
||||
let dir = std::env::temp_dir().join("cr6_theme_resolve");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
std::fs::write(dir.join("dark.css"), ":root { --bg: #1a1a2e; --text: #eaeaea; }").unwrap();
|
||||
|
||||
let contributes = ExtContributes {
|
||||
themes: vec![ExtTheme {
|
||||
id: "dark-theme".into(),
|
||||
name: "Dark Theme".into(),
|
||||
description: Some("A dark color scheme".into()),
|
||||
css_file: "dark.css".into(),
|
||||
cover_image: Some("images/dark-preview.png".into()),
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let ext = make_loaded_extension("theme-ext", &dir.to_string_lossy(), contributes);
|
||||
let result = resolve_extension_contributions(&ext);
|
||||
|
||||
assert_eq!(result.themes.len(), 1);
|
||||
let theme = &result.themes[0];
|
||||
assert_eq!(theme.extension_name, "theme-ext");
|
||||
assert_eq!(theme.css_content, ":root { --bg: #1a1a2e; --text: #eaeaea; }");
|
||||
assert!(theme.cover_image.as_ref().unwrap().contains("images/dark-preview.png"));
|
||||
|
||||
std::fs::remove_dir_all(&dir).unwrap();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CR-7: WebUI route namespace validation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn cr7_webui_valid_namespace_resolves() {
|
||||
let contributes = ExtContributes {
|
||||
webui: vec![ExtWebui {
|
||||
id: "dashboard".into(),
|
||||
directory: "dist".into(),
|
||||
routes: vec![ExtWebuiRoute {
|
||||
path: "/my-ext/dashboard".into(),
|
||||
method: "GET".into(),
|
||||
handler: "handler.js".into(),
|
||||
}],
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let ext = make_loaded_extension("my-ext", "/ext/my-ext", contributes);
|
||||
let result = resolve_extension_contributions(&ext);
|
||||
|
||||
assert_eq!(result.webui.len(), 1);
|
||||
assert_eq!(result.webui[0].extension_name, "my-ext");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cr7_webui_wrong_namespace_rejected() {
|
||||
let contributes = ExtContributes {
|
||||
webui: vec![ExtWebui {
|
||||
id: "bad-route".into(),
|
||||
directory: "dist".into(),
|
||||
routes: vec![ExtWebuiRoute {
|
||||
path: "/other-ext/api".into(),
|
||||
method: "GET".into(),
|
||||
handler: "handler.js".into(),
|
||||
}],
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let ext = make_loaded_extension("my-ext", "/ext/my-ext", contributes);
|
||||
let result = resolve_extension_contributions(&ext);
|
||||
|
||||
// Invalid route should be filtered out
|
||||
assert!(result.webui.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cr7_webui_reserved_prefix_rejected() {
|
||||
let contributes = ExtContributes {
|
||||
webui: vec![ExtWebui {
|
||||
id: "reserved".into(),
|
||||
directory: "dist".into(),
|
||||
routes: vec![ExtWebuiRoute {
|
||||
path: "/api/data".into(),
|
||||
method: "GET".into(),
|
||||
handler: "handler.js".into(),
|
||||
}],
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Extension name "api" would match namespace but /api/ is reserved
|
||||
let ext = make_loaded_extension("api", "/ext/api", contributes);
|
||||
let result = resolve_extension_contributions(&ext);
|
||||
assert!(result.webui.is_empty());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CR-8: Settings tab with position
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn cr8_settings_tab_position_preserved() {
|
||||
let contributes = ExtContributes {
|
||||
settings_tabs: vec![ExtSettingsTab {
|
||||
id: "ext-settings".into(),
|
||||
label: "My Extension".into(),
|
||||
icon: Some("icons/gear.svg".into()),
|
||||
url: "settings/index.html".into(),
|
||||
position: Some(SettingsTabPosition {
|
||||
relative_to: "general".into(),
|
||||
placement: "after".into(),
|
||||
}),
|
||||
order: 80,
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let ext = make_loaded_extension("my-ext", "/ext/my-ext", contributes);
|
||||
let result = resolve_extension_contributions(&ext);
|
||||
|
||||
assert_eq!(result.settings_tabs.len(), 1);
|
||||
let tab = &result.settings_tabs[0];
|
||||
assert_eq!(tab.extension_name, "my-ext");
|
||||
assert_eq!(tab.id, "ext-my-ext-ext-settings");
|
||||
assert_eq!(tab.url, "/api/extensions/my-ext/assets/settings/index.html");
|
||||
assert_eq!(
|
||||
tab.icon.as_deref(),
|
||||
Some("/api/extensions/my-ext/assets/icons/gear.svg")
|
||||
);
|
||||
assert_eq!(tab.order, 80);
|
||||
let pos = tab.position.as_ref().unwrap();
|
||||
assert_eq!(pos.relative_to, "general");
|
||||
assert_eq!(pos.placement, "after");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CR-9: Model provider resolution
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn cr9_model_provider_resolved() {
|
||||
let contributes = ExtContributes {
|
||||
model_providers: vec![ExtModelProvider {
|
||||
id: "custom-provider".into(),
|
||||
name: "Custom LLM".into(),
|
||||
description: Some("Custom model provider".into()),
|
||||
protocol: Some("openai".into()),
|
||||
base_url: Some("https://api.custom.com/v1".into()),
|
||||
models: vec!["custom-model-1".into(), "custom-model-2".into()],
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let ext = make_loaded_extension("provider-ext", "/ext/provider-ext", contributes);
|
||||
let result = resolve_extension_contributions(&ext);
|
||||
|
||||
assert_eq!(result.model_providers.len(), 1);
|
||||
let provider = &result.model_providers[0];
|
||||
assert_eq!(provider.extension_name, "provider-ext");
|
||||
assert_eq!(provider.id, "custom-provider");
|
||||
assert_eq!(provider.protocol.as_deref(), Some("openai"));
|
||||
assert_eq!(provider.models.len(), 2);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CR-10: i18n data loading
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn cr10_i18n_data_loaded_for_supported_locale() {
|
||||
let dir = std::env::temp_dir().join("cr10_i18n_resolve");
|
||||
let i18n_dir = dir.join("i18n");
|
||||
std::fs::create_dir_all(&i18n_dir).unwrap();
|
||||
std::fs::write(
|
||||
i18n_dir.join("zh-CN.json"),
|
||||
r#"{"greeting": "你好", "settings.title": "设置"}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let ext = make_loaded_extension_with_i18n(
|
||||
"i18n-ext",
|
||||
&dir.to_string_lossy(),
|
||||
I18nConfig {
|
||||
locales: vec!["en".into(), "zh-CN".into()],
|
||||
directory: "i18n".into(),
|
||||
},
|
||||
);
|
||||
|
||||
let result = resolve_i18n_for_all(&[ext], "zh-CN");
|
||||
assert_eq!(result.len(), 1);
|
||||
let messages = &result["i18n-ext"];
|
||||
assert_eq!(messages["greeting"], "你好");
|
||||
assert_eq!(messages["settings.title"], "设置");
|
||||
|
||||
std::fs::remove_dir_all(&dir).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cr10_i18n_unsupported_locale_returns_empty() {
|
||||
let dir = std::env::temp_dir().join("cr10_i18n_unsupported");
|
||||
let i18n_dir = dir.join("i18n");
|
||||
std::fs::create_dir_all(&i18n_dir).unwrap();
|
||||
std::fs::write(i18n_dir.join("en.json"), r#"{"key": "value"}"#).unwrap();
|
||||
|
||||
let ext = make_loaded_extension_with_i18n(
|
||||
"en-only-ext",
|
||||
&dir.to_string_lossy(),
|
||||
I18nConfig {
|
||||
locales: vec!["en".into()],
|
||||
directory: "i18n".into(),
|
||||
},
|
||||
);
|
||||
|
||||
let result = resolve_i18n_for_all(&[ext], "fr");
|
||||
assert!(result.is_empty());
|
||||
|
||||
std::fs::remove_dir_all(&dir).unwrap();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Cross-cutting: resolve_all_contributions merges and filters
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn resolve_all_merges_contributions_from_multiple_extensions() {
|
||||
let dir = std::env::temp_dir().join("resolve_all_merges_contributions_from_multiple_extensions");
|
||||
std::fs::create_dir_all(dir.join("a/skills")).unwrap();
|
||||
std::fs::create_dir_all(dir.join("b/skills")).unwrap();
|
||||
std::fs::write(dir.join("a/skills/skill-a.md"), "# a").unwrap();
|
||||
std::fs::write(dir.join("b/skills/skill-b.md"), "# b").unwrap();
|
||||
|
||||
let ext_a = make_loaded_extension(
|
||||
"ext-a",
|
||||
&dir.join("a").to_string_lossy(),
|
||||
ExtContributes {
|
||||
skills: vec![ExtSkill {
|
||||
name: "skill-a".into(),
|
||||
description: None,
|
||||
path: Some("skills/skill-a.md".into()),
|
||||
}],
|
||||
model_providers: vec![ExtModelProvider {
|
||||
id: "mp-a".into(),
|
||||
name: "Provider A".into(),
|
||||
description: None,
|
||||
protocol: None,
|
||||
base_url: None,
|
||||
models: vec![],
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
let ext_b = make_loaded_extension(
|
||||
"ext-b",
|
||||
&dir.join("b").to_string_lossy(),
|
||||
ExtContributes {
|
||||
skills: vec![ExtSkill {
|
||||
name: "skill-b".into(),
|
||||
description: None,
|
||||
path: Some("skills/skill-b.md".into()),
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let result = resolve_all_contributions(&[ext_a, ext_b]);
|
||||
assert_eq!(result.skills.len(), 2);
|
||||
assert_eq!(result.model_providers.len(), 1);
|
||||
|
||||
std::fs::remove_dir_all(dir).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_all_skips_disabled_extensions() {
|
||||
let mut ext = make_loaded_extension(
|
||||
"disabled-ext",
|
||||
"/ext/disabled",
|
||||
ExtContributes {
|
||||
skills: vec![ExtSkill {
|
||||
name: "hidden".into(),
|
||||
description: None,
|
||||
path: None,
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
ext.state.enabled = false;
|
||||
|
||||
let result = resolve_all_contributions(&[ext]);
|
||||
assert!(result.skills.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn channel_plugin_resolved_with_entry_point() {
|
||||
let contributes = ExtContributes {
|
||||
channel_plugins: vec![ExtChannelPlugin {
|
||||
id: "slack".into(),
|
||||
name: "Slack".into(),
|
||||
description: Some("Slack channel plugin".into()),
|
||||
platform: Some("slack".into()),
|
||||
entry_point: Some("plugins/slack.js".into()),
|
||||
icon: Some("icons/slack.png".into()),
|
||||
credential_fields: vec![serde_json::json!({ "key": "token" })],
|
||||
config_fields: vec![serde_json::json!({ "key": "channel" })],
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let ext = make_loaded_extension("channel-ext", "/ext/channel-ext", contributes);
|
||||
let result = resolve_extension_contributions(&ext);
|
||||
|
||||
assert_eq!(result.channel_plugins.len(), 1);
|
||||
assert_eq!(result.channel_plugins[0].extension_name, "channel-ext");
|
||||
assert!(
|
||||
result.channel_plugins[0]
|
||||
.entry_point
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.contains("plugins/slack.js")
|
||||
);
|
||||
assert_eq!(result.channel_plugins[0].credential_fields.len(), 1);
|
||||
assert_eq!(result.channel_plugins[0].config_fields.len(), 1);
|
||||
}
|
||||
Reference in New Issue
Block a user