project initialization
Some checks failed
System Monitoring / Health Checks (push) Has been cancelled
System Monitoring / Performance Monitoring (push) Has been cancelled
System Monitoring / Database Monitoring (push) Has been cancelled
System Monitoring / Cache Monitoring (push) Has been cancelled
System Monitoring / Log Monitoring (push) Has been cancelled
System Monitoring / Resource Monitoring (push) Has been cancelled
System Monitoring / Uptime Monitoring (push) Has been cancelled
System Monitoring / Backup Monitoring (push) Has been cancelled
System Monitoring / Security Monitoring (push) Has been cancelled
System Monitoring / Monitoring Dashboard (push) Has been cancelled
System Monitoring / Alerting (push) Has been cancelled
Security Scanning / Dependency Scanning (push) Has been cancelled
Security Scanning / Code Security Scanning (push) Has been cancelled
Security Scanning / Secrets Scanning (push) Has been cancelled
Security Scanning / Container Security Scanning (push) Has been cancelled
Security Scanning / Compliance Checking (push) Has been cancelled
Security Scanning / Security Dashboard (push) Has been cancelled
Security Scanning / Security Remediation (push) Has been cancelled

This commit is contained in:
2025-10-05 02:37:33 +08:00
parent 2cbb6d5fa1
commit b3fff546e9
226 changed files with 97805 additions and 35 deletions

View File

@@ -0,0 +1,461 @@
"""
Unit tests for General Helper Utilities
Tests for general utility functions:
- Date/time helpers
- String helpers
- Number helpers
- File helpers
- Security helpers
Author: Claude
"""
import pytest
from django.test import TestCase
from django.utils import timezone
from datetime import date, datetime, time, timedelta
from decimal import Decimal
import hashlib
import json
from backend.src.core.utils.helpers import (
format_datetime,
parse_date_string,
generate_unique_id,
sanitize_filename,
calculate_percentage,
format_currency,
truncate_text,
validate_email,
generate_random_string,
hash_password,
verify_password,
get_file_extension,
format_file_size,
is_valid_json,
flatten_dict,
merge_dicts,
retry_function,
cache_result
)
class HelperUtilitiesTest(TestCase):
"""Test cases for helper utilities"""
def test_format_datetime(self):
"""Test datetime formatting"""
test_datetime = datetime(2024, 1, 15, 14, 30, 45, tzinfo=timezone.utc)
# Test default formatting
formatted = format_datetime(test_datetime)
self.assertIn('2024', formatted)
self.assertIn('14:30', formatted)
# Test custom formatting
custom_format = format_datetime(test_datetime, '%Y-%m-%d')
self.assertEqual(custom_format, '2024-01-15')
# Test timezone conversion
local_format = format_datetime(test_datetime, timezone_name='Asia/Kuala_Lumpur')
self.assertIn('22:30', local_format) # UTC+8
def test_parse_date_string(self):
"""Test date string parsing"""
test_cases = [
{'input': '2024-01-15', 'expected': date(2024, 1, 15)},
{'input': '15/01/2024', 'expected': date(2024, 1, 15)},
{'input': '01-15-2024', 'expected': date(2024, 1, 15)},
{'input': '20240115', 'expected': date(2024, 1, 15)},
]
for case in test_cases:
result = parse_date_string(case['input'])
self.assertEqual(result, case['expected'])
def test_parse_date_string_invalid(self):
"""Test invalid date string parsing"""
invalid_dates = [
'invalid-date',
'2024-13-01', # Invalid month
'2024-02-30', # Invalid day
'2024/02/30', # Invalid format
]
for date_str in invalid_dates:
with self.assertRaises(Exception):
parse_date_string(date_str)
def test_generate_unique_id(self):
"""Test unique ID generation"""
# Test default generation
id1 = generate_unique_id()
id2 = generate_unique_id()
self.assertNotEqual(id1, id2)
self.assertEqual(len(id1), 36) # UUID length
# Test with prefix
prefixed_id = generate_unique_id(prefix='USR')
self.assertTrue(prefixed_id.startswith('USR_'))
# Test with custom length
short_id = generate_unique_id(length=8)
self.assertEqual(len(short_id), 8)
def test_sanitize_filename(self):
"""Test filename sanitization"""
test_cases = [
{
'input': 'test file.txt',
'expected': 'test_file.txt'
},
{
'input': 'my*document?.pdf',
'expected': 'my_document.pdf'
},
{
'input': ' spaces file .jpg ',
'expected': 'spaces_file.jpg'
},
{
'input': '../../../malicious/path.txt',
'expected': 'malicious_path.txt'
}
]
for case in test_cases:
result = sanitize_filename(case['input'])
self.assertEqual(result, case['expected'])
def test_calculate_percentage(self):
"""Test percentage calculation"""
test_cases = [
{'part': 50, 'total': 100, 'expected': 50.0},
{'part': 25, 'total': 200, 'expected': 12.5},
{'part': 0, 'total': 100, 'expected': 0.0},
{'part': 100, 'total': 100, 'expected': 100.0},
]
for case in test_cases:
result = calculate_percentage(case['part'], case['total'])
self.assertEqual(result, case['expected'])
def test_calculate_percentage_invalid(self):
"""Test percentage calculation with invalid inputs"""
# Division by zero
with self.assertRaises(Exception):
calculate_percentage(50, 0)
# Negative values
with self.assertRaises(Exception):
calculate_percentage(-10, 100)
def test_format_currency(self):
"""Test currency formatting"""
amount = Decimal('1234.56')
# Test default formatting (MYR)
formatted = format_currency(amount)
self.assertEqual(formatted, 'RM 1,234.56')
# Test different currency
usd_formatted = format_currency(amount, currency='USD')
self.assertEqual(usd_formatted, '$ 1,234.56')
# Test custom locale
custom_locale = format_currency(amount, locale='en_US')
self.assertIn('$', custom_locale)
# Test no decimals
no_decimals = format_currency(amount, decimals=0)
self.assertEqual(no_decimals, 'RM 1,235')
def test_truncate_text(self):
"""Test text truncation"""
text = "This is a long text that needs to be truncated"
# Test basic truncation
truncated = truncate_text(text, 20)
self.assertEqual(len(truncated), 20)
self.assertTrue(truncated.endswith('...'))
# Test with custom suffix
custom_suffix = truncate_text(text, 15, suffix=' [more]')
self.assertTrue(custom_suffix.endswith(' [more]'))
# Test text shorter than limit
short_text = "Short text"
result = truncate_text(short_text, 20)
self.assertEqual(result, short_text)
def test_validate_email(self):
"""Test email validation"""
valid_emails = [
'user@example.com',
'test.email+tag@domain.co.uk',
'user_name@sub.domain.com',
'123user@example.org'
]
invalid_emails = [
'invalid-email',
'@example.com',
'user@',
'user@.com',
'user..name@example.com',
'user@example..com'
]
for email in valid_emails:
self.assertTrue(validate_email(email))
for email in invalid_emails:
self.assertFalse(validate_email(email))
def test_generate_random_string(self):
"""Test random string generation"""
# Test default length
random_str = generate_random_string()
self.assertEqual(len(random_str), 12)
# Test custom length
custom_length = generate_random_string(length=20)
self.assertEqual(len(custom_length), 20)
# Test different character sets
numeric = generate_random_string(length=10, chars='0123456789')
self.assertTrue(numeric.isdigit())
# Test uniqueness
str1 = generate_random_string(length=20)
str2 = generate_random_string(length=20)
self.assertNotEqual(str1, str2)
def test_hash_password(self):
"""Test password hashing"""
password = 'test_password_123'
# Test password hashing
hashed = hash_password(password)
self.assertNotEqual(hashed, password)
self.assertIn('$', hashed) # bcrypt hash format
# Test same password produces different hashes (salt)
hashed2 = hash_password(password)
self.assertNotEqual(hashed, hashed2)
def test_verify_password(self):
"""Test password verification"""
password = 'test_password_123'
hashed = hash_password(password)
# Test correct password
self.assertTrue(verify_password(password, hashed))
# Test incorrect password
self.assertFalse(verify_password('wrong_password', hashed))
# Test invalid hash
self.assertFalse(verify_password(password, 'invalid_hash'))
def test_get_file_extension(self):
"""Test file extension extraction"""
test_cases = [
{'input': 'document.pdf', 'expected': '.pdf'},
{'input': 'image.JPG', 'expected': '.jpg'},
{'input': 'archive.tar.gz', 'expected': '.gz'},
{'input': 'no_extension', 'expected': ''},
{'input': '.hidden_file', 'expected': ''},
]
for case in test_cases:
result = get_file_extension(case['input'])
self.assertEqual(result.lower(), case['expected'].lower())
def test_format_file_size(self):
"""Test file size formatting"""
test_cases = [
{'bytes': 500, 'expected': '500 B'},
{'bytes': 1024, 'expected': '1 KB'},
{'bytes': 1536, 'expected': '1.5 KB'},
{'bytes': 1048576, 'expected': '1 MB'},
{'bytes': 1073741824, 'expected': '1 GB'},
{'bytes': 1099511627776, 'expected': '1 TB'},
]
for case in test_cases:
result = format_file_size(case['bytes'])
self.assertEqual(result, case['expected'])
def test_is_valid_json(self):
"""Test JSON validation"""
valid_jsons = [
'{"key": "value"}',
'[]',
'null',
'123',
'"string"',
'{"nested": {"key": "value"}}'
]
invalid_jsons = [
'{invalid json}',
'undefined',
'function() {}',
'{key: "value"}', # Unquoted key
'["unclosed array"',
]
for json_str in valid_jsons:
self.assertTrue(is_valid_json(json_str))
for json_str in invalid_jsons:
self.assertFalse(is_valid_json(json_str))
def test_flatten_dict(self):
"""Test dictionary flattening"""
nested_dict = {
'user': {
'name': 'John',
'profile': {
'age': 30,
'city': 'KL'
}
},
'settings': {
'theme': 'dark',
'notifications': True
}
}
flattened = flatten_dict(nested_dict)
expected_keys = [
'user_name',
'user_profile_age',
'user_profile_city',
'settings_theme',
'settings_notifications'
]
for key in expected_keys:
self.assertIn(key, flattened)
self.assertEqual(flattened['user_name'], 'John')
self.assertEqual(flattened['user_profile_age'], 30)
def test_merge_dicts(self):
"""Test dictionary merging"""
dict1 = {'a': 1, 'b': 2, 'c': 3}
dict2 = {'b': 20, 'd': 4, 'e': 5}
merged = merge_dicts(dict1, dict2)
self.assertEqual(merged['a'], 1) # From dict1
self.assertEqual(merged['b'], 20) # From dict2 (overwritten)
self.assertEqual(merged['c'], 3) # From dict1
self.assertEqual(merged['d'], 4) # From dict2
self.assertEqual(merged['e'], 5) # From dict2
def test_retry_function(self):
"""Test function retry mechanism"""
# Test successful execution
def successful_function():
return "success"
result = retry_function(successful_function, max_retries=3)
self.assertEqual(result, "success")
# Test function that fails then succeeds
call_count = 0
def flaky_function():
nonlocal call_count
call_count += 1
if call_count < 3:
raise Exception("Temporary failure")
return "eventual_success"
result = retry_function(flaky_function, max_retries=5)
self.assertEqual(result, "eventual_success")
self.assertEqual(call_count, 3)
# Test function that always fails
def failing_function():
raise Exception("Permanent failure")
with self.assertRaises(Exception):
retry_function(failing_function, max_retries=3)
def test_cache_result(self):
"""Test result caching decorator"""
# Create a function that counts calls
call_count = 0
@cache_result(timeout=60) # 60 second cache
def expensive_function(x, y):
nonlocal call_count
call_count += 1
return x + y
# First call should execute function
result1 = expensive_function(2, 3)
self.assertEqual(result1, 5)
self.assertEqual(call_count, 1)
# Second call with same arguments should use cache
result2 = expensive_function(2, 3)
self.assertEqual(result2, 5)
self.assertEqual(call_count, 1) # No additional call
# Call with different arguments should execute function
result3 = expensive_function(3, 4)
self.assertEqual(result3, 7)
self.assertEqual(call_count, 2)
def test_decimal_conversion(self):
"""Test decimal conversion utilities"""
# Test string to decimal
decimal_value = Decimal('123.45')
self.assertEqual(decimal_value, Decimal('123.45'))
# Test float to decimal (with precision warning)
float_value = 123.45
decimal_from_float = Decimal(str(float_value))
self.assertEqual(decimal_from_float, Decimal('123.45'))
def test_timezone_handling(self):
"""Test timezone handling utilities"""
# Test timezone aware datetime
utc_now = timezone.now()
self.assertIsNotNone(utc_now.tzinfo)
# Test timezone conversion
kl_time = format_datetime(utc_now, timezone_name='Asia/Kuala_Lumpur')
self.assertIn('+08', kl_time)
def test_string_manipulation(self):
"""Test string manipulation utilities"""
# Test string cleaning
dirty_string = " Hello World \n\t"
clean_string = " ".join(dirty_string.split())
self.assertEqual(clean_string, "Hello World")
# Test case conversion
test_string = "Hello World"
self.assertEqual(test_string.lower(), "hello world")
self.assertEqual(test_string.upper(), "HELLO WORLD")
self.assertEqual(test_string.title(), "Hello World")
def test_list_operations(self):
"""Test list operation utilities"""
# Test list deduplication
duplicate_list = [1, 2, 2, 3, 4, 4, 5]
unique_list = list(set(duplicate_list))
self.assertEqual(len(unique_list), 5)
# Test list sorting
unsorted_list = [3, 1, 4, 1, 5, 9, 2, 6]
sorted_list = sorted(unsorted_list)
self.assertEqual(sorted_list, [1, 1, 2, 3, 4, 5, 6, 9])