@@ -121,3 +121,63 @@ def test_main_warns_on_legacy_config_keys(controller, _, tmp_path):
121121 # conn=simulator wins; the stale adapter/mqtt_host are ignored.
122122 assert settings .adapter == "dotbot-simulator"
123123 assert settings .mqtt_host != "stale"
124+
125+
126+ def test_scaffold_sim_state_creates_example_when_accepted (tmp_path , monkeypatch ):
127+ """Interactive simulator run with nothing specified + `y` writes an
128+ editable `simulator_init_state.toml` in the cwd."""
129+ from dotbot import SIMULATOR_INIT_STATE_DEFAULT
130+ from dotbot .controller_app import _maybe_scaffold_sim_state
131+
132+ monkeypatch .chdir (tmp_path )
133+ with patch ("sys.stdin" ) as stdin , patch ("click.confirm" , return_value = True ):
134+ stdin .isatty .return_value = True
135+ _maybe_scaffold_sim_state (None ) # the value main() passes by default
136+ created = tmp_path / SIMULATOR_INIT_STATE_DEFAULT
137+ assert created .is_file ()
138+ assert "[[dotbots]]" in created .read_text ()
139+
140+
141+ def test_scaffold_sim_state_declined_writes_nothing (tmp_path , monkeypatch ):
142+ monkeypatch .chdir (tmp_path )
143+ from dotbot import SIMULATOR_INIT_STATE_DEFAULT
144+ from dotbot .controller_app import _maybe_scaffold_sim_state
145+
146+ with patch ("sys.stdin" ) as stdin , patch ("click.confirm" , return_value = False ):
147+ stdin .isatty .return_value = True
148+ _maybe_scaffold_sim_state (None )
149+ assert not (tmp_path / SIMULATOR_INIT_STATE_DEFAULT ).exists ()
150+
151+
152+ def test_scaffold_sim_state_noninteractive_never_prompts (tmp_path , monkeypatch ):
153+ """No TTY (CI, a pipe) → no prompt, no file; the packaged world is used."""
154+ monkeypatch .chdir (tmp_path )
155+ from dotbot import SIMULATOR_INIT_STATE_DEFAULT
156+ from dotbot .controller_app import _maybe_scaffold_sim_state
157+
158+ with patch ("sys.stdin" ) as stdin , patch ("click.confirm" ) as confirm :
159+ stdin .isatty .return_value = False
160+ _maybe_scaffold_sim_state (None )
161+ confirm .assert_not_called ()
162+ assert not (tmp_path / SIMULATOR_INIT_STATE_DEFAULT ).exists ()
163+
164+
165+ def test_scaffold_sim_state_skips_when_explicit_path_given (tmp_path , monkeypatch ):
166+ monkeypatch .chdir (tmp_path )
167+ from dotbot .controller_app import _maybe_scaffold_sim_state
168+
169+ with patch ("sys.stdin" ) as stdin , patch ("click.confirm" ) as confirm :
170+ stdin .isatty .return_value = True
171+ _maybe_scaffold_sim_state ("my_world.toml" ) # explicit path → no prompt
172+ confirm .assert_not_called ()
173+
174+
175+ @patch ("dotbot_utils.serial_interface.serial.Serial.open" )
176+ @patch ("dotbot.controller.Controller.run" )
177+ @patch ("dotbot.controller_app._maybe_scaffold_sim_state" )
178+ def test_main_simulator_offers_scaffold_with_none (scaffold , _run , _serial ):
179+ """Regression: `--conn simulator` with no flag/config must reach the
180+ scaffold with None (the option default), not a sentinel string."""
181+ runner = CliRunner ()
182+ runner .invoke (main , ["--conn" , "simulator" ])
183+ scaffold .assert_called_once_with (None )
0 commit comments