Skip to content

Commit c22853b

Browse files
authored
fix: Allow project config to use modern schema without being rewritten (#205)
1 parent a9ed846 commit c22853b

File tree

4 files changed

+68
-11
lines changed

4 files changed

+68
-11
lines changed

plugins/titan-plugin-github/tests/unit/test_gh_network.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,31 @@ def test_run_command_api_error(gh_network):
125125
assert "pull request not found" in str(exc_info.value).lower()
126126

127127

128+
def test_run_command_api_error_logs_with_custom_logger(gh_network):
129+
"""API failures should be logged via the custom structured logger."""
130+
gh_network._logger = Mock()
131+
132+
with patch('titan_plugin_github.clients.network.gh_network.subprocess.run') as mock_run:
133+
mock_run.side_effect = subprocess.CalledProcessError(
134+
returncode=1,
135+
cmd=["gh", "pr", "create", "--body", "secret body"],
136+
stderr="GraphQL: Something went wrong",
137+
)
138+
139+
with pytest.raises(GitHubAPIError):
140+
gh_network.run_command(["pr", "create", "--body", "secret body"])
141+
142+
gh_network._logger.error.assert_called_once()
143+
event_name = gh_network._logger.error.call_args.args[0]
144+
event_fields = gh_network._logger.error.call_args.kwargs
145+
146+
assert event_name == "gh_command_failed"
147+
assert event_fields["subcommand"] == "pr"
148+
assert event_fields["action"] == "create"
149+
assert event_fields["exit_code"] == 1
150+
assert isinstance(event_fields["duration"], float)
151+
152+
128153
def test_run_command_cli_not_found(gh_network):
129154
"""Test command execution when gh CLI is not installed"""
130155
with patch('titan_plugin_github.clients.network.gh_network.subprocess.run') as mock_run:

plugins/titan-plugin-github/titan_plugin_github/clients/network/gh_network.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,15 +99,15 @@ def run_command(
9999
)
100100
return result.stdout.strip()
101101
except subprocess.CalledProcessError as e:
102-
self._logger.debug(
102+
self._logger.error(
103103
"gh_command_failed",
104104
subcommand=subcommand,
105105
action=action,
106106
duration=round(time.time() - start, 3),
107107
exit_code=e.returncode,
108108
)
109109
error_msg = e.stderr.strip() if e.stderr else str(e)
110-
raise GitHubAPIError(msg.GitHub.API_ERROR.format(error_msg=error_msg))
110+
raise GitHubAPIError(msg.GitHub.API_ERROR.format(error_msg=error_msg)) from e
111111
except FileNotFoundError:
112112
raise GitHubError(msg.GitHub.CLI_NOT_FOUND)
113113
except Exception as e:

tests/core/test_config.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,43 @@ def test_load_does_not_rewrite_legacy_project_config(
413413
assert loaded_project == legacy_project_data
414414

415415

416+
def test_load_accepts_already_migrated_project_config(
417+
tmp_path: Path, monkeypatch, mocker
418+
):
419+
mocker.patch("titan_cli.core.config.PluginRegistry")
420+
421+
global_config_path = tmp_path / "home" / ".titan" / "config.toml"
422+
global_config_path.parent.mkdir(parents=True)
423+
with open(global_config_path, "wb") as f:
424+
tomli_w.dump({"config_version": "1.0"}, f)
425+
426+
project_root = tmp_path / "project"
427+
project_config_path = project_root / ".titan" / "config.toml"
428+
project_config_path.parent.mkdir(parents=True)
429+
project_config_data = {
430+
"config_version": "1.0",
431+
"project": {"name": "demo-project"},
432+
}
433+
with open(project_config_path, "wb") as f:
434+
tomli_w.dump(project_config_data, f)
435+
436+
monkeypatch.setattr(TitanConfig, "GLOBAL_CONFIG", global_config_path)
437+
monkeypatch.setattr(
438+
TitanConfig,
439+
"_find_project_config",
440+
lambda self, path: project_config_path,
441+
)
442+
monkeypatch.setattr("titan_cli.core.config.find_project_root", lambda: project_root)
443+
444+
config = TitanConfig()
445+
446+
with open(project_config_path, "rb") as f:
447+
loaded_project = tomli.load(f)
448+
449+
assert config.config.project.name == "demo-project"
450+
assert loaded_project == project_config_data
451+
452+
416453
def test_config_finds_titan_at_git_root_not_subdir(tmp_path: Path, monkeypatch, mocker):
417454
"""
418455
When running from /monorepo/app with .titan only at /monorepo,

titan_cli/core/config.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,7 @@ class TitanConfig:
1818

1919
GLOBAL_CONFIG = Path.home() / ".titan" / "config.toml"
2020
global_migration_manager = MigrationManager()
21-
project_migration_manager = MigrationManager(
22-
migrations=[],
23-
target_version="legacy",
24-
)
21+
project_migration_manager = MigrationManager()
2522

2623
def __init__(
2724
self,
@@ -69,10 +66,7 @@ def load(self, skip_plugin_init: bool = False):
6966
self.project_config_path = self._find_project_config(project_root)
7067

7168
# Load project config if it exists
72-
self.project_config = self._load_and_migrate_toml(
73-
self.project_config_path,
74-
migration_manager=self.project_migration_manager,
75-
)
69+
self.project_config = self._load_toml(self.project_config_path)
7670

7771
# Merge and validate final config
7872
merged = self._merge_configs(self.global_config, self.project_config)
@@ -133,6 +127,7 @@ def _load_and_migrate_toml(
133127
self,
134128
path: Optional[Path],
135129
migration_manager: MigrationManager,
130+
write_on_migration: bool = True,
136131
) -> dict:
137132
"""Load TOML and normalize it to the current config schema."""
138133
raw_config = self._load_toml(path)
@@ -148,7 +143,7 @@ def _load_and_migrate_toml(
148143
to_version=migration_result.final_version,
149144
steps=migration_result.applied_steps,
150145
)
151-
if path is not None:
146+
if write_on_migration and path is not None:
152147
self._write_toml(path, migration_result.data)
153148

154149
return migration_result.data

0 commit comments

Comments
 (0)