#!/usr/bin/env python

import socket
import subprocess
import urllib2
import time
import re
import unittest

import fac
import custom_ssl
import query_onionoo
# It is needed for 'ask_is_exit'.
# The function must be inside 'fac'.
from fac import fac_socket
from fac import transact
from fac import format_addr

FAC_ONIONOO_PORT = 9003
FAC_ONIONOO_ADDR = ("127.0.0.1", FAC_ONIONOO_PORT)

ONIONOO_URL_QUERY = "https://onionoo.torproject.org/details?type=relay&running=true"

ONIONOO_FIELD1 = 'relays'
ONIONOO_FIELD2 = 'or_addresses'
ONIONOO_FIELD3 = 'exit_addresses'
ONIONOO_FIELD4 = 'exit_policy'

def ask_is_exit(onionoo_addr, proxy_addr):
    """It implements the client functionality in order to talk with the TCP server
    through the protocol defined by the TCP server ( it should be used within the
    Facilitator to check a proxy IP)."""
    f = fac_socket(onionoo_addr)
    params = [("FROM", format_addr(proxy_addr))]
    try:
        command, params = transact(f, "CHECK_IP", *params)
    finally:
        f.close()
    return (command == "IS_EXIT", command)

class NullDevice():
    """A class used to print to nowhere in a portable way."""
    def write(self, s):
        pass
    def flush(self):
        pass

class customSSLTest(unittest.TestCase):
    def test_goodHTTPS(self):
        """It tries to connect to the secure Google address."""
        handler = custom_ssl.VerifiedHTTPSHandler(ca_certs=custom_ssl.certifi.where())
        opener = urllib2.build_opener(handler)
        response = opener.open("https://www.google.es")
        data = response.read()
        self.assertNotEqual(re.match(ur'^<!doctype html>.+', data), None)

    def test_badHTTPS(self):
        """It tries to connect to a not secured Google address through HTTPS, so it must raise an Exception."""
        handler = custom_ssl.VerifiedHTTPSHandler(ca_certs=custom_ssl.certifi.where())
        opener = urllib2.build_opener(handler)
        self.assertRaises(custom_ssl.InvalidCertificateException, opener.open, "https://74.125.232.50")


class queryOnionooServerTest(unittest.TestCase):
    def test_connection(self):
        """It tries to open a connection with a bad URL, so it must return None."""
        last_modified = query_onionoo.LastModified()
        res=query_onionoo.query_onionoo_server('', last_modified, NullDevice(), run_for_test=True)
        self.assertEqual(res, None)

    def test_modifyLastModified(self):
        """It is checking that the function 'query_onionoo_server' is modifying the global variable last_modified.
        This global variable is holding the timestamp with the last time that the Onionoo server was queried"""
        last_modified = query_onionoo.LastModified()
        self.assertEqual(last_modified.get(), None)
        res=query_onionoo.query_onionoo_server(ONIONOO_URL_QUERY, last_modified, NullDevice(), run_for_test=True)
        self.assertNotEqual(last_modified.get(), None)

    def test_JsonFields(self):
        """It is checking that the basic protocol to communicate with the Onionoo server didn't change."""
        last_modified = query_onionoo.LastModified()
        res=query_onionoo.query_onionoo_server(ONIONOO_URL_QUERY, last_modified, NullDevice(), run_for_test=True)
        self.assertNotEqual(res, None)
        self.assertEqual(isinstance(res, tuple), True)
        self.assertEqual(len(res), 3)
        if res is not None and isinstance(res, tuple) and len(res) == 3:
            self.assertEqual(ONIONOO_FIELD1 in res[0], True)
            #ONIONOO_FIELD2 is a mandatory field, but ONIONOO_FIELD3 and ONIONOO_FIELD4 are optionals.
            self.assertEqual(ONIONOO_FIELD2 in res[1], True)
            self.assertEqual(len(res[2]) > 0, True)


class facOnionooServerTest(unittest.TestCase):

    def setUp(self):
        self.process = subprocess.Popen(["./fac-onionoo", "-d", "-p", str(FAC_ONIONOO_PORT), "-o", ONIONOO_URL_QUERY, "-l", "/dev/null"])
        # Give enough time to initialize the TCP server.
        time.sleep(0.1)

    def tearDown(self):
        self.process.terminate()
   
    def test_ask1(self):
        """It is checking the 'fac-onionoo' server with a Google address as a proxy IP, so it must return False
        to the query if the IP belongs to a Tor Exit node.
        The query to the Onionoo server takes time, so the test is checking the code that the 'fac-onionoo'
        server returns after a query. If the code is NOT_EXIT_0 that means that the 'fac-onionoo' server
        still have an empty local db (it didn't finalize the query to the Onionoo server)."""
        is_not_initialized = True
        # We need enough time to query the Onionoo server.
        print '\n'
        while is_not_initialized:
           res = ask_is_exit(FAC_ONIONOO_ADDR, ("74.125.232.50", None))
           if res[1] == "NOT_EXIT_0":
               print "Still not finished the query to the Onionoo Server, going to sleep 10 seconds and trying again."
               time.sleep(10)
           else:
               is_not_initialized = False
        self.assertEqual(res[0], False)

    def test_ask2(self):
        """It is checking the 'fac-onionoo' server with a Tor Exit node obtained from the Tor Browser Bundle 
        when it is starting and its public IP is checked against 'check.torproject.org'. Take care because
        the current IP could be down or closed, so it is better to use your own fresh IP.
        The query to the Onionoo server takes time, so the test is checking the code that the 'fac-onionoo'
        server returns after a query. If the code is NOT_EXIT_0 that means that the 'fac-onionoo' server
        still have an empty local db (it didn't finalize the query to the Onionoo server)."""
        is_not_initialized = True
        # We need enough time to query the Onionoo server.
        print '\n'
	while is_not_initialized:
           res = ask_is_exit(FAC_ONIONOO_ADDR, ("31.172.30.1", None))
           if res[1] == "NOT_EXIT_0":
               print "Still not finished the query to the Onionoo Server, going to sleep 10 seconds and trying again."
               time.sleep(10)
           else:
               is_not_initialized = False
        self.assertEqual(res[0], True)    

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