# Path: djangosaml2idp/utils.py
# def encode_saml(saml_envelope: str, use_zlib: bool = False) -> bytes:
#     # Not sure where 2:-4 came from, but that's how pysaml2 does it, and it works
#     before_base64 = zlib.compress(saml_envelope.encode())[2:-4] if use_zlib else saml_envelope.encode()
#     return base64.b64encode(before_base64)
# 
# def extract_validuntil_from_metadata(metadata: str) -> datetime.datetime:
#     ''' Extract the ValidUntil timestamp from the given metadata. Returns that timestamp if successfully, raise a ValidationError otherwise.
#     '''
#     try:
#         metadata_expiration_dt = arrow.get(ET.fromstring(metadata).attrib['validUntil']).datetime
#     except Exception as e:
#         fallback = getattr(settings, "SAML_IDP_FALLBACK_EXPIRATION_DAYS", 0)
#         if fallback:
#             return now() + datetime.timedelta(days=fallback)
#         raise ValidationError(f'Could not extra ValidUntil timestamp from metadata: {e}')
# 
#     if not settings.USE_TZ:
#         return metadata_expiration_dt.replace(tzinfo=None)
#     if is_naive(metadata_expiration_dt):
#         return make_aware(metadata_expiration_dt)
#     return metadata_expiration_dt
# 
# def fetch_metadata(remote_metadata_url: str) -> str:
#     ''' Fetch remote metadata. Raise a ValidationError if it could not successfully fetch something from the url '''
#     try:
#         content = requests.get(remote_metadata_url, timeout=(3, 10))
#         if content.status_code != 200:
#             raise Exception(f'Non-successful request, received status code {content.status_code}')
#     except Exception as e:
#         raise ValidationError(f'Could not fetch metadata from {remote_metadata_url}: {e}')
#     return content.text
# 
# def repr_saml(saml: str, b64: bool = False):
#     """ Decode SAML from b64 and b64 deflated and return a pretty printed representation
#     """
#     try:
#         msg = base64.b64decode(saml).decode() if b64 else saml
#         dom = xml.dom.minidom.parseString(msg)
#     except (UnicodeDecodeError, ExpatError):
#         # in HTTP-REDIRECT the base64 must be inflated
#         compressed = base64.b64decode(saml)
#         inflated = zlib.decompress(compressed, -15)
#         dom = xml.dom.minidom.parseString(inflated.decode())
#     return dom.toprettyxml()
# 
# def validate_metadata(metadata: str) -> str:
#     ''' Validate if the given metadata is valid xml, raise a ValidationError otherwise. Returns the metadata string back.
#     '''
#     try:
#         ET.fromstring(metadata)
#     except Exception as e:
#         raise ValidationError(f'Metadata is not valid metadata xml: {e}')
#     return metadata
# 
# def verify_request_signature(req_info: StatusResponse) -> None:
#     """ Signature verification for authn request signature_check is at
#         saml2.sigver.SecurityContext.correctly_signed_authn_request
#     """
#     if not req_info.signature_check(req_info.xmlstr):
#         raise ValueError(_("Message signature verification failure"))
# 
# Path: tests/testing_utilities.py
# def mocked_requests_get(*args, **kwargs):
#     class MockResponse:
#         def __init__(self, text, status_code):
#             self.text = text
#             self.status_code = status_code
# 
#     if args[0] == 'http://not_found':
#         return MockResponse('not found', 404)
# 
#     return MockResponse('ok', 200)


import datetime
import xml
import pytest
from unittest import mock
from django.core.exceptions import ValidationError
from django.utils import timezone
from djangosaml2idp.utils import (encode_saml,
                                  extract_validuntil_from_metadata,
                                  fetch_metadata, repr_saml, validate_metadata,
                                  verify_request_signature)
from .testing_utilities import mocked_requests_get
class TestMetadataValidation:
    def test_validate_metadata_invalid(self):
        with pytest.raises(ValidationError):
            validate_metadata('')

    def test_validate_metadata_valid(self):
        md = validate_metadata('<?xml version="1.0" encoding="UTF-8"?><content></content>')
        assert md == '<?xml version="1.0" encoding="UTF-8"?><content></content>'

    def test_extract_validuntil_from_metadata_invalid(self):
        with pytest.raises(ValidationError):
            extract_validuntil_from_metadata('')

    @pytest.mark.parametrize('use_tz, tzinfo', [(True, timezone.utc), (False, None)])
    def test_extract_validuntil_from_metadata_valid(self, settings, sp_metadata_xml, use_tz, tzinfo):
        settings.USE_TZ = use_tz
        valid_until_dt_extracted = extract_validuntil_from_metadata(sp_metadata_xml)
        assert valid_until_dt_extracted == datetime.datetime(2099, 2, 14, 17, 43, 34, tzinfo=tzinfo)


class TestUtils:
    def test_verify_request_signature(self):
        # TODO: use real saml response
        class DummyStatusResponseFailing:
            xmlstr: str = ''

            def signature_check(self, *args, **kwargs):
                return False

        with pytest.raises(ValueError):

