logger = $this->createStub(LoggerInterface::class); } private function apiClient(): ApiClient { return $this->apiClient ??= new ApiClient( $this->createStub(SerializerInterface::class), $this->createStub(ApiResponseParser::class), $this->createStub(FilesystemOperator::class), $this->logger, $this->createStub(BookingDataProcessor::class), new RequestStack(), new RequestIdGenerator(), [ 'bpn_username' => 'test', 'bpn_password' => 'test', 'bpn_api_ip' => '127.0.0.1', 'bpn_api_ports' => [9000], ], ); } public function testEmptyResponseThrowsImmediateConnectionCloseException(): void { $this->logger = $this->createMock(LoggerInterface::class); $stream = fopen('php://memory', 'r+'); // Write nothing — stream is immediately at EOF rewind($stream); $this->setOperationStartTime(); $this->setSelectedPort(9000); $this->logger->expects($this->once()) ->method('warning') ->with( 'Server closed connection without sending data', $this->callback(static function (array $context): bool { return 9000 === $context['port']; }), ); $this->expectException(ImmediateConnectionCloseException::class); $this->expectExceptionMessage('Server closed connection without sending data'); $this->invokeReceive($stream); } public function testNonEmptyResponseReturnsData(): void { $stream = fopen('php://memory', 'r+'); fwrite($stream, '0000000005Hello'); rewind($stream); $this->setOperationStartTime(); $result = $this->invokeReceive($stream); self::assertSame('0000000005Hello', $result); } private function invokeReceive($socket): string { $method = new \ReflectionMethod(ApiClient::class, 'receive'); return $method->invoke($this->apiClient(), $socket); } private function setOperationStartTime(): void { $property = new \ReflectionProperty(ApiClient::class, 'operationStartTime'); $property->setValue($this->apiClient(), microtime(true)); } private function setSelectedPort(int $port): void { $property = new \ReflectionProperty(ApiClient::class, 'selectedPort'); $property->setValue($this->apiClient(), $port); } }