testModel = new TestDefSiteModel(); } public function testCreateTechnicalWithoutReferenceOrTestMap(): void { foreach (['TEST', 'PARAM'] as $type) { $this->assertTechnicalCreated($type); } } public function testCreateTechnicalCanAcceptNullSiteAndNumericFields(): void { $payload = $this->buildTechnicalPayload('TEST', [ 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'Decimal' => null, 'Factor' => 2.5, 'ReqQty' => 1.75, 'ReqQtyUnit' => 'uL', ]); $payload['SiteID'] = null; $payload['SeqScr'] = null; $payload['SeqRpt'] = null; $payload['refnum'] = [ [ 'NumRefType' => 'NMRC', 'RangeType' => 'REF', 'Sex' => '2', 'LowSign' => 'GE', 'Low' => 10, 'HighSign' => 'LE', 'High' => 20, 'AgeStart' => null, 'AgeEnd' => null, 'Flag' => 'N', 'Interpretation' => 'Nullable range', 'SpcType' => 'GEN', ], ]; $response = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload); $response->assertStatus(201); $testSiteId = json_decode($response->getJSON(), true)['data']['TestSiteId']; $show = $this->call('get', $this->endpoint . '/' . $testSiteId); $show->assertStatus(200); $data = json_decode($show->getJSON(), true)['data']; $this->assertNull($data['SiteID']); $this->assertNull($data['SeqScr']); $this->assertNull($data['SeqRpt']); $this->assertNull($data['Decimal']); $this->assertSame(2.5, (float) $data['Factor']); $this->assertSame(1.75, (float) $data['ReqQty']); $this->assertNull($data['refnum'][0]['AgeStart']); $this->assertNull($data['refnum'][0]['AgeEnd']); } public function testCreateTechnicalWithNumericReference(): void { $refnum = $this->buildRefNumEntries('NMRC', true); foreach (['TEST', 'PARAM'] as $type) { $this->assertTechnicalCreated($type, [ 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', ], $refnum); } } public function testCreateTechnicalNumericReferenceReturnsAgeInDays(): void { $payload = $this->buildTechnicalPayload('TEST', [ 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', ]); $payload['refnum'] = [ [ 'NumRefType' => 'NMRC', 'RangeType' => 'REF', 'Sex' => '2', 'LowSign' => 'GE', 'Low' => 10, 'HighSign' => 'LE', 'High' => 20, 'AgeStart' => 6570, 'AgeEnd' => 36135, 'Flag' => 'N', 'Interpretation' => 'Adult range in days', 'SpcType' => 'GEN', ], ]; $response = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload); $response->assertStatus(201); $testSiteId = json_decode($response->getJSON(), true)['data']['TestSiteId']; $show = $this->call('get', $this->endpoint . '/' . $testSiteId); $show->assertStatus(200); $data = json_decode($show->getJSON(), true)['data']; $this->assertSame(6570, (int) $data['refnum'][0]['AgeStart']); $this->assertSame(36135, (int) $data['refnum'][0]['AgeEnd']); } public function testNumericRefRangeNotesPersistAfterCreate(): void { $notes = 'Auto note ' . uniqid(); $refnum = $this->buildRefNumEntries('NMRC', false); $refnum[0]['Notes'] = $notes; $payload = $this->buildTechnicalPayload('TEST', [ 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', ]); $payload['refnum'] = $refnum; $response = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload); $response->assertStatus(201); $json = json_decode($response->getJSON(), true); $testSiteId = $json['data']['TestSiteId']; $show = $this->call('get', $this->endpoint . '/' . $testSiteId); $show->assertStatus(200); $showJson = json_decode($show->getJSON(), true); $this->assertSame($notes, $showJson['data']['refnum'][0]['Notes']); } public function testNumericRefRangeSpcTypePersistAfterCreate(): void { $spcType = 'EDTA'; $refnum = $this->buildRefNumEntries('NMRC', false); $refnum[0]['SpcType'] = $spcType; $payload = $this->buildTechnicalPayload('TEST', [ 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', ]); $payload['refnum'] = $refnum; $response = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload); $response->assertStatus(201); $json = json_decode($response->getJSON(), true); $testSiteId = $json['data']['TestSiteId']; $show = $this->call('get', $this->endpoint . '/' . $testSiteId); $show->assertStatus(200); $showJson = json_decode($show->getJSON(), true); $this->assertSame($spcType, $showJson['data']['refnum'][0]['SpcType']); } public function testCreateTechnicalWithThresholdReference(): void { $refnum = $this->buildRefNumEntries('THOLD', true); foreach (['TEST', 'PARAM'] as $type) { $this->assertTechnicalCreated($type, [ 'ResultType' => 'NMRIC', 'RefType' => 'THOLD', ], $refnum); } } public function testCreateTechnicalWithTextReference(): void { $reftxt = $this->buildRefTxtEntries('TEXT', true); foreach (['TEST', 'PARAM'] as $type) { $this->assertTechnicalCreated($type, [ 'ResultType' => 'TEXT', 'RefType' => 'TEXT', ], null, $reftxt); } } public function testCreateTechnicalWithValuesetReference(): void { $reftxt = $this->buildRefTxtEntries('VSET', true); foreach (['TEST', 'PARAM'] as $type) { $this->assertTechnicalCreated($type, [ 'ResultType' => 'VSET', 'RefType' => 'VSET', ], null, $reftxt); } } public function testCreateTechnicalWithNumericReferenceAndTestMap(): void { $refnum = $this->buildRefNumEntries('NMRC', true); $testmap = $this->buildTestMap(true, true); foreach (['TEST', 'PARAM'] as $type) { $this->assertTechnicalCreated($type, [ 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', ], $refnum, null, $testmap); } } public function testCreateTechnicalWithThresholdReferenceAndTestMap(): void { $refnum = $this->buildRefNumEntries('THOLD', true); $testmap = $this->buildTestMap(true, true); foreach (['TEST', 'PARAM'] as $type) { $this->assertTechnicalCreated($type, [ 'ResultType' => 'NMRIC', 'RefType' => 'THOLD', ], $refnum, null, $testmap); } } public function testCreateTechnicalWithTextReferenceAndTestMap(): void { $reftxt = $this->buildRefTxtEntries('TEXT', true); $testmap = $this->buildTestMap(true, true); foreach (['TEST', 'PARAM'] as $type) { $this->assertTechnicalCreated($type, [ 'ResultType' => 'TEXT', 'RefType' => 'TEXT', ], null, $reftxt, $testmap); } } public function testCreateTechnicalWithValuesetReferenceAndTestMap(): void { $reftxt = $this->buildRefTxtEntries('VSET', true); $testmap = $this->buildTestMap(true, true); foreach (['TEST', 'PARAM'] as $type) { $this->assertTechnicalCreated($type, [ 'ResultType' => 'VSET', 'RefType' => 'VSET', ], null, $reftxt, $testmap); } } public function testCreateTechnicalValuesetWithoutReferenceButWithMap(): void { $testmap = $this->buildTestMap(false, true); foreach (['TEST', 'PARAM'] as $type) { $this->assertTechnicalCreated($type, [ 'ResultType' => 'VSET', 'RefType' => 'VSET', ], null, null, $testmap); } } public function testCreateCalculatedTestWithoutReferenceOrMap(): void { $this->assertCalculatedCreated(false); } public function testCreateCalculatedTestWithReferenceAndTestMap(): void { $refnum = $this->buildRefNumEntries('NMRC', true); $testmap = $this->buildTestMap(true, true); $members = $this->resolveMemberIds(['GLU', 'CREA']); $this->assertCalculatedCreated(true, $refnum, $testmap, $members); } public function testCreateGroupTestWithMembers(): void { $members = $this->resolveMemberIds(['GLU', 'CREA']); $testmap = $this->buildTestMap(true, true); $payload = $this->buildGroupPayload($members, $testmap); $response = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload); $response->assertStatus(201); $response->assertJSONFragment([ 'status' => 'created', 'message' => 'Test created successfully', ]); $json = json_decode($response->getJSON(), true); $this->assertArrayHasKey('data', $json); $this->assertArrayHasKey('TestSiteId', $json['data']); $this->assertIsInt($json['data']['TestSiteId']); } private function assertTechnicalCreated( string $type, array $details = [], ?array $refnum = null, ?array $reftxt = null, ?array $testmap = null ): void { $payload = $this->buildTechnicalPayload($type, $details); if ($refnum !== null) { $payload['refnum'] = $refnum; } if ($reftxt !== null) { $payload['reftxt'] = $reftxt; } if ($testmap !== null) { $payload['testmap'] = $testmap; } $response = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload); $response->assertStatus(201); $response->assertJSONFragment([ 'status' => 'created', 'message' => 'Test created successfully', ]); $json = json_decode($response->getJSON(), true); $this->assertArrayHasKey('data', $json); $this->assertArrayHasKey('TestSiteId', $json['data']); $this->assertIsInt($json['data']['TestSiteId']); } private function assertCalculatedCreated( bool $withDetails, ?array $refnum = null, ?array $testmap = null, array $members = [] ): void { $payload = $this->buildCalculatedPayload($members); if ($withDetails && $refnum !== null) { $payload['refnum'] = $refnum; } if ($withDetails && $testmap !== null) { $payload['testmap'] = $testmap; } $response = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload); $response->assertStatus(201); $response->assertJSONFragment([ 'status' => 'created', 'message' => 'Test created successfully', ]); $json = json_decode($response->getJSON(), true); $this->assertArrayHasKey('data', $json); $this->assertArrayHasKey('TestSiteId', $json['data']); $this->assertIsInt($json['data']['TestSiteId']); } private function buildTechnicalPayload(string $testType, array $details = []): array { $payload = [ 'SiteID' => self::SITE_ID, 'TestSiteCode' => $this->generateTestCode($testType), 'TestSiteName' => 'Auto ' . strtoupper($testType), 'TestType' => $testType, 'SeqScr' => 900, 'SeqRpt' => 900, 'isVisibleScr' => 1, 'isVisibleRpt' => 1, 'isCountStat' => 1, ]; $payload['details'] = $this->normalizeDetails($details); return $payload; } private function buildCalculatedPayload(array $members = []): array { $payload = [ 'SiteID' => self::SITE_ID, 'TestSiteCode' => $this->generateTestCode('CALC'), 'TestSiteName' => 'Auto CALC', 'TestType' => 'CALC', 'SeqScr' => 1000, 'SeqRpt' => 1000, 'isVisibleScr' => 1, 'isVisibleRpt' => 1, 'isCountStat' => 0, 'details' => [ 'DisciplineID' => 2, 'DepartmentID' => 2, 'FormulaCode' => '{GLU} + {CREA}', 'members' => array_map(fn ($id) => ['TestSiteID' => $id], $members), ], ]; return $payload; } private function buildGroupPayload(array $members, array $testmap): array { return [ 'SiteID' => self::SITE_ID, 'TestSiteCode' => $this->generateTestCode('PANEL'), 'TestSiteName' => 'Auto Group', 'TestType' => 'GROUP', 'SeqScr' => 300, 'SeqRpt' => 300, 'isVisibleScr' => 1, 'isVisibleRpt' => 1, 'isCountStat' => 1, 'details' => [ 'members' => array_map(fn ($id) => ['TestSiteID' => $id], $members), ], 'testmap' => $testmap, ]; } private function normalizeDetails(array $details): array { $normalized = [ 'DisciplineID' => $details['DisciplineID'] ?? 2, 'DepartmentID' => $details['DepartmentID'] ?? 2, 'Method' => $details['Method'] ?? 'Automated test', 'Unit1' => $details['Unit1'] ?? 'mg/dL', 'Decimal' => array_key_exists('Decimal', $details) ? $details['Decimal'] : 0, ]; foreach (['ResultType', 'RefType', 'FormulaCode', 'members', 'ExpectedTAT', 'Factor', 'ReqQty', 'ReqQtyUnit', 'Unit2', 'VSet', 'CollReq'] as $key) { if (array_key_exists($key, $details)) { $normalized[$key] = $details[$key]; } } return $normalized; } private function buildRefNumEntries(string $numRefType, bool $multiple = false): array { $rangeType = $numRefType === 'THOLD' ? 'PANIC' : 'REF'; $entries = [ [ 'NumRefType' => $numRefType, 'RangeType' => $rangeType, 'Sex' => '2', 'LowSign' => 'GE', 'Low' => 10, 'HighSign' => 'LE', 'High' => $numRefType === 'THOLD' ? 40 : 20, 'AgeStart' => 0, 'AgeEnd' => 43800, 'Flag' => 'N', 'Interpretation' => 'Normal range', 'SpcType' => 'GEN', 'Notes' => 'Default numeric range note', ], ]; if ($multiple) { $entries[] = [ 'NumRefType' => $numRefType, 'RangeType' => $rangeType, 'Sex' => '1', 'LowSign' => '>', 'Low' => 5, 'HighSign' => '<', 'High' => $numRefType === 'THOLD' ? 50 : 15, 'AgeStart' => 0, 'AgeEnd' => 36135, 'Flag' => 'N', 'Interpretation' => 'Alternate range', 'SpcType' => 'GEN', 'Notes' => 'Alternate numeric range note', ]; } return $entries; } private function buildRefTxtEntries(string $txtRefType, bool $multiple = false): array { $entries = [ [ 'SpcType' => 'GEN', 'TxtRefType' => $txtRefType, 'Sex' => '2', 'AgeStart' => 0, 'AgeEnd' => 43800, 'RefTxt' => $txtRefType === 'VSET' ? 'NORM=Normal;ABN=Abnormal' : 'NORM=Normal', 'Flag' => 'N', ], ]; if ($multiple) { $entries[] = [ 'SpcType' => 'GEN', 'TxtRefType' => $txtRefType, 'Sex' => '1', 'AgeStart' => 0, 'AgeEnd' => 43800, 'RefTxt' => $txtRefType === 'VSET' ? 'HIGH=High;LOW=Low' : 'ABN=Abnormal', 'Flag' => 'N', ]; } return $entries; } private function buildTestMap(bool $multipleMaps = false, bool $multipleDetails = false): array { $map = [ [ 'HostType' => 'SITE', 'HostID' => '1', 'ClientType' => 'WST', 'ClientID' => '1', 'details' => [ [ 'HostTestCode' => 'GLU', 'HostTestName' => 'Glucose', 'ConDefID' => 1, 'ClientTestCode' => 'GLU_C', 'ClientTestName' => 'Glucose Client', ], ], ], ]; if ($multipleDetails) { $map[0]['details'][] = [ 'HostTestCode' => 'CREA', 'HostTestName' => 'Creatinine', 'ConDefID' => 2, 'ClientTestCode' => 'CREA_C', 'ClientTestName' => 'Creatinine Client', ]; } if ($multipleMaps) { $map[] = [ 'HostType' => 'WST', 'HostID' => '3', 'ClientType' => 'INST', 'ClientID' => '2', 'details' => [ [ 'HostTestCode' => 'HB', 'HostTestName' => 'Hemoglobin', 'ConDefID' => 3, 'ClientTestCode' => 'HB_C', 'ClientTestName' => 'Hemoglobin Client', ], ], ]; } return $map; } private function generateTestCode(string $prefix): string { $clean = strtoupper(substr($prefix, 0, 3)); $suffix = strtoupper(substr(md5((string) microtime(true) . random_int(0, 9999)), 0, 6)); return substr($clean . $suffix, 0, 10); } private function resolveMemberIds(array $codes): array { $ids = []; foreach ($codes as $code) { $row = $this->testModel->where('TestSiteCode', $code)->where('EndDate IS NULL')->first(); $this->assertNotEmpty($row, "Seeded test code {$code} not found"); $ids[] = (int) $row['TestSiteID']; } return $ids; } }