55
66from flightdeck .config import ConfigError , load_org
77from flightdeck .schemas import Feedback , Run
8- from tests .conftest import ORG , SUPPORT_WORKFLOW , write_org
8+ from tests .conftest import MODELS , ORG , SUPPORT_WORKFLOW , write_org
99
1010
1111def _run (run_id : str , when : datetime , ** overrides ) -> Run :
@@ -67,6 +67,23 @@ def test_month_cost_sums_only_that_month(store):
6767# ------------------------------------------------------------------ config loading
6868
6969
70+ def _use_case (** overrides ):
71+ fields = {
72+ "id" : "ticket-triage" ,
73+ "name" : "Ticket triage" ,
74+ "department" : "Support" ,
75+ "task_minutes" : 8 ,
76+ "tasks_per_month" : 200 ,
77+ "automation_potential" : 0.6 ,
78+ "data_readiness" : 4 ,
79+ "process_stability" : 4 ,
80+ "risk" : 2 ,
81+ "effort_weeks" : 3 ,
82+ }
83+ fields .update (overrides )
84+ return fields
85+
86+
7087def test_missing_org_file_suggests_init (tmp_path ):
7188 with pytest .raises (ConfigError , match = "flightdeck init" ):
7289 load_org (tmp_path )
@@ -82,8 +99,13 @@ def test_unknown_keys_fail_loudly(tmp_path):
8299def test_dangling_use_case_reference_fails (tmp_path ):
83100 workflow = dict (SUPPORT_WORKFLOW )
84101 workflow ["use_case" ] = "does-not-exist"
85- with pytest .raises (ConfigError , match = "does-not-exist" ):
86- load_org (write_org (tmp_path / "org" , workflows = [workflow ]))
102+ root = write_org (tmp_path / "org" , workflows = [workflow ])
103+ path = root / "workflows" / "support-reply.yaml"
104+
105+ with pytest .raises (ConfigError ) as excinfo :
106+ load_org (root )
107+
108+ assert str (excinfo .value ) == f"{ path } : use_case 'does-not-exist' not found in usecases.yaml"
87109
88110
89111def test_empty_model_registry_fails (tmp_path ):
@@ -93,6 +115,127 @@ def test_empty_model_registry_fails(tmp_path):
93115 load_org (root )
94116
95117
118+ @pytest .mark .parametrize ("registry" , [{}, {"models" : None }])
119+ def test_missing_or_null_model_collection_is_an_empty_registry (tmp_path , registry ):
120+ root = write_org (tmp_path / "org" )
121+ (root / "models.yaml" ).write_text (yaml .safe_dump (registry ), encoding = "utf-8" )
122+
123+ with pytest .raises (ConfigError , match = r"models\.yaml: the model registry is empty" ):
124+ load_org (root )
125+
126+
127+ @pytest .mark .parametrize ("collection" , [{}, {"usecases" : None }])
128+ def test_missing_or_null_use_case_collection_loads_empty (tmp_path , collection ):
129+ root = write_org (tmp_path / "org" , workflows = [])
130+ (root / "usecases.yaml" ).write_text (yaml .safe_dump (collection ), encoding = "utf-8" )
131+
132+ assert load_org (root ).usecases == {}
133+
134+
135+ def test_absent_workflow_directory_loads_empty (tmp_path ):
136+ root = write_org (tmp_path / "org" , workflows = [])
137+
138+ assert not (root / "workflows" ).exists ()
139+ assert load_org (root ).workflows == {}
140+
141+
142+ def test_invalid_model_reports_the_model_registry_path (tmp_path ):
143+ model = {** MODELS [0 ], "tier" : "unsupported" }
144+ root = write_org (tmp_path / "org" , models = [model ])
145+
146+ with pytest .raises (ConfigError ) as excinfo :
147+ load_org (root )
148+
149+ message = str (excinfo .value )
150+ assert str (root / "models.yaml" ) in message
151+ assert "invalid configuration" in message
152+ assert "tier" in message
153+
154+
155+ def test_duplicate_model_id_reports_kind_and_registry_path (tmp_path ):
156+ root = write_org (tmp_path / "org" , models = [dict (MODELS [0 ]), dict (MODELS [0 ])])
157+
158+ with pytest .raises (ConfigError ) as excinfo :
159+ load_org (root )
160+
161+ assert str (excinfo .value ) == f"{ root / 'models.yaml' } : duplicate model id 'mock-fast-eu'"
162+
163+
164+ def test_invalid_use_case_reports_the_use_case_path (tmp_path ):
165+ root = write_org (tmp_path / "org" , workflows = [])
166+ (root / "usecases.yaml" ).write_text (
167+ yaml .safe_dump ({"usecases" : [_use_case (task_minutes = 0 )]}), encoding = "utf-8"
168+ )
169+
170+ with pytest .raises (ConfigError ) as excinfo :
171+ load_org (root )
172+
173+ message = str (excinfo .value )
174+ assert str (root / "usecases.yaml" ) in message
175+ assert "invalid configuration" in message
176+ assert "task_minutes" in message
177+
178+
179+ def test_duplicate_use_case_id_reports_kind_and_use_case_path (tmp_path ):
180+ root = write_org (tmp_path / "org" , workflows = [])
181+ cases = [_use_case (), _use_case (name = "Another triage" )]
182+ (root / "usecases.yaml" ).write_text (yaml .safe_dump ({"usecases" : cases }), encoding = "utf-8" )
183+
184+ with pytest .raises (ConfigError ) as excinfo :
185+ load_org (root )
186+
187+ assert str (excinfo .value ) == f"{ root / 'usecases.yaml' } : duplicate use case id 'ticket-triage'"
188+
189+
190+ @pytest .mark .parametrize ("suffix" , [".yaml" , ".yml" ])
191+ def test_invalid_workflow_reports_its_workflow_path (tmp_path , suffix ):
192+ workflow = {
193+ ** SUPPORT_WORKFLOW ,
194+ "baseline" : {** SUPPORT_WORKFLOW ["baseline" ], "minutes_per_task" : 0 },
195+ }
196+ root = write_org (tmp_path / "org" , workflows = [])
197+ workflows_dir = root / "workflows"
198+ workflows_dir .mkdir ()
199+ path = workflows_dir / f"invalid{ suffix } "
200+ path .write_text (yaml .safe_dump (workflow ), encoding = "utf-8" )
201+
202+ with pytest .raises (ConfigError ) as excinfo :
203+ load_org (root )
204+
205+ message = str (excinfo .value )
206+ assert str (path ) in message
207+ assert "invalid configuration" in message
208+ assert "baseline.minutes_per_task" in message
209+
210+
211+ def test_duplicate_workflow_id_wins_over_dangling_use_case_in_second_file (tmp_path ):
212+ root = write_org (tmp_path / "org" , workflows = [])
213+ workflows_dir = root / "workflows"
214+ workflows_dir .mkdir ()
215+ first = workflows_dir / "first.yaml"
216+ second = workflows_dir / "second.yaml"
217+ duplicate_with_dangling_use_case = {** SUPPORT_WORKFLOW , "use_case" : "does-not-exist" }
218+ first .write_text (yaml .safe_dump (SUPPORT_WORKFLOW ), encoding = "utf-8" )
219+ second .write_text (yaml .safe_dump (duplicate_with_dangling_use_case ), encoding = "utf-8" )
220+
221+ with pytest .raises (ConfigError ) as excinfo :
222+ load_org (root )
223+
224+ assert str (excinfo .value ) == f"{ second } : duplicate workflow id 'support-reply'"
225+
226+
227+ def test_mixed_workflow_extensions_use_yaml_then_yml_order (tmp_path ):
228+ root = write_org (tmp_path / "org" , workflows = [])
229+ workflows_dir = root / "workflows"
230+ workflows_dir .mkdir ()
231+ yaml_workflow = {** SUPPORT_WORKFLOW , "id" : "yaml-first" }
232+ yml_workflow = {** SUPPORT_WORKFLOW , "id" : "yml-second" }
233+ (workflows_dir / "z.yaml" ).write_text (yaml .safe_dump (yaml_workflow ), encoding = "utf-8" )
234+ (workflows_dir / "a.yml" ).write_text (yaml .safe_dump (yml_workflow ), encoding = "utf-8" )
235+
236+ assert list (load_org (root ).workflows ) == ["yaml-first" , "yml-second" ]
237+
238+
96239def test_invalid_redact_pattern_is_a_loud_config_error (tmp_path ):
97240 # A bad regex must fail at LOAD, naming the org file and the pattern — never
98241 # at run time, inside the redactor, mid-run.
0 commit comments