"""Offline tests for check_email_auth.py: made-up DNS answers, no network. Run: python -m unittest test_check_email_auth -v (from this folder)."""
import unittest
from unittest import mock

import check_email_auth as C


def run(fn, domain, records):
    """records: {(name, type): [answers]}; unknown names answer with nothing."""
    def fake_query(name, rtype):
        return 0, records.get((name, rtype), [])
    out = []
    with mock.patch.object(C, "query", fake_query):
        fn(domain, out)
    return out


def levels(out, what):
    return [lv for lv, w, _ in out if w == what]


class Spf(unittest.TestCase):
    def test_missing(self):
        self.assertEqual(levels(run(C.spf, "a.com", {}), "SPF"), ["FAIL"])

    def test_ok_softfail(self):
        o = run(C.spf, "a.com", {("a.com", "TXT"): ['"v=spf1 include:_spf.google.com ~all"']})
        self.assertEqual(levels(o, "SPF"), ["PASS"])

    def test_ok_hardfail(self):
        self.assertEqual(levels(run(C.spf, "a.com", {("a.com", "TXT"): ['"v=spf1 -all"']}), "SPF"), ["PASS"])

    def test_plus_all_fails(self):
        self.assertEqual(levels(run(C.spf, "a.com", {("a.com", "TXT"): ['"v=spf1 +all"']}), "SPF"), ["FAIL"])

    def test_two_records_fail(self):
        o = run(C.spf, "a.com", {("a.com", "TXT"): ['"v=spf1 -all"', '"v=spf1 ~all"']})
        self.assertIn("FAIL", levels(o, "SPF"))

    def test_no_all_warns(self):
        self.assertEqual(levels(run(C.spf, "a.com", {("a.com", "TXT"): ['"v=spf1 include:x.com"']}), "SPF"), ["WARN"])

    def test_too_many_lookups_warns(self):
        rec = '"v=spf1 ' + " ".join(f"include:s{i}.com" for i in range(11)) + ' ~all"'
        self.assertIn("WARN", levels(run(C.spf, "a.com", {("a.com", "TXT"): [rec]}), "SPF"))

    def test_other_txt_ignored(self):
        o = run(C.spf, "a.com", {("a.com", "TXT"): ['"google-site-verification=abc"']})
        self.assertEqual(levels(o, "SPF"), ["FAIL"])

    def test_split_strings_are_joined(self):
        o = run(C.spf, "a.com", {("a.com", "TXT"): ['"v=spf1 include:a.com " "~all"']})
        self.assertEqual(levels(o, "SPF"), ["PASS"])


class Dmarc(unittest.TestCase):
    def rec(self, text):
        return {("_dmarc.a.com", "TXT"): ['"' + text + '"']}

    def test_missing(self):
        self.assertEqual(levels(run(C.dmarc, "a.com", {}), "DMARC"), ["FAIL"])

    def test_reject_with_reports(self):
        self.assertEqual(levels(run(C.dmarc, "a.com", self.rec("v=DMARC1; p=reject; rua=mailto:r@a.com")), "DMARC"), ["PASS"])

    def test_quarantine_passes(self):
        self.assertEqual(levels(run(C.dmarc, "a.com", self.rec("v=DMARC1; p=quarantine; rua=mailto:r@a.com")), "DMARC"), ["PASS"])

    def test_none_warns(self):
        self.assertEqual(levels(run(C.dmarc, "a.com", self.rec("v=DMARC1; p=none; rua=mailto:r@a.com")), "DMARC"), ["WARN"])

    def test_no_rua_adds_warning(self):
        self.assertIn("WARN", levels(run(C.dmarc, "a.com", self.rec("v=DMARC1; p=reject")), "DMARC"))

    def test_missing_policy_fails(self):
        self.assertIn("FAIL", levels(run(C.dmarc, "a.com", self.rec("v=DMARC1; rua=mailto:r@a.com")), "DMARC"))


class Dkim(unittest.TestCase):
    def test_none_found_warns_not_fails(self):
        self.assertEqual(levels(run(C.dkim, "a.com", {}), "DKIM"), ["WARN"])

    def test_google_selector_found(self):
        o = run(C.dkim, "a.com", {("google._domainkey.a.com", "TXT"): ['"v=DKIM1; k=rsa; p=abc"']})
        self.assertEqual(levels(o, "DKIM"), ["PASS"])

    def test_cname_selector_found(self):
        o = run(C.dkim, "a.com", {("selector1._domainkey.a.com", "CNAME"): ["selector1-a-com._domainkey.x.onmicrosoft.com."]})
        self.assertEqual(levels(o, "DKIM"), ["PASS"])


class Main(unittest.TestCase):
    def test_exit_code_fail_on_missing_records(self):
        with mock.patch.object(C, "query", lambda n, t: (0, [])):
            self.assertEqual(C.main(["a.com"]), 1)

    def test_exit_code_zero_when_good(self):
        recs = {("a.com", "TXT"): ['"v=spf1 -all"'], ("_dmarc.a.com", "TXT"): ['"v=DMARC1; p=reject; rua=mailto:r@a.com"']}
        with mock.patch.object(C, "query", lambda n, t: (0, recs.get((n, t), []))):
            self.assertEqual(C.main(["a.com"]), 0)

    def test_network_error_is_reported_not_guessed(self):
        def boom(n, t):
            raise OSError("no network")
        with mock.patch.object(C, "query", boom):
            self.assertEqual(C.main(["a.com"]), 2)  # nothing was verified: not reported as clean


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