예제 #1
0
    def __init__(
        self,
        subject: Optional[str] = None,
        *,
        do_syntax_check_first: Optional[bool] = None,
        db_session: Optional[Session] = None,
        use_collection: Optional[bool] = None,
    ) -> None:
        self.collection_query_tool = CollectionQueryTool()

        if self.params is None:
            self.params = CheckerParamsBase()

        if self.status is None:
            self.status = CheckerStatusBase()

        if subject is not None:
            self.subject = subject

        if do_syntax_check_first is not None:
            self.do_syntax_check_first = do_syntax_check_first
        else:
            self.do_syntax_check_first = self.STD_DO_SYNTAX_CHECK_FIRST

        if use_collection is not None:
            self.use_collection = use_collection
        else:
            self.guess_and_set_use_collection()

        self.db_session = db_session
예제 #2
0
    def __post_init__(self) -> None:
        self.stdout_printer = StdoutPrinter()
        self.file_printer = FilePrinter()
        self.whois_dataset = get_whois_dataset_object(
            db_session=self.db_session)
        self.inactive_dataset = get_inactive_dataset_object(
            db_session=self.db_session)
        self.continue_dataset = get_continue_databaset_object(
            db_session=self.db_session)
        self.status_file_generator = StatusFileGenerator().guess_all_settings()
        self.counter = FilesystemCounter()
        self.collection_query_tool = CollectionQueryTool()

        self.header_already_printed = False

        return super().__post_init__()
예제 #3
0
    def test_setpreferred_status_origin_through_init(self) -> None:
        """
        Tests the overwritting of the preferred status origin through the class
        constructor.
        """

        given = "frequent"
        expected = given

        query_tool = CollectionQueryTool(preferred_status_origin=given)
        actual = query_tool.preferred_status_origin

        self.assertEqual(expected, actual)
예제 #4
0
    def test_set_url_base_through_init(self) -> None:
        """
        Tests the overwritting of the URL to work from through the class
        constructor.
        """

        given = "https://example.net"
        expected = given

        query_tool = CollectionQueryTool(url_base=given)
        actual = query_tool.url_base

        self.assertEqual(expected, actual)
예제 #5
0
    def test_set_token_through_init(self) -> None:
        """
        Tests the overwritting of the token to work through the class
        constructor.
        """

        given = secrets.token_urlsafe(6)
        expected = given

        query_tool = CollectionQueryTool(token=given)
        actual = query_tool.token

        self.assertEqual(expected, actual)
예제 #6
0
    def test_set_preferred_status_origin_through_init_none_given(self) -> None:
        """
        Tests the overwritting of the preferred status origin through the class
        constructor.

        In this test, we test the case that nothing is given.
        """

        given = None
        expected = "frequent"

        query_tool = CollectionQueryTool(preferred_status_origin=given)
        actual = query_tool.preferred_status_origin

        self.assertEqual(expected, actual)
예제 #7
0
    def test_set_url_base_through_init_none_given(self) -> None:
        """
        Tests the overwritting of the URL to work from through the class
        constructor.

        In this test, we test the case that the URL base is not given.
        """

        given = None
        expected = "http://localhost:8001"

        query_tool = CollectionQueryTool(url_base=given)
        actual = query_tool.url_base

        self.assertEqual(expected, actual)
예제 #8
0
    def test_set_token_through_init_environment_variable_given(self) -> None:
        """
        Tests the overwritting of the token to work through the class
        constructor.

        In this test we test the case that the environment variable is given.
        """

        given = secrets.token_urlsafe(6)
        expected = given

        os.environ["PYFUNCEBLE_COLLECTION_API_TOKEN"] = given

        query_tool = CollectionQueryTool(token=None)
        actual = query_tool.token

        self.assertEqual(expected, actual)
예제 #9
0
    def test_set_token_through_init_environment_variable_not_given(
            self) -> None:
        """
        Tests the overwritting of the token to work through the class
        constructor.

        In this test we test the case that nothing is given or declared.
        """

        if "PYFUNCEBLE_COLLECTION_API_TOKEN" in os.environ:
            del os.environ["PYFUNCEBLE_COLLECTION_API_TOKEN"]

        expected = ""

        query_tool = CollectionQueryTool(token=None)
        actual = query_tool.token

        self.assertEqual(expected, actual)
