createOrderTestPatient(); // Get available tests from testdefsite $testsResult = $this->call('get', 'api/test'); $testsBody = json_decode($testsResult->getBody(), true); $availableTests = $testsBody['data'] ?? []; // Skip if no tests available if (empty($availableTests)) { $this->markTestSkipped('No tests available in testdefsite table'); } $testSiteID = $availableTests[0]['TestSiteID']; // Create order with tests $payload = [ 'InternalPID' => $internalPID, 'Priority' => 'R', 'Tests' => [ ['TestSiteID' => $testSiteID] ] ]; $result = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload); // Assertions $result->assertStatus(201); $body = json_decode($result->getBody(), true); $this->assertEquals('success', $body['status']); $this->assertArrayHasKey('data', $body); $this->assertArrayHasKey('OrderID', $body['data']); $this->assertArrayHasKey('Specimens', $body['data']); $this->assertArrayHasKey('Tests', $body['data']); $this->assertIsArray($body['data']['Specimens']); $this->assertIsArray($body['data']['Tests']); $this->assertNotEmpty($body['data']['Tests'], 'Tests array should not be empty'); return $body['data']['OrderID']; } public function testCreateOrderValidationFailsWithoutInternalPID() { $payload = [ 'Tests' => [ ['TestSiteID' => 1] ] ]; $result = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload); $result->assertStatus(400); $body = json_decode(strip_tags($result->getBody()), true); $this->assertIsArray($body); $messages = $body['messages'] ?? $body['errors'] ?? []; $this->assertIsArray($messages); $this->assertArrayHasKey('InternalPID', $messages); } public function testCreateOrderFailsWithInvalidPatient() { $payload = [ 'InternalPID' => 999999, 'Tests' => [ ['TestSiteID' => 1] ] ]; $result = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload); $result->assertStatus(400); $body = json_decode(strip_tags($result->getBody()), true); $this->assertIsArray($body); $messages = $body['messages'] ?? $body['errors'] ?? []; $this->assertIsArray($messages); $this->assertArrayHasKey('InternalPID', $messages); } public function testCreateOrderWithMultipleTests() { $internalPID = $this->createOrderTestPatient(); // Get available tests $testsResult = $this->call('get', 'api/test'); $testsBody = json_decode($testsResult->getBody(), true); $availableTests = $testsBody['data'] ?? []; if (count($availableTests) < 2) { $this->markTestSkipped('Need at least 2 tests for this test'); } $testSiteID1 = $availableTests[0]['TestSiteID']; $testSiteID2 = $availableTests[1]['TestSiteID']; // Create order with multiple tests $payload = [ 'InternalPID' => $internalPID, 'Priority' => 'S', 'Comment' => 'Urgent order for multiple tests', 'Tests' => [ ['TestSiteID' => $testSiteID1], ['TestSiteID' => $testSiteID2] ] ]; $result = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload); $result->assertStatus(201); $body = json_decode($result->getBody(), true); $this->assertEquals('success', $body['status']); $this->assertGreaterThanOrEqual(1, count($body['data']['Specimens']), 'Should have at least one specimen'); $this->assertGreaterThanOrEqual(2, count($body['data']['Tests']), 'Should have at least two tests'); } public function testOrderShowIncludesDisciplineAndSequenceOrdering() { $internalPID = $this->createOrderTestPatient(); $testSiteIDs = $this->collectTestSiteIDs(2); $orderID = $this->createOrderWithTests($internalPID, $testSiteIDs); $response = $this->call('get', $this->endpoint . '/' . $orderID); $response->assertStatus(200); $body = json_decode($response->getBody(), true); $this->assertEquals('success', $body['status']); $tests = $body['data']['Tests'] ?? []; $this->assertNotEmpty($tests, 'Tests payload should not be empty'); $lastKey = null; foreach ($tests as $test) { $this->assertArrayHasKey('Discipline', $test); $this->assertArrayHasKey('TestType', $test); $this->assertNotEmpty($test['TestType'], 'Each test should report a test type'); $this->assertArrayHasKey('SeqScr', $test); $this->assertArrayHasKey('SeqRpt', $test); $discipline = $test['Discipline']; $this->assertArrayHasKey('DisciplineID', $discipline); $this->assertArrayHasKey('DisciplineName', $discipline); $this->assertArrayHasKey('SeqScr', $discipline); $this->assertArrayHasKey('SeqRpt', $discipline); $currentKey = $this->buildTestSortKey($test); if ($lastKey !== null) { $this->assertGreaterThanOrEqual($lastKey, $currentKey, 'Tests are not ordered by discipline/test sequence'); } $lastKey = $currentKey; } } private function createOrderTestPatient(): int { $faker = Factory::create('id_ID'); $patientPayload = [ "PatientID" => "ORD" . $faker->numberBetween(1, 1000). $faker->numberBetween(1, 1000).$faker->numberBetween(1, 1000), "AlternatePID" => "DMY" . $faker->numberBetween(1, 1000). $faker->numberBetween(1, 1000).$faker->numberBetween(1, 1000), "Prefix" => $faker->title, "NameFirst" => "Order", "NameMiddle" => $faker->firstName, "NameMaiden" => $faker->firstName, "NameLast" => "Test", "Suffix" => "S.Kom", "NameAlias" => $faker->userName, "Sex" => $faker->numberBetween(5, 6), "PlaceOfBirth" => $faker->city, "Birthdate" => "1990-01-01", "ZIP" => $faker->postcode, "Street_1" => $faker->streetAddress, "Street_2" => "RT " . $faker->numberBetween(1, 10) . " RW " . $faker->numberBetween(1, 10), "Street_3" => "Blok " . $faker->numberBetween(1, 20), "City" => $faker->city, "Province" => $faker->state, "EmailAddress1" => "A" . $faker->numberBetween(1, 1000). $faker->numberBetween(1, 1000).$faker->numberBetween(1, 1000).'@gmail.com', "EmailAddress2" => "B" . $faker->numberBetween(1, 1000). $faker->numberBetween(1, 1000).$faker->numberBetween(1, 1000).'@gmail.com', "Phone" => $faker->numerify('08##########'), "MobilePhone" => $faker->numerify('08##########'), "Race" => (string) $faker->numberBetween(175, 205), "Country" => (string) $faker->numberBetween(221, 469), "MaritalStatus" => (string) $faker->numberBetween(8, 15), "Religion" => (string) $faker->numberBetween(206, 212), "Ethnic" => (string) $faker->numberBetween(213, 220), "Citizenship" => "WNI", "DeathIndicator" => (string) $faker->numberBetween(16, 17), "LinkTo" => (string) $faker->numberBetween(2, 3), "Custodian" => 1, "PatIdt" => [ "IdentifierType" => "KTP", "Identifier" => $faker->nik() ?? $faker->numerify('################') ], "PatAtt" => [ [ "Address" => "/api/upload/" . $faker->uuid . ".jpg" ] ], "PatCom" => $faker->sentence, ]; if ($patientPayload['DeathIndicator'] == '16') { $patientPayload['DeathDateTime'] = $faker->date('Y-m-d H:i:s'); } else { $patientPayload['DeathDateTime'] = null; } $patientModel = new \App\Models\Patient\PatientModel(); $internalPID = $patientModel->createPatient($patientPayload); $this->assertNotNull($internalPID, 'Failed to create test patient. Response: ' . print_r($patientPayload, true)); return $internalPID; } private function createOrderWithTests(int $internalPID, array $testSiteIDs, string $priority = 'R'): string { $payload = [ 'InternalPID' => $internalPID, 'Priority' => $priority, 'Tests' => array_map(fn ($testSiteID) => ['TestSiteID' => $testSiteID], $testSiteIDs), ]; $result = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload); $result->assertStatus(201); $body = json_decode($result->getBody(), true); $this->assertEquals('success', $body['status']); $orderID = $body['data']['OrderID'] ?? null; $this->assertNotNull($orderID, 'Order creation response is missing OrderID'); return $orderID; } private function collectTestSiteIDs(int $count = 2): array { $response = $this->call('get', 'api/test'); $body = json_decode($response->getBody(), true); $availableTests = $body['data'] ?? []; if (count($availableTests) < $count) { $this->markTestSkipped('Need at least ' . $count . ' tests to validate ordering.'); } $ids = array_values(array_filter(array_column($availableTests, 'TestSiteID'))); return array_slice($ids, 0, $count); } private function buildTestSortKey(array $test): string { $discipline = $test['Discipline'] ?? []; $discSeqScr = $this->normalizeSequenceValue($discipline['SeqScr'] ?? null); $discSeqRpt = $this->normalizeSequenceValue($discipline['SeqRpt'] ?? null); $testSeqScr = $this->normalizeSequenceValue($test['SeqScr'] ?? null); $testSeqRpt = $this->normalizeSequenceValue($test['SeqRpt'] ?? null); $resultID = isset($test['ResultID']) ? (int)$test['ResultID'] : 0; return sprintf('%06d-%06d-%06d-%06d-%010d', $discSeqScr, $discSeqRpt, $testSeqScr, $testSeqRpt, $resultID); } private function normalizeSequenceValue($value): int { if (is_numeric($value)) { return (int)$value; } return 999999; } }