test: cover SpawnPod profile config building
This commit is contained in:
parent
6e73c0f700
commit
b94891ed1b
|
|
@ -503,46 +503,64 @@ impl SpawnPodTool {
|
||||||
scope_allow: &[ScopeRule],
|
scope_allow: &[ScopeRule],
|
||||||
selector: SpawnProfileSelector,
|
selector: SpawnProfileSelector,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let mut config = match selector {
|
build_spawn_config_json_for_profile(
|
||||||
SpawnProfileSelector::Inherit => manifest_to_reusable_config(&self.spawner_manifest),
|
&self.spawner_manifest,
|
||||||
SpawnProfileSelector::Default | SpawnProfileSelector::Registry(_) => {
|
&self.available_profiles,
|
||||||
let registry = self.available_profiles.registry.as_ref().ok_or_else(|| {
|
&self.spawner_pwd,
|
||||||
format!(
|
name,
|
||||||
"profile discovery failed for SpawnPod: {}{}",
|
instruction_override,
|
||||||
self.available_profiles
|
scope_allow,
|
||||||
.diagnostic()
|
selector,
|
||||||
.if_empty("unknown error"),
|
)
|
||||||
self.available_profiles.error_suffix()
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let profile_selector = match selector {
|
|
||||||
SpawnProfileSelector::Default => ProfileSelector::Default,
|
|
||||||
SpawnProfileSelector::Registry(selector) => selector,
|
|
||||||
SpawnProfileSelector::Inherit => unreachable!(),
|
|
||||||
};
|
|
||||||
let resolved = ProfileResolver::new()
|
|
||||||
.with_workspace_base(&self.spawner_pwd)
|
|
||||||
.resolve_from_registry(
|
|
||||||
&profile_selector,
|
|
||||||
registry,
|
|
||||||
ProfileResolveOptions::with_pod_name(name),
|
|
||||||
)
|
|
||||||
.map_err(|e| profile_error_with_available(e, &self.available_profiles))?;
|
|
||||||
manifest_to_reusable_config(&resolved.manifest)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
config.pod.name = Some(name.to_string());
|
|
||||||
config.scope = ScopeConfig {
|
|
||||||
allow: scope_allow.to_vec(),
|
|
||||||
deny: Vec::new(),
|
|
||||||
};
|
|
||||||
if let Some(instruction) = instruction_override {
|
|
||||||
config.worker.instruction = Some(instruction.to_string());
|
|
||||||
}
|
|
||||||
serde_json::to_string(&config).map_err(|e| format!("spawn config serialisation: {e}"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_spawn_config_json_for_profile(
|
||||||
|
spawner_manifest: &PodManifest,
|
||||||
|
available_profiles: &AvailableProfiles,
|
||||||
|
spawner_pwd: &Path,
|
||||||
|
name: &str,
|
||||||
|
instruction_override: Option<&str>,
|
||||||
|
scope_allow: &[ScopeRule],
|
||||||
|
selector: SpawnProfileSelector,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let mut config = match selector {
|
||||||
|
SpawnProfileSelector::Inherit => manifest_to_reusable_config(spawner_manifest),
|
||||||
|
SpawnProfileSelector::Default | SpawnProfileSelector::Registry(_) => {
|
||||||
|
let registry = available_profiles.registry.as_ref().ok_or_else(|| {
|
||||||
|
format!(
|
||||||
|
"profile discovery failed for SpawnPod: {}{}",
|
||||||
|
available_profiles.diagnostic().if_empty("unknown error"),
|
||||||
|
available_profiles.error_suffix()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let profile_selector = match selector {
|
||||||
|
SpawnProfileSelector::Default => ProfileSelector::Default,
|
||||||
|
SpawnProfileSelector::Registry(selector) => selector,
|
||||||
|
SpawnProfileSelector::Inherit => unreachable!(),
|
||||||
|
};
|
||||||
|
let resolved = ProfileResolver::new()
|
||||||
|
.with_workspace_base(spawner_pwd)
|
||||||
|
.resolve_from_registry(
|
||||||
|
&profile_selector,
|
||||||
|
registry,
|
||||||
|
ProfileResolveOptions::with_pod_name(name),
|
||||||
|
)
|
||||||
|
.map_err(|e| profile_error_with_available(e, available_profiles))?;
|
||||||
|
manifest_to_reusable_config(&resolved.manifest)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
config.pod.name = Some(name.to_string());
|
||||||
|
config.scope = ScopeConfig {
|
||||||
|
allow: scope_allow.to_vec(),
|
||||||
|
deny: Vec::new(),
|
||||||
|
};
|
||||||
|
if let Some(instruction) = instruction_override {
|
||||||
|
config.worker.instruction = Some(instruction.to_string());
|
||||||
|
}
|
||||||
|
serde_json::to_string(&config).map_err(|e| format!("spawn config serialisation: {e}"))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn build_spawn_config_json(
|
fn build_spawn_config_json(
|
||||||
name: &str,
|
name: &str,
|
||||||
|
|
@ -782,6 +800,122 @@ pub fn spawn_pod_tool(
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use manifest::{AuthRef, ModelManifest, PodManifest, SchemeKind};
|
use manifest::{AuthRef, ModelManifest, PodManifest, SchemeKind};
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
fn abs_rule(path: &Path, permission: Permission) -> ScopeRule {
|
||||||
|
ScopeRule {
|
||||||
|
target: path.to_path_buf(),
|
||||||
|
permission,
|
||||||
|
recursive: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parent_manifest(root: &Path, deny: Option<&Path>) -> PodManifest {
|
||||||
|
PodManifestConfig {
|
||||||
|
pod: PodMetaConfig {
|
||||||
|
name: Some("parent".into()),
|
||||||
|
prompt_pack: None,
|
||||||
|
},
|
||||||
|
model: ModelManifest {
|
||||||
|
scheme: Some(SchemeKind::Anthropic),
|
||||||
|
model_id: Some("parent-model".into()),
|
||||||
|
auth: Some(AuthRef::None),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
worker: WorkerManifestConfig {
|
||||||
|
instruction: Some("$insomnia/parent".into()),
|
||||||
|
language: Some("Parentish".into()),
|
||||||
|
max_tokens: Some(1234),
|
||||||
|
stop_sequences: Some(vec!["STOP".into()]),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
scope: ScopeConfig {
|
||||||
|
allow: vec![abs_rule(root, Permission::Write)],
|
||||||
|
deny: deny
|
||||||
|
.map(|path| vec![abs_rule(path, Permission::Read)])
|
||||||
|
.unwrap_or_default(),
|
||||||
|
},
|
||||||
|
session: Some(SessionConfigPartial {
|
||||||
|
record_event_trace: Some(true),
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.try_into()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_project_profile_registry(
|
||||||
|
project: &Path,
|
||||||
|
default: Option<&str>,
|
||||||
|
profiles: &[(&str, &str, &str)],
|
||||||
|
) -> AvailableProfiles {
|
||||||
|
let insomnia = project.join(".insomnia");
|
||||||
|
let profile_dir = insomnia.join("profiles");
|
||||||
|
std::fs::create_dir_all(&profile_dir).unwrap();
|
||||||
|
let mut registry_toml = String::new();
|
||||||
|
if let Some(default) = default {
|
||||||
|
registry_toml.push_str(&format!("default = \"{default}\"\n"));
|
||||||
|
}
|
||||||
|
registry_toml.push_str("[profile]\n");
|
||||||
|
for (name, file, body) in profiles {
|
||||||
|
std::fs::write(profile_dir.join(file), body).unwrap();
|
||||||
|
registry_toml.push_str(&format!("{name} = \"profiles/{file}\"\n"));
|
||||||
|
}
|
||||||
|
let registry_path = insomnia.join("profiles.toml");
|
||||||
|
std::fs::write(®istry_path, registry_toml).unwrap();
|
||||||
|
AvailableProfiles {
|
||||||
|
registry: Some(
|
||||||
|
ProfileDiscovery::with_sources(None, None, Some(registry_path))
|
||||||
|
.discover()
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
diagnostic: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn child_config_from_profile(
|
||||||
|
spawner_manifest: &PodManifest,
|
||||||
|
available: &AvailableProfiles,
|
||||||
|
cwd: &Path,
|
||||||
|
name: &str,
|
||||||
|
instruction_override: Option<&str>,
|
||||||
|
scope: &[ScopeRule],
|
||||||
|
selector: SpawnProfileSelector,
|
||||||
|
) -> PodManifestConfig {
|
||||||
|
let json = build_spawn_config_json_for_profile(
|
||||||
|
spawner_manifest,
|
||||||
|
available,
|
||||||
|
cwd,
|
||||||
|
name,
|
||||||
|
instruction_override,
|
||||||
|
scope,
|
||||||
|
selector,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
serde_json::from_str(&json).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
const CODER_PROFILE: &str = r#"
|
||||||
|
local profile = require("insomnia.profile")
|
||||||
|
local scope = require("insomnia.scope")
|
||||||
|
return profile {
|
||||||
|
slug = "coder",
|
||||||
|
model = { scheme = "anthropic", model_id = "coder-model" },
|
||||||
|
worker = { instruction = "$insomnia/coder", language = "Coderish", max_tokens = 2222 },
|
||||||
|
scope = scope.workspace_write(),
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
const REVIEWER_PROFILE: &str = r#"
|
||||||
|
local profile = require("insomnia.profile")
|
||||||
|
local scope = require("insomnia.scope")
|
||||||
|
return profile {
|
||||||
|
slug = "reviewer",
|
||||||
|
model = { scheme = "anthropic", model_id = "reviewer-model" },
|
||||||
|
worker = { instruction = "$insomnia/reviewer", language = "Reviewerish", max_tokens = 3333 },
|
||||||
|
scope = scope.workspace_write(),
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn spawn_config_inherits_inline_spawner_model() {
|
fn spawn_config_inherits_inline_spawner_model() {
|
||||||
|
|
@ -868,6 +1002,278 @@ mod tests {
|
||||||
assert!(parsed.session.is_none());
|
assert!(parsed.session.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn omitted_profile_resolves_effective_registry_default() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let project = tmp.path().join("project");
|
||||||
|
let delegated = tmp.path().join("delegated");
|
||||||
|
std::fs::create_dir_all(&project).unwrap();
|
||||||
|
std::fs::create_dir_all(&delegated).unwrap();
|
||||||
|
let available = write_project_profile_registry(
|
||||||
|
&project,
|
||||||
|
Some("reviewer"),
|
||||||
|
&[
|
||||||
|
("coder", "coder.lua", CODER_PROFILE),
|
||||||
|
("reviewer", "reviewer.lua", REVIEWER_PROFILE),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let parent = parent_manifest(&project, None);
|
||||||
|
let scope = vec![abs_rule(&delegated, Permission::Read)];
|
||||||
|
|
||||||
|
let config = child_config_from_profile(
|
||||||
|
&parent,
|
||||||
|
&available,
|
||||||
|
&project,
|
||||||
|
"child-default",
|
||||||
|
None,
|
||||||
|
&scope,
|
||||||
|
SpawnProfileSelector::Default,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(config.pod.name.as_deref(), Some("child-default"));
|
||||||
|
assert_eq!(config.model.model_id.as_deref(), Some("reviewer-model"));
|
||||||
|
assert_eq!(
|
||||||
|
config.worker.instruction.as_deref(),
|
||||||
|
Some("$insomnia/reviewer")
|
||||||
|
);
|
||||||
|
assert_eq!(config.worker.language.as_deref(), Some("Reviewerish"));
|
||||||
|
assert_eq!(config.scope.allow, scope);
|
||||||
|
assert!(config.scope.deny.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn source_qualified_profile_role_config_reaches_spawn_config() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let project = tmp.path().join("project");
|
||||||
|
let delegated = tmp.path().join("delegated");
|
||||||
|
std::fs::create_dir_all(&project).unwrap();
|
||||||
|
std::fs::create_dir_all(&delegated).unwrap();
|
||||||
|
let available = write_project_profile_registry(
|
||||||
|
&project,
|
||||||
|
Some("coder"),
|
||||||
|
&[
|
||||||
|
("coder", "coder.lua", CODER_PROFILE),
|
||||||
|
("reviewer", "reviewer.lua", REVIEWER_PROFILE),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let parent = parent_manifest(&project, None);
|
||||||
|
let scope = vec![abs_rule(&delegated, Permission::Write)];
|
||||||
|
|
||||||
|
let config = child_config_from_profile(
|
||||||
|
&parent,
|
||||||
|
&available,
|
||||||
|
&project,
|
||||||
|
"review-child",
|
||||||
|
None,
|
||||||
|
&scope,
|
||||||
|
SpawnProfileSelector::Registry(ProfileSelector::source_named(
|
||||||
|
ProfileRegistrySource::Project,
|
||||||
|
"reviewer",
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(config.pod.name.as_deref(), Some("review-child"));
|
||||||
|
assert_eq!(config.model.model_id.as_deref(), Some("reviewer-model"));
|
||||||
|
assert_eq!(
|
||||||
|
config.worker.instruction.as_deref(),
|
||||||
|
Some("$insomnia/reviewer")
|
||||||
|
);
|
||||||
|
assert_eq!(config.worker.language.as_deref(), Some("Reviewerish"));
|
||||||
|
assert_eq!(config.worker.max_tokens, Some(3333));
|
||||||
|
assert_eq!(config.scope.allow, scope);
|
||||||
|
assert!(config.scope.deny.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inherit_copies_reusable_parent_fields_and_replaces_runtime_authority() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let parent_root = tmp.path().join("parent-root");
|
||||||
|
let parent_deny = parent_root.join("secret");
|
||||||
|
let delegated = tmp.path().join("delegated");
|
||||||
|
std::fs::create_dir_all(&parent_deny).unwrap();
|
||||||
|
std::fs::create_dir_all(&delegated).unwrap();
|
||||||
|
let parent = parent_manifest(&parent_root, Some(&parent_deny));
|
||||||
|
let scope = vec![abs_rule(&delegated, Permission::Read)];
|
||||||
|
let available = AvailableProfiles {
|
||||||
|
registry: None,
|
||||||
|
diagnostic: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = child_config_from_profile(
|
||||||
|
&parent,
|
||||||
|
&available,
|
||||||
|
tmp.path(),
|
||||||
|
"inherited-child",
|
||||||
|
None,
|
||||||
|
&scope,
|
||||||
|
SpawnProfileSelector::Inherit,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(config.pod.name.as_deref(), Some("inherited-child"));
|
||||||
|
assert_eq!(config.model.model_id.as_deref(), Some("parent-model"));
|
||||||
|
assert_eq!(
|
||||||
|
config.worker.instruction.as_deref(),
|
||||||
|
Some("$insomnia/parent")
|
||||||
|
);
|
||||||
|
assert_eq!(config.worker.language.as_deref(), Some("Parentish"));
|
||||||
|
assert_eq!(config.worker.max_tokens, Some(1234));
|
||||||
|
assert_eq!(
|
||||||
|
config.worker.stop_sequences.as_deref(),
|
||||||
|
Some(&["STOP".to_string()][..])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
config.session.as_ref().and_then(|s| s.record_event_trace),
|
||||||
|
Some(true)
|
||||||
|
);
|
||||||
|
assert_eq!(config.scope.allow, scope);
|
||||||
|
assert!(config.scope.deny.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn instruction_override_changes_only_worker_instruction() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let project = tmp.path().join("project");
|
||||||
|
let delegated = tmp.path().join("delegated");
|
||||||
|
std::fs::create_dir_all(&project).unwrap();
|
||||||
|
std::fs::create_dir_all(&delegated).unwrap();
|
||||||
|
let available = write_project_profile_registry(
|
||||||
|
&project,
|
||||||
|
Some("reviewer"),
|
||||||
|
&[("reviewer", "reviewer.lua", REVIEWER_PROFILE)],
|
||||||
|
);
|
||||||
|
let parent = parent_manifest(&project, None);
|
||||||
|
let scope = vec![abs_rule(&delegated, Permission::Write)];
|
||||||
|
|
||||||
|
let config = child_config_from_profile(
|
||||||
|
&parent,
|
||||||
|
&available,
|
||||||
|
&project,
|
||||||
|
"override-child",
|
||||||
|
Some("$user/custom-reviewer"),
|
||||||
|
&scope,
|
||||||
|
SpawnProfileSelector::Default,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
config.worker.instruction.as_deref(),
|
||||||
|
Some("$user/custom-reviewer")
|
||||||
|
);
|
||||||
|
assert_eq!(config.model.model_id.as_deref(), Some("reviewer-model"));
|
||||||
|
assert_eq!(config.worker.language.as_deref(), Some("Reviewerish"));
|
||||||
|
assert_eq!(config.worker.max_tokens, Some(3333));
|
||||||
|
assert_eq!(config.scope.allow, scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn profile_and_inherited_scope_are_replaced_by_delegated_scope() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let project = tmp.path().join("project");
|
||||||
|
let delegated = tmp.path().join("delegated");
|
||||||
|
let parent_root = tmp.path().join("parent-root");
|
||||||
|
std::fs::create_dir_all(&project).unwrap();
|
||||||
|
std::fs::create_dir_all(&delegated).unwrap();
|
||||||
|
std::fs::create_dir_all(&parent_root).unwrap();
|
||||||
|
let available = write_project_profile_registry(
|
||||||
|
&project,
|
||||||
|
Some("reviewer"),
|
||||||
|
&[("reviewer", "reviewer.lua", REVIEWER_PROFILE)],
|
||||||
|
);
|
||||||
|
let parent = parent_manifest(&parent_root, Some(&parent_root.join("deny")));
|
||||||
|
let scope = vec![abs_rule(&delegated, Permission::Read)];
|
||||||
|
|
||||||
|
let profile_config = child_config_from_profile(
|
||||||
|
&parent,
|
||||||
|
&available,
|
||||||
|
&project,
|
||||||
|
"profile-child",
|
||||||
|
None,
|
||||||
|
&scope,
|
||||||
|
SpawnProfileSelector::Default,
|
||||||
|
);
|
||||||
|
let inherit_config = child_config_from_profile(
|
||||||
|
&parent,
|
||||||
|
&available,
|
||||||
|
&project,
|
||||||
|
"inherit-child",
|
||||||
|
None,
|
||||||
|
&scope,
|
||||||
|
SpawnProfileSelector::Inherit,
|
||||||
|
);
|
||||||
|
|
||||||
|
for config in [profile_config, inherit_config] {
|
||||||
|
assert_eq!(config.scope.allow, scope);
|
||||||
|
assert!(config.scope.deny.is_empty());
|
||||||
|
assert!(!config.scope.allow.iter().any(|rule| rule.target == project));
|
||||||
|
assert!(
|
||||||
|
!config
|
||||||
|
.scope
|
||||||
|
.allow
|
||||||
|
.iter()
|
||||||
|
.any(|rule| rule.target == parent_root)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_ambiguous_and_no_default_diagnostics_include_available_selectors() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let project = tmp.path().join("project");
|
||||||
|
std::fs::create_dir_all(&project).unwrap();
|
||||||
|
let available = write_project_profile_registry(
|
||||||
|
&project,
|
||||||
|
None,
|
||||||
|
&[("coder", "coder.lua", CODER_PROFILE)],
|
||||||
|
);
|
||||||
|
let parent = parent_manifest(&project, None);
|
||||||
|
let scope = vec![abs_rule(&project, Permission::Read)];
|
||||||
|
|
||||||
|
let invalid = parse_spawn_profile_selector(Some("./reviewer.lua"))
|
||||||
|
.map_err(|msg| format!("{msg}{}", available.error_suffix()))
|
||||||
|
.unwrap_err();
|
||||||
|
assert!(invalid.contains("Use `default`, `inherit`"));
|
||||||
|
assert!(invalid.contains("`project:coder`"));
|
||||||
|
|
||||||
|
let no_default = build_spawn_config_json_for_profile(
|
||||||
|
&parent,
|
||||||
|
&available,
|
||||||
|
&project,
|
||||||
|
"child",
|
||||||
|
None,
|
||||||
|
&scope,
|
||||||
|
SpawnProfileSelector::Default,
|
||||||
|
)
|
||||||
|
.unwrap_err();
|
||||||
|
assert!(no_default.contains("no default profile"), "{no_default}");
|
||||||
|
assert!(no_default.contains("Use `default`, `inherit`"));
|
||||||
|
assert!(no_default.contains("`project:coder`"));
|
||||||
|
|
||||||
|
let user_config = tmp.path().join("user-profiles.toml");
|
||||||
|
std::fs::write(&user_config, "[profile]\ncoder = \"user-coder.lua\"\n").unwrap();
|
||||||
|
let project_config = project.join(".insomnia/profiles.toml");
|
||||||
|
let ambiguous = AvailableProfiles {
|
||||||
|
registry: Some(
|
||||||
|
ProfileDiscovery::with_sources(None, Some(user_config), Some(project_config))
|
||||||
|
.discover()
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
diagnostic: None,
|
||||||
|
};
|
||||||
|
let ambiguous_error = build_spawn_config_json_for_profile(
|
||||||
|
&parent,
|
||||||
|
&ambiguous,
|
||||||
|
&project,
|
||||||
|
"child",
|
||||||
|
None,
|
||||||
|
&scope,
|
||||||
|
SpawnProfileSelector::Registry(ProfileSelector::named("coder")),
|
||||||
|
)
|
||||||
|
.unwrap_err();
|
||||||
|
assert!(ambiguous_error.contains("ambiguous"), "{ambiguous_error}");
|
||||||
|
assert!(ambiguous_error.contains("user:coder"));
|
||||||
|
assert!(ambiguous_error.contains("project:coder"));
|
||||||
|
assert!(ambiguous_error.contains("Use `default`, `inherit`"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn spawn_profile_selector_rejects_path_like_values() {
|
fn spawn_profile_selector_rejects_path_like_values() {
|
||||||
for raw in [
|
for raw in [
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user