@@ -77,6 +77,86 @@ def test_profile_from_dict_rejects_unknown_knob() -> None:
7777 assert "not_a_knob" in exc .value .message
7878
7979
80+ def test_role_profile_from_dict_rejects_string_false_for_feasible () -> None :
81+ # The bug this guards: `feasible = "false"` is a non-empty STRING, which
82+ # is truthy in Python — the renderer's `if not rp.feasible` must never
83+ # see a value like this pass validation and silently flip a role to
84+ # feasible.
85+ with pytest .raises (ModelGearError ) as exc :
86+ RoleProfile .from_dict ("cortex" , {"feasible" : "false" })
87+ assert exc .value .code == EXIT_USER_ERROR
88+ assert "cortex" in exc .value .message
89+ assert "feasible" in exc .value .message
90+ assert "bool" in exc .value .message
91+ assert "str" in exc .value .message
92+
93+
94+ def test_role_profile_from_dict_rejects_string_false_for_enforce_eager () -> None :
95+ # Same bug for enforce_eager: a truthy string would render `--enforce-eager`
96+ # from a TOML value the operator intended as "off".
97+ with pytest .raises (ModelGearError ) as exc :
98+ RoleProfile .from_dict ("reranker" , {"enforce_eager" : "false" })
99+ assert exc .value .code == EXIT_USER_ERROR
100+ assert "reranker" in exc .value .message
101+ assert "enforce_eager" in exc .value .message
102+
103+
104+ def test_role_profile_from_dict_accepts_none_for_enforce_eager () -> None :
105+ rp = RoleProfile .from_dict ("cortex" , {"enforce_eager" : None })
106+ assert rp .enforce_eager is None
107+
108+
109+ def test_role_profile_from_dict_rejects_none_for_feasible () -> None :
110+ # feasible has no Optional in the schema (default True, never None).
111+ with pytest .raises (ModelGearError ) as exc :
112+ RoleProfile .from_dict ("cortex" , {"feasible" : None })
113+ assert exc .value .code == EXIT_USER_ERROR
114+ assert "feasible" in exc .value .message
115+
116+
117+ def test_role_profile_from_dict_rejects_bool_for_gpu_mem_util () -> None :
118+ # bool is a subclass of int in Python — must be rejected explicitly for a
119+ # numeric knob, not silently accepted as 0.0/1.0.
120+ with pytest .raises (ModelGearError ) as exc :
121+ RoleProfile .from_dict ("cortex" , {"gpu_mem_util" : True })
122+ assert exc .value .code == EXIT_USER_ERROR
123+ assert "gpu_mem_util" in exc .value .message
124+ assert "cortex" in exc .value .message
125+
126+
127+ def test_role_profile_from_dict_accepts_int_and_float_for_gpu_mem_util () -> None :
128+ assert RoleProfile .from_dict ("cortex" , {"gpu_mem_util" : 1 }).gpu_mem_util == 1
129+ assert RoleProfile .from_dict ("cortex" , {"gpu_mem_util" : 0.3 }).gpu_mem_util == 0.3
130+
131+
132+ def test_role_profile_from_dict_rejects_bool_for_max_model_len () -> None :
133+ with pytest .raises (ModelGearError ) as exc :
134+ RoleProfile .from_dict ("cortex" , {"max_model_len" : False })
135+ assert exc .value .code == EXIT_USER_ERROR
136+ assert "max_model_len" in exc .value .message
137+
138+
139+ def test_role_profile_from_dict_rejects_bool_for_max_num_seqs () -> None :
140+ with pytest .raises (ModelGearError ) as exc :
141+ RoleProfile .from_dict ("cortex" , {"max_num_seqs" : True })
142+ assert exc .value .code == EXIT_USER_ERROR
143+ assert "max_num_seqs" in exc .value .message
144+
145+
146+ def test_role_profile_from_dict_rejects_wrong_type_for_string_knobs () -> None :
147+ for knob in ("model" , "quantization" , "kv_cache_dtype" , "attention_backend" ):
148+ with pytest .raises (ModelGearError ) as exc :
149+ RoleProfile .from_dict ("cortex" , {knob : 123 })
150+ assert exc .value .code == EXIT_USER_ERROR
151+ assert knob in exc .value .message
152+
153+
154+ def test_role_profile_from_dict_accepts_none_for_string_knobs () -> None :
155+ for knob in ("model" , "quantization" , "kv_cache_dtype" , "attention_backend" ):
156+ rp = RoleProfile .from_dict ("cortex" , {knob : None })
157+ assert getattr (rp , knob ) is None
158+
159+
80160def test_profile_from_dict_rejects_unknown_top_level_key () -> None :
81161 with pytest .raises (ModelGearError ):
82162 Profile .from_dict ("bogus" , {"nope" : True })
@@ -268,6 +348,39 @@ def test_operator_profile_overrides_a_builtin_of_the_same_name(tmp_path) -> None
268348 assert builtin_spark .role ("cortex" ).model == "sakamakismile/Qwen3.6-27B-Text-NVFP4-MTP"
269349
270350
351+ def test_mixed_case_operator_file_overrides_the_builtin (tmp_path ) -> None :
352+ # The bug this guards: discover_operator_profiles() used to key profiles
353+ # by the RAW filename stem, so `profiles/Thor.toml` never matched a
354+ # resolve_profile("thor") lookup (which normalises with .strip().lower())
355+ # and silently failed to override the builtin.
356+ profiles_dir = tmp_path / "profiles"
357+ profiles_dir .mkdir ()
358+ (profiles_dir / "Thor.toml" ).write_text (
359+ '[roles.cortex]\n model = "operator/mixed-case-override"\n ' ,
360+ encoding = "utf-8" ,
361+ )
362+ found = loader .discover_operator_profiles (tmp_path )
363+ assert set (found .keys ()) == {"thor" }
364+ assert found ["thor" ].role ("cortex" ).model == "operator/mixed-case-override"
365+
366+ resolved = loader .resolve_profile ("thor" , deploy_dir = tmp_path )
367+ assert resolved .role ("cortex" ).model == "operator/mixed-case-override"
368+
369+ resolved_upper = loader .resolve_profile ("THOR" , deploy_dir = tmp_path )
370+ assert resolved_upper .role ("cortex" ).model == "operator/mixed-case-override"
371+
372+
373+ def test_operator_profile_case_collision_raises_user_error (tmp_path ) -> None :
374+ profiles_dir = tmp_path / "profiles"
375+ profiles_dir .mkdir ()
376+ (profiles_dir / "Thor.toml" ).write_text ('[roles.cortex]\n model = "a"\n ' , encoding = "utf-8" )
377+ (profiles_dir / "thor.toml" ).write_text ('[roles.cortex]\n model = "b"\n ' , encoding = "utf-8" )
378+ with pytest .raises (ModelGearError ) as exc :
379+ loader .discover_operator_profiles (tmp_path )
380+ assert exc .value .code == EXIT_USER_ERROR
381+ assert "thor" in exc .value .message .lower ()
382+
383+
271384def test_discover_operator_profiles_missing_dir_returns_empty (tmp_path ) -> None :
272385 assert loader .discover_operator_profiles (tmp_path / "nonexistent" ) == {}
273386
0 commit comments