Some checks are pending
Run unit tests / run-unit-tests (push) Waiting to run
Add logging and tests for the homepage and settings page. Make both support dependency injection. Reviewed-on: https://gitea.subcultureofone.org/greg/tkr/pulls/42 Co-authored-by: Greg Sarjeant <greg@subcultureofone.org> Co-committed-by: Greg Sarjeant <greg@subcultureofone.org>
312 lines
12 KiB
PHP
312 lines
12 KiB
PHP
<?php
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
class HomeControllerTest extends TestCase
|
|
{
|
|
private PDO $mockPdo;
|
|
private PDOStatement $mockStatement;
|
|
private ConfigModel $mockConfig;
|
|
private UserModel $mockUser;
|
|
private string $tempLogDir;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
// Set up temporary logging
|
|
$this->tempLogDir = sys_get_temp_dir() . '/tkr_test_logs_' . uniqid();
|
|
mkdir($this->tempLogDir . '/logs', 0777, true);
|
|
Log::init($this->tempLogDir . '/logs/tkr.log');
|
|
|
|
// Set up global config for logging level (DEBUG = 1)
|
|
global $config;
|
|
$config = new stdClass();
|
|
$config->logLevel = 1; // Allow DEBUG level logs
|
|
|
|
// Create mock PDO and PDOStatement
|
|
$this->mockStatement = $this->createMock(PDOStatement::class);
|
|
$this->mockPdo = $this->createMock(PDO::class);
|
|
|
|
// Mock config
|
|
$this->mockConfig = new ConfigModel($this->mockPdo);
|
|
$this->mockConfig->itemsPerPage = 10;
|
|
$this->mockConfig->basePath = '/tkr';
|
|
|
|
// Mock user
|
|
$this->mockUser = new UserModel($this->mockPdo);
|
|
$this->mockUser->displayName = 'Test User';
|
|
$this->mockUser->mood = '😊';
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
// Clean up temp directory
|
|
if (is_dir($this->tempLogDir)) {
|
|
$this->deleteDirectory($this->tempLogDir);
|
|
}
|
|
}
|
|
|
|
private function deleteDirectory(string $dir): void
|
|
{
|
|
if (!is_dir($dir)) return;
|
|
|
|
$files = array_diff(scandir($dir), ['.', '..']);
|
|
foreach ($files as $file) {
|
|
$path = $dir . '/' . $file;
|
|
is_dir($path) ? $this->deleteDirectory($path) : unlink($path);
|
|
}
|
|
rmdir($dir);
|
|
}
|
|
|
|
private function setupMockDatabase(array $tickData): void
|
|
{
|
|
// Mock PDO prepare method to return our mock statement
|
|
$this->mockPdo->method('prepare')
|
|
->willReturn($this->mockStatement);
|
|
|
|
// Mock statement execute method
|
|
$this->mockStatement->method('execute')
|
|
->willReturn(true);
|
|
|
|
// Mock statement fetchAll to return our test data
|
|
$this->mockStatement->method('fetchAll')
|
|
->willReturn($tickData);
|
|
}
|
|
|
|
private function setupMockDatabaseForInsert(bool $shouldSucceed = true): void
|
|
{
|
|
if ($shouldSucceed) {
|
|
// Mock successful insert
|
|
$this->mockPdo->method('prepare')
|
|
->willReturn($this->mockStatement);
|
|
|
|
$this->mockStatement->method('execute')
|
|
->willReturn(true);
|
|
} else {
|
|
// Mock database error
|
|
$this->mockPdo->method('prepare')
|
|
->willThrowException(new PDOException("Database error"));
|
|
}
|
|
}
|
|
|
|
public function testGetHomeDataWithNoTicks(): void
|
|
{
|
|
$this->setupMockDatabase([]); // Empty array = no ticks
|
|
|
|
$controller = new HomeController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
|
$data = $controller->getHomeData(1);
|
|
|
|
// Should return proper structure
|
|
$this->assertArrayHasKey('config', $data);
|
|
$this->assertArrayHasKey('user', $data);
|
|
$this->assertArrayHasKey('tickList', $data);
|
|
|
|
// Config and user should be the injected instances
|
|
$this->assertSame($this->mockConfig, $data['config']);
|
|
$this->assertSame($this->mockUser, $data['user']);
|
|
|
|
// Should have tick list HTML (even if empty)
|
|
$this->assertIsString($data['tickList']);
|
|
}
|
|
|
|
public function testGetHomeDataWithTicks(): void
|
|
{
|
|
// Set up test tick data that the database would return
|
|
$testTicks = [
|
|
['id' => 1, 'timestamp' => '2025-01-31 12:00:00', 'tick' => 'First tick'],
|
|
['id' => 2, 'timestamp' => '2025-01-31 13:00:00', 'tick' => 'Second tick'],
|
|
['id' => 3, 'timestamp' => '2025-01-31 14:00:00', 'tick' => 'Third tick'],
|
|
];
|
|
|
|
$this->setupMockDatabase($testTicks);
|
|
|
|
$controller = new HomeController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
|
$data = $controller->getHomeData(1);
|
|
|
|
// Should return proper structure
|
|
$this->assertArrayHasKey('config', $data);
|
|
$this->assertArrayHasKey('user', $data);
|
|
$this->assertArrayHasKey('tickList', $data);
|
|
|
|
// Should contain tick content in HTML
|
|
$this->assertStringContainsString('First tick', $data['tickList']);
|
|
$this->assertStringContainsString('Second tick', $data['tickList']);
|
|
$this->assertStringContainsString('Third tick', $data['tickList']);
|
|
}
|
|
|
|
public function testGetHomeDataCallsDatabaseCorrectly(): void
|
|
{
|
|
$this->setupMockDatabase([]);
|
|
|
|
// Verify that PDO prepare is called with the correct SQL
|
|
$this->mockPdo->expects($this->once())
|
|
->method('prepare')
|
|
->with('SELECT id, timestamp, tick FROM tick ORDER BY timestamp DESC LIMIT ? OFFSET ?')
|
|
->willReturn($this->mockStatement);
|
|
|
|
// Verify that execute is called with correct parameters for page 2
|
|
$this->mockStatement->expects($this->once())
|
|
->method('execute')
|
|
->with([10, 10]); // itemsPerPage=10, page 2 = offset 10
|
|
|
|
$controller = new HomeController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
|
$controller->getHomeData(2); // Page 2
|
|
}
|
|
|
|
public function testProcessTickSuccess(): void
|
|
{
|
|
$this->setupMockDatabaseForInsert(true);
|
|
|
|
// Verify the INSERT SQL is called correctly
|
|
$this->mockPdo->expects($this->once())
|
|
->method('prepare')
|
|
->with('INSERT INTO tick(timestamp, tick) values (?, ?)')
|
|
->willReturn($this->mockStatement);
|
|
|
|
// Verify execute is called with timestamp and content
|
|
$this->mockStatement->expects($this->once())
|
|
->method('execute')
|
|
->with($this->callback(function($params) {
|
|
// First param should be a timestamp, second should be the tick content
|
|
return count($params) === 2
|
|
&& is_string($params[0])
|
|
&& $params[1] === 'This is a test tick';
|
|
}));
|
|
|
|
$controller = new HomeController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
|
$postData = ['new_tick' => 'This is a test tick'];
|
|
|
|
$result = $controller->processTick($postData);
|
|
|
|
$this->assertTrue($result['success']);
|
|
$this->assertEquals('Tick saved successfully', $result['message']);
|
|
}
|
|
|
|
public function testProcessTickEmptyContent(): void
|
|
{
|
|
// PDO shouldn't be called at all for empty content
|
|
$this->mockPdo->expects($this->never())->method('prepare');
|
|
|
|
$controller = new HomeController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
|
$postData = ['new_tick' => ' ']; // Just whitespace
|
|
|
|
$result = $controller->processTick($postData);
|
|
|
|
$this->assertFalse($result['success']);
|
|
$this->assertEquals('Empty tick ignored', $result['message']);
|
|
}
|
|
|
|
public function testProcessTickMissingField(): void
|
|
{
|
|
// PDO shouldn't be called at all for missing field
|
|
$this->mockPdo->expects($this->never())->method('prepare');
|
|
|
|
$controller = new HomeController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
|
$postData = []; // No new_tick field
|
|
|
|
$result = $controller->processTick($postData);
|
|
|
|
$this->assertFalse($result['success']);
|
|
$this->assertEquals('No tick content provided', $result['message']);
|
|
}
|
|
|
|
public function testProcessTickTrimsWhitespace(): void
|
|
{
|
|
$this->setupMockDatabaseForInsert(true);
|
|
|
|
// Verify execute is called with trimmed content
|
|
$this->mockStatement->expects($this->once())
|
|
->method('execute')
|
|
->with($this->callback(function($params) {
|
|
return $params[1] === 'This has whitespace'; // Should be trimmed
|
|
}));
|
|
|
|
$controller = new HomeController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
|
$postData = ['new_tick' => ' This has whitespace '];
|
|
|
|
$result = $controller->processTick($postData);
|
|
|
|
$this->assertTrue($result['success']);
|
|
}
|
|
|
|
public function testProcessTickHandlesDatabaseError(): void
|
|
{
|
|
$this->setupMockDatabaseForInsert(false); // Will throw exception
|
|
|
|
$controller = new HomeController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
|
$postData = ['new_tick' => 'This will fail'];
|
|
|
|
$result = $controller->processTick($postData);
|
|
|
|
$this->assertFalse($result['success']);
|
|
$this->assertEquals('Failed to save tick', $result['message']);
|
|
}
|
|
|
|
public function testLoggingOnHomePageLoad(): void
|
|
{
|
|
$testTicks = [
|
|
['id' => 1, 'timestamp' => '2025-01-31 12:00:00', 'tick' => 'Test tick']
|
|
];
|
|
$this->setupMockDatabase($testTicks);
|
|
|
|
$controller = new HomeController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
|
$controller->getHomeData(1);
|
|
|
|
// Check that logs were written
|
|
$logFile = $this->tempLogDir . '/logs/tkr.log';
|
|
$this->assertFileExists($logFile);
|
|
|
|
$logContent = file_get_contents($logFile);
|
|
$this->assertStringContainsString('Loading home page 1', $logContent);
|
|
$this->assertStringContainsString('Home page loaded with 1 ticks', $logContent);
|
|
}
|
|
|
|
public function testLoggingOnTickCreation(): void
|
|
{
|
|
$this->setupMockDatabaseForInsert(true);
|
|
|
|
$controller = new HomeController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
|
$postData = ['new_tick' => 'Test tick for logging'];
|
|
|
|
$controller->processTick($postData);
|
|
|
|
// Check that logs were written
|
|
$logFile = $this->tempLogDir . '/logs/tkr.log';
|
|
$this->assertFileExists($logFile);
|
|
|
|
$logContent = file_get_contents($logFile);
|
|
$this->assertStringContainsString('New tick created: Test tick for logging', $logContent);
|
|
}
|
|
|
|
public function testLoggingOnEmptyTick(): void
|
|
{
|
|
$controller = new HomeController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
|
$postData = ['new_tick' => ''];
|
|
|
|
$controller->processTick($postData);
|
|
|
|
// Check that logs were written
|
|
$logFile = $this->tempLogDir . '/logs/tkr.log';
|
|
|
|
// The log file should exist (Log::init creates it) and contain the debug message
|
|
$this->assertFileExists($logFile);
|
|
|
|
$logContent = file_get_contents($logFile);
|
|
$this->assertStringContainsString('Empty tick submission ignored', $logContent);
|
|
}
|
|
|
|
public function testLoggingOnDatabaseError(): void
|
|
{
|
|
$this->setupMockDatabaseForInsert(false);
|
|
|
|
$controller = new HomeController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
|
$postData = ['new_tick' => 'This will fail'];
|
|
|
|
$controller->processTick($postData);
|
|
|
|
// Check that logs were written
|
|
$logFile = $this->tempLogDir . '/logs/tkr.log';
|
|
$this->assertFileExists($logFile);
|
|
|
|
$logContent = file_get_contents($logFile);
|
|
$this->assertStringContainsString('Failed to save tick: Database error', $logContent);
|
|
}
|
|
} |