diff --git a/app/Controllers/RestAuthenticationController.php b/app/Controllers/RestAuthenticationController.php index 54612c5..4be7b2a 100755 --- a/app/Controllers/RestAuthenticationController.php +++ b/app/Controllers/RestAuthenticationController.php @@ -853,8 +853,70 @@ 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); + // $getAllhrData[$key]['allowed_modules'] = $decoded; + // $HRAccessData['pre_client_id'] = md5($HRAccessData['pre_client_id']); + // $HRAccessData['post_client_id'] = md5($HRAccessData['post_client_id']); + + $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']); diff --git a/tests/unit/GetVerifiedHrDataTest.php b/tests/unit/GetVerifiedHrDataTest.php new file mode 100644 index 0000000..ca2e39a --- /dev/null +++ b/tests/unit/GetVerifiedHrDataTest.php @@ -0,0 +1,450 @@ +jwtSecret}"); + $_ENV['JWT_SECRET'] = $this->jwtSecret; + $_SERVER['JWT_SECRET'] = $this->jwtSecret; + + putenv('POST_ENROLLMENT_BASEURL=http://example.com/'); + $_ENV['POST_ENROLLMENT_BASEURL'] = 'http://example.com/'; + + $this->mockCurlRequest(['ok' => true, 'source' => 'post_enrollment']); + $this->mockRateLimiter(); + } + + protected function tearDown(): void + { + $this->clearInjectedDbConnections(); + Services::reset(true); + + putenv('JWT_SECRET'); + putenv('POST_ENROLLMENT_BASEURL'); + unset( + $_ENV['JWT_SECRET'], + $_SERVER['JWT_SECRET'], + $_ENV['POST_ENROLLMENT_BASEURL'] + ); + + parent::tearDown(); + } + + public function testInvalidOtpReturnsFailed404() + { + $hrModel = $this->createHrModelStub([ + 'first' => null, + 'findAll' => [], + ]); + + $controller = $this->buildController( + ['email' => 'hr@example.com', 'otp' => '000000'], + $hrModel + ); + + $body = $this->decodeResponse($controller->getVerifiedHrData()); + + $this->assertSame('failed', $body['status']); + $this->assertSame(404, $body['code']); + $this->assertSame('', $body['data']); + $this->assertSame(['ok' => true, 'source' => 'post_enrollment'], $body['post_enrollment']); + } + + public function testValidOtpWithNoActiveBranchesReturnsFailed404() + { + $hrModel = $this->createHrModelStub([ + 'first' => ['id' => 5, 'email' => 'hr@example.com', 'mobile' => '9876543210'], + 'findAll' => [], + 'update' => true, + ]); + + $authHistoryModel = $this->createMock(AuthHistoryModel::class); + $authHistoryModel->expects($this->once())->method('insert'); + + $controller = $this->buildController( + ['email' => 'hr@example.com', 'otp' => '123456'], + $hrModel, + $authHistoryModel + ); + + $body = $this->decodeResponse($controller->getVerifiedHrData()); + + $this->assertSame('failed', $body['status']); + $this->assertSame(404, $body['code']); + $this->assertSame('', $body['data']); + } + + public function testValidMobileOtpReturnsSuccessWithClaimsSubMenu() + { + $branchRow = [ + 'id' => 5, + 'mobile' => '9876543210', + 'email' => 'hr@example.com', + 'client_id' => 42, + 'client_name' => 'Acme Corp', + 'short_name' => 'ACME', + 'client_branch_id' => 7, + 'branch_name' => 'HQ', + 'post_branch_id' => 70, + ]; + + $hrModel = $this->createHrModelStub([ + 'first' => ['id' => 5, 'email' => 'hr@example.com', 'mobile' => '9876543210'], + 'findAll' => [$branchRow], + 'update' => true, + ]); + + $authHistoryModel = $this->createMock(AuthHistoryModel::class); + $authHistoryModel->expects($this->once())->method('insert'); + + $this->injectDbConnection('postDB', $this->createQueryResultStub([ + 'pre_client_id' => 42, + 'post_client_id' => 420, + 'pre_branch_id' => 7, + 'pre_hr_id' => 5, + 'allowed_modules' => json_encode(['post' => [1, 4, 5]]), + 'allowed_active_policies' => json_encode([101, 102]), + ])); + + $this->injectDbConnection('tests', $this->createQueryResultStub( + null, + [ + ['allocg' => 'EB'], + ['allocg' => 'GPA'], + ] + )); + + $controller = $this->buildController( + ['mobile_no' => '9876543210', 'otp' => '123456'], + $hrModel, + $authHistoryModel + ); + + $body = $this->decodeResponse($controller->getVerifiedHrData()); + + $this->assertSame('success', $body['status']); + $this->assertSame(200, $body['code']); + $this->assertCount(1, $body['data']); + $this->assertSame(['post' => [1, 4, 5]], $body['data'][0]['allowed_modules']); + $this->assertSame(['EB', 'Non-EB'], $body['data'][0]['claims_sub_menu']); + $this->assertNotEmpty($body['data'][0]['token']); + $this->assertSame(['ok' => true, 'source' => 'post_enrollment'], $body['post_enrollment']); + } + + public function testClaimsModuleWithoutActivePoliciesStripsModuleId4() + { + $branchRow = [ + 'id' => 5, + 'mobile' => null, + 'email' => 'hr@example.com', + 'client_id' => 42, + 'client_name' => 'Acme Corp', + 'short_name' => 'ACME', + 'client_branch_id' => 7, + 'branch_name' => 'HQ', + 'post_branch_id' => 70, + ]; + + $hrModel = $this->createHrModelStub([ + 'first' => ['id' => 5, 'email' => 'hr@example.com', 'mobile' => null], + 'findAll' => [$branchRow], + 'update' => true, + ]); + + $authHistoryModel = $this->createMock(AuthHistoryModel::class); + $authHistoryModel->method('insert')->willReturn(1); + + $this->injectDbConnection('postDB', $this->createQueryResultStub([ + 'pre_client_id' => 42, + 'post_client_id' => 420, + 'pre_branch_id' => 7, + 'pre_hr_id' => 5, + 'allowed_modules' => json_encode(['post' => [1, 4, 5]]), + 'allowed_active_policies' => json_encode([]), + ])); + + $controller = $this->buildController( + ['email' => 'hr@example.com', 'otp' => '123456'], + $hrModel, + $authHistoryModel + ); + + $body = $this->decodeResponse($controller->getVerifiedHrData()); + + $this->assertSame('success', $body['status']); + $this->assertSame(['post' => [1, 5]], $body['data'][0]['allowed_modules']); + $this->assertSame([], $body['data'][0]['claims_sub_menu']); + $this->assertNotEmpty($body['data'][0]['token']); + } + + public function testMissingHrAccessReturnsEmptyModulesAndEmptyToken() + { + $branchRow = [ + 'id' => 5, + 'mobile' => '9876543210', + 'email' => 'hr@example.com', + 'client_id' => 42, + 'client_name' => 'Acme Corp', + 'short_name' => 'ACME', + 'client_branch_id' => 7, + 'branch_name' => 'HQ', + 'post_branch_id' => 70, + ]; + + $hrModel = $this->createHrModelStub([ + 'first' => ['id' => 5, 'email' => 'hr@example.com', 'mobile' => '9876543210'], + 'findAll' => [$branchRow], + 'update' => true, + ]); + + $authHistoryModel = $this->createMock(AuthHistoryModel::class); + $authHistoryModel->method('insert')->willReturn(1); + + $this->injectDbConnection('postDB', $this->createQueryResultStub(null)); + + $controller = $this->buildController( + ['mobile_no' => '9876543210', 'otp' => '123456'], + $hrModel, + $authHistoryModel + ); + + $body = $this->decodeResponse($controller->getVerifiedHrData()); + + $this->assertSame('success', $body['status']); + $this->assertSame(200, $body['code']); + $this->assertSame([], $body['data'][0]['allowed_modules']); + $this->assertSame('', $body['data'][0]['token']); + } + + private function buildController( + array $payload, + object $hrModel, + ?AuthHistoryModel $authHistoryModel = null + ): RestAuthenticationController { + $json = (object) $payload; + + $uri = $this->createMock(URI::class); + $uri->method('getPath')->willReturn('/employeeRest/getVerifiedHrData'); + $uri->method('getSegments')->willReturn(['employeeRest', 'getVerifiedHrData']); + + $request = $this->getMockBuilder(IncomingRequest::class) + ->disableOriginalConstructor() + ->onlyMethods([ + 'getJSON', + 'getHeaderLine', + 'getIPAddress', + 'getMethod', + 'getPost', + 'getVar', + ]) + ->getMock(); + + $request->uri = $uri; + $request->method('getJSON')->willReturn($json); + $request->method('getHeaderLine')->with('User-Agent')->willReturn('PHPUnit'); + $request->method('getIPAddress')->willReturn('127.0.0.1'); + $request->method('getMethod')->willReturn('post'); + $request->method('getPost')->willReturn([]); + $request->method('getVar')->willReturnCallback(static function ($key) { + return match ($key) { + 'rateLimitFingerprint' => 'test-fingerprint', + 'rateLimitIdentity' => 'hr@example.com', + default => null, + }; + }); + + Services::injectMock('request', $request); + + $controller = new RestAuthenticationController(); + $controller->initController($request, Services::response(), Services::logger()); + + $reflection = new ReflectionClass($controller); + + $hrProp = $reflection->getProperty('hrModel'); + $hrProp->setAccessible(true); + $hrProp->setValue($controller, $hrModel); + + if ($authHistoryModel !== null) { + $authProp = $reflection->getProperty('authHistoryModel'); + $authProp->setAccessible(true); + $authProp->setValue($controller, $authHistoryModel); + } + + return $controller; + } + + private function createHrModelStub(array $results): object + { + return new class ($results) { + private array $results; + private $setPayload = null; + + public function __construct(array $results) + { + $this->results = $results; + } + + public function select(...$args) + { + return $this; + } + + public function join(...$args) + { + return $this; + } + + public function where(...$args) + { + return $this; + } + + public function set($data) + { + $this->setPayload = $data; + + return $this; + } + + public function first() + { + return $this->results['first'] ?? null; + } + + public function findAll() + { + return $this->results['findAll'] ?? []; + } + + public function update(...$args) + { + return $this->results['update'] ?? true; + } + }; + } + + /** + * Fluent DB stub returning either a single row (getRowArray) or many rows (getResultArray). + */ + private function createQueryResultStub(?array $row, array $rows = []): object + { + return new class ($row, $rows) { + private ?array $row; + private array $rows; + + public function __construct(?array $row, array $rows) + { + $this->row = $row; + $this->rows = $rows; + } + + public function table(...$args) + { + return $this; + } + + public function select(...$args) + { + return $this; + } + + public function join(...$args) + { + return $this; + } + + public function where(...$args) + { + return $this; + } + + public function whereIn(...$args) + { + return $this; + } + + public function get() + { + return $this; + } + + public function getRowArray(): ?array + { + return $this->row; + } + + public function getResultArray(): array + { + return $this->rows; + } + }; + } + + private function injectDbConnection(string $group, object $connection): void + { + $property = new ReflectionProperty(DatabaseConfig::class, 'instances'); + $property->setAccessible(true); + + $instances = $property->getValue() ?? []; + $instances[$group] = $connection; + $property->setValue(null, $instances); + } + + private function clearInjectedDbConnections(): void + { + $property = new ReflectionProperty(DatabaseConfig::class, 'instances'); + $property->setAccessible(true); + $property->setValue(null, []); + } + + private function mockCurlRequest(array $payload): void + { + $response = $this->createMock(ResponseInterface::class); + $response->method('getBody')->willReturn(json_encode($payload)); + + $curl = $this->getMockBuilder(\CodeIgniter\HTTP\CURLRequest::class) + ->disableOriginalConstructor() + ->onlyMethods(['post', 'get']) + ->getMock(); + $curl->method('post')->willReturn($response); + $curl->method('get')->willReturn($response); + + Services::injectMock('curlrequest', $curl); + } + + private function mockRateLimiter(): void + { + $limiter = $this->getMockBuilder(\App\Libraries\RateLimiterService::class) + ->disableOriginalConstructor() + ->onlyMethods(['recordIpFailure', 'recordUserFailure']) + ->getMock(); + $limiter->method('recordIpFailure')->willReturn(null); + $limiter->method('recordUserFailure')->willReturn(null); + + Services::injectMock('limiter', $limiter); + } + + private function decodeResponse($response): array + { + return json_decode($response->getBody(), true); + } +}