"""User-perspective tests for ``mgf.common.typing``.

The four ``runtime_checkable`` Protocols. Consumers use these to
type-hint duck-typed inputs without importing concrete classes.
"""

from __future__ import annotations

from mgf.common.progress import CancellationToken, RecordingReporter
from mgf.common.typing import (
    CancellationProtocol,
    LogSinkProtocol,
    ReporterProtocol,
    VaultProtocol,
)
from mgf.common.vault import EnvVault


class TestProtocolsAreRuntimeCheckable:
    """The point of ``runtime_checkable`` — ``isinstance`` works
    on any duck-typed object."""

    def test_cancellation_protocol_matches_concrete(self) -> None:
        assert isinstance(CancellationToken(), CancellationProtocol)

    def test_reporter_protocol_matches_concrete(self) -> None:
        assert isinstance(RecordingReporter(), ReporterProtocol)

    def test_vault_protocol_matches_concrete(self) -> None:
        assert isinstance(EnvVault(prefix="X_"), VaultProtocol)

    def test_log_sink_protocol_matches_duck_type(self) -> None:
        """Any object with ``append_info`` + ``append_error`` matches."""

        class _MyLogSink:
            def append_info(self, message: str) -> None:
                pass

            def append_error(self, message: str) -> None:
                pass

        assert isinstance(_MyLogSink(), LogSinkProtocol)


class TestProtocolsRejectMismatches:
    """isinstance returns False for incomplete impls."""

    def test_partial_impl_not_a_match(self) -> None:
        class _PartialReporter:
            def report(self, message: str, current: int = 0, total: int = 0) -> None:
                pass

            # missing is_cancelled

        assert not isinstance(_PartialReporter(), ReporterProtocol)
