Skip to main content

prosaic_project/
scenario.rs

1//! `tests/*.toml` schema — narrative-flow scenarios with discourse assertions.
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct Scenario {
8    pub name: String,
9    #[serde(default)]
10    pub description: String,
11    #[serde(default)]
12    pub engine: ScenarioEngineOverride,
13    pub events: Vec<ScenarioEvent>,
14    #[serde(default)]
15    pub expected: Option<Expected>,
16}
17
18#[derive(Debug, Clone, Default, Serialize, Deserialize)]
19#[serde(default)]
20pub struct ScenarioEngineOverride {
21    pub variation: Option<String>,
22    pub language: Option<String>,
23    pub salience_thresholds: Option<crate::manifest::SalienceThresholdsConfig>,
24    pub faithfulness_min: Option<f64>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct ScenarioEvent {
29    pub template: String,
30    /// Free-form context map. Values can be strings, numbers, bools, or arrays of strings.
31    #[serde(default)]
32    pub context: HashMap<String, toml::Value>,
33    /// RST relation hint (Elaboration, Contrast, Result, etc.). String form for TOML friendliness.
34    #[serde(default)]
35    pub rst_relation: Option<String>,
36}
37
38#[derive(Debug, Clone, Default, Serialize, Deserialize)]
39#[serde(default)]
40pub struct Expected {
41    pub output: Option<String>,
42    pub faithfulness_min: Option<f64>,
43    pub discourse: Vec<ExpectedDiscourse>,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct ExpectedDiscourse {
48    pub event_index: usize,
49    #[serde(default)]
50    pub reference_form: Option<String>,
51    #[serde(default)]
52    pub connective_contains: Option<String>,
53    #[serde(default)]
54    pub transition: Option<String>,
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    fn parse_minimal_scenario() {
63        let toml_str = r#"
64            name = "smoke"
65            [[events]]
66            template = "code.added"
67            context = { name = "Foo", entity_type = "class" }
68        "#;
69        let s: Scenario = toml::from_str(toml_str).unwrap();
70        assert_eq!(s.name, "smoke");
71        assert_eq!(s.events.len(), 1);
72        assert_eq!(s.events[0].template, "code.added");
73        assert_eq!(
74            s.events[0].context.get("name").unwrap().as_str().unwrap(),
75            "Foo"
76        );
77    }
78
79    #[test]
80    fn parse_full_scenario_with_expectations() {
81        let toml_str = r#"
82            name = "PR 142"
83            description = "auth refactor"
84
85            [engine]
86            variation = "fixed"
87            language = "en"
88            faithfulness_min = 0.9
89
90            [[events]]
91            template = "code.modified"
92            context = { name = "UserService", consumer_count = 6 }
93
94            [[events]]
95            template = "code.moved"
96            context = { name = "UserService" }
97            rst_relation = "elaboration"
98
99            [expected]
100            output = "..."
101            faithfulness_min = 0.85
102
103            [[expected.discourse]]
104            event_index = 1
105            reference_form = "Pronoun"
106
107            [[expected.discourse]]
108            event_index = 1
109            connective_contains = "also"
110        "#;
111        let s: Scenario = toml::from_str(toml_str).unwrap();
112        assert_eq!(s.events.len(), 2);
113        assert_eq!(s.events[1].rst_relation.as_deref(), Some("elaboration"));
114        let exp = s.expected.unwrap();
115        assert_eq!(exp.faithfulness_min, Some(0.85));
116        assert_eq!(exp.discourse.len(), 2);
117        assert_eq!(exp.discourse[0].reference_form.as_deref(), Some("Pronoun"));
118        assert_eq!(
119            exp.discourse[1].connective_contains.as_deref(),
120            Some("also")
121        );
122    }
123
124    #[test]
125    fn parse_array_context_value() {
126        let toml_str = r#"
127            name = "list"
128            [[events]]
129            template = "code.modified"
130            context = { name = "X", consumers = ["a", "b", "c"] }
131        "#;
132        let s: Scenario = toml::from_str(toml_str).unwrap();
133        let arr = s.events[0]
134            .context
135            .get("consumers")
136            .unwrap()
137            .as_array()
138            .unwrap();
139        assert_eq!(arr.len(), 3);
140        assert_eq!(arr[0].as_str().unwrap(), "a");
141    }
142}