Files
ivatar/ivatar/ivataraccount/test_auth_models.py

280 lines
11 KiB
Python

# -*- coding: utf-8 -*-
"""
Test authentication models for ivatar
"""
import os
import django
from django.test import TestCase
from django.contrib.auth.models import User
from django.utils import timezone
os.environ["DJANGO_SETTINGS_MODULE"] = "ivatar.settings"
django.setup()
from ivatar.ivataraccount.auth_models import AuthToken, BruteForceAttempt
from ivatar.utils import random_string
class AuthTokenTestCase(TestCase):
"""
Test cases for AuthToken model
"""
def setUp(self):
"""Set up test data"""
self.username = random_string()
self.password = random_string()
self.email = f"{random_string()}@{random_string()}.com"
self.user = User.objects.create_user(
username=self.username, password=self.password, email=self.email
)
def test_auth_token_creation(self):
"""Test basic AuthToken creation"""
token = AuthToken.objects.create(
token="test-token-123", user=self.user, ip_address="127.0.0.1"
)
self.assertEqual(token.user, self.user)
self.assertEqual(token.token, "test-token-123")
self.assertEqual(token.ip_address, "127.0.0.1")
self.assertTrue(token.is_active)
self.assertIsNotNone(token.created_at)
self.assertIsNotNone(token.expires_at)
def test_auth_token_auto_expiration(self):
"""Test that expiration is set automatically"""
token = AuthToken.objects.create(token="test-token-456", user=self.user)
# Should be set to 1 hour from now
expected_expiry = timezone.now() + timezone.timedelta(hours=1)
time_diff = abs((token.expires_at - expected_expiry).total_seconds())
self.assertLess(time_diff, 5) # Within 5 seconds
def test_auth_token_is_expired(self):
"""Test token expiration checking"""
# Create expired token
past_time = timezone.now() - timezone.timedelta(hours=2)
token = AuthToken.objects.create(token="expired-token", user=self.user)
token.expires_at = past_time
token.save()
self.assertTrue(token.is_expired())
# Create valid token
future_time = timezone.now() + timezone.timedelta(hours=1)
valid_token = AuthToken.objects.create(token="valid-token", user=self.user)
valid_token.expires_at = future_time
valid_token.save()
self.assertFalse(valid_token.is_expired())
def test_auth_token_string_representation(self):
"""Test string representation of AuthToken"""
token = AuthToken.objects.create(token="test-token-789", user=self.user)
expected_str = f"Token for {self.user.username} (expires: {token.expires_at})"
self.assertEqual(str(token), expected_str)
def test_auth_token_unique_constraint(self):
"""Test that tokens must be unique"""
AuthToken.objects.create(token="unique-token", user=self.user)
# Try to create another token with the same value
with self.assertRaises(Exception): # IntegrityError
AuthToken.objects.create(token="unique-token", user=self.user)
class BruteForceAttemptTestCase(TestCase):
"""
Test cases for BruteForceAttempt model
"""
def setUp(self):
"""Set up test data"""
self.username = random_string()
self.password = random_string()
self.email = f"{random_string()}@{random_string()}.com"
self.user = User.objects.create_user(
username=self.username, password=self.password, email=self.email
)
self.ip_address = "127.0.0.1"
self.user_agent = "Mozilla/5.0 Test Browser"
def test_brute_force_attempt_creation_authenticated_user(self):
"""Test BruteForceAttempt creation for authenticated user"""
attempt = BruteForceAttempt.objects.create(
user=self.user, ip_address=self.ip_address, user_agent=self.user_agent
)
self.assertEqual(attempt.user, self.user)
self.assertEqual(attempt.ip_address, self.ip_address)
self.assertEqual(attempt.user_agent, self.user_agent)
self.assertEqual(attempt.attempt_count, 1)
self.assertFalse(attempt.is_blocked)
self.assertIsNotNone(attempt.first_attempt)
self.assertIsNotNone(attempt.last_attempt)
def test_brute_force_attempt_creation_unauthenticated_user(self):
"""Test BruteForceAttempt creation for unauthenticated user"""
username = random_string()
attempt = BruteForceAttempt.objects.create(
username=username, ip_address=self.ip_address, user_agent=self.user_agent
)
self.assertIsNone(attempt.user)
self.assertEqual(attempt.username, username)
self.assertEqual(attempt.ip_address, self.ip_address)
self.assertEqual(attempt.user_agent, self.user_agent)
self.assertEqual(attempt.attempt_count, 1)
self.assertFalse(attempt.is_blocked)
def test_brute_force_attempt_save_updates_last_attempt(self):
"""Test that save() updates last_attempt timestamp"""
attempt = BruteForceAttempt.objects.create(
user=self.user, ip_address=self.ip_address
)
original_last_attempt = attempt.last_attempt
# Wait a moment and save again
import time
time.sleep(0.1)
attempt.save()
self.assertGreater(attempt.last_attempt, original_last_attempt)
def test_should_block_within_time_window(self):
"""Test should_block() method within time window"""
attempt = BruteForceAttempt.objects.create(user=self.user, attempt_count=3)
# Should not block with 3 attempts (below threshold of 5)
self.assertFalse(attempt.should_block(max_attempts=5, time_window_minutes=15))
# Should block with 6 attempts (above threshold)
attempt.attempt_count = 6
attempt.save()
self.assertTrue(attempt.should_block(max_attempts=5, time_window_minutes=15))
def test_should_block_outside_time_window(self):
"""Test should_block() resets when outside time window"""
# Create attempt with old timestamp
old_time = timezone.now() - timezone.timedelta(minutes=20)
attempt = BruteForceAttempt.objects.create(
user=self.user, attempt_count=10 # Above threshold
)
attempt.first_attempt = old_time
attempt.save()
# Should reset and not block (outside 15-minute window)
self.assertFalse(attempt.should_block(max_attempts=5, time_window_minutes=15))
# Reset the attempt count using the new method
attempt.reset_if_expired(time_window_minutes=15)
# Should have reset attempt count
attempt.refresh_from_db()
self.assertEqual(attempt.attempt_count, 0)
self.assertFalse(attempt.is_blocked)
def test_should_block_when_explicitly_blocked(self):
"""Test should_block() when is_blocked is True"""
attempt = BruteForceAttempt.objects.create(
user=self.user, attempt_count=2, is_blocked=True
)
# Should block regardless of attempt count
self.assertTrue(attempt.should_block(max_attempts=5, time_window_minutes=15))
def test_increment_attempt(self):
"""Test increment_attempt() method"""
attempt = BruteForceAttempt.objects.create(user=self.user, attempt_count=3)
attempt.increment_attempt()
self.assertEqual(attempt.attempt_count, 4)
self.assertFalse(attempt.is_blocked) # Still below threshold
# Increment to threshold
attempt.increment_attempt()
self.assertEqual(attempt.attempt_count, 5)
self.assertTrue(attempt.is_blocked) # Should be blocked now
def test_increment_attempt_blocks_at_threshold(self):
"""Test that increment_attempt() blocks at threshold"""
attempt = BruteForceAttempt.objects.create(user=self.user, attempt_count=4)
attempt.increment_attempt()
self.assertEqual(attempt.attempt_count, 5)
self.assertTrue(attempt.is_blocked)
def test_brute_force_attempt_string_representation(self):
"""Test string representation of BruteForceAttempt"""
# Test with user
attempt_with_user = BruteForceAttempt.objects.create(
user=self.user, attempt_count=3
)
expected_str_user = f"Brute force attempt for {self.user.username} (3 attempts)"
self.assertEqual(str(attempt_with_user), expected_str_user)
# Test with username only
test_username = random_string()
attempt_with_username = BruteForceAttempt.objects.create(
username=test_username, ip_address=self.ip_address, attempt_count=2
)
expected_str_username = f"Brute force attempt for {test_username} (2 attempts)"
self.assertEqual(str(attempt_with_username), expected_str_username)
# Test with IP only
attempt_with_ip = BruteForceAttempt.objects.create(
ip_address=self.ip_address, attempt_count=1
)
expected_str_ip = f"Brute force attempt for {self.ip_address} (1 attempts)"
self.assertEqual(str(attempt_with_ip), expected_str_ip)
def test_unique_together_constraint(self):
"""Test unique_together constraint"""
test_username = random_string()
# Create first attempt
BruteForceAttempt.objects.create(
user=self.user, username=test_username, ip_address=self.ip_address
)
# Try to create duplicate (should fail)
with self.assertRaises(Exception): # IntegrityError
BruteForceAttempt.objects.create(
user=self.user, username=test_username, ip_address=self.ip_address
)
def test_get_or_create_behavior(self):
"""Test get_or_create behavior for brute force attempts"""
# First call should create
attempt, created = BruteForceAttempt.objects.get_or_create(
user=self.user, defaults={"attempt_count": 0}
)
self.assertTrue(created)
self.assertEqual(attempt.attempt_count, 0)
# Second call should get existing
attempt2, created2 = BruteForceAttempt.objects.get_or_create(
user=self.user, defaults={"attempt_count": 0}
)
self.assertFalse(created2)
self.assertEqual(attempt.id, attempt2.id)
self.assertEqual(attempt2.attempt_count, 0)
def test_custom_max_attempts_and_time_window(self):
"""Test custom max_attempts and time_window parameters"""
attempt = BruteForceAttempt.objects.create(user=self.user, attempt_count=2)
# Test with custom parameters
self.assertFalse(attempt.should_block(max_attempts=3, time_window_minutes=30))
attempt.attempt_count = 3
attempt.save()
self.assertTrue(attempt.should_block(max_attempts=3, time_window_minutes=30))