# Path: djangosaml2idp/utils.py
# def encode_saml(saml_envelope: str, use_zlib: bool = False) -> bytes:
#     # Not sure where 2:-4 came from, but that's how pysaml2 does it, and it works
#     before_base64 = zlib.compress(saml_envelope.encode())[2:-4] if use_zlib else saml_envelope.encode()
#     return base64.b64encode(before_base64)
# 
# def extract_validuntil_from_metadata(metadata: str) -> datetime.datetime:
#     ''' Extract the ValidUntil timestamp from the given metadata. Returns that timestamp if successfully, raise a ValidationError otherwise.
#     '''
#     try:
#         metadata_expiration_dt = arrow.get(ET.fromstring(metadata).attrib['validUntil']).datetime
#     except Exception as e:
#         fallback = getattr(settings, "SAML_IDP_FALLBACK_EXPIRATION_DAYS", 0)
#         if fallback:
#             return now() + datetime.timedelta(days=fallback)
#         raise ValidationError(f'Could not extra ValidUntil timestamp from metadata: {e}')
# 
#     if not settings.USE_TZ:
#         return metadata_expiration_dt.replace(tzinfo=None)
#     if is_naive(metadata_expiration_dt):
#         return make_aware(metadata_expiration_dt)
#     return metadata_expiration_dt
# 
# def fetch_metadata(remote_metadata_url: str) -> str:
#     ''' Fetch remote metadata. Raise a ValidationError if it could not successfully fetch something from the url '''
#     try:
#         content = requests.get(remote_metadata_url, timeout=(3, 10))
#         if content.status_code != 200:
#             raise Exception(f'Non-successful request, received status code {content.status_code}')
#     except Exception as e:
#         raise ValidationError(f'Could not fetch metadata from {remote_metadata_url}: {e}')
#     return content.text
# 
# def repr_saml(saml: str, b64: bool = False):
#     """ Decode SAML from b64 and b64 deflated and return a pretty printed representation
#     """
#     try:
#         msg = base64.b64decode(saml).decode() if b64 else saml
#         dom = xml.dom.minidom.parseString(msg)
#     except (UnicodeDecodeError, ExpatError):
#         # in HTTP-REDIRECT the base64 must be inflated
#         compressed = base64.b64decode(saml)
#         inflated = zlib.decompress(compressed, -15)
#         dom = xml.dom.minidom.parseString(inflated.decode())
#     return dom.toprettyxml()
# 
# def validate_metadata(metadata: str) -> str:
#     ''' Validate if the given metadata is valid xml, raise a ValidationError otherwise. Returns the metadata string back.
#     '''
#     try:
#         ET.fromstring(metadata)
#     except Exception as e:
#         raise ValidationError(f'Metadata is not valid metadata xml: {e}')
#     return metadata
# 
# def verify_request_signature(req_info: StatusResponse) -> None:
#     """ Signature verification for authn request signature_check is at
#         saml2.sigver.SecurityContext.correctly_signed_authn_request
#     """
#     if not req_info.signature_check(req_info.xmlstr):
#         raise ValueError(_("Message signature verification failure"))
# 
# Path: tests/testing_utilities.py
# def mocked_requests_get(*args, **kwargs):
#     class MockResponse:
#         def __init__(self, text, status_code):
#             self.text = text
#             self.status_code = status_code
# 
#     if args[0] == 'http://not_found':
#         return MockResponse('not found', 404)
# 
#     return MockResponse('ok', 200)


# Path: tests/test_utils.py
import datetime
import xml
import pytest
from unittest import mock
from django.core.exceptions import ValidationError
from django.utils import timezone
from djangosaml2idp.utils import (encode_saml,
                                  extract_validuntil_from_metadata,
                                  fetch_metadata, repr_saml, validate_metadata,
                                  verify_request_signature)
from .testing_utilities import mocked_requests_get

class TestMetadataValidation:
    def test_validate_metadata_invalid(self):
        with pytest.raises(ValidationError):
            validate_metadata('')

    def test_validate_metadata_valid(self):
        md = validate_metadata('<?xml version="1.0" encoding="UTF-8"?><content></content>')
        assert md == '<?xml version="1.0" encoding="UTF-8"?><content></content>'

    def test_extract_validuntil_from_metadata_invalid(self):
        with pytest.raises(ValidationError):
            extract_validuntil_from_metadata('')

    @pytest.mark.parametrize('use_tz, tzinfo', [(True, timezone.utc), (False, None)])
    def test_extract_validuntil_from_metadata_valid(self, settings, sp_metadata_xml, use_tz, tzinfo):
        settings.USE_TZ = use_tz
        valid_until_dt_extracted = extract_validuntil_from_metadata(sp_metadata_xml)
        assert valid_until_dt_extracted == datetime.datetime(2099, 2, 14, 17, 43, 34, tzinfo=tzinfo)


class TestUtils:
    def test_verify_request_signature(self):
        # TODO: use real saml response
        class DummyStatusResponseFailing:
            xmlstr: str = ''

            def signature_check(self, *args, **kwargs):
                return False

        with pytest.raises(ValueError):

            verify_request_signature(DummyStatusResponseFailing())