164 lines
5.9 KiB
PHP
164 lines
5.9 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace MintyPHP\Tests\Http;
|
||
|
|
|
||
|
|
use MintyPHP\Http\ErrorDataCollector;
|
||
|
|
use MintyPHP\Http\RequestContext;
|
||
|
|
use PHPUnit\Framework\TestCase;
|
||
|
|
|
||
|
|
class ErrorDataCollectorTest extends TestCase
|
||
|
|
{
|
||
|
|
private array $serverBackup = [];
|
||
|
|
|
||
|
|
protected function setUp(): void
|
||
|
|
{
|
||
|
|
$this->serverBackup = $_SERVER;
|
||
|
|
RequestContext::resetForTests();
|
||
|
|
}
|
||
|
|
|
||
|
|
protected function tearDown(): void
|
||
|
|
{
|
||
|
|
$_SERVER = $this->serverBackup;
|
||
|
|
RequestContext::resetForTests();
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testCollectReturnsAllFourSections(): void
|
||
|
|
{
|
||
|
|
$exception = new \RuntimeException('Test exception', 99);
|
||
|
|
$result = ErrorDataCollector::collect($exception);
|
||
|
|
|
||
|
|
$this->assertArrayHasKey('exception', $result);
|
||
|
|
$this->assertArrayHasKey('request', $result);
|
||
|
|
$this->assertArrayHasKey('environment', $result);
|
||
|
|
$this->assertArrayHasKey('queries', $result);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testExceptionSectionContainsCorrectDetails(): void
|
||
|
|
{
|
||
|
|
$exception = new \LogicException('Something wrong', 7);
|
||
|
|
$result = ErrorDataCollector::collect($exception);
|
||
|
|
|
||
|
|
$exc = $result['exception'];
|
||
|
|
$this->assertSame('LogicException', $exc['class']);
|
||
|
|
$this->assertSame('Something wrong', $exc['message']);
|
||
|
|
$this->assertSame(7, $exc['code']);
|
||
|
|
$this->assertStringContainsString('ErrorDataCollectorTest.php', $exc['file']);
|
||
|
|
$this->assertIsInt($exc['line']);
|
||
|
|
$this->assertIsArray($exc['trace']);
|
||
|
|
$this->assertNotEmpty($exc['trace']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testChainedExceptionsAreCollected(): void
|
||
|
|
{
|
||
|
|
$inner = new \InvalidArgumentException('inner cause');
|
||
|
|
$outer = new \RuntimeException('outer', 0, $inner);
|
||
|
|
|
||
|
|
$result = ErrorDataCollector::collect($outer);
|
||
|
|
|
||
|
|
$this->assertArrayHasKey('previous', $result['exception']);
|
||
|
|
$this->assertSame('InvalidArgumentException', $result['exception']['previous']['class']);
|
||
|
|
$this->assertSame('inner cause', $result['exception']['previous']['message']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testCodeSnippetIsExtractedForKnownFile(): void
|
||
|
|
{
|
||
|
|
$exception = new \RuntimeException('snippet test');
|
||
|
|
$result = ErrorDataCollector::collect($exception);
|
||
|
|
|
||
|
|
$firstFrame = $result['exception']['trace'][0] ?? null;
|
||
|
|
$this->assertNotNull($firstFrame);
|
||
|
|
$this->assertArrayHasKey('code_snippet', $firstFrame);
|
||
|
|
// The snippet should contain lines around the throw site.
|
||
|
|
$this->assertNotEmpty($firstFrame['code_snippet']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testRequestSectionContainsBasicData(): void
|
||
|
|
{
|
||
|
|
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||
|
|
$_SERVER['REQUEST_URI'] = '/admin/users/edit?id=5';
|
||
|
|
$_SERVER['REMOTE_ADDR'] = '10.0.0.1';
|
||
|
|
$_SERVER['HTTP_USER_AGENT'] = 'TestAgent/1.0';
|
||
|
|
|
||
|
|
$exception = new \RuntimeException('request test');
|
||
|
|
$result = ErrorDataCollector::collect($exception);
|
||
|
|
|
||
|
|
$req = $result['request'];
|
||
|
|
$this->assertSame('POST', $req['method']);
|
||
|
|
$this->assertSame('/admin/users/edit?id=5', $req['url']);
|
||
|
|
$this->assertSame('10.0.0.1', $req['ip']);
|
||
|
|
$this->assertSame('TestAgent/1.0', $req['user_agent']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testHeadersAreRedacted(): void
|
||
|
|
{
|
||
|
|
$_SERVER['HTTP_AUTHORIZATION'] = 'Bearer secret-token';
|
||
|
|
$_SERVER['HTTP_COOKIE'] = 'session=abc123';
|
||
|
|
$_SERVER['HTTP_X_API_KEY'] = 'my-key';
|
||
|
|
$_SERVER['HTTP_ACCEPT'] = 'text/html';
|
||
|
|
|
||
|
|
$exception = new \RuntimeException('header redaction');
|
||
|
|
$result = ErrorDataCollector::collect($exception);
|
||
|
|
|
||
|
|
$headers = $result['request']['headers'];
|
||
|
|
$this->assertSame('[REDACTED]', $headers['authorization']);
|
||
|
|
$this->assertSame('[REDACTED]', $headers['cookie']);
|
||
|
|
$this->assertSame('[REDACTED]', $headers['x-api-key']);
|
||
|
|
$this->assertSame('text/html', $headers['accept']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testPostParamsAreRedacted(): void
|
||
|
|
{
|
||
|
|
$_POST = [
|
||
|
|
'username' => 'admin',
|
||
|
|
'password' => 'secret123',
|
||
|
|
'csrf_token' => 'abc',
|
||
|
|
'email' => 'test@example.com',
|
||
|
|
];
|
||
|
|
|
||
|
|
$exception = new \RuntimeException('post redaction');
|
||
|
|
$result = ErrorDataCollector::collect($exception);
|
||
|
|
|
||
|
|
$post = $result['request']['post'];
|
||
|
|
$this->assertSame('admin', $post['username']);
|
||
|
|
$this->assertSame('[REDACTED]', $post['password']);
|
||
|
|
$this->assertSame('[REDACTED]', $post['csrf_token']);
|
||
|
|
$this->assertSame('test@example.com', $post['email']);
|
||
|
|
|
||
|
|
$_POST = [];
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testEnvironmentSectionContainsPhpInfo(): void
|
||
|
|
{
|
||
|
|
$exception = new \RuntimeException('env test');
|
||
|
|
$result = ErrorDataCollector::collect($exception);
|
||
|
|
|
||
|
|
$env = $result['environment'];
|
||
|
|
$this->assertSame(PHP_VERSION, $env['php_version']);
|
||
|
|
$this->assertSame(PHP_SAPI, $env['php_sapi']);
|
||
|
|
$this->assertIsArray($env['extensions']);
|
||
|
|
$this->assertNotEmpty($env['extensions']);
|
||
|
|
$this->assertIsInt($env['memory_current']);
|
||
|
|
$this->assertIsInt($env['memory_peak']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testArgsAreSummarizedByTypeOnly(): void
|
||
|
|
{
|
||
|
|
// Trace frames should contain type summaries, not actual argument values.
|
||
|
|
$exception = new \RuntimeException('args test');
|
||
|
|
$result = ErrorDataCollector::collect($exception);
|
||
|
|
|
||
|
|
foreach ($result['exception']['trace'] as $frame) {
|
||
|
|
if (isset($frame['args_summary'])) {
|
||
|
|
foreach ($frame['args_summary'] as $summary) {
|
||
|
|
$this->assertIsString($summary);
|
||
|
|
// Should be type descriptions, not raw values.
|
||
|
|
$this->assertMatchesRegularExpression(
|
||
|
|
'/^(null|true|false|int|float|string\(\d+\)|array\(\d+\)|resource|unknown|[A-Z][\w\\\\]*)$/',
|
||
|
|
$summary,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|