import pytest
from mock import (
    Mock,
    patch,
    call
)
from requests.exceptions import HTTPError
from jenkinsapi.artifact import Artifact
from jenkinsapi.jenkinsbase import JenkinsBase
from jenkinsapi.fingerprint import Fingerprint
from jenkinsapi.custom_exceptions import ArtifactBroken
try:
    import unittest2 as unittest
except ImportError:
    import unittest


@pytest.fixture()
def artifact(mocker):
    return Artifact(
        'artifact.zip', 'http://foo/job/TestJob/1/artifact/artifact.zip',
        mocker.MagicMock()
    )


def test_verify_download_valid_positive(artifact, monkeypatch):
    def fake_md5(cls, fspath):   # pylint: disable=unused-argument
        return '097c42989a9e5d9dcced7b35ec4b0486'

    monkeypatch.setattr(Artifact, '_md5sum', fake_md5)

    def fake_poll(cls, tree=None):   # pylint: disable=unused-argument
        return {}

    monkeypatch.setattr(JenkinsBase, '_poll', fake_poll)

    def fake_validate(cls, filename,   # pylint: disable=unused-argument
                      job, build):   # pylint: disable=unused-argument
        return True

    monkeypatch.setattr(Fingerprint, 'validate_for_build', fake_validate)

    assert artifact._verify_download('/tmp/artifact.zip',
                                     strict_validation=False)


def test_verify_download_valid_positive_with_rename(artifact, monkeypatch):
    def fake_md5(cls, fspath):   # pylint: disable=unused-argument
        return '097c42989a9e5d9dcced7b35ec4b0486'

    monkeypatch.setattr(Artifact, '_md5sum', fake_md5)

    def fake_poll(cls, tree=None):   # pylint: disable=unused-argument
        return {}

    monkeypatch.setattr(JenkinsBase, '_poll', fake_poll)

    def fake_validate(cls, filename,   # pylint: disable=unused-argument
                      job, build):   # pylint: disable=unused-argument
        return filename == 'artifact.zip'

    monkeypatch.setattr(Fingerprint, 'validate_for_build', fake_validate)

    assert artifact._verify_download('/tmp/temporary_filename',
                                     strict_validation=False)


def test_verify_download_valid_negative(artifact, monkeypatch):
    def fake_md5(cls, fspath):   # pylint: disable=unused-argument
        return '097c42989a9e5d9dcced7b35ec4b0486'

    monkeypatch.setattr(Artifact, '_md5sum', fake_md5)

    class FakeResponse(object):
        status_code = 404
        text = '{}'

    class FakeHTTPError(HTTPError):
        def __init__(self):
            self.response = FakeResponse()

    def fake_poll(cls, tree=None):   # pylint: disable=unused-argument
        raise FakeHTTPError()

    monkeypatch.setattr(JenkinsBase, '_poll', fake_poll)

    def fake_validate(cls, filename,   # pylint: disable=unused-argument
                      job, build):   # pylint: disable=unused-argument
        return True

    monkeypatch.setattr(Fingerprint, 'validate_for_build', fake_validate)

    assert artifact._verify_download('/tmp/artifact.zip',
                                     strict_validation=False)


def test_verify_dl_valid_negative_strict(artifact, monkeypatch):
    def fake_md5(cls, fspath):   # pylint: disable=unused-argument
        return '097c42989a9e5d9dcced7b35ec4b0486'

    monkeypatch.setattr(Artifact, '_md5sum', fake_md5)

    class FakeResponse(object):
        status_code = 404
        text = '{}'

    class FakeHTTPError(HTTPError):
        def __init__(self):
            self.response = FakeResponse()

    def fake_poll(cls, tree=None):   # pylint: disable=unused-argument
        raise FakeHTTPError()

    monkeypatch.setattr(JenkinsBase, '_poll', fake_poll)

    with pytest.raises(ArtifactBroken) as ab:
        artifact._verify_download('/tmp/artifact.zip',
                                  strict_validation=True)

    assert 'Artifact 097c42989a9e5d9dcced7b35ec4b0486 seems to be broken' \
           in str(ab.value)


