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
8 changes: 8 additions & 0 deletions sdks/python-cli/omi_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,14 @@ def load(path: Optional[Path] = None) -> Config:
)

active = data.get("active_profile", DEFAULT_PROFILE_NAME)
if not isinstance(active, str):
return Config(
path=p,
active_profile=DEFAULT_PROFILE_NAME,
profiles={},
load_error=f"'active_profile' must be a string, got {type(active).__name__}",
)

profiles_data = data.get("profiles", {})

# Validate that profiles is a table (dict), not a string or other scalar.
Expand Down
48 changes: 48 additions & 0 deletions sdks/python-cli/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,3 +415,51 @@ def test_no_profiles_section(config_path: Path) -> None:
config = cfg.load()
assert not config.was_load_error
assert config.active_profile == "other"


# -- Regression tests for non-string active_profile selector (Issue #13442) --


@pytest.mark.parametrize(
"invalid_toml,expected_type",
[
('active_profile = ["work"]\n', "list"),
("active_profile = 42\n", "int"),
("active_profile = true\n", "bool"),
("[active_profile]\nname = 'work'\n", "dict"),
],
)
def test_active_profile_non_string_records_load_error(
config_path: Path, invalid_toml: str, expected_type: str
) -> None:
"""active_profile must be a string; non-string values should set load_error instead of crashing."""
config_path.write_text(invalid_toml, encoding="utf-8")
config = cfg.load()
assert config.was_load_error
assert config.active_profile == cfg.DEFAULT_PROFILE_NAME
assert config.profiles == {}
assert config.load_error is not None
assert f"'active_profile' must be a string, got {expected_type}" in config.load_error


def test_active_profile_non_string_refuses_save_overwrite(config_path: Path) -> None:
"""A config with invalid active_profile type must not be overwritten by save()."""
config_path.write_text('active_profile = ["work"]\n[profiles.work]\napi_base = "https://api.omi.me"\n', encoding="utf-8")
config = cfg.load()
assert config.was_load_error

with pytest.raises(PermissionError, match="refusing to overwrite"):
cfg.save(config)

# The file on disk is preserved intact
assert 'active_profile = ["work"]' in config_path.read_text(encoding="utf-8")


def test_active_profile_non_string_diagnostics_succeed(config_path: Path, cli_runner) -> None:
"""Read-only diagnostics commands must still succeed when active_profile is invalid."""
config_path.write_text('active_profile = ["work"]\n', encoding="utf-8")
result = cli_runner.invoke(app, ["version"])
assert result.exit_code == 0, result.output

result_path = cli_runner.invoke(app, ["config", "path"])
assert result_path.exit_code == 0, result_path.output
Loading