Files
craft-libs/crates/ai-core/tests/spec_template.rs
T
Jeremy bd175be54f craft-ai-core: rename from craft-ai-model; lapse each kind of consent on its own; keep models found at a new address
Consent expiry is now per kind of data: renewing text no longer renews an expired image permission. A connection edit keeps models found by a test at the new address, and drops only the ones saved for the old one.
2026-10-15 20:06:57 +01:00

68 lines
2.8 KiB
Rust

//! Substitution rules for declarative specs: what a template may name, and that the structure of
//! a request can never come from the user's text.
use craft_ai_core::spec::template::{Slot, Values, render_text, render_value, slots, value_slots};
use serde_json::json;
fn values() -> Values<'static> {
Values { prompt: Some("a red door"), model: Some("img-1"), job_id: Some("job-7") }
}
#[test]
fn the_three_names_are_known_and_nothing_else() {
assert_eq!(slots("{prompt} {model} {job_id}").unwrap(), vec![Slot::Prompt, Slot::Model, Slot::JobId]);
assert_eq!(slots("no slots here").unwrap(), vec![]);
for other in ["{seed}", "{param.size}", "{Prompt}", "{}", "{ prompt }"] {
let why = slots(other).unwrap_err();
assert!(why.contains("unsupported: placeholder"), "{other}: {why}");
}
assert_eq!(slots("{seed}").unwrap_err(), "unsupported: placeholder {seed}");
}
#[test]
fn braces_are_closed_or_doubled() {
assert!(slots("a { b").unwrap_err().contains("never closed"));
assert!(slots("a } b").unwrap_err().contains("lone }"));
assert_eq!(render_text("{{literal}}", &values()).unwrap(), "{literal}");
assert_eq!(render_text("{{{prompt}}}", &values()).unwrap(), "{a red door}");
}
#[test]
fn text_is_inserted_and_never_read_again() {
let v = Values { prompt: Some("{job_id} and {{ and }"), ..values() };
assert_eq!(render_text("say: {prompt}!", &v).unwrap(), "say: {job_id} and {{ and }!");
}
#[test]
fn a_prompt_cannot_add_a_field_or_change_a_key() {
let template = json!({"model": "{model}", "prompt": "{prompt}", "n": 1});
let v = Values { prompt: Some(r#"x","admin":true,"y":"#), ..values() };
let body = render_value(&template, &v).unwrap();
assert_eq!(body["prompt"], json!(r#"x","admin":true,"y":"#));
assert_eq!(body.as_object().unwrap().len(), 3);
assert!(body.get("admin").is_none());
assert_eq!(body["n"], json!(1));
}
#[test]
fn keys_are_literal_and_nested_values_are_rendered() {
let template = json!({"a": [{"b": "{model}"}, "{job_id}"], "c": {"d": "{prompt}"}});
let body = render_value(&template, &values()).unwrap();
assert_eq!(body, json!({"a": [{"b": "img-1"}, "job-7"], "c": {"d": "a red door"}}));
assert_eq!(value_slots(&template).unwrap(), vec![Slot::Model, Slot::JobId, Slot::Prompt]);
assert!(value_slots(&json!({"{prompt}": 1})).unwrap_err().contains("key"));
}
#[test]
fn a_key_is_never_substituted_even_when_it_looks_like_a_slot() {
let body = render_value(&json!({"{prompt}": "{model}"}), &values()).unwrap();
assert_eq!(body, json!({"{prompt}": "img-1"}));
}
#[test]
fn a_slot_with_no_value_is_an_error_not_an_empty_string() {
let v = Values { prompt: Some("x"), ..Values::default() };
let why = render_text("/jobs/{job_id}", &v).unwrap_err();
assert!(why.contains("{job_id} is not available"), "{why}");
}