feat: Add comprehensive documentation suite and reorganize project structure

- Created complete documentation in docs/ directory
- Added PROJECT_OVERVIEW.md with feature highlights and getting started guide
- Added ARCHITECTURE.md with system design and technical details
- Added SECURITY.md with comprehensive security implementation guide
- Added DEVELOPMENT.md with development workflows and best practices
- Added DEPLOYMENT.md with production deployment instructions
- Added API.md with complete REST API documentation
- Added CONTRIBUTING.md with contribution guidelines
- Added CHANGELOG.md with version history and migration notes
- Reorganized all documentation files into docs/ directory for better organization
- Updated README.md with proper documentation links and quick navigation
- Enhanced project structure with professional documentation standards
This commit is contained in:
SamiAhmed7777
2025-10-21 00:39:45 -07:00
commit 0b7e2d0a5b
6080 changed files with 1332936 additions and 0 deletions

531
tests/Unit/AuthTest.php Normal file
View File

@@ -0,0 +1,531 @@
<?php
namespace EasyStream\Tests\Unit;
use PHPUnit\Framework\TestCase;
use VAuth;
class AuthTest extends TestCase
{
private $auth;
private $testUserId;
protected function setUp(): void
{
$this->auth = VAuth::getInstance();
// Clear session data
if (isset($_SESSION)) {
$_SESSION = [];
}
// Clear cookies
$_COOKIE = [];
// Mock server variables
$_SERVER = [
'REQUEST_URI' => '/test',
'REQUEST_METHOD' => 'GET',
'HTTP_USER_AGENT' => 'PHPUnit Auth Test',
'REMOTE_ADDR' => '127.0.0.1',
'HTTPS' => '1'
];
// Clean up any existing test data
$this->cleanupTestData();
}
protected function tearDown(): void
{
// Clean up test data
$this->cleanupTestData();
// Clear session
if (isset($_SESSION)) {
$_SESSION = [];
}
$_COOKIE = [];
}
private function cleanupTestData()
{
global $class_database;
$db = $class_database->dbConnection();
// Clean up test users and related data
$testEmails = ['test@example.com', 'testuser@example.com', 'newuser@example.com'];
$testUsernames = ['testuser', 'newuser', 'authtest'];
foreach ($testEmails as $email) {
$db->Execute("DELETE FROM db_sessions WHERE user_id IN (SELECT user_id FROM db_users WHERE email = ?)", [$email]);
$db->Execute("DELETE FROM db_login_history WHERE email = ?", [$email]);
$db->Execute("DELETE FROM db_users WHERE email = ?", [$email]);
}
foreach ($testUsernames as $username) {
$db->Execute("DELETE FROM db_sessions WHERE user_id IN (SELECT user_id FROM db_users WHERE username = ?)", [$username]);
$db->Execute("DELETE FROM db_login_history WHERE username = ?", [$username]);
$db->Execute("DELETE FROM db_users WHERE username = ?", [$username]);
}
}
/**
* Test VAuth singleton pattern
*/
public function testSingletonPattern()
{
$auth1 = VAuth::getInstance();
$auth2 = VAuth::getInstance();
$this->assertSame($auth1, $auth2);
$this->assertInstanceOf(VAuth::class, $auth1);
}
/**
* Test user registration with valid data
*/
public function testUserRegistrationSuccess()
{
$userData = [
'username' => 'testuser',
'email' => 'test@example.com',
'password' => 'TestPassword123!'
];
$result = $this->auth->register($userData);
$this->assertTrue($result['success']);
$this->assertStringContainsString('Registration successful', $result['message']);
$this->assertArrayHasKey('user_id', $result);
$this->testUserId = $result['user_id'];
}
/**
* Test user registration with invalid data
*/
public function testUserRegistrationValidation()
{
// Test missing username
$result = $this->auth->register([
'email' => 'test@example.com',
'password' => 'TestPassword123!'
]);
$this->assertFalse($result['success']);
$this->assertStringContainsString('username', $result['message']);
// Test missing email
$result = $this->auth->register([
'username' => 'testuser',
'password' => 'TestPassword123!'
]);
$this->assertFalse($result['success']);
$this->assertStringContainsString('email', $result['message']);
// Test missing password
$result = $this->auth->register([
'username' => 'testuser',
'email' => 'test@example.com'
]);
$this->assertFalse($result['success']);
$this->assertStringContainsString('password', $result['message']);
// Test invalid email
$result = $this->auth->register([
'username' => 'testuser',
'email' => 'invalid-email',
'password' => 'TestPassword123!'
]);
$this->assertFalse($result['success']);
$this->assertStringContainsString('Invalid email', $result['message']);
// Test weak password
$result = $this->auth->register([
'username' => 'testuser',
'email' => 'test@example.com',
'password' => 'weak'
]);
$this->assertFalse($result['success']);
$this->assertStringContainsString('8 characters', $result['message']);
// Test password without special characters
$result = $this->auth->register([
'username' => 'testuser',
'email' => 'test@example.com',
'password' => 'TestPassword123'
]);
$this->assertFalse($result['success']);
$this->assertStringContainsString('special character', $result['message']);
}
/**
* Test duplicate user registration
*/
public function testDuplicateUserRegistration()
{
$userData = [
'username' => 'testuser',
'email' => 'test@example.com',
'password' => 'TestPassword123!'
];
// First registration should succeed
$result1 = $this->auth->register($userData);
$this->assertTrue($result1['success']);
// Second registration with same username should fail
$result2 = $this->auth->register($userData);
$this->assertFalse($result2['success']);
$this->assertStringContainsString('already exists', $result2['message']);
// Registration with same email but different username should fail
$userData['username'] = 'differentuser';
$result3 = $this->auth->register($userData);
$this->assertFalse($result3['success']);
$this->assertStringContainsString('already exists', $result3['message']);
}
/**
* Test email verification
*/
public function testEmailVerification()
{
// Register a user first
$userData = [
'username' => 'testuser',
'email' => 'test@example.com',
'password' => 'TestPassword123!'
];
$registerResult = $this->auth->register($userData);
$this->assertTrue($registerResult['success']);
// Get verification token from database
global $class_database;
$db = $class_database->dbConnection();
$sql = "SELECT verification_token FROM db_users WHERE email = ?";
$result = $db->Execute($sql, ['test@example.com']);
$this->assertFalse($result->EOF);
$token = $result->fields['verification_token'];
$this->assertNotEmpty($token);
// Test email verification
$verifyResult = $this->auth->verifyEmail($token);
$this->assertTrue($verifyResult['success']);
$this->assertStringContainsString('verified successfully', $verifyResult['message']);
// Test verification with invalid token
$invalidResult = $this->auth->verifyEmail('invalid_token');
$this->assertFalse($invalidResult['success']);
$this->assertStringContainsString('Invalid', $invalidResult['message']);
// Test verification with already used token
$usedResult = $this->auth->verifyEmail($token);
$this->assertFalse($usedResult['success']);
$this->assertStringContainsString('Invalid', $usedResult['message']);
}
/**
* Test user login with valid credentials
*/
public function testLoginSuccess()
{
// Create and verify a test user
$this->createVerifiedTestUser();
// Test login with username
$result = $this->auth->login('testuser', 'TestPassword123!');
$this->assertTrue($result['success']);
$this->assertStringContainsString('Login successful', $result['message']);
$this->assertArrayHasKey('user', $result);
$this->assertEquals('testuser', $result['user']['username']);
// Logout for next test
$this->auth->logout();
// Test login with email
$result = $this->auth->login('test@example.com', 'TestPassword123!');
$this->assertTrue($result['success']);
$this->assertEquals('test@example.com', $result['user']['email']);
}
/**
* Test login with invalid credentials
*/
public function testLoginFailure()
{
$this->createVerifiedTestUser();
// Test with wrong password
$result = $this->auth->login('testuser', 'WrongPassword123!');
$this->assertFalse($result['success']);
$this->assertStringContainsString('Invalid credentials', $result['message']);
// Test with non-existent user
$result = $this->auth->login('nonexistent', 'TestPassword123!');
$this->assertFalse($result['success']);
$this->assertStringContainsString('Invalid credentials', $result['message']);
// Test with empty credentials
$result = $this->auth->login('', '');
$this->assertFalse($result['success']);
$this->assertStringContainsString('required', $result['message']);
}
/**
* Test login rate limiting
*/
public function testLoginRateLimiting()
{
$this->createVerifiedTestUser();
// Make multiple failed login attempts
for ($i = 0; $i < 6; $i++) {
$result = $this->auth->login('testuser', 'WrongPassword');
}
// Next attempt should be rate limited
$result = $this->auth->login('testuser', 'TestPassword123!');
$this->assertFalse($result['success']);
$this->assertStringContainsString('Too many', $result['message']);
}
/**
* Test session management
*/
public function testSessionManagement()
{
$this->createVerifiedTestUser();
// Test not authenticated initially
$this->assertFalse($this->auth->isAuthenticated());
$this->assertNull($this->auth->getCurrentUser());
// Login
$result = $this->auth->login('testuser', 'TestPassword123!');
$this->assertTrue($result['success']);
// Test authenticated after login
$this->assertTrue($this->auth->isAuthenticated());
$currentUser = $this->auth->getCurrentUser();
$this->assertNotNull($currentUser);
$this->assertEquals('testuser', $currentUser['username']);
$this->assertEquals('test@example.com', $currentUser['email']);
// Test logout
$logoutResult = $this->auth->logout();
$this->assertTrue($logoutResult['success']);
// Test not authenticated after logout
$this->assertFalse($this->auth->isAuthenticated());
$this->assertNull($this->auth->getCurrentUser());
}
/**
* Test remember me functionality
*/
public function testRememberMeFunctionality()
{
$this->createVerifiedTestUser();
// Login with remember me
$result = $this->auth->login('testuser', 'TestPassword123!', true);
$this->assertTrue($result['success']);
// Check if remember token cookie would be set (we can't actually test cookie setting in unit tests)
$this->assertTrue($this->auth->isAuthenticated());
// Logout
$this->auth->logout();
$this->assertFalse($this->auth->isAuthenticated());
}
/**
* Test password reset request
*/
public function testPasswordResetRequest()
{
$this->createVerifiedTestUser();
// Test valid email
$result = $this->auth->requestPasswordReset('test@example.com');
$this->assertTrue($result['success']);
$this->assertStringContainsString('reset link', $result['message']);
// Test invalid email format
$result = $this->auth->requestPasswordReset('invalid-email');
$this->assertFalse($result['success']);
$this->assertStringContainsString('Invalid email', $result['message']);
// Test non-existent email (should still return success for security)
$result = $this->auth->requestPasswordReset('nonexistent@example.com');
$this->assertTrue($result['success']);
$this->assertStringContainsString('reset link', $result['message']);
}
/**
* Test password reset with token
*/
public function testPasswordReset()
{
$this->createVerifiedTestUser();
// Request password reset
$resetResult = $this->auth->requestPasswordReset('test@example.com');
$this->assertTrue($resetResult['success']);
// Get reset token from database
global $class_database;
$db = $class_database->dbConnection();
$sql = "SELECT reset_token FROM db_users WHERE email = ?";
$result = $db->Execute($sql, ['test@example.com']);
$this->assertFalse($result->EOF);
$token = $result->fields['reset_token'];
$this->assertNotEmpty($token);
// Test password reset with valid token
$newPassword = 'NewPassword123!';
$resetResult = $this->auth->resetPassword($token, $newPassword);
$this->assertTrue($resetResult['success']);
$this->assertStringContainsString('reset successfully', $resetResult['message']);
// Test login with new password
$loginResult = $this->auth->login('testuser', $newPassword);
$this->assertTrue($loginResult['success']);
// Test old password no longer works
$this->auth->logout();
$oldLoginResult = $this->auth->login('testuser', 'TestPassword123!');
$this->assertFalse($oldLoginResult['success']);
}
/**
* Test password reset validation
*/
public function testPasswordResetValidation()
{
// Test invalid token
$result = $this->auth->resetPassword('invalid_token', 'NewPassword123!');
$this->assertFalse($result['success']);
$this->assertStringContainsString('Invalid', $result['message']);
// Test weak password
$result = $this->auth->resetPassword('some_token', 'weak');
$this->assertFalse($result['success']);
$this->assertStringContainsString('8 characters', $result['message']);
// Test password without special characters
$result = $this->auth->resetPassword('some_token', 'NewPassword123');
$this->assertFalse($result['success']);
$this->assertStringContainsString('special character', $result['message']);
}
/**
* Test password reset rate limiting
*/
public function testPasswordResetRateLimiting()
{
$this->createVerifiedTestUser();
// Make multiple password reset requests
for ($i = 0; $i < 4; $i++) {
$this->auth->requestPasswordReset('test@example.com');
}
// Next request should be rate limited
$result = $this->auth->requestPasswordReset('test@example.com');
$this->assertFalse($result['success']);
$this->assertStringContainsString('Too many', $result['message']);
}
/**
* Test session security features
*/
public function testSessionSecurity()
{
$this->createVerifiedTestUser();
// Login
$this->auth->login('testuser', 'TestPassword123!');
$this->assertTrue($this->auth->isAuthenticated());
$originalSessionId = session_id();
// Simulate session regeneration (happens during login)
$this->assertNotEmpty($originalSessionId);
// Test session data integrity
$this->assertEquals('testuser', $_SESSION['USERNAME']);
$this->assertEquals('test@example.com', $_SESSION['EMAIL']);
$this->assertArrayHasKey('SESSION_TOKEN', $_SESSION);
$this->assertArrayHasKey('LOGIN_TIME', $_SESSION);
$this->assertArrayHasKey('IP_ADDRESS', $_SESSION);
}
/**
* Helper method to create a verified test user
*/
private function createVerifiedTestUser()
{
$userData = [
'username' => 'testuser',
'email' => 'test@example.com',
'password' => 'TestPassword123!'
];
$registerResult = $this->auth->register($userData);
$this->assertTrue($registerResult['success']);
// Get and use verification token
global $class_database;
$db = $class_database->dbConnection();
$sql = "SELECT verification_token FROM db_users WHERE email = ?";
$result = $db->Execute($sql, ['test@example.com']);
if (!$result->EOF) {
$token = $result->fields['verification_token'];
$this->auth->verifyEmail($token);
}
}
/**
* Test edge cases and error handling
*/
public function testEdgeCases()
{
// Test registration with null values
$result = $this->auth->register([
'username' => null,
'email' => null,
'password' => null
]);
$this->assertFalse($result['success']);
// Test login with null values
$result = $this->auth->login(null, null);
$this->assertFalse($result['success']);
// Test very long username
$longUsername = str_repeat('a', 100);
$result = $this->auth->register([
'username' => $longUsername,
'email' => 'test@example.com',
'password' => 'TestPassword123!'
]);
$this->assertFalse($result['success']);
// Test SQL injection attempts
$maliciousUsername = "admin'; DROP TABLE db_users; --";
$result = $this->auth->register([
'username' => $maliciousUsername,
'email' => 'test@example.com',
'password' => 'TestPassword123!'
]);
// Should either fail validation or be safely escaped
$this->assertIsArray($result);
}
}

