From 5327bde032c53053c59f51171bd6506ed4365d9e Mon Sep 17 00:00:00 2001 From: Yasen Pramatarov Date: Wed, 19 Feb 2025 15:31:01 +0200 Subject: [PATCH] Adds tests for middleware --- app/helpers/security.php | 2 +- app/includes/csrf_middleware.php | 2 +- app/includes/rate_limit_middleware.php | 45 +++-- app/includes/security_headers_middleware.php | 4 +- app/includes/session_middleware.php | 115 +++++++---- .../Middleware/RateLimitMiddlewareTest.php | 182 ++++++++++++++++++ .../Middleware/SessionMiddlewareTest.php | 110 +++++++++++ .../Security/SecurityHeadersTest.php | 8 +- tests/framework/TestCase.php | 12 +- tests/framework/TestSessionHandler.php | 126 ++++++++++++ tests/framework/Unit/Classes/AgentTest.php | 12 +- tests/framework/Unit/Classes/DatabaseTest.php | 12 +- tests/framework/Unit/Classes/FeedbackTest.php | 10 +- tests/framework/Unit/Classes/LogTest.php | 22 +-- tests/framework/Unit/Classes/PlatformTest.php | 2 +- tests/framework/bootstrap.php | 43 ++--- tests/framework/test_php.ini | 7 + 17 files changed, 587 insertions(+), 127 deletions(-) create mode 100644 tests/framework/Integration/Middleware/RateLimitMiddlewareTest.php create mode 100644 tests/framework/Integration/Middleware/SessionMiddlewareTest.php create mode 100644 tests/framework/TestSessionHandler.php create mode 100644 tests/framework/test_php.ini diff --git a/app/helpers/security.php b/app/helpers/security.php index 39035e4..2fb5e39 100644 --- a/app/helpers/security.php +++ b/app/helpers/security.php @@ -2,7 +2,7 @@ /** * Security Helper - * + * * Security helper, to be used with all the forms in the app. * Implements singleton pattern for consistent state management. */ diff --git a/app/includes/csrf_middleware.php b/app/includes/csrf_middleware.php index b19937a..22cbf32 100644 --- a/app/includes/csrf_middleware.php +++ b/app/includes/csrf_middleware.php @@ -2,7 +2,7 @@ require_once __DIR__ . '/../helpers/security.php'; -function verifyCsrfToken() { +function applyCsrfMiddleware() { $security = SecurityHelper::getInstance(); // Skip CSRF check for GET requests diff --git a/app/includes/rate_limit_middleware.php b/app/includes/rate_limit_middleware.php index 3fcd0a6..0c48043 100644 --- a/app/includes/rate_limit_middleware.php +++ b/app/includes/rate_limit_middleware.php @@ -11,6 +11,7 @@ require_once __DIR__ . '/../classes/ratelimiter.php'; * @return bool True if request is allowed, false if rate limited */ function checkRateLimit($database, $endpoint, $userId = null) { + $isTest = defined('PHPUNIT_RUNNING'); $rateLimiter = new RateLimiter($database['db']); $ipAddress = $_SERVER['REMOTE_ADDR']; @@ -19,28 +20,34 @@ function checkRateLimit($database, $endpoint, $userId = null) { // Get remaining requests for error message $remaining = $rateLimiter->getRemainingPageRequests($ipAddress, $endpoint, $userId); - // Set rate limit headers - header('X-RateLimit-Remaining: ' . $remaining); - header('X-RateLimit-Reset: ' . (time() + 60)); // Reset in 1 minute + if (!$isTest) { + // Set rate limit headers + header('X-RateLimit-Remaining: ' . $remaining); + header('X-RateLimit-Reset: ' . (time() + 60)); // Reset in 1 minute - // Return 429 Too Many Requests - http_response_code(429); + // Return 429 Too Many Requests + http_response_code(429); - // If AJAX request, return JSON - if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && - strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') { - header('Content-Type: application/json'); - echo json_encode([ - 'success' => false, - 'message' => 'Too many requests. Please try again in a minute.', - 'messageData' => Feedback::getMessageData('ERROR', 'DEFAULT', 'Too many requests. Please try again in a minute.', true) - ]); - } else { - // For regular requests, set flash message and redirect - Feedback::flash('ERROR', 'DEFAULT', 'Too many requests. Please try again in a minute.', true); - header('Location: ' . htmlspecialchars($app_root)); + // If AJAX request, return JSON + if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && + strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') { + header('Content-Type: application/json'); + echo json_encode([ + 'success' => false, + 'message' => 'Too many requests. Please try again in a minute.', + 'messageData' => Feedback::getMessageData('ERROR', 'DEFAULT', 'Too many requests. Please try again in a minute.', true) + ]); + } else { + // For regular requests, set flash message and redirect + Feedback::flash('ERROR', 'DEFAULT', 'Too many requests. Please try again in a minute.', true); + header('Location: ' . htmlspecialchars($app_root)); + } + exit; } - exit; + + // In test mode, just set the flash message + Feedback::flash('ERROR', 'DEFAULT', 'Too many requests. Please try again in a minute.', true); + return false; } // Record this request diff --git a/app/includes/security_headers_middleware.php b/app/includes/security_headers_middleware.php index f4ab237..e155262 100644 --- a/app/includes/security_headers_middleware.php +++ b/app/includes/security_headers_middleware.php @@ -2,7 +2,7 @@ /** * Security Headers Middleware - * + * * Sets various security headers to protect against common web vulnerabilities: * - HSTS: Force HTTPS connections * - CSP: Content Security Policy to prevent XSS and other injection attacks @@ -14,7 +14,7 @@ function applySecurityHeaders($testMode = false) { $headers = []; - + // Get current page $current_page = $_GET['page'] ?? 'dashboard'; diff --git a/app/includes/session_middleware.php b/app/includes/session_middleware.php index 59fc13d..1d8e576 100644 --- a/app/includes/session_middleware.php +++ b/app/includes/session_middleware.php @@ -2,48 +2,91 @@ /** * Session Middleware - * + * * Validates session status and handles session timeout. * This middleware should be included in all protected pages. */ -// Start session if not already started -if (session_status() === PHP_SESSION_NONE) { - session_start(); -} +function applySessionMiddleware($config, $app_root) { + $isTest = defined('PHPUNIT_RUNNING'); -// Check if user is logged in -if (!isset($_SESSION['USER_ID'])) { - header('Location: ' . $app_root . '?page=login'); - exit(); -} + // Access $_SESSION directly in test mode + if (!$isTest) { + // Start session if not already started + if (session_status() !== PHP_SESSION_ACTIVE && !headers_sent()) { + session_start([ + 'cookie_httponly' => 1, + 'cookie_secure' => 1, + 'cookie_samesite' => 'Strict', + 'gc_maxlifetime' => 1440 // 24 minutes + ]); + } + } -// Check session timeout -$session_timeout = isset($_SESSION['REMEMBER_ME']) ? (30 * 24 * 60 * 60) : 1440; // 30 days or 24 minutes -if (isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > $session_timeout)) { - // Session has expired - session_unset(); - session_destroy(); - setcookie('username', '', [ - 'expires' => time() - 3600, - 'path' => $config['folder'], - 'domain' => $config['domain'], - 'secure' => isset($_SERVER['HTTPS']), - 'httponly' => true, - 'samesite' => 'Strict' - ]); - header('Location: ' . $app_root . '?page=login&timeout=1'); - exit(); -} + // Check if user is logged in + if (!isset($_SESSION['USER_ID'])) { + if (!$isTest) { + header('Location: ' . $app_root . '?page=login'); + exit(); + } + return false; + } -// Update last activity time -$_SESSION['LAST_ACTIVITY'] = time(); + // Check session timeout + $session_timeout = isset($_SESSION['REMEMBER_ME']) ? (30 * 24 * 60 * 60) : 1440; // 30 days or 24 minutes + if (isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > $session_timeout)) { + // Session has expired + $oldSessionData = $_SESSION; + $_SESSION = array(); -// Regenerate session ID periodically (every 30 minutes) -if (!isset($_SESSION['CREATED'])) { - $_SESSION['CREATED'] = time(); -} else if (time() - $_SESSION['CREATED'] > 1800) { - // Regenerate session ID and update creation time - session_regenerate_id(true); - $_SESSION['CREATED'] = time(); + if (!$isTest && session_status() === PHP_SESSION_ACTIVE) { + session_unset(); + session_destroy(); + + // Start a new session to prevent errors + if (!headers_sent()) { + session_start([ + 'cookie_httponly' => 1, + 'cookie_secure' => 1, + 'cookie_samesite' => 'Strict', + 'gc_maxlifetime' => 1440 + ]); + } + } + + if (!$isTest && !headers_sent()) { + setcookie('username', '', [ + 'expires' => time() - 3600, + 'path' => $config['folder'], + 'domain' => $config['domain'], + 'secure' => isset($_SERVER['HTTPS']), + 'httponly' => true, + 'samesite' => 'Strict' + ]); + } + + if (!$isTest) { + header('Location: ' . $app_root . '?page=login&timeout=1'); + exit(); + } + return false; + } + + // Update last activity time + $_SESSION['LAST_ACTIVITY'] = time(); + + // Regenerate session ID periodically (every 30 minutes) + if (!isset($_SESSION['CREATED'])) { + $_SESSION['CREATED'] = time(); + } else if (time() - $_SESSION['CREATED'] > 1800) { + // Regenerate session ID and update creation time + if (!$isTest && !headers_sent() && session_status() === PHP_SESSION_ACTIVE) { + $oldData = $_SESSION; + session_regenerate_id(true); + $_SESSION = $oldData; + $_SESSION['CREATED'] = time(); + } + } + + return true; } diff --git a/tests/framework/Integration/Middleware/RateLimitMiddlewareTest.php b/tests/framework/Integration/Middleware/RateLimitMiddlewareTest.php new file mode 100644 index 0000000..c75ea94 --- /dev/null +++ b/tests/framework/Integration/Middleware/RateLimitMiddlewareTest.php @@ -0,0 +1,182 @@ +db = new Database([ + 'type' => 'sqlite', + 'dbFile' => ':memory:' + ]); + + // Create rate limiter table + $this->db->getConnection()->exec("CREATE TABLE pages_rate_limits ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + ip_address TEXT NOT NULL, + endpoint TEXT NOT NULL, + request_time DATETIME DEFAULT CURRENT_TIMESTAMP + )"); + + // Create ip_whitelist table + $this->db->getConnection()->exec("CREATE TABLE ip_whitelist ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + ip_address TEXT NOT NULL UNIQUE, + is_network BOOLEAN DEFAULT 0 CHECK(is_network IN (0,1)), + description TEXT, + created_at TEXT DEFAULT (DATETIME('now')), + created_by TEXT + )"); + + // Create ip_blacklist table + $this->db->getConnection()->exec("CREATE TABLE ip_blacklist ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + ip_address TEXT NOT NULL UNIQUE, + is_network BOOLEAN DEFAULT 0 CHECK(is_network IN (0,1)), + reason TEXT, + expiry_time TEXT NULL, + created_at TEXT DEFAULT (DATETIME('now')), + created_by TEXT + )"); + + $this->rateLimiter = new RateLimiter($this->db); + + // Mock $_SERVER variables + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + $_SERVER['REQUEST_URI'] = '/login'; + $_SERVER['REQUEST_METHOD'] = 'POST'; + + // Define testing constant + if (!defined('TESTING')) { + define('TESTING', true); + } + } + + protected function tearDown(): void + { + // Clean up rate limit records + $this->db->getConnection()->exec('DELETE FROM pages_rate_limits'); + + parent::tearDown(); + } + + public function testRateLimitMiddleware() + { + // Test multiple requests + for ($i = 1; $i <= 5; $i++) { + $result = checkRateLimit(['db' => $this->db], '/login'); + + if ($i <= 5) { + // First 5 requests should pass + $this->assertTrue($result); + } else { + // 6th and subsequent requests should be blocked + $this->assertFalse($result); + } + } + } + + public function testRateLimitBypass() + { + // Test AJAX request bypass + $_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'; + $result = checkRateLimit(['db' => $this->db], '/login'); + $this->assertTrue($result); + } + + public function testRateLimitReset() + { + // Use up the rate limit + for ($i = 0; $i < 5; $i++) { + checkRateLimit(['db' => $this->db], '/login'); + } + + // Wait for rate limit to reset (use a short window for testing) + sleep(2); + + // Should be able to make request again + $result = checkRateLimit(['db' => $this->db], '/login'); + $this->assertTrue($result); + } + + public function testDifferentEndpoints() + { + // Use up rate limit for login + for ($i = 0; $i < 5; $i++) { + checkRateLimit(['db' => $this->db], '/login'); + } + + // Should still be able to access different endpoint + $result = checkRateLimit(['db' => $this->db], '/dashboard'); + $this->assertTrue($result); + } + + public function testDifferentIpAddresses() + { + // Use up rate limit for first IP + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + for ($i = 0; $i < 5; $i++) { + checkRateLimit(['db' => $this->db], '/login'); + } + + // Different IP should not be affected + $_SERVER['REMOTE_ADDR'] = '127.0.0.2'; + $result = checkRateLimit(['db' => $this->db], '/login'); + $this->assertTrue($result); + } + + public function testWhitelistedIp() + { + // Add IP to whitelist + $this->db->execute( + 'INSERT INTO ip_whitelist (ip_address, description, created_by) VALUES (?, ?, ?)', + ['127.0.0.1', 'Test whitelist', 'PHPUnit'] + ); + + // Should be able to make more requests than limit + for ($i = 0; $i < 10; $i++) { + $result = checkRateLimit(['db' => $this->db], '/login'); + $this->assertTrue($result); + } + } + + public function testBlacklistedIp() + { + // Add IP to blacklist + $this->db->execute( + 'INSERT INTO ip_blacklist (ip_address, reason, created_by) VALUES (?, ?, ?)', + ['127.0.0.1', 'Test blacklist', 'PHPUnit'] + ); + + // Should be blocked immediately + $result = checkRateLimit(['db' => $this->db], '/login'); + $this->assertFalse($result); + } + + public function testRateLimitPersistence() + { + // Use up some of the rate limit + for ($i = 0; $i < 2; $i++) { + checkRateLimit(['db' => $this->db], '/login'); + } + + // Destroy and restart session + //session_destroy(); + //session_start(); + + // Should still count previous requests + $result = checkRateLimit(['db' => $this->db], '/login'); + $this->assertTrue($result); + } +} diff --git a/tests/framework/Integration/Middleware/SessionMiddlewareTest.php b/tests/framework/Integration/Middleware/SessionMiddlewareTest.php new file mode 100644 index 0000000..9de6580 --- /dev/null +++ b/tests/framework/Integration/Middleware/SessionMiddlewareTest.php @@ -0,0 +1,110 @@ +config = [ + 'folder' => '/app', + 'domain' => 'localhost' + ]; + $this->app_root = 'https://localhost/app'; + } + + protected function tearDown(): void + { + parent::tearDown(); + } + + public function testSessionStart() + { + $_SESSION = ['USER_ID' => 1]; + $result = applySessionMiddleware($this->config, $this->app_root); + + $this->assertTrue($result); + $this->assertArrayHasKey('LAST_ACTIVITY', $_SESSION); + $this->assertArrayHasKey('CREATED', $_SESSION); + $this->assertArrayHasKey('USER_ID', $_SESSION); + $this->assertEquals(1, $_SESSION['USER_ID']); + } + + public function testSessionTimeout() + { + $_SESSION = [ + 'USER_ID' => 1, + 'LAST_ACTIVITY' => time() - 1500 // 25 minutes ago + ]; + + $result = applySessionMiddleware($this->config, $this->app_root); + + $this->assertFalse($result); + $this->assertArrayNotHasKey('USER_ID', $_SESSION, 'Session should be cleared after timeout'); + } + + public function testSessionRegeneration() + { + $now = time(); + $_SESSION = [ + 'USER_ID' => 1, + 'CREATED' => $now - 1900 // 31+ minutes ago + ]; + + $result = applySessionMiddleware($this->config, $this->app_root); + + $this->assertTrue($result); + $this->assertEquals(1, $_SESSION['USER_ID']); + $this->assertGreaterThanOrEqual($now - 1900, $_SESSION['CREATED']); + $this->assertLessThanOrEqual($now + 10, $_SESSION['CREATED']); + } + + public function testRememberMe() + { + $_SESSION = [ + 'USER_ID' => 1, + 'REMEMBER_ME' => true, + 'LAST_ACTIVITY' => time() - 86500 // More than 24 hours ago + ]; + + $result = applySessionMiddleware($this->config, $this->app_root); + + $this->assertTrue($result); + $this->assertArrayHasKey('USER_ID', $_SESSION); + } + + public function testNoUserSession() + { + $_SESSION = []; + $result = applySessionMiddleware($this->config, $this->app_root); + + $this->assertFalse($result); + $this->assertArrayNotHasKey('USER_ID', $_SESSION); + } + + public function testSessionHeaders() + { + $_SESSION = [ + 'USER_ID' => 1, + 'LAST_ACTIVITY' => time() - 1500 // 25 minutes ago + ]; + + $result = applySessionMiddleware($this->config, $this->app_root); + + $this->assertFalse($result); + $this->assertArrayNotHasKey('USER_ID', $_SESSION, 'Session should be cleared after timeout'); + } +} diff --git a/tests/framework/Integration/Security/SecurityHeadersTest.php b/tests/framework/Integration/Security/SecurityHeadersTest.php index 2b80561..dbef01f 100644 --- a/tests/framework/Integration/Security/SecurityHeadersTest.php +++ b/tests/framework/Integration/Security/SecurityHeadersTest.php @@ -20,7 +20,7 @@ class SecurityHeadersTest extends TestCase { // Apply security headers in test mode $headers = \applySecurityHeaders(true); - + // Check security headers $this->assertContains('X-Frame-Options: DENY', $headers); $this->assertContains('X-XSS-Protection: 1; mode=block', $headers); @@ -32,7 +32,7 @@ class SecurityHeadersTest extends TestCase { // Apply security headers in test mode $headers = \applySecurityHeaders(true); - + // Get CSP header $cspHeader = ''; foreach ($headers as $header) { @@ -138,10 +138,10 @@ class SecurityHeadersTest extends TestCase public function testPermissionsPolicyForMediaEnabledPages() { $_SERVER['REQUEST_URI'] = '/media/upload'; - + // Apply security headers in test mode $headers = \applySecurityHeaders(true); - + // Get Permissions-Policy header $permissionsHeader = ''; foreach ($headers as $header) { diff --git a/tests/framework/TestCase.php b/tests/framework/TestCase.php index f4dd305..bd88e83 100644 --- a/tests/framework/TestCase.php +++ b/tests/framework/TestCase.php @@ -7,23 +7,23 @@ class TestCase extends PHPUnit\Framework\TestCase protected function setUp(): void { parent::setUp(); - + // Set up test environment $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; $_SERVER['HTTP_USER_AGENT'] = 'PHPUnit Test Browser'; - + // Include common app files require_once dirname(__DIR__, 2) . '/app/includes/config.php'; require_once dirname(__DIR__, 2) . '/app/includes/functions.php'; - + // Clean up any existing session if (session_status() === PHP_SESSION_ACTIVE) { session_destroy(); } - + // Reset session data $_SESSION = []; - + // Only start session if headers haven't been sent if (!headers_sent() && session_status() === PHP_SESSION_NONE) { session_start(); @@ -37,7 +37,7 @@ class TestCase extends PHPUnit\Framework\TestCase $_SESSION = []; session_destroy(); } - + parent::tearDown(); } diff --git a/tests/framework/TestSessionHandler.php b/tests/framework/TestSessionHandler.php new file mode 100644 index 0000000..f0ae178 --- /dev/null +++ b/tests/framework/TestSessionHandler.php @@ -0,0 +1,126 @@ + 1, + 'cookie_secure' => 1, + 'cookie_samesite' => 'Strict', + 'gc_maxlifetime' => 1440 // 24 minutes + ]); + } + + self::$initialized = true; + } + } + + /** + * Start a fresh session + */ + public static function startSession() + { + // Clean up any existing session first + self::cleanupSession(); + + // Initialize new session + if (session_status() !== PHP_SESSION_ACTIVE && !headers_sent()) { + session_name('jilo'); + session_start([ + 'cookie_httponly' => 1, + 'cookie_secure' => 1, + 'cookie_samesite' => 'Strict', + 'gc_maxlifetime' => 1440 + ]); + self::$initialized = true; + } + } + + /** + * Clean up the current session + */ + public static function cleanupSession() + { + if (session_status() === PHP_SESSION_ACTIVE) { + session_write_close(); + } + + if (isset($_COOKIE[session_name()])) { + setcookie(session_name(), '', time()-3600, '/'); + } + + $_SESSION = array(); + + if (session_status() === PHP_SESSION_ACTIVE) { + session_destroy(); + } + + self::$initialized = false; + } + + public function open($path, $name): bool + { + return true; + } + + public function close(): bool + { + return true; + } + + public function read($id): string|false + { + return $this->data[$id] ?? ''; + } + + public function write($id, $data): bool + { + $this->data[$id] = $data; + return true; + } + + public function destroy($id): bool + { + unset($this->data[$id]); + return true; + } + + public function gc($max_lifetime): int|false + { + return 0; + } +} diff --git a/tests/framework/Unit/Classes/AgentTest.php b/tests/framework/Unit/Classes/AgentTest.php index 18ae082..60bea33 100644 --- a/tests/framework/Unit/Classes/AgentTest.php +++ b/tests/framework/Unit/Classes/AgentTest.php @@ -107,7 +107,7 @@ class AgentTest extends TestCase 'secret_key' => 'test_secret', 'check_period' => 60 ]; - + $this->agent->addAgent($hostId, $data); // Test getting agent details @@ -127,7 +127,7 @@ class AgentTest extends TestCase 'secret_key' => 'test_secret', 'check_period' => 60 ]; - + $this->agent->addAgent($hostId, $data); // Get agent ID @@ -167,7 +167,7 @@ class AgentTest extends TestCase 'secret_key' => 'test_secret', 'check_period' => 60 ]; - + $this->agent->addAgent($hostId, $data); // Get agent ID @@ -197,7 +197,7 @@ class AgentTest extends TestCase 'secret_key' => 'test_secret', 'check_period' => 60 ]; - + $this->agent->addAgent($hostId, $data); // Get agent ID @@ -210,7 +210,7 @@ class AgentTest extends TestCase ->setConstructorArgs([$this->db]) ->onlyMethods(['fetchAgent']) ->getMock(); - + $mockResponse = json_encode([ 'status' => 'ok', 'metrics' => [ @@ -226,7 +226,7 @@ class AgentTest extends TestCase $response = $mockAgent->fetchAgent($agentId); $this->assertJson($response); - + $data = json_decode($response, true); $this->assertEquals('ok', $data['status']); } diff --git a/tests/framework/Unit/Classes/DatabaseTest.php b/tests/framework/Unit/Classes/DatabaseTest.php index e9bcd54..7d4582e 100644 --- a/tests/framework/Unit/Classes/DatabaseTest.php +++ b/tests/framework/Unit/Classes/DatabaseTest.php @@ -11,11 +11,11 @@ class DatabaseTest extends TestCase protected function setUp(): void { parent::setUp(); - + // Set development environment for detailed errors global $config; $config['environment'] = 'development'; - + $this->config = [ 'type' => 'sqlite', 'dbFile' => ':memory:' @@ -39,7 +39,7 @@ class DatabaseTest extends TestCase 'user' => 'test', 'password' => 'test' ]; - + $mariadbConfig = [ 'type' => 'mariadb', 'host' => 'invalid-host', @@ -48,11 +48,11 @@ class DatabaseTest extends TestCase 'user' => 'test', 'password' => 'test' ]; - + // Both should fail to connect and return null $mysqlDb = new Database($mysqlConfig); $this->assertNull($mysqlDb->getConnection()); - + $mariaDb = new Database($mariadbConfig); $this->assertNull($mariaDb->getConnection()); } @@ -67,7 +67,7 @@ class DatabaseTest extends TestCase 'user' => 'test', 'password' => 'test' ]; - + $invalidDb = new Database($invalidConfig); $this->assertNull($invalidDb->getConnection()); } diff --git a/tests/framework/Unit/Classes/FeedbackTest.php b/tests/framework/Unit/Classes/FeedbackTest.php index 44ef9e0..93feff1 100644 --- a/tests/framework/Unit/Classes/FeedbackTest.php +++ b/tests/framework/Unit/Classes/FeedbackTest.php @@ -9,12 +9,12 @@ class FeedbackTest extends TestCase protected function setUp(): void { parent::setUp(); - + // Start session for flash messages if (session_status() === PHP_SESSION_NONE && !headers_sent()) { session_start(); } - + // Initialize session variables $_SESSION = []; $_SESSION['flash_messages'] = []; @@ -34,10 +34,10 @@ class FeedbackTest extends TestCase // Add a test message Feedback::flash('LOGIN', 'LOGIN_SUCCESS', 'Test message'); $messages = $_SESSION['flash_messages']; - + $this->assertIsArray($messages); $this->assertCount(1, $messages); - + $message = $messages[0]; $this->assertEquals('LOGIN', $message['category']); $this->assertEquals('LOGIN_SUCCESS', $message['key']); @@ -67,7 +67,7 @@ class FeedbackTest extends TestCase public function testGetMessageData() { $data = Feedback::getMessageData('LOGIN', 'LOGIN_SUCCESS', 'Test message'); - + $this->assertIsArray($data); $this->assertEquals(Feedback::TYPE_SUCCESS, $data['type']); $this->assertEquals('Test message', $data['message']); diff --git a/tests/framework/Unit/Classes/LogTest.php b/tests/framework/Unit/Classes/LogTest.php index c82333c..b99b05a 100644 --- a/tests/framework/Unit/Classes/LogTest.php +++ b/tests/framework/Unit/Classes/LogTest.php @@ -56,7 +56,7 @@ class LogTest extends TestCase $stmt = $this->db->getConnection()->prepare("SELECT * FROM logs WHERE scope = ?"); $stmt->execute(['test']); $log = $stmt->fetch(PDO::FETCH_ASSOC); - + $this->assertEquals(1, $log['user_id']); $this->assertEquals('Test message', $log['message']); $this->assertEquals('test', $log['scope']); @@ -67,7 +67,7 @@ class LogTest extends TestCase // Insert test logs $this->log->insertLog(1, 'Test message 1', 'user'); $this->log->insertLog(1, 'Test message 2', 'user'); - + $logs = $this->log->readLog(1, 'user'); $this->assertCount(2, $logs); $this->assertEquals('Test message 1', $logs[0]['message']); @@ -80,15 +80,15 @@ class LogTest extends TestCase $this->log->insertLog(1, 'Old message', 'user'); sleep(1); // Ensure different timestamps $this->log->insertLog(1, 'New message', 'user'); - + $now = date('Y-m-d H:i:s'); $oneHourAgo = date('Y-m-d H:i:s', strtotime('-1 hour')); - + $logs = $this->log->readLog(1, 'user', 0, '', [ 'from_time' => $oneHourAgo, 'until_time' => $now ]); - + $this->assertCount(2, $logs); } @@ -98,11 +98,11 @@ class LogTest extends TestCase $this->log->insertLog(1, 'Message 1', 'user'); $this->log->insertLog(1, 'Message 2', 'user'); $this->log->insertLog(1, 'Message 3', 'user'); - + // Test with limit $logs = $this->log->readLog(1, 'user', 0, 2); $this->assertCount(2, $logs); - + // Test with offset $logs = $this->log->readLog(1, 'user', 2, 2); $this->assertCount(1, $logs); @@ -113,11 +113,11 @@ class LogTest extends TestCase // Insert test logs $this->log->insertLog(1, 'Test message', 'user'); $this->log->insertLog(1, 'Another message', 'user'); - + $logs = $this->log->readLog(1, 'user', 0, '', [ 'message' => 'Test' ]); - + $this->assertCount(1, $logs); $this->assertEquals('Test message', $logs[0]['message']); } @@ -127,11 +127,11 @@ class LogTest extends TestCase // Insert test logs for different users $this->log->insertLog(1, 'User 1 message', 'user'); $this->log->insertLog(2, 'User 2 message', 'user'); - + $logs = $this->log->readLog(1, 'user', 0, '', [ 'id' => 1 ]); - + $this->assertCount(1, $logs); $this->assertEquals('User 1 message', $logs[0]['message']); } diff --git a/tests/framework/Unit/Classes/PlatformTest.php b/tests/framework/Unit/Classes/PlatformTest.php index 539aa62..b9d570c 100644 --- a/tests/framework/Unit/Classes/PlatformTest.php +++ b/tests/framework/Unit/Classes/PlatformTest.php @@ -67,7 +67,7 @@ class PlatformTest extends TestCase $stmt = $this->db->getConnection()->prepare('SELECT * FROM platforms WHERE name = ?'); $stmt->execute([$data['name']]); $platform = $stmt->fetch(PDO::FETCH_ASSOC); - + $this->assertNotNull($platform); $this->assertEquals($data['name'], $platform['name']); $this->assertEquals($data['jitsi_url'], $platform['jitsi_url']); diff --git a/tests/framework/bootstrap.php b/tests/framework/bootstrap.php index 887e203..38fe4ae 100644 --- a/tests/framework/bootstrap.php +++ b/tests/framework/bootstrap.php @@ -3,18 +3,14 @@ // Set test environment define('PHPUNIT_RUNNING', true); -// Configure session before starting it -ini_set('session.use_strict_mode', '1'); -ini_set('session.use_only_cookies', '1'); -ini_set('session.cookie_httponly', '1'); -ini_set('session.cookie_secure', '1'); -ini_set('session.cookie_samesite', 'Lax'); -ini_set('session.gc_maxlifetime', 1440); - -// Start session if not already started -//if (session_status() === PHP_SESSION_NONE) { -// session_start(); -//} +// Configure session before any output +if (!headers_sent()) { + // Configure session settings + ini_set('session.cookie_httponly', 1); + ini_set('session.cookie_secure', 1); + ini_set('session.cookie_samesite', 'Strict'); + ini_set('session.gc_maxlifetime', 1440); // 24 minutes +} // Load Composer's autoloader require_once __DIR__ . '/vendor/autoload.php'; @@ -33,26 +29,9 @@ $GLOBALS['config'] = [ 'db' => [ 'type' => 'sqlite', 'dbFile' => ':memory:' - ], - 'folder' => '/', - 'domain' => 'localhost', - 'login' => [ - 'max_attempts' => 5, - 'lockout_time' => 900 ] ]; -// Initialize system_messages array -$GLOBALS['system_messages'] = []; - -// Set up server variables -$_SERVER['PHP_SELF'] = '/index.php'; -$_SERVER['REMOTE_ADDR'] = '127.0.0.1'; -$_SERVER['HTTP_USER_AGENT'] = 'PHPUnit Test Browser'; -$_SERVER['HTTP_HOST'] = 'localhost'; -$_SERVER['REQUEST_URI'] = '/?page=login'; -$_SERVER['HTTPS'] = 'on'; - // Define global connectDB function if (!function_exists('connectDB')) { function connectDB($config) { @@ -62,3 +41,9 @@ if (!function_exists('connectDB')) { ]; } } + +// Set up server variables +$_SERVER['PHP_SELF'] = '/index.php'; +$_SERVER['REMOTE_ADDR'] = '127.0.0.1'; +$_SERVER['HTTP_USER_AGENT'] = 'PHPUnit Test Browser'; +$_SERVER['HTTPS'] = 'on'; diff --git a/tests/framework/test_php.ini b/tests/framework/test_php.ini new file mode 100644 index 0000000..45be211 --- /dev/null +++ b/tests/framework/test_php.ini @@ -0,0 +1,7 @@ +[Session] +session.cookie_httponly = 1 +session.use_only_cookies = 1 +session.cookie_secure = 1 +session.cookie_samesite = "Strict" +session.gc_maxlifetime = 1440 +session.use_strict_mode = 1