예제 #10
0
    def setUp(self) -> None:
        """
        Sets everything needed by the tests.
        """

        self.query_tool = CollectionQueryTool()

        self.response_dataset = {
            "subject": "example.net",
            "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
            "status": {
                "syntax": {
                    "latest": {
                        "status": "VALID",
                        "status_source": "SYNTAX",
                        "tested_at": "2021-09-28T19:32:07.167Z",
                        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                    },
                    "frequent": "VALID",
                },
                "availability": {
                    "latest": {
                        "status": "ACTIVE",
                        "status_source": "WHOIS",
                        "tested_at": "2021-09-28T19:32:07.167Z",
                        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                    },
                    "frequent": "ACTIVE",
                },
                "reputation": {
                    "latest": {
                        "status": "SANE",
                        "status_source": "REPUTATION",
                        "tested_at": "2021-09-28T19:32:07.167Z",
                        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                    },
                    "frequent": "SANE",
                },
                "whois": {
                    "expiration_date": "2021-09-28T19:32:07.167Z",
                    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                    "subject_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                },
            },
        }

        self.status_dataset = {
            "status": "ACTIVE",
            "status_source": "WHOIS",
            "tested_at": "2021-09-28T20:55:41.730Z",
            "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
            "subject_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        }

        self.availability_status_dataset = {
            "checker_type": "AVAILABILITY",
            "dns_lookup": {
                "NS": ["a.iana-servers.net.", "b.iana-servers.net."]
            },
            "dns_lookup_record": {
                "dns_name": "example.com.",
                "follow_nameserver_order": True,
                "nameserver": "9.9.9.9",
                "port": 53,
                "preferred_protocol": "UDP",
                "query_record_type": "NS",
                "query_timeout": 5.0,
                "response": ["a.iana-servers.net.", "b.iana-servers.net."],
                "subject": "example.com",
                "used_protocol": "UDP",
            },
            "domain_syntax": True,
            "expiration_date": None,
            "http_status_code": None,
            "idna_subject": "example.com",
            "ip_syntax": False,
            "ipv4_range_syntax": False,
            "ipv4_syntax": False,
            "ipv6_range_syntax": False,
            "ipv6_syntax": False,
            "netinfo": None,
            "params": {
                "do_syntax_check_first": False,
                "use_dns_lookup": True,
                "use_extra_rules": True,
                "use_http_code_lookup": True,
                "use_netinfo_lookup": True,
                "use_reputation_lookup": False,
                "use_whois_db": True,
                "use_whois_lookup": False,
            },
            "second_level_domain_syntax": True,
            "status": "ACTIVE",
            "status_after_extra_rules": None,
            "status_before_extra_rules": None,
            "status_source": "DNSLOOKUP",
            "status_source_after_extra_rules": None,
            "status_source_before_extra_rules": None,
            "subdomain_syntax": False,
            "subject": "example.com",
            "tested_at": datetime.fromisoformat("2021-03-09T17:42:15.771647"),
            "url_syntax": False,
            "whois_lookup_record": {
                "expiration_date": None,
                "port": 43,
                "query_timeout": 5.0,
                "record": None,
                "server": None,
                "subject": "example.com",
            },
            "whois_record": None,
        }
        return super().setUp()
예제 #11
0
class TestCollectionQueryTool(unittest.TestCase):
    """
    Tests the Collection query tool.
    """
    def setUp(self) -> None:
        """
        Sets everything needed by the tests.
        """

        self.query_tool = CollectionQueryTool()

        self.response_dataset = {
            "subject": "example.net",
            "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
            "status": {
                "syntax": {
                    "latest": {
                        "status": "VALID",
                        "status_source": "SYNTAX",
                        "tested_at": "2021-09-28T19:32:07.167Z",
                        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                    },
                    "frequent": "VALID",
                },
                "availability": {
                    "latest": {
                        "status": "ACTIVE",
                        "status_source": "WHOIS",
                        "tested_at": "2021-09-28T19:32:07.167Z",
                        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                    },
                    "frequent": "ACTIVE",
                },
                "reputation": {
                    "latest": {
                        "status": "SANE",
                        "status_source": "REPUTATION",
                        "tested_at": "2021-09-28T19:32:07.167Z",
                        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                    },
                    "frequent": "SANE",
                },
                "whois": {
                    "expiration_date": "2021-09-28T19:32:07.167Z",
                    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                    "subject_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                },
            },
        }

        self.status_dataset = {
            "status": "ACTIVE",
            "status_source": "WHOIS",
            "tested_at": "2021-09-28T20:55:41.730Z",
            "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
            "subject_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        }

        self.availability_status_dataset = {
            "checker_type": "AVAILABILITY",
            "dns_lookup": {
                "NS": ["a.iana-servers.net.", "b.iana-servers.net."]
            },
            "dns_lookup_record": {
                "dns_name": "example.com.",
                "follow_nameserver_order": True,
                "nameserver": "9.9.9.9",
                "port": 53,
                "preferred_protocol": "UDP",
                "query_record_type": "NS",
                "query_timeout": 5.0,
                "response": ["a.iana-servers.net.", "b.iana-servers.net."],
                "subject": "example.com",
                "used_protocol": "UDP",
            },
            "domain_syntax": True,
            "expiration_date": None,
            "http_status_code": None,
            "idna_subject": "example.com",
            "ip_syntax": False,
            "ipv4_range_syntax": False,
            "ipv4_syntax": False,
            "ipv6_range_syntax": False,
            "ipv6_syntax": False,
            "netinfo": None,
            "params": {
                "do_syntax_check_first": False,
                "use_dns_lookup": True,
                "use_extra_rules": True,
                "use_http_code_lookup": True,
                "use_netinfo_lookup": True,
                "use_reputation_lookup": False,
                "use_whois_db": True,
                "use_whois_lookup": False,
            },
            "second_level_domain_syntax": True,
            "status": "ACTIVE",
            "status_after_extra_rules": None,
            "status_before_extra_rules": None,
            "status_source": "DNSLOOKUP",
            "status_source_after_extra_rules": None,
            "status_source_before_extra_rules": None,
            "subdomain_syntax": False,
            "subject": "example.com",
            "tested_at": datetime.fromisoformat("2021-03-09T17:42:15.771647"),
            "url_syntax": False,
            "whois_lookup_record": {
                "expiration_date": None,
                "port": 43,
                "query_timeout": 5.0,
                "record": None,
                "server": None,
                "subject": "example.com",
            },
            "whois_record": None,
        }
        return super().setUp()

    def tearDown(self) -> None:
        """
        Destroys everything needed by the tests.
        """

        del self.query_tool
        del self.response_dataset
        del self.availability_status_dataset

    def test_set_token_return(self) -> None:
        """
        Tests the response from the method which let us set the token to work
        with.
        """

        given = secrets.token_urlsafe(6)

        actual = self.query_tool.set_token(given)

        self.assertIsInstance(actual, CollectionQueryTool)

    def test_set_token_method(self) -> None:
        """
        Tests the method which let us set the token to work with.
        """

        given = secrets.token_urlsafe(6)
        expected = given

        self.query_tool.set_token(given)
        actual = self.query_tool.token

        self.assertEqual(expected, actual)

    def test_set_token_attribute(self) -> None:
        """
        Tests the overwritting of the token attribute.
        """

        given = secrets.token_urlsafe(6)
        expected = given

        self.query_tool.token = given
        actual = self.query_tool.token

        self.assertEqual(expected, actual)

    def test_set_token_through_init(self) -> None:
        """
        Tests the overwritting of the token to work through the class
        constructor.
        """

        given = secrets.token_urlsafe(6)
        expected = given

        query_tool = CollectionQueryTool(token=given)
        actual = query_tool.token

        self.assertEqual(expected, actual)

    def test_set_token_through_init_environment_variable_not_given(
            self) -> None:
        """
        Tests the overwritting of the token to work through the class
        constructor.

        In this test we test the case that nothing is given or declared.
        """

        if "PYFUNCEBLE_COLLECTION_API_TOKEN" in os.environ:
            del os.environ["PYFUNCEBLE_COLLECTION_API_TOKEN"]

        expected = ""

        query_tool = CollectionQueryTool(token=None)
        actual = query_tool.token

        self.assertEqual(expected, actual)

    def test_set_token_through_init_environment_variable_given(self) -> None:
        """
        Tests the overwritting of the token to work through the class
        constructor.

        In this test we test the case that the environment variable is given.
        """

        given = secrets.token_urlsafe(6)
        expected = given

        os.environ["PYFUNCEBLE_COLLECTION_API_TOKEN"] = given

        query_tool = CollectionQueryTool(token=None)
        actual = query_tool.token

        self.assertEqual(expected, actual)

    def test_set_token_not_str(self) -> None:
        """
        Tests the method which let us set the token to work with for the case
        that the given token is not a :py:class:`str`.
        """

        given = ["Hello", "World!"]

        self.assertRaises(TypeError, lambda: self.query_tool.set_token(given))

    def test_set_url_base_return(self) -> None:
        """
        Tests the response from the method which let us set the URL to work
        from.
        """

        given = "https://example.org"

        actual = self.query_tool.set_url_base(given)

        self.assertIsInstance(actual, CollectionQueryTool)

    def test_set_url_base_method(self) -> None:
        """
        Tests the method which let us set the URL to work from.
        """

        given = "https://example.org"
        expected = given

        self.query_tool.set_url_base(given)
        actual = self.query_tool.url_base

        self.assertEqual(expected, actual)

    def test_set_url_base_attribute(self) -> None:
        """
        Tests the overwritting of the url_base attribute.
        """

        given = "https://example.org"
        expected = given

        self.query_tool.url_base = given
        actual = self.query_tool.url_base

        self.assertEqual(expected, actual)

    def test_set_url_base_through_init(self) -> None:
        """
        Tests the overwritting of the URL to work from through the class
        constructor.
        """

        given = "https://example.net"
        expected = given

        query_tool = CollectionQueryTool(url_base=given)
        actual = query_tool.url_base

        self.assertEqual(expected, actual)

    def test_set_url_base_through_init_none_given(self) -> None:
        """
        Tests the overwritting of the URL to work from through the class
        constructor.

        In this test, we test the case that the URL base is not given.
        """

        given = None
        expected = "http://*****:*****@unittest.mock.patch.object(requests.Session, "post")
    def test_collection_contain(self, request_mock) -> None:
        """
        Tests the method which let us pull the subject from the collection.
        """

        response_dict = self.response_dataset
        response_dict["subject"] = "example.com"

        def mocking(*args, **kwargs):  # pylint: disable=unused-argument
            response_content = json.dumps(response_dict)

            response = requests.models.Response()
            response.url = "https://example.org/v1/search"
            response.status_code = 200

            # pylint: disable=protected-access
            response._content = str.encode(response_content)

            response.history = [response]

            return response

        self.query_tool.url_base = "https://example.org"
        request_mock.side_effect = mocking

        expected = True
        actual = "example.com" in self.query_tool

        self.assertEqual(expected, actual)

    @unittest.mock.patch.object(requests.Session, "post")
    def test_collection_not_contain(self, request_mock) -> None:
        """
        Tests the method which let us pull the subject from the collection.
        """

        response_dict = {"detail": "Invalid subject."}

        def mocking(*args, **kwargs):  # pylint: disable=unused-argument
            response_content = json.dumps(response_dict)

            response = requests.models.Response()
            response.url = "https://example.org/v1/search"
            response.status_code = 404

            # pylint: disable=protected-access
            response._content = str.encode(response_content)

            response.history = [response]

            return response

        self.query_tool.url_base = "https://example.org"
        request_mock.side_effect = mocking

        expected = False
        actual = "example.com" in self.query_tool

        self.assertEqual(expected, actual)

    @unittest.mock.patch.object(requests.Session, "post")
    def test_getitem(self, request_mock) -> None:
        """
        Tests the method which let us pull the subject from the collection.
        """

        response_dict = self.response_dataset
        response_dict["subject"] = "example.org"

        def mocking(*args, **kwargs):  # pylint: disable=unused-argument
            response_content = json.dumps(response_dict)

            response = requests.models.Response()
            response.url = "https://example.org/v1/search"
            response.status_code = 200

            # pylint: disable=protected-access
            response._content = str.encode(response_content)

            response.history = [response]

            return response

        self.query_tool.url_base = "https://example.org"
        request_mock.side_effect = mocking

        expected = response_dict
        actual = self.query_tool["example.org"]

        self.assertEqual(expected, actual)

    @unittest.mock.patch.object(requests.Session, "post")
    def test_getitem_not_found(self, request_mock) -> None:
        """
        Tests the method which let us pull the subject from the collection.
        """

        response_dict = {"detail": "Invalid subject."}

        def mocking(*args, **kwargs):  # pylint: disable=unused-argument
            response_content = json.dumps(response_dict)

            response = requests.models.Response()
            response.url = "https://example.org/v1/search"
            response.status_code = 404

            # pylint: disable=protected-access
            response._content = str.encode(response_content)

            response.history = [response]

            return response

        self.query_tool.url_base = "https://example.org"
        request_mock.side_effect = mocking

        expected = None
        actual = self.query_tool["example.de"]

        self.assertEqual(expected, actual)

    @unittest.mock.patch.object(requests.Session, "post")
    def test_pull(self, request_mock) -> None:
        """
        Tests the method which let us pull the subject from the collection.
        """

        response_dict = self.response_dataset
        response_dict["subject"] = "example.net"

        def mocking(*args, **kwargs):  # pylint: disable=unused-argument
            response_content = json.dumps(response_dict)

            response = requests.models.Response()
            response.url = "https://example.org/v1/search"
            response.status_code = 200

            # pylint: disable=protected-access
            response._content = str.encode(response_content)

            response.history = [response]

            return response

        self.query_tool.url_base = "https://example.org"
        request_mock.side_effect = mocking

        expected = response_dict
        actual = self.query_tool.pull("example.net")

        self.assertEqual(expected, actual)

    @unittest.mock.patch.object(requests.Session, "post")
    def test_pull_subject_not_found(self, request_mock) -> None:
        """
        Tests the method which let us pull the subject from the collection.

        In this test case we check what happens when a subject is not found.
        """

        response_dict = {"detail": "Invalid subject."}

        def mocking(*args, **kwargs):  # pylint: disable=unused-argument
            response_content = json.dumps(response_dict)

            response = requests.models.Response()
            response.url = "https://example.org/v1/search"
            response.status_code = 404

            # pylint: disable=protected-access
            response._content = str.encode(response_content)

            response.history = [response]

            return response

        self.query_tool.url_base = "https://example.org"
        request_mock.side_effect = mocking

        expected = None
        actual = self.query_tool.pull("example.net")

        self.assertEqual(expected, actual)

    @unittest.mock.patch.object(requests.Session, "post")
    def test_pull_subject_no_json_response(self, request_mock) -> None:
        """
        Tests the method which let us pull the subject from the collection.

        In this test case we check what happens when no JSON response is given.
        """
        def mocking(*args, **kwargs):  # pylint: disable=unused-argument
            response_content = "I'm a teapot."

            response = requests.models.Response()
            response.url = "https://example.org/v1/search"
            response.status_code = 418

            # pylint: disable=protected-access
            response._content = str.encode(response_content)

            response.history = [response]

            return response

        self.query_tool.url_base = "https://example.org"
        request_mock.side_effect = mocking

        expected = None
        actual = self.query_tool.pull("example.net")

        self.assertEqual(expected, actual)

    def test_pull_subject_not_str(self) -> None:
        """
        Tests the method which let us pull the subject from the collection.

        In this test we test the case that the given subject is not a
        :py:class:`str`.
        """

        self.query_tool.url_base = "https://example.org"

        self.assertRaises(TypeError, lambda: self.query_tool.pull(284))

    @unittest.mock.patch.object(requests.Session, "post")
    def test_push(self, request_mock) -> None:
        """
        Tests the method which let us push some dataset into the collection.
        """

        response_dict = self.response_dataset
        response_dict["subject"] = "example.net"

        def mocking(*args, **kwargs):  # pylint: disable=unused-argument
            response_content = json.dumps(response_dict)

            response = requests.models.Response()
            response.url = "https://example.org/v1/status/availability"
            response.status_code = 200

            # pylint: disable=protected-access
            response._content = str.encode(response_content)

            response.history = [response]

            return response

        self.query_tool.url_base = "https://example.org"
        self.query_tool.token = secrets.token_urlsafe(6)

        request_mock.side_effect = mocking

        expected = response_dict
        actual = self.query_tool.push(
            AvailabilityCheckerStatus(**self.availability_status_dataset))

        self.assertEqual(expected, actual)

    @unittest.mock.patch.object(requests.Session, "post")
    def test_push_no_json_response(self, request_mock) -> None:
        """
        Tests the method which let us push some dataset into the collection.

        In this test case, we test the case that the response is not JSON
        encoded.
        """

        response_dict = self.response_dataset
        response_dict["subject"] = "example.net"

        def mocking(*args, **kwargs):  # pylint: disable=unused-argument
            response_content = "I'm a teapot."

            response = requests.models.Response()
            response.url = "https://example.org/v1/status/availability"
            response.status_code = 418

            # pylint: disable=protected-access
            response._content = str.encode(response_content)

            response.history = [response]

            return response

        self.query_tool.url_base = "https://example.org"
        request_mock.side_effect = mocking

        expected = None
        actual = self.query_tool.push(
            AvailabilityCheckerStatus(**self.availability_status_dataset))

        self.assertEqual(expected, actual)

    @unittest.mock.patch.object(requests.Session, "post")
    def test_push_with_whois(self, request_mock) -> None:
        """
        Tests the method which let us push some dataset into the collection.
        """

        response_dict = self.response_dataset
        response_dict["subject"] = "example.net"
        self.availability_status_dataset["expiration_date"] = "23-nov-2090"

        def mocking(*args, **kwargs):  # pylint: disable=unused-argument
            response_content = json.dumps(response_dict)

            response = requests.models.Response()
            response.url = "https://example.org/v1/status/availability"
            response.status_code = 200

            # pylint: disable=protected-access
            response._content = str.encode(response_content)

            response.history = [response]

            return response

        self.query_tool.url_base = "https://example.org"
        self.query_tool.token = secrets.token_urlsafe(6)
        request_mock.side_effect = mocking

        expected = response_dict
        actual = self.query_tool.push(
            AvailabilityCheckerStatus(**self.availability_status_dataset))

        self.assertEqual(expected, actual)

    @unittest.mock.patch.object(requests.Session, "post")
    def test_push_with_whois_no_json_response(self, request_mock) -> None:
        """
        Tests the method which let us push some dataset into the collection.
        """

        response_dict = self.response_dataset
        response_dict["subject"] = "example.net"
        self.availability_status_dataset["expiration_date"] = "23-nov-2090"

        def mocking(*args, **kwargs):  # pylint: disable=unused-argument
            response_content = "I'm a teapot."

            response = requests.models.Response()
            response.url = "https://example.org/v1/status/availability"
            response.status_code = 418

            # pylint: disable=protected-access
            response._content = str.encode(response_content)

            response.history = [response]

            return response

        self.query_tool.url_base = "https://example.org"
        self.query_tool.token = secrets.token_urlsafe(6)
        request_mock.side_effect = mocking

        expected = None
        actual = self.query_tool.push(
            AvailabilityCheckerStatus(**self.availability_status_dataset))

        self.assertEqual(expected, actual)

    def test_push_with_whois_token_not_given(self) -> None:
        """
        Tests the method which let us push some dataset into the collection.

        In this test, we test the case that no token is given.
        """

        response_dict = self.response_dataset
        response_dict["subject"] = "example.net"
        self.availability_status_dataset["expiration_date"] = "23-nov-2090"

        if "PYFUNCEBLE_COLLECTION_API_TOKEN" in os.environ:
            del os.environ["PYFUNCEBLE_COLLECTION_API_TOKEN"]

        self.query_tool.token = ""

        expected = None
        actual = self.query_tool.push(
            AvailabilityCheckerStatus(**self.availability_status_dataset))

        self.assertEqual(expected, actual)

    def test_push_subject_not_str(self) -> None:
        """
        Tests the method which let us push some dataset into the collection.

        In this test, we test the case that the given subject is not a string.
        """

        self.availability_status_dataset["subject"] = 293

        self.assertRaises(
            TypeError,
            lambda: self.query_tool.push(
                AvailabilityCheckerStatus(**self.availability_status_dataset)),
        )

    def test_push_checker_status_not_correct(self) -> None:
        """
        Tests the method which let us push some dataset into the collection.

        In this test, we test the case that the given checker status is not
        correct.
        """

        self.availability_status_dataset["subject"] = "foo.example.org"

        self.assertRaises(
            TypeError,
            lambda: self.query_tool.push(self.availability_status_dataset),
        )

    def test_push_subject_empty_str(self) -> None:
        """
        Tests the method which let us push some dataset into the collection.

        In this test, we test the case that the given subject is an empty string.
        """

        self.availability_status_dataset["subject"] = ""

        self.assertRaises(
            ValueError,
            lambda: self.query_tool.push(
                AvailabilityCheckerStatus(**self.availability_status_dataset)),
        )

    def test_push_checker_type_not_str(self) -> None:
        """
        Tests the method which let us push some dataset into the collection.

        In this test, we test the case that the given subject is not a string.
        """

        self.availability_status_dataset["checker_type"] = 987

        self.assertRaises(
            TypeError,
            lambda: self.query_tool.push(
                AvailabilityCheckerStatus(**self.availability_status_dataset)),
        )

    def test_push_checker_type_not_supported(self) -> None:
        """
        Tests the method which let us push some dataset into the collection.

        In this test, we test the case that the given subject is not a string.
        """

        self.availability_status_dataset["checker_type"] = "GIT"
        self.query_tool.token = secrets.token_urlsafe(6)

        self.assertRaises(
            ValueError,
            lambda: self.query_tool.push(
                AvailabilityCheckerStatus(**self.availability_status_dataset)),
        )

    def test_push_token_not_given(self) -> None:
        """
        Tests the method which let us push some dataset into the collection.

        In this test, we test the case that no token is given.
        """

        if "PYFUNCEBLE_COLLECTION_API_TOKEN" in os.environ:
            del os.environ["PYFUNCEBLE_COLLECTION_API_TOKEN"]

        self.query_tool.token = ""

        expected = None
        actual = self.query_tool.push(
            AvailabilityCheckerStatus(**self.availability_status_dataset))

        self.assertEqual(expected, actual)
예제 #12
0
class ProducerWorker(WorkerBase):
    """
    Provides our producer worker. The objective of this worker is to provides
    a single worker (or process if you prefer) which will be used to handle
    the production of output to stdout or files.
    """

    STD_NAME: str = "pyfunceble_producer_worker"

    stdout_printer: Optional[StdoutPrinter] = None
    file_printer: Optional[FilePrinter] = None
    whois_dataset: Optional[WhoisDatasetBase] = None
    inactive_dataset: Optional[InactiveDatasetBase] = None
    continue_dataset: Optional[ContinueDatasetBase] = None
    status_file_generator: Optional[StatusFileGenerator] = None
    counter: Optional[FilesystemCounter] = None
    collection_query_tool: Optional[CollectionQueryTool] = None

    header_already_printed: Optional[bool] = None

    INACTIVE_STATUSES: Tuple[str, ...] = (
        PyFunceble.storage.STATUS.down,
        PyFunceble.storage.STATUS.invalid,
    )

    def __post_init__(self) -> None:
        self.stdout_printer = StdoutPrinter()
        self.file_printer = FilePrinter()
        self.whois_dataset = get_whois_dataset_object(
            db_session=self.db_session)
        self.inactive_dataset = get_inactive_dataset_object(
            db_session=self.db_session)
        self.continue_dataset = get_continue_databaset_object(
            db_session=self.db_session)
        self.status_file_generator = StatusFileGenerator().guess_all_settings()
        self.counter = FilesystemCounter()
        self.collection_query_tool = CollectionQueryTool()

        self.header_already_printed = False

        return super().__post_init__()

    @staticmethod
    def should_we_ignore(test_result: CheckerStatusBase) -> bool:
        """
        Checks if we should ignore the given datasets.

        :param test_result:
            The test result to check.
        """

        return isinstance(test_result,
                          str) and test_result.startswith("ignored_")

    @staticmethod
    def should_we_print_status_to_stdout(status: str) -> bool:
        """
        Checks if we are allows to print the given status (to stdout).

        :param status:
            The status to check.
        """

        if isinstance(
                PyFunceble.storage.CONFIGURATION.cli_testing.display_mode.
                status, list):
            to_keep = PyFunceble.storage.CONFIGURATION.cli_testing.display_mode.status
        else:
            to_keep = [
                PyFunceble.storage.CONFIGURATION.cli_testing.display_mode.
                status
            ]

        to_keep = [x.upper() for x in to_keep]
        status = status.upper()

        return "ALL" in to_keep or status in to_keep

    def should_we_block_status_file_printer(
            self, test_dataset: dict, test_result: CheckerStatusBase) -> bool:
        """
        Checks if we should block the file printer.

        The reason behindn this is that we don't want to generate an output
        when a subject was already into the inactive database.
        """

        return ("from_inactive" in test_dataset
                and test_result.status in self.INACTIVE_STATUSES)

    def run_stdout_printer(self, test_result: CheckerStatusBase) -> None:
        """
        Runs the stdout printer (if necessary).

        :param test_result:
            The rest result dataset.
        """

        if not PyFunceble.storage.CONFIGURATION.cli_testing.display_mode.quiet:
            # pylint: disable=line-too-long

            if self.should_we_print_status_to_stdout(test_result.status):
                self.stdout_printer.template_to_use = get_template_to_use()

                if not self.header_already_printed:
                    self.stdout_printer.print_header()
                    self.header_already_printed = True

                self.stdout_printer.set_dataset(
                    test_result.to_dict()).print_interpolated_line()
            else:
                print_single_line()
        else:
            print_single_line()

    def run_whois_backup(self, test_result: CheckerStatusBase) -> None:
        """
        Runs the backup or update of the WHOIS record in our dataset storage.

        :param test_result:
            The test result.
        """

        if (hasattr(test_result, "expiration_date")
                and test_result.expiration_date and test_result.whois_record):
            # Note: The whois record is always given if the status does not come
            # from the database.

            self.whois_dataset.update({
                "subject":
                test_result.subject,
                "idna_subject":
                test_result.idna_subject,
                "expiration_date":
                test_result.expiration_date,
                "epoch":
                str(
                    datetime.datetime.strptime(test_result.expiration_date,
                                               "%d-%b-%Y").timestamp()),
            })

    def run_inactive_backup(self, test_dataset: dict,
                            test_result: CheckerStatusBase) -> None:
        """
        Runs the backup or update of the Inactive dataset storage.

        The idea is that if the status is OK (active), we just remove it from
        the dataset storage. Otherwise, we just keep it in there :-)
        """

        if test_dataset["type"] != "single":
            dataset = test_result.to_dict()
            dataset.update(test_dataset)

            PyFunceble.facility.Logger.debug("Test Dataset: %r", test_dataset)
            PyFunceble.facility.Logger.debug("Test Result: %r", test_result)

            if "from_inactive" in dataset:
                if test_result.status in self.INACTIVE_STATUSES:
                    if dataset["destination"]:
                        # Note: This handles the case that someone try to test a
                        # single subject.
                        # In fact, we should not generate any file if the output
                        # directory is not given.
                        self.inactive_dataset.update(dataset)
                else:
                    self.inactive_dataset.remove(dataset)
            elif (test_result.status in self.INACTIVE_STATUSES
                  and dataset["destination"]):
                self.inactive_dataset.update(dataset)

    def run_continue_backup(self, test_dataset: dict,
                            test_result: CheckerStatusBase) -> None:
        """
        Runs the backup or update of the auto-continue dataset storage.
        """

        if test_dataset["type"] != "single":
            dataset = test_result.to_dict()
            dataset.update(test_dataset)

            if (isinstance(self.continue_dataset, CSVContinueDataset)
                    and dataset["output_dir"]):
                # Note: This handles the case that someone try to test a single subject.
                # In fact, we should not generate any file if the output directory
                # is not given.
                #
                # The exception handler is just there to catch the case that the
                # method is not implemented because we are more likely using an
                # alternative backup (for example mariadb).

                self.continue_dataset.set_base_directory(dataset["output_dir"])
            self.continue_dataset.update(dataset)

    def run_ignored_file_printer(self, test_dataset: dict,
                                 test_result: str) -> None:
        """
        Runs the analytic behind the file printer.

        .. warning::
            Thie method assume that the givne dataset is ignored from the normal
            file printer.
        """

        if (test_result == "ignored_inactive" and test_dataset["destination"]
                and not PyFunceble.storage.CONFIGURATION.cli_testing.
                file_generation.no_file):
            if "idna_subject" not in test_dataset:
                # The printer always prints the idna subject.
                test_dataset["idna_subject"] = domain2idna(
                    test_dataset["subject"])

            # We use this object just to get the output directory :-)
            self.status_file_generator.set_parent_dirname(
                test_dataset["destination"])

            # We now generate the file which let the end-user know that
            # we ignored the test of the current test dataset because there
            # is no need to retest it yet.
            # pylint: disable=line-too-long
            self.file_printer.destination = os.path.join(
                self.status_file_generator.get_output_basedir(),
                PyFunceble.cli.storage.OUTPUTS.logs.directories.parent,
                PyFunceble.cli.storage.OUTPUTS.logs.filenames.
                inactive_not_retested,
            )

            self.file_printer.template_to_use = "plain"
            self.file_printer.dataset = test_dataset
            self.file_printer.print_interpolated_line()

    def run_status_file_printer(self, test_dataset: dict,
                                test_result: CheckerStatusBase) -> None:
        """
        Runs the status file printer.
        """

        if (test_dataset["destination"] and not PyFunceble.storage.
                CONFIGURATION.cli_testing.file_generation.no_file):
            previous_allow_hosts_file = self.status_file_generator.allow_hosts_files
            previous_allow_plain_file = self.status_file_generator.allow_plain_files

            self.status_file_generator.parent_dirname = test_dataset[
                "destination"]
            self.status_file_generator.status = test_result
            self.status_file_generator.test_dataset = test_dataset

            if test_dataset["subject_type"] == "url":
                self.status_file_generator.allow_hosts_files = False
                self.status_file_generator.allow_plain_files = True

            self.status_file_generator.start()
            self.status_file_generator.allow_hosts_files = previous_allow_hosts_file
            self.status_file_generator.allow_plain_files = previous_allow_plain_file

    def run_counter(self, test_dataset: dict,
                    test_result: CheckerStatusBase) -> None:
        """
        Runs the counter of the current file.
        """

        if (test_dataset["destination"] and not PyFunceble.storage.
                CONFIGURATION.cli_testing.file_generation.no_file):
            # Note: We don't want hiden data to be counted.

            self.counter.set_parent_dirname(
                test_dataset["destination"]).count(test_result)

    def target(self, consumed: Any) -> Optional[Tuple[Any, ...]]:
        if not isinstance(consumed, tuple):
            PyFunceble.facility.Logger.info(
                "Skipping latest dataset because consumed data was not a tuple."
            )
            return None

        # Just for human brain.
        test_dataset, test_result = consumed

        if not isinstance(test_dataset, dict):
            PyFunceble.facility.Logger.info(
                "Skipping because test dataset is not a dictionnary.")
            return None

        if self.should_we_ignore(test_result):
            PyFunceble.facility.Logger.info(
                "Ignored test dataset. Reason: No output wanted.")
            self.run_ignored_file_printer(test_dataset, test_result)
            return None

        if not isinstance(test_result, CheckerStatusBase):
            PyFunceble.facility.Logger.info(
                "Skipping latest dataset because consumed status is not "
                "a status object..")
            return None

        if (PyFunceble.storage.CONFIGURATION.collection.push
                and test_result.status_source != "COLLECTION"):
            self.collection_query_tool.push(test_result)

        self.run_whois_backup(test_result)
        self.run_inactive_backup(test_dataset, test_result)
        self.run_continue_backup(test_dataset, test_result)

        if not self.should_we_block_status_file_printer(
                test_dataset, test_result):
            self.run_status_file_printer(test_dataset, test_result)
            self.run_counter(test_dataset, test_result)

        self.run_stdout_printer(test_result)
        test_dataset["from_producer"] = True

        return test_dataset, test_result