from vulnix.vulnerability import Vulnerability, Node
import json
import pkg_resources

V = Vulnerability


def load(cve):
    return json.loads(pkg_resources.resource_string(
        'vulnix', 'tests/fixtures/{}.json'.format(cve)))


def test_parse_single_matches():
    v = V.parse(load('CVE-2016-2183'))
    # redhat entries do not get compressed, python & openssl do
    assert v.nodes == [
        Node('redhat', 'jboss_enterprise_application_platform', '6.0.0'),
        Node('redhat', 'jboss_enterprise_web_server', '1.0.0'),
        Node('redhat', 'jboss_enterprise_web_server', '2.0.0'),
        Node('redhat', 'jboss_web_server', '3.0'),
        Node('python', 'python', '3.3'),
        Node('python', 'python', '3.4.0'),
        Node('python', 'python', '3.5'),
        Node('python', 'python', '3.6'),
        Node('openssl', 'openssl', '1.0.1a'),
        Node('openssl', 'openssl', '1.0.1b'),
        Node('openssl', 'openssl', '1.0.1c'),
        Node('openssl', 'openssl', '1.0.2a'),
        Node('openssl', 'openssl', '1.0.2b'),
        Node('openssl', 'openssl', '1.0.2c'),
    ]
    assert v.match('openssl', '1.0.1a')
    assert not v.match('openssl', '1.0.1d')


def test_ignore_AND_operator():
    # The AND operators are usually incomplete or incorrect. We prefer to get a
    # few more false positives by ignoring them altogether.
    v = V.parse(load('CVE-2010-0748'))
    assert v.nodes == [
        Node('transmissionbt', 'transmission', ['<1.92'])
    ]
    assert v.match('transmission', '1.91')
    assert not v.match('transmission', '1.92')


def test_parse_escaped_colons():
    cve = {
        'cve': {'CVE_data_meta': {'ID': 'CVE-2014-0477'}},
        'configurations': {'nodes': [{
            'operator': 'OR',
            'cpe_match': [{
                'vulnerable': True,
                'cpe23Uri': 'cpe:2.3:a:email\\:\\:address_module_project:' +
                            'email\\:\\:address:1.902:*:*:*:*:perl:*:*',
            }]
        }]}
    }
    v = V.parse(cve)
    assert len(v.nodes) == 1
    assert v.nodes[0].vendor == 'email-address_module_project'
    assert v.nodes[0].product == 'email-address'


def test_product_not_found(nvd):
    assert [] == nvd.by_product('nonexistent-product')


def test_str():
    assert 'CVE-2016-2183', V(
        'CVE-2016-2183', Node('openssl', 'openssl', '1.0.1a'))


def test_match_multiple_version_ranges():
    v = V.parse(load('CVE-2019-11041'))
    assert v.match('php', '7.2.20')
    assert not v.match('php', '7.2.21')


def test_match_right_version():
    # This CVE contains a non-matching range for openssl and a matching range
    # for an Oracle product. Make sure that the matching algo considers the
    # product name.
    v = V.parse(load('CVE-2015-1789'))
    assert v.match('openssl', '1.0.2-beta1')
    assert not v.match('openssl', '1.1.1d')


def test_compress_strings():
    v = V.parse(load('CVE-2015-1789'))
    assert v.nodes[0].vendor == v.nodes[1].vendor
    assert id(v.nodes[0].vendor) == id(v.nodes[1].vendor)
    assert v.nodes[0].product == v.nodes[1].product
    assert id(v.nodes[0].product) == id(v.nodes[1].product)


def test_cvss_score():
    v = V.parse(load('CVE-2019-10160'))
    assert v.cvssv3 == 9.8
    assert v.cvssv2 == 5.0


def test_no_cvss_score():
    v = V.parse(load('CVE-2010-0748'))
    assert v.cvssv3 == 0.0
    assert v.cvssv2 == 0.0


def test_cve_ordering():
    assert V('CVE-2019-0001') < V('CVE-2019-0002')
    assert V('CVE-2019-0002') == V('CVE-2019-0002')
    assert V('CVE-2019-0001') > V('CVE-2018-0001')
    assert V('CVE-2019-10000') > V('CVE-2018-2000')
    assert V('CVE-2019-0700') > V('CVE-2018-0699')
