use std::fs;
use std::path::Path;
use warble_cli::compile_project_to_ir;
const PREPARED_WIDGETS: &str = r#"{
"context_version": 2,
"parseable": true,
"metrics": [
{
"name": "id",
"owner": "widgets",
"declared": false,
"additivity": null
}
],
"dimensions": [],
"models": [
{
"name": "widgets",
"has_timestamp": false,
"columns": [
"id"
]
}
],
"lineage": {
"nodes": [
{
"id": "model:widgets",
"kind": "model"
}
],
"edges": []
},
"lineage_diagnostics": [],
"analysis": {
"impact": {
"model:widgets": {
"downstream": [],
"severity": {
"rank": 0,
"name": "none"
}
}
},
"consumers": {
"queries": 0,
"dashboards": 0
}
}
}"#;
fn write_project(dir: &Path, variant_ref: &str) {
fs::create_dir_all(dir.join("context")).unwrap();
fs::create_dir_all(dir.join("components/asker/steps")).unwrap();
fs::create_dir_all(dir.join("components/asker/fragments")).unwrap();
fs::write(
dir.join("profile.yml"),
"profile: fixture\ncontext:\n project: ./context/binding.yml\ncomponents:\n - use: asker\n",
)
.unwrap();
fs::write(dir.join("context/context.json"), PREPARED_WIDGETS).unwrap();
fs::write(
dir.join("context/binding.yml"),
"kind: prepared\nproject: widgets-layer\ndocument: context/context.json\n",
)
.unwrap();
fs::write(
dir.join("components/asker/component.yml"),
format!(
r#"
id: asker
verb: asker
type: analytical
realization_kind: skill
binding_mode: runtime_selected
context_precondition: []
params: []
llm_steps:
- {{ name: ask, tier: cheap, prompt_ref: steps/ask.md }}
trigger: {{ kind: one_shot }}
guardrails:
- {{ name: read_only_execution, locked: true }}
required_capabilities: [llm:cheap]
borrowed_actions: []
effect:
render_blocks: []
outcome:
kind: none
slots:
- name: verification
variants:
base: {variant_ref}
default: base
"#
),
)
.unwrap();
fs::write(
dir.join("components/asker/steps/ask.md"),
"Ask something. {{ slot.verification }}\n",
)
.unwrap();
fs::write(
dir.join("components/asker/fragments/base.md"),
"Verify the answer.\n",
)
.unwrap();
}
#[test]
fn a_well_behaved_slot_variant_compiles_and_lands_in_the_ir() {
let project = tempfile::tempdir().unwrap();
write_project(project.path(), "fragments/base.md");
let ir = compile_project_to_ir(project.path())
.expect("a relative variant reference inside the component directory must compile");
assert_eq!(
ir["components"][0]["slots"][0]["variants"]["base"],
"Verify the answer."
);
}
#[test]
fn a_slot_variant_escaping_with_dotdot_is_rejected_on_the_real_compile_path() {
let project = tempfile::tempdir().unwrap();
write_project(project.path(), "../../escape.md");
let err = compile_project_to_ir(project.path())
.expect_err("an escaping variant reference must never compile");
assert!(
err.contains("slot 'verification' variant 'base'"),
"the error must name the slot and variant that was rejected: {err}"
);
assert!(
err.contains("../../escape.md"),
"the error must name the offending reference: {err}"
);
}
#[test]
fn an_absolute_slot_variant_reference_is_rejected_on_the_real_compile_path() {
let project = tempfile::tempdir().unwrap();
write_project(project.path(), "/etc/passwd");
let err = compile_project_to_ir(project.path()).expect_err(
"an absolute variant reference must never compile — it would read a file outside any component into the IR",
);
assert!(
err.contains("slot 'verification' variant 'base'"),
"the error must name the slot and variant that was rejected: {err}"
);
assert!(
err.contains("/etc/passwd"),
"the error must name the offending reference: {err}"
);
}
#[test]
fn a_missing_slot_variant_target_is_rejected_on_the_real_compile_path() {
let project = tempfile::tempdir().unwrap();
write_project(project.path(), "fragments/missing.md");
let err = compile_project_to_ir(project.path())
.expect_err("a variant reference naming a file that does not exist must never compile");
assert!(
err.contains("slot 'verification' variant 'base'"),
"the error must name the slot and variant that was rejected: {err}"
);
assert!(
err.contains("does not exist"),
"the error must say the file is missing: {err}"
);
}
fn write_profile_slot_project(dir: &Path, variant_ref: &str) {
write_project(dir, "fragments/base.md");
let component = fs::read_to_string(dir.join("components/asker/component.yml")).unwrap();
let trimmed = component
.split("slots:")
.next()
.expect("the fixture component declares slots")
.to_string();
fs::write(dir.join("components/asker/component.yml"), trimmed).unwrap();
fs::write(
dir.join("components/asker/steps/ask.md"),
"Ask something.\n",
)
.unwrap();
fs::write(
dir.join("profile.yml"),
format!(
"profile: fixture\ncontext:\n project: ./context/binding.yml\nsystem_prompt: |\n Framing. {{{{ slot.plan_mode }}}}\nslots:\n - name: plan_mode\n variants:\n enabled: {variant_ref}\n default: enabled\ncomponents:\n - use: asker\n"
),
)
.unwrap();
fs::create_dir_all(dir.join("fragments")).unwrap();
fs::write(dir.join("fragments/plan.md"), "Draft a plan first.\n").unwrap();
}
#[test]
fn a_well_behaved_profile_slot_variant_compiles_and_lands_at_the_top_level() {
let project = tempfile::tempdir().unwrap();
write_profile_slot_project(project.path(), "fragments/plan.md");
let ir = compile_project_to_ir(project.path())
.expect("a relative profile-level variant reference must compile");
assert_eq!(ir["slots"][0]["name"], "plan_mode");
assert_eq!(ir["slots"][0]["variants"]["enabled"], "Draft a plan first.");
}
#[test]
fn a_profile_slot_variant_escaping_with_dotdot_is_rejected() {
let project = tempfile::tempdir().unwrap();
write_profile_slot_project(project.path(), "../../escape.md");
let err = compile_project_to_ir(project.path())
.expect_err("an escaping profile-level variant reference must never compile");
assert!(
err.contains("profile slot 'plan_mode' variant 'enabled'"),
"the error must name the profile slot and variant: {err}"
);
assert!(err.contains("../../escape.md"), "{err}");
}
#[test]
fn an_absolute_profile_slot_variant_reference_is_rejected() {
let project = tempfile::tempdir().unwrap();
write_profile_slot_project(project.path(), "/etc/passwd");
let err = compile_project_to_ir(project.path())
.expect_err("an absolute profile-level variant reference must never compile");
assert!(
err.contains("profile slot 'plan_mode' variant 'enabled'"),
"{err}"
);
assert!(err.contains("/etc/passwd"), "{err}");
}
#[test]
fn a_slot_with_no_default_fails_compile() {
let project = tempfile::tempdir().unwrap();
write_project(project.path(), "fragments/base.md");
let component = fs::read_to_string(project.path().join("components/asker/component.yml"))
.unwrap()
.replace(" default: base\n", "");
fs::write(
project.path().join("components/asker/component.yml"),
component,
)
.unwrap();
let err = compile_project_to_ir(project.path())
.expect_err("a slot without a default must never compile");
assert!(
err.contains("missing field") && err.contains("default"),
"a slot with no `default:` must be rejected while parsing, naming the missing field: {err}"
);
}