use std::collections::BTreeSet;
use supercode::configfile::{resolve, HarnessConfig, ResolveOptions};
use supercode::{Config, ModuleActivation, ModuleId, ToolRegistry};
fn resolve_preset(name: &str) -> supercode::configfile::Resolved {
let top = format!("extends = \"{name}\"\n");
resolve(&top, None, &ResolveOptions::default())
.unwrap_or_else(|e| panic!("preset `{name}` failed to resolve: {e}"))
}
fn assert_exact_activation(hc: &HarnessConfig, expected: &[ModuleId]) {
let act = ModuleActivation::from_harness(hc);
let expected_set: BTreeSet<ModuleId> = expected.iter().copied().collect();
for &m in ModuleId::ALL {
let want = expected_set.contains(&m);
let got = act.is_active(m);
assert_eq!(
got, want,
"module `{m}`: expected active={want}, got active={got}"
);
}
assert_eq!(
act.len(),
expected_set.len(),
"activation set size mismatch (dedup check)"
);
}
#[test]
fn pi_core_exact_activation_set() {
let r = resolve_preset("pi-core");
assert_exact_activation(
&r.harness,
&[
ModuleId::Trust,
ModuleId::SessionTree,
ModuleId::SessionShare,
ModuleId::Server,
ModuleId::Plugins,
ModuleId::Tui,
],
);
}
#[test]
fn cc_parity_exact_activation_set() {
let r = resolve_preset("cc-parity");
assert_exact_activation(
&r.harness,
&[
ModuleId::ToolsSearch,
ModuleId::ToolsWeb,
ModuleId::ToolsQuestion,
ModuleId::Todos,
ModuleId::PlanMode,
ModuleId::Subagents,
ModuleId::ToolsBackground,
ModuleId::PermissionsApprovals,
ModuleId::PermissionsRules,
ModuleId::PermissionsProtectedPaths,
ModuleId::Trust,
ModuleId::McpClient,
ModuleId::DeferredTools,
ModuleId::Hooks,
ModuleId::Memory,
ModuleId::Checkpoint,
ModuleId::SessionTree,
ModuleId::ModelCatalog,
ModuleId::Tui,
],
);
}
#[test]
fn cx_parity_exact_activation_set() {
let r = resolve_preset("cx-parity");
assert_exact_activation(
&r.harness,
&[
ModuleId::ToolsPersistentShell,
ModuleId::ToolsApplyPatch,
ModuleId::Todos,
ModuleId::ToolsWeb,
ModuleId::ToolsBackground,
ModuleId::Subagents,
ModuleId::DeferredTools,
ModuleId::StructuredOutput,
ModuleId::PermissionsApprovals,
ModuleId::PermissionsRules,
ModuleId::PermissionsProtectedPaths,
ModuleId::Trust,
ModuleId::McpClient,
ModuleId::McpServer,
ModuleId::Hooks,
ModuleId::ModelCatalog,
ModuleId::Tui,
],
);
}
#[test]
fn oc_parity_exact_activation_set() {
let r = resolve_preset("oc-parity");
assert_exact_activation(
&r.harness,
&[
ModuleId::ToolsSearch,
ModuleId::Todos,
ModuleId::ToolsWeb,
ModuleId::Subagents,
ModuleId::ToolsApplyPatch,
ModuleId::PermissionsApprovals,
ModuleId::PermissionsRules,
ModuleId::Trust,
ModuleId::McpClient,
ModuleId::Plugins,
ModuleId::Lsp,
ModuleId::Formatters,
ModuleId::Checkpoint,
ModuleId::SessionShare,
ModuleId::Server,
ModuleId::ModelCatalog,
ModuleId::Tui,
],
);
}
#[test]
fn token_saver_exact_activation_set() {
let r = resolve_preset("token-saver");
assert_exact_activation(
&r.harness,
&[
ModuleId::Trust,
ModuleId::SessionTree,
ModuleId::SessionShare,
ModuleId::Server,
ModuleId::Plugins,
ModuleId::Tui,
ModuleId::Reduction,
ModuleId::DeferredTools,
ModuleId::Cache,
ModuleId::ModelCatalog,
],
);
}
#[test]
fn supercode_default_exact_activation_set() {
let r = resolve_preset("supercode-default");
assert_exact_activation(
&r.harness,
&[
ModuleId::Tui,
ModuleId::ToolsSearch,
ModuleId::ToolsApplyPatch,
ModuleId::ToolsPersistentShell,
ModuleId::Todos,
ModuleId::Notify,
],
);
}
fn tool_names(r: &ToolRegistry) -> Vec<String> {
r.iter().map(|t| t.name().to_string()).collect()
}
#[test]
fn module_registry_off_by_default_matches_with_builtins_exactly() {
let config = Config::default();
assert!(!config.module_registry, "module_registry must default off");
let from_config = ToolRegistry::from_config(&config);
let builtins = ToolRegistry::with_builtins();
assert_eq!(tool_names(&from_config), tool_names(&builtins));
}
#[test]
fn module_registry_off_matches_with_builtins_even_with_a_nonempty_activation_set() {
let r = resolve_preset("cc-parity");
let mut config = r.config;
assert!(!config.module_registry);
config.module_activation = ModuleActivation::from_harness(&r.harness);
let from_config = ToolRegistry::from_config(&config);
let builtins = ToolRegistry::with_builtins();
assert_eq!(tool_names(&from_config), tool_names(&builtins));
}
#[test]
fn supercode_default_with_module_registry_on_still_matches_with_builtins() {
let top = "extends = \"supercode-default\"\n[experimental]\nmodule_registry = true\n";
let r = resolve(top, None, &ResolveOptions::default()).expect("resolves");
assert!(r.config.module_registry);
let from_config = ToolRegistry::from_config(&r.config);
let builtins = ToolRegistry::with_builtins();
assert_eq!(tool_names(&from_config), tool_names(&builtins));
}
struct ToolModuleCase {
module_toml: &'static str,
tool_name: &'static str,
}
const TOOL_MODULE_CASES: &[ToolModuleCase] = &[
ToolModuleCase {
module_toml: "[capabilities.todos]\nenabled = true\n",
tool_name: "update_plan",
},
ToolModuleCase {
module_toml: "[capabilities.tools_apply_patch]\nenabled = true\n",
tool_name: "apply_patch",
},
ToolModuleCase {
module_toml: "[capabilities.tools_persistent_shell]\nenabled = true\n",
tool_name: "shell",
},
];
fn resolve_module_registry_on(extra: &str) -> supercode::Config {
let top = format!("[experimental]\nmodule_registry = true\n{extra}");
resolve(&top, None, &ResolveOptions::default())
.expect("resolves")
.config
}
#[test]
fn disabled_module_contributes_no_tool_enabled_module_does() {
for case in TOOL_MODULE_CASES {
let off_config = resolve_module_registry_on("");
let off_registry = ToolRegistry::from_config(&off_config);
assert!(
off_registry.get(case.tool_name).is_none(),
"`{}` must be ABSENT from the registry when its module is off",
case.tool_name
);
let on_config = resolve_module_registry_on(case.module_toml);
let on_registry = ToolRegistry::from_config(&on_config);
assert!(
on_registry.get(case.tool_name).is_some(),
"`{}` must be PRESENT in the registry when its module is on",
case.tool_name
);
}
}
#[test]
fn tools_search_off_means_none_of_list_dir_glob_search_are_registered() {
let config = resolve_module_registry_on("");
let registry = ToolRegistry::from_config(&config);
for name in ["list_dir", "glob", "search"] {
assert!(
registry.get(name).is_none(),
"`{name}` must be absent when tools_search is off"
);
}
}
#[test]
fn tools_search_on_registers_all_three_by_default() {
let config = resolve_module_registry_on("[capabilities.tools_search]\nenabled = true\n");
let registry = ToolRegistry::from_config(&config);
for name in ["list_dir", "glob", "search"] {
assert!(
registry.get(name).is_some(),
"`{name}` must be present when tools_search is on with default sub-flags"
);
}
}
#[test]
fn tools_search_sub_flags_narrow_individually() {
let config = resolve_module_registry_on(
"[capabilities.tools_search]\nenabled = true\nlist_dir = false\nglob = false\n",
);
let registry = ToolRegistry::from_config(&config);
assert!(registry.get("list_dir").is_none());
assert!(registry.get("glob").is_none());
assert!(
registry.get("search").is_some(),
"content_search sub-flag stayed at its true default"
);
}
#[test]
fn core_tools_enabled_list_gates_the_default_four() {
let config = resolve_module_registry_on("[core.tools]\nenabled = [\"bash\"]\n");
let registry = ToolRegistry::from_config(&config);
assert!(registry.get("bash").is_some());
assert!(registry.get("read_file").is_none());
assert!(registry.get("write_file").is_none());
assert!(registry.get("edit_file").is_none());
}
fn system_prompt_for(top_toml: &str) -> String {
let config = resolve(top_toml, None, &ResolveOptions::default())
.expect("resolves")
.config;
let provider: Box<dyn supercode::Provider> = Box::new(supercode::OpenAiProvider::new(
"http://127.0.0.1:0",
"test-key",
Default::default(),
));
let agent = supercode::Agent::with_provider(config, provider);
agent.history()[0].content.clone().unwrap_or_default()
}
#[test]
fn skills_off_means_no_skills_section() {
let top = "[experimental]\nmodule_registry = true\n[core.prompts]\nfoo = \"bar {args}\"\n";
let prompt = system_prompt_for(top);
assert!(
!prompt.contains("# Skills"),
"skills off must not add a section"
);
}
#[test]
fn skills_on_with_read_file_active_adds_the_section() {
let top = "[experimental]\nmodule_registry = true\n\
[core.skills]\nenabled = true\n\
[core.prompts]\nfoo = \"bar {args}\"\n";
let prompt = system_prompt_for(top);
assert!(prompt.contains("# Skills"), "expected a skills section");
assert!(prompt.contains("foo"));
}
#[test]
fn skills_on_with_only_bash_active_still_adds_the_section_d7_warn_not_error() {
let top = "[experimental]\nmodule_registry = true\n\
[core.tools]\nenabled = [\"bash\"]\n\
[core.skills]\nenabled = true\n\
[core.prompts]\nfoo = \"bar {args}\"\n";
let resolved =
resolve(top, None, &ResolveOptions::default()).expect("resolves (warn, not error)");
assert!(resolved.warnings.iter().any(|w| w.contains("D-7")));
let provider: Box<dyn supercode::Provider> = Box::new(supercode::OpenAiProvider::new(
"http://127.0.0.1:0",
"test-key",
Default::default(),
));
let agent = supercode::Agent::with_provider(resolved.config, provider);
assert!(agent.history()[0]
.content
.as_deref()
.unwrap_or("")
.contains("# Skills"));
}
#[test]
fn skills_on_with_no_read_pathway_at_all_is_a_hard_resolve_error() {
let top = "[experimental]\nmodule_registry = true\n\
[core.tools]\nenabled = []\n\
[core.skills]\nenabled = true\n";
let err = resolve(top, None, &ResolveOptions::default())
.expect_err("no read_file/bash at all must hard-error (D-7)");
assert!(format!("{err}").contains("core.skills"));
}
#[test]
fn skills_gated_by_module_registry_flag_even_when_enabled_on_config() {
let config = supercode::Config::builder()
.skills_enabled(true)
.core_tools_enabled(vec!["read_file".to_string()])
.prompt("foo", "bar {args}")
.build();
assert!(!config.module_registry);
let provider: Box<dyn supercode::Provider> = Box::new(supercode::OpenAiProvider::new(
"http://127.0.0.1:0",
"test-key",
Default::default(),
));
let agent = supercode::Agent::with_provider(config, provider);
assert!(
!agent.history()[0]
.content
.as_deref()
.unwrap_or("")
.contains("# Skills"),
"module_registry off must suppress the skills section regardless of skills_enabled"
);
}
#[test]
fn c1_fires_when_apply_patch_and_edit_write_coexist_without_bits() {
let top = "[core.tools]\nenabled = [\"read_file\", \"bash\", \"edit_file\", \"write_file\"]\n\
[capabilities.tools_apply_patch]\nenabled = true\n";
let r = resolve(top, None, &ResolveOptions::default()).expect("resolves");
assert!(
r.warnings.iter().any(|w| w.contains("C1")),
"expected a C1 warning: {:?}",
r.warnings
);
}
#[test]
fn c1_does_not_fire_with_per_model_bits_and_model_catalog_on() {
let top = "[core.tools]\nenabled = [\"read_file\", \"bash\", \"edit_file\", \"write_file\"]\n\
[capabilities.tools_apply_patch]\nenabled = true\nper_model = true\n\
[capabilities.model_catalog]\nenabled = true\n";
let r = resolve(top, None, &ResolveOptions::default()).expect("resolves");
assert!(
!r.warnings.iter().any(|w| w.contains("C1")),
"expected no C1 warning with per_model bits + model_catalog on: {:?}",
r.warnings
);
}
#[test]
fn c1_does_not_fire_when_apply_patch_is_the_only_write_path() {
let top = "[core.tools]\nenabled = [\"bash\"]\n\
[capabilities.tools_apply_patch]\nenabled = true\nper_model = true\n";
let r = resolve(top, None, &ResolveOptions::default()).expect("resolves");
assert!(!r.warnings.iter().any(|w| w.contains("C1")));
}
#[test]
fn supercode_default_carries_the_c1_warning_faithfully() {
let r = resolve_preset("supercode-default");
assert!(r.warnings.iter().any(|w| w.contains("C1")));
}
#[test]
fn case_mismatched_sandbox_key_is_rejected_with_a_warning_not_honored() {
let top = "[capabilities.permissions]\nenabled = true\nSandbox = \"danger_full_access\"\n";
let r = resolve(top, None, &ResolveOptions::default()).expect("resolves (warns, not errors)");
assert!(
r.warnings.iter().any(|w| w.contains("SECURITY")),
"expected a SECURITY case-sensitivity warning: {:?}",
r.warnings
);
assert!(!supercode::modules::ModuleId::PermissionsSandbox.is_active(&r.harness));
}
#[test]
fn case_mismatched_tier_key_inside_sandbox_table_is_rejected() {
let top = "[capabilities.permissions]\nenabled = true\n\
[capabilities.permissions.sandbox]\nenabled = true\nTier = \"read_only\"\n";
let r = resolve(top, None, &ResolveOptions::default()).expect("resolves (warns, not errors)");
assert!(
r.warnings.iter().any(|w| w.contains("SECURITY")),
"expected a SECURITY case-sensitivity warning: {:?}",
r.warnings
);
}
#[test]
fn correctly_cased_permissions_keys_never_trip_the_security_warning() {
let top = "[capabilities.permissions]\nenabled = true\napproval = \"untrusted\"\nsandbox = \"read_only\"\nauto_approved_tools = [\"read_file\"]\n\
[capabilities.permissions.rules]\nenabled = true\ndeny = []\nask = []\nallow = [\"*\"]\n\
[capabilities.permissions.protected_paths]\nenabled = true\npaths = [\".git/**\"]\n";
let r = resolve(top, None, &ResolveOptions::default()).expect("resolves");
assert!(
!r.warnings.iter().any(|w| w.contains("SECURITY")),
"correctly-cased keys must never trip the SECURITY warning: {:?}",
r.warnings
);
}
#[test]
fn every_reserved_preset_never_trips_the_security_warning() {
for name in supercode::presets::RESERVED_PRESET_NAMES {
let r = resolve_preset(name);
assert!(
!r.warnings.iter().any(|w| w.contains("SECURITY")),
"preset `{name}` unexpectedly tripped the SECURITY warning: {:?}",
r.warnings
);
}
}