# mypy: disable-error-code="attr-defined" import io import pytest import subprocess import typing import unittest.mock from pathlib import Path import cipy import cipy.script from matchers import HasAttributes def mock_run( args: typing.Sequence[str], *, stdout: int | None = None, stderr: int | None = None, check: bool, # Force an error if any of our mocked runs doesnt specify check **kwargs: typing.Any, ): return subprocess.CompletedProcess( args=args, stdout=None if stdout is None else "", stderr=None if stderr is None else "", returncode=0, ) @pytest.fixture(scope="function", autouse=True) def disable_subprocess() -> typing.Iterator[None]: mock = unittest.mock.Mock() mock.side_effect = mock_run with unittest.mock.patch("subprocess.run", mock): yield def test_node_run_runs_main() -> None: action = cipy.script.NodeScript(name="example", main=Path("/dev/null")) action.run(cipy.Context()) assert subprocess.run.call_args_list[0].args == (["node", "/dev/null"],) def test_node_cleanup_noop_without_post() -> None: action = cipy.script.NodeScript(name="example", main=Path("/dev/null")) action.cleanup(cipy.Context()) subprocess.run.assert_not_called() def test_node_cleanup_runs_post() -> None: action = cipy.script.NodeScript( name="example", main=Path("/dev/null"), post=Path("/dev/zero") ) action.cleanup(cipy.Context()) assert subprocess.run.call_args_list[0].args == (["node", "/dev/zero"],) def test_script_writes_to_a_file() -> None: action = cipy.script.Script(script=""" echo Lorem Ipsum """) def check(args, **kwargs): assert Path(args[-1]).is_file() with open(args[-1], "r", encoding="utf8") as file: assert file.read() == action.script return mock_run(args, **kwargs) subprocess.run.side_effect = check action.run(cipy.Context()) def test_script_performs_mustache_substitution() -> None: action = cipy.script.Script(script=""" echo {{ foo }} """) def check(args, **kwargs): assert Path(args[-1]).is_file() with open(args[-1], "r", encoding="utf8") as file: assert file.read() == "echo 1" return mock_run(args, **kwargs) subprocess.run.side_effect = check action.run(cipy.Context(foo=1)) def test_script_propogates_mustache_error(ci_logger) -> None: action = cipy.script.Script(script=""" echo {{ foo }} """) subprocess.run.side_effect = RuntimeError("Should not be reached") assert action.run(cipy.Context()) is cipy.Status.FAILURE ci_logger.format.assert_called_with( HasAttributes(name="Script", message="Could not find key 'foo'\n") )