First init

This commit is contained in:
2025-09-18 12:26:04 +02:00
commit 071d0f59a2
10 changed files with 625 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
<?php
namespace HerrleinIT\LogHandler\Tests\Fakes;
use BadMethodCallException;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7\Response;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Throwable;
class FakeLoghandlerClient implements ClientInterface
{
/** @var array<int, array{method:string,uri:string,options:array}> */
public array $requests = [];
/**
* @param array<int, ResponseInterface|Throwable|callable> $queue
*/
public function __construct(private array $queue = []) {}
public function request(string $method, $uri, array $options = []): ResponseInterface
{
$this->requests[] = [
'method' => strtoupper($method),
'uri' => (string) $uri,
'options' => $options,
];
if ($this->queue !== []) {
$next = array_shift($this->queue);
if ($next instanceof Throwable) {
throw $next;
}
if (is_callable($next)) {
$result = $next($this, $method, $uri, $options);
if ($result instanceof ResponseInterface) {
return $result;
}
}
if ($next instanceof ResponseInterface) {
return $next;
}
}
return new Response(200);
}
public function requestAsync($method, $uri, array $options = []): PromiseInterface
{
return new FulfilledPromise($this->request($method, $uri, $options));
}
public function send(RequestInterface $request, array $options = []): ResponseInterface
{
return $this->request($request->getMethod(), $request->getUri(), $options + ['request' => $request]);
}
public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface
{
return $this->requestAsync($request->getMethod(), $request->getUri(), $options + ['request' => $request]);
}
public function getConfig(?string $option = null): mixed
{
if ($option !== null) {
throw new BadMethodCallException('Fake client does not expose configurable options.');
}
return [];
}
public function append(ResponseInterface|Throwable|callable $next): void
{
$this->queue[] = $next;
}
}

View File

@@ -0,0 +1,104 @@
<?php
namespace HerrleinIT\LogHandler\Tests\Feature;
use ErrorException;
use HerrleinIT\LogHandler\Handlers\LogForwardingHandler;
use HerrleinIT\LogHandler\LogForwarder;
use HerrleinIT\LogHandler\Tests\Fakes\FakeLoghandlerClient;
use HerrleinIT\LogHandler\Tests\TestCase;
use Illuminate\Support\Facades\Log;
use Illuminate\Testing\Fluent\AssertableJson;
class LogForwardingTest extends TestCase
{
public function test_exception_payload_is_forwarded(): void
{
$client = new FakeLoghandlerClient();
$this->app->instance(LogForwarder::class, new LogForwarder($this->app['config'], $client));
$exception = new ErrorException('Testerror', 0, E_WARNING, '/var/test/testfile.php', 1337);
$channel = Log::channel('loghandler');
$this->assertContains(
LogForwardingHandler::class,
array_map(fn ($handler) => $handler::class, $channel->getLogger()->getHandlers())
);
$channel->warning('Ignored message', ['exception' => $exception]);
$this->assertCount(1, $client->requests);
$request = $client->requests[0];
$this->assertSame('POST', $request['method']);
$this->assertSame('http://test.example/api/error/create', $request['uri']);
$this->assertSame('Bearer test-token', $request['options']['headers']['Authorization']);
AssertableJson::fromArray($request['options']['json'])
->where('source', config('loghandler.source'))
->where('error', 'Testerror')
->where('type', 'E_WARNING')
->where('file', '/var/test/testfile.php')
->where('line', '1337')
->where('trace', fn ($trace) => is_string($trace) && $trace !== '')
->etc();
}
public function test_disabled_handler_skips_forwarding(): void
{
$client = new FakeLoghandlerClient();
config()->set('loghandler.enabled', false);
$this->app->instance(LogForwarder::class, new LogForwarder($this->app['config'], $client));
Log::channel('loghandler')->error('Testerror');
$this->assertCount(0, $client->requests);
}
public function test_missing_token_skips_forwarding(): void
{
$client = new FakeLoghandlerClient();
config()->set('loghandler.token', '');
$this->app->instance(LogForwarder::class, new LogForwarder($this->app['config'], $client));
Log::channel('loghandler')->error('Testerror');
$this->assertCount(0, $client->requests);
}
public function test_stack_channel_includes_handler(): void
{
$client = new FakeLoghandlerClient();
$this->app->instance(LogForwarder::class, new LogForwarder($this->app['config'], $client));
$stack = Log::channel('stack');
$this->assertContains(
LogForwardingHandler::class,
array_map(fn ($handler) => $handler::class, $stack->getHandlers())
);
$stack->error('Stack error', [
'file' => '/var/test/testfile.php',
'line' => 99,
]);
$this->assertCount(1, $client->requests);
$request = $client->requests[0];
$this->assertSame('POST', $request['method']);
$this->assertSame('http://test.example/api/error/create', $request['uri']);
AssertableJson::fromArray($request['options']['json'])
->where('source', config('loghandler.source'))
->where('error', 'Stack error')
->where('type', 'E_ERROR')
->where('file', '/var/test/testfile.php')
->where('line', '99')
->where('trace', '')
->etc();
}
}

53
tests/TestCase.php Normal file
View File

@@ -0,0 +1,53 @@
<?php
namespace HerrleinIT\LogHandler\Tests;
use HerrleinIT\LogHandler\LogHandlerServiceProvider;
use Illuminate\Support\Facades\Log;
use Orchestra\Testbench\TestCase as Orchestra;
abstract class TestCase extends Orchestra
{
protected function getPackageProviders($app): array
{
return [LogHandlerServiceProvider::class];
}
protected function defineEnvironment($app): void
{
$app['config']->set('logging.default', 'stack');
$app['config']->set('logging.channels.loghandler', [
'driver' => 'loghandler',
'level' => 'debug',
'bubble' => true,
]);
$app['config']->set('logging.channels.single', [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'replace_placeholders' => true,
]);
$app['config']->set('logging.channels.stack', [
'driver' => 'stack',
'channels' => ['single', 'loghandler'],
'ignore_exceptions' => false,
]);
$app['config']->set('loghandler', [
'endpoint' => 'http://test.example/api/error/create',
'token' => 'test-token',
'source' => 'Project Name',
'enabled' => true,
'timeout_ms' => 3000,
'retry_times' => 0,
'include_trace' => false,
]);
}
protected function tearDown(): void
{
parent::tearDown();
Log::forgetChannel('loghandler');
Log::forgetChannel('stack');
}
}