Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions esphome_device_builder/helpers/device_yaml/_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ def generate_adoption_yaml(
splices *api_encryption_key* in; ``None`` leaves the device's own key alone.
"""
lines: list[str] = ["substitutions:"]
lines.append(f" name: {name}")
lines.append(f" name: {_safe_yaml_scalar(name)}")
if friendly_name:
lines.append(f" friendly_name: {_safe_yaml_scalar(friendly_name)}")
lines.append("")
Expand Down Expand Up @@ -216,14 +216,12 @@ def generate_device_yaml(
# Board reference comment so users can find the source manifest
lines: list[str] = [*_board_header_lines(board)]

# ESPHome core. ``name`` arrives already slug-safe (see
# ``mutations_create``), but ``friendly_name`` is raw user
# input that may contain ``:``, ``#``, leading indicators, or
# other YAML metacharacters — route it through the safe-scalar
# renderer so a label like ``Bedroom #2`` doesn't truncate at
# the comment marker on round trip.
# ESPHome core. Both values go through the safe-scalar renderer:
# an all-digit ``name`` would parse back as an int, and a
# ``friendly_name`` like ``Bedroom #2`` would truncate at the
# comment marker.
lines.append("esphome:")
lines.append(f" name: {name}")
lines.append(f" name: {_safe_yaml_scalar(name)}")
lines.append(f" friendly_name: {_safe_yaml_scalar(friendly_name)}")
lines.append("")

Expand Down Expand Up @@ -496,7 +494,7 @@ def generate_minimal_stub_yaml(
about to edit.
"""
header = (
f"esphome:\n name: {name}\n"
f"esphome:\n name: {_safe_yaml_scalar(name)}\n"
f" friendly_name: {_safe_yaml_scalar(friendly_name)}\n\n"
"# Replace this with your actual platform if you aren't using ESP32.\n"
"esp32:\n board: esp32dev\n\n"
Expand Down
17 changes: 17 additions & 0 deletions tests/controllers/devices/test_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,23 @@ async def test_create_device_quotes_friendly_name_with_yaml_metachars(
assert storage.friendly_name == "Bedroom #2: lamp"


@pytest.mark.usefixtures("stub_create_device_metadata_helpers")
async def test_create_device_quotes_all_digit_name(
tmp_path: Path, make_controller: MakeControllerFactory
) -> None:
"""An all-digit name is written quoted so it parses back as a string."""
ctrl = make_controller(tmp_path, with_state_monitor=True, with_boards=True)
boards = StubBoardLookups(ctrl)
boards.find_by_pio_board_returns(None)
boards.find_by_platform_variant_returns(None)

result = await ctrl.create_device(name="8266")

assert result.configuration == "8266.yaml"
content = (tmp_path / "8266.yaml").read_text("utf-8")
assert ' name: "8266"\n' in content


@pytest.mark.usefixtures("stub_create_device_metadata_helpers")
async def test_create_device_rejects_name_with_no_hostname_safe_characters(
tmp_path: Path, make_controller: MakeControllerFactory
Expand Down
21 changes: 21 additions & 0 deletions tests/test_device_yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,27 @@ def test_generate_minimal_stub_yaml_has_required_blocks() -> None:
assert "\ncaptive_portal:\n" in out


@pytest.mark.parametrize("name", ["8266", "0x1f", "017", "esp-8266"])
def test_generators_emit_name_as_string(name: str) -> None:
"""Every generator's ``name`` parses back as a string, even when number-like."""
stub = generate_minimal_stub_yaml(name, "Lamp", wifi_secrets_available=False)
full = generate_device_yaml(
name, "Lamp", _make_esp32_board(), ssid="", psk="", wifi_secrets_available=False
)
adopt = generate_adoption_yaml(
name,
"Lamp",
"k",
"github://x/y.yaml@main",
wifi_secrets_available=False,
api_encryption_key=None,
)
for text, section in ((stub, "esphome"), (full, "esphome"), (adopt, "substitutions")):
parsed = yaml_util.parse_yaml(Path("x.yaml"), text)[section]["name"]
assert isinstance(parsed, str)
assert parsed == name


def test_generate_minimal_stub_yaml_emits_per_device_encryption_key() -> None:
"""API encryption key is freshly generated each call.

Expand Down
Loading