This commit is contained in:
mikael-zakaria 2026-01-08 08:59:25 +07:00
commit c8e18ed283
7 changed files with 94 additions and 425 deletions

View File

@ -230,6 +230,13 @@ $routes->group('api', function ($routes) {
// Specimen // Specimen
$routes->group('specimen', function ($routes) { $routes->group('specimen', function ($routes) {
// Container aliases - 'container' and 'containerdef' both point to ContainerDefController
$routes->group('container', function ($routes) {
$routes->get('/', 'Specimen\ContainerDefController::index');
$routes->get('(:num)', 'Specimen\ContainerDefController::show/$1');
$routes->post('/', 'Specimen\ContainerDefController::create');
$routes->patch('/', 'Specimen\ContainerDefController::update');
});
$routes->group('containerdef', function ($routes) { $routes->group('containerdef', function ($routes) {
$routes->get('/', 'Specimen\ContainerDefController::index'); $routes->get('/', 'Specimen\ContainerDefController::index');
$routes->get('(:num)', 'Specimen\ContainerDefController::show/$1'); $routes->get('(:num)', 'Specimen\ContainerDefController::show/$1');

View File

@ -0,0 +1,55 @@
<?php
namespace App\Database\Seeds;
use CodeIgniter\Database\Seeder;
use CodeIgniter\HTTP\CURLRequest;
class AreaGeoSeeder extends Seeder
{
/**
* API configuration for fetching zones data
*/
protected string $apiUrl = 'https://your-api-domain.com/api/zones';
public function run()
{
// Fetch data from external API
$options = [
'baseURI' => $this->apiUrl,
'timeout' => 30,
];
$client = new CURLRequest($options);
$response = $client->get('');
if ($response->getStatusCode() !== 200) {
echo "Failed to fetch data from API. Status: " . $response->getStatusCode() . "\n";
return;
}
$externalData = $response->getJSON(true);
if (empty($externalData)) {
echo "No data found from API.\n";
return;
}
// Prepare data for insertion (exclude AreaGeoID to allow auto-increment)
$data = [];
foreach ($externalData as $row) {
$data[] = [
'AreaCode' => $row['zonecode'] ?? null,
'Class' => $row['zoneclass'] ?? null,
'AreaName' => str_replace('_', ' ', $row['zonename'] ?? ''),
'Parent' => $row['parentzoneid'] ?? null,
];
}
// Insert into local database
$this->db->table('areageo')->insertBatch($data);
echo "Successfully seeded " . count($data) . " area geo records.\n";
}
}

View File

@ -4,14 +4,17 @@ namespace App\Database\Seeds;
use CodeIgniter\Database\Seeder; use CodeIgniter\Database\Seeder;
class DBSeeder extends Seeder { class DBSeeder extends Seeder
public function run() { {
public function run()
{
$this->call('ValueSetSeeder'); $this->call('ValueSetSeeder');
$this->call('ValueSetCountrySeeder'); $this->call('ValueSetCountrySeeder');
$this->call('OrganizationSeeder'); $this->call('OrganizationSeeder');
$this->call('CounterSeeder'); $this->call('CounterSeeder');
$this->call('ContactSeeder'); $this->call('ContactSeeder');
$this->call('LocationSeeder'); $this->call('LocationSeeder');
$this->call('AreaGeoSeeder');
$this->call('SpecimenSeeder'); $this->call('SpecimenSeeder');
$this->call('TestSeeder'); $this->call('TestSeeder');
$this->call('PatientSeeder'); $this->call('PatientSeeder');

View File

@ -4,17 +4,18 @@ namespace App\Database\Seeds;
use CodeIgniter\Database\Seeder; use CodeIgniter\Database\Seeder;
class DummySeeder extends Seeder { class DummySeeder extends Seeder
public function run() { {
public function run()
{
$now = date('Y-m-d H:i:s'); $now = date('Y-m-d H:i:s');
// users // users
// Password: 'password' for all users (bcrypt hash) // Password: 'password' for all users (bcrypt hash)
$passwordHash = password_hash('password', PASSWORD_BCRYPT); $passwordHash = password_hash('123', PASSWORD_BCRYPT);
$data = [ $data = [
['id' => 1, 'role_id' => 1, 'username' => 'zaka', 'password' => $passwordHash], ['id' => 1, 'role_id' => 1, 'username' => 'lisfse', 'password' => $passwordHash],
['id' => 2, 'role_id' => 1, 'username' => 'tes' , 'password' => $passwordHash], ['id' => 2, 'role_id' => 1, 'username' => 'tes', 'password' => $passwordHash]
['id' => 3, 'role_id' => 1, 'username' => 'tes2', 'password' => $passwordHash],
]; ];
$this->db->table('users')->insertBatch($data); $this->db->table('users')->insertBatch($data);

View File

@ -74,30 +74,7 @@ class PatientSeeder extends Seeder
echo "Valueset data seeded.\n"; echo "Valueset data seeded.\n";
// ======================================== // ========================================
// 2. AREAGEO - Province & City // 2. PATIENT - Main patient data
// ========================================
$areageos = [
['AreaGeoID' => 1, 'AreaCode' => '31', 'Class' => 1, 'AreaName' => 'DKI Jakarta', 'Parent' => null],
['AreaGeoID' => 2, 'AreaCode' => '3101', 'Class' => 2, 'AreaName' => 'Jakarta Pusat', 'Parent' => 1],
['AreaGeoID' => 3, 'AreaCode' => '3102', 'Class' => 2, 'AreaName' => 'Jakarta Utara', 'Parent' => 1],
['AreaGeoID' => 4, 'AreaCode' => '3103', 'Class' => 2, 'AreaName' => 'Jakarta Barat', 'Parent' => 1],
['AreaGeoID' => 5, 'AreaCode' => '3104', 'Class' => 2, 'AreaName' => 'Jakarta Selatan', 'Parent' => 1],
['AreaGeoID' => 6, 'AreaCode' => '3105', 'Class' => 2, 'AreaName' => 'Jakarta Timur', 'Parent' => 1],
['AreaGeoID' => 7, 'AreaCode' => '32', 'Class' => 1, 'AreaName' => 'Jawa Barat', 'Parent' => null],
['AreaGeoID' => 8, 'AreaCode' => '3201', 'Class' => 2, 'AreaName' => 'Bandung', 'Parent' => 7],
['AreaGeoID' => 9, 'AreaCode' => '3202', 'Class' => 2, 'AreaName' => 'Bogor', 'Parent' => 7],
];
foreach ($areageos as $area) {
$exists = $this->db->table('areageo')->where('AreaGeoID', $area['AreaGeoID'])->get()->getRow();
if (!$exists) {
$this->db->table('areageo')->insert($area);
}
}
echo "AreaGeo data seeded.\n";
// ========================================
// 3. PATIENT - Main patient data
// ======================================== // ========================================
$patients = [ $patients = [
[ [
@ -111,14 +88,14 @@ class PatientSeeder extends Seeder
'NameLast' => 'Patient', 'NameLast' => 'Patient',
'Suffix' => 'S.Kom', 'Suffix' => 'S.Kom',
'NameAlias' => 'DummyTest', 'NameAlias' => 'DummyTest',
'Gender' => 5, // Male 'Gender' => 5,
'PlaceOfBirth' => 'Jakarta', 'PlaceOfBirth' => 'Jakarta',
'Birthdate' => '1990-05-15', 'Birthdate' => '1990-05-15',
'Street_1' => 'Jl. Sudirman No. 123', 'Street_1' => 'Jl. Sudirman No. 123',
'Street_2' => 'RT 01 RW 02', 'Street_2' => 'RT 01 RW 02',
'Street_3' => 'Kelurahan Menteng', 'Street_3' => 'Kelurahan Menteng',
'City' => '2', // Jakarta Pusat 'City' => '2',
'Province' => '1', // DKI Jakarta 'Province' => '1',
'ZIP' => '10110', 'ZIP' => '10110',
'EmailAddress1' => 'dummy1@test.com', 'EmailAddress1' => 'dummy1@test.com',
'EmailAddress2' => 'dummy1alt@test.com', 'EmailAddress2' => 'dummy1alt@test.com',
@ -126,13 +103,13 @@ class PatientSeeder extends Seeder
'MobilePhone' => '081234567890', 'MobilePhone' => '081234567890',
'Custodian' => null, 'Custodian' => null,
'AccountNumber' => null, 'AccountNumber' => null,
'Country' => 221, // Indonesia 'Country' => 221,
'Race' => 175, // Asian 'Race' => 175,
'MaritalStatus' => 9, // Married 'MaritalStatus' => 9,
'Religion' => 206, // Islam 'Religion' => 206,
'Ethnic' => 213, // Javanese 'Ethnic' => 213,
'Citizenship' => 'WNI', 'Citizenship' => 'WNI',
'DeathIndicator' => 17, // Alive 'DeathIndicator' => 17,
'TimeOfDeath' => null, 'TimeOfDeath' => null,
'LinkTo' => null, 'LinkTo' => null,
'CreateDate' => $now, 'CreateDate' => $now,

View File

@ -1,374 +0,0 @@
<?php
namespace Tests\Feature\TestDef;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
use App\Models\Test\TestDefSiteModel;
use App\Models\Test\TestDefTechModel;
use App\Models\Test\TestDefCalModel;
use App\Models\Test\TestDefGrpModel;
/**
* Integration tests for Test Definitions API
*
* Tests the CRUD operations for test definitions through the API
*/
class TestDefSiteTest extends CIUnitTestCase
{
use DatabaseTestTrait;
protected $seed = 'App\Database\Seeds\TestSeeder';
protected $refresh = true;
/**
* Test listing all tests returns success response
*/
public function testIndexReturnsSuccessResponse(): void
{
$result = $this->withHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json'
])->get('api/tests');
$result->assertStatus(200);
$result->assertJSONExact([
'status' => 'success',
'message' => 'Data fetched successfully',
'data' => $result->getJSON(true)['data']
]);
}
/**
* Test listing all tests returns array
*/
public function testIndexReturnsArray(): void
{
$result = $this->withHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json'
])->get('api/tests');
$result->assertStatus(200);
$response = $result->getJSON(true);
$this->assertIsArray($response['data']);
}
/**
* Test index contains test type information
*/
public function testIndexContainsTypeInformation(): void
{
$result = $this->withHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json'
])->get('api/tests');
$result->assertStatus(200);
$response = $result->getJSON(true);
if (!empty($response['data'])) {
$test = $response['data'][0];
$this->assertArrayHasKey('TypeCode', $test);
$this->assertArrayHasKey('TypeName', $test);
}
}
/**
* Test filtering by test type
*/
public function testIndexFiltersByTestType(): void
{
// Test filtering by TEST type (VID = 1)
$result = $this->withHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json'
])->get('api/tests?TestType=1');
$result->assertStatus(200);
$response = $result->getJSON(true);
foreach ($response['data'] as $test) {
$this->assertEquals('TEST', $test['TypeCode']);
}
}
/**
* Test filtering by keyword
*/
public function testIndexFiltersByKeyword(): void
{
$result = $this->withHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json'
])->get('api/tests?TestSiteName=HB');
$result->assertStatus(200);
$response = $result->getJSON(true);
if (!empty($response['data'])) {
foreach ($response['data'] as $test) {
$this->assertStringContainsString('HB', $test['TestSiteName']);
}
}
}
/**
* Test showing single test returns success
*/
public function testShowReturnsSuccess(): void
{
// Get a test ID from the seeder data
$model = new TestDefSiteModel();
$test = $model->first();
if ($test) {
$result = $this->withHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json'
])->get("api/tests/{$test['TestSiteID']}");
$result->assertStatus(200);
$response = $result->getJSON(true);
$this->assertArrayHasKey('data', $response);
} else {
$this->markTestSkipped('No test data available');
}
}
/**
* Test showing single test includes type-specific details for TEST type
*/
public function testShowIncludesTechDetailsForTestType(): void
{
$model = new TestDefSiteModel();
$test = $model->first();
if ($test && $test['TypeCode'] === 'TEST') {
$result = $this->withHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json'
])->get("api/tests/{$test['TestSiteID']}");
$result->assertStatus(200);
$response = $result->getJSON(true);
$this->assertArrayHasKey('testdeftech', $response['data']);
} else {
$this->markTestSkipped('No TEST type data available');
}
}
/**
* Test showing single test includes type-specific details for CALC type
*/
public function testShowIncludesCalcDetailsForCalcType(): void
{
$model = new TestDefSiteModel();
$test = $model->first();
if ($test && $test['TypeCode'] === 'CALC') {
$result = $this->withHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json'
])->get("api/tests/{$test['TestSiteID']}");
$result->assertStatus(200);
$response = $result->getJSON(true);
$this->assertArrayHasKey('testdefcal', $response['data']);
} else {
$this->markTestSkipped('No CALC type data available');
}
}
/**
* Test showing single test includes type-specific details for GROUP type
*/
public function testShowIncludesGrpDetailsForGroupType(): void
{
$model = new TestDefSiteModel();
$test = $model->first();
if ($test && $test['TypeCode'] === 'GROUP') {
$result = $this->withHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json'
])->get("api/tests/{$test['TestSiteID']}");
$result->assertStatus(200);
$response = $result->getJSON(true);
$this->assertArrayHasKey('testdefgrp', $response['data']);
} else {
$this->markTestSkipped('No GROUP type data available');
}
}
/**
* Test creating a new test
*/
public function testCreateTest(): void
{
$testData = [
'SiteID' => 1,
'TestSiteCode' => 'NEWTEST',
'TestSiteName' => 'New Test',
'TestType' => 1, // TEST type
'Description' => 'Test description',
'SeqScr' => 100,
'SeqRpt' => 100,
'VisibleScr' => 1,
'VisibleRpt' => 1,
'CountStat' => 1
];
$result = $this->withHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json'
])->post('api/tests', $testData);
$result->assertStatus(201);
$response = $result->getJSON(true);
$this->assertArrayHasKey('data', $response);
$this->assertArrayHasKey('TestSiteId', $response['data']);
}
/**
* Test creating test with validation error (missing required fields)
*/
public function testCreateTestValidationError(): void
{
$testData = [
'SiteID' => 1
// Missing required fields
];
$result = $this->withHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json'
])->post('api/tests', $testData);
$result->assertStatus(400);
}
/**
* Test updating a test
*/
public function testUpdateTest(): void
{
$model = new TestDefSiteModel();
$test = $model->first();
if ($test) {
$updateData = [
'TestSiteID' => $test['TestSiteID'],
'TestSiteName' => 'Updated Test Name',
'Description' => 'Updated description'
];
$result = $this->withHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json'
])->patch('api/tests', $updateData);
$result->assertStatus(200);
$response = $result->getJSON(true);
$this->assertEquals('success', $response['status']);
} else {
$this->markTestSkipped('No test data available');
}
}
/**
* Test deleting a test (soft delete)
*/
public function testDeleteTest(): void
{
$model = new TestDefSiteModel();
$test = $model->first();
if ($test) {
$deleteData = [
'TestSiteID' => $test['TestSiteID']
];
$result = $this->withHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json'
])->delete('api/tests', $deleteData);
$result->assertStatus(200);
$response = $result->getJSON(true);
$this->assertEquals('success', $response['status']);
$this->assertArrayHasKey('EndDate', $response['data']);
} else {
$this->markTestSkipped('No test data available');
}
}
/**
* Test getting non-existent test returns empty data
*/
public function testShowNonExistentTest(): void
{
$result = $this->withHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json'
])->get('api/tests/999999');
$result->assertStatus(200);
$response = $result->getJSON(true);
$this->assertNull($response['data']);
}
/**
* Test test types are correctly mapped from valueset
*/
public function testTestTypesAreMapped(): void
{
$model = new TestDefSiteModel();
$tests = $model->findAll();
$validTypes = ['TEST', 'PARAM', 'CALC', 'GROUP', 'TITLE'];
foreach ($tests as $test) {
if (isset($test['TypeCode'])) {
$this->assertContains($test['TypeCode'], $validTypes);
}
}
}
/**
* Test filtering by visible on screen
*/
public function testIndexFiltersByVisibleScr(): void
{
$result = $this->withHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json'
])->get('api/tests?VisibleScr=1');
$result->assertStatus(200);
$response = $result->getJSON(true);
foreach ($response['data'] as $test) {
$this->assertEquals(1, $test['VisibleScr']);
}
}
/**
* Test all test types from seeder are present
*/
public function testAllTestTypesArePresent(): void
{
$model = new TestDefSiteModel();
$tests = $model->findAll();
$typeCodes = array_column($tests, 'TypeCode');
$uniqueTypes = array_unique($typeCodes);
// Check that we have at least TEST and PARAM types from seeder
$this->assertContains('TEST', $uniqueTypes);
$this->assertContains('PARAM', $uniqueTypes);
$this->assertContains('CALC', $uniqueTypes);
$this->assertContains('GROUP', $uniqueTypes);
}
}