diff --git a/app/Controllers/Test/TestMapController.php b/app/Controllers/Test/TestMapController.php index c97e98d..f0ade22 100755 --- a/app/Controllers/Test/TestMapController.php +++ b/app/Controllers/Test/TestMapController.php @@ -48,6 +48,7 @@ class TestMapController extends BaseController { public function index() { $rows = $this->model->getUniqueGroupings(); + $rows = $this->applyIndexFilters($rows); if (empty($rows)) { return $this->respond([ 'status' => 'success', 'message' => "no Data.", 'data' => [] ], 200); } $rows = ValueSet::transformLabels($rows, [ @@ -223,6 +224,47 @@ class TestMapController extends BaseController { return $row; } + private function applyIndexFilters(array $rows): array + { + $hostFilter = trim((string) $this->request->getGet('host')); + $clientFilter = trim((string) $this->request->getGet('client')); + + if ($hostFilter === '' && $clientFilter === '') { + return $rows; + } + + return array_values(array_filter($rows, function (array $row) use ($hostFilter, $clientFilter): bool { + if ($hostFilter !== '' && !$this->matchesSearch($row, 'Host', $hostFilter)) { + return false; + } + + if ($clientFilter !== '' && !$this->matchesSearch($row, 'Client', $clientFilter)) { + return false; + } + + return true; + })); + } + + private function matchesSearch(array $row, string $prefix, string $filter): bool + { + $haystacks = [ + (string) ($row[$prefix . 'Name'] ?? ''), + (string) ($row[$prefix . 'ID'] ?? ''), + (string) ($row[$prefix . 'Type'] ?? ''), + ]; + + $needle = strtolower($filter); + + foreach ($haystacks as $value) { + if ($value !== '' && str_contains(strtolower($value), $needle)) { + return true; + } + } + + return false; + } + private function resolveDetailOperations(mixed $detailsPayload): ?array { if ($detailsPayload === null) { diff --git a/app/Models/Test/TestMapDetailModel.php b/app/Models/Test/TestMapDetailModel.php index 1e0177a..3a21efd 100755 --- a/app/Models/Test/TestMapDetailModel.php +++ b/app/Models/Test/TestMapDetailModel.php @@ -24,14 +24,16 @@ class TestMapDetailModel extends BaseModel { protected $useSoftDeletes = true; protected $deletedField = "EndDate"; - /** - * Get all details for a test map - */ - public function getDetailsByTestMap($testMapID) { - return $this->where('TestMapID', $testMapID) - ->where('EndDate IS NULL') - ->findAll(); - } + /** + * Get all details for a test map + */ + public function getDetailsByTestMap($testMapID) { + return $this->select('testmapdetail.*, containerdef.ConName AS ContainerLabel') + ->join('containerdef', 'containerdef.ConDefID = testmapdetail.ConDefID', 'left') + ->where('testmapdetail.TestMapID', $testMapID) + ->where('testmapdetail.EndDate IS NULL') + ->findAll(); + } /** * Get test map detail by host test code diff --git a/public/api-docs.bundled.yaml b/public/api-docs.bundled.yaml index 693f213..e24ed27 100755 --- a/public/api-docs.bundled.yaml +++ b/public/api-docs.bundled.yaml @@ -4045,6 +4045,19 @@ paths: summary: List all test mappings security: - bearerAuth: [] + parameters: + - name: host + in: query + required: false + schema: + type: string + description: Filter by host name, type, or ID + - name: client + in: query + required: false + schema: + type: string + description: Filter by client name, type, or ID responses: '200': description: List of test mappings @@ -8713,6 +8726,9 @@ components: ConDefID: type: integer description: Container definition ID + ContainerLabel: + type: string + description: Container definition name ClientTestCode: type: string description: Test code in client system diff --git a/public/components/schemas/tests.yaml b/public/components/schemas/tests.yaml index dd7de5d..20ed8c2 100755 --- a/public/components/schemas/tests.yaml +++ b/public/components/schemas/tests.yaml @@ -595,12 +595,15 @@ TestMapDetail: HostTestName: type: string description: Test name in host system - ConDefID: - type: integer - description: Container definition ID - ClientTestCode: - type: string - description: Test code in client system + ConDefID: + type: integer + description: Container definition ID + ContainerLabel: + type: string + description: Container definition name + ClientTestCode: + type: string + description: Test code in client system ClientTestName: type: string description: Test name in client system diff --git a/public/paths/testmap.yaml b/public/paths/testmap.yaml index 17f7b49..88f2989 100755 --- a/public/paths/testmap.yaml +++ b/public/paths/testmap.yaml @@ -1,10 +1,23 @@ /api/test/testmap: - get: + get: tags: [Test] - summary: List all test mappings - security: - - bearerAuth: [] - responses: + summary: List all test mappings + security: + - bearerAuth: [] + parameters: + - name: host + in: query + required: false + schema: + type: string + description: Filter by host name, type, or ID + - name: client + in: query + required: false + schema: + type: string + description: Filter by client name, type, or ID + responses: '200': description: List of test mappings content: diff --git a/tests/feature/Test/TestMapPatchTest.php b/tests/feature/Test/TestMapPatchTest.php index af1967d..9d2c86c 100755 --- a/tests/feature/Test/TestMapPatchTest.php +++ b/tests/feature/Test/TestMapPatchTest.php @@ -1,56 +1,56 @@ - 'localhost', - 'aud' => 'localhost', - 'iat' => time(), - 'nbf' => time(), - 'exp' => time() + 3600, - 'uid' => 1, - 'email' => 'admin@admin.com', - ]; - $this->token = JWT::encode($payload, $key, 'HS256'); - } - - private function authHeaders(): array - { - return ['Cookie' => 'token=' . $this->token]; - } - + 'localhost', + 'aud' => 'localhost', + 'iat' => time(), + 'nbf' => time(), + 'exp' => time() + 3600, + 'uid' => 1, + 'email' => 'admin@admin.com', + ]; + $this->token = JWT::encode($payload, $key, 'HS256'); + } + + private function authHeaders(): array + { + return ['Cookie' => 'token=' . $this->token]; + } + private function createTestMap(array $data = []): array { - $payload = array_merge([ - 'HostType' => 'SITE', - 'HostID' => 1, - 'ClientType' => 'SITE', - 'ClientID' => 1, - ], $data); - - $response = $this->withHeaders($this->authHeaders()) - ->withBodyFormat('json') - ->call('post', $this->endpoint, $payload); - - $response->assertStatus(201); - $created = json_decode($response->getJSON(), true); - $id = $created['data']; - + $payload = array_merge([ + 'HostType' => 'SITE', + 'HostID' => 1, + 'ClientType' => 'SITE', + 'ClientID' => 1, + ], $data); + + $response = $this->withHeaders($this->authHeaders()) + ->withBodyFormat('json') + ->call('post', $this->endpoint, $payload); + + $response->assertStatus(201); + $created = json_decode($response->getJSON(), true); + $id = $created['data']; + $show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}"); $show->assertStatus(200); $showData = json_decode($show->getJSON(), true)['data']; @@ -60,201 +60,280 @@ class TestMapPatchTest extends CIUnitTestCase return $showData; } - public function testPartialUpdateTestMapSuccess() - { - $testMap = $this->createTestMap(); - $id = $testMap['TestMapID']; - - $patch = $this->withHeaders($this->authHeaders()) - ->withBodyFormat('json') - ->call('patch', "{$this->endpoint}/{$id}", ['ClientType' => 'WST']); - - $patch->assertStatus(200); - $patchData = json_decode($patch->getJSON(), true); - $this->assertEquals('success', $patchData['status']); - - $show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}"); - $show->assertStatus(200); - $showData = json_decode($show->getJSON(), true)['data']; - - $this->assertEquals('WST', $showData['ClientType']); - $this->assertEquals((string) $testMap['HostID'], (string) $showData['HostID']); - } - - public function testPartialUpdateTestMapNotFound() - { - $patch = $this->withHeaders($this->authHeaders()) - ->withBodyFormat('json') - ->call('patch', "{$this->endpoint}/999999", ['ClientType' => 'WST']); - - $patch->assertStatus(404); - } - - public function testPartialUpdateTestMapInvalidId() - { - $patch = $this->withHeaders($this->authHeaders()) - ->withBodyFormat('json') - ->call('patch', "{$this->endpoint}/invalid", ['ClientType' => 'WST']); - - $patch->assertStatus(400); - } - - public function testPartialUpdateTestMapEmptyPayload() - { - $testMap = $this->createTestMap(); - $id = $testMap['TestMapID']; - - $patch = $this->withHeaders($this->authHeaders()) - ->withBodyFormat('json') - ->call('patch', "{$this->endpoint}/{$id}", []); - - $patch->assertStatus(400); - } - - public function testPartialUpdateTestMapSingleField() - { - $testMap = $this->createTestMap(); - $id = $testMap['TestMapID']; - - $patch = $this->withHeaders($this->authHeaders()) - ->withBodyFormat('json') - ->call('patch', "{$this->endpoint}/{$id}", ['HostID' => 2]); - - $patch->assertStatus(200); - $showData = json_decode($this->withHeaders($this->authHeaders()) - ->call('get', "{$this->endpoint}/{$id}") - ->getJSON(), true)['data']; - - $this->assertEquals('2', (string) $showData['HostID']); - $this->assertEquals((string) $testMap['ClientID'], (string) $showData['ClientID']); - } - - public function testCreateTestMapWithDetails() - { - $details = [ - [ - 'HostTestCode' => 'HB', - 'HostTestName' => 'Hemoglobin', - 'ClientTestCode' => '2', - 'ClientTestName' => 'Hemoglobin', - ], - [ - 'HostTestCode' => 'HCT', - 'HostTestName' => 'Hematocrit', - 'ClientTestCode' => '3', - 'ClientTestName' => 'Hematocrit', - ], - ]; - - $testMap = $this->createTestMap([ - 'HostType' => 'SITE', - 'HostID' => 1, - 'ClientType' => 'SITE', - 'ClientID' => 2, - 'details' => $details, - ]); - - $this->assertCount(2, $testMap['details']); - $this->assertEquals('2', $testMap['details'][0]['ClientTestCode']); - } - - public function testPatchTestMapDetailOperations() - { - $initialDetails = [ - [ - 'HostTestCode' => 'HB', - 'HostTestName' => 'Hemoglobin', - 'ClientTestCode' => '2', - 'ClientTestName' => 'Hemoglobin', - ], - [ - 'HostTestCode' => 'HCT', - 'HostTestName' => 'Hematocrit', - 'ClientTestCode' => '3', - 'ClientTestName' => 'Hematocrit', - ], - ]; - - $testMap = $this->createTestMap([ - 'HostType' => 'SITE', - 'HostID' => 1, - 'ClientType' => 'SITE', - 'ClientID' => 1, - 'details' => $initialDetails, - ]); - - $existingDetails = $testMap['details']; - $editDetail = $existingDetails[0]; - $deleteDetail = $existingDetails[1]; - - $patch = $this->withHeaders($this->authHeaders()) - ->withBodyFormat('json') - ->call('patch', "{$this->endpoint}/{$testMap['TestMapID']}", [ - 'ClientType' => 'WST', - 'details' => [ - 'edited' => [ - [ - 'TestMapDetailID' => $editDetail['TestMapDetailID'], - 'ClientTestName' => 'Hemoglobin Updated', - ], - ], - 'created' => [ - [ - 'HostTestCode' => 'MCV', - 'HostTestName' => 'MCV', - 'ClientTestCode' => '4', - 'ClientTestName' => 'MCV', - ], - ], - 'deleted' => [$deleteDetail['TestMapDetailID']], - ], - ]); - - $patch->assertStatus(200); - $patchData = json_decode($patch->getJSON(), true); - $this->assertEquals('success', $patchData['status']); - - $show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$testMap['TestMapID']}"); - $show->assertStatus(200); - $showData = json_decode($show->getJSON(), true)['data']; - - $this->assertEquals('WST', $showData['ClientType']); - $this->assertCount(2, $showData['details']); - $detailIds = array_column($showData['details'], 'TestMapDetailID'); - $this->assertContains($editDetail['TestMapDetailID'], $detailIds); - $this->assertNotContains($deleteDetail['TestMapDetailID'], $detailIds); - - $updatedDetails = array_values(array_filter($showData['details'], static fn ($row) => $row['TestMapDetailID'] === $editDetail['TestMapDetailID'])); - $this->assertNotEmpty($updatedDetails); - $this->assertEquals('Hemoglobin Updated', $updatedDetails[0]['ClientTestName']); - } - - public function testDeleteTestMapRemovesDetails() + public function testIndexFiltersByHost(): void { $testMap = $this->createTestMap([ 'HostType' => 'SITE', 'HostID' => 2, 'ClientType' => 'SITE', + 'ClientID' => 1, + ]); + + $response = $this->withHeaders($this->authHeaders()) + ->call('get', $this->endpoint . '?host=2'); + + $response->assertStatus(200); + $rows = json_decode($response->getJSON(), true)['data']; + + $this->assertNotEmpty(array_values(array_filter($rows, static fn (array $row): bool => (int) $row['TestMapID'] === (int) $testMap['TestMapID']))); + + foreach ($rows as $row) { + $this->assertTrue( + str_contains(strtolower((string) ($row['HostID'] ?? '')), '2') + || str_contains(strtolower((string) ($row['HostName'] ?? '')), '2') + || str_contains(strtolower((string) ($row['HostType'] ?? '')), '2') + ); + } + } + + public function testIndexFiltersByClient(): void + { + $testMap = $this->createTestMap([ + 'HostType' => 'SITE', + 'HostID' => 1, + 'ClientType' => 'SITE', 'ClientID' => 3, + ]); + + $response = $this->withHeaders($this->authHeaders()) + ->call('get', $this->endpoint . '?client=3'); + + $response->assertStatus(200); + $rows = json_decode($response->getJSON(), true)['data']; + + $this->assertNotEmpty(array_values(array_filter($rows, static fn (array $row): bool => (int) $row['TestMapID'] === (int) $testMap['TestMapID']))); + + foreach ($rows as $row) { + $this->assertTrue( + str_contains(strtolower((string) ($row['ClientID'] ?? '')), '3') + || str_contains(strtolower((string) ($row['ClientName'] ?? '')), '3') + || str_contains(strtolower((string) ($row['ClientType'] ?? '')), '3') + ); + } + } + + public function testPartialUpdateTestMapSuccess() + { + $testMap = $this->createTestMap(); + $id = $testMap['TestMapID']; + + $patch = $this->withHeaders($this->authHeaders()) + ->withBodyFormat('json') + ->call('patch', "{$this->endpoint}/{$id}", ['ClientType' => 'WST']); + + $patch->assertStatus(200); + $patchData = json_decode($patch->getJSON(), true); + $this->assertEquals('success', $patchData['status']); + + $show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}"); + $show->assertStatus(200); + $showData = json_decode($show->getJSON(), true)['data']; + + $this->assertEquals('WST', $showData['ClientType']); + $this->assertEquals((string) $testMap['HostID'], (string) $showData['HostID']); + } + + public function testPartialUpdateTestMapNotFound() + { + $patch = $this->withHeaders($this->authHeaders()) + ->withBodyFormat('json') + ->call('patch', "{$this->endpoint}/999999", ['ClientType' => 'WST']); + + $patch->assertStatus(404); + } + + public function testPartialUpdateTestMapInvalidId() + { + $patch = $this->withHeaders($this->authHeaders()) + ->withBodyFormat('json') + ->call('patch', "{$this->endpoint}/invalid", ['ClientType' => 'WST']); + + $patch->assertStatus(400); + } + + public function testPartialUpdateTestMapEmptyPayload() + { + $testMap = $this->createTestMap(); + $id = $testMap['TestMapID']; + + $patch = $this->withHeaders($this->authHeaders()) + ->withBodyFormat('json') + ->call('patch', "{$this->endpoint}/{$id}", []); + + $patch->assertStatus(400); + } + + public function testPartialUpdateTestMapSingleField() + { + $testMap = $this->createTestMap(); + $id = $testMap['TestMapID']; + + $patch = $this->withHeaders($this->authHeaders()) + ->withBodyFormat('json') + ->call('patch', "{$this->endpoint}/{$id}", ['HostID' => 2]); + + $patch->assertStatus(200); + $showData = json_decode($this->withHeaders($this->authHeaders()) + ->call('get', "{$this->endpoint}/{$id}") + ->getJSON(), true)['data']; + + $this->assertEquals('2', (string) $showData['HostID']); + $this->assertEquals((string) $testMap['ClientID'], (string) $showData['ClientID']); + } + + public function testCreateTestMapWithDetails() + { + $details = [ + [ + 'HostTestCode' => 'HB', + 'HostTestName' => 'Hemoglobin', + 'ClientTestCode' => '2', + 'ClientTestName' => 'Hemoglobin', + ], + [ + 'HostTestCode' => 'HCT', + 'HostTestName' => 'Hematocrit', + 'ClientTestCode' => '3', + 'ClientTestName' => 'Hematocrit', + ], + ]; + + $testMap = $this->createTestMap([ + 'HostType' => 'SITE', + 'HostID' => 1, + 'ClientType' => 'SITE', + 'ClientID' => 2, + 'details' => $details, + ]); + + $this->assertCount(2, $testMap['details']); + $this->assertEquals('2', $testMap['details'][0]['ClientTestCode']); + } + + public function testPatchTestMapDetailOperations() + { + $initialDetails = [ + [ + 'HostTestCode' => 'HB', + 'HostTestName' => 'Hemoglobin', + 'ClientTestCode' => '2', + 'ClientTestName' => 'Hemoglobin', + ], + [ + 'HostTestCode' => 'HCT', + 'HostTestName' => 'Hematocrit', + 'ClientTestCode' => '3', + 'ClientTestName' => 'Hematocrit', + ], + ]; + + $testMap = $this->createTestMap([ + 'HostType' => 'SITE', + 'HostID' => 1, + 'ClientType' => 'SITE', + 'ClientID' => 1, + 'details' => $initialDetails, + ]); + + $existingDetails = $testMap['details']; + $editDetail = $existingDetails[0]; + $deleteDetail = $existingDetails[1]; + + $patch = $this->withHeaders($this->authHeaders()) + ->withBodyFormat('json') + ->call('patch', "{$this->endpoint}/{$testMap['TestMapID']}", [ + 'ClientType' => 'WST', + 'details' => [ + 'edited' => [ + [ + 'TestMapDetailID' => $editDetail['TestMapDetailID'], + 'ClientTestName' => 'Hemoglobin Updated', + ], + ], + 'created' => [ + [ + 'HostTestCode' => 'MCV', + 'HostTestName' => 'MCV', + 'ClientTestCode' => '4', + 'ClientTestName' => 'MCV', + ], + ], + 'deleted' => [$deleteDetail['TestMapDetailID']], + ], + ]); + + $patch->assertStatus(200); + $patchData = json_decode($patch->getJSON(), true); + $this->assertEquals('success', $patchData['status']); + + $show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$testMap['TestMapID']}"); + $show->assertStatus(200); + $showData = json_decode($show->getJSON(), true)['data']; + + $this->assertEquals('WST', $showData['ClientType']); + $this->assertCount(2, $showData['details']); + $detailIds = array_column($showData['details'], 'TestMapDetailID'); + $this->assertContains($editDetail['TestMapDetailID'], $detailIds); + $this->assertNotContains($deleteDetail['TestMapDetailID'], $detailIds); + + $updatedDetails = array_values(array_filter($showData['details'], static fn ($row) => $row['TestMapDetailID'] === $editDetail['TestMapDetailID'])); + $this->assertNotEmpty($updatedDetails); + $this->assertEquals('Hemoglobin Updated', $updatedDetails[0]['ClientTestName']); + } + + public function testShowTestMapIncludesContainerLabel(): void + { + $testMap = $this->createTestMap([ + 'HostType' => 'SITE', + 'HostID' => 1, + 'ClientType' => 'SITE', + 'ClientID' => 2, 'details' => [ [ - 'HostTestCode' => 'PLT', - 'HostTestName' => 'Platelet', - 'ClientTestCode' => '5', - 'ClientTestName' => 'Platelet', + 'HostTestCode' => 'HB', + 'HostTestName' => 'Hemoglobin', + 'ConDefID' => 1, + 'ClientTestCode' => '2', + 'ClientTestName' => 'Hemoglobin', ], ], ]); - $delete = $this->withHeaders($this->authHeaders()) - ->withBodyFormat('json') - ->call('delete', $this->endpoint, ['TestMapID' => $testMap['TestMapID']]); + $show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$testMap['TestMapID']}"); + $show->assertStatus(200); - $delete->assertStatus(200); - - $details = $this->withHeaders($this->authHeaders()) - ->call('get', "{$this->endpoint}/detail/by-testmap/{$testMap['TestMapID']}"); - $details->assertStatus(200); - $this->assertEquals([], json_decode($details->getJSON(), true)['data']); + $showData = json_decode($show->getJSON(), true)['data']; + $this->assertNotEmpty($showData['details']); + $this->assertArrayHasKey('ContainerLabel', $showData['details'][0]); + $this->assertNotEmpty($showData['details'][0]['ContainerLabel']); } -} + + public function testDeleteTestMapRemovesDetails() + { + $testMap = $this->createTestMap([ + 'HostType' => 'SITE', + 'HostID' => 2, + 'ClientType' => 'SITE', + 'ClientID' => 3, + 'details' => [ + [ + 'HostTestCode' => 'PLT', + 'HostTestName' => 'Platelet', + 'ClientTestCode' => '5', + 'ClientTestName' => 'Platelet', + ], + ], + ]); + + $delete = $this->withHeaders($this->authHeaders()) + ->withBodyFormat('json') + ->call('delete', $this->endpoint, ['TestMapID' => $testMap['TestMapID']]); + + $delete->assertStatus(200); + + $details = $this->withHeaders($this->authHeaders()) + ->call('get', "{$this->endpoint}/detail/by-testmap/{$testMap['TestMapID']}"); + $details->assertStatus(200); + $this->assertEquals([], json_decode($details->getJSON(), true)['data']); + } +}