import unittest

from uffd.password_hash import *

class TestPasswordHashRegistry(unittest.TestCase):
	def test(self):
		registry = PasswordHashRegistry()

		@registry.register
		class TestPasswordHash:
			METHOD_NAME = 'test'
			def __init__(self, value, **kwargs):
				self.value = value
				self.kwargs = kwargs

		@registry.register
		class Test2PasswordHash:
			METHOD_NAME = 'test2'

		result = registry.parse('{test}data', key='value')
		self.assertIsInstance(result, TestPasswordHash)
		self.assertEqual(result.value, '{test}data')
		self.assertEqual(result.kwargs, {'key': 'value'})
		with self.assertRaises(ValueError):
			registry.parse('{invalid}data')
		with self.assertRaises(ValueError):
			registry.parse('invalid')
		with self.assertRaises(ValueError):
			registry.parse('{invalid')

class TestPasswordHash(unittest.TestCase):
	def setUp(self):
		class TestPasswordHash(PasswordHash):
			@classmethod
			def from_password(cls, password):
				cls(build_value(cls.METHOD_NAME, password))

			def verify(self, password):
				return self.data == password

		class TestPasswordHash1(TestPasswordHash):
			METHOD_NAME = 'test1'

		class TestPasswordHash2(TestPasswordHash):
			METHOD_NAME = 'test2'

		self.TestPasswordHash1 = TestPasswordHash1
		self.TestPasswordHash2 = TestPasswordHash2

	def test(self):
		obj = self.TestPasswordHash1('{test1}data')
		self.assertEqual(obj.value, '{test1}data')
		self.assertEqual(obj.data, 'data')
		self.assertIs(obj.target_cls, self.TestPasswordHash1)
		self.assertFalse(obj.needs_rehash)

	def test_invalid(self):
		with self.assertRaises(ValueError):
			self.TestPasswordHash1('invalid')
		with self.assertRaises(ValueError):
			self.TestPasswordHash1('{invalid}data')
		with self.assertRaises(ValueError):
			self.TestPasswordHash1('{test2}data')

	def test_target_cls(self):
		obj = self.TestPasswordHash1('{test1}data', target_cls=self.TestPasswordHash1)
		self.assertEqual(obj.value, '{test1}data')
		self.assertEqual(obj.data, 'data')
		self.assertIs(obj.target_cls, self.TestPasswordHash1)
		self.assertFalse(obj.needs_rehash)
		obj = self.TestPasswordHash1('{test1}data', target_cls=self.TestPasswordHash2)
		self.assertEqual(obj.value, '{test1}data')
		self.assertEqual(obj.data, 'data')
		self.assertIs(obj.target_cls, self.TestPasswordHash2)
		self.assertTrue(obj.needs_rehash)
		obj = self.TestPasswordHash1('{test1}data', target_cls=PasswordHash)
		self.assertEqual(obj.value, '{test1}data')
		self.assertEqual(obj.data, 'data')
		self.assertIs(obj.target_cls, PasswordHash)
		self.assertFalse(obj.needs_rehash)

class TestPlaintextPasswordHash(unittest.TestCase):
	def test_verify(self):
		obj = PlaintextPasswordHash('{plain}password')
		self.assertTrue(obj.verify('password'))
		self.assertFalse(obj.verify('notpassword'))

	def test_from_password(self):
		obj = PlaintextPasswordHash.from_password('password')
		self.assertEqual(obj.value, '{plain}password')
		self.assertTrue(obj.verify('password'))
		self.assertFalse(obj.verify('notpassword'))

class TestHashlibPasswordHash(unittest.TestCase):
	def test_verify(self):
		obj = SHA512PasswordHash('{sha512}sQnzu7wkTrgkQZF+0G1hi5AI3Qmzvv0bXgc5THBqi7mAsdd4Xll27ASbRt9fEyavWi6m0QP9B8lThf+rDKy8hg==')
		self.assertTrue(obj.verify('password'))
		self.assertFalse(obj.verify('notpassword'))

	def test_from_password(self):
		obj = SHA512PasswordHash.from_password('password')
		self.assertIsNotNone(obj.value)
		self.assertTrue(obj.value.startswith('{sha512}'))
		self.assertTrue(obj.verify('password'))
		self.assertFalse(obj.verify('notpassword'))

class TestSaltedHashlibPasswordHash(unittest.TestCase):
	def test_verify(self):
		obj = SaltedSHA512PasswordHash('{ssha512}dOeDLmVpHJThhHeag10Hm2g4T7s3SBE6rGHcXUolXJHVufY4qT782rwZ/0XE6cuLcBZ0KpnwmUzRpAEtZBdv+JYEEtZQs/uC')
		self.assertTrue(obj.verify('password'))
		self.assertFalse(obj.verify('notpassword'))

	def test_from_password(self):
		obj = SaltedSHA512PasswordHash.from_password('password')
		self.assertIsNotNone(obj.value)
		self.assertTrue(obj.value.startswith('{ssha512}'))
		self.assertTrue(obj.verify('password'))
		self.assertFalse(obj.verify('notpassword'))

