# Test suite for easy_hmac
from base64 import b64encode
from datetime import datetime
from easy_hmac import core, exceptions
import hashlib
import hmac
import unittest


class TestEasyHmac(unittest.TestCase):
    """
    Houses the tests for the easy_hmac package
    """

    def compute_dummy_request(self, token, body, a_timestamp, application_id):
        """
        Computes a dummy request for testing purposes
        """

        method = "GET"
        path = f"/api/v1/applications/{application_id}/bundle"
        timestamp = a_timestamp
        contentType = "application/json"

        hash = hashlib.md5(body.encode())
        contentMD5 = b64encode(hash.digest()).decode("utf-8")
        message_parts = [method, contentMD5, contentType, timestamp, path]
        message = "\n".join(message_parts)

        signature = hmac.new(
            bytes(token["secret"], "latin-1"),
            bytes(message, "latin-1"),
            digestmod=hashlib.sha256,
        )
        hmac_base64 = b64encode(signature.digest()).decode("utf-8")

        headers = {
            "Date": timestamp,
            "Content-MD5": contentMD5,
            "Content-Type": contentType,
            "Authorization": "HMAC {}:{}".format(token["identifier"], hmac_base64),
        }

        return {"method": method, "body": body, "path": path, "headers": headers}

    def test_verify_hmac(self):
        """
        Tests if verify_hmac actually verifies the HMAC generated by a dummy request
        """

        token = {
            "name": "adriano",
            "secret": "6ff2dc141c0841e2a43c25be9ae9b097",
            "identifier": "2e42a19593f047e080285e49864b0fb6",
        }

        timestamp = datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S GMT")
        request = TestEasyHmac.compute_dummy_request(token, "", timestamp, 1234)
        body = request["body"]
        headers = request["headers"]
        path = request["path"]
        method = request["method"]

        # preparing test_request parameters
        auth_header = headers["Authorization"]
        auth_method, auth_string = auth_header.split(" ")
        token_identifier, hmac_base64 = auth_string.split(":")

        actual = core.verify_hmac(
            token["secret"],
            hmac_base64,
            headers["Content-MD5"],
            body.encode(),
            headers["Date"],
            headers["Content-Type"],
            path,
            method,
        )

        self.assertTrue(actual)

    def test_verify_hmac_wrong_secret(self):
        """
        Tests if verify_hmac raises exception if secret is wrong
        """

        token = {
            "name": "adriano",
            "secret": "6ff2dc141c0841e2a43c25be9ae9b097",
            "identifier": "2e42a19593f047e080285e49864b0fb6",
        }

        timestamp = datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S GMT")
        request = TestEasyHmac.compute_dummy_request(token, "", timestamp, 1234)
        body = request["body"]
        headers = request["headers"]
        path = request["path"]
        method = request["method"]

        # preparing test_request parameters
        auth_header = headers["Authorization"]
        auth_method, auth_string = auth_header.split(" ")
        token_identifier, hmac_base64 = auth_string.split(":")

        wrong_secret = "Adriano"
        self.assertRaises(
            exceptions.AuthenticationFailed,
            core.verify_hmac,
            wrong_secret,
            hmac_base64,
            headers["Content-MD5"],
            body.encode(),
            headers["Date"],
            headers["Content-Type"],
            path,
            method,
        )

    def test_verify_hmac_malformed_timestamp(self):
        """
        Tests if verify_hmac raises exception if timestamp is malformed
        """

        token = {
            "name": "adriano",
            "secret": "6ff2dc141c0841e2a43c25be9ae9b097",
            "identifier": "2e42a19593f047e080285e49864b0fb6",
        }

        malformed_timestamp = "Sunday Nov 6 08:49:37 1994"
        request = TestEasyHmac.compute_dummy_request(
            token, "", malformed_timestamp, 1234
        )
        body = request["body"]
        headers = request["headers"]
        path = request["path"]
        method = request["method"]

        # preparing test_request parameters
        auth_header = headers["Authorization"]
        auth_method, auth_string = auth_header.split(" ")
        token_identifier, hmac_base64 = auth_string.split(":")

        # malformed_date = "Sun Nov  6 08:49:37 1994"
        self.assertRaises(
            exceptions.AuthenticationFailed,
            core.verify_hmac,
            token["secret"],
            hmac_base64,
            headers["Content-MD5"],
            body.encode(),
            headers["Date"],
            headers["Content-Type"],
            path,
            method,
        )

    def test_verify_hmac_timestamp_too_old(self):
        """
        Tests if verify_hmac raises exception if timestamp is too old
        """

        token = {
            "name": "adriano",
            "secret": "6ff2dc141c0841e2a43c25be9ae9b097",
            "identifier": "2e42a19593f047e080285e49864b0fb6",
        }

        old_timestamp = "Fri, 10 Dec 2021 00:16:57 GMT"
        request = TestEasyHmac.compute_dummy_request(token, "", old_timestamp, 1234)
        body = request["body"]
        headers = request["headers"]
        path = request["path"]
        method = request["method"]

        # preparing test_request parameters
        auth_header = headers["Authorization"]
        auth_method, auth_string = auth_header.split(" ")
        token_identifier, hmac_base64 = auth_string.split(":")

        self.assertRaises(
            exceptions.AuthenticationFailed,
            core.verify_hmac,
            token["secret"],
            hmac_base64,
            headers["Content-MD5"],
            body.encode(),
            headers["Date"],
            headers["Content-Type"],
            path,
            method,
        )

    def test_verify_hmac_wrong_body(self):
        """
        Tests if verify_hmac raises exception if the body of the message is different is wrong
        """

        token = {
            "name": "adriano",
            "secret": "6ff2dc141c0841e2a43c25be9ae9b097",
            "identifier": "2e42a19593f047e080285e49864b0fb6",
        }

        timestamp = datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S GMT")
        request = TestEasyHmac.compute_dummy_request(token, "", timestamp, 1234)
        headers = request["headers"]
        path = request["path"]
        method = request["method"]

        # preparing test_request parameters
        auth_header = headers["Authorization"]
        auth_method, auth_string = auth_header.split(" ")
        token_identifier, hmac_base64 = auth_string.split(":")

        wrong_body = "{'hello': 'world'}"
        self.assertRaises(
            exceptions.AuthenticationFailed,
            core.verify_hmac,
            token["secret"],
            hmac_base64,
            headers["Content-MD5"],
            wrong_body.encode(),
            headers["Date"],
            headers["Content-Type"],
            path,
            method,
        )

    def test_generate_hmac(self):
        """
        Tests if generate_hmac_sha256 will generate the same HMAC generated by a sample webhook
        given the parameters are the same
        """

        secret = "79721503-d1ef-46b7-b4ca-fec39ece902f"
        body = '{"event": "crawler.lifecycle_updated", "payload": {"uuid": "cb8c79cd-8d79-4698-90a2-662eeab8da98", "vendor_specific_id": "1639095349767-4727fc80", "vendor_label": "adriano", "bank_id": 130, "timestamp": "2021-12-10T00:16:08.048401Z", "status": "PROCESSING", "sequence": -1}}'
        secret = secret
        method = "POST"
        timestamp = "Fri, 10 Dec 2021 00:16:57 GMT"
        path = ""
        result_digest = core.generate_hmac_sha256(secret, method, body, path, timestamp)
        actual_signature = b64encode(result_digest).decode()
        expected_signature = "SLtkg8653hvTwT0onBWI47vky0O65kkL99XN/cUskXc="
        self.assertEqual(
            expected_signature,
            actual_signature,
            f"Expected = {expected_signature} but got Actual = {actual_signature}",
        )


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