"""Native entrypoints preserve JSON callers while emitting only fixed public failure fields."""
import contextlib
import io
import json
import unittest
from unittest.mock import patch

import native_bundle
import native_pods
from native_prepare_evidence import report
from native_project_graph import Refused


class EvidenceTests(unittest.TestCase):
    def test_both_entrypoints_emit_failures_without_copying_private_diagnostic_text(self):
        for module, operation, argv, phase in [
            (native_bundle, "run", ["--", "true"], "bundle"),
            (native_pods, "prepare", ["."], "pods"),
        ]:
            error = Refused("native_configuration_failed")
            error.diagnostic = 'PRIVATE_SENTINEL could not find compatible versions for pod "Fixture"'
            out, err = io.StringIO(), io.StringIO()
            with self.subTest(phase=phase), patch.object(module, operation, side_effect=error):
                with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err):
                    code = module.main(argv)
                self.assertEqual(code, 1)
                self.assertFalse(json.loads(out.getvalue())["ok"])
                self.assertNotIn("PRIVATE_SENTINEL", err.getvalue())
                receipt = json.loads(err.getvalue().strip().split("::", 2)[2])
                self.assertEqual(receipt["phase"], phase)
                self.assertEqual(receipt["signals"], ["pod_version_conflict"])

    def test_a_dropped_handshake_is_named_and_is_not_a_certificate_verdict(self):
        for diagnostic, expected in [
            ("fatal: unable to access '[URL redacted]': LibreSSL SSL_connect: "
             "SSL_ERROR_SYSCALL in connection to github.com:443", ["tls_handshake_reset"]),
            ("Bundler::HTTPError Could not fetch specs due to underlying error "
             "<SSL_connect returned=1 errno=0 state=error: unexpected eof while reading>",
             ["tls_handshake_reset"]),
            ("SSL_connect returned=1 errno=0 state=error: certificate verify failed",
             ["tls_verification"]),
        ]:
            err = io.StringIO()
            with self.subTest(diagnostic=diagnostic[:40]), contextlib.redirect_stderr(err):
                report({"ok": False, "code": "native_pods_install_failed", "diagnostic": diagnostic}, "pods")
            self.assertEqual(json.loads(err.getvalue().strip().split("::", 2)[2])["signals"], expected)

    def test_a_failed_child_command_is_not_reported_as_a_ruby_bundle_fault(self):
        self.assertEqual(native_bundle.remediation("native_gemfile_lock_commit_required"),
                         native_bundle.BUNDLE_REMEDIATION)
        self.assertEqual(native_bundle.remediation("native_bundle_install_failed"),
                         native_bundle.BUNDLE_REMEDIATION)
        for code in ("native_configuration_failed", "native_pods_operation_interrupted",
                     "native_bundle_operation_unverified"):
            with self.subTest(code=code):
                self.assertEqual(native_bundle.remediation(code), native_bundle.COMMAND_REMEDIATION)
                self.assertNotIn("Gemfile", native_bundle.remediation(code))

    def test_success_is_silent_and_unknown_failures_cannot_inject_annotations(self):
        err = io.StringIO()
        with contextlib.redirect_stderr(err):
            report({"ok": True}, "bundle")
        self.assertEqual(err.getvalue(), "")
        with contextlib.redirect_stderr(err):
            report({"ok": False, "code": "PRIVATE_SENTINEL\n::error::injected",
                    "diagnostic": "PRIVATE_SENTINEL"}, "PRIVATE_SENTINEL")
        self.assertNotIn("PRIVATE_SENTINEL", err.getvalue())
        receipt = json.loads(err.getvalue().strip().split("::", 2)[2])
        self.assertEqual(receipt["code"], "native_prepare_unclassified")
        self.assertEqual(receipt["signals"], [])

    def test_a_lockfile_mention_alone_does_not_claim_a_deployment_lock_conflict(self):
        err = io.StringIO()
        with contextlib.redirect_stderr(err):
            report({"ok": False, "code": "native_configuration_failed", "diagnostic": "Using Podfile.lock"}, "bundle")
        self.assertEqual(json.loads(err.getvalue().strip().split("::", 2)[2])["signals"], [])