View File

@@ -0,0 +1,157 @@
<?php
use PHPUnit\Framework\TestCase;
class ContentUploadTest extends TestCase
{
private $storageDir;
private $originalUploadDir;
private $originalUploadUrl;
protected function setUp(): void
{
parent::setUp();
$this->storageDir = __DIR__ . '/../temp/uploads';
if (!is_dir($this->storageDir)) {
mkdir($this->storageDir, 0755, true);
}
global $cfg;
$this->originalUploadDir = $cfg['upload_files_dir'] ?? null;
$this->originalUploadUrl = $cfg['upload_files_url'] ?? null;
$cfg['upload_files_dir'] = $this->storageDir;
$cfg['upload_files_url'] = 'https://example.com/uploads';
// Reset singleton for clean state between tests
$reflection = new ReflectionProperty(VContent::class, 'instance');
$reflection->setAccessible(true);
$reflection->setValue(null);
}
protected function tearDown(): void
{
global $cfg;
if ($this->originalUploadDir !== null) {
$cfg['upload_files_dir'] = $this->originalUploadDir;
}
if ($this->originalUploadUrl !== null) {
$cfg['upload_files_url'] = $this->originalUploadUrl;
}
if (is_dir($this->storageDir)) {
$this->removeDirectory($this->storageDir);
}
$reflection = new ReflectionProperty(VContent::class, 'instance');
$reflection->setAccessible(true);
$reflection->setValue(null);
parent::tearDown();
}
public function testHandleUploadStoresFileSuccessfully(): void
{
$content = VContent::getInstance();
$tmpFile = tempnam(sys_get_temp_dir(), 'upload');
file_put_contents($tmpFile, str_repeat('A', 1024));
$file = [
'name' => 'test-video.mp4',
'tmp_name' => $tmpFile,
'size' => filesize($tmpFile),
'error' => UPLOAD_ERR_OK,
'type' => 'video/mp4'
];
$result = $content->handleUpload($file, 123, [
'type' => 'video',
'store_metadata' => false
]);
$this->assertTrue($result['success'], 'Expected upload to succeed');
$this->assertArrayHasKey('data', $result);
$data = $result['data'];
$this->assertEquals('video', $data['type']);
$this->assertNotEmpty($data['stored_path']);
$storedPath = $this->storageDir . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $data['stored_path']);
$this->assertFileExists($storedPath, 'Stored file should exist on disk');
$progress = $content->getUploadProgress($data['upload_id']);
$this->assertEquals('completed', $progress['status']);
$this->assertEquals(100, $progress['progress']);
}
public function testHandleUploadRejectsInvalidMimeType(): void
{
$content = VContent::getInstance();
$tmpFile = tempnam(sys_get_temp_dir(), 'upload');
file_put_contents($tmpFile, "<?php echo 'malicious'; ?>");
$file = [
'name' => 'malware.php',
'tmp_name' => $tmpFile,
'size' => filesize($tmpFile),
'error' => UPLOAD_ERR_OK,
'type' => 'text/x-php'
];
$result = $content->handleUpload($file, 321, [
'type' => 'document',
'store_metadata' => false
]);
$this->assertFalse($result['success'], 'Upload should fail for dangerous MIME types');
$this->assertNotEmpty($result['message']);
}
public function testProgressLifecycle(): void
{
$content = VContent::getInstance();
$uploadId = 'progress_test_' . bin2hex(random_bytes(4));
$content->initProgress($uploadId, 2000);
$progress = $content->getUploadProgress($uploadId);
$this->assertEquals('in_progress', $progress['status']);
$this->assertEquals(0, $progress['progress']);
$content->updateProgress($uploadId, 1000, 2000);
$progress = $content->getUploadProgress($uploadId);
$this->assertEquals(50, $progress['progress']);
$content->completeProgress($uploadId);
$progress = $content->getUploadProgress($uploadId);
$this->assertEquals('completed', $progress['status']);
$this->assertEquals(100, $progress['progress']);
$content->cleanupProgress($uploadId);
$progress = $content->getUploadProgress($uploadId);
$this->assertEquals('unknown', $progress['status']);
}
private function removeDirectory($dir): void
{
if (!is_dir($dir)) {
return;
}
$items = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($items as $item) {
if ($item->isDir()) {
@rmdir($item->getRealPath());
} else {
@unlink($item->getRealPath());
}
}
@rmdir($dir);
}
}

