diff --git a/README.md b/README.md index e27637d96..a79f1c017 100644 --- a/README.md +++ b/README.md @@ -403,6 +403,7 @@ such a checkout, use `bash ./scripts/.sh ...`. # Recommended if using Docker/Container-based sandbox make setup-sandbox ``` + Reads the configured sandbox image from UTF-8 `config.yaml`, with or without a leading BOM, using LF or CRLF line endings. 4. **(Optional) Load sample memory data for local review**: ```bash diff --git a/backend/tests/test_setup_sandbox.py b/backend/tests/test_setup_sandbox.py new file mode 100644 index 000000000..caab50078 --- /dev/null +++ b/backend/tests/test_setup_sandbox.py @@ -0,0 +1,45 @@ +"""Regression coverage for sandbox image selection before pulling.""" + +import os +import subprocess +from pathlib import Path + +import pytest +from support.shell import find_script_bash + +REPO_ROOT = Path(__file__).resolve().parents[2] +BASH = find_script_bash() +pytestmark = pytest.mark.skipif(BASH is None, reason="repo shell-script tests need Git Bash on Windows") + + +@pytest.mark.parametrize("newline", ["\n", "\r\n"], ids=["lf", "crlf"]) +@pytest.mark.parametrize("encoding", ["utf-8", "utf-8-sig"]) +@pytest.mark.parametrize("configured", [True, False], ids=["configured", "commented"]) +def test_setup_sandbox_image_selection(tmp_path, encoding, configured, newline): + image = "example.invalid/custom-sandbox:review" + config = f"sandbox:\n image: {image}\n" if configured else f"# sandbox:\n# image: {image}\n" + (tmp_path / "config.yaml").write_bytes(config.replace("\n", newline).encode(encoding)) + bin_dir = tmp_path / "bin" + bin_dir.mkdir() + capture = tmp_path / "docker-args.txt" + # Stub both container engines so the test never pulls a real image. + for name, script in { + "docker": '#!/usr/bin/env bash\nprintf "%s\\n" "$@" >> "$CAPTURE_DOCKER_ARGS"\n', + "container": "#!/usr/bin/env bash\nexit 0\n", + }.items(): + path = bin_dir / name + path.write_text(script, encoding="utf-8") + path.chmod(0o755) + env = {**os.environ, "PATH": f"{bin_dir}{os.pathsep}{os.environ['PATH']}", "CAPTURE_DOCKER_ARGS": str(capture)} + result = subprocess.run([BASH, str(REPO_ROOT / "scripts/setup-sandbox.sh")], cwd=tmp_path, env=env, capture_output=True, text=True, check=True) + # Preserve carriage returns in arguments instead of normalizing them away. + args = capture.read_bytes().decode("utf-8").split("\n")[:-1] + assert args[0] == "pull" + assert len(args) == 2 + if configured: + assert args[1] == image + assert f"Using configured image: {image}" in result.stdout + assert "Using default image:" not in result.stdout + else: + assert args[1] != image + assert f"Using default image: {args[1]}" in result.stdout diff --git a/scripts/AGENTS.md b/scripts/AGENTS.md index daaf275fb..b8180d639 100644 --- a/scripts/AGENTS.md +++ b/scripts/AGENTS.md @@ -6,6 +6,8 @@ indentless lists are supported; nested option names and block-scalar text must not enable the browser extra. Keep the detector standard-library-only because it runs before dependency synchronization. Read UTF-8 config files with or without a leading BOM so the first section remains detectable. +`setup-sandbox.sh` also strips the leading BOM and normalizes CRLF before selecting the image; +keep its shell filter compatible with GNU and BSD sed. The root `PORT` value configures Docker's published nginx ingress only; local orchestration pins Next.js to `3000`. Runtime commands launch from the already diff --git a/scripts/setup-sandbox.sh b/scripts/setup-sandbox.sh index 36bd6f69f..9c1381393 100755 --- a/scripts/setup-sandbox.sh +++ b/scripts/setup-sandbox.sh @@ -12,8 +12,9 @@ echo "" IMAGE="" CONFIGURED=1 if [ -f "config.yaml" ]; then - # Look for uncommented image: field under the sandbox section - IMAGE=$(grep -A 20 "^sandbox:" config.yaml 2>/dev/null | grep "^ image:" | awk '{print $2}' | head -1 || true) + # Strip a leading UTF-8 BOM and CRLF line endings before matching. + # Bash expands the bytes so this works with both GNU and BSD sed. + IMAGE=$(sed $'1s/^\xef\xbb\xbf//;s/\r$//' config.yaml 2>/dev/null | grep -A 20 "^sandbox:" | grep "^ image:" | awk '{print $2}' | head -1 || true) fi if [ -z "$IMAGE" ]; then