class TestCryptPasswordHash(unittest.TestCase):
	def test_verify(self):
		obj = CryptPasswordHash('{crypt}$5$UbTTMBH9NRurlQcX$bUiUTyedvmArlVt.62ZLRV80e2v3DjcBp/tSDkP2imD')
		self.assertTrue(obj.verify('password'))
		self.assertFalse(obj.verify('notpassword'))

	def test_from_password(self):
		obj = CryptPasswordHash.from_password('password')
		self.assertIsNotNone(obj.value)
		self.assertTrue(obj.value.startswith('{crypt}'))
		self.assertTrue(obj.verify('password'))
		self.assertFalse(obj.verify('notpassword'))

class TestArgon2PasswordHash(unittest.TestCase):
	def test_verify(self):
		obj = Argon2PasswordHash('{argon2}$argon2id$v=19$m=102400,t=2,p=8$Jc8LpCgPLjwlN/7efHLvwQ$ZqSg3CFb2/hBb3X8hOq4aw')
		self.assertTrue(obj.verify('password'))
		self.assertFalse(obj.verify('notpassword'))
		obj = Argon2PasswordHash('{argon2}$invalid$')
		self.assertFalse(obj.verify('password'))

	def test_from_password(self):
		obj = Argon2PasswordHash.from_password('password')
		self.assertIsNotNone(obj.value)
		self.assertTrue(obj.value.startswith('{argon2}'))
		self.assertTrue(obj.verify('password'))
		self.assertFalse(obj.verify('notpassword'))

	def test_needs_rehash(self):
		obj = Argon2PasswordHash('{argon2}$argon2id$v=19$m=102400,t=2,p=8$Jc8LpCgPLjwlN/7efHLvwQ$ZqSg3CFb2/hBb3X8hOq4aw')
		self.assertFalse(obj.needs_rehash)
		obj = Argon2PasswordHash('{argon2}$argon2id$v=19$m=102400,t=2,p=8$Jc8LpCgPLjwlN/7efHLvwQ$ZqSg3CFb2/hBb3X8hOq4aw', target_cls=PlaintextPasswordHash)
		self.assertTrue(obj.needs_rehash)
		obj = Argon2PasswordHash('{argon2}$argon2d$v=19$m=102400,t=2,p=8$kshPgLU1+h72l/Z8QWh8Ig$tYerKCe/5I2BCPKu8hCl2w')
		self.assertTrue(obj.needs_rehash)
		obj = Argon2PasswordHash('{argon2}$argon2id$v=19$m=102400,t=1,p=8$aa6i4vg/szKX5xHVGFaAeQ$v6j0ltuVqQaZlmuepaVJ1A')
		self.assertTrue(obj.needs_rehash)

class TestInvalidPasswordHash(unittest.TestCase):
	def test(self):
		obj = InvalidPasswordHash('test')
		self.assertEqual(obj.value, 'test')
		self.assertFalse(obj.verify('test'))
		self.assertTrue(obj.needs_rehash)
		self.assertFalse(obj)
		obj = InvalidPasswordHash(None)
		self.assertIsNone(obj.value)
		self.assertFalse(obj.verify('test'))
		self.assertTrue(obj.needs_rehash)
		self.assertFalse(obj)

class TestPasswordWrapper(unittest.TestCase):
	def setUp(self):
		class Test:
			password_hash = None
			password = PasswordHashAttribute('password_hash', PlaintextPasswordHash)

		self.test = Test()

	def test_get_none(self):
		self.test.password_hash = None
		obj = self.test.password
		self.assertIsInstance(obj, InvalidPasswordHash)
		self.assertEqual(obj.value, None)
		self.assertTrue(obj.needs_rehash)

	def test_get_valid(self):
		self.test.password_hash = '{plain}password'
		obj = self.test.password
		self.assertIsInstance(obj, PlaintextPasswordHash)
		self.assertEqual(obj.value, '{plain}password')
		self.assertFalse(obj.needs_rehash)

	def test_get_needs_rehash(self):
		self.test.password_hash = '{ssha512}dOeDLmVpHJThhHeag10Hm2g4T7s3SBE6rGHcXUolXJHVufY4qT782rwZ/0XE6cuLcBZ0KpnwmUzRpAEtZBdv+JYEEtZQs/uC'
		obj = self.test.password
		self.assertIsInstance(obj, SaltedSHA512PasswordHash)
		self.assertEqual(obj.value, '{ssha512}dOeDLmVpHJThhHeag10Hm2g4T7s3SBE6rGHcXUolXJHVufY4qT782rwZ/0XE6cuLcBZ0KpnwmUzRpAEtZBdv+JYEEtZQs/uC')
		self.assertTrue(obj.needs_rehash)

	def test_set(self):
		self.test.password = 'password'
		self.assertEqual(self.test.password_hash, '{plain}password')

	def test_set_none(self):
		self.test.password = None
		self.assertIsNone(self.test.password_hash)