tkr/tests/Controller/TickController/TickControllerTest.php
Greg Sarjeant 2e82f946ae Add TickController tests and logs (#50)
Closes https://gitea.subcultureofone.org/greg/tkr/issues/45

Reviewed-on: https://gitea.subcultureofone.org/greg/tkr/pulls/50
Co-authored-by: Greg Sarjeant <greg@subcultureofone.org>
Co-committed-by: Greg Sarjeant <greg@subcultureofone.org>
2025-08-02 20:57:01 +00:00

273 lines
9.7 KiB
PHP

<?php
use PHPUnit\Framework\TestCase;
class TickControllerTest extends TestCase
{
private $mockPdo;
private $config;
private $user;
private string $tempLogDir;
private string $testLogFile;
protected function setUp(): void
{
// Set up log capture
$this->tempLogDir = sys_get_temp_dir() . '/tkr_test_logs_' . uniqid();
mkdir($this->tempLogDir, 0777, true);
$this->testLogFile = $this->tempLogDir . '/tkr.log';
Log::init($this->testLogFile);
Log::setRouteContext('GET tick/123');
// Set up mocks
$this->mockPdo = $this->createMock(PDO::class);
$this->config = new ConfigModel($this->mockPdo);
$this->config->baseUrl = 'https://example.com';
$this->config->basePath = '/tkr/';
$this->config->itemsPerPage = 10;
$this->config->logLevel = 1; // DEBUG level for testing
$this->user = new UserModel($this->mockPdo);
// Set up global $app for simplified dependency access
global $app;
$app = [
'db' => $this->mockPdo,
'config' => $this->config,
'user' => $this->user,
];
}
protected function tearDown(): void
{
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);
}
public function testIndexWithValidTick(): void
{
// Set up mock database response for successful tick retrieval
$expectedTickData = [
'tickTime' => '2025-01-31 12:00:00',
'tick' => 'This is a test tick with some content'
];
$mockStatement = $this->createMock(PDOStatement::class);
$mockStatement->expects($this->once())
->method('execute')
->with([123]);
$mockStatement->expects($this->once())
->method('fetch')
->with(PDO::FETCH_ASSOC)
->willReturn([
'timestamp' => '2025-01-31 12:00:00',
'tick' => 'This is a test tick with some content'
]);
$this->mockPdo->expects($this->once())
->method('prepare')
->with('SELECT timestamp, tick FROM tick WHERE id=?')
->willReturn($mockStatement);
// Capture output since render() outputs directly
ob_start();
$controller = new TickController();
$controller->index(123);
$output = ob_get_clean();
// Should not be a 404 or 500 error
$this->assertStringNotContainsString('404', $output);
$this->assertStringNotContainsString('500', $output);
// Should contain the tick content (through the template)
// Note: We can't easily test the full template rendering without more setup,
// but we can verify no error occurred
// Verify logging
$logContent = file_get_contents($this->testLogFile);
$this->assertStringContainsString('Fetching tick with ID: 123', $logContent);
$this->assertStringContainsString('Successfully loaded tick 123: This is a test tick with some content', $logContent);
}
public function testIndexWithNonexistentTick(): void
{
// Mock database returns null/empty for non-existent tick
$mockStatement = $this->createMock(PDOStatement::class);
$mockStatement->expects($this->once())
->method('execute')
->with([999]);
$mockStatement->expects($this->once())
->method('fetch')
->with(PDO::FETCH_ASSOC)
->willReturn(false); // No row found
$this->mockPdo->expects($this->once())
->method('prepare')
->with('SELECT timestamp, tick FROM tick WHERE id=?')
->willReturn($mockStatement);
// Capture output
ob_start();
$controller = new TickController();
$controller->index(999);
$output = ob_get_clean();
// Should return 404 error
$this->assertStringContainsString('404 - Tick Not Found', $output);
// Verify logging
$logContent = file_get_contents($this->testLogFile);
$this->assertStringContainsString('Fetching tick with ID: 999', $logContent);
$this->assertStringContainsString('Tick not found for ID: 999', $logContent);
}
public function testIndexWithEmptyTickData(): void
{
// Mock database returns empty array (edge case)
$mockStatement = $this->createMock(PDOStatement::class);
$mockStatement->expects($this->once())
->method('execute')
->with([456]);
$mockStatement->expects($this->once())
->method('fetch')
->with(PDO::FETCH_ASSOC)
->willReturn([]); // Empty array
$this->mockPdo->expects($this->once())
->method('prepare')
->with('SELECT timestamp, tick FROM tick WHERE id=?')
->willReturn($mockStatement);
// Capture output
ob_start();
$controller = new TickController();
$controller->index(456);
$output = ob_get_clean();
// Should return 404 error for empty data
$this->assertStringContainsString('404 - Tick Not Found', $output);
// Verify logging
$logContent = file_get_contents($this->testLogFile);
$this->assertStringContainsString('Tick not found for ID: 456', $logContent);
}
public function testIndexWithDatabaseException(): void
{
// Mock database throws exception
$this->mockPdo->expects($this->once())
->method('prepare')
->with('SELECT timestamp, tick FROM tick WHERE id=?')
->willThrowException(new PDOException('Database connection failed'));
// Capture output
ob_start();
$controller = new TickController();
$controller->index(123);
$output = ob_get_clean();
// Should return 500 error
$this->assertStringContainsString('500 - Internal Server Error', $output);
// Verify error logging
$logContent = file_get_contents($this->testLogFile);
$this->assertStringContainsString('Failed to load tick 123: Database connection failed', $logContent);
}
public function testIndexWithLongTickContent(): void
{
// Test logging truncation for long tick content
$longContent = str_repeat('This is a very long tick content that should be truncated in the logs. ', 10);
$mockStatement = $this->createMock(PDOStatement::class);
$mockStatement->expects($this->once())
->method('execute')
->with([789]);
$mockStatement->expects($this->once())
->method('fetch')
->with(PDO::FETCH_ASSOC)
->willReturn([
'timestamp' => '2025-01-31 15:30:00',
'tick' => $longContent
]);
$this->mockPdo->expects($this->once())
->method('prepare')
->with('SELECT timestamp, tick FROM tick WHERE id=?')
->willReturn($mockStatement);
// Capture output
ob_start();
$controller = new TickController();
$controller->index(789);
$output = ob_get_clean();
// Verify logging shows truncated content with ellipsis
$logContent = file_get_contents($this->testLogFile);
$this->assertStringContainsString('Successfully loaded tick 789:', $logContent);
$this->assertStringContainsString('...', $logContent); // Should be truncated
// Verify the log doesn't contain the full long content
$this->assertStringNotContainsString($longContent, $logContent);
}
public function testIndexWithShortTickContent(): void
{
// Test that short content is not truncated in logs
$shortContent = 'Short tick';
$mockStatement = $this->createMock(PDOStatement::class);
$mockStatement->expects($this->once())
->method('execute')
->with([100]);
$mockStatement->expects($this->once())
->method('fetch')
->with(PDO::FETCH_ASSOC)
->willReturn([
'timestamp' => '2025-01-31 09:15:00',
'tick' => $shortContent
]);
$this->mockPdo->expects($this->once())
->method('prepare')
->with('SELECT timestamp, tick FROM tick WHERE id=?')
->willReturn($mockStatement);
// Capture output
ob_start();
$controller = new TickController();
$controller->index(100);
$output = ob_get_clean();
// Verify logging shows full content without ellipsis
$logContent = file_get_contents($this->testLogFile);
$this->assertStringContainsString('Successfully loaded tick 100: Short tick', $logContent);
$this->assertStringNotContainsString('...', $logContent); // Should NOT be truncated
}
}