def test_verify_download_invalid(artifact, monkeypatch):
    def fake_md5(cls, fspath):   # pylint: disable=unused-argument
        return '097c42989a9e5d9dcced7b35ec4b0486'

    monkeypatch.setattr(Artifact, '_md5sum', fake_md5)

    def fake_poll(cls, tree=None):   # pylint: disable=unused-argument
        return {}

    monkeypatch.setattr(JenkinsBase, '_poll', fake_poll)

    def fake_validate(cls, filename,   # pylint: disable=unused-argument
                      job, build):   # pylint: disable=unused-argument
        return False

    monkeypatch.setattr(Fingerprint, 'validate_for_build', fake_validate)

    with pytest.raises(ArtifactBroken) as ab:
        artifact._verify_download('/tmp/artifact.zip',
                                  strict_validation=True)

    assert 'Artifact 097c42989a9e5d9dcced7b35ec4b0486 seems to be broken' \
           in str(ab.value)


class ArtifactTest(unittest.TestCase):

    def setUp(self):
        self._build = build = Mock()
        build.buildno = 9999
        job = self._build.job
        job.jenkins.baseurl = 'http://localhost'
        job.name = 'TestJob'
        self._artifact = Artifact(
            'artifact.zip',
            'http://localhost/job/TestJob/9999/artifact/artifact.zip', build)

    @patch('jenkinsapi.artifact.os.path.exists', spec=True, return_value=True)
    def test_save_has_valid_local_copy(self, mock_exists):
        artifact = self._artifact
        artifact._verify_download = Mock(return_value=True)

        assert artifact.save('/tmp/artifact.zip') == '/tmp/artifact.zip'

        mock_exists.assert_called_once_with('/tmp/artifact.zip')
        artifact._verify_download.assert_called_once_with(
            '/tmp/artifact.zip', False)

    @patch('jenkinsapi.artifact.os.path.exists', spec=True, return_value=True)
    def test_save_has_invalid_local_copy_dl_again(self, mock_exists):
        artifact = self._artifact
        artifact._verify_download = Mock(side_effect=[ArtifactBroken, True])
        artifact._do_download = Mock(return_value='/tmp/artifact.zip')

        assert artifact.save('/tmp/artifact.zip', True) == '/tmp/artifact.zip'

        mock_exists.assert_called_once_with('/tmp/artifact.zip')
        artifact._do_download.assert_called_once_with('/tmp/artifact.zip')
        assert artifact._verify_download.mock_calls == \
            [call('/tmp/artifact.zip', True)] * 2

    @patch('jenkinsapi.artifact.os.path.exists', spec=True, return_value=True)
    def test_has_invalid_lcl_copy_dl_but_invalid(
            self, mock_exists):
        artifact = self._artifact
        artifact._verify_download = Mock(
            side_effect=[ArtifactBroken, ArtifactBroken])
        artifact._do_download = Mock(return_value='/tmp/artifact.zip')

        with pytest.raises(ArtifactBroken):
            artifact.save('/tmp/artifact.zip', True)

        mock_exists.assert_called_once_with('/tmp/artifact.zip')
        artifact._do_download.assert_called_once_with('/tmp/artifact.zip')
        assert artifact._verify_download.mock_calls == \
            [call('/tmp/artifact.zip', True)] * 2

    @patch('jenkinsapi.artifact.os.path.exists', spec=True, return_value=False)
    def test_save_has_no_local_copy(self, mock_exists):
        artifact = self._artifact
        artifact._do_download = Mock(return_value='/tmp/artifact.zip')
        artifact._verify_download = Mock(return_value=True)

        assert artifact.save('/tmp/artifact.zip') == '/tmp/artifact.zip'

        mock_exists.assert_called_once_with('/tmp/artifact.zip')
        artifact._do_download.assert_called_once_with('/tmp/artifact.zip')
        artifact._verify_download.assert_called_once_with(
            '/tmp/artifact.zip', False)
