from __future__ import annotations

import subprocess
import unittest
from unittest import mock

import sys
from pathlib import Path

SCRIPTS = Path(__file__).resolve().parents[1] / "scripts"
if str(SCRIPTS) not in sys.path:
    sys.path.insert(0, str(SCRIPTS))
import remote_exec as module


class RemoteExecTests(unittest.TestCase):
    def test_shell_hostile_argv_is_one_quoted_remote_command(self) -> None:
        template = 'go-template={{range $k, $_ := .data}}{{$k}}{{"\\n"}}{{end}}'
        completed = subprocess.CompletedProcess([], 0, stdout="token-a\ntoken-b\n", stderr="")
        runner = mock.Mock(return_value=completed)
        module.run_remote(
            "railiance01",
            ["kubectl", "get", "secret", "example", "-o", template, "literal;$(false)", "a'b"],
            label="hostile argv",
            runner=runner,
        )
        command = runner.call_args.args[0]
        self.assertEqual(["ssh", "-o", "BatchMode=yes", "railiance01"], command[:4])
        self.assertIn("'go-template={{range $k, $_ := .data}}", command[4])
        self.assertIn("'literal;$(false)'", command[4])
        self.assertIn("'a'\"'\"'b'", command[4])

    def test_invalid_host_and_nul_fail_closed(self) -> None:
        with self.assertRaises(module.RemoteExecutionError):
            module.run_remote("bad host", ["true"], label="invalid")
        with self.assertRaises(module.RemoteExecutionError):
            module.remote_command(["bad\0value"])

    def test_error_does_not_include_remote_output(self) -> None:
        runner = mock.Mock(
            return_value=subprocess.CompletedProcess([], 9, stdout="sensitive", stderr="also-sensitive")
        )
        with self.assertRaisesRegex(module.RemoteExecutionError, "exit 9") as caught:
            module.run_remote("railiance01", ["false"], label="safe failure", runner=runner)
        self.assertNotIn("sensitive", str(caught.exception))


if __name__ == "__main__":
    unittest.main()
