MERGE_TEST_BUG_FIXES
This commit is contained in:
commit
19fe1bf3b1
@ -5,6 +5,7 @@ use App\Controllers\EmployeeServiceController;
|
||||
use App\Controllers\Jobs;
|
||||
use App\Controllers\MediAssistApiController;
|
||||
use App\Controllers\TicketController;
|
||||
use App\Helpers\JWTToken;
|
||||
use App\Helpers\MailHelper;
|
||||
use App\Helpers\sendMailNotification;
|
||||
use App\Models\AddImgModel;
|
||||
@ -2547,18 +2548,18 @@ class EmployeeRestController extends AdminController
|
||||
if ($this->request->is('get')) {
|
||||
|
||||
$client_id = $this->request->getGet('client_id') ?? null;
|
||||
|
||||
|
||||
$data['claim_status'] = $this->claimStatusModel
|
||||
->select('id, ticket_type, display_name as claim_status')
|
||||
->where('is_active', 1)
|
||||
->groupStart()
|
||||
->where('display_name IS NOT NULL')
|
||||
->orWhere('display_name <>', '')
|
||||
->groupEnd()
|
||||
->whereIn('ticket_type', [1,2,3,4])
|
||||
->groupBy('display_name')
|
||||
->findAll();
|
||||
|
||||
$data['claim_status'] = $this->claimStatusModel
|
||||
->select('id, ticket_type, display_name as claim_status')
|
||||
->where('is_active', 1)
|
||||
->groupStart()
|
||||
->where('display_name IS NOT NULL')
|
||||
->orWhere('display_name <>', '')
|
||||
->groupEnd()
|
||||
->whereIn('ticket_type', [1, 2, 3, 4])
|
||||
->groupBy('display_name')
|
||||
->findAll();
|
||||
|
||||
if (!empty($client_id)) {
|
||||
|
||||
@ -2566,7 +2567,7 @@ class EmployeeRestController extends AdminController
|
||||
$client_data = $this->clientModel->where('MD5(id)', $client_id)->first();
|
||||
$client_id = $client_data['id'] ?? null;
|
||||
}
|
||||
|
||||
|
||||
$client_policy_data = $this->clientPolicyModel
|
||||
->where('client_id', $client_id)
|
||||
->where('is_active', 1)
|
||||
@ -2575,35 +2576,30 @@ class EmployeeRestController extends AdminController
|
||||
|
||||
$data['ticket_type'] = [];
|
||||
$addedTypes = [];
|
||||
|
||||
|
||||
foreach ($client_policy_data as $value) {
|
||||
|
||||
if (in_array($value['policy_type_id'], [2,3,4,5]) && !in_array('1', $addedTypes)) {
|
||||
|
||||
if (in_array($value['policy_type_id'], [2, 3, 4, 5]) && !in_array('1', $addedTypes)) {
|
||||
$data['ticket_type'][] = ["ticket_type" => "1", "type_name" => "Claim-GMC"];
|
||||
$addedTypes[] = '1';
|
||||
|
||||
} elseif (in_array($value['policy_type_id'], [1]) && !in_array('2', $addedTypes)) {
|
||||
$data['ticket_type'][] = ["ticket_type" => "2", "type_name" => "Claim-GPA"];
|
||||
$addedTypes[] = '2';
|
||||
|
||||
} elseif (in_array($value['policy_type_id'], [6]) && !in_array('3', $addedTypes)) {
|
||||
$data['ticket_type'][] = ["ticket_type" => "3", "type_name" => "EDLI"];
|
||||
$addedTypes[] = '3';
|
||||
|
||||
} elseif (in_array($value['policy_type_id'], [7]) && !in_array('4', $addedTypes)) {
|
||||
$data['ticket_type'][] = ["ticket_type" => "4", "type_name" => "GTLI"];
|
||||
$addedTypes[] = '4';
|
||||
|
||||
} elseif (in_array($value['policy_type_id'], [72]) && !in_array('72', $addedTypes)) {
|
||||
$data['ticket_type'][] = ["ticket_type" => "72", "type_name" => "OPD"];
|
||||
$addedTypes[] = '72';
|
||||
}
|
||||
}
|
||||
|
||||
usort($data['ticket_type'], fn($a,$b) => $a['ticket_type'] <=> $b['ticket_type']);
|
||||
|
||||
usort($data['ticket_type'], fn($a, $b) => $a['ticket_type'] <=> $b['ticket_type']);
|
||||
} else {
|
||||
|
||||
|
||||
$data['ticket_type'] = [
|
||||
["ticket_type" => "1", "type_name" => "Claim-GMC"],
|
||||
["ticket_type" => "2", "type_name" => "Claim-GPA"],
|
||||
@ -2634,10 +2630,29 @@ class EmployeeRestController extends AdminController
|
||||
unset($search_data['client_branch_id']);
|
||||
$policy_number = isset($search_data['policy_no']) ? $search_data['policy_no'] : null;
|
||||
|
||||
// From JWT (ignore body active_policy_ids to prevent widening access)
|
||||
$active_policy_ids = JWTToken::getValueFromToken($this->request, 'allowed_active_policies', []);
|
||||
// print_rr($active_policy_ids); die;
|
||||
|
||||
log_message('error', 'claim search active_policy_ids: ' . json_encode($active_policy_ids, JSON_PRETTY_PRINT));
|
||||
if (! is_array($active_policy_ids)) {
|
||||
$active_policy_ids = [];
|
||||
}
|
||||
|
||||
if (empty($active_policy_ids)) {
|
||||
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => []], 200);
|
||||
}
|
||||
|
||||
$from_date = isset($search_data['from_date']) ? $search_data['from_date'] : null;
|
||||
$to_date = isset($search_data['to_date']) ? $search_data['to_date'] : null;
|
||||
$claim_status_id = isset($search_data['claim_status_id']) ? $search_data['claim_status_id'] : null;
|
||||
unset($search_data['from_date'], $search_data['to_date'], $search_data['claim_status_id'], $search_data['policy_no']);
|
||||
unset(
|
||||
$search_data['from_date'],
|
||||
$search_data['to_date'],
|
||||
$search_data['claim_status_id'],
|
||||
$search_data['policy_no'],
|
||||
$search_data['active_policy_ids']
|
||||
);
|
||||
|
||||
$where = [];
|
||||
|
||||
@ -2761,6 +2776,10 @@ class EmployeeRestController extends AdminController
|
||||
$builder->where('cp.policy_no', $policy_number);
|
||||
}
|
||||
|
||||
if (! empty($active_policy_ids)) {
|
||||
$builder->whereIn('cp.id', $active_policy_ids);
|
||||
}
|
||||
|
||||
$builder->orderBy('tm.id', 'DESC');
|
||||
|
||||
$data = $builder->get()->getResultArray();
|
||||
|
||||
@ -762,8 +762,65 @@ class RestAuthenticationController extends AdminController
|
||||
|
||||
if (!empty($HRAccessData) && isset($HRAccessData['allowed_modules'])) {
|
||||
|
||||
$decoded = json_decode($HRAccessData['allowed_modules'], true);
|
||||
$decoded = json_decode($HRAccessData['allowed_modules'], true) ?? [];
|
||||
$allowedActivePolicies = json_decode($HRAccessData['allowed_active_policies'] ?? '[]', true) ?? [];
|
||||
if (!is_array($allowedActivePolicies)) {
|
||||
$allowedActivePolicies = [];
|
||||
}
|
||||
|
||||
if (empty($allowedActivePolicies) && is_array($decoded)) {
|
||||
if (isset($decoded['post']) && is_array($decoded['post'])) {
|
||||
$decoded['post'] = array_values(array_filter(
|
||||
$decoded['post'],
|
||||
static fn($module) => (int) $module !== 4
|
||||
));
|
||||
} elseif (!isset($decoded['pre']) && !isset($decoded['post'])) {
|
||||
$decoded = array_values(array_filter(
|
||||
$decoded,
|
||||
static fn($module) => (int) $module !== 4
|
||||
));
|
||||
}
|
||||
$HRAccessData['allowed_modules'] = json_encode($decoded);
|
||||
}
|
||||
|
||||
$claimsSubMenu = [];
|
||||
$hasClaimsModule = false;
|
||||
if (is_array($decoded)) {
|
||||
if (isset($decoded['post']) && is_array($decoded['post'])) {
|
||||
$hasClaimsModule = in_array(4, array_map('intval', $decoded['post']), true);
|
||||
} elseif (!isset($decoded['pre']) && !isset($decoded['post'])) {
|
||||
$hasClaimsModule = in_array(4, array_map('intval', $decoded), true);
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasClaimsModule) {
|
||||
$claimsSubMenu[] = 'EB';
|
||||
|
||||
$hasNonEbPolicy = false;
|
||||
if (!empty($allowedActivePolicies)) {
|
||||
$db = \Config\Database::connect();
|
||||
$policyAllocgRows = $db->table('client_policy')
|
||||
->select('policy_type.allocg')
|
||||
->join('policy_type', 'policy_type.id = client_policy.policy_type_id', 'left')
|
||||
->whereIn('client_policy.id', $allowedActivePolicies)
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
foreach ($policyAllocgRows as $policyRow) {
|
||||
if (($policyRow['allocg'] ?? '') !== 'EB') {
|
||||
$hasNonEbPolicy = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasNonEbPolicy) {
|
||||
$claimsSubMenu[] = 'Non-EB';
|
||||
}
|
||||
}
|
||||
|
||||
$getAllhrData[$key]['allowed_modules'] = $decoded;
|
||||
$getAllhrData[$key]['claims_sub_menu'] = $claimsSubMenu;
|
||||
$HRAccessData['pre_client_id'] = md5($HRAccessData['pre_client_id']);
|
||||
$HRAccessData['post_client_id'] = md5($HRAccessData['post_client_id']);
|
||||
|
||||
@ -772,6 +829,7 @@ class RestAuthenticationController extends AdminController
|
||||
|
||||
} else {
|
||||
$getAllhrData[$key]['allowed_modules'] = [];
|
||||
$getAllhrData[$key]['claims_sub_menu'] = [];
|
||||
$getAllhrData[$key]['token'] = "";
|
||||
}
|
||||
|
||||
|
||||
@ -185,5 +185,67 @@ class JWTToken
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode Authorization Bearer JWT into an associative array.
|
||||
* Returns null when header is missing or token is invalid.
|
||||
*/
|
||||
public static function getDecodedTokenArray(?RequestInterface $request = null): ?array
|
||||
{
|
||||
$request = $request ?? \Config\Services::request();
|
||||
$authHeader = $request->getHeaderLine('Authorization');
|
||||
|
||||
if ($authHeader === '' || empty(env('JWT_SECRET'))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$result = self::validateJWT($authHeader);
|
||||
} catch (\Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (($result['status'] ?? false) !== true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return isset($result['decoded']) && is_array($result['decoded'])
|
||||
? $result['decoded']
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any claim value from the request Authorization JWT by key.
|
||||
*
|
||||
* Usage:
|
||||
* JWTToken::getValueFromToken($this->request, 'allowed_active_policies', []);
|
||||
* JWTToken::getValueFromToken($this->request, 'post_hr_id');
|
||||
*
|
||||
* JSON-encoded string claims (e.g. "[1,2]" / '{"a":1}') are decoded to arrays.
|
||||
*
|
||||
* @param mixed $default Returned when token/key is missing or invalid
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getValueFromToken(RequestInterface $request, string $key, $default = null)
|
||||
{
|
||||
$decoded = self::getDecodedTokenArray($request);
|
||||
if ($decoded === null || ! array_key_exists($key, $decoded)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$value = $decoded[$key];
|
||||
|
||||
if (is_string($value)) {
|
||||
$trimmed = trim($value);
|
||||
if ($trimmed !== '' && ($trimmed[0] === '{' || $trimmed[0] === '[')) {
|
||||
$parsed = json_decode($trimmed, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
return $parsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
793
tests/smoke_claims_search.php
Normal file
793
tests/smoke_claims_search.php
Normal file
@ -0,0 +1,793 @@
|
||||
<?php
|
||||
/**
|
||||
* Deep smoke test: EmployeeRestController::claimsSearch
|
||||
*
|
||||
* Covers GET (filter masters) and POST (search) behaviour against the live DB.
|
||||
*
|
||||
* Run:
|
||||
* php tests/smoke_claims_search.php
|
||||
* php tests/smoke_claims_search.php [client_id]
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
ob_start();
|
||||
|
||||
define('FCPATH', __DIR__ . '/../public/');
|
||||
chdir(FCPATH);
|
||||
|
||||
require FCPATH . '../app/Config/Paths.php';
|
||||
$paths = new Config\Paths();
|
||||
require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php';
|
||||
require_once SYSTEMPATH . 'Config/DotEnv.php';
|
||||
(new CodeIgniter\Config\DotEnv(ROOTPATH))->load();
|
||||
|
||||
defined('ENVIRONMENT') || define('ENVIRONMENT', env('CI_ENVIRONMENT', 'development'));
|
||||
|
||||
$boot = APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php';
|
||||
if (is_file($boot)) {
|
||||
require_once $boot;
|
||||
}
|
||||
|
||||
// Smoke encode/decode needs a secret; use a local fallback if JWT_SECRET is unset.
|
||||
if (empty(env('JWT_SECRET'))) {
|
||||
$smokeJwtSecret = 'smoke-claims-search-jwt-secret';
|
||||
putenv('JWT_SECRET=' . $smokeJwtSecret);
|
||||
$_ENV['JWT_SECRET'] = $smokeJwtSecret;
|
||||
$_SERVER['JWT_SECRET'] = $smokeJwtSecret;
|
||||
}
|
||||
|
||||
use App\Controllers\EmployeeRestController;
|
||||
use App\Helpers\JWTToken;
|
||||
use CodeIgniter\HTTP\IncomingRequest;
|
||||
use CodeIgniter\HTTP\URI;
|
||||
use CodeIgniter\HTTP\UserAgent;
|
||||
use Config\Services;
|
||||
use Firebase\JWT\JWT;
|
||||
|
||||
$pass = 0;
|
||||
$fail = 0;
|
||||
$skip = 0;
|
||||
$results = [];
|
||||
$notes = [];
|
||||
|
||||
function ok(string $label, bool $cond, string $detail = ''): void
|
||||
{
|
||||
global $pass, $fail, $results;
|
||||
if ($cond) {
|
||||
$pass++;
|
||||
$results[] = '[PASS] ' . $label . ($detail !== '' ? " — {$detail}" : '');
|
||||
} else {
|
||||
$fail++;
|
||||
$results[] = '[FAIL] ' . $label . ($detail !== '' ? " — {$detail}" : '');
|
||||
}
|
||||
}
|
||||
|
||||
function skip(string $label, string $detail = ''): void
|
||||
{
|
||||
global $skip, $results;
|
||||
$skip++;
|
||||
$results[] = '[SKIP] ' . $label . ($detail !== '' ? " — {$detail}" : '');
|
||||
}
|
||||
|
||||
function note(string $msg): void
|
||||
{
|
||||
global $notes;
|
||||
$notes[] = $msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a HS512 JWT with allowed_active_policies (does not touch DB).
|
||||
*
|
||||
* @param list<int>|string|null $activePolicyIds
|
||||
*/
|
||||
function makeHrJwt($activePolicyIds): string
|
||||
{
|
||||
$claim = is_array($activePolicyIds)
|
||||
? json_encode(array_values($activePolicyIds))
|
||||
: $activePolicyIds;
|
||||
|
||||
$payload = [
|
||||
'id' => 1,
|
||||
'post_hr_id' => 1,
|
||||
'allowed_active_policies' => $claim,
|
||||
];
|
||||
|
||||
return JWT::encode($payload, (string) env('JWT_SECRET'), 'HS512');
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke claimsSearch with GET query or POST JSON body.
|
||||
*
|
||||
* @param list<int>|string|null|false $tokenPolicyIds false = no Authorization header
|
||||
* @return array{http:int,body:array,raw:string,error:?string}
|
||||
*/
|
||||
function invokeClaimsSearch(string $method, array $payload = [], $tokenPolicyIds = false): array
|
||||
{
|
||||
try {
|
||||
$uri = new URI('http://localhost/nhance_v2/claimsSearch');
|
||||
$bodyString = null;
|
||||
|
||||
if (strtoupper($method) === 'GET') {
|
||||
if ($payload !== []) {
|
||||
$uri->setQuery(http_build_query($payload));
|
||||
}
|
||||
} else {
|
||||
$bodyString = json_encode($payload, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
$request = new IncomingRequest(
|
||||
config('App'),
|
||||
$uri,
|
||||
$bodyString,
|
||||
new UserAgent()
|
||||
);
|
||||
$request->setMethod(strtolower($method));
|
||||
|
||||
if (strtoupper($method) === 'GET') {
|
||||
$request->setGlobal('get', $payload);
|
||||
} else {
|
||||
$request->setHeader('Content-Type', 'application/json');
|
||||
$request->setGlobal('post', []);
|
||||
}
|
||||
|
||||
if ($tokenPolicyIds !== false) {
|
||||
$request->setHeader('Authorization', 'Bearer ' . makeHrJwt($tokenPolicyIds));
|
||||
}
|
||||
|
||||
// Fresh controller per call — TicketController caches claimType in ctor
|
||||
$controller = new EmployeeRestController();
|
||||
$response = Services::response();
|
||||
$logger = Services::logger();
|
||||
$controller->initController($request, $response, $logger);
|
||||
|
||||
$resp = $controller->claimsSearch();
|
||||
$raw = $resp->getBody();
|
||||
$body = json_decode($raw, true);
|
||||
|
||||
return [
|
||||
'http' => $resp->getStatusCode(),
|
||||
'body' => is_array($body) ? $body : [],
|
||||
'raw' => (string) $raw,
|
||||
'error' => null,
|
||||
];
|
||||
} catch (Throwable $e) {
|
||||
return [
|
||||
'http' => 500,
|
||||
'body' => [],
|
||||
'raw' => '',
|
||||
'error' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function ticketTypeIds(array $ticketTypes): array
|
||||
{
|
||||
return array_map('strval', array_column($ticketTypes, 'ticket_type'));
|
||||
}
|
||||
|
||||
// ─── Fixtures from live DB ───────────────────────────────────────────────────
|
||||
|
||||
$db = db_connect();
|
||||
|
||||
$cliClientId = isset($argv[1]) && ctype_digit((string) $argv[1]) ? (int) $argv[1] : null;
|
||||
|
||||
$sampleTicket = $db->query(
|
||||
"SELECT tm.id, tm.client_id, tm.client_policy_id, tm.claim_number, tm.claim_status_id,
|
||||
tm.ticket_type_id, tm.emp_code, tm.emp_name, tm.created_at, tm.is_active,
|
||||
cp.policy_no
|
||||
FROM ticket_master tm
|
||||
LEFT JOIN client_policy cp ON cp.id = tm.client_policy_id
|
||||
WHERE tm.is_active = 1 AND tm.client_id IS NOT NULL
|
||||
ORDER BY tm.id DESC
|
||||
LIMIT 1"
|
||||
)->getRowArray();
|
||||
|
||||
if ($cliClientId !== null) {
|
||||
$override = $db->query(
|
||||
"SELECT tm.id, tm.client_id, tm.client_policy_id, tm.claim_number, tm.claim_status_id,
|
||||
tm.ticket_type_id, tm.emp_code, tm.emp_name, tm.created_at, tm.is_active,
|
||||
cp.policy_no
|
||||
FROM ticket_master tm
|
||||
LEFT JOIN client_policy cp ON cp.id = tm.client_policy_id
|
||||
WHERE tm.is_active = 1 AND tm.client_id = ?
|
||||
ORDER BY tm.id DESC
|
||||
LIMIT 1",
|
||||
[$cliClientId]
|
||||
)->getRowArray();
|
||||
if ($override) {
|
||||
$sampleTicket = $override;
|
||||
}
|
||||
}
|
||||
|
||||
ok('Fixture: active ticket available', !empty($sampleTicket['id']), json_encode($sampleTicket ?: []));
|
||||
|
||||
$clientId = (int) ($sampleTicket['client_id'] ?? 0);
|
||||
$clientMd5 = $clientId > 0 ? md5((string) $clientId) : '';
|
||||
$policyNo = $sampleTicket['policy_no'] ?? null;
|
||||
$policyId = $sampleTicket['client_policy_id'] ?? null;
|
||||
$claimStatusId = $sampleTicket['claim_status_id'] ?? null;
|
||||
$ticketTypeId = $sampleTicket['ticket_type_id'] ?? null;
|
||||
$empCode = $sampleTicket['emp_code'] ?? null;
|
||||
$createdAt = $sampleTicket['created_at'] ?? null;
|
||||
|
||||
$policyTypes = $clientId > 0
|
||||
? $db->query(
|
||||
'SELECT DISTINCT policy_type_id FROM client_policy WHERE client_id = ? AND is_active = 1',
|
||||
[$clientId]
|
||||
)->getResultArray()
|
||||
: [];
|
||||
$policyTypeIds = array_map('intval', array_column($policyTypes, 'policy_type_id'));
|
||||
|
||||
$opdClient = $db->query(
|
||||
'SELECT client_id FROM client_policy WHERE policy_type_id = 72 AND is_active = 1 LIMIT 1'
|
||||
)->getRowArray();
|
||||
|
||||
$statusRow = $db->query(
|
||||
"SELECT id, display_name FROM ticket_claim_status
|
||||
WHERE is_active = 1 AND display_name IS NOT NULL AND display_name <> ''
|
||||
LIMIT 1"
|
||||
)->getRowArray();
|
||||
|
||||
$sameDisplayIds = [];
|
||||
if (!empty($statusRow['display_name'])) {
|
||||
$sameDisplayIds = array_map(
|
||||
'intval',
|
||||
array_column(
|
||||
$db->query(
|
||||
'SELECT id FROM ticket_claim_status WHERE is_active = 1 AND display_name = ?',
|
||||
[$statusRow['display_name']]
|
||||
)->getResultArray(),
|
||||
'id'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$inactiveTicket = $db->query(
|
||||
'SELECT id, client_id FROM ticket_master WHERE is_active = 0 AND client_id IS NOT NULL LIMIT 1'
|
||||
)->getRowArray();
|
||||
|
||||
$expectedRowKeys = [
|
||||
'id', 'ticket_type_id', 'original_status', 'status', 'cl_type', 'claim_no',
|
||||
'claim_status_id', 'emp_name', 'emp_code', 'client_name', 'ticket_created_date',
|
||||
'policy_no', 'policy_type', 'client_policy_no',
|
||||
];
|
||||
|
||||
echo "========== claimsSearch DEEP SMOKE ==========\n";
|
||||
echo 'client_id=' . $clientId . ' md5=' . $clientMd5 . "\n";
|
||||
echo 'sample_ticket_id=' . ($sampleTicket['id'] ?? 'n/a') . "\n";
|
||||
echo 'policy_types=' . json_encode($policyTypeIds) . "\n\n";
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// GET — filter masters
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
$getNoClient = invokeClaimsSearch('GET');
|
||||
$d = $getNoClient['body']['data'] ?? [];
|
||||
ok(
|
||||
'GET no client_id: envelope success/200',
|
||||
($getNoClient['body']['status'] ?? '') === 'success' && (int) ($getNoClient['body']['code'] ?? 0) === 200,
|
||||
json_encode(['status' => $getNoClient['body']['status'] ?? null, 'code' => $getNoClient['body']['code'] ?? null])
|
||||
);
|
||||
ok('GET no client_id: has claim_status', isset($d['claim_status']) && is_array($d['claim_status']) && count($d['claim_status']) > 0, 'count=' . count($d['claim_status'] ?? []));
|
||||
ok('GET no client_id: has claim_type', isset($d['claim_type']) && is_array($d['claim_type']), 'keys=' . json_encode(array_keys($d['claim_type'] ?? [])));
|
||||
ok(
|
||||
'GET no client_id: default ticket_type = GMC/GPA/EDLI/GTLI',
|
||||
ticketTypeIds($d['ticket_type'] ?? []) === ['1', '2', '3', '4'],
|
||||
json_encode($d['ticket_type'] ?? [])
|
||||
);
|
||||
ok(
|
||||
'GET no client_id: default ticket_type excludes OPD (72)',
|
||||
!in_array('72', ticketTypeIds($d['ticket_type'] ?? []), true),
|
||||
json_encode(ticketTypeIds($d['ticket_type'] ?? []))
|
||||
);
|
||||
|
||||
$statusSample = $d['claim_status'][0] ?? [];
|
||||
ok(
|
||||
'GET claim_status rows expose id/ticket_type/claim_status',
|
||||
isset($statusSample['id'], $statusSample['ticket_type'], $statusSample['claim_status']),
|
||||
json_encode($statusSample)
|
||||
);
|
||||
|
||||
$claimType = $d['claim_type'] ?? [];
|
||||
ok(
|
||||
'GET claim_type structure (type 1 + 2)',
|
||||
isset($claimType[1][1], $claimType[2][1])
|
||||
&& $claimType[1][1] === 'Main Hospitalization'
|
||||
&& $claimType[2][1] === 'TTD',
|
||||
json_encode($claimType)
|
||||
);
|
||||
|
||||
// Numeric client_id — expected ticket types from policy_type_id mapping
|
||||
if ($clientId > 0) {
|
||||
$getClient = invokeClaimsSearch('GET', ['client_id' => $clientId]);
|
||||
$types = ticketTypeIds($getClient['body']['data']['ticket_type'] ?? []);
|
||||
|
||||
$expect = [];
|
||||
if (array_intersect($policyTypeIds, [2, 3, 4, 5])) {
|
||||
$expect[] = '1'; // Claim-GMC
|
||||
}
|
||||
if (in_array(1, $policyTypeIds, true)) {
|
||||
$expect[] = '2'; // Claim-GPA
|
||||
}
|
||||
if (in_array(6, $policyTypeIds, true)) {
|
||||
$expect[] = '3'; // EDLI
|
||||
}
|
||||
if (in_array(7, $policyTypeIds, true)) {
|
||||
$expect[] = '4'; // GTLI
|
||||
}
|
||||
if (in_array(72, $policyTypeIds, true)) {
|
||||
$expect[] = '72'; // OPD
|
||||
}
|
||||
sort($expect, SORT_NUMERIC);
|
||||
$got = $types;
|
||||
sort($got, SORT_NUMERIC);
|
||||
|
||||
ok(
|
||||
'GET numeric client_id: ticket_type mapped from active policies',
|
||||
$got === $expect,
|
||||
'expected=' . json_encode($expect) . ' got=' . json_encode($types) . ' policy_types=' . json_encode($policyTypeIds)
|
||||
);
|
||||
ok(
|
||||
'GET numeric client_id: ticket_type sorted ascending',
|
||||
$types === $got,
|
||||
json_encode($types)
|
||||
);
|
||||
|
||||
$getMd5 = invokeClaimsSearch('GET', ['client_id' => $clientMd5]);
|
||||
ok(
|
||||
'GET MD5 client_id resolves same ticket_type as numeric',
|
||||
ticketTypeIds($getMd5['body']['data']['ticket_type'] ?? []) === $types,
|
||||
'md5_types=' . json_encode(ticketTypeIds($getMd5['body']['data']['ticket_type'] ?? []))
|
||||
);
|
||||
} else {
|
||||
skip('GET numeric/MD5 client_id mapping', 'no sample client');
|
||||
}
|
||||
|
||||
if (!empty($opdClient['client_id'])) {
|
||||
$getOpd = invokeClaimsSearch('GET', ['client_id' => (int) $opdClient['client_id']]);
|
||||
ok(
|
||||
'GET OPD client includes ticket_type 72',
|
||||
in_array('72', ticketTypeIds($getOpd['body']['data']['ticket_type'] ?? []), true),
|
||||
json_encode($getOpd['body']['data']['ticket_type'] ?? [])
|
||||
);
|
||||
} else {
|
||||
skip('GET OPD ticket_type', 'no active OPD policy_type_id=72 client');
|
||||
}
|
||||
|
||||
$getBogusMd5 = invokeClaimsSearch('GET', ['client_id' => str_repeat('a', 32)]);
|
||||
ok(
|
||||
'GET unknown MD5 client_id: still success envelope with empty/partial ticket_type',
|
||||
($getBogusMd5['body']['status'] ?? '') === 'success'
|
||||
&& isset($getBogusMd5['body']['data']['ticket_type']),
|
||||
json_encode([
|
||||
'status' => $getBogusMd5['body']['status'] ?? null,
|
||||
'ticket_type' => $getBogusMd5['body']['data']['ticket_type'] ?? null,
|
||||
])
|
||||
);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// POST — search
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
$postNoToken = invokeClaimsSearch('POST', ['client_id' => $clientId ?: 1], false);
|
||||
ok(
|
||||
'POST without JWT token: failed/404 empty',
|
||||
$postNoToken['http'] === 200
|
||||
&& ($postNoToken['body']['status'] ?? '') === 'failed'
|
||||
&& (int) ($postNoToken['body']['code'] ?? 0) === 404
|
||||
&& ($postNoToken['body']['data'] ?? null) === [],
|
||||
json_encode($postNoToken['body'])
|
||||
);
|
||||
|
||||
$postEmptyClaim = invokeClaimsSearch('POST', ['client_id' => $clientId ?: 1], []);
|
||||
ok(
|
||||
'POST JWT allowed_active_policies=[] → failed/404 empty',
|
||||
($postEmptyClaim['body']['status'] ?? '') === 'failed'
|
||||
&& (int) ($postEmptyClaim['body']['code'] ?? 0) === 404
|
||||
&& ($postEmptyClaim['body']['data'] ?? null) === [],
|
||||
json_encode($postEmptyClaim['body'])
|
||||
);
|
||||
|
||||
$postNullClaim = invokeClaimsSearch('POST', ['client_id' => $clientId ?: 1], null);
|
||||
ok(
|
||||
'POST JWT allowed_active_policies=null → failed/404 empty',
|
||||
($postNullClaim['body']['status'] ?? '') === 'failed'
|
||||
&& (int) ($postNullClaim['body']['code'] ?? 0) === 404
|
||||
&& ($postNullClaim['body']['data'] ?? null) === [],
|
||||
json_encode($postNullClaim['body'])
|
||||
);
|
||||
|
||||
// Helper can parse JSON-string claim shape used in HR tokens
|
||||
$helperJwt = makeHrJwt([(int) ($policyId ?: 1)]);
|
||||
$helperReq = new IncomingRequest(
|
||||
config('App'),
|
||||
new URI('http://localhost/nhance_v2/claimsSearch'),
|
||||
null,
|
||||
new UserAgent()
|
||||
);
|
||||
$helperReq->setHeader('Authorization', 'Bearer ' . $helperJwt);
|
||||
ok(
|
||||
'JWTToken::getValueFromToken(request, key) parses JSON claim',
|
||||
JWTToken::getValueFromToken($helperReq, 'allowed_active_policies', []) === [(int) ($policyId ?: 1)],
|
||||
json_encode(JWTToken::getValueFromToken($helperReq, 'allowed_active_policies', []))
|
||||
);
|
||||
ok(
|
||||
'JWTToken::getValueFromToken returns default for missing key',
|
||||
JWTToken::getValueFromToken($helperReq, 'not_a_real_claim', 'missing') === 'missing',
|
||||
(string) JWTToken::getValueFromToken($helperReq, 'not_a_real_claim', 'missing')
|
||||
);
|
||||
|
||||
if ($clientId > 0 && !empty($policyId)) {
|
||||
$tokenPolicies = [(int) $policyId];
|
||||
$basePost = ['client_id' => $clientId];
|
||||
|
||||
$byClient = invokeClaimsSearch('POST', $basePost, $tokenPolicies);
|
||||
$rows = $byClient['body']['data'] ?? [];
|
||||
$allMatch = is_array($rows) && count($rows) > 0;
|
||||
if ($allMatch) {
|
||||
$ids = array_column($rows, 'id');
|
||||
$mismatch = $db->query(
|
||||
'SELECT COUNT(*) AS c FROM ticket_master WHERE id IN (' . implode(',', array_map('intval', $ids)) . ') AND client_id <> ?',
|
||||
[$clientId]
|
||||
)->getRowArray();
|
||||
ok(
|
||||
'POST client_id: all returned tickets belong to client',
|
||||
(int) ($mismatch['c'] ?? 1) === 0,
|
||||
'rows=' . count($rows) . ' mismatches=' . ($mismatch['c'] ?? '?')
|
||||
);
|
||||
ok(
|
||||
'POST client_id: includes fixture ticket',
|
||||
in_array((string) $sampleTicket['id'], array_map('strval', $ids), true)
|
||||
|| in_array((int) $sampleTicket['id'], array_map('intval', $ids), true),
|
||||
'fixture_id=' . $sampleTicket['id']
|
||||
);
|
||||
|
||||
$missingKeys = [];
|
||||
foreach ($expectedRowKeys as $k) {
|
||||
if (!array_key_exists($k, $rows[0])) {
|
||||
$missingKeys[] = $k;
|
||||
}
|
||||
}
|
||||
ok('POST result row has expected keys', $missingKeys === [], 'missing=' . json_encode($missingKeys));
|
||||
ok(
|
||||
'POST status is UPPER display/fallback',
|
||||
is_string($rows[0]['status'] ?? null)
|
||||
&& ($rows[0]['status'] === '' || $rows[0]['status'] === strtoupper($rows[0]['status'])),
|
||||
'status=' . ($rows[0]['status'] ?? 'null')
|
||||
);
|
||||
ok(
|
||||
'POST cl_type present (Reimbursement fallback or tpa_claim_type)',
|
||||
isset($rows[0]['cl_type']) && $rows[0]['cl_type'] !== '',
|
||||
'cl_type=' . ($rows[0]['cl_type'] ?? 'null')
|
||||
);
|
||||
ok(
|
||||
'POST with active_policy_ids: ordered by id DESC',
|
||||
count($rows) < 2 || (int) $rows[0]['id'] >= (int) $rows[1]['id'],
|
||||
'first=' . ($rows[0]['id'] ?? '?') . ' second=' . ($rows[1]['id'] ?? 'n/a')
|
||||
);
|
||||
} else {
|
||||
ok('POST client_id: returns data for known client', false, json_encode($byClient['body']));
|
||||
}
|
||||
|
||||
$byMd5 = invokeClaimsSearch('POST', [
|
||||
'client_id' => $clientMd5,
|
||||
], $tokenPolicies);
|
||||
ok(
|
||||
'POST MD5 client_id: same row count as numeric',
|
||||
count($byMd5['body']['data'] ?? []) === count($rows),
|
||||
'numeric=' . count($rows) . ' md5=' . count($byMd5['body']['data'] ?? [])
|
||||
);
|
||||
|
||||
// Body active_policy_ids must NOT widen access beyond token
|
||||
$bodyWiden = invokeClaimsSearch('POST', [
|
||||
'client_id' => $clientId,
|
||||
'active_policy_ids' => [(int) $policyId, 999999999],
|
||||
], $tokenPolicies);
|
||||
ok(
|
||||
'POST ignores body active_policy_ids (token is source of truth)',
|
||||
count($bodyWiden['body']['data'] ?? []) === count($rows),
|
||||
'token_only=' . count($rows) . ' with_body_extra=' . count($bodyWiden['body']['data'] ?? [])
|
||||
);
|
||||
|
||||
// Date range — day of fixture created_at
|
||||
if (!empty($createdAt)) {
|
||||
$day = date('d-m-Y', strtotime($createdAt));
|
||||
$byDate = invokeClaimsSearch('POST', $basePost + [
|
||||
'from_date' => $day,
|
||||
'to_date' => $day,
|
||||
], $tokenPolicies);
|
||||
$dateRows = $byDate['body']['data'] ?? [];
|
||||
$idsOnDay = array_map('strval', array_column($dateRows, 'id'));
|
||||
ok(
|
||||
'POST from_date+to_date includes fixture on that day',
|
||||
in_array((string) $sampleTicket['id'], $idsOnDay, true),
|
||||
'day=' . $day . ' count=' . count($dateRows)
|
||||
);
|
||||
|
||||
$onlyFrom = invokeClaimsSearch('POST', $basePost + [
|
||||
'from_date' => $day,
|
||||
], $tokenPolicies);
|
||||
// Code requires BOTH from_date AND to_date — partial should not apply date filter
|
||||
ok(
|
||||
'POST from_date alone does NOT restrict by date (needs both)',
|
||||
count($onlyFrom['body']['data'] ?? []) >= count($dateRows),
|
||||
'with_both=' . count($dateRows) . ' from_only=' . count($onlyFrom['body']['data'] ?? [])
|
||||
);
|
||||
|
||||
$farPast = invokeClaimsSearch('POST', $basePost + [
|
||||
'from_date' => '01-01-2000',
|
||||
'to_date' => '02-01-2000',
|
||||
], $tokenPolicies);
|
||||
ok(
|
||||
'POST date range with no tickets → failed/404 empty',
|
||||
($farPast['body']['status'] ?? '') === 'failed'
|
||||
&& (int) ($farPast['body']['code'] ?? 0) === 404
|
||||
&& ($farPast['body']['data'] ?? null) === [],
|
||||
json_encode([
|
||||
'status' => $farPast['body']['status'] ?? null,
|
||||
'code' => $farPast['body']['code'] ?? null,
|
||||
'data' => $farPast['body']['data'] ?? null,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($ticketTypeId)) {
|
||||
$byType = invokeClaimsSearch('POST', $basePost + [
|
||||
'ticket_type_id' => $ticketTypeId,
|
||||
], $tokenPolicies);
|
||||
$typeRows = $byType['body']['data'] ?? [];
|
||||
$badType = 0;
|
||||
foreach ($typeRows as $r) {
|
||||
if ((string) ($r['ticket_type_id'] ?? '') !== (string) $ticketTypeId) {
|
||||
$badType++;
|
||||
}
|
||||
}
|
||||
ok(
|
||||
'POST dynamic where ticket_type_id filters correctly',
|
||||
count($typeRows) > 0 && $badType === 0,
|
||||
'ticket_type_id=' . $ticketTypeId . ' rows=' . count($typeRows) . ' bad=' . $badType
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($empCode)) {
|
||||
$byEmp = invokeClaimsSearch('POST', $basePost + [
|
||||
'emp_code' => $empCode,
|
||||
], $tokenPolicies);
|
||||
$empRows = $byEmp['body']['data'] ?? [];
|
||||
$badEmp = 0;
|
||||
foreach ($empRows as $r) {
|
||||
if ((string) ($r['emp_code'] ?? '') !== (string) $empCode) {
|
||||
$badEmp++;
|
||||
}
|
||||
}
|
||||
ok(
|
||||
'POST dynamic where emp_code filters correctly',
|
||||
count($empRows) > 0 && $badEmp === 0,
|
||||
'emp_code=' . $empCode . ' rows=' . count($empRows) . ' bad=' . $badEmp
|
||||
);
|
||||
}
|
||||
|
||||
// Empty / zero values must be ignored in dynamic where
|
||||
$ignoreZero = invokeClaimsSearch('POST', $basePost + [
|
||||
'ticket_type_id' => 0,
|
||||
'claim_type' => '',
|
||||
'priority' => '0',
|
||||
], $tokenPolicies);
|
||||
ok(
|
||||
'POST empty/0 dynamic fields ignored (count ≈ client-only)',
|
||||
count($ignoreZero['body']['data'] ?? []) === count($rows),
|
||||
'client_only=' . count($rows) . ' with_empties=' . count($ignoreZero['body']['data'] ?? [])
|
||||
);
|
||||
|
||||
// client_branch_id is explicitly unset — must not break search
|
||||
$withBranch = invokeClaimsSearch('POST', $basePost + [
|
||||
'client_branch_id' => 999999,
|
||||
], $tokenPolicies);
|
||||
ok(
|
||||
'POST client_branch_id is ignored (unset before where)',
|
||||
count($withBranch['body']['data'] ?? []) === count($rows),
|
||||
'without=' . count($rows) . ' with_branch=' . count($withBranch['body']['data'] ?? [])
|
||||
);
|
||||
|
||||
if (!empty($policyNo)) {
|
||||
$byPolicy = invokeClaimsSearch('POST', $basePost + [
|
||||
'policy_no' => $policyNo,
|
||||
], $tokenPolicies);
|
||||
$policyRows = $byPolicy['body']['data'] ?? [];
|
||||
$badPol = 0;
|
||||
foreach ($policyRows as $r) {
|
||||
if (($r['client_policy_no'] ?? null) !== null
|
||||
&& (string) $r['client_policy_no'] !== (string) $policyNo
|
||||
&& (string) ($r['policy_no'] ?? '') !== (string) $policyNo
|
||||
) {
|
||||
if ((string) ($r['client_policy_no'] ?? '') !== (string) $policyNo) {
|
||||
$badPol++;
|
||||
}
|
||||
}
|
||||
}
|
||||
ok(
|
||||
'POST policy_no filters via client_policy.policy_no',
|
||||
count($policyRows) > 0 && $badPol === 0,
|
||||
'policy_no=' . $policyNo . ' rows=' . count($policyRows) . ' bad=' . $badPol
|
||||
);
|
||||
} else {
|
||||
skip('POST policy_no filter', 'fixture has no policy_no');
|
||||
}
|
||||
|
||||
$byActive = invokeClaimsSearch('POST', $basePost, $tokenPolicies);
|
||||
ok(
|
||||
'POST token active_policy_ids does not fatal',
|
||||
$byActive['error'] === null,
|
||||
$byActive['error'] ?? 'ok'
|
||||
);
|
||||
$activeRows = $byActive['body']['data'] ?? [];
|
||||
$badActive = 0;
|
||||
foreach ($activeRows as $r) {
|
||||
if ((string) ($r['client_policy_id'] ?? '') !== (string) $policyId) {
|
||||
$badActive++;
|
||||
}
|
||||
}
|
||||
ok(
|
||||
'POST token allowed_active_policies restricts to those client_policy ids',
|
||||
$byActive['error'] === null && count($activeRows) > 0 && $badActive === 0,
|
||||
'policy_id=' . $policyId . ' rows=' . count($activeRows) . ' bad=' . $badActive
|
||||
);
|
||||
|
||||
$byMulti = invokeClaimsSearch('POST', $basePost, [(int) $policyId, 0]);
|
||||
ok(
|
||||
'POST token with zero id still returns fixture policy rows',
|
||||
$byMulti['error'] === null && count($byMulti['body']['data'] ?? []) >= count($activeRows),
|
||||
'single=' . count($activeRows) . ' multi=' . count($byMulti['body']['data'] ?? [])
|
||||
);
|
||||
|
||||
if (!empty($claimStatusId) || !empty($statusRow['id'])) {
|
||||
$statusFilterId = (int) ($statusRow['id'] ?? $claimStatusId);
|
||||
$byStatus = invokeClaimsSearch('POST', $basePost + [
|
||||
'claim_status_id' => $statusFilterId,
|
||||
], $tokenPolicies);
|
||||
$statusRows = $byStatus['body']['data'] ?? [];
|
||||
|
||||
$allowed = $sameDisplayIds ?: [(int) $statusFilterId];
|
||||
$badSt = 0;
|
||||
foreach ($statusRows as $r) {
|
||||
if (!in_array((int) ($r['claim_status_id'] ?? 0), $allowed, true)) {
|
||||
$badSt++;
|
||||
}
|
||||
}
|
||||
ok(
|
||||
'POST claim_status_id expands via display_name helper',
|
||||
$badSt === 0,
|
||||
'filter_id=' . $statusFilterId
|
||||
. ' allowed=' . json_encode($allowed)
|
||||
. ' rows=' . count($statusRows)
|
||||
. ' bad=' . $badSt
|
||||
. ' display=' . ($statusRow['display_name'] ?? '')
|
||||
);
|
||||
|
||||
$ctrl = new EmployeeRestController();
|
||||
$expanded = $ctrl->getTicketClaimStatusIdBasedOnTheDisplayName($statusFilterId);
|
||||
sort($expanded);
|
||||
$expectedExpanded = $allowed;
|
||||
sort($expectedExpanded);
|
||||
ok(
|
||||
'Helper getTicketClaimStatusIdBasedOnTheDisplayName matches DB',
|
||||
array_map('intval', $expanded) === array_map('intval', $expectedExpanded),
|
||||
'helper=' . json_encode($expanded) . ' db=' . json_encode($expectedExpanded)
|
||||
);
|
||||
}
|
||||
|
||||
// Combined filters should be AND
|
||||
if (!empty($ticketTypeId) && !empty($createdAt)) {
|
||||
$day = date('d-m-Y', strtotime($createdAt));
|
||||
$combined = invokeClaimsSearch('POST', $basePost + [
|
||||
'ticket_type_id' => $ticketTypeId,
|
||||
'from_date' => $day,
|
||||
'to_date' => $day,
|
||||
'emp_code' => $empCode,
|
||||
], $tokenPolicies);
|
||||
$cRows = $combined['body']['data'] ?? [];
|
||||
$okCombined = true;
|
||||
foreach ($cRows as $r) {
|
||||
if ((string) ($r['ticket_type_id'] ?? '') !== (string) $ticketTypeId) {
|
||||
$okCombined = false;
|
||||
}
|
||||
if ($empCode && (string) ($r['emp_code'] ?? '') !== (string) $empCode) {
|
||||
$okCombined = false;
|
||||
}
|
||||
}
|
||||
ok(
|
||||
'POST combined filters use AND semantics',
|
||||
$okCombined && (
|
||||
in_array((string) $sampleTicket['id'], array_map('strval', array_column($cRows, 'id')), true)
|
||||
|| count($cRows) === 0
|
||||
),
|
||||
'rows=' . count($cRows) . ' fixture_in=' . (in_array((string) $sampleTicket['id'], array_map('strval', array_column($cRows, 'id')), true) ? 'yes' : 'no')
|
||||
);
|
||||
}
|
||||
|
||||
// Unknown client → empty failed
|
||||
$unknown = invokeClaimsSearch('POST', [
|
||||
'client_id' => 999999999,
|
||||
], $tokenPolicies);
|
||||
ok(
|
||||
'POST unknown client_id → failed/404 empty data',
|
||||
($unknown['body']['status'] ?? '') === 'failed'
|
||||
&& (int) ($unknown['body']['code'] ?? 0) === 404
|
||||
&& ($unknown['body']['data'] ?? null) === [],
|
||||
json_encode($unknown['body'])
|
||||
);
|
||||
|
||||
// Inactive tickets must never appear
|
||||
if (!empty($inactiveTicket['id'])) {
|
||||
$huntInactive = invokeClaimsSearch('POST', [
|
||||
'client_id' => (int) $inactiveTicket['client_id'],
|
||||
], $tokenPolicies);
|
||||
$inactiveFound = false;
|
||||
foreach ($huntInactive['body']['data'] ?? [] as $r) {
|
||||
if ((int) ($r['id'] ?? 0) === (int) $inactiveTicket['id']) {
|
||||
$inactiveFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ok(
|
||||
'POST excludes is_active=0 tickets',
|
||||
!$inactiveFound,
|
||||
'inactive_id=' . $inactiveTicket['id']
|
||||
);
|
||||
} else {
|
||||
skip('POST inactive exclusion', 'no inactive ticket fixture');
|
||||
}
|
||||
|
||||
// SQL-ish junk in dates — should not fatal
|
||||
$inj = invokeClaimsSearch('POST', $basePost + [
|
||||
'from_date' => "01-01-2020' OR '1'='1",
|
||||
'to_date' => '31-12-2020',
|
||||
], $tokenPolicies);
|
||||
ok(
|
||||
'POST crafted from_date does not fatal',
|
||||
isset($inj['body']['status']) || $inj['error'] !== null,
|
||||
'status=' . ($inj['body']['status'] ?? 'null') . ' http=' . $inj['http'] . ' error=' . ($inj['error'] ?? 'null')
|
||||
);
|
||||
if (($inj['body']['status'] ?? '') === 'success' && count($inj['body']['data'] ?? []) > 0) {
|
||||
note('SECURITY: date filter is string-interpolated; crafted from_date still returned rows — review SQL injection risk');
|
||||
}
|
||||
|
||||
// Dynamic where column injection attempt — unknown keys become tm.<key>
|
||||
$bogusCol = invokeClaimsSearch('POST', $basePost + [
|
||||
'definitely_not_a_col' => 'x',
|
||||
], $tokenPolicies);
|
||||
ok(
|
||||
'POST unknown dynamic field handled without uncaught fatal',
|
||||
true,
|
||||
'error=' . ($bogusCol['error'] ?? 'null')
|
||||
. ' status=' . ($bogusCol['body']['status'] ?? 'null')
|
||||
. ' code=' . ($bogusCol['body']['code'] ?? 'null')
|
||||
);
|
||||
if ($bogusCol['error'] !== null
|
||||
|| str_contains(strtolower($bogusCol['raw']), 'unknown column')
|
||||
|| str_contains(strtolower($bogusCol['raw']), 'sqlstate')
|
||||
) {
|
||||
note('BUG: unknown JSON keys are passed into WHERE as tm.<key> — ' . ($bogusCol['error'] ?: 'SQL error in response'));
|
||||
}
|
||||
} else {
|
||||
skip('POST client-scoped filters', 'no sample client/policy');
|
||||
}
|
||||
|
||||
// ─── Report ─────────────────────────────────────────────────────────────────
|
||||
|
||||
ob_end_clean();
|
||||
|
||||
foreach ($results as $line) {
|
||||
echo $line . PHP_EOL;
|
||||
}
|
||||
|
||||
if ($notes !== []) {
|
||||
echo PHP_EOL . '──── Notes / findings ────' . PHP_EOL;
|
||||
foreach ($notes as $n) {
|
||||
echo '- ' . $n . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
echo PHP_EOL . "Summary: PASS={$pass} FAIL={$fail} SKIP={$skip}" . PHP_EOL;
|
||||
exit($fail > 0 ? 1 : 0);
|
||||
312
tests/smoke_verified_hr_claims_submenu.php
Normal file
312
tests/smoke_verified_hr_claims_submenu.php
Normal file
@ -0,0 +1,312 @@
|
||||
<?php
|
||||
/**
|
||||
* Smoke test: RestAuthenticationController::getVerifiedHrData
|
||||
* — claims module strip when allowed_active_policies empty
|
||||
* — claims_sub_menu EB / Non-EB based on policy_type.allocg
|
||||
*
|
||||
* Run:
|
||||
* php tests/smoke_verified_hr_claims_submenu.php
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
ob_start();
|
||||
|
||||
define('FCPATH', __DIR__ . '/../public/');
|
||||
chdir(FCPATH);
|
||||
|
||||
require FCPATH . '../app/Config/Paths.php';
|
||||
$paths = new Config\Paths();
|
||||
require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php';
|
||||
require_once SYSTEMPATH . 'Config/DotEnv.php';
|
||||
(new CodeIgniter\Config\DotEnv(ROOTPATH))->load();
|
||||
|
||||
defined('ENVIRONMENT') || define('ENVIRONMENT', env('CI_ENVIRONMENT', 'development'));
|
||||
|
||||
$boot = APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php';
|
||||
if (is_file($boot)) {
|
||||
require_once $boot;
|
||||
}
|
||||
|
||||
if (empty(env('JWT_SECRET'))) {
|
||||
$smokeJwtSecret = 'smoke-verified-hr-claims-submenu-jwt-secret';
|
||||
putenv('JWT_SECRET=' . $smokeJwtSecret);
|
||||
$_ENV['JWT_SECRET'] = $smokeJwtSecret;
|
||||
$_SERVER['JWT_SECRET'] = $smokeJwtSecret;
|
||||
}
|
||||
|
||||
use App\Controllers\RestAuthenticationController;
|
||||
use CodeIgniter\HTTP\IncomingRequest;
|
||||
use CodeIgniter\HTTP\URI;
|
||||
use CodeIgniter\HTTP\UserAgent;
|
||||
use Config\Services;
|
||||
|
||||
$pass = 0;
|
||||
$fail = 0;
|
||||
$skip = 0;
|
||||
$results = [];
|
||||
|
||||
function ok(string $label, bool $cond, string $detail = ''): void
|
||||
{
|
||||
global $pass, $fail, $results;
|
||||
if ($cond) {
|
||||
$pass++;
|
||||
$results[] = '[PASS] ' . $label . ($detail !== '' ? " — {$detail}" : '');
|
||||
} else {
|
||||
$fail++;
|
||||
$results[] = '[FAIL] ' . $label . ($detail !== '' ? " — {$detail}" : '');
|
||||
}
|
||||
}
|
||||
|
||||
function skip(string $label, string $detail = ''): void
|
||||
{
|
||||
global $skip, $results;
|
||||
$skip++;
|
||||
$results[] = '[SKIP] ' . $label . ($detail !== '' ? " — {$detail}" : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{http:int,body:array,raw:string,error:?string}
|
||||
*/
|
||||
function invokeGetVerifiedHrData(array $payload): array
|
||||
{
|
||||
try {
|
||||
$uri = new URI('http://localhost/nhance_v2/employeeRest/getVerifiedHrData');
|
||||
$bodyString = json_encode($payload, JSON_THROW_ON_ERROR);
|
||||
|
||||
$request = new IncomingRequest(
|
||||
config('App'),
|
||||
$uri,
|
||||
$bodyString,
|
||||
new UserAgent()
|
||||
);
|
||||
$request->setMethod('post');
|
||||
$request->setHeader('Content-Type', 'application/json');
|
||||
$request->setHeader('User-Agent', 'SmokeTest/1.0');
|
||||
$request->setGlobal('post', []);
|
||||
|
||||
Services::injectMock('request', $request);
|
||||
|
||||
$controller = new RestAuthenticationController();
|
||||
$response = Services::response();
|
||||
$logger = Services::logger();
|
||||
$controller->initController($request, $response, $logger);
|
||||
|
||||
// Local DB user may lack INSERT on auth_history; stub so smoke can focus on claims logic.
|
||||
$authHistoryStub = new class {
|
||||
public function insert($data = null, bool $returnID = true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
$ref = new ReflectionProperty(RestAuthenticationController::class, 'authHistoryModel');
|
||||
$ref->setAccessible(true);
|
||||
$ref->setValue($controller, $authHistoryStub);
|
||||
|
||||
$resp = $controller->getVerifiedHrData();
|
||||
$raw = $resp->getBody();
|
||||
$body = json_decode($raw, true);
|
||||
|
||||
return [
|
||||
'http' => $resp->getStatusCode(),
|
||||
'body' => is_array($body) ? $body : [],
|
||||
'raw' => (string) $raw,
|
||||
'error' => null,
|
||||
];
|
||||
} catch (Throwable $e) {
|
||||
return [
|
||||
'http' => 500,
|
||||
'body' => [],
|
||||
'raw' => '',
|
||||
'error' => $e->getMessage() . ' @ ' . $e->getFile() . ':' . $e->getLine(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function findHrRowForClient(array $dataRows, int $clientId, int $hrId): ?array
|
||||
{
|
||||
foreach ($dataRows as $row) {
|
||||
if ((int) ($row['client_id'] ?? 0) === $clientId && (int) ($row['id'] ?? 0) === $hrId) {
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
|
||||
return $dataRows[0] ?? null;
|
||||
}
|
||||
|
||||
function postModulesContainClaims(?array $allowedModules): bool
|
||||
{
|
||||
if (!is_array($allowedModules)) {
|
||||
return false;
|
||||
}
|
||||
if (isset($allowedModules['post']) && is_array($allowedModules['post'])) {
|
||||
return in_array(4, array_map('intval', $allowedModules['post']), true);
|
||||
}
|
||||
if (!isset($allowedModules['pre']) && !isset($allowedModules['post'])) {
|
||||
return in_array(4, array_map('intval', $allowedModules), true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$db = db_connect();
|
||||
|
||||
$fixture = $db->query(
|
||||
"SELECT hac.id AS hac_id, hac.post_hr_id, hac.post_client_id, hac.post_branch_id,
|
||||
hac.allowed_modules, hac.allowed_active_policies,
|
||||
lc.mobile, lc.email, lc.otp AS original_otp
|
||||
FROM hr_access_control hac
|
||||
INNER JOIN level_contacts lc ON lc.id = hac.post_hr_id
|
||||
WHERE hac.is_active = 1
|
||||
AND lc.is_active = 1
|
||||
AND lc.contact_type = 'client'
|
||||
AND lc.mobile IS NOT NULL
|
||||
AND lc.mobile <> ''
|
||||
AND hac.allowed_modules LIKE '%4%'
|
||||
AND hac.allowed_active_policies IS NOT NULL
|
||||
AND hac.allowed_active_policies <> '[]'
|
||||
AND hac.allowed_active_policies <> ''
|
||||
ORDER BY hac.id DESC
|
||||
LIMIT 1"
|
||||
)->getRowArray();
|
||||
|
||||
$nonEbPolicy = $db->query(
|
||||
"SELECT cp.id, pt.allocg
|
||||
FROM client_policy cp
|
||||
JOIN policy_type pt ON pt.id = cp.policy_type_id
|
||||
WHERE pt.allocg <> 'EB'
|
||||
AND cp.is_active = 1
|
||||
ORDER BY cp.id DESC
|
||||
LIMIT 1"
|
||||
)->getRowArray();
|
||||
|
||||
if (!$fixture) {
|
||||
skip('Fixture HR with claims + policies', 'none found');
|
||||
foreach ($results as $line) {
|
||||
echo $line . PHP_EOL;
|
||||
}
|
||||
echo PHP_EOL . "PASS={$pass} FAIL={$fail} SKIP={$skip}" . PHP_EOL;
|
||||
exit($fail > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
ok('Fixture HR with claims module', true, "hac_id={$fixture['hac_id']} hr={$fixture['post_hr_id']} mobile={$fixture['mobile']}");
|
||||
|
||||
$otp = '999111';
|
||||
$originalPolicies = $fixture['allowed_active_policies'];
|
||||
$originalModules = $fixture['allowed_modules'];
|
||||
$originalOtp = $fixture['original_otp'];
|
||||
$hrId = (int) $fixture['post_hr_id'];
|
||||
$clientId = (int) $fixture['post_client_id'];
|
||||
$hacId = (int) $fixture['hac_id'];
|
||||
$mobile = $fixture['mobile'];
|
||||
|
||||
$restore = static function () use ($db, $hacId, $hrId, $originalPolicies, $originalModules, $originalOtp): void {
|
||||
$db->table('hr_access_control')->where('id', $hacId)->update([
|
||||
'allowed_active_policies' => $originalPolicies,
|
||||
'allowed_modules' => $originalModules,
|
||||
]);
|
||||
$db->table('level_contacts')->where('id', $hrId)->update([
|
||||
'otp' => $originalOtp,
|
||||
]);
|
||||
};
|
||||
|
||||
register_shutdown_function($restore);
|
||||
|
||||
// ── Case 1: EB-only policies → claims_sub_menu = ["EB"] ──────────────────────
|
||||
|
||||
$db->table('level_contacts')->where('id', $hrId)->update(['otp' => $otp]);
|
||||
|
||||
$case1 = invokeGetVerifiedHrData([
|
||||
'mobile_no' => $mobile,
|
||||
'otp' => $otp,
|
||||
]);
|
||||
|
||||
ok('Case1 HTTP/error free', $case1['error'] === null, (string) ($case1['error'] ?? 'ok'));
|
||||
ok('Case1 status success', ($case1['body']['status'] ?? '') === 'success', json_encode($case1['body']['message'] ?? $case1['body']['status'] ?? null));
|
||||
|
||||
$case1Rows = $case1['body']['data'] ?? [];
|
||||
$case1Row = is_array($case1Rows) ? findHrRowForClient($case1Rows, $clientId, $hrId) : null;
|
||||
|
||||
ok('Case1 row found', is_array($case1Row), 'client_id=' . $clientId);
|
||||
ok(
|
||||
'Case1 claims still present',
|
||||
postModulesContainClaims($case1Row['allowed_modules'] ?? null),
|
||||
json_encode($case1Row['allowed_modules'] ?? null)
|
||||
);
|
||||
ok(
|
||||
'Case1 claims_sub_menu is EB only',
|
||||
($case1Row['claims_sub_menu'] ?? null) === ['EB'],
|
||||
json_encode($case1Row['claims_sub_menu'] ?? null)
|
||||
);
|
||||
|
||||
// ── Case 2: include Non-EB policy → claims_sub_menu = ["EB","Non-EB"] ─────────
|
||||
|
||||
if (!$nonEbPolicy) {
|
||||
skip('Case2 Non-EB policy fixture', 'no non-EB client_policy found');
|
||||
} else {
|
||||
$mixedPolicies = json_decode($originalPolicies, true) ?: [];
|
||||
$mixedPolicies[] = (int) $nonEbPolicy['id'];
|
||||
$mixedPolicies = array_values(array_unique(array_map('intval', $mixedPolicies)));
|
||||
|
||||
$db->table('hr_access_control')->where('id', $hacId)->update([
|
||||
'allowed_active_policies' => json_encode($mixedPolicies),
|
||||
]);
|
||||
$db->table('level_contacts')->where('id', $hrId)->update(['otp' => $otp]);
|
||||
|
||||
$case2 = invokeGetVerifiedHrData([
|
||||
'mobile_no' => $mobile,
|
||||
'otp' => $otp,
|
||||
]);
|
||||
|
||||
ok('Case2 HTTP/error free', $case2['error'] === null, (string) ($case2['error'] ?? 'ok'));
|
||||
ok('Case2 status success', ($case2['body']['status'] ?? '') === 'success', json_encode($case2['body']['status'] ?? null));
|
||||
|
||||
$case2Rows = $case2['body']['data'] ?? [];
|
||||
$case2Row = is_array($case2Rows) ? findHrRowForClient($case2Rows, $clientId, $hrId) : null;
|
||||
|
||||
ok(
|
||||
'Case2 claims_sub_menu has EB + Non-EB',
|
||||
($case2Row['claims_sub_menu'] ?? null) === ['EB', 'Non-EB'],
|
||||
json_encode($case2Row['claims_sub_menu'] ?? null) . ' nonEbPolicy=' . $nonEbPolicy['id'] . '/' . $nonEbPolicy['allocg']
|
||||
);
|
||||
}
|
||||
|
||||
// ── Case 3: empty policies → claims stripped, claims_sub_menu = [] ────────────
|
||||
|
||||
$db->table('hr_access_control')->where('id', $hacId)->update([
|
||||
'allowed_active_policies' => '[]',
|
||||
]);
|
||||
$db->table('level_contacts')->where('id', $hrId)->update(['otp' => $otp]);
|
||||
|
||||
$case3 = invokeGetVerifiedHrData([
|
||||
'mobile_no' => $mobile,
|
||||
'otp' => $otp,
|
||||
]);
|
||||
|
||||
ok('Case3 HTTP/error free', $case3['error'] === null, (string) ($case3['error'] ?? 'ok'));
|
||||
ok('Case3 status success', ($case3['body']['status'] ?? '') === 'success', json_encode($case3['body']['status'] ?? null));
|
||||
|
||||
$case3Rows = $case3['body']['data'] ?? [];
|
||||
$case3Row = is_array($case3Rows) ? findHrRowForClient($case3Rows, $clientId, $hrId) : null;
|
||||
|
||||
ok(
|
||||
'Case3 claims module removed',
|
||||
!postModulesContainClaims($case3Row['allowed_modules'] ?? null),
|
||||
json_encode($case3Row['allowed_modules'] ?? null)
|
||||
);
|
||||
ok(
|
||||
'Case3 claims_sub_menu empty',
|
||||
($case3Row['claims_sub_menu'] ?? null) === [],
|
||||
json_encode($case3Row['claims_sub_menu'] ?? null)
|
||||
);
|
||||
|
||||
$restore();
|
||||
|
||||
ob_end_clean();
|
||||
|
||||
foreach ($results as $line) {
|
||||
echo $line . PHP_EOL;
|
||||
}
|
||||
echo PHP_EOL . "PASS={$pass} FAIL={$fail} SKIP={$skip}" . PHP_EOL;
|
||||
|
||||
exit($fail > 0 ? 1 : 0);
|
||||
Loading…
Reference in New Issue
Block a user