# -*- coding: utf-8 -*-
#
# This file is part of BridgeDB, a Tor bridge distribution system.
#
# :authors: <NAME> 0xA3ADB67A2CDB8B35 <<EMAIL>>
#           please also see AUTHORS file
# :copyright: (c) 2014-2017, Isis Lovecruft
#             (c) 2007-2017, The Tor Project, Inc.
#             (c) 2007-2017, all entities within the AUTHORS file
# :license: see LICENSE for licensing information

"""Unittests for the :mod:`bridgedb.util` module."""

from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals

import logging
import os
import time

from twisted.mail.smtp import Address
from twisted.trial import unittest

from bridgedb import util


class MiscLoggingUtilTests(unittest.TestCase):
    """Unittests for miscellaneous logging functions in :mod:`bridgedb.util`."""

    def test_getLogHandlers(self):
        """util._getLogHandlers() should return ['rotating', 'console'] if
        both stderr and logfile logging are enabled.
        """
        logHandlers = util._getLogHandlers()
        self.assertIsInstance(logHandlers, list)
        self.assertEqual(len(logHandlers), 2)

    def test_getLogHandlers_disableStderr(self):
        """util._getLogHandlers() should return ['rotating'] if stderr logging
        is disabled.
        """
        logHandlers = util._getLogHandlers(logToStderr=False)
        self.assertIsInstance(logHandlers, list)
        self.assertEqual(len(logHandlers), 1)
        self.assertTrue('console' not in logHandlers)

    def test_getLogHandlers_disable_logfile(self):
        """util._getLogHandlers() should return ['console'] if stderr logging
        is disabled.
        """
        logHandlers = util._getLogHandlers(logToFile=False)
        self.assertIsInstance(logHandlers, list)
        self.assertEqual(len(logHandlers), 1)
        self.assertTrue('rotating' not in logHandlers)

    def test_getRotatingFileHandler(self):
        """_getRotatingFileHandler() should create a file with 0600
        permissions (os.ST_WRITE | os.ST_APPEND).
        """
        filename = str(self.id()) + '.log'
        logHandler = util._getRotatingFileHandler(filename)
        self.assertTrue(os.path.isfile(filename))
        self.assertEqual(os.stat_result(os.stat(filename)).st_mode, 33152)
        self.assertIsInstance(logHandler(),
                              util.logging.handlers.RotatingFileHandler)

    def test_configureLogging(self):
        """Configure logging should be callable without borking anything."""
        from bridgedb.persistent import Conf
        util.configureLogging(Conf())
        util.logging.info("BridgeDB's email address: <EMAIL>")


class FileUtilityTests(unittest.TestCase):
    """Unittests for `bridgedb.util.deleteFilesOlderThan`."""

    def setUp(self):
        self._directory = self.id()
        self.newfile = os.sep.join([self._directory, "newfile"])
        self.oldfile = os.sep.join([self._directory, "oldfile"])
        self.testfiles = [self.newfile, self.oldfile]
        os.mkdir(self._directory)

        now = time.time()

        for fn in self.testfiles:
            with open(fn, "w") as fd:
                fd.flush()

        # Change the mtime of the "oldfile" to be two days old:
        os.utime(self.oldfile, (now, now - (48 * 60 * 60)))

    def test_deleteFilesOlderThan_deletes_old_files(self):
        """The function should delete appropriate files."""
        deleted = util.deleteFilesOlderThan(self.testfiles ,24 * 60 * 60)
        self.assertIn(self.oldfile, deleted)

    def test_deleteFilesOlderThan_keeps_new_files(self):
        """The function should delete appropriate files."""
        deleted = util.deleteFilesOlderThan(self.testfiles ,24 * 60 * 60)
        self.assertNotIn(self.newfile, deleted)


class LevenshteinDistanceTests(unittest.TestCase):
    """Unittests for `bridgedb.util.levenshteinDistance`."""

    def test_levenshteinDistance_blank_blank(self):
        """The Levenshtein Distance between '' and '' should be 0."""
        distance = util.levenshteinDistance('', '')
        self.assertEqual(distance, 0)

    def test_levenshteinDistance_cat_cat(self):
        """The Levenshtein Distance between 'cat' and 'cat' should be 0."""
        distance = util.levenshteinDistance('cat', 'cat')
        self.assertEqual(distance, 0)

    def test_levenshteinDistance_bat_cat(self):
        """The Levenshtein Distance between 'bat' and 'cat' should be 1."""
        distance = util.levenshteinDistance('bat', 'cat')
        self.assertEqual(distance, 1)

    def test_levenshteinDistance_bar_cat(self):
        """The Levenshtein Distance between 'bar' and 'cat' should be 2."""
        distance = util.levenshteinDistance('bar', 'cat')
        self.assertEqual(distance, 2)

    def test_levenshteinDistance_bridgedb_doge(self):
        """The Levenshtein Distance between 'bridgedb' and 'doge' should be 6."""
        distance = util.levenshteinDistance('bridgedb', 'doge')
        self.assertEqual(distance, 6)

    def test_levenshteinDistance_feidanchaoren0043_feidanchaoren0011(self):
        """The Levenshtein Distance between the usernames in
        '<EMAIL>' and '<EMAIL>' should
        be less than an EMAIL_FUZZY_MATCH parameter.
        """
        email1 = Address('<EMAIL>')
        email2 = Address('<EMAIL>')
        # Fuzzy match if the Levenshtein Distance is less than or equal to:
        fuzzyMatch = 4
        distance = util.levenshteinDistance(email1.local, email2.local)
        self.assertLessEqual(distance, fuzzyMatch)


class JustifiedLogFormatterTests(unittest.TestCase):
    """Unittests for :class:`bridgedb.util.JustifiedLogFormatter`."""

    def setUp(self):
        # name, level, path, lineno, message, args, exc_info
        self.record = logging.LogRecord('name', logging.INFO, '/foo/bar/baz',
                                        12345, 'This is a message', None, None)

    def test_util_JustifiedLogFormatter(self):
        formatter = util.JustifiedLogFormatter()
        self.assertIsInstance(formatter, logging.Formatter)

    def test_util_JustifiedLogFormatter_logThreads(self):
        formatter = util.JustifiedLogFormatter(logThreads=True)
        self.assertIsInstance(formatter, logging.Formatter)

    def test_util_JustifiedLogFormatter_formatCallingFuncName(self):
        formatter = util.JustifiedLogFormatter()
        record = formatter._formatCallingFuncName(self.record)
        self.assertIsInstance(formatter, logging.Formatter)
        self.assertIsInstance(record, logging.LogRecord)

    def test_util_JustifiedLogFormatter_format(self):
        formatter = util.JustifiedLogFormatter()
        formatted = formatter.format(self.record)
        self.assertIsInstance(formatter, logging.Formatter)
        self.assertIsInstance(formatted, str)
        self.assertNotEqual(formatted, '')
        self.assertTrue('INFO' in formatted)
        self.assertTrue('This is a message' in formatted)
