From 6fc3629014988f7d49b7dbfbb543804d003e763c Mon Sep 17 00:00:00 2001 From: Yasen Pramatarov Date: Tue, 18 Feb 2025 16:36:31 +0200 Subject: [PATCH] Adds initial unit tests --- tests/README.md | 40 +++ tests/framework/.gitignore | 4 + tests/framework/TestCase.php | 68 +++++ tests/framework/Unit/Classes/AgentTest.php | 233 ++++++++++++++++++ tests/framework/Unit/Classes/DatabaseTest.php | 131 ++++++++++ tests/framework/Unit/Classes/FeedbackTest.php | 112 +++++++++ tests/framework/Unit/Classes/HostTest.php | 183 ++++++++++++++ .../framework/Unit/Classes/JiloServerTest.php | 89 +++++++ tests/framework/Unit/Classes/LogTest.php | 138 +++++++++++ tests/framework/Unit/Classes/PlatformTest.php | 222 +++++++++++++++++ .../Unit/Classes/RateLimiterTest.php | 110 +++++++++ tests/framework/Unit/Classes/RouterTest.php | 86 +++++++ tests/framework/Unit/Classes/SettingsTest.php | 29 +++ tests/framework/Unit/Classes/UserTest.php | 164 ++++++++++++ .../framework/Unit/Classes/ValidatorTest.php | 143 +++++++++++ .../Unit/Helpers/SecurityHelperTest.php | 113 +++++++++ tests/framework/bootstrap.php | 64 +++++ tests/framework/composer.json | 50 ++++ tests/framework/phpunit.xml | 26 ++ 19 files changed, 2005 insertions(+) create mode 100644 tests/README.md create mode 100644 tests/framework/.gitignore create mode 100644 tests/framework/TestCase.php create mode 100644 tests/framework/Unit/Classes/AgentTest.php create mode 100644 tests/framework/Unit/Classes/DatabaseTest.php create mode 100644 tests/framework/Unit/Classes/FeedbackTest.php create mode 100644 tests/framework/Unit/Classes/HostTest.php create mode 100644 tests/framework/Unit/Classes/JiloServerTest.php create mode 100644 tests/framework/Unit/Classes/LogTest.php create mode 100644 tests/framework/Unit/Classes/PlatformTest.php create mode 100644 tests/framework/Unit/Classes/RateLimiterTest.php create mode 100644 tests/framework/Unit/Classes/RouterTest.php create mode 100644 tests/framework/Unit/Classes/SettingsTest.php create mode 100644 tests/framework/Unit/Classes/UserTest.php create mode 100644 tests/framework/Unit/Classes/ValidatorTest.php create mode 100644 tests/framework/Unit/Helpers/SecurityHelperTest.php create mode 100644 tests/framework/bootstrap.php create mode 100644 tests/framework/composer.json create mode 100644 tests/framework/phpunit.xml diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..eb2ac88 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,40 @@ +# Jilo Web Test Suite + +This directory contains the test suite for the Jilo Web application. All testing-related files are isolated here to keep the main application clean. + +## Structure + +``` +tests/ +├── framework/ # Test framework files +│ ├── composer.json # Composer configuration for tests +│ ├── phpunit.xml # PHPUnit configuration +│ ├── Unit/ # Unit tests +│ ├── Integration/ # Integration tests +│ └── TestCase.php # Base test case class +└── README.md # This file +``` + +## Running Tests + +1. Change to the framework directory: +```bash +cd tests/framework +``` + +2. Install dependencies (first time only): +```bash +composer install +``` + +3. Run all tests: +```bash +composer test +``` + +4. Generate coverage report: +```bash +composer test-coverage +``` + +The coverage report will be generated in `tests/framework/coverage/`. diff --git a/tests/framework/.gitignore b/tests/framework/.gitignore new file mode 100644 index 0000000..bc868f4 --- /dev/null +++ b/tests/framework/.gitignore @@ -0,0 +1,4 @@ +/vendor/ +/coverage/ +.phpunit.result.cache +composer.lock diff --git a/tests/framework/TestCase.php b/tests/framework/TestCase.php new file mode 100644 index 0000000..f4dd305 --- /dev/null +++ b/tests/framework/TestCase.php @@ -0,0 +1,68 @@ +db = new Database([ + 'type' => 'sqlite', + 'dbFile' => ':memory:' + ]); + + // Create jilo_agents table + $this->db->getConnection()->exec(" + CREATE TABLE jilo_agents ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + host_id INTEGER NOT NULL, + agent_type_id INTEGER NOT NULL, + url TEXT NOT NULL, + secret_key TEXT, + check_period INTEGER DEFAULT 60, + created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')), + updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) + ) + "); + + // Create jilo_agent_types table + $this->db->getConnection()->exec(" + CREATE TABLE jilo_agent_types ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + description TEXT NOT NULL, + endpoint TEXT NOT NULL + ) + "); + + // Create hosts table + $this->db->getConnection()->exec(" + CREATE TABLE hosts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + platform_id INTEGER NOT NULL, + name TEXT NOT NULL + ) + "); + + // Insert test host + $this->db->getConnection()->exec(" + INSERT INTO hosts (id, platform_id, name) VALUES (1, 1, 'Test Host') + "); + + // Insert test agent type + $this->db->getConnection()->exec(" + INSERT INTO jilo_agent_types (id, description, endpoint) + VALUES (1, 'Test Agent Type', '/api/test') + "); + + $this->agent = new Agent($this->db); + } + + public function testAddAgent() + { + $hostId = 1; + $data = [ + 'type_id' => 1, + 'url' => 'http://test.agent:8080', + 'secret_key' => 'test_secret', + 'check_period' => 60 + ]; + + try { + $result = $this->agent->addAgent($hostId, $data); + $this->assertTrue($result); + + // Verify agent was created + $stmt = $this->db->getConnection()->prepare('SELECT * FROM jilo_agents WHERE host_id = ?'); + $stmt->execute([$hostId]); + $agent = $stmt->fetch(PDO::FETCH_ASSOC); + + $this->assertEquals($data['url'], $agent['url']); + $this->assertEquals($data['secret_key'], $agent['secret_key']); + $this->assertEquals($data['check_period'], $agent['check_period']); + } catch (Exception $e) { + $this->fail('An error occurred while adding agent: ' . $e->getMessage()); + } + } + + public function testGetAgentDetails() + { + // Add test agent + $hostId = 1; + $data = [ + 'type_id' => 1, + 'url' => 'http://test.agent:8080', + 'secret_key' => 'test_secret', + 'check_period' => 60 + ]; + + $this->agent->addAgent($hostId, $data); + + // Test getting agent details + $agents = $this->agent->getAgentDetails($hostId); + $this->assertIsArray($agents); + $this->assertCount(1, $agents); + $this->assertEquals($data['url'], $agents[0]['url']); + } + + public function testEditAgent() + { + // Add test agent + $hostId = 1; + $data = [ + 'type_id' => 1, + 'url' => 'http://test.agent:8080', + 'secret_key' => 'test_secret', + 'check_period' => 60 + ]; + + $this->agent->addAgent($hostId, $data); + + // Get agent ID + $stmt = $this->db->getConnection()->prepare('SELECT id FROM jilo_agents WHERE host_id = ? LIMIT 1'); + $stmt->execute([$hostId]); + $agentId = $stmt->fetch(PDO::FETCH_COLUMN); + + // Update agent + $updateData = [ + 'type_id' => 1, + 'url' => 'http://updated.agent:8080', + 'secret_key' => 'updated_secret', + 'check_period' => 120, + 'agent_type_id' => 1 // Add this field for the update + ]; + + $result = $this->agent->editAgent($agentId, $updateData); + $this->assertTrue($result); + + // Verify update + $stmt = $this->db->getConnection()->prepare('SELECT * FROM jilo_agents WHERE id = ?'); + $stmt->execute([$agentId]); + $agent = $stmt->fetch(PDO::FETCH_ASSOC); + + $this->assertEquals($updateData['url'], $agent['url']); + $this->assertEquals($updateData['secret_key'], $agent['secret_key']); + $this->assertEquals($updateData['check_period'], $agent['check_period']); + } + + public function testDeleteAgent() + { + // Add test agent + $hostId = 1; + $data = [ + 'type_id' => 1, + 'url' => 'http://test.agent:8080', + 'secret_key' => 'test_secret', + 'check_period' => 60 + ]; + + $this->agent->addAgent($hostId, $data); + + // Get agent ID + $stmt = $this->db->getConnection()->prepare('SELECT id FROM jilo_agents WHERE host_id = ? LIMIT 1'); + $stmt->execute([$hostId]); + $agentId = $stmt->fetch(PDO::FETCH_COLUMN); + + // Delete agent + $result = $this->agent->deleteAgent($agentId); + $this->assertTrue($result); + + // Verify deletion + $stmt = $this->db->getConnection()->prepare('SELECT COUNT(*) FROM jilo_agents WHERE id = ?'); + $stmt->execute([$agentId]); + $count = $stmt->fetch(PDO::FETCH_COLUMN); + + $this->assertEquals(0, $count); + } + + public function testFetchAgent() + { + // Add test agent + $hostId = 1; + $data = [ + 'type_id' => 1, + 'url' => 'http://test.agent:8080', + 'secret_key' => 'test_secret', + 'check_period' => 60 + ]; + + $this->agent->addAgent($hostId, $data); + + // Get agent ID + $stmt = $this->db->getConnection()->prepare('SELECT id FROM jilo_agents WHERE host_id = ? LIMIT 1'); + $stmt->execute([$hostId]); + $agentId = $stmt->fetch(PDO::FETCH_COLUMN); + + // Mock fetch response + $mockAgent = $this->getMockBuilder(Agent::class) + ->setConstructorArgs([$this->db]) + ->onlyMethods(['fetchAgent']) + ->getMock(); + + $mockResponse = json_encode([ + 'status' => 'ok', + 'metrics' => [ + 'cpu_usage' => 25.5, + 'memory_usage' => 1024, + 'uptime' => 3600 + ] + ]); + + $mockAgent->expects($this->once()) + ->method('fetchAgent') + ->willReturn($mockResponse); + + $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 new file mode 100644 index 0000000..e9bcd54 --- /dev/null +++ b/tests/framework/Unit/Classes/DatabaseTest.php @@ -0,0 +1,131 @@ +config = [ + 'type' => 'sqlite', + 'dbFile' => ':memory:' + ]; + } + + public function testDatabaseConnection() + { + $db = new Database($this->config); + $this->assertNotNull($db->getConnection()); + } + + public function testMysqlAndMariadbEquivalence() + { + // Test that mysql and mariadb are treated the same + $mysqlConfig = [ + 'type' => 'mysql', + 'host' => 'invalid-host', + 'port' => 3306, + 'dbname' => 'test', + 'user' => 'test', + 'password' => 'test' + ]; + + $mariadbConfig = [ + 'type' => 'mariadb', + 'host' => 'invalid-host', + 'port' => 3306, + 'dbname' => 'test', + '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()); + } + + public function testInvalidDatabaseType() + { + $invalidConfig = [ + 'type' => 'invalid', + 'host' => 'localhost', + 'port' => 3306, + 'dbname' => 'test', + 'user' => 'test', + 'password' => 'test' + ]; + + $invalidDb = new Database($invalidConfig); + $this->assertNull($invalidDb->getConnection()); + } + + public function testMySQLConnectionMissingData() + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('MySQL connection data is missing'); + + $config = [ + 'type' => 'mysql', + 'host' => 'localhost', + 'port' => 3306, + 'dbname' => 'test', + // Missing user parameter + 'password' => 'test' + ]; + new Database($config); + } + + public function testPrepareAndExecute() + { + $db = new Database($this->config); + + // Create test table + $db->execute('CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)'); + + // Test prepare and execute + $result = $db->execute('INSERT INTO test (name) VALUES (?)', ['test_name']); + $this->assertEquals(1, $result->rowCount()); + + // Verify insertion + $result = $db->execute('SELECT name FROM test WHERE id = ?', [1]); + $row = $result->fetch(PDO::FETCH_ASSOC); + $this->assertEquals('test_name', $row['name']); + } + + public function testTransaction() + { + $db = new Database($this->config); + + // Create test table + $db->execute('CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)'); + + // Test successful transaction + $db->beginTransaction(); + $db->execute('INSERT INTO test (name) VALUES (?)', ['transaction_test']); + $db->commit(); + + $result = $db->execute('SELECT COUNT(*) as count FROM test'); + $this->assertEquals(1, $result->fetch(PDO::FETCH_ASSOC)['count']); + + // Test rollback + $db->beginTransaction(); + $db->execute('INSERT INTO test (name) VALUES (?)', ['rollback_test']); + $db->rollBack(); + + $result = $db->execute('SELECT COUNT(*) as count FROM test'); + $this->assertEquals(1, $result->fetch(PDO::FETCH_ASSOC)['count']); + } +} diff --git a/tests/framework/Unit/Classes/FeedbackTest.php b/tests/framework/Unit/Classes/FeedbackTest.php new file mode 100644 index 0000000..44ef9e0 --- /dev/null +++ b/tests/framework/Unit/Classes/FeedbackTest.php @@ -0,0 +1,112 @@ +assertIsArray($messages); + $this->assertCount(1, $messages); + + $message = $messages[0]; + $this->assertEquals('LOGIN', $message['category']); + $this->assertEquals('LOGIN_SUCCESS', $message['key']); + $this->assertEquals('Test message', $message['custom_message']); + } + + public function testRender() + { + // Test success message with custom text + $output = Feedback::render('LOGIN', 'LOGIN_SUCCESS', 'Success message'); + $this->assertStringContainsString('alert-success', $output); + $this->assertStringContainsString('Success message', $output); + $this->assertStringContainsString('alert-dismissible', $output); + + // Test error message (non-dismissible) + $output = Feedback::render('LOGIN', 'LOGIN_FAILED', 'Error message'); + $this->assertStringContainsString('alert-danger', $output); + $this->assertStringContainsString('Error message', $output); + $this->assertStringNotContainsString('alert-dismissible', $output); + + // Test small message + $output = Feedback::render('LOGIN', 'LOGIN_SUCCESS', 'Small message', true, true); + $this->assertStringContainsString('alert-sm', $output); + $this->assertStringContainsString('btn-close-sm', $output); + } + + 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']); + $this->assertTrue($data['dismissible']); + $this->assertFalse($data['small']); + + // Test with default message + $data = Feedback::getMessageData('LOGIN', 'LOGIN_SUCCESS'); + $this->assertNotNull($data['message']); + } + + public function testFlash() + { + Feedback::flash('LOGIN', 'LOGIN_SUCCESS', 'Test message'); + $this->assertArrayHasKey('flash_messages', $_SESSION); + $this->assertCount(1, $_SESSION['flash_messages']); + + $message = $_SESSION['flash_messages'][0]; + $this->assertEquals('LOGIN', $message['category']); + $this->assertEquals('LOGIN_SUCCESS', $message['key']); + $this->assertEquals('Test message', $message['custom_message']); + } + + public function testPredefinedMessageTypes() + { + $this->assertEquals('success', Feedback::TYPE_SUCCESS); + $this->assertEquals('danger', Feedback::TYPE_ERROR); + $this->assertEquals('info', Feedback::TYPE_INFO); + $this->assertEquals('warning', Feedback::TYPE_WARNING); + } + + public function testMessageConfigurations() + { + $config = Feedback::get('LOGIN', 'LOGIN_SUCCESS'); + $this->assertEquals(Feedback::TYPE_SUCCESS, $config['type']); + $this->assertTrue($config['dismissible']); + + $config = Feedback::get('LOGIN', 'LOGIN_FAILED'); + $this->assertEquals(Feedback::TYPE_ERROR, $config['type']); + $this->assertFalse($config['dismissible']); + } +} diff --git a/tests/framework/Unit/Classes/HostTest.php b/tests/framework/Unit/Classes/HostTest.php new file mode 100644 index 0000000..e5cce13 --- /dev/null +++ b/tests/framework/Unit/Classes/HostTest.php @@ -0,0 +1,183 @@ +db = new \Database([ + 'type' => 'sqlite', + 'dbFile' => ':memory:' + ]); + + // Create hosts table + $this->db->getConnection()->exec(" + CREATE TABLE hosts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + platform_id INTEGER NOT NULL, + name TEXT NOT NULL, + address TEXT NOT NULL + ) + "); + + // Create jilo_agents table for relationship testing + $this->db->getConnection()->exec(" + CREATE TABLE jilo_agents ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + host_id INTEGER NOT NULL, + agent_type_id INTEGER NOT NULL, + url TEXT NOT NULL, + secret_key TEXT, + check_period INTEGER DEFAULT 60 + ) + "); + + $this->host = new \Host($this->db); + } + + public function testAddHost() + { + $data = [ + 'platform_id' => 1, + 'name' => 'Test host', + 'address' => '192.168.1.100' + ]; + + $result = $this->host->addHost($data); + $this->assertTrue($result); + + // Verify host was created + $stmt = $this->db->getConnection()->prepare('SELECT * FROM hosts WHERE platform_id = ? AND name = ?'); + $stmt->execute([$data['platform_id'], $data['name']]); + $host = $stmt->fetch(\PDO::FETCH_ASSOC); + + $this->assertEquals($data['name'], $host['name']); + $this->assertEquals($data['address'], $host['address']); + } + + public function testGetHostDetails() + { + // Add test host + $data = [ + 'platform_id' => 1, + 'name' => 'Test host', + 'address' => '192.168.1.100' + ]; + $this->host->addHost($data); + + // Test getting all hosts + $hosts = $this->host->getHostDetails(); + $this->assertIsArray($hosts); + $this->assertCount(1, $hosts); + + // Test getting hosts by platform + $hosts = $this->host->getHostDetails(1); + $this->assertIsArray($hosts); + $this->assertCount(1, $hosts); + $this->assertEquals($data['name'], $hosts[0]['name']); + + // Test getting specific host + $hosts = $this->host->getHostDetails(1, $hosts[0]['id']); + $this->assertIsArray($hosts); + $this->assertCount(1, $hosts); + $this->assertEquals($data['name'], $hosts[0]['name']); + } + + public function testEditHost() + { + // Add test host + $data = [ + 'platform_id' => 1, + 'name' => 'Test host', + 'address' => '192.168.1.100' + ]; + $this->host->addHost($data); + + // Get host ID + $stmt = $this->db->getConnection()->prepare('SELECT id FROM hosts WHERE platform_id = ? AND name = ?'); + $stmt->execute([$data['platform_id'], $data['name']]); + $hostId = $stmt->fetch(\PDO::FETCH_COLUMN); + + // Update host + $updateData = [ + 'id' => $hostId, + 'name' => 'Updated host', + 'address' => '192.168.1.200' + ]; + + $result = $this->host->editHost($data['platform_id'], $updateData); + $this->assertTrue($result); + + // Verify update + $stmt = $this->db->getConnection()->prepare('SELECT * FROM hosts WHERE id = ?'); + $stmt->execute([$hostId]); + $host = $stmt->fetch(\PDO::FETCH_ASSOC); + + $this->assertEquals($updateData['name'], $host['name']); + $this->assertEquals($updateData['address'], $host['address']); + } + + public function testDeleteHost() + { + // Add test host + $data = [ + 'platform_id' => 1, + 'name' => 'Test host', + 'address' => '192.168.1.100' + ]; + $this->host->addHost($data); + + // Get host ID + $stmt = $this->db->getConnection()->prepare('SELECT id FROM hosts WHERE platform_id = ? AND name = ?'); + $stmt->execute([$data['platform_id'], $data['name']]); + $hostId = $stmt->fetch(\PDO::FETCH_COLUMN); + + // Add test agent to the host + $this->db->getConnection()->exec(" + INSERT INTO jilo_agents (host_id, agent_type_id, url, secret_key) + VALUES ($hostId, 1, 'http://test:8080', 'secret') + "); + + // Delete host + $result = $this->host->deleteHost($hostId); + $this->assertTrue($result); + + // Verify host deletion + $stmt = $this->db->getConnection()->prepare('SELECT COUNT(*) FROM hosts WHERE id = ?'); + $stmt->execute([$hostId]); + $hostCount = $stmt->fetch(\PDO::FETCH_COLUMN); + $this->assertEquals(0, $hostCount); + + // Verify agent deletion + $stmt = $this->db->getConnection()->prepare('SELECT COUNT(*) FROM jilo_agents WHERE host_id = ?'); + $stmt->execute([$hostId]); + $agentCount = $stmt->fetch(\PDO::FETCH_COLUMN); + $this->assertEquals(0, $agentCount); + } + + public function testEditNonexistentHost() + { + $updateData = [ + 'id' => 999, + 'name' => 'Nonexistent host', + 'address' => '192.168.1.200' + ]; + + $result = $this->host->editHost(1, $updateData); + $this->assertIsString($result); + $this->assertStringContainsString('No host found', $result); + } +} diff --git a/tests/framework/Unit/Classes/JiloServerTest.php b/tests/framework/Unit/Classes/JiloServerTest.php new file mode 100644 index 0000000..0edb07d --- /dev/null +++ b/tests/framework/Unit/Classes/JiloServerTest.php @@ -0,0 +1,89 @@ +db = new Database([ + 'type' => 'sqlite', + 'dbFile' => ':memory:' + ]); + + // Create servers table + $this->db->getConnection()->exec(" + CREATE TABLE servers ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + host_id INTEGER NOT NULL, + port INTEGER NOT NULL, + status TEXT DEFAULT 'offline', + last_seen INTEGER, + version TEXT, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL + ) + "); + + $this->server = new Server($this->db); + } + + public function testGetServerStatus() + { + // Create mock server that overrides file_get_contents + $mockServer = $this->getMockBuilder(Server::class) + ->setConstructorArgs([$this->db]) + ->onlyMethods(['getServerStatus']) + ->getMock(); + + // Test successful response + $mockServer->expects($this->exactly(2)) + ->method('getServerStatus') + ->willReturnMap([ + ['localhost', 8080, '/health', true], + ['localhost', 8081, '/health', false] + ]); + + $this->assertTrue($mockServer->getServerStatus('localhost', 8080)); + $this->assertFalse($mockServer->getServerStatus('localhost', 8081)); + } + + public function testGetServerStatusWithCustomEndpoint() + { + $mockServer = $this->getMockBuilder(Server::class) + ->setConstructorArgs([$this->db]) + ->onlyMethods(['getServerStatus']) + ->getMock(); + + $mockServer->expects($this->once()) + ->method('getServerStatus') + ->with('localhost', 8080, '/custom/health') + ->willReturn(true); + + $this->assertTrue($mockServer->getServerStatus('localhost', 8080, '/custom/health')); + } + + public function testGetServerStatusWithDefaults() + { + $mockServer = $this->getMockBuilder(Server::class) + ->setConstructorArgs([$this->db]) + ->onlyMethods(['getServerStatus']) + ->getMock(); + + $mockServer->expects($this->once()) + ->method('getServerStatus') + ->with('127.0.0.1', 8080, '/health') + ->willReturn(true); + + $this->assertTrue($mockServer->getServerStatus()); + } +} diff --git a/tests/framework/Unit/Classes/LogTest.php b/tests/framework/Unit/Classes/LogTest.php new file mode 100644 index 0000000..c82333c --- /dev/null +++ b/tests/framework/Unit/Classes/LogTest.php @@ -0,0 +1,138 @@ +db = new Database([ + 'type' => 'sqlite', + 'dbFile' => ':memory:' + ]); + + // Create users table + $this->db->getConnection()->exec(" + CREATE TABLE users ( + id INTEGER PRIMARY KEY, + username TEXT NOT NULL + ) + "); + + // Create test user + $this->db->getConnection()->exec(" + INSERT INTO users (id, username) VALUES (1, 'testuser'), (2, 'testuser2') + "); + + // Create logs table + $this->db->getConnection()->exec(" + CREATE TABLE logs ( + id INTEGER PRIMARY KEY, + user_id INTEGER, + scope TEXT NOT NULL, + message TEXT NOT NULL, + time DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) + ) + "); + + $this->log = new Log($this->db); + } + + public function testInsertLog() + { + $result = $this->log->insertLog(1, 'Test message', 'test'); + $this->assertTrue($result); + + $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']); + } + + public function testReadLog() + { + // 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']); + $this->assertEquals('Test message 2', $logs[1]['message']); + } + + public function testReadLogWithTimeFilter() + { + // Insert test logs with different times + $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); + } + + public function testReadLogWithPagination() + { + // Insert test logs + $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); + } + + public function testReadLogWithMessageFilter() + { + // 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']); + } + + public function testReadLogWithUserFilter() + { + // 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 new file mode 100644 index 0000000..539aa62 --- /dev/null +++ b/tests/framework/Unit/Classes/PlatformTest.php @@ -0,0 +1,222 @@ +db = new Database([ + 'type' => 'sqlite', + 'dbFile' => ':memory:' + ]); + + // Create hosts table + $this->db->getConnection()->exec(" + CREATE TABLE hosts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + platform_id INTEGER NOT NULL, + name TEXT NOT NULL + ) + "); + + // Create jilo_agents table + $this->db->getConnection()->exec(" + CREATE TABLE jilo_agents ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + host_id INTEGER NOT NULL + ) + "); + + // Create platforms table + $this->db->getConnection()->exec(" + CREATE TABLE platforms ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + jitsi_url TEXT NOT NULL, + jilo_database TEXT NOT NULL, + created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')), + updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) + ) + "); + + $this->platform = new Platform($this->db); + } + + public function testAddPlatform() + { + $data = [ + 'name' => 'Test platform', + 'jitsi_url' => 'https://jitsi.example.com', + 'jilo_database' => '/path/to/jilo.db' + ]; + + $result = $this->platform->addPlatform($data); + $this->assertTrue($result); + + // Verify platform was created + $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']); + $this->assertEquals($data['jilo_database'], $platform['jilo_database']); + } + + public function testGetPlatformDetails() + { + // Create test platform + $stmt = $this->db->getConnection()->prepare('INSERT INTO platforms (name, jitsi_url, jilo_database) VALUES (?, ?, ?)'); + $stmt->execute(['Test platform', 'https://jitsi.example.com', '/path/to/jilo.db']); + $platformId = $this->db->getConnection()->lastInsertId(); + + // Test getting specific platform + $platform = $this->platform->getPlatformDetails($platformId); + $this->assertIsArray($platform); + $this->assertEquals('Test platform', $platform[0]['name']); + + // Test getting all platforms + $platforms = $this->platform->getPlatformDetails(); + $this->assertIsArray($platforms); + $this->assertCount(1, $platforms); + } + + public function testEditPlatform() + { + // Create test platform + $stmt = $this->db->getConnection()->prepare('INSERT INTO platforms (name, jitsi_url, jilo_database) VALUES (?, ?, ?)'); + $stmt->execute(['Test platform', 'https://jitsi.example.com', '/path/to/jilo.db']); + $platformId = $this->db->getConnection()->lastInsertId(); + + $updateData = [ + 'name' => 'Updated platform', + 'jitsi_url' => 'https://new.example.com', + 'jilo_database' => '/path/to/jilo.db' + ]; + + $result = $this->platform->editPlatform($platformId, $updateData); + $this->assertTrue($result); + + // Verify update + $stmt = $this->db->getConnection()->prepare('SELECT * FROM platforms WHERE id = ?'); + $stmt->execute([$platformId]); + $platform = $stmt->fetch(PDO::FETCH_ASSOC); + + $this->assertEquals($updateData['name'], $platform['name']); + $this->assertEquals($updateData['jitsi_url'], $platform['jitsi_url']); + } + + public function testDeletePlatform() + { + // Create test platform + $stmt = $this->db->getConnection()->prepare('INSERT INTO platforms (name, jitsi_url, jilo_database) VALUES (?, ?, ?)'); + $stmt->execute(['Test platform', 'https://jitsi.example.com', '/path/to/jilo.db']); + $platformId = $this->db->getConnection()->lastInsertId(); + + // Create test host + $stmt = $this->db->getConnection()->prepare('INSERT INTO hosts (platform_id, name) VALUES (?, ?)'); + $stmt->execute([$platformId, 'Test host']); + $hostId = $this->db->getConnection()->lastInsertId(); + + // Create test agent + $stmt = $this->db->getConnection()->prepare('INSERT INTO jilo_agents (host_id) VALUES (?)'); + $stmt->execute([$hostId]); + + $result = $this->platform->deletePlatform($platformId); + $this->assertTrue($result); + + // Verify platform deletion + $stmt = $this->db->getConnection()->prepare('SELECT COUNT(*) as count FROM platforms WHERE id = ?'); + $stmt->execute([$platformId]); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + $this->assertEquals(0, $result['count']); + + // Verify host deletion + $stmt = $this->db->getConnection()->prepare('SELECT COUNT(*) as count FROM hosts WHERE platform_id = ?'); + $stmt->execute([$platformId]); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + $this->assertEquals(0, $result['count']); + + // Verify agent deletion + $stmt = $this->db->getConnection()->prepare('SELECT COUNT(*) as count FROM jilo_agents WHERE host_id = ?'); + $stmt->execute([$hostId]); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + $this->assertEquals(0, $result['count']); + } + + public function testValidatePlatformData() + { + $validData = [ + 'name' => 'Test platform', + 'jitsi_url' => 'https://jitsi.example.com', + 'jilo_database' => '/path/to/jilo.db' + ]; + + $result = $this->platform->addPlatform($validData); + $this->assertTrue($result); + + // Verify platform was created + $stmt = $this->db->getConnection()->prepare('SELECT COUNT(*) as count FROM platforms WHERE name = ?'); + $stmt->execute([$validData['name']]); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + $this->assertEquals(1, $result['count']); + + // Test invalid data (missing required fields) + $invalidData = [ + 'name' => 'Test platform 2' + // Missing jitsi_url and jilo_database + ]; + + $result = $this->platform->addPlatform($invalidData); + $this->assertIsString($result); // Should return error message + + // Verify platform was not created + $stmt = $this->db->getConnection()->prepare('SELECT COUNT(*) as count FROM platforms WHERE name = ?'); + $stmt->execute([$invalidData['name']]); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + $this->assertEquals(0, $result['count']); + } + + public function testCheckJiloDatabaseAccess() + { + // Create a temporary SQLite database for testing + $tempDb = tempnam(sys_get_temp_dir(), 'jilo_test_'); + $testDb = new \SQLite3($tempDb); + $testDb->close(); + + $data = [ + 'name' => 'Test platform', + 'jitsi_url' => 'https://jitsi.example.com', + 'jilo_database' => $tempDb + ]; + + $result = $this->platform->addPlatform($data); + $this->assertTrue($result); + + // Verify platform was created + $stmt = $this->db->getConnection()->prepare('SELECT COUNT(*) as count FROM platforms WHERE jilo_database = ?'); + $stmt->execute([$tempDb]); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + $this->assertEquals(1, $result['count']); + + // Test with non-existent database + $data['name'] = 'Another platform'; + $data['jilo_database'] = '/nonexistent/path/db.sqlite'; + $result = $this->platform->addPlatform($data); + $this->assertTrue($result); // No database validation in Platform class + + // Clean up + unlink($tempDb); + } +} diff --git a/tests/framework/Unit/Classes/RateLimiterTest.php b/tests/framework/Unit/Classes/RateLimiterTest.php new file mode 100644 index 0000000..b5e15cc --- /dev/null +++ b/tests/framework/Unit/Classes/RateLimiterTest.php @@ -0,0 +1,110 @@ +db = new Database([ + 'type' => 'sqlite', + 'dbFile' => ':memory:' + ]); + + $this->rateLimiter = new RateLimiter($this->db); + } + + public function testGetRecentAttempts() + { + $ip = '127.0.0.1'; + $username = 'testuser'; + + // Clean up any existing attempts first + $stmt = $this->db->getConnection()->prepare("DELETE FROM {$this->rateLimiter->authRatelimitTable} WHERE ip_address = ?"); + $stmt->execute([$ip]); + + // Initially should have no attempts + $attempts = $this->rateLimiter->getRecentAttempts($ip); + $this->assertEquals(0, $attempts); + + // Add a login attempt + $stmt = $this->db->getConnection()->prepare("INSERT INTO {$this->rateLimiter->authRatelimitTable} (ip_address, username) VALUES (?, ?)"); + $stmt->execute([$ip, $username]); + + // Should now have 1 attempt + $attempts = $this->rateLimiter->getRecentAttempts($ip); + $this->assertEquals(1, $attempts); + } + + public function testIpBlacklisting() + { + $ip = '192.0.2.1'; // Using TEST-NET-1 range + + // Should be blacklisted by default (TEST-NET-1 range) + $this->assertTrue($this->rateLimiter->isIpBlacklisted($ip)); + + // Test with non-blacklisted IP + $nonBlacklistedIp = '8.8.8.8'; // Google DNS + $this->assertFalse($this->rateLimiter->isIpBlacklisted($nonBlacklistedIp)); + + // Add IP to blacklist + $stmt = $this->db->getConnection()->prepare("INSERT INTO {$this->rateLimiter->blacklistTable} (ip_address, reason) VALUES (?, ?)"); + $stmt->execute([$nonBlacklistedIp, 'Test blacklist']); + + // IP should now be blacklisted + $this->assertTrue($this->rateLimiter->isIpBlacklisted($nonBlacklistedIp)); + } + + public function testIpWhitelisting() + { + $ip = '127.0.0.1'; // Localhost + + // Clean up any existing whitelist entries + $stmt = $this->db->getConnection()->prepare("DELETE FROM {$this->rateLimiter->whitelistTable} WHERE ip_address = ?"); + $stmt->execute([$ip]); + + // Add to whitelist + $stmt = $this->db->getConnection()->prepare("INSERT INTO {$this->rateLimiter->whitelistTable} (ip_address, description) VALUES (?, ?)"); + $stmt->execute([$ip, 'Test whitelist']); + + // Should be whitelisted + $this->assertTrue($this->rateLimiter->isIpWhitelisted($ip)); + + // Test with non-whitelisted IP + $nonWhitelistedIp = '8.8.8.8'; // Google DNS + $this->assertFalse($this->rateLimiter->isIpWhitelisted($nonWhitelistedIp)); + + // Add to whitelist + $stmt = $this->db->getConnection()->prepare("INSERT INTO {$this->rateLimiter->whitelistTable} (ip_address, description) VALUES (?, ?)"); + $stmt->execute([$nonWhitelistedIp, 'Test whitelist']); + + // Should now be whitelisted + $this->assertTrue($this->rateLimiter->isIpWhitelisted($nonWhitelistedIp)); + } + + public function testIpRangeBlacklisting() + { + $ip = '8.8.8.8'; // Google DNS + $networkIp = '8.8.8.0/24'; // Network containing Google DNS + + // Initially IP should not be blacklisted + $this->assertFalse($this->rateLimiter->isIpBlacklisted($ip)); + + // Add network to blacklist + $stmt = $this->db->getConnection()->prepare("INSERT INTO {$this->rateLimiter->blacklistTable} (ip_address, is_network, reason) VALUES (?, 1, ?)"); + $stmt->execute([$networkIp, 'Test network blacklist']); + + // IP in range should now be blacklisted + $this->assertTrue($this->rateLimiter->isIpBlacklisted($ip)); + } +} diff --git a/tests/framework/Unit/Classes/RouterTest.php b/tests/framework/Unit/Classes/RouterTest.php new file mode 100644 index 0000000..a316c74 --- /dev/null +++ b/tests/framework/Unit/Classes/RouterTest.php @@ -0,0 +1,86 @@ +router = new Router('', true); // Empty controller path and dry-run mode + } + + public function testAddRoute() + { + $this->router->add('/test', 'TestController@index'); + $this->assertTrue(true); // If we get here, no exception was thrown + } + + public function testDispatchRoute() + { + $this->router->add('/users/(\d+)', 'UserController@show'); + + $match = $this->router->dispatch('/users/123'); + $this->assertIsArray($match); + $this->assertEquals('UserController@show', $match['callback']); + $this->assertEquals(['123'], $match['params']); + } + + public function testDispatchRouteWithMultipleParameters() + { + $this->router->add('/users/(\d+)/posts/(\d+)', 'PostController@show'); + + $match = $this->router->dispatch('/users/123/posts/456'); + $this->assertIsArray($match); + $this->assertEquals('PostController@show', $match['callback']); + $this->assertEquals(['123', '456'], $match['params']); + } + + public function testNoMatchingRoute() + { + $this->router->add('/test', 'TestController@index'); + + $match = $this->router->dispatch('/nonexistent'); + $this->assertNull($match); + } + + public function testDispatchWithQueryString() + { + $this->router->add('/test', 'TestController@index'); + + $match = $this->router->dispatch('/test?param=value'); + $this->assertIsArray($match); + $this->assertEquals('TestController@index', $match['callback']); + $this->assertEquals([], $match['params']); + } + + public function testOptionalParameters() + { + $this->router->add('/users(?:/(\d+))?', 'UserController@index'); + + // Test with parameter + $match1 = $this->router->dispatch('/users/123'); + $this->assertIsArray($match1); + $this->assertEquals('UserController@index', $match1['callback']); + $this->assertEquals(['123'], $match1['params']); + + // Test without parameter + $match2 = $this->router->dispatch('/users'); + $this->assertIsArray($match2); + $this->assertEquals('UserController@index', $match2['callback']); + $this->assertEquals([], $match2['params']); + } + + public function testInvokeWithMissingController() + { + $router = new Router(''); // Empty controller path, not in dry-run mode + ob_start(); + $router->dispatch('/test'); + $output = ob_get_clean(); + $this->assertEquals('404 page not found', $output); + } +} diff --git a/tests/framework/Unit/Classes/SettingsTest.php b/tests/framework/Unit/Classes/SettingsTest.php new file mode 100644 index 0000000..c288461 --- /dev/null +++ b/tests/framework/Unit/Classes/SettingsTest.php @@ -0,0 +1,29 @@ +settings = new Settings(null); + } + + public function testGetPlatformJsFileWithInvalidUrl() + { + $result = $this->settings->getPlatformJsFile('invalid-url', 'test.js'); + $this->assertEquals('Invalid URL: invalid-url/test.js', $result); + } + + public function testGetPlatformJsFileWithValidUrl() + { + $result = $this->settings->getPlatformJsFile('https://example.com', 'test.js'); + $this->assertStringContainsString("can't be loaded", $result); + } +} diff --git a/tests/framework/Unit/Classes/UserTest.php b/tests/framework/Unit/Classes/UserTest.php new file mode 100644 index 0000000..26a8ba7 --- /dev/null +++ b/tests/framework/Unit/Classes/UserTest.php @@ -0,0 +1,164 @@ +db = new Database([ + 'type' => 'sqlite', + 'dbFile' => ':memory:' + ]); + + // Create users table + $this->db->getConnection()->exec(" + CREATE TABLE users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT NOT NULL UNIQUE, + password TEXT NOT NULL + ) + "); + + // Create users_meta table + $this->db->getConnection()->exec(" + CREATE TABLE users_meta ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + name TEXT, + email TEXT, + timezone TEXT, + bio TEXT, + avatar TEXT, + FOREIGN KEY (user_id) REFERENCES users(id) + ) + "); + + // Create tables for rate limiter + $this->db->getConnection()->exec(" + CREATE TABLE login_attempts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + ip_address TEXT NOT NULL, + username TEXT NOT NULL, + attempted_at TEXT DEFAULT (DATETIME('now')) + ) + "); + + $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 + ) + "); + + $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->user = new User($this->db); + } + + public function testRegister() + { + $result = $this->user->register('testuser', 'password123'); + $this->assertTrue($result); + + // Verify user was created + $stmt = $this->db->getConnection()->prepare('SELECT * FROM users WHERE username = ?'); + $stmt->execute(['testuser']); + $user = $stmt->fetch(\PDO::FETCH_ASSOC); + + $this->assertEquals('testuser', $user['username']); + $this->assertTrue(password_verify('password123', $user['password'])); + + // Verify user_meta was created + $stmt = $this->db->getConnection()->prepare('SELECT * FROM users_meta WHERE user_id = ?'); + $stmt->execute([$user['id']]); + $meta = $stmt->fetch(\PDO::FETCH_ASSOC); + + $this->assertNotNull($meta); + } + + public function testLogin() + { + // Create a test user + $password = 'password123'; + $hashedPassword = password_hash($password, PASSWORD_DEFAULT); + + $stmt = $this->db->getConnection()->prepare('INSERT INTO users (username, password) VALUES (?, ?)'); + $stmt->execute(['testuser', $hashedPassword]); + + // Mock $_SERVER['REMOTE_ADDR'] for rate limiter + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + + // Test successful login + try { + $result = $this->user->login('testuser', $password); + $this->assertTrue($result); + } catch (Exception $e) { + $this->fail('Login should not throw an exception for valid credentials: ' . $e->getMessage()); + } + + // Test failed login + try { + $this->user->login('testuser', 'wrongpassword'); + $this->fail('Login should throw an exception for invalid credentials'); + } catch (Exception $e) { + $this->assertStringContainsString('Invalid credentials', $e->getMessage()); + } + + // Test nonexistent user + try { + $this->user->login('nonexistent', $password); + $this->fail('Login should throw an exception for nonexistent user'); + } catch (Exception $e) { + $this->assertStringContainsString('Invalid credentials', $e->getMessage()); + } + } + + public function testGetUserDetails() + { + // Create a test user + $stmt = $this->db->getConnection()->prepare('INSERT INTO users (username, password) VALUES (?, ?)'); + $stmt->execute(['testuser', 'hashedpassword']); + $userId = $this->db->getConnection()->lastInsertId(); + + // Create user meta with some data + $stmt = $this->db->getConnection()->prepare('INSERT INTO users_meta (user_id, name, email) VALUES (?, ?, ?)'); + $stmt->execute([$userId, 'Test User', 'test@example.com']); + + $userDetails = $this->user->getUserDetails($userId); + $this->assertIsArray($userDetails); + $this->assertCount(1, $userDetails); // Should return one row + $user = $userDetails[0]; // Get the first row + $this->assertEquals('testuser', $user['username']); + $this->assertEquals('Test User', $user['name']); + $this->assertEquals('test@example.com', $user['email']); + + // Test nonexistent user + $userDetails = $this->user->getUserDetails(999); + $this->assertEmpty($userDetails); + } +} diff --git a/tests/framework/Unit/Classes/ValidatorTest.php b/tests/framework/Unit/Classes/ValidatorTest.php new file mode 100644 index 0000000..a31835d --- /dev/null +++ b/tests/framework/Unit/Classes/ValidatorTest.php @@ -0,0 +1,143 @@ + 'John']; + $validator = new Validator($data); + $rules = ['name' => ['required' => true]]; + + $this->assertTrue($validator->validate($rules)); + $this->assertEmpty($validator->getErrors()); + + // Test invalid data + $data = ['name' => '']; + $validator = new Validator($data); + $this->assertFalse($validator->validate($rules)); + $this->assertNotEmpty($validator->getErrors()); + } + + public function testEmail() + { + // Test valid email + $data = ['email' => 'test@example.com']; + $validator = new Validator($data); + $rules = ['email' => ['email' => true]]; + + $this->assertTrue($validator->validate($rules)); + $this->assertEmpty($validator->getErrors()); + + // Test invalid email + $data = ['email' => 'invalid-email']; + $validator = new Validator($data); + $this->assertFalse($validator->validate($rules)); + $this->assertNotEmpty($validator->getErrors()); + } + + public function testMinLength() + { + // Test valid length + $data = ['password' => '123456']; + $validator = new Validator($data); + $rules = ['password' => ['min' => 6]]; + + $this->assertTrue($validator->validate($rules)); + $this->assertEmpty($validator->getErrors()); + + // Test invalid length + $data = ['password' => '12345']; + $validator = new Validator($data); + $this->assertFalse($validator->validate($rules)); + $this->assertNotEmpty($validator->getErrors()); + } + + public function testMaxLength() + { + // Test valid length + $data = ['username' => '12345']; + $validator = new Validator($data); + $rules = ['username' => ['max' => 5]]; + + $this->assertTrue($validator->validate($rules)); + $this->assertEmpty($validator->getErrors()); + + // Test invalid length + $data = ['username' => '123456']; + $validator = new Validator($data); + $this->assertFalse($validator->validate($rules)); + $this->assertNotEmpty($validator->getErrors()); + } + + public function testNumeric() + { + // Test valid number + $data = ['age' => '25']; + $validator = new Validator($data); + $rules = ['age' => ['numeric' => true]]; + + $this->assertTrue($validator->validate($rules)); + $this->assertEmpty($validator->getErrors()); + + // Test invalid number + $data = ['age' => 'twenty-five']; + $validator = new Validator($data); + $this->assertFalse($validator->validate($rules)); + $this->assertNotEmpty($validator->getErrors()); + } + + public function testUrl() + { + // Test valid URL + $data = ['website' => 'https://example.com']; + $validator = new Validator($data); + $rules = ['website' => ['url' => true]]; + + $this->assertTrue($validator->validate($rules)); + $this->assertEmpty($validator->getErrors()); + + // Test invalid URL + $data = ['website' => 'not-a-url']; + $validator = new Validator($data); + $this->assertFalse($validator->validate($rules)); + $this->assertNotEmpty($validator->getErrors()); + } + + public function testMultipleRules() + { + // Test valid data + $data = ['email' => 'test@example.com']; + $validator = new Validator($data); + $rules = ['email' => [ + 'required' => true, + 'email' => true, + 'max' => 50 + ]]; + + $this->assertTrue($validator->validate($rules)); + $this->assertEmpty($validator->getErrors()); + + // Test invalid data + $data = ['email' => str_repeat('a', 51) . '@example.com']; + $validator = new Validator($data); + $this->assertFalse($validator->validate($rules)); + $this->assertNotEmpty($validator->getErrors()); + } + + public function testCustomErrorMessages() + { + $data = ['age' => 'not-a-number']; + $validator = new Validator($data); + $rules = ['age' => ['numeric' => true]]; + + $this->assertFalse($validator->validate($rules)); + $errors = $validator->getErrors(); + $this->assertNotEmpty($errors); + $this->assertEquals('Must be a number', $errors['age'][0]); + } +} diff --git a/tests/framework/Unit/Helpers/SecurityHelperTest.php b/tests/framework/Unit/Helpers/SecurityHelperTest.php new file mode 100644 index 0000000..33eeb5e --- /dev/null +++ b/tests/framework/Unit/Helpers/SecurityHelperTest.php @@ -0,0 +1,113 @@ +security = SecurityHelper::getInstance(); + } + + public function testGenerateCsrfToken() + { + $token = $this->security->generateCsrfToken(); + + $this->assertNotEmpty($token); + $this->assertEquals(64, strlen($token)); // 32 bytes = 64 hex chars + $this->assertEquals($token, $_SESSION['csrf_token']); + } + + public function testVerifyCsrfToken() + { + $token = $this->security->generateCsrfToken(); + + $this->assertTrue($this->security->verifyCsrfToken($token)); + $this->assertFalse($this->security->verifyCsrfToken('invalid_token')); + $this->assertFalse($this->security->verifyCsrfToken('')); + } + + public function testSanitizeString() + { + $input = ''; + $expected = 'alert("xss")'; + + $this->assertEquals($expected, $this->security->sanitizeString($input)); + $this->assertEquals('', $this->security->sanitizeString(null)); + $this->assertEquals('', $this->security->sanitizeString([])); + } + + public function testValidateEmail() + { + $this->assertTrue($this->security->validateEmail('test@example.com')); + $this->assertTrue($this->security->validateEmail('user.name+tag@example.co.uk')); + $this->assertFalse($this->security->validateEmail('invalid.email')); + $this->assertFalse($this->security->validateEmail('@example.com')); + } + + public function testValidateInt() + { + $this->assertTrue($this->security->validateInt('123')); + $this->assertTrue($this->security->validateInt('-123')); + $this->assertFalse($this->security->validateInt('12.3')); + $this->assertFalse($this->security->validateInt('abc')); + } + + public function testValidateUrl() + { + $this->assertTrue($this->security->validateUrl('https://example.com')); + $this->assertTrue($this->security->validateUrl('http://sub.example.co.uk/path?query=1')); + $this->assertTrue($this->security->validateUrl('ftp://example.com')); // Any valid URL is accepted + $this->assertFalse($this->security->validateUrl('not-a-url')); + } + + public function testSanitizeArray() + { + $input = [ + 'name' => 'John', + 'email' => 'john@example.com', + 'nested' => [ + 'key' => 'value' + ] + ]; + + $allowedKeys = ['name', 'email']; + $result = $this->security->sanitizeArray($input, $allowedKeys); + + $this->assertArrayHasKey('name', $result); + $this->assertArrayHasKey('email', $result); + $this->assertArrayNotHasKey('nested', $result); + $this->assertEquals('John', $result['name']); // HTML tags are stripped + $this->assertEquals('john@example.com', $result['email']); + } + + public function testValidateFormData() + { + $data = [ + 'name' => 'John Doe', + 'email' => 'invalid-email', + 'age' => 'not-a-number', + 'website' => 'not-a-url' + ]; + + $rules = [ + 'name' => ['type' => 'string', 'required' => true, 'min' => 2, 'max' => 50], + 'email' => ['type' => 'email', 'required' => true], + 'age' => ['type' => 'integer', 'required' => true], + 'website' => ['type' => 'url', 'required' => true] + ]; + + $errors = $this->security->validateFormData($data, $rules); + + $this->assertIsArray($errors); + $this->assertCount(3, $errors); + $this->assertArrayHasKey('email', $errors); + $this->assertArrayHasKey('age', $errors); + $this->assertArrayHasKey('website', $errors); + } +} diff --git a/tests/framework/bootstrap.php b/tests/framework/bootstrap.php new file mode 100644 index 0000000..887e203 --- /dev/null +++ b/tests/framework/bootstrap.php @@ -0,0 +1,64 @@ + [ + '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) { + global $dbWeb; + return [ + 'db' => $dbWeb + ]; + } +} diff --git a/tests/framework/composer.json b/tests/framework/composer.json new file mode 100644 index 0000000..4e55f7e --- /dev/null +++ b/tests/framework/composer.json @@ -0,0 +1,50 @@ +{ + "name": "lindeas/jilo-web-tests", + "description": "Test Suite for Jilo Web Application", + "type": "project", + "require": { + "php": ">=7.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "phpunit/php-code-coverage": "^9.2", + "mockery/mockery": "^1.5" + }, + "autoload": { + "files": [ + "../../app/includes/errors.php", + "../../app/classes/database.php", + "../../app/classes/agent.php", + "../../app/classes/host.php", + "../../app/classes/platform.php", + "../../app/classes/server.php", + "../../app/classes/log.php", + "../../app/classes/feedback.php", + "../../app/classes/settings.php", + "../../app/classes/validator.php", + "../../app/classes/router.php", + "../../app/classes/ratelimiter.php", + "../../app/classes/user.php", + "../../app/helpers/security.php", + "../../app/includes/errors.php" + ], + "classmap": [ + "TestCase.php" + ] + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "./" + } + }, + "scripts": { + "test": "phpunit", + "test-coverage": "phpunit --coverage-html coverage" + }, + "config": { + "optimize-autoloader": true, + "preferred-install": "dist", + "sort-packages": true + }, + "minimum-stability": "stable" +} diff --git a/tests/framework/phpunit.xml b/tests/framework/phpunit.xml new file mode 100644 index 0000000..706b87e --- /dev/null +++ b/tests/framework/phpunit.xml @@ -0,0 +1,26 @@ + + + + + ./Unit + + + + + ../../app + + + ../../app/templates + ../../app/includes + + + + + + + +