Uploaded From CV. Swandhana Server
This commit is contained in:
No files matched your search
+111
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Aranyasen\HL7\Tests;
|
||||
|
||||
use Aranyasen\HL7\Message;
|
||||
use Aranyasen\HL7\Messages\ACK;
|
||||
use Aranyasen\HL7\Segments\MSH;
|
||||
use Exception;
|
||||
|
||||
class AckTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function test()
|
||||
{
|
||||
$msg = new Message();
|
||||
$msg->addSegment(new MSH());
|
||||
|
||||
$msh = $msg->getSegmentByIndex(0);
|
||||
$msh->setField(15, 'AL');
|
||||
$msh->setField(16, 'NE');
|
||||
|
||||
$ack = new ACK($msg);
|
||||
|
||||
$seg1 = $ack->getSegmentByIndex(1);
|
||||
|
||||
self::assertSame('CA', $seg1->getField(1), 'Error code is CA');
|
||||
|
||||
$msg = new Message();
|
||||
$msh = new MSH();
|
||||
$msh->setField(15);
|
||||
$msh->setField(16, 'NE');
|
||||
$msg->addSegment($msh);
|
||||
$ack = new ACK($msg);
|
||||
|
||||
$seg1 = $ack->getSegmentByIndex(1);
|
||||
|
||||
self::assertSame('CA', $seg1->getField(1), 'Error code is CA');
|
||||
|
||||
$msg = new Message();
|
||||
$msh = new MSH();
|
||||
$msh->setField(16);
|
||||
$msh->setField(15);
|
||||
$msg->addSegment($msh);
|
||||
$ack = new ACK($msg);
|
||||
|
||||
$seg1 = $ack->getSegmentByIndex(1);
|
||||
|
||||
self::assertSame('AA', $seg1->getField(1), 'Error code is AA');
|
||||
|
||||
$ack->setAckCode('E');
|
||||
self::assertSame('AE', $seg1->getField(1), 'Error code is AE');
|
||||
|
||||
$ack->setAckCode('CR');
|
||||
self::assertSame('CR', $seg1->getField(1), 'Error code is CR');
|
||||
|
||||
$ack->setAckCode('CR', 'XX');
|
||||
self::assertSame('XX', $seg1->getField(3), 'Set message and code');
|
||||
|
||||
$msg = new Message();
|
||||
$msg->addSegment(new MSH());
|
||||
$msh = $msg->getSegmentByIndex(0);
|
||||
$msh->setField(16, 'NE');
|
||||
$msh->setField(11, 'P');
|
||||
$msh->setField(12, '2.4');
|
||||
$msh->setField(15, 'NE');
|
||||
|
||||
$ack = new ACK($msg);
|
||||
$seg0 = $ack->getSegmentByIndex(0);
|
||||
self::assertSame('P', $seg0->getField(11), 'Field 11 is P');
|
||||
self::assertSame('2.4', $seg0->getField(12), 'Field 12 is 2.4');
|
||||
self::assertSame('NE', $seg0->getField(15), 'Field 15 is NE');
|
||||
self::assertSame('NE', $seg0->getField(16), 'Field 16 is NE');
|
||||
|
||||
$ack = new ACK($msg);
|
||||
$ack->setErrorMessage('Some error');
|
||||
$seg1 = $ack->getSegmentByIndex(1);
|
||||
self::assertSame('Some error', $seg1->getField(3), 'Setting error message');
|
||||
self::assertSame('CE', $seg1->getField(1), 'Code CE after setting message');
|
||||
}
|
||||
|
||||
/** @test
|
||||
* @throws Exception
|
||||
*/
|
||||
public function a_MSH_can_be_provided_to_get_the_fields_from(): void
|
||||
{
|
||||
$msg = new Message("MSH|^~\\&|1|\rPV1|1|O|^AAAA1^^^BB|");
|
||||
$msh = new MSH(
|
||||
[
|
||||
'MSH', '^~\&', 'HL7 Corp', 'HL7 HQ', 'VISION', 'MISYS', '200404061744', '', ['DFT', 'P03'], 'TC-22222',
|
||||
'T', '2.3',
|
||||
]
|
||||
);
|
||||
$ack = new ACK($msg, $msh);
|
||||
self::assertSame("MSH|^~\&|VISION|MISYS|HL7 Corp|HL7 HQ|||ACK|\nMSA|AA|TC-22222|\n", $ack->toString(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @throws Exception
|
||||
*/
|
||||
public function globals_can_be_passed_to_constructor(): void
|
||||
{
|
||||
$msg = new Message("MSH|^~\\&|1|\rPV1|1|O|^AAAA1^^^BB|");
|
||||
$ack = new ACK($msg, null, ['SEGMENT_SEPARATOR' => '\r\n']);
|
||||
self::assertSame("MSH|^~\&|1||||||ACK|\r\nMSA|AA|\r\n", $ack->toString(true));
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Aranyasen\HL7\Tests;
|
||||
|
||||
use Aranyasen\Exceptions\HL7ConnectionException;
|
||||
use Aranyasen\Exceptions\HL7Exception;
|
||||
use Aranyasen\HL7\Message;
|
||||
use Aranyasen\HL7\Connection;
|
||||
use RuntimeException;
|
||||
|
||||
class ConnectionTest extends TestCase
|
||||
{
|
||||
use Hl7ListenerTrait;
|
||||
|
||||
protected int $port = 12011;
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
$this->deletePipe();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @throws HL7ConnectionException
|
||||
* @throws HL7Exception
|
||||
*/
|
||||
public function a_message_can_be_sent_to_a_hl7_server(): void
|
||||
{
|
||||
if (!extension_loaded('pcntl')) {
|
||||
self::markTestSkipped("Extension pcntl_fork is not loaded");
|
||||
}
|
||||
$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
|
||||
|
||||
$connection = new Connection('localhost', $this->port);
|
||||
$msg = new Message("MSH|^~\\&|1|\rPV1|1|O|^AAAA1^^^BB|", null, true, true);
|
||||
$ack = $connection->send($msg);
|
||||
self::assertInstanceOf(Message::class, $ack);
|
||||
self::assertSame('MSH|^~\&|1||||||ACK|\nMSA|AA|\n|\n', $ack->toString());
|
||||
|
||||
self::assertStringContainsString("MSH|^~\\&|1|\nPV1|1|O|^AAAA1^^^BB|", $this->getWhatServerGot());
|
||||
|
||||
$this->closeTcpSocket($connection->getSocket()); // Clean up listener
|
||||
pcntl_wait($status); // Wait till child is closed
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @throws HL7ConnectionException
|
||||
* @throws HL7Exception
|
||||
*/
|
||||
public function do_not_wait_for_ack_after_sending_if_corresponding_parameter_is_set(): void
|
||||
{
|
||||
if (!extension_loaded('pcntl')) {
|
||||
self::markTestSkipped("Extension pcntl_fork is not loaded");
|
||||
}
|
||||
$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
|
||||
|
||||
$connection = new Connection('localhost', $this->port);
|
||||
$msg = new Message("MSH|^~\\&|1|\rPV1|1|O|^AAAA1^^^BB|", null, true, true);
|
||||
self::assertNull($connection->send($msg, ' UTF-8', true));
|
||||
|
||||
$this->closeTcpSocket($connection->getSocket()); // Clean up listener
|
||||
pcntl_wait($status); // Wait till child is closed
|
||||
}
|
||||
}
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Aranyasen\HL7\Tests;
|
||||
|
||||
use Aranyasen\Exceptions\HL7Exception;
|
||||
use Aranyasen\HL7;
|
||||
use Aranyasen\HL7\Segments\PID;
|
||||
use DMS\PHPUnitExtensions\ArraySubset\Assert;
|
||||
|
||||
class HL7Test extends TestCase
|
||||
{
|
||||
/** @test
|
||||
* @throws HL7Exception
|
||||
*/
|
||||
public function hl7_message_can_be_created_using_factory_class(): void
|
||||
{
|
||||
$msg = HL7::build()->createMessage();
|
||||
self::assertIsObject($msg);
|
||||
self::assertSame(HL7\Message::class, $msg::class);
|
||||
}
|
||||
|
||||
/** @test
|
||||
* @throws HL7Exception
|
||||
*/
|
||||
public function factory_creates_a_msh_segment_with_defaults_when_no_message_string_provided(): void
|
||||
{
|
||||
$msg = HL7::build()->createMessage();
|
||||
self::assertStringContainsString('MSH|^~\&|||||', $msg->toString(true));
|
||||
}
|
||||
|
||||
/** @test
|
||||
* @throws HL7Exception
|
||||
*/
|
||||
public function a_message_can_be_built_from_a_provided_hl7_string(): void
|
||||
{
|
||||
$msg = HL7::from("MSH|^~\\&|1|\rABC|||xxx|\r")->createMessage();
|
||||
self::assertSame("MSH|^~\\&|1|\nABC|||xxx|\n", $msg->toString(true));
|
||||
}
|
||||
|
||||
/** @test
|
||||
* @throws HL7Exception
|
||||
*/
|
||||
public function hl7_property_in_a_given_hl7_string_can_be_overridden(): void
|
||||
{
|
||||
self::markTestSkipped('property cannot be overridden yet in Message() constructor');
|
||||
$msg = HL7::from("MSH|^~\\&|1|\rABC|||xxx|\r")
|
||||
->withEscapeCharacter('=')
|
||||
->createMessage();
|
||||
self::assertSame("MSH|^~=&|1|\nABC|||xxx|\n", $msg->toString(true));
|
||||
}
|
||||
/**
|
||||
* @test
|
||||
* @throws HL7Exception
|
||||
*/
|
||||
public function hl7_properties_can_be_set_using_chained_methods(): void
|
||||
{
|
||||
$msg = HL7::build()
|
||||
->withSegmentSeparator('[')
|
||||
->withFieldSeparator('#')
|
||||
->withComponentSeparator('*')
|
||||
->withSubcomponentSeparator('}')
|
||||
->withRepetitionSeparator('`')
|
||||
->withEscapeCharacter('=')
|
||||
->withHL7Version('555.666')
|
||||
->createMessage();
|
||||
self::assertStringContainsString('MSH#*`=}#', $msg->toString(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @throws Exception
|
||||
*/
|
||||
public function empty_subfields_can_be_retained_if_needed(): void
|
||||
{
|
||||
$msg = HL7::from("MSH|^~\\&|1|\rPV1|1|O|^AAAA1^^^BB|")->keepEmptySubfields()->createMessage();
|
||||
Assert::assertArraySubset(['', 'AAAA1', '', '', 'BB'], $msg->getSegmentByIndex(1)->getField(3));
|
||||
}
|
||||
|
||||
/** @test
|
||||
* @throws HL7Exception
|
||||
*/
|
||||
public function indices_of_each_segment_can_be_reset_when_message_is_composed(): void
|
||||
{
|
||||
// Create a message with a PID segment
|
||||
$msg1 = HL7::from("MSH|^~\&|||||||ORM^O01||P|2.3.1|")->createMessage();
|
||||
$msg1->addSegment(new PID());
|
||||
self::assertSame(
|
||||
"MSH|^~\&|||||||ORM^O01||P|2.3.1|\nPID|1|\n",
|
||||
$msg1->toString(true),
|
||||
'PID index in first message is 1'
|
||||
);
|
||||
|
||||
// Create another message with a PID segment
|
||||
$msg2 = Hl7::from("MSH|^~\&|||||||ORM^O01||P|2.3.1|")->createMessage();
|
||||
$msg2->addSegment(new PID());
|
||||
self::assertSame(
|
||||
"MSH|^~\&|||||||ORM^O01||P|2.3.1|\nPID|2|\n",
|
||||
$msg2->toString(true),
|
||||
'PID index gets incremented'
|
||||
);
|
||||
|
||||
// Create another message with a PID segment, this time with resetIndices()
|
||||
$msg3 = Hl7::from("MSH|^~\&|||||||ORM^O01||P|2.3.1|")
|
||||
->resetIndices()
|
||||
->createMessage();
|
||||
$msg3->addSegment(new PID());
|
||||
self::assertSame("MSH|^~\&|||||||ORM^O01||P|2.3.1|\nPID|1|\n", $msg3->toString(true), 'PID index resets to 1');
|
||||
|
||||
// Create a message with a PID segment
|
||||
$msg4 = Hl7::from("MSH|^~\&|||||||ORM^O01||P|2.3.1|")->createMessage();
|
||||
$msg4->addSegment(new PID());
|
||||
self::assertSame(
|
||||
"MSH|^~\&|||||||ORM^O01||P|2.3.1|\nPID|2|\n",
|
||||
$msg4->toString(true),
|
||||
'PID index gets incremented'
|
||||
);
|
||||
|
||||
$msg5 = Hl7::from("MSH|^~\&|||||||ORM^O01||P|2.3.1|")->createMessage();
|
||||
$msg5->resetSegmentIndices();
|
||||
$msg5->addSegment(new PID());
|
||||
self::assertSame("MSH|^~\&|||||||ORM^O01||P|2.3.1|\nPID|1|\n", $msg5->toString(true), 'PID index resets to 1');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @throws HL7Exception
|
||||
*/
|
||||
public function auto_increment_of_segment_indices_can_be_avoided(): void
|
||||
{
|
||||
$hl7String = "MSH|^~\&|||||||ORU^R01|00001|P|2.3.1|\n" . "OBX|1||11^AA|\n" . "OBX|1||22^BB|\n";
|
||||
|
||||
$msg = Hl7::from($hl7String)
|
||||
->createMessage();
|
||||
self::assertSame(
|
||||
"MSH|^~\&|||||||ORU^R01|00001|P|2.3.1|\n" . "OBX|1||11^AA|\n" . "OBX|2||22^BB|\n",
|
||||
$msg->toString(true),
|
||||
'without autoIncrementIndices(false), each instance is auto-incremented'
|
||||
);
|
||||
|
||||
$msg = Hl7::from($hl7String)
|
||||
->autoIncrementIndices(false)
|
||||
->createMessage();
|
||||
self::assertSame(
|
||||
"MSH|^~\&|||||||ORU^R01|00001|P|2.3.1|\n" . "OBX|1||11^AA|\n" . "OBX|1||22^BB|\n",
|
||||
$msg->toString(true)
|
||||
);
|
||||
|
||||
$msg = Hl7::from("MSH|^~\&|||||||ORU^R01|00001|P|2.3.1|\n" . "PID|||3^0\n")
|
||||
->autoIncrementIndices(false)
|
||||
->createMessage();
|
||||
self::assertSame(
|
||||
"MSH|^~\&|||||||ORU^R01|00001|P|2.3.1|\n" . "PID|||3^0|\n",
|
||||
$msg->toString(true),
|
||||
"With auto-incrementing off, a segment index shouldn't be inserted if not present"
|
||||
);
|
||||
}
|
||||
|
||||
/** @test
|
||||
* @throws HL7Exception
|
||||
*/
|
||||
public function repetition_separation_character_can_be_ignored(): void
|
||||
{
|
||||
$message = Hl7::from("MSH|^~\&|||||||ADT^A01||P|2.3.1|\nPID|||3^0~4^1")
|
||||
->createMessage();
|
||||
self::assertIsArray(
|
||||
$message->getSegmentByIndex(1)->getField(3),
|
||||
'By default repetition should be split into array'
|
||||
);
|
||||
|
||||
$message = Hl7::from("MSH|^~\&|||||||ADT^A01||P|2.3.1|\nPID|||3^0~4^1")
|
||||
->doNotSplitRepetition() // Ignore repetition separator character
|
||||
->createMessage();
|
||||
$patientIdentifierList = $message->getSegmentByIndex(1)->getField(3);
|
||||
self::assertIsArray($patientIdentifierList);
|
||||
self::assertSame(['3', '0~4', '1'], $patientIdentifierList);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_throws_exception_when_multiple_character_property_is_provided(): void
|
||||
{
|
||||
$this->expectException(HL7Exception::class);
|
||||
$this->expectExceptionMessage("Parameter should be a single character. Received: 'aa'");
|
||||
HL7::build()->withSegmentSeparator('aa');
|
||||
}
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Aranyasen\HL7\Tests;
|
||||
|
||||
use Aranyasen\Exceptions\HL7Exception;
|
||||
use Aranyasen\HL7\Message;
|
||||
use Aranyasen\HL7\Messages\ACK;
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* 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 string $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 string $MESSAGE_PREFIX = "\013";
|
||||
protected string $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
|
||||
*/
|
||||
public function createTcpServer(int $port, int $totalClientsToConnect): void
|
||||
{
|
||||
if (!extension_loaded('sockets')) {
|
||||
throw new RuntimeException("need ext-sockets to run this");
|
||||
}
|
||||
if (($socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) {
|
||||
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)) === false) {
|
||||
throw new RuntimeException('socket_bind() failed: reason: ' . socket_strerror($ret) . "\n");
|
||||
}
|
||||
if (($ret = socket_listen($socket, 5)) === false) {
|
||||
throw new RuntimeException('socket_listen() failed: reason: ' . socket_strerror($ret) . "\n");
|
||||
}
|
||||
|
||||
$clientCount = 0;
|
||||
while (true) { // Loop over each client
|
||||
if (($clientSocket = socket_accept($socket)) === false) {
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string ACK string
|
||||
* @throws HL7Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
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);
|
||||
return (new ACK($msg))->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up temporary pipe file generated for testing
|
||||
*/
|
||||
private function deletePipe(): void
|
||||
{
|
||||
if (file_exists($this->pipeName)) {
|
||||
unlink($this->pipeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
+638
@@ -0,0 +1,638 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Aranyasen\HL7\Tests;
|
||||
|
||||
use Aranyasen\Exceptions\HL7Exception;
|
||||
use Aranyasen\HL7\Message;
|
||||
use Aranyasen\HL7\Segment;
|
||||
use Aranyasen\HL7\Segments\MSH;
|
||||
use Aranyasen\HL7\Segments\OBR;
|
||||
use Aranyasen\HL7\Segments\OBX;
|
||||
use Aranyasen\HL7\Segments\PID;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use DMS\PHPUnitExtensions\ArraySubset\Assert;
|
||||
|
||||
class MessageTest extends TestCase
|
||||
{
|
||||
/** @test */
|
||||
public function segments_can_be_retrieved_using_index(): void
|
||||
{
|
||||
$msg = new Message("MSH|^~\\&|1|\rABC|\r");
|
||||
self::assertSame('ABC', $msg->getSegmentByIndex(1)->getName());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function segmentByIndex_returns_null_when_target_index_is_beyond_the_total_number_of_segments(): void
|
||||
{
|
||||
$msg = new Message("MSH|^~\\&|1|\rABC|\r");
|
||||
self::assertNull($msg->getSegmentByIndex(2));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_throws_error_when_field_separator_control_char_doesnt_match_the_actual_field_separators(): void
|
||||
{
|
||||
$this->expectException(HL7Exception::class);
|
||||
$msg = new Message("MSH|^~\\&#\r");
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @throws Exception
|
||||
*/
|
||||
public function subfields_can_be_retained_when_required(): void
|
||||
{
|
||||
$msg = new Message("MSH|^~\\&|1|\rPV1|1|O|^AAAA1^^^BB|", null, true);
|
||||
$pv1 = $msg->getSegmentByIndex(1);
|
||||
$fields = $pv1->getField(3);
|
||||
Assert::assertArraySubset(['', 'AAAA1', '', '', 'BB'], $fields);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function segments_can_be_added_to_existing_message(): void
|
||||
{
|
||||
$msg = new Message();
|
||||
$msg->addSegment(new MSH());
|
||||
$msg->addSegment(new Segment('PID'));
|
||||
|
||||
$seg0 = $msg->getSegmentByIndex(0);
|
||||
$seg1 = $msg->getSegmentByIndex(1);
|
||||
|
||||
self::assertSame('MSH', $seg0->getName(), 'Segment 0 name MSH');
|
||||
self::assertSame('PID', $seg1->getName(), 'Segment 1 name PID');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function fields_can_be_added_to_existing_segments(): void
|
||||
{
|
||||
$msg = new Message();
|
||||
$msg->addSegment(new MSH());
|
||||
$msg->addSegment(new Segment('ABC'));
|
||||
$seg0 = $msg->getSegmentByIndex(0);
|
||||
$seg1 = $msg->getSegmentByIndex(1);
|
||||
|
||||
$seg0->setField(3, 'XXX');
|
||||
$seg1->setField(2, 'Foo');
|
||||
|
||||
self::assertSame('XXX', $seg0->getField(3), '3d field of MSH');
|
||||
self::assertSame('Foo', $seg1->getField(2), '2nd field of ABC');
|
||||
self::assertSame('XXX', $msg->getSegmentByIndex(0)->getField(3), '3d field of MSH');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function control_characters_are_properly_set_from_MSH_Segment(): void
|
||||
{
|
||||
$msg = new Message("MSH|^~\\&|1|\rABC|||xxx|\r");
|
||||
self::assertSame("^~\\&", $msg->getSegmentByIndex(0)->getField(2), 'Encoding characters');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @throws Exception
|
||||
*/
|
||||
public function control_characters_can_be_customized_using_second_argument(): void
|
||||
{
|
||||
$msg = new Message("MSH|^~\\&|1|\nABC|||xxx|\n", ['SEGMENT_SEPARATOR' => '\r\n']);
|
||||
self::assertSame('MSH|^~\\&|1|\r\nABC|||xxx|\r\n', $msg->toString(), 'Custom line-endings');
|
||||
self::assertSame(
|
||||
"MSH|^~\\&|1|\r\nABC|||xxx|\r\n",
|
||||
$msg->toString(true),
|
||||
'toString() respects custom line-endings'
|
||||
);
|
||||
}
|
||||
|
||||
/** @test
|
||||
* @throws HL7Exception
|
||||
*/
|
||||
public function message_can_be_converted_to_string(): void
|
||||
{
|
||||
$msg = new Message("MSH|^~\\&|1|\rABC|||xxx|\r");
|
||||
self::assertSame('MSH|^~\\&|1|\nABC|||xxx|\n', $msg->toString(), 'String representation of message');
|
||||
self::assertSame(
|
||||
"MSH|^~\\&|1|\nABC|||xxx|\n",
|
||||
$msg->toString(true),
|
||||
'Pretty print representation of message'
|
||||
);
|
||||
}
|
||||
|
||||
/** @test
|
||||
* @throws HL7Exception
|
||||
*/
|
||||
public function toString_method_throws_exception_when_message_empty(): void
|
||||
{
|
||||
$msg = new Message();
|
||||
$this->expectException(HL7Exception::class);
|
||||
$msg->toString();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function fields_and_subfields_can_be_set_properly(): void
|
||||
{
|
||||
$msg = new Message("MSH|^~\\&|1|\rABC|||xx^x&y&z^yy^zz|\r");
|
||||
$segment1 = $msg->getSegmentByIndex(1);
|
||||
$fields = $segment1->getField(3);
|
||||
|
||||
self::assertSame($fields[0], 'xx', 'Composed field');
|
||||
self::assertSame($fields[1][1], 'y', 'Subcomposed field');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function fields_can_be_separated_by_custom_character(): void
|
||||
{
|
||||
$msg = new Message("MSH*^~\\&*1\rABC***xxx\r"); // Use * as a field separator
|
||||
|
||||
self::assertSame(
|
||||
'MSH*^~\&*1*\nABC***xxx*\n',
|
||||
$msg->toString(),
|
||||
'String representation of message with * as field separator'
|
||||
);
|
||||
self::assertSame('1', $msg->getSegmentByIndex(0)->getField(3), '3d field of MSH');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function components_of_a_field_can_be_separated_by_custom_character(): void
|
||||
{
|
||||
$msg = new Message("MSH|*~\\&|1\rABC|||x*y*z\r");
|
||||
$field = $msg->getSegmentByIndex(1)->getField(3);
|
||||
self::assertSame('x', $field[0], 'Composed field with * as separator between subfields');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function subcomponents_can_be_separated_by_custom_character(): void
|
||||
{
|
||||
$msg = new Message("MSH|^~\\@|1\rABC|||a^x@y@z^b\r");
|
||||
$field = $msg->getSegmentByIndex(1)->getField(3);
|
||||
self::assertSame('y', $field[1][1], 'Subcomposed field with @ as separator');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function segments_can_be_added_from_message(): void
|
||||
{
|
||||
$msg = new Message();
|
||||
$msg->addSegment(new Segment('XXX'));
|
||||
self::assertSame('XXX', $msg->getSegmentByIndex(0)->getName(), 'Add segment');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function segment_can_be_removed_from_message(): void
|
||||
{
|
||||
$msg = new Message("MSH|^~\\&|1|\nAAA|1||xxx|\nBBB|1|\nBBB|2|");
|
||||
$segment = $msg->getFirstSegmentInstance('BBB');
|
||||
$msg->removeSegment($segment);
|
||||
self::assertSame("MSH|^~\\&|1|\nAAA|1||xxx|\nBBB|2|\n", $msg->toString(true));
|
||||
|
||||
$msg = new Message("MSH|^~\\&|1|\nAAA|1||xxx|\nBBB|1|a|\nBBB|2|b|\nBBB|3|c|");
|
||||
$segment = $msg->getSegmentsByName('BBB')[1];
|
||||
$msg->removeSegment($segment, true);
|
||||
self::assertSame(
|
||||
"MSH|^~\\&|1|\nAAA|1||xxx|\nBBB|1|a|\nBBB|2|c|\n",
|
||||
$msg->toString(true),
|
||||
'Should reset index of subsequent segments'
|
||||
);
|
||||
|
||||
$msg = new Message("MSH|^~\\&|1|\nAAA|1||xxx|\nPID|1|\nBBB|2|");
|
||||
$segment = $msg->getFirstSegmentInstanceByClass(PID::class);
|
||||
$msg->removeSegment($segment);
|
||||
self::assertSame("MSH|^~\\&|1|\nAAA|1||xxx|\nBBB|2|\n", $msg->toString(true));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function segments_can_be_removed_from_message_using_index(): void
|
||||
{
|
||||
$msg = new Message();
|
||||
$msg->addSegment(new Segment('XXX'));
|
||||
$msg->addSegment(new Segment('YYY'));
|
||||
$msg->removeSegmentByIndex(0);
|
||||
self::assertSame('YYY', $msg->getSegmentByIndex(0)->getName(), 'Remove segment');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @throws Exception
|
||||
*/
|
||||
public function segments_can_be_removed_by_name(): void
|
||||
{
|
||||
$msg = new Message("MSH|^~\\&|1|\nAAA|1||xxx|\nAAA|2||\nBBB|1|\n");
|
||||
$count = $msg->removeSegmentsByName('AAA');
|
||||
self::assertSame("MSH|^~\\&|1|\nBBB|1|\n", $msg->toString(true), 'Removes consecutive segments');
|
||||
self::assertSame(2, $count);
|
||||
|
||||
$msg = new Message("MSH|^~\\&|1|\nAAA|1||xxx|\nBBB|1|\nAAA|2|\n");
|
||||
$count = $msg->removeSegmentsByName('AAA');
|
||||
self::assertSame("MSH|^~\\&|1|\nBBB|1|\n", $msg->toString(true), 'removes non-consecutive segments');
|
||||
self::assertSame(2, $count);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function a_new_segment_can_be_inserted_between_two_existing_segments(): void
|
||||
{
|
||||
$msg = new Message();
|
||||
$msg->addSegment(new Segment('AAA'));
|
||||
$msg->addSegment(new Segment('BBB'));
|
||||
$msg->insertSegment(new Segment('XXX'), 1);
|
||||
|
||||
self::assertSame('XXX', $msg->getSegmentByIndex(1)->getName(), 'Inserted segment');
|
||||
self::assertSame('BBB', $msg->getSegmentByIndex(2)->getName(), 'Existing segment should shift');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function MSH_segment_can_be_inserted_with_new_control_characters(): void
|
||||
{
|
||||
$msg = new Message();
|
||||
$msh = new MSH(['MSH', '#~\&', '', '', '', '', '', '', ['AAA', 'BBB'], '', '', '555']);
|
||||
$msg->insertSegment($msh, 0);
|
||||
self::assertSame('MSH', $msg->getSegmentByIndex(0)->getName(), 'Inserted MSH');
|
||||
self::assertSame(
|
||||
'MSH|#~\&|||||||AAA#BBB|||555|\n',
|
||||
$msg->toString(),
|
||||
'Component separator should be # and version should be 555'
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function insertSegment_appends_when_the_target_index_is_same_as_the_count_of_total_segments(): void
|
||||
{
|
||||
$msg = new Message("MSH|^~\\&|1|\rAAA|1", null, true);
|
||||
$msg->insertSegment(new Segment('XXX'), 2);
|
||||
self::assertSame('MSH|^~\&|1|\nAAA|1|\nXXX|\n', $msg->toString());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_should_not_be_possible_to_insert_segment_beyond_last_index(): void
|
||||
{
|
||||
$msg = new Message();
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$msg->insertSegment(new Segment('ZZ1'), 3);
|
||||
self::assertEmpty($msg->getSegmentByIndex(3), 'Erroneous insert');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function a_segment_can_be_overwritten(): void
|
||||
{
|
||||
$msg = new Message();
|
||||
$msg->addSegment(new Segment('AAA'));
|
||||
$msg->setSegment(new Segment('BBB'), 0);
|
||||
self::assertCount(0, $msg->getSegmentsByName('AAA'), 'No AAA segment anymore');
|
||||
self::assertSame('BBB', $msg->getSegmentByIndex(0)->getName(), 'BBB should have replaced AAA');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function setSegment_throws_exception_when_the_target_index_is_beyond_total_count_of_segments(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$msg = new Message("MSH|^~\\&|1|\nAAA|1|\n");
|
||||
$msg->setSegment(new Segment('BBB'), 3);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function setSegment_resets_control_characters_when_adding_MSH_segment_at_0th_index(): void
|
||||
{
|
||||
$msg = new Message();
|
||||
$msh = new MSH(['MSH', '#~\&', '', '', '', '', '', '', ['AAA', 'BBB'], '', '', '555']);
|
||||
$msg->setSegment($msh, 0);
|
||||
self::assertSame(
|
||||
'MSH|#~\&|||||||AAA#BBB|||555|\n',
|
||||
$msg->toString(),
|
||||
'Component separator should be # and version should be 555'
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function same_segment_type_can_be_added_multiple_times(): void
|
||||
{
|
||||
$msg = new Message();
|
||||
$msg->addSegment(new Segment('AAA'));
|
||||
$msg->addSegment(new Segment('AAA'));
|
||||
self::assertCount(2, $msg->getSegmentsByName('AAA'), 'Message should have 2 AAAs');
|
||||
}
|
||||
|
||||
/** @test
|
||||
* @throws Exception
|
||||
*/
|
||||
public function field_separator_can_be_changed_after_message_creation(): void
|
||||
{
|
||||
$msg = new Message();
|
||||
$msh = new MSH([]);
|
||||
|
||||
$msh->setField(1, '*');
|
||||
$msh->setField(2, 'abcd');
|
||||
$msg->addSegment($msh);
|
||||
self::assertSame('MSH*abcd*\n', $msg->toString(), '* should be used as field separator');
|
||||
|
||||
$msh->setField(1, '|');
|
||||
self::assertSame('MSH|abcd|\n', $msg->toString(), 'Field separator should be changed to |');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @throws Exception
|
||||
*/
|
||||
public function a_message_can_be_constructed_from_a_string(): void
|
||||
{
|
||||
$str = 'MSH|^~\&|Aranya HL7|Aranya HQ|VISION|MISYS|200404061744||DFT^P03|TC-2743|P^T|2.3|||AL|NE||ASCII||| |';
|
||||
$msg = new Message($str);
|
||||
self::assertCount(1, $msg->getSegments(), 'Message from string');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function a_message_can_be_converted_to_a_string(): void
|
||||
{
|
||||
$str = 'MSH|^~\&|Aranya HL7|Aranya HQ|VISION|MISYS|200404061744||DFT^P03|TC-2743|P^T|2.3|||AL|NE||ASCII||| |';
|
||||
$msg = new Message($str);
|
||||
self::assertSame("$str\n", $msg->toString(true), 'Message to string with subcomponents');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function a_segment_can_be_retrieved_as_a_string(): void
|
||||
{
|
||||
$msg = new Message("MSH*^~\\&*1\rXXX*a^b^c*a^b1&b2^c*xxx\r");
|
||||
self::assertSame("MSH*^~\\&*1*", $msg->getSegmentAsString(0), 'MSH segment as string');
|
||||
|
||||
$xxx = new Segment('XXX');
|
||||
$xxx->setField(2, ['a', ['b1', 'b2'], 'c']);
|
||||
$msg->addSegment($xxx);
|
||||
|
||||
self::assertSame('XXX*a^b^c*a^b1&b2^c*xxx*', $msg->getSegmentAsString(1), 'XXX segment as string');
|
||||
self::assertSame('XXX**a^b1&b2^c*', $msg->getSegmentAsString(2), 'XXX segment as string');
|
||||
|
||||
// Get segment field as string
|
||||
self::assertSame('1', $msg->getSegmentFieldAsString(0, 3), 'MSH(3) as string');
|
||||
self::assertSame('a^b1&b2^c', $msg->getSegmentFieldAsString(1, 2), 'XXX(2) as string');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function getSegmentAsString_returns_null_if_the_target_segment_doesnt_exist(): void
|
||||
{
|
||||
$msg = new Message("MSH|^~\\&|1|\nABC|");
|
||||
self::assertNull($msg->getSegmentAsString(5));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function getSegmentFieldAsString_returns_null_if_the_target_segment_doesnt_exist(): void
|
||||
{
|
||||
$msg = new Message("MSH|^~\\&|1|\nABC|");
|
||||
self::assertNull($msg->getSegmentFieldAsString(5, 1));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function getSegmentFieldAsString_returns_null_if_the_target_field_doesnt_exist(): void
|
||||
{
|
||||
$msg = new Message("MSH|^~\\&|1|\nABC|");
|
||||
self::assertNull($msg->getSegmentFieldAsString(1, 5));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function an_exception_will_be_throw_in_invalid_string(): void
|
||||
{
|
||||
$this->expectException(HL7Exception::class);
|
||||
$this->expectExceptionMessage('Not a valid message: invalid control segment');
|
||||
new Message("I'm an invalid message");
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function segment_ending_bar_can_be_omitted(): void
|
||||
{
|
||||
$msg = new Message("MSH|^~\\&|1|\nABC|||xxx|\n", ['SEGMENT_ENDING_BAR' => false]);
|
||||
self::assertSame("MSH|^~\\&|1\nABC|||xxx\n", $msg->toString(true), 'No ending bar on each segment');
|
||||
|
||||
$msg = new Message("MSH|^~\\&|1|\nABC|||xxx|\n");
|
||||
self::assertSame("MSH|^~\\&|1|\nABC|||xxx|\n", $msg->toString(true), 'Ending bar retains by default');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function segment_index_can_be_retrieved_from_a_message(): void
|
||||
{
|
||||
$message = new Message("MSH|^~\\&|1|\n");
|
||||
$pid = new PID();
|
||||
$message->addSegment($pid);
|
||||
self::assertSame(1, $message->getSegmentIndex($pid));
|
||||
|
||||
$message = new Message("MSH|^~\\&|1|\nABC|||xxx|\n");
|
||||
$pid = new PID();
|
||||
$message->addSegment($pid);
|
||||
self::assertSame(2, $message->getSegmentIndex($pid));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function segmentIndex_returns_null_if_the_target_segment_is_not_found(): void
|
||||
{
|
||||
$message = new Message("MSH|^~\\&|1|\nABC|\n");
|
||||
self::assertNull($message->getSegmentIndex(new Segment('XXX')));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function message_type_can_be_checked(): void
|
||||
{
|
||||
$msg = new Message("MSH|^~\&|||||||ORM^O01||P|2.3.1|");
|
||||
self::assertTrue($msg->isOrm());
|
||||
self::assertFalse($msg->isOru());
|
||||
self::assertFalse($msg->isSiu());
|
||||
|
||||
$msg = new Message("MSH|^~\&|||||||ORU^O01||P|2.3.1|");
|
||||
self::assertTrue($msg->isOru());
|
||||
self::assertFalse($msg->isOrm());
|
||||
self::assertFalse($msg->isSiu());
|
||||
|
||||
$msg = new Message("MSH|^~\&|||||||SIU^S12||P|2.3.1|");
|
||||
self::assertTrue($msg->isSiu());
|
||||
self::assertFalse($msg->isOrm());
|
||||
self::assertFalse($msg->isOru());
|
||||
|
||||
$msg = new Message("MSH|^~\&|||||||ADT^A01|");
|
||||
self::assertTrue($msg->isAdt());
|
||||
self::assertFalse($msg->isOrm());
|
||||
self::assertFalse($msg->isOru());
|
||||
}
|
||||
|
||||
/**
|
||||
* The segment classes use static properties to maintain segment-index. This is required as one can add new segments
|
||||
* to a given message object and expect the index to auto-increment. The side effect to this is, if you create a
|
||||
* second message, adding the same segments will now start indexing from their index in the last message instead of
|
||||
* 1! This happens because static properties are class properties, not object ones, and thus shared across all
|
||||
* objects.
|
||||
*
|
||||
* To counter this, use 'true' in the 4th argument while creating a message, or call resetSegmentIndices()
|
||||
* explicitly.
|
||||
*
|
||||
* This test verifies both.
|
||||
*
|
||||
* @test
|
||||
* @throws Exception
|
||||
*/
|
||||
public function segment_id_can_be_reset_on_demand(): void
|
||||
{
|
||||
PID::resetIndex(); // Start with a clean slate
|
||||
|
||||
// Create a message with a PID segment
|
||||
$msg1 = new Message("MSH|^~\&|||||||ORM^O01||P|2.3.1|");
|
||||
$msg1->addSegment(new PID());
|
||||
self::assertSame(
|
||||
"MSH|^~\&|||||||ORM^O01||P|2.3.1|\nPID|1|\n",
|
||||
$msg1->toString(true),
|
||||
'PID index in first message is 1'
|
||||
);
|
||||
|
||||
// Create another message with a PID segment
|
||||
$msg2 = new Message("MSH|^~\&|||||||ORM^O01||P|2.3.1|");
|
||||
$msg2->addSegment(new PID());
|
||||
self::assertSame(
|
||||
"MSH|^~\&|||||||ORM^O01||P|2.3.1|\nPID|2|\n",
|
||||
$msg2->toString(true),
|
||||
'PID index gets incremented'
|
||||
);
|
||||
|
||||
// Create another message with a PID segment, this time 4th argument to message as true
|
||||
$msg3 = new Message("MSH|^~\&|||||||ORM^O01||P|2.3.1|", resetIndices: true);
|
||||
$msg3->addSegment(new PID());
|
||||
self::assertSame("MSH|^~\&|||||||ORM^O01||P|2.3.1|\nPID|1|\n", $msg3->toString(true), 'PID index resets to 1');
|
||||
|
||||
// Create a message with a PID segment
|
||||
$msg4 = new Message("MSH|^~\&|||||||ORM^O01||P|2.3.1|");
|
||||
$msg4->addSegment(new PID());
|
||||
self::assertSame(
|
||||
"MSH|^~\&|||||||ORM^O01||P|2.3.1|\nPID|2|\n",
|
||||
$msg4->toString(true),
|
||||
'PID index gets incremented'
|
||||
);
|
||||
|
||||
$msg5 = new Message("MSH|^~\&|||||||ORM^O01||P|2.3.1|");
|
||||
$msg5->resetSegmentIndices();
|
||||
$msg5->addSegment(new PID());
|
||||
self::assertSame("MSH|^~\&|||||||ORM^O01||P|2.3.1|\nPID|1|\n", $msg5->toString(true), 'PID index resets to 1');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @throws Exception
|
||||
*/
|
||||
public function segment_index_autoincrement_can_be_avoided(): void
|
||||
{
|
||||
$hl7String = "MSH|^~\&|||||||ORU^R01|00001|P|2.3.1|\n" . "OBX|1||11^AA|\n" . "OBX|1||22^BB|\n";
|
||||
|
||||
$msg = new Message($hl7String, resetIndices: true);
|
||||
self::assertSame(
|
||||
"MSH|^~\&|||||||ORU^R01|00001|P|2.3.1|\n" . "OBX|1||11^AA|\n" . "OBX|2||22^BB|\n",
|
||||
$msg->toString(true),
|
||||
'without 5th argument as false, each instance is auto-incremented'
|
||||
);
|
||||
|
||||
$msg = new Message($hl7String, null, true, true, false);
|
||||
self::assertSame(
|
||||
"MSH|^~\&|||||||ORU^R01|00001|P|2.3.1|\n" . "OBX|1||11^AA|\n" . "OBX|1||22^BB|\n",
|
||||
$msg->toString(true)
|
||||
);
|
||||
|
||||
$msg = new Message("MSH|^~\&|||||||ORU^R01|00001|P|2.3.1|\n" . "PID|||3^0\n", null, true, false, false);
|
||||
self::assertSame(
|
||||
"MSH|^~\&|||||||ORU^R01|00001|P|2.3.1|\n" . "PID|||3^0|\n",
|
||||
$msg->toString(true),
|
||||
"With auto-incrementing off, a segment index shouldn't be inserted if not present"
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function a_segments_presence_can_be_checked(): void
|
||||
{
|
||||
$message = new Message("MSH|^~\&|||||||ORM^O01||P|2.3.1|");
|
||||
self::assertFalse($message->hasSegment('PID'));
|
||||
|
||||
$message->addSegment(new PID());
|
||||
self::assertTrue($message->hasSegment('PID'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function first_of_the_given_segment_name_can_be_easily_obtained_using_a_helper_method(): void
|
||||
{
|
||||
$message = new Message("MSH|^~\&|||||||ORM^O01||P|2.3.1|");
|
||||
$message->addSegment(new PID());
|
||||
$firstPidSegment = $message->getFirstSegmentInstance('PID');
|
||||
self::assertNotNull($firstPidSegment);
|
||||
self::assertIsObject($firstPidSegment);
|
||||
self::assertInstanceOf(PID::class, $firstPidSegment);
|
||||
|
||||
$firstPidSegment = $message->getFirstSegmentInstanceByClass(PID::class);
|
||||
self::assertNotNull($firstPidSegment);
|
||||
self::assertIsObject($firstPidSegment);
|
||||
self::assertInstanceOf(PID::class, $firstPidSegment);
|
||||
|
||||
self::assertNull($message->getFirstSegmentInstance('XXX'), 'Non existing segment should return null');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function message_can_be_verified_as_empty(): void
|
||||
{
|
||||
self::assertTrue((new Message())->isEmpty());
|
||||
self::assertFalse((new Message("MSH|^~\&|||||||ORM^O01||P|2.3.1|"))->isEmpty());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function field_with_repetition_separator_splits_into_array_by_default(): void
|
||||
{
|
||||
$message = new Message("MSH|^~\&|||||||ADT^A01||P|2.3.1|\nPID|||3^0~4^1"); // Repetition separator is ~
|
||||
$patientIdentifierList = $message->getSegmentByIndex(1)->getField(3);
|
||||
self::assertIsArray($patientIdentifierList);
|
||||
self::assertSame('3', $patientIdentifierList[0][0]);
|
||||
self::assertSame('0', $patientIdentifierList[0][1]);
|
||||
self::assertSame('4', $patientIdentifierList[1][0]);
|
||||
self::assertSame('1', $patientIdentifierList[1][1]);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function field_with_repetition_separator_can_be_split_into_array(): void
|
||||
{
|
||||
$message = new Message("MSH|^~\&|||||||ADT^A01||P|2.3.1|\nPID|||3^0~4^1", doNotSplitRepetition: false);
|
||||
$patientIdentifierList = $message->getSegmentByIndex(1)->getField(3);
|
||||
self::assertIsArray($patientIdentifierList);
|
||||
self::assertSame('3', $patientIdentifierList[0][0]);
|
||||
self::assertSame('0', $patientIdentifierList[0][1]);
|
||||
self::assertSame('4', $patientIdentifierList[1][0]);
|
||||
self::assertSame('1', $patientIdentifierList[1][1]);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function repetition_separation_character_can_be_ignored(): void
|
||||
{
|
||||
$message = new Message("MSH|^~\&|||||||ADT^A01||P|2.3.1|\nPID|||3^0~4^1", doNotSplitRepetition: true);
|
||||
$pid = $message->getSegmentByIndex(1);
|
||||
$patientIdentifierList = $pid->getField(3);
|
||||
self::assertIsArray($patientIdentifierList);
|
||||
self::assertSame(['3', '0~4', '1'], $patientIdentifierList);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function a_message_can_be_saved_in_a_file(): void
|
||||
{
|
||||
$hl7File = __DIR__ . DIRECTORY_SEPARATOR . 'hl7_test_' . random_int(100, 1000) . '.hl7';
|
||||
$message = new Message("MSH|^~\&|");
|
||||
$message->toFile($hl7File);
|
||||
self::assertFileExists($hl7File);
|
||||
self::assertSame($message->toString(true), file_get_contents($hl7File));
|
||||
if (file_exists($hl7File)) {
|
||||
unlink($hl7File);
|
||||
}
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function segments_can_be_retrieved_by_class(): void
|
||||
{
|
||||
$message = new Message(
|
||||
"MSH|^~\&|||||||ADT^A01||P|2.3.1|\nPID|||3^0~4^1\nOBX|1|||\nOBX|2|||",
|
||||
doNotSplitRepetition: true
|
||||
);
|
||||
$MSHs = $message->getSegmentsByClass(MSH::class);
|
||||
self::assertCount(1, $MSHs);
|
||||
self::assertInstanceOf(MSH::class, $MSHs[0]);
|
||||
$PIDs = $message->getSegmentsByClass(PID::class);
|
||||
self::assertCount(1, $PIDs);
|
||||
self::assertInstanceOf(PID::class, $PIDs[0]);
|
||||
$OBXs = $message->getSegmentsByClass(OBX::class);
|
||||
self::assertCount(2, $OBXs);
|
||||
self::assertInstanceOf(OBX::class, $OBXs[0]);
|
||||
$OBRs = $message->getSegmentsByClass(OBR::class);
|
||||
self::assertCount(0, $OBRs);
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Aranyasen\HL7\Tests;
|
||||
|
||||
use Aranyasen\HL7\Segment;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class SegmentTest extends TestCase
|
||||
{
|
||||
/** @test */
|
||||
public function it_throws_error_when_segment_name_has_less_than_3_characters(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
new Segment('XX');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_throws_exception_when_a_blank_string_is_passed_as_segment_name(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
new Segment('');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_throws_exception_when_segment_name_is_not_in_upper_case(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
new Segment('xxx');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function field_at_a_given_nonzero_index_can_be_set(): void
|
||||
{
|
||||
$seg = new Segment('XXX');
|
||||
$seg->setField(1, 'YYY');
|
||||
self::assertSame('YYY', $seg->getField(1), 'Field 1 is YYY');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function field_at_index_0_can_not_be_changed(): void
|
||||
{
|
||||
$seg = new Segment('XXX');
|
||||
$seg->setField(0, 'YYY');
|
||||
self::assertNotSame('YYY', $seg->getField(0), 'Field 0 has not been changed');
|
||||
self::assertSame('XXX', $seg->getField(0), 'Field 0 is still the same');
|
||||
self::assertSame('XXX', $seg->getName(), 'Segment name is still the same');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function a_segment_can_be_constructed_from_an_array(): void
|
||||
{
|
||||
$seg = new Segment('XXX', ['a', 'b', 'c', ['p', 'q', 'r'], 'd']);
|
||||
self::assertSame('c', $seg->getField(3), 'Constructor with array');
|
||||
self::assertSame('r', $seg->getField(4)[2], 'Constructor with array for composed fields');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function field_can_be_cleared(): void
|
||||
{
|
||||
$segment = new Segment('XXX', ['a']);
|
||||
self::assertSame('a', $segment->getField(1));
|
||||
$segment->clearField(1);
|
||||
self::assertNull($segment->getField(1), 'Field 1 should be NULL');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function field_can_be_set_using_array(): void
|
||||
{
|
||||
$seg = new Segment('XXX');
|
||||
$seg->setField(3, ['1', '2', '3']);
|
||||
self::assertIsArray($seg->getField(3), 'Composed field 1^2^3');
|
||||
self::assertCount(3, $seg->getField(3), 'Getting composed fields as array');
|
||||
self::assertSame('2', $seg->getField(3)[1], 'Getting single value from composed field');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function fields_from_a_given_position_to_end_can_be_retrieved_in_an_array(): void
|
||||
{
|
||||
$seg = new Segment('XXX');
|
||||
$seg->setField(8, 'aaa');
|
||||
self::assertCount(7, $seg->getFields(2), 'Getting all fields from 2nd index');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function a_chunk_of_fields_can_be_retrieved_from_a_segment(): void
|
||||
{
|
||||
$seg = new Segment('XXX');
|
||||
$seg->setField(8, 'aaa');
|
||||
self::assertCount(3, $seg->getFields(2, 4), 'Getting fields from 2 till 4');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function setting_field_beyond_last_index_creates_empty_fields_in_between(): void
|
||||
{
|
||||
$seg = new Segment('XXX');
|
||||
$seg->setField(8, 'aaa');
|
||||
self::assertCount(9, $seg->getFields(), 'Number of fields in segment');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function total_size_of_a_segment_can_be_obtained(): void
|
||||
{
|
||||
$seg = new Segment('XXX');
|
||||
$seg->setField(8, ['']);
|
||||
self::assertSame(8, $seg->size(), 'Size operator');
|
||||
$seg->setField(12, 'x');
|
||||
self::assertSame(12, $seg->size(), 'Size operator');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function a_field_can_have_0_as_a_value(): void
|
||||
{
|
||||
$segment = new Segment('XXX');
|
||||
|
||||
$segment->setField(1, 0);
|
||||
self::assertSame(0, $segment->getField(1));
|
||||
|
||||
$segment->setField(1, '0');
|
||||
self::assertSame('0', $segment->getField(1));
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Aranyasen\HL7\Tests\Segments;
|
||||
|
||||
use Aranyasen\HL7\Segments\MSH;
|
||||
use Aranyasen\HL7\Tests\TestCase;
|
||||
use Exception;
|
||||
|
||||
class MSHTest extends TestCase
|
||||
{
|
||||
/** @test */
|
||||
public function MSH_formed_without_any_arguments_should_have_mandatory_fields_set_automatically(): void
|
||||
{
|
||||
$msh = new MSH();
|
||||
self::assertSame('|', $msh->getField(1));
|
||||
self::assertSame('^~\\&', $msh->getField(2));
|
||||
self::assertSame('2.3', $msh->getVersionId());
|
||||
self::assertNotEmpty($msh->getDateTimeOfMessage());
|
||||
self::assertMatchesRegularExpression('/\d{14}/', $msh->getDateTimeOfMessage());
|
||||
self::assertNotEmpty($msh->getMessageControlId());
|
||||
}
|
||||
|
||||
/** @test
|
||||
* @throws Exception
|
||||
*/
|
||||
public function an_array_of_fields_can_be_passed_to_constructor_to_construct_MSH(): void
|
||||
{
|
||||
$msh = new MSH(
|
||||
[
|
||||
'MSH', '^~\&', 'HL7 Corp', 'HL7 HQ', 'VISION', 'MISYS', '200404061744', '', ['DFT', 'P03'], 'TC-22222',
|
||||
'T', '2.3',
|
||||
]
|
||||
);
|
||||
self::assertSame(
|
||||
[
|
||||
'MSH', '', '^~\&', 'HL7 Corp', 'HL7 HQ', 'VISION', 'MISYS', '200404061744', '', ['DFT', 'P03'],
|
||||
'TC-22222', 'T', '2.3',
|
||||
],
|
||||
$msh->getFields()
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function field_separator_can_be_set(): void
|
||||
{
|
||||
$msh = new MSH();
|
||||
$msh->setField(1, '*');
|
||||
self::assertSame('*', $msh->getField(1), 'MSH Field sep field (MSH(1))');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function more_than_one_character_as_field_separator_is_not_accepted(): void
|
||||
{
|
||||
$msh = new MSH();
|
||||
$msh->setField(1, 'xx');
|
||||
self::assertSame('|', $msh->getField(1), 'MSH Field sep field (MSH(1))');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function version_id_can_be_string_or_array_for_v2_7_onwards(): void
|
||||
{
|
||||
$msh = new MSH();
|
||||
$msh->setVersionId('2.3');
|
||||
self::assertSame('2.3', $msh->getVersionId());
|
||||
|
||||
$msh->setVersionId(['2.7', 'NZL', '1.0']);
|
||||
self::assertSame(['2.7', 'NZL', '1.0'], $msh->getVersionId());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function index_2_in_MSH_accepts_only_4_character_strings(): void
|
||||
{
|
||||
$msh = new MSH();
|
||||
$msh->setField(2, 'xxxx');
|
||||
self::assertSame('xxxx', $msh->getField(2), 'Special fields not changed');
|
||||
|
||||
$msh->setField(2, 'yyyyy');
|
||||
self::assertSame('xxxx', $msh->getField(2), 'Special fields not changed');
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Aranyasen\HL7\Tests\Segments;
|
||||
|
||||
use Aranyasen\HL7\Message;
|
||||
use Aranyasen\HL7\Segments\NK1;
|
||||
use Aranyasen\HL7\Tests\TestCase;
|
||||
|
||||
class NK1Test extends TestCase
|
||||
{
|
||||
/** @test */
|
||||
public function NK1_segment_can_be_parsed_from_a_HL7_message(): void
|
||||
{
|
||||
$msg = new Message("MSH|^~\\&|1|\nNK1|1|John^Doe|\n");
|
||||
$nk1Segments = $msg->getSegmentsByName('NK1');
|
||||
self::assertIsArray($nk1Segments);
|
||||
self::assertCount(1, $nk1Segments);
|
||||
self::assertSame(['John', 'Doe'], $nk1Segments[0]->getNKName());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function next_of_kin_name_can_be_set_and_get(): void
|
||||
{
|
||||
$nk1Segment = new NK1();
|
||||
$nk1Segment->setNKName("John Doe");
|
||||
self::assertSame("John Doe", $nk1Segment->getNKName());
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Aranyasen\HL7\Tests\Segments;
|
||||
|
||||
use Aranyasen\HL7\Segments\OBX;
|
||||
use Aranyasen\HL7\Tests\TestCase;
|
||||
|
||||
class OBXTest extends TestCase
|
||||
{
|
||||
/** @test */
|
||||
public function segment_id_automatically_increments_when_a_new_instance_is_created(): void
|
||||
{
|
||||
OBX::resetIndex(); // Previous tests would have incremented the static $index variable
|
||||
|
||||
$obx = new OBX();
|
||||
$this->assertSame(1, $obx->getID());
|
||||
$obx = new OBX();
|
||||
$this->assertSame(2, $obx->getID());
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Aranyasen\HL7\Tests\Segments;
|
||||
|
||||
use Aranyasen\Exceptions\HL7Exception;
|
||||
use Aranyasen\HL7\Message;
|
||||
use Aranyasen\HL7\Segments\PID;
|
||||
use Aranyasen\HL7\Tests\TestCase;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class PIDTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider validSexValues
|
||||
* @test
|
||||
*/
|
||||
public function PID_8_should_accept_value_for_sex_as_per_hl7_standard(string $validSexValue): void
|
||||
{
|
||||
$pidSegment = (new PID());
|
||||
$pidSegment->setSex($validSexValue);
|
||||
self::assertSame($validSexValue, $pidSegment->getSex(), "Sex should have been set with '$validSexValue'");
|
||||
self::assertSame($validSexValue, $pidSegment->getField(8), "Sex should have been set with '$validSexValue'");
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider invalidSexValues
|
||||
* @test
|
||||
* @param string $invalidSexValue
|
||||
*/
|
||||
public function PID_8_should_not_accept_non_standard_values_for_sex(string $invalidSexValue): void
|
||||
{
|
||||
$pidSegment = (new PID());
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage("Sex should one of 'A', 'F', 'M', 'N', 'O' or 'U'. Given: '$invalidSexValue'");
|
||||
$pidSegment->setSex($invalidSexValue);
|
||||
self::assertEmpty($pidSegment->getSex(), "Sex should not have been set with value: $invalidSexValue");
|
||||
self::assertEmpty($pidSegment->getField(8), "Sex should not have been set with value: $invalidSexValue");
|
||||
}
|
||||
|
||||
/** @test
|
||||
* @throws HL7Exception
|
||||
*/
|
||||
public function PID_5_should_parse_properly(): void
|
||||
{
|
||||
$messageString = "MSH|^~\\&|1|\r" .
|
||||
'PID|||||' .
|
||||
'Test &""&firstname &""&""^F^N N^^^^F^^^^""~Test &""&Test^Test^""^^^^B~""&""&""^^^^^^P~^Lastname^^^^^N|' .
|
||||
"\r";
|
||||
$msg = new Message($messageString, null, false, true, true, true);
|
||||
|
||||
self::assertSame(
|
||||
'MSH|^~\&|1|\nPID|1||||Test &""&firstname &""&""^F^N N^F^""~Test &""&Test^Test^""^B~""&""&""^P~^' .
|
||||
'Lastname^N|\n',
|
||||
$msg->toString()
|
||||
);
|
||||
}
|
||||
|
||||
public function validSexValues(): array
|
||||
{
|
||||
return [
|
||||
['A'], ['F'], ['M'], ['N'], ['O'], ['U']
|
||||
];
|
||||
}
|
||||
|
||||
public function invalidSexValues(): array
|
||||
{
|
||||
return [
|
||||
['B'], ['Z'], ['z'], ['a']
|
||||
];
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Aranyasen\HL7\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase as TC;
|
||||
|
||||
abstract class TestCase extends TC
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
//
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
// Any tearDown should appear before parent::tearDown()
|
||||
parent::tearDown();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user