-
Notifications
You must be signed in to change notification settings - Fork 85
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor and add tests for Connection.php (#28)
Refactor and add tests for Connection.php
- Loading branch information
Showing
4 changed files
with
223 additions
and
85 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,92 +1,75 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Aranyasen\HL7\tests; | ||
namespace Aranyasen\HL7\Tests; | ||
|
||
use Aranyasen\Exceptions\HL7ConnectionException; | ||
use Aranyasen\Exceptions\HL7Exception; | ||
use Aranyasen\HL7\Message; | ||
use Aranyasen\HL7\Connection; | ||
use Aranyasen\HL7\Segment; | ||
use Aranyasen\HL7\Segments\MSH; | ||
use RuntimeException; | ||
|
||
class ConnectionTest extends TestCase | ||
{ | ||
public function test() | ||
{ | ||
$this->markTestIncomplete(); | ||
|
||
$msg = new Message(); | ||
$msg->addSegment(new MSH()); | ||
|
||
$seg1 = new Segment('PID'); | ||
|
||
$seg1->setField(3, 'XXX'); | ||
use Hl7ListenerTrait; | ||
|
||
$msg->addSegment($seg1); | ||
protected $port = 12011; | ||
|
||
// If you have fork support, try this... | ||
|
||
// $pid = pcntl_fork(); | ||
// | ||
// if (! $pid) { | ||
// // Server process | ||
// set_time_limit(0); | ||
// | ||
// // Turn on implicit output flushing so we see what we're getting | ||
// // as it comes in. | ||
// ob_implicit_flush(); | ||
// | ||
// if (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) < 0) { | ||
// echo 'socket_create() failed: reason: ' . socket_strerror($sock) . "\n"; | ||
// } | ||
// | ||
// if (($ret = socket_bind($sock, "localhost", 12011)) < 0) { | ||
// echo 'socket_bind() failed: reason: ' . socket_strerror($ret) . "\n"; | ||
// } | ||
// | ||
// if (($ret = socket_listen($sock, 5)) < 0) { | ||
// echo 'socket_listen() failed: reason: ' . socket_strerror($ret) . "\n"; | ||
// } | ||
// | ||
// do { | ||
// if (($msgsock = socket_accept($sock)) < 0) { | ||
// echo 'socket_accept() failed: reason: ' . socket_strerror($msgsock) . "\n"; | ||
// break; | ||
// } | ||
// | ||
// if (false === ($buf = socket_read($msgsock, 8192, PHP_NORMAL_READ))) { | ||
// echo 'socket_read() failed: reason: ' . socket_strerror($ret) . "\n"; | ||
// break 2; | ||
// } | ||
// | ||
// echo "Incoming: $buf\n"; | ||
// | ||
// $msg = new Message($buf); | ||
// | ||
// $ack = new ACK($msg); | ||
// socket_write($msgsock, "\013" . $ack->toString() . "\034\015", \strlen($ack->toString() + 3)); | ||
// echo 'Said: ' . $ack->toString(1) . "\n"; | ||
// | ||
// } while (true); | ||
// | ||
// socket_close($msgsock); | ||
// | ||
// exit; | ||
// } | ||
/** | ||
* @test | ||
* @throws HL7ConnectionException | ||
* @throws HL7Exception | ||
* @throws \ReflectionException | ||
*/ | ||
public function a_message_can_be_sent_to_a_hl7_server(): void | ||
{ | ||
$pid = pcntl_fork(); | ||
if ($pid === -1) { | ||
throw new RuntimeException('Could not fork'); | ||
} | ||
if (!$pid) { // In child process | ||
$this->createTcpServer($this->port, 1); | ||
} | ||
if ($pid) { // in Parent process... | ||
sleep(2); // Give a second to server (child) to start up. TODO: Speed up by polling | ||
|
||
$socket = $this->getMock('Net_Socket'); | ||
$connection = new Connection('localhost', $this->port); | ||
$msg = new Message("MSH|^~\\&|1|\rPV1|1|O|^AAAA1^^^BB|", null, true, true); | ||
$ack = $connection->send($msg); | ||
$this->assertInstanceOf(Message::class, $ack); | ||
$this->assertSame('MSH|^~\&|1||||||ACK|\nMSA|AA|\n|\n', $ack->toString()); | ||
|
||
$socket->expects($this->once()) | ||
->method('write') | ||
->with("\013" . $msg->toString() . "\034\015"); | ||
$this->assertStringContainsString("MSH|^~\\&|1|\nPV1|1|O|^AAAA1^^^BB|", $this->getWhatServerGot()); | ||
|
||
$socket->expects($this->once()) | ||
->method('read') | ||
->will($this->returnValue("MSH*^~\\&*1\rPID***xxx\r" . "\034\015")); | ||
$this->closeTcpSocket($connection->getSocket()); // Clean up listener | ||
pcntl_wait($status); // Wait till child is closed | ||
} | ||
} | ||
|
||
$conn = new Connection($socket); | ||
/** | ||
* @test | ||
* @throws HL7ConnectionException | ||
* @throws HL7Exception | ||
* @throws \ReflectionException | ||
*/ | ||
public function do_not_wait_for_ack_after_sending_if_corresponding_parameter_is_set(): void | ||
{ | ||
$pid = pcntl_fork(); | ||
if ($pid === -1) { | ||
throw new RuntimeException('Could not fork'); | ||
} | ||
if (!$pid) { // In child process | ||
$this->createTcpServer($this->port, 1); | ||
} | ||
if ($pid) { // in Parent process... | ||
sleep(2); // Give a second to server (child) to start up | ||
|
||
$resp = $conn->send($msg); | ||
$connection = new Connection('localhost', $this->port); | ||
$msg = new Message("MSH|^~\\&|1|\rPV1|1|O|^AAAA1^^^BB|", null, true, true); | ||
$this->assertNull($connection->send($msg,' UTF-8', true)); | ||
|
||
$this->assertInstanceOf(Message::class, $resp); | ||
$this->closeTcpSocket($connection->getSocket()); // Clean up listener | ||
pcntl_wait($status); // Wait till child is closed | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
<?php | ||
|
||
namespace Aranyasen\HL7\Tests; | ||
|
||
use Aranyasen\Exceptions\HL7Exception; | ||
use Aranyasen\HL7\Message; | ||
use Aranyasen\HL7\Messages\ACK; | ||
|
||
/** | ||
* Trait Hl7ListenerTrait | ||
* | ||
* Create a TCP socket server to receive HL7 messages. It responds to HL7 messages with an ACK | ||
* It also creates a pipe so client can get back exactly what it sent. Useful for testing... | ||
* To close the server, send "\n" or "shutdown\n" | ||
* @package Aranyasen\HL7\Tests | ||
*/ | ||
trait Hl7ListenerTrait | ||
{ | ||
private $pipeName = "pipe1"; | ||
|
||
// As per MLLP protocol, the sender prefixes and suffixes the HL7 message with certain codes. If these need to be | ||
// overwritten, simply declare these after the 'use Hl7ListenerTrait' statement in the calling class | ||
protected $MESSAGE_PREFIX = "\013"; | ||
protected $MESSAGE_SUFFIX = "\034\015"; | ||
|
||
public function writeToPipe(string $value): void | ||
{ | ||
$pipe = fopen($this->pipeName,'wb'); | ||
fwrite($pipe, $value); | ||
} | ||
|
||
public function readFromPipe(): string | ||
{ | ||
$pipe = fopen($this->pipeName,'rb'); | ||
return fread($pipe, 1024); | ||
} | ||
|
||
public function getWhatServerGot(): string | ||
{ | ||
return $this->readFromPipe(); | ||
} | ||
|
||
/** | ||
* @param int $port | ||
* @param int $totalClientsToConnect How many clients are expected to connect to this server, once it's up | ||
* @throws HL7Exception | ||
* @throws \ReflectionException | ||
*/ | ||
public function createTcpServer(int $port, int $totalClientsToConnect): void | ||
{ | ||
if (($socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) < 0) { | ||
throw new \RuntimeException('socket_create() failed: reason: ' . socket_strerror(socket_last_error()) . "\n"); | ||
} | ||
|
||
// This is to avoid "address already in use" error while doing ->bind() | ||
if (!socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1)) { | ||
echo socket_strerror(socket_last_error($socket)); | ||
exit(-1); | ||
} | ||
|
||
if (($ret = socket_bind($socket, "localhost", $port)) < 0) { | ||
throw new \RuntimeException('socket_bind() failed: reason: ' . socket_strerror($ret) . "\n"); | ||
} | ||
if (($ret = socket_listen($socket, 5)) < 0) { | ||
throw new \RuntimeException('socket_listen() failed: reason: ' . socket_strerror($ret) . "\n"); | ||
} | ||
|
||
$clientCount = 0; | ||
while (true) { // Loop over each client | ||
if (($clientSocket = socket_accept($socket)) < 0) { | ||
echo 'socket_accept() failed: reason: ' . socket_strerror(socket_last_error()) . "\n"; | ||
socket_close($clientSocket); | ||
exit(); | ||
} | ||
if ($clientSocket === false) { | ||
continue; | ||
} | ||
|
||
$clientCount++; | ||
$clientName = 'Unknown'; | ||
socket_getpeername($clientSocket, $clientName); | ||
// echo "Client {$clientCount} ({$clientName}) connected\n"; // Uncomment to debug | ||
|
||
while (true) { // Keep reading a given client until they send "shutdown" or an empty string | ||
$buffer = socket_read($clientSocket, 1024); // Keeps reading until bytes exhaust, /n or /r | ||
if (false === $buffer) { | ||
break; | ||
} | ||
// echo "\n--- From client: '$buffer' ---\n\n"; // Uncomment to debug | ||
if (!$buffer || empty(trim($buffer)) || false !== stripos($buffer, 'shutdown')) { | ||
break; | ||
} | ||
|
||
$ackString = $this->getAckString($buffer); | ||
$message = $this->MESSAGE_PREFIX . $ackString . $this->MESSAGE_SUFFIX; | ||
socket_write($clientSocket, $message, strlen($message)); | ||
|
||
// Also write to a pipe/msg queue for client to get the actual message | ||
$this->writeToPipe($buffer); | ||
} | ||
|
||
socket_shutdown($clientSocket); | ||
socket_close($clientSocket); | ||
|
||
if ($totalClientsToConnect > 0 && $clientCount >= $totalClientsToConnect) { | ||
break; | ||
} | ||
} | ||
socket_close($socket); | ||
exit(0); // Child process needs it | ||
} | ||
|
||
/** | ||
* @param $socket | ||
*/ | ||
public function closeTcpSocket($socket): void | ||
{ | ||
$msg = "\n"; // Or send "shutdown\n" | ||
socket_write($socket, $msg, strlen($msg)); // Tell the client to shutdown | ||
} | ||
|
||
/** | ||
* @param string $hl7 | ||
* @return string ACK string | ||
* @throws HL7Exception | ||
* @throws \ReflectionException | ||
*/ | ||
private function getAckString(string $hl7): string | ||
{ | ||
// Remove message prefix and suffix | ||
$hl7 = preg_replace('/^' . $this->MESSAGE_PREFIX . '/', '', $hl7); | ||
$hl7 = preg_replace('/' . $this->MESSAGE_SUFFIX . '$/', '', $hl7); | ||
|
||
$msg = new Message(trim($hl7), null, true, true); | ||
$ack = new ACK($msg); | ||
return $ack->toString(); | ||
} | ||
// TODO: This trait leaves a file pipe1 behind. Clean it up: in tearDown: unlink($this->>pipe) | ||
} |