View File

@@ -0,0 +1,224 @@
<?php
namespace EasyStream\Tests\Unit;
use PHPUnit\Framework\TestCase;
use VErrorHandler;
class ErrorHandlerTest extends TestCase
{
private $errorHandler;
private $originalErrorReporting;
protected function setUp(): void
{
$this->errorHandler = VErrorHandler::getInstance();
$this->originalErrorReporting = error_reporting();
// Set up test environment
global $cfg;
$cfg['debug_mode'] = false; // Test production mode
}
protected function tearDown(): void
{
// Restore original error reporting
error_reporting($this->originalErrorReporting);
}
/**
* Test error handler singleton pattern
*/
public function testSingletonPattern()
{
$handler1 = VErrorHandler::getInstance();
$handler2 = VErrorHandler::getInstance();
$this->assertSame($handler1, $handler2);
}
/**
* Test error type detection
*/
public function testErrorTypeDetection()
{
// Test that error handler can be instantiated without errors
$this->assertInstanceOf(VErrorHandler::class, $this->errorHandler);
}
/**
* Test error logging functionality
*/
public function testErrorLogging()
{
// Test application error logging
$this->errorHandler->logApplicationError('Test application error', ['test' => true]);
// Test validation error logging
$this->errorHandler->logValidationError('email', 'invalid-email', 'email_format', ['form' => 'registration']);
// Test authentication error logging
$this->errorHandler->logAuthError('Invalid credentials', 'testuser', ['ip' => '127.0.0.1']);
// Verify no exceptions were thrown
$this->assertTrue(true);
}
/**
* Test error handling in production mode
*/
public function testProductionMode()
{
global $cfg;
$cfg['debug_mode'] = false;
// Create new instance to test production mode
$handler = VErrorHandler::getInstance();
$this->assertInstanceOf(VErrorHandler::class, $handler);
}
/**
* Test error handling in debug mode
*/
public function testDebugMode()
{
global $cfg;
$cfg['debug_mode'] = true;
// Create new instance to test debug mode
$handler = VErrorHandler::getInstance();
$this->assertInstanceOf(VErrorHandler::class, $handler);
}
/**
* Test error severity mapping
*/
public function testErrorSeverityMapping()
{
// Test different error severities are handled properly
$severities = [
E_ERROR,
E_WARNING,
E_NOTICE,
E_USER_ERROR,
E_USER_WARNING,
E_USER_NOTICE
];
foreach ($severities as $severity) {
// Test that each severity can be processed
$this->assertIsInt($severity);
}
$this->assertTrue(true);
}
/**
* Test custom error contexts
*/
public function testCustomErrorContexts()
{
$contexts = [
['user_id' => 123, 'action' => 'upload'],
['ip' => '192.168.1.1', 'user_agent' => 'Test Browser'],
['request_id' => 'req_123', 'session_id' => 'sess_456']
];
foreach ($contexts as $context) {
$this->errorHandler->logApplicationError('Test error with context', $context);
}
$this->assertTrue(true);
}
/**
* Test error message sanitization
*/
public function testErrorMessageSanitization()
{
$maliciousMessages = [
'Error with <script>alert("xss")</script>',
'Database error: SELECT * FROM users WHERE password = "secret"',
'Path traversal: ../../../etc/passwd'
];
foreach ($maliciousMessages as $message) {
$this->errorHandler->logApplicationError($message);
}
// Verify no exceptions were thrown
$this->assertTrue(true);
}
/**
* Test error rate limiting
*/
public function testErrorRateLimiting()
{
// Log multiple similar errors rapidly
for ($i = 0; $i < 10; $i++) {
$this->errorHandler->logApplicationError('Repeated error message');
}
// Verify system handles repeated errors gracefully
$this->assertTrue(true);
}
/**
* Test memory usage during error handling
*/
public function testMemoryUsageDuringErrorHandling()
{
$initialMemory = memory_get_usage();
// Generate multiple errors
for ($i = 0; $i < 100; $i++) {
$this->errorHandler->logApplicationError("Memory test error {$i}");
}
$finalMemory = memory_get_usage();
// Verify memory usage didn't grow excessively
$memoryIncrease = $finalMemory - $initialMemory;
$this->assertLessThan(10 * 1024 * 1024, $memoryIncrease); // Less than 10MB increase
}
/**
* Test error handling with invalid data
*/
public function testErrorHandlingWithInvalidData()
{
// Test with null values
$this->errorHandler->logApplicationError(null);
// Test with empty strings
$this->errorHandler->logApplicationError('');
// Test with very long messages
$longMessage = str_repeat('A', 10000);
$this->errorHandler->logApplicationError($longMessage);
// Test with special characters
$specialMessage = "Error with unicode: 世界 and emojis: 🚨";
$this->errorHandler->logApplicationError($specialMessage);
$this->assertTrue(true);
}
/**
* Test concurrent error handling
*/
public function testConcurrentErrorHandling()
{
// Simulate concurrent errors
for ($i = 0; $i < 50; $i++) {
$this->errorHandler->logApplicationError("Concurrent error {$i}");
$this->errorHandler->logValidationError("field{$i}", "value{$i}", 'required');
$this->errorHandler->logAuthError("Auth error {$i}", "user{$i}");
}
$this->assertTrue(true);
}
}

