Files
alfa-bank-sdk/tests/Feature/LoggingTest.php
2026-06-09 16:11:15 +03:00

295 lines
9.6 KiB
PHP

<?php
use Apoloz\AlfaBank\Core\AlfaBankConnector;
use Apoloz\AlfaBank\Core\AlfaConfig;
use Apoloz\AlfaBank\Http\Middleware\LoggingRequestMiddleware;
use Apoloz\AlfaBank\Http\Middleware\LoggingResponseMiddleware;
use Apoloz\AlfaBank\Requests\Accounts\GetAccountsRequest;
use Apoloz\AlfaBank\Sbp\SbpConnector;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Saloon\Http\Faking\MockClient;
use Saloon\Http\Faking\MockResponse;
function createLoggingConfig(): AlfaConfig
{
$cert = tempnam(sys_get_temp_dir(), 'cert');
$key = tempnam(sys_get_temp_dir(), 'key');
return new AlfaConfig(
baseUrl: 'https://test.alfabank.ru',
certificatePath: $cert,
privateKeyPath: $key
);
}
test('AlfaBankConnector logs request with host by default', function (): void {
/** @var LoggerInterface&MockObject */
$logger = $this->createMock(LoggerInterface::class);
$logger->expects($this->exactly(2))
->method('info')
->with(
$this->logicalOr(
$this->stringContains('AlfaBank Request'),
$this->stringContains('AlfaBank Response')
),
$this->callback(function (array $context): bool {
return isset($context['host']) && $context['host'] === 'https://test.alfabank.ru';
})
);
$mockClient = new MockClient([
'*' => MockResponse::make(['accounts' => []], 200),
]);
$config = createLoggingConfig();
$connector = new AlfaBankConnector($config, loggingEnabled: true, logger: $logger);
$connector->withMockClient($mockClient);
$connector->send(new GetAccountsRequest);
});
test('AlfaBankConnector logs response with host by default', function (): void {
/** @var LoggerInterface&MockObject */
$logger = $this->createMock(LoggerInterface::class);
$calls = [];
$logger->expects($this->exactly(2))
->method('info')
->willReturnCallback(function (string $message, array $context) use (&$calls): void {
$calls[] = $message;
});
$mockClient = new MockClient([
'*' => MockResponse::make(['accounts' => []], 200),
]);
$config = createLoggingConfig();
$connector = new AlfaBankConnector($config, loggingEnabled: true, logger: $logger);
$connector->withMockClient($mockClient);
$connector->send(new GetAccountsRequest);
expect($calls)->toHaveCount(2);
expect($calls[0])->toContain('AlfaBank Request');
expect($calls[1])->toContain('AlfaBank Response');
});
test('AlfaBankConnector does not log when loggingEnabled is false', function (): void {
/** @var LoggerInterface&MockObject */
$logger = $this->createMock(LoggerInterface::class);
$logger->expects($this->never())->method('info');
$mockClient = new MockClient([
'*' => MockResponse::make(['accounts' => []], 200),
]);
$config = createLoggingConfig();
$connector = new AlfaBankConnector($config, loggingEnabled: false, logger: $logger);
$connector->withMockClient($mockClient);
$connector->send(new GetAccountsRequest);
});
test('withoutLogging disables logging', function (): void {
/** @var LoggerInterface&MockObject */
$logger = $this->createMock(LoggerInterface::class);
$logger->expects($this->never())->method('info');
$mockClient = new MockClient([
'*' => MockResponse::make(['accounts' => []], 200),
]);
$config = createLoggingConfig();
$connector = new AlfaBankConnector($config, loggingEnabled: true, logger: $logger);
$connector->withoutLogging();
$connector->withMockClient($mockClient);
$connector->send(new GetAccountsRequest);
});
test('withLogging re-enables logging after withoutLogging', function (): void {
/** @var LoggerInterface&MockObject */
$logger = $this->createMock(LoggerInterface::class);
$logger->expects($this->exactly(2))->method('info');
$mockClient = new MockClient([
'*' => MockResponse::make(['accounts' => []], 200),
]);
$config = createLoggingConfig();
$connector = new AlfaBankConnector($config, loggingEnabled: false, logger: $logger);
$connector->withLogging($logger);
$connector->withMockClient($mockClient);
$connector->send(new GetAccountsRequest);
});
test('SbpConnector logs request with host by default', function (): void {
/** @var LoggerInterface&MockObject */
$logger = $this->createMock(LoggerInterface::class);
$logger->expects($this->exactly(2))
->method('info')
->with(
$this->logicalOr(
$this->stringContains('AlfaBank Request'),
$this->stringContains('AlfaBank Response')
),
$this->callback(function (array $context): bool {
return isset($context['host']) && $context['host'] === 'https://test.alfabank.ru';
})
);
$mockClient = new MockClient([
'*' => MockResponse::make([], 200),
]);
$config = createLoggingConfig();
$connector = new SbpConnector($config, 'alias', loggingEnabled: true, logger: $logger);
$connector->withMockClient($mockClient);
$connector->send(new class extends \Saloon\Http\Request
{
protected \Saloon\Enums\Method $method = \Saloon\Enums\Method::GET;
public function resolveEndpoint(): string
{
return '/test';
}
});
});
test('SbpConnector withoutLogging disables logging', function (): void {
/** @var LoggerInterface&MockObject */
$logger = $this->createMock(LoggerInterface::class);
$logger->expects($this->never())->method('info');
$mockClient = new MockClient([
'*' => MockResponse::make([], 200),
]);
$config = createLoggingConfig();
$connector = new SbpConnector($config, 'alias', loggingEnabled: true, logger: $logger);
$connector->withoutLogging();
$connector->withMockClient($mockClient);
$connector->send(new class extends \Saloon\Http\Request
{
protected \Saloon\Enums\Method $method = \Saloon\Enums\Method::GET;
public function resolveEndpoint(): string
{
return '/test';
}
});
});
test('LoggingRequestMiddleware masks sensitive headers', function (): void {
$middleware = new LoggingRequestMiddleware(
$this->createMock(LoggerInterface::class)
);
$reflection = new ReflectionMethod($middleware, 'maskHeaders');
$result = $reflection->invoke($middleware, [
'Authorization' => 'Bearer secret-token',
'Accept' => 'application/json',
]);
expect($result['Authorization'])->toBe('Bearer *****');
expect($result['Accept'])->toBe('application/json');
});
test('LoggingRequestMiddleware masks sensitive body fields', function (): void {
$middleware = new LoggingRequestMiddleware(
$this->createMock(LoggerInterface::class)
);
$reflection = new ReflectionMethod($middleware, 'maskBody');
$result = $reflection->invoke($middleware, [
'pan' => '4276123456789012',
'cvc' => '123',
'amount' => 1000,
'nested' => [
'client_secret' => 'super-secret',
],
]);
expect($result['pan'])->toBe('*****');
expect($result['cvc'])->toBe('*****');
expect($result['amount'])->toBe(1000);
expect($result['nested']['client_secret'])->toBe('*****');
});
test('AlfaBankConnector without logger uses NullLogger silently', function (): void {
$mockClient = new MockClient([
'*' => MockResponse::make(['accounts' => []], 200),
]);
$config = createLoggingConfig();
$connector = new AlfaBankConnector($config, loggingEnabled: true);
$connector->withMockClient($mockClient);
expect($connector->send(new GetAccountsRequest))->status()->toBe(200);
});
test('logs non-JSON response body without throwing', function (): void {
/** @var LoggerInterface&MockObject */
$logger = $this->createMock(LoggerInterface::class);
$loggedBody = null;
$logger->expects($this->exactly(2))
->method('info')
->willReturnCallback(function (string $message, array $context) use (&$loggedBody): void {
if (str_contains($message, 'Response')) {
$loggedBody = $context['body'];
}
});
$mockClient = new MockClient([
'*' => MockResponse::make('eyJhbGciOiJIUzI1NiJ9...', 200, ['Content-Type' => 'application/jwt']),
]);
$config = createLoggingConfig();
$connector = new AlfaBankConnector($config, loggingEnabled: true, logger: $logger);
$connector->withMockClient($mockClient);
$response = $connector->send(new GetAccountsRequest);
expect($response->status())->toBe(200);
expect($loggedBody)->toBe('eyJhbGciOiJIUzI1NiJ9...');
});
test('logs plain text response without throwing', function (): void {
/** @var LoggerInterface&MockObject */
$logger = $this->createMock(LoggerInterface::class);
$loggedBody = null;
$logger->expects($this->exactly(2))
->method('info')
->willReturnCallback(function (string $message, array $context) use (&$loggedBody): void {
if (str_contains($message, 'Response')) {
$loggedBody = $context['body'];
}
});
$mockClient = new MockClient([
'*' => MockResponse::make('Internal Server Error', 200, ['Content-Type' => 'text/plain']),
]);
$config = createLoggingConfig();
$connector = new AlfaBankConnector($config, loggingEnabled: true, logger: $logger);
$connector->withMockClient($mockClient);
$response = $connector->send(new GetAccountsRequest);
expect($response->status())->toBe(200);
expect($loggedBody)->toBe('Internal Server Error');
});