Adds initial unit tests

main
Yasen Pramatarov 2025-02-18 16:36:31 +02:00
parent 2da13af04c
commit 6fc3629014
19 changed files with 2005 additions and 0 deletions

40
tests/README.md 100644
View File

@ -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/`.

4
tests/framework/.gitignore vendored 100644
View File

@ -0,0 +1,4 @@
/vendor/
/coverage/
.phpunit.result.cache
composer.lock

View File

@ -0,0 +1,68 @@
<?php
require_once __DIR__ . '/vendor/autoload.php';
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();
}
}
protected function tearDown(): void
{
// Clean up session after each test
if (session_status() === PHP_SESSION_ACTIVE) {
$_SESSION = [];
session_destroy();
}
parent::tearDown();
}
/**
* Helper method to start a new session if needed
*/
protected function ensureSession(): void
{
if (session_status() === PHP_SESSION_NONE && !headers_sent()) {
session_start();
}
}
protected function captureHeaders(): array
{
$headers = [];
$callback = function($header) use (&$headers) {
$headers[] = $header;
};
// Mock header function
if (!function_exists('header')) {
eval('function header($header) use ($callback) { $callback($header); }');
}
return $headers;
}
}

View File

@ -0,0 +1,233 @@
<?php
require_once dirname(__DIR__, 4) . '/app/classes/database.php';
require_once dirname(__DIR__, 4) . '/app/classes/agent.php';
use PHPUnit\Framework\TestCase;
class AgentTest extends TestCase
{
private $db;
private $agent;
protected function setUp(): void
{
parent::setUp();
// Set development environment for detailed errors
global $config;
$config['environment'] = 'development';
// Set up test database
$this->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']);
}
}

View File

@ -0,0 +1,131 @@
<?php
require_once dirname(__DIR__, 4) . '/app/classes/database.php';
use PHPUnit\Framework\TestCase;
class DatabaseTest extends TestCase
{
private $config;
protected function setUp(): void
{
parent::setUp();
// Set development environment for detailed errors
global $config;
$config['environment'] = 'development';
$this->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']);
}
}

View File

@ -0,0 +1,112 @@
<?php
require_once dirname(__DIR__, 4) . '/app/classes/feedback.php';
use PHPUnit\Framework\TestCase;
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'] = [];
}
protected function tearDown(): void
{
// Clean up session
if (session_status() === PHP_SESSION_ACTIVE) {
session_destroy();
}
parent::tearDown();
}
public function testGetFlash()
{
// 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']);
$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']);
}
}

View File

@ -0,0 +1,183 @@
<?php
require_once dirname(__DIR__, 4) . '/app/classes/host.php';
use PHPUnit\Framework\TestCase;
class HostTest extends TestCase
{
private $db;
private $host;
protected function setUp(): void
{
parent::setUp();
// Set development environment for detailed errors
global $config;
$config['environment'] = 'development';
// Set up test database
$this->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);
}
}

View File

@ -0,0 +1,89 @@
<?php
require_once dirname(__DIR__, 4) . '/app/classes/database.php';
require_once dirname(__DIR__, 4) . '/app/classes/server.php';
use PHPUnit\Framework\TestCase;
class JiloServerTest extends TestCase
{
private $db;
private $server;
protected function setUp(): void
{
parent::setUp();
// Set up test database
$this->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());
}
}

View File

@ -0,0 +1,138 @@
<?php
require_once dirname(__DIR__, 4) . '/app/classes/database.php';
require_once dirname(__DIR__, 4) . '/app/classes/log.php';
use PHPUnit\Framework\TestCase;
class LogTest extends TestCase
{
private $db;
private $log;
protected function setUp(): void
{
parent::setUp();
// Set up test database
$this->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']);
}
}

View File

@ -0,0 +1,222 @@
<?php
require_once dirname(__DIR__, 4) . '/app/classes/database.php';
require_once dirname(__DIR__, 4) . '/app/classes/platform.php';
use PHPUnit\Framework\TestCase;
class PlatformTest extends TestCase
{
private $db;
private $platform;
protected function setUp(): void
{
parent::setUp();
// Set up test database
$this->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);
}
}

View File

@ -0,0 +1,110 @@
<?php
require_once dirname(__DIR__, 4) . '/app/classes/database.php';
require_once dirname(__DIR__, 4) . '/app/classes/ratelimiter.php';
require_once dirname(__DIR__, 4) . '/app/classes/log.php';
use PHPUnit\Framework\TestCase;
class RateLimiterTest extends TestCase
{
private $rateLimiter;
private $db;
protected function setUp(): void
{
parent::setUp();
// Set up in-memory SQLite database
$this->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));
}
}

View File

@ -0,0 +1,86 @@
<?php
require_once dirname(__DIR__, 4) . '/app/classes/router.php';
use PHPUnit\Framework\TestCase;
class RouterTest extends TestCase
{
private $router;
protected function setUp(): void
{
parent::setUp();
$this->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);
}
}

View File

@ -0,0 +1,29 @@
<?php
require_once dirname(__DIR__, 4) . '/app/classes/database.php';
require_once dirname(__DIR__, 4) . '/app/classes/settings.php';
use PHPUnit\Framework\TestCase;
class SettingsTest extends TestCase
{
private $settings;
protected function setUp(): void
{
parent::setUp();
$this->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);
}
}

View File

@ -0,0 +1,164 @@
<?php
require_once dirname(__DIR__, 4) . '/app/classes/database.php';
require_once dirname(__DIR__, 4) . '/app/classes/user.php';
require_once dirname(__DIR__, 4) . '/app/classes/ratelimiter.php';
use PHPUnit\Framework\TestCase;
class UserTest extends TestCase
{
private $db;
private $user;
protected function setUp(): void
{
parent::setUp();
// Set up test database
$this->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);
}
}

View File

@ -0,0 +1,143 @@
<?php
require_once dirname(__DIR__, 4) . '/app/classes/validator.php';
use PHPUnit\Framework\TestCase;
class ValidatorTest extends TestCase
{
public function testRequired()
{
// Test valid data
$data = ['name' => '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]);
}
}

View File

@ -0,0 +1,113 @@
<?php
require_once dirname(__DIR__, 4) . '/app/helpers/security.php';
use PHPUnit\Framework\TestCase;
class SecurityHelperTest extends TestCase
{
private SecurityHelper $security;
protected function setUp(): void
{
parent::setUp();
$this->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 = '<script>alert("xss")</script>';
$expected = 'alert(&quot;xss&quot;)';
$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' => '<b>John</b>',
'email' => 'john@example.com',
'nested' => [
'key' => '<i>value</i>'
]
];
$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);
}
}

View File

@ -0,0 +1,64 @@
<?php
// 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();
//}
// Load Composer's autoloader
require_once __DIR__ . '/vendor/autoload.php';
// Set error reporting
error_reporting(E_ALL);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
// Set timezone
date_default_timezone_set('UTC');
// Define global variables needed by the application
$GLOBALS['app_root'] = '/';
$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) {
global $dbWeb;
return [
'db' => $dbWeb
];
}
}

View File

@ -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"
}

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="bootstrap.php"
colors="true"
testdox="true">
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./Unit</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">../../app</directory>
</include>
<exclude>
<directory>../../app/templates</directory>
<directory>../../app/includes</directory>
</exclude>
</coverage>
<php>
<env name="APP_ENV" value="testing"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
</php>
</phpunit>