From acf1b6735cf8e7f77a3af9eee3b559e8cbc6ffd4 Mon Sep 17 00:00:00 2001 From: bhuvan-somisetty Date: Mon, 21 Sep 2026 07:45:57 +0530 Subject: [PATCH 1/3] fix(cli): reject path-like project names in init --- concore_cli/commands/init.py | 7 +++++++ tests/test_cli.py | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/concore_cli/commands/init.py b/concore_cli/commands/init.py index 53fd53f..cfaf73c 100644 --- a/concore_cli/commands/init.py +++ b/concore_cli/commands/init.py @@ -258,8 +258,14 @@ def _build_graphml(project_name, selected_langs): # --------------------------------------------------------------------------- +def _check_project_name(name): + if name in (".", "..") or Path(name).name != name: + raise ValueError("Project name must not contain path separators") + + def init_project_interactive(name, selected_langs, console): """Create a project with one node per selected language (no edges).""" + _check_project_name(name) project_path = Path(name) if project_path.exists(): @@ -327,6 +333,7 @@ def init_project_interactive(name, selected_langs, console): def init_project(name, template, console): """Non-interactive init — single Python node skeleton.""" + _check_project_name(name) project_path = Path(name) if project_path.exists(): diff --git a/tests/test_cli.py b/tests/test_cli.py index d746040..ad832a4 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -55,6 +55,15 @@ def test_init_existing_directory(self): self.assertNotEqual(result.exit_code, 0) self.assertIn("already exists", result.output) + def test_init_rejects_path_in_name(self): + with self.runner.isolated_filesystem(temp_dir=self.temp_dir): + Path("work").mkdir() + os.chdir("work") + result = self.runner.invoke(cli, ["init", "../x"]) + self.assertNotEqual(result.exit_code, 0) + self.assertIn("path separators", result.output) + self.assertFalse(Path("../x").exists()) + def test_validate_missing_file(self): result = self.runner.invoke(cli, ["validate", "nonexistent.graphml"]) self.assertNotEqual(result.exit_code, 0) From 747bc8eee85765a449e23f02c3b15365e51db946 Mon Sep 17 00:00:00 2001 From: bhuvan-somisetty Date: Sat, 26 Sep 2026 08:06:38 +0530 Subject: [PATCH 2/3] test(cli): cover both init paths for path-like project names --- tests/test_cli.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index ad832a4..b8f8ff8 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -55,14 +55,18 @@ def test_init_existing_directory(self): self.assertNotEqual(result.exit_code, 0) self.assertIn("already exists", result.output) - def test_init_rejects_path_in_name(self): - with self.runner.isolated_filesystem(temp_dir=self.temp_dir): - Path("work").mkdir() - os.chdir("work") - result = self.runner.invoke(cli, ["init", "../x"]) - self.assertNotEqual(result.exit_code, 0) - self.assertIn("path separators", result.output) - self.assertFalse(Path("../x").exists()) + def test_init_rejects_path_like_name(self): + # init must not create the project outside the current directory (#588) + for args in (["init", "../x"], ["init", "../x", "--interactive"]): + with self.subTest(args=args): + with self.runner.isolated_filesystem(temp_dir=self.temp_dir): + with patch( + "concore_cli.cli.run_wizard", return_value=["python"] + ): + result = self.runner.invoke(cli, args) + self.assertNotEqual(result.exit_code, 0) + self.assertIn("path separators", result.output) + self.assertFalse(Path("../x").exists()) def test_validate_missing_file(self): result = self.runner.invoke(cli, ["validate", "nonexistent.graphml"]) From 6d6dc8a36049d63471de3f7bba7ce98eeb2a9e30 Mon Sep 17 00:00:00 2001 From: bhuvan-somisetty Date: Sat, 26 Sep 2026 08:14:28 +0530 Subject: [PATCH 3/3] test(cli): patch run_wizard on the cli module directly --- tests/test_cli.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index b8f8ff8..e6d7ab2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -2,12 +2,15 @@ import tempfile import shutil import os +import sys import json from pathlib import Path from unittest.mock import patch from click.testing import CliRunner from concore_cli.cli import cli +cli_module = sys.modules[cli.callback.__module__] + class TestConcoreCLI(unittest.TestCase): def setUp(self): @@ -60,8 +63,8 @@ def test_init_rejects_path_like_name(self): for args in (["init", "../x"], ["init", "../x", "--interactive"]): with self.subTest(args=args): with self.runner.isolated_filesystem(temp_dir=self.temp_dir): - with patch( - "concore_cli.cli.run_wizard", return_value=["python"] + with patch.object( + cli_module, "run_wizard", return_value=["python"] ): result = self.runner.invoke(cli, args) self.assertNotEqual(result.exit_code, 0)