View File

448
tests/Unit/RBACTest.php Normal file
View File

@@ -0,0 +1,448 @@
<?php
namespace EasyStream\Tests\Unit;
use PHPUnit\Framework\TestCase;
use VRBAC;
use VAuth;
class RBACTest extends TestCase
{
private $rbac;
private $auth;
private $testUserId;
private $adminUserId;
protected function setUp(): void
{
$this->rbac = VRBAC::getInstance();
$this->auth = VAuth::getInstance();
// Clear session data
if (isset($_SESSION)) {
$_SESSION = [];
}
// Mock server variables
$_SERVER = [
'REQUEST_URI' => '/test',
'REQUEST_METHOD' => 'GET',
'HTTP_USER_AGENT' => 'PHPUnit RBAC Test',
'REMOTE_ADDR' => '127.0.0.1',
'HTTP_ACCEPT' => 'text/html'
];
// Clean up any existing test data
$this->cleanupTestData();
// Create test users
$this->createTestUsers();
}
protected function tearDown(): void
{
// Clean up test data
$this->cleanupTestData();
// Clear session
if (isset($_SESSION)) {
$_SESSION = [];
}
}
private function cleanupTestData()
{
global $class_database;
$db = $class_database->dbConnection();
// Clean up test users and related data
$testEmails = ['rbactest@example.com', 'rbacadmin@example.com', 'rbacmember@example.com'];
$testUsernames = ['rbactest', 'rbacadmin', 'rbacmember'];
foreach ($testEmails as $email) {
$userId = $db->GetOne("SELECT user_id FROM db_users WHERE email = ?", [$email]);
if ($userId) {
$db->Execute("DELETE FROM db_user_permissions WHERE user_id = ?", [$userId]);
$db->Execute("DELETE FROM db_role_history WHERE user_id = ?", [$userId]);
$db->Execute("DELETE FROM db_user_suspensions WHERE user_id = ?", [$userId]);
$db->Execute("DELETE FROM db_user_bans WHERE user_id = ?", [$userId]);
$db->Execute("DELETE FROM db_sessions WHERE user_id = ?", [$userId]);
$db->Execute("DELETE FROM db_users WHERE user_id = ?", [$userId]);
}
}
}
private function createTestUsers()
{
// Create regular test user
$userData = [
'username' => 'rbactest',
'email' => 'rbactest@example.com',
'password' => 'TestPassword123!'
];
$result = $this->auth->register($userData);
if ($result['success']) {
$this->testUserId = $result['user_id'];
// Verify email
global $class_database;
$db = $class_database->dbConnection();
$db->Execute("UPDATE db_users SET email_verified = 1 WHERE user_id = ?", [$this->testUserId]);
}
// Create admin test user
$adminData = [
'username' => 'rbacadmin',
'email' => 'rbacadmin@example.com',
'password' => 'AdminPassword123!'
];
$adminResult = $this->auth->register($adminData);
if ($adminResult['success']) {
$this->adminUserId = $adminResult['user_id'];
// Set as admin and verify
global $class_database;
$db = $class_database->dbConnection();
$db->Execute("UPDATE db_users SET role = 'admin', email_verified = 1 WHERE user_id = ?", [$this->adminUserId]);
}
}
/**
* Test RBAC singleton pattern
*/
public function testSingletonPattern()
{
$rbac1 = VRBAC::getInstance();
$rbac2 = VRBAC::getInstance();
$this->assertSame($rbac1, $rbac2);
$this->assertInstanceOf(VRBAC::class, $rbac1);
}
/**
* Test role hierarchy
*/
public function testRoleHierarchy()
{
// Test role levels
$this->assertTrue($this->rbac->hasRole('guest', $this->testUserId));
$this->assertTrue($this->rbac->hasRole('member', $this->testUserId));
$this->assertFalse($this->rbac->hasRole('admin', $this->testUserId));
$this->assertTrue($this->rbac->hasRole('guest', $this->adminUserId));
$this->assertTrue($this->rbac->hasRole('member', $this->adminUserId));
$this->assertTrue($this->rbac->hasRole('admin', $this->adminUserId));
}
/**
* Test basic permission checking
*/
public function testBasicPermissions()
{
// Test member permissions
$this->assertTrue($this->rbac->hasPermission('content.view', $this->testUserId));
$this->assertTrue($this->rbac->hasPermission('content.create', $this->testUserId));
$this->assertFalse($this->rbac->hasPermission('admin.dashboard', $this->testUserId));
// Test admin permissions
$this->assertTrue($this->rbac->hasPermission('content.view', $this->adminUserId));
$this->assertTrue($this->rbac->hasPermission('admin.dashboard', $this->adminUserId));
$this->assertTrue($this->rbac->hasPermission('user.ban', $this->adminUserId));
}
/**
* Test guest permissions
*/
public function testGuestPermissions()
{
// Test guest permissions without user ID (not logged in)
$this->assertTrue($this->rbac->hasPermission('content.view'));
$this->assertTrue($this->rbac->hasPermission('comment.view'));
$this->assertFalse($this->rbac->hasPermission('content.create'));
$this->assertFalse($this->rbac->hasPermission('admin.dashboard'));
}
/**
* Test custom user permissions
*/
public function testCustomUserPermissions()
{
// Grant custom permission
$result = $this->rbac->grantPermission($this->testUserId, 'feature.beta', $this->adminUserId);
$this->assertTrue($result);
// Test custom permission
$this->assertTrue($this->rbac->hasPermission('feature.beta', $this->testUserId));
// Revoke custom permission
$revokeResult = $this->rbac->revokePermission($this->testUserId, 'feature.beta', $this->adminUserId);
$this->assertTrue($revokeResult);
// Test permission is revoked
$this->assertFalse($this->rbac->hasPermission('feature.beta', $this->testUserId));
}
/**
* Test permission expiration
*/
public function testPermissionExpiration()
{
// Grant permission with expiration
$expiresAt = date('Y-m-d H:i:s', time() + 3600); // 1 hour from now
$result = $this->rbac->grantPermission($this->testUserId, 'upload.large_files', $this->adminUserId, $expiresAt);
$this->assertTrue($result);
// Test permission is active
$this->assertTrue($this->rbac->hasPermission('upload.large_files', $this->testUserId));
// Test with expired permission (simulate by setting past date)
global $class_database;
$db = $class_database->dbConnection();
$db->Execute("UPDATE db_user_permissions SET expires_at = ? WHERE user_id = ? AND permission = ?",
[date('Y-m-d H:i:s', time() - 3600), $this->testUserId, 'upload.large_files']);
// Test permission is expired
$this->assertFalse($this->rbac->hasPermission('upload.large_files', $this->testUserId));
}
/**
* Test multiple permission checking
*/
public function testMultiplePermissions()
{
$permissions = ['content.view', 'content.create', 'comment.create'];
// Test hasAnyPermission
$this->assertTrue($this->rbac->hasAnyPermission($permissions, $this->testUserId));
$this->assertTrue($this->rbac->hasAnyPermission(['admin.dashboard', 'content.view'], $this->testUserId));
$this->assertFalse($this->rbac->hasAnyPermission(['admin.dashboard', 'admin.system'], $this->testUserId));
// Test hasAllPermissions
$this->assertTrue($this->rbac->hasAllPermissions($permissions, $this->testUserId));
$this->assertFalse($this->rbac->hasAllPermissions(['content.view', 'admin.dashboard'], $this->testUserId));
}
/**
* Test role changes
*/
public function testRoleChanges()
{
// Change user role
$result = $this->rbac->changeUserRole($this->testUserId, 'verified', $this->adminUserId, 'Test role change');
$this->assertTrue($result);
// Test new role permissions
$this->assertTrue($this->rbac->hasRole('verified', $this->testUserId));
$this->assertTrue($this->rbac->hasPermission('content.publish', $this->testUserId));
// Test invalid role change
$invalidResult = $this->rbac->changeUserRole($this->testUserId, 'invalid_role', $this->adminUserId);
$this->assertFalse($invalidResult);
}
/**
* Test user suspension
*/
public function testUserSuspension()
{
// Suspend user
$result = $this->rbac->suspendUser($this->testUserId, 'Test suspension', $this->adminUserId);
$this->assertTrue($result);
// Test suspended user has no permissions
$this->assertFalse($this->rbac->hasPermission('content.view', $this->testUserId));
$this->assertFalse($this->rbac->hasPermission('content.create', $this->testUserId));
// Reinstate user
$reinstateResult = $this->rbac->reinstateUser($this->testUserId, 'Test reinstatement', $this->adminUserId);
$this->assertTrue($reinstateResult);
// Test reinstated user has permissions again
$this->assertTrue($this->rbac->hasPermission('content.view', $this->testUserId));
$this->assertTrue($this->rbac->hasPermission('content.create', $this->testUserId));
}
/**
* Test user banning
*/
public function testUserBanning()
{
// Ban user
$result = $this->rbac->banUser($this->testUserId, 'Test ban', $this->adminUserId, false);
$this->assertTrue($result);
// Test banned user has no permissions
$this->assertFalse($this->rbac->hasPermission('content.view', $this->testUserId));
$this->assertFalse($this->rbac->hasRole('member', $this->testUserId));
// Reinstate user
$reinstateResult = $this->rbac->reinstateUser($this->testUserId, 'Test unban', $this->adminUserId);
$this->assertTrue($reinstateResult);
// Test unbanned user has permissions again
$this->assertTrue($this->rbac->hasPermission('content.view', $this->testUserId));
}
/**
* Test context-based permissions
*/
public function testContextPermissions()
{
// Test content ownership context
$context = ['content_owner_id' => $this->testUserId];
// User should be able to edit their own content
$this->assertTrue($this->rbac->hasPermission('content.edit', $this->testUserId, $context));
$this->assertTrue($this->rbac->hasPermission('content.delete', $this->testUserId, $context));
// User should not be able to edit others' content without permission
$otherContext = ['content_owner_id' => $this->adminUserId];
$this->assertFalse($this->rbac->hasPermission('content.moderate', $this->testUserId, $otherContext));
}
/**
* Test permission middleware
*/
public function testPermissionMiddleware()
{
// Mock current user session
$_SESSION['USER_ID'] = $this->testUserId;
$_SESSION['USERNAME'] = 'rbactest';
$_SESSION['EMAIL'] = 'rbactest@example.com';
$_SESSION['ROLE'] = 'member';
$_SESSION['LOGIN_TIME'] = time();
$_SESSION['LAST_ACTIVITY'] = time();
// Test successful permission check
$this->expectOutputString(''); // No output expected for successful check
ob_start();
$result = $this->rbac->requirePermission('content.view');
$output = ob_get_clean();
$this->assertTrue($result);
$this->assertEmpty($output);
// Clear session for next test
$_SESSION = [];
}
/**
* Test role middleware
*/
public function testRoleMiddleware()
{
// Mock current user session
$_SESSION['USER_ID'] = $this->adminUserId;
$_SESSION['USERNAME'] = 'rbacadmin';
$_SESSION['EMAIL'] = 'rbacadmin@example.com';
$_SESSION['ROLE'] = 'admin';
$_SESSION['LOGIN_TIME'] = time();
$_SESSION['LAST_ACTIVITY'] = time();
// Test successful role check
$this->expectOutputString(''); // No output expected for successful check
ob_start();
$result = $this->rbac->requireRole('admin');
$output = ob_get_clean();
$this->assertTrue($result);
$this->assertEmpty($output);
// Clear session
$_SESSION = [];
}
/**
* Test getting user permissions
*/
public function testGetUserPermissions()
{
$permissions = $this->rbac->getUserPermissions($this->testUserId);
$this->assertIsArray($permissions);
$this->assertContains('content.view', $permissions);
$this->assertContains('content.create', $permissions);
$this->assertNotContains('admin.dashboard', $permissions);
// Test admin permissions
$adminPermissions = $this->rbac->getUserPermissions($this->adminUserId);
$this->assertContains('admin.dashboard', $adminPermissions);
$this->assertContains('user.ban', $adminPermissions);
}
/**
* Test getting role permissions
*/
public function testGetRolePermissions()
{
$memberPermissions = $this->rbac->getRolePermissions('member');
$this->assertIsArray($memberPermissions);
$this->assertContains('content.view', $memberPermissions);
$this->assertContains('content.create', $memberPermissions);
$adminPermissions = $this->rbac->getRolePermissions('admin');
$this->assertContains('admin.dashboard', $adminPermissions);
$this->assertContains('user.ban', $adminPermissions);
$guestPermissions = $this->rbac->getRolePermissions('guest');
$this->assertContains('content.view', $guestPermissions);
$this->assertNotContains('content.create', $guestPermissions);
}
/**
* Test permission validation edge cases
*/
public function testPermissionEdgeCases()
{
// Test with non-existent user
$this->assertFalse($this->rbac->hasPermission('content.view', 99999));
// Test with invalid permission
$this->assertFalse($this->rbac->hasPermission('invalid.permission', $this->testUserId));
// Test with null user ID and no session
$this->assertTrue($this->rbac->hasPermission('content.view')); // Should check guest permissions
// Test with empty permission
$this->assertFalse($this->rbac->hasPermission('', $this->testUserId));
}
/**
* Test permission caching
*/
public function testPermissionCaching()
{
// First call should query database
$permissions1 = $this->rbac->getUserPermissions($this->testUserId);
// Second call should use cache
$permissions2 = $this->rbac->getUserPermissions($this->testUserId);
$this->assertEquals($permissions1, $permissions2);
// Grant new permission (should clear cache)
$this->rbac->grantPermission($this->testUserId, 'feature.beta', $this->adminUserId);
// Should get updated permissions
$permissions3 = $this->rbac->getUserPermissions($this->testUserId);
$this->assertContains('feature.beta', $permissions3);
}
/**
* Test error handling
*/
public function testErrorHandling()
{
// Test with invalid parameters
$result = $this->rbac->grantPermission(null, 'test.permission', $this->adminUserId);
$this->assertFalse($result);
$result = $this->rbac->changeUserRole($this->testUserId, null, $this->adminUserId);
$this->assertFalse($result);
$result = $this->rbac->suspendUser(null, 'test', $this->adminUserId);
$this->assertFalse($result);
}
}

364
tests/Unit/SecurityTest.php Normal file
View File

@@ -0,0 +1,364 @@
<?php
namespace EasyStream\Tests\Unit;
use PHPUnit\Framework\TestCase;
use VSecurity;
class SecurityTest extends TestCase
{
private $security;
protected function setUp(): void
{
$this->security = VSecurity::getInstance();
// Clear any existing session data
if (isset($_SESSION)) {
$_SESSION = [];
}
// Clear superglobals for clean testing
$_GET = [];
$_POST = [];
}
protected function tearDown(): void
{
// Clean up after each test
if (isset($_SESSION)) {
$_SESSION = [];
}
$_GET = [];
$_POST = [];
}
/**
* Test input validation with various data types
*/
public function testInputValidationWithEdgeCases()
{
// Test integer validation
$this->assertEquals(123, VSecurity::validateInput('123', 'int'));
$this->assertEquals(0, VSecurity::validateInput('0', 'int'));
$this->assertEquals(-123, VSecurity::validateInput('-123', 'int'));
$this->assertNull(VSecurity::validateInput('abc', 'int'));
$this->assertNull(VSecurity::validateInput('123.45', 'int'));
$this->assertNull(VSecurity::validateInput('999999999999999999999', 'int'));
// Test integer with min/max constraints
$this->assertEquals(50, VSecurity::validateInput('50', 'int', null, ['min' => 10, 'max' => 100]));
$this->assertNull(VSecurity::validateInput('5', 'int', null, ['min' => 10, 'max' => 100]));
$this->assertNull(VSecurity::validateInput('150', 'int', null, ['min' => 10, 'max' => 100]));
// Test float validation
$this->assertEquals(123.45, VSecurity::validateInput('123.45', 'float'));
$this->assertEquals(0.0, VSecurity::validateInput('0.0', 'float'));
$this->assertNull(VSecurity::validateInput('abc', 'float'));
// Test email validation
$this->assertEquals('test@example.com', VSecurity::validateInput('test@example.com', 'email'));
$this->assertEquals('user+tag@domain.co.uk', VSecurity::validateInput('user+tag@domain.co.uk', 'email'));
$this->assertNull(VSecurity::validateInput('invalid-email', 'email'));
$this->assertNull(VSecurity::validateInput('test@', 'email'));
$this->assertNull(VSecurity::validateInput('@example.com', 'email'));
// Test URL validation
$this->assertEquals('https://example.com', VSecurity::validateInput('https://example.com', 'url'));
$this->assertEquals('http://localhost:8080/path', VSecurity::validateInput('http://localhost:8080/path', 'url'));
$this->assertNull(VSecurity::validateInput('not-a-url', 'url'));
$this->assertNull(VSecurity::validateInput('ftp://invalid', 'url'));
// Test alpha validation
$this->assertEquals('abcDEF', VSecurity::validateInput('abcDEF', 'alpha'));
$this->assertEquals('test', VSecurity::validateInput('test123!@#', 'alpha'));
$this->assertNull(VSecurity::validateInput('123', 'alpha'));
// Test alphanum validation
$this->assertEquals('abc123', VSecurity::validateInput('abc123', 'alphanum'));
$this->assertEquals('test123', VSecurity::validateInput('test123!@#', 'alphanum'));
$this->assertNull(VSecurity::validateInput('!@#', 'alphanum'));
// Test slug validation
$this->assertEquals('test-slug_123', VSecurity::validateInput('test-slug_123', 'slug'));
$this->assertEquals('test-slug', VSecurity::validateInput('test-slug!@#', 'slug'));
// Test filename validation
$this->assertEquals('test.txt', VSecurity::validateInput('test.txt', 'filename'));
$this->assertEquals('file_name.pdf', VSecurity::validateInput('file_name.pdf', 'filename'));
$this->assertEquals('test.txt', VSecurity::validateInput('test/path.txt', 'filename'));
// Test boolean validation
$this->assertTrue(VSecurity::validateInput('true', 'boolean'));
$this->assertTrue(VSecurity::validateInput('1', 'boolean'));
$this->assertTrue(VSecurity::validateInput('yes', 'boolean'));
$this->assertFalse(VSecurity::validateInput('false', 'boolean'));
$this->assertFalse(VSecurity::validateInput('0', 'boolean'));
$this->assertFalse(VSecurity::validateInput('no', 'boolean'));
// Test string validation with length constraints
$this->assertEquals('test', VSecurity::validateInput('test', 'string', null, ['min_length' => 2, 'max_length' => 10]));
$this->assertNull(VSecurity::validateInput('a', 'string', null, ['min_length' => 2, 'max_length' => 10]));
$this->assertNull(VSecurity::validateInput('verylongstring', 'string', null, ['min_length' => 2, 'max_length' => 10]));
}
/**
* Test GET parameter handling
*/
public function testGetParameterHandling()
{
$_GET['test_int'] = '123';
$_GET['test_string'] = 'hello world';
$_GET['test_email'] = 'test@example.com';
$_GET['test_invalid'] = 'invalid_email';
$this->assertEquals(123, VSecurity::getParam('test_int', 'int'));
$this->assertEquals('hello world', VSecurity::getParam('test_string', 'string'));
$this->assertEquals('test@example.com', VSecurity::getParam('test_email', 'email'));
$this->assertEquals('default', VSecurity::getParam('test_invalid', 'email', 'default'));
$this->assertNull(VSecurity::getParam('nonexistent', 'string'));
$this->assertEquals('default', VSecurity::getParam('nonexistent', 'string', 'default'));
}
/**
* Test POST parameter handling
*/
public function testPostParameterHandling()
{
$_POST['username'] = 'testuser';
$_POST['age'] = '25';
$_POST['email'] = 'user@example.com';
$_POST['malicious'] = '<script>alert("xss")</script>';
$this->assertEquals('testuser', VSecurity::postParam('username', 'string'));
$this->assertEquals(25, VSecurity::postParam('age', 'int'));
$this->assertEquals('user@example.com', VSecurity::postParam('email', 'email'));
$this->assertEquals('&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;', VSecurity::postParam('malicious', 'string'));
$this->assertNull(VSecurity::postParam('nonexistent', 'string'));
}
/**
* Test CSRF token generation and validation
*/
public function testCSRFProtection()
{
// Test token generation
$token1 = VSecurity::generateCSRFToken('test_action');
$this->assertIsString($token1);
$this->assertEquals(64, strlen($token1)); // 32 bytes = 64 hex chars
// Test token validation
$this->assertTrue(VSecurity::validateCSRFToken($token1, 'test_action'));
// Test invalid token
$this->assertFalse(VSecurity::validateCSRFToken('invalid_token', 'test_action'));
// Test token is one-time use
$this->assertFalse(VSecurity::validateCSRFToken($token1, 'test_action'));
// Test different actions have different tokens
$token2 = VSecurity::generateCSRFToken('different_action');
$this->assertNotEquals($token1, $token2);
// Test default action
$defaultToken = VSecurity::generateCSRFToken();
$this->assertTrue(VSecurity::validateCSRFToken($defaultToken, 'default'));
}
/**
* Test CSRF field generation
*/
public function testCSRFFieldGeneration()
{
$field = VSecurity::getCSRFField('test_form');
$this->assertStringContainsString('<input type="hidden"', $field);
$this->assertStringContainsString('name="csrf_token"', $field);
$this->assertStringContainsString('value="', $field);
// Extract token from field
preg_match('/value="([^"]+)"/', $field, $matches);
$token = $matches[1] ?? '';
$this->assertNotEmpty($token);
$this->assertEquals(64, strlen($token));
}
/**
* Test CSRF validation from POST data
*/
public function testCSRFValidationFromPost()
{
$token = VSecurity::generateCSRFToken('form_submit');
$_POST['csrf_token'] = $token;
$this->assertTrue(VSecurity::validateCSRFFromPost('form_submit'));
// Test with invalid token
$_POST['csrf_token'] = 'invalid_token';
$this->assertFalse(VSecurity::validateCSRFFromPost('form_submit'));
// Test with missing token
unset($_POST['csrf_token']);
$this->assertFalse(VSecurity::validateCSRFFromPost('form_submit'));
}
/**
* Test output escaping
*/
public function testOutputEscaping()
{
$maliciousInput = '<script>alert("xss")</script>';
$escaped = VSecurity::escapeOutput($maliciousInput);
$this->assertEquals('&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;', $escaped);
// Test with quotes and ampersands
$input = 'Hello "world" & <friends>';
$escaped = VSecurity::escapeOutput($input);
$this->assertEquals('Hello &quot;world&quot; &amp; &lt;friends&gt;', $escaped);
}
/**
* Test JavaScript escaping
*/
public function testJavaScriptEscaping()
{
$input = 'Hello "world" & <script>';
$escaped = VSecurity::escapeJS($input);
$this->assertIsString($escaped);
$this->assertStringContainsString('\u003C', $escaped); // < should be escaped
$this->assertStringContainsString('\u0022', $escaped); // " should be escaped
}
/**
* Test file upload validation
*/
public function testFileUploadValidation()
{
// Create mock uploaded file
$validFile = createMockUploadedFile('test.txt', 'Hello World', 'text/plain');
// Test valid file
$result = VSecurity::validateFileUpload($validFile, ['text/plain'], 1024);
$this->assertTrue($result['valid']);
$this->assertEquals('text/plain', $result['mime_type']);
// Test file too large
$result = VSecurity::validateFileUpload($validFile, ['text/plain'], 5);
$this->assertFalse($result['valid']);
$this->assertStringContainsString('too large', $result['error']);
// Test invalid MIME type
$result = VSecurity::validateFileUpload($validFile, ['image/jpeg'], 1024);
$this->assertFalse($result['valid']);
$this->assertStringContainsString('Invalid file type', $result['error']);
// Test invalid upload (no file)
$invalidFile = ['tmp_name' => '', 'size' => 0];
$result = VSecurity::validateFileUpload($invalidFile);
$this->assertFalse($result['valid']);
$this->assertStringContainsString('No file uploaded', $result['error']);
}
/**
* Test rate limiting functionality
*/
public function testRateLimiting()
{
$key = 'test_user_' . uniqid();
// Test within limits
for ($i = 0; $i < 5; $i++) {
$this->assertTrue(VSecurity::checkRateLimit($key, 5, 60));
}
// Test exceeding limits
$this->assertFalse(VSecurity::checkRateLimit($key, 5, 60));
// Test different key
$key2 = 'test_user_' . uniqid();
$this->assertTrue(VSecurity::checkRateLimit($key2, 5, 60));
}
/**
* Test XSS prevention in various contexts
*/
public function testXSSPrevention()
{
$xssPayloads = [
'<script>alert("XSS")</script>',
'javascript:alert("XSS")',
'<img src="x" onerror="alert(\'XSS\')">',
'<svg onload="alert(1)">',
'"><script>alert("XSS")</script>',
'\';alert(String.fromCharCode(88,83,83))//\';alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//--></SCRIPT>">\'><SCRIPT>alert(String.fromCharCode(88,83,83))</SCRIPT>'
];
foreach ($xssPayloads as $payload) {
$sanitized = VSecurity::validateInput($payload, 'string');
// Should not contain dangerous elements
$this->assertStringNotContainsString('<script>', strtolower($sanitized));
$this->assertStringNotContainsString('javascript:', strtolower($sanitized));
$this->assertStringNotContainsString('onerror=', strtolower($sanitized));
$this->assertStringNotContainsString('onload=', strtolower($sanitized));
}
}
/**
* Test SQL injection prevention patterns
*/
public function testSQLInjectionPrevention()
{
$sqlInjectionPayloads = [
"'; DROP TABLE users; --",
"1' OR '1'='1",
"1; UPDATE users SET password='hacked' WHERE 1=1; --",
"' UNION SELECT * FROM users --",
"admin'--",
"admin'/*",
"' OR 1=1#"
];
foreach ($sqlInjectionPayloads as $payload) {
// These should be safely handled by input validation
$result = VSecurity::validateInput($payload, 'string');
// The result should be escaped and safe
$this->assertIsString($result);
$this->assertStringNotContainsString('DROP TABLE', strtoupper($result));
$this->assertStringNotContainsString('UNION SELECT', strtoupper($result));
}
}
/**
* Test edge cases and boundary conditions
*/
public function testEdgeCases()
{
// Test null input
$this->assertEquals('default', VSecurity::validateInput(null, 'string', 'default'));
// Test empty string
$this->assertEquals('', VSecurity::validateInput('', 'string'));
// Test whitespace handling
$this->assertEquals('test', VSecurity::validateInput(' test ', 'string'));
// Test very long strings
$longString = str_repeat('a', 10000);
$result = VSecurity::validateInput($longString, 'string', null, ['max_length' => 100]);
$this->assertNull($result);
// Test unicode handling
$unicode = 'Hello 世界 🌍';
$result = VSecurity::validateInput($unicode, 'string');
$this->assertStringContainsString('Hello', $result);
// Test array input (should be handled gracefully)
$result = VSecurity::validateInput(['array', 'input'], 'string', 'default');
$this->assertEquals('default', $result);
}
}