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); } }