"""Tests for the ``ProcessError`` taxonomy.

Pinning: subclass relationships (every leaf inherits from
``ProcessError`` AND from ``OperationError`` so existing CLI top-
level handlers map them to non-zero exit codes per EH-04); each
leaf carries the documented attributes.
"""

from __future__ import annotations

import pytest

from mgf.common.exceptions import (
    AppError,
    OperationError,
    ProcessError,
    ProcessNonZeroExitError,
    ProcessSignaledError,
    ProcessTimeoutError,
    ServiceNotReadyError,
)


class TestProcessErrorTaxonomy:
    @pytest.mark.parametrize(
        "leaf",
        [ProcessTimeoutError, ProcessNonZeroExitError, ProcessSignaledError, ServiceNotReadyError],
    )
    def test_every_leaf_is_processerror(self, leaf: type[ProcessError]) -> None:
        assert issubclass(leaf, ProcessError)

    @pytest.mark.parametrize(
        "leaf",
        [ProcessTimeoutError, ProcessNonZeroExitError, ProcessSignaledError, ServiceNotReadyError],
    )
    def test_every_leaf_is_operationerror(self, leaf: type[ProcessError]) -> None:
        # EH-04: existing top-level handlers that map OperationError
        # → non-zero exit catch ProcessError-shaped failures unchanged.
        assert issubclass(leaf, OperationError)

    def test_processerror_is_apperror(self) -> None:
        assert issubclass(ProcessError, AppError)


class TestProcessErrorAttributes:
    def test_timeout_carries_command_id_and_timeout(self) -> None:
        exc = ProcessTimeoutError(
            command=["sleep", "10"],
            command_id="ab12cd34",
            timeout=2.5,
        )
        assert exc.command == ["sleep", "10"]
        assert exc.command_id == "ab12cd34"
        assert exc.timeout == 2.5
        assert "2.5s" in str(exc)
        assert "ab12cd34" in str(exc)

    def test_nonzero_carries_returncode_and_tail(self) -> None:
        exc = ProcessNonZeroExitError(
            command=["false"],
            command_id="ab12cd34",
            returncode=42,
            stderr_tail="something went wrong",
        )
        assert exc.returncode == 42
        assert exc.stderr_tail == "something went wrong"
        assert "42" in str(exc)
        assert "something went wrong" in str(exc)

    def test_signaled_carries_signal(self) -> None:
        import signal as _signal

        exc = ProcessSignaledError(
            command=["sleep", "10"],
            command_id="ab12cd34",
            signal=_signal.SIGTERM,
        )
        assert exc.signal == _signal.SIGTERM
        assert "signal" in str(exc).lower()

    def test_service_not_ready_carries_probe_description(self) -> None:
        exc = ServiceNotReadyError(
            command=["postgres"],
            command_id="ab12cd34",
            probe_description="port localhost:5432",
            timeout=10.0,
            last_error="Connection refused",
        )
        assert exc.probe_description == "port localhost:5432"
        assert exc.timeout == 10.0
        assert exc.last_error == "Connection refused"
        assert "port localhost:5432" in str(exc)
        assert "Connection refused" in str(exc)
