/** * Descomplicar® Crescimento Digital * https://descomplicar.pt */ CI = &get_instance(); // Load required libraries $this->CI->load->library('desk_moloni/molonioauth'); $this->CI->load->library('desk_moloni/tokenmanager'); $this->oauth = $this->CI->molonioauth; $this->token_manager = $this->CI->tokenmanager; // Test credentials (use environment variables or test config) $this->test_client_id = getenv('MOLONI_TEST_CLIENT_ID') ?: 'test_client_id'; $this->test_client_secret = getenv('MOLONI_TEST_CLIENT_SECRET') ?: 'test_client_secret'; $this->test_redirect_uri = 'https://test.example.com/oauth/callback'; // Clear any existing tokens $this->token_manager->clear_tokens(); } protected function tearDown(): void { // Clean up after tests $this->token_manager->clear_tokens(); // Reset OAuth configuration update_option('desk_moloni_client_id', ''); update_option('desk_moloni_client_secret', ''); } /** * Test OAuth configuration */ public function testOAuthConfiguration() { // Test initial state (not configured) $this->assertFalse($this->oauth->is_configured()); // Test configuration $result = $this->oauth->configure($this->test_client_id, $this->test_client_secret, [ 'redirect_uri' => $this->test_redirect_uri, 'use_pkce' => true ]); $this->assertTrue($result); $this->assertTrue($this->oauth->is_configured()); // Test configuration persistence $status = $this->oauth->get_status(); $this->assertTrue($status['configured']); $this->assertTrue($status['use_pkce']); $this->assertEquals($this->test_redirect_uri, $status['redirect_uri']); } /** * Test OAuth configuration validation */ public function testOAuthConfigurationValidation() { // Test empty client ID $this->expectException(InvalidArgumentException::class); $this->oauth->configure('', $this->test_client_secret); } /** * Test OAuth configuration with invalid parameters */ public function testOAuthConfigurationInvalidParameters() { // Test empty client secret $this->expectException(InvalidArgumentException::class); $this->oauth->configure($this->test_client_id, ''); } /** * Test authorization URL generation */ public function testAuthorizationUrlGeneration() { // Configure OAuth first $this->oauth->configure($this->test_client_id, $this->test_client_secret, [ 'redirect_uri' => $this->test_redirect_uri ]); // Generate authorization URL $state = 'test_state_' . time(); $auth_url = $this->oauth->get_authorization_url($state); // Verify URL structure $this->assertStringContainsString('https://api.moloni.pt/v1/oauth2/authorize', $auth_url); $this->assertStringContainsString('client_id=' . urlencode($this->test_client_id), $auth_url); $this->assertStringContainsString('redirect_uri=' . urlencode($this->test_redirect_uri), $auth_url); $this->assertStringContainsString('state=' . $state, $auth_url); $this->assertStringContainsString('response_type=code', $auth_url); // Test PKCE parameters $this->assertStringContainsString('code_challenge=', $auth_url); $this->assertStringContainsString('code_challenge_method=S256', $auth_url); } /** * Test authorization URL generation without configuration */ public function testAuthorizationUrlWithoutConfiguration() { $this->expectException(Exception::class); $this->expectExceptionMessage('OAuth not configured'); $this->oauth->get_authorization_url(); } /** * Test OAuth callback handling with mock data */ public function testOAuthCallbackHandling() { // Configure OAuth $this->oauth->configure($this->test_client_id, $this->test_client_secret); // Mock successful token response $mock_response = [ 'access_token' => 'test_access_token_' . time(), 'refresh_token' => 'test_refresh_token_' . time(), 'expires_in' => 3600, 'token_type' => 'Bearer', 'scope' => 'read write' ]; // Save mock tokens $result = $this->token_manager->save_tokens($mock_response); $this->assertTrue($result); // Verify token storage $this->assertTrue($this->token_manager->are_tokens_valid()); $this->assertEquals($mock_response['access_token'], $this->token_manager->get_access_token()); $this->assertEquals($mock_response['refresh_token'], $this->token_manager->get_refresh_token()); } /** * Test token encryption and decryption */ public function testTokenEncryption() { $test_token = 'test_access_token_' . uniqid(); // Test token save and retrieval $token_data = [ 'access_token' => $test_token, 'refresh_token' => 'test_refresh_' . uniqid(), 'expires_in' => 3600 ]; $result = $this->token_manager->save_tokens($token_data); $this->assertTrue($result); // Verify token retrieval $retrieved_token = $this->token_manager->get_access_token(); $this->assertEquals($test_token, $retrieved_token); // Verify encrypted storage (tokens should not be stored in plain text) $stored_encrypted = get_option('desk_moloni_access_token_encrypted'); $this->assertNotEmpty($stored_encrypted); $this->assertNotEquals($test_token, $stored_encrypted); } /** * Test token expiration logic */ public function testTokenExpiration() { // Save token that expires in 1 second $token_data = [ 'access_token' => 'test_token', 'expires_in' => 1 ]; $this->token_manager->save_tokens($token_data); // Token should be valid initially $this->assertTrue($this->token_manager->are_tokens_valid()); // Wait for expiration sleep(2); // Token should be expired now $this->assertFalse($this->token_manager->are_tokens_valid()); } /** * Test token clearing */ public function testTokenClearing() { // Save some tokens $token_data = [ 'access_token' => 'test_token', 'refresh_token' => 'test_refresh', 'expires_in' => 3600 ]; $this->token_manager->save_tokens($token_data); $this->assertTrue($this->token_manager->are_tokens_valid()); // Clear tokens $result = $this->token_manager->clear_tokens(); $this->assertTrue($result); // Verify tokens are cleared $this->assertFalse($this->token_manager->are_tokens_valid()); $this->assertNull($this->token_manager->get_access_token()); $this->assertNull($this->token_manager->get_refresh_token()); } /** * Test OAuth status reporting */ public function testOAuthStatus() { // Test unconfigured status $status = $this->oauth->get_status(); $this->assertFalse($status['configured']); $this->assertFalse($status['connected']); // Configure OAuth $this->oauth->configure($this->test_client_id, $this->test_client_secret); $status = $this->oauth->get_status(); $this->assertTrue($status['configured']); $this->assertFalse($status['connected']); // No tokens yet // Add tokens $this->token_manager->save_tokens([ 'access_token' => 'test_token', 'expires_in' => 3600 ]); $status = $this->oauth->get_status(); $this->assertTrue($status['configured']); $this->assertTrue($status['connected']); } /** * Test OAuth configuration testing */ public function testOAuthConfigurationTesting() { // Test without configuration $test_result = $this->oauth->test_configuration(); $this->assertFalse($test_result['is_valid']); $this->assertContains('OAuth not configured', $test_result['issues']); // Configure OAuth $this->oauth->configure($this->test_client_id, $this->test_client_secret); // Test with configuration $test_result = $this->oauth->test_configuration(); // Should pass basic configuration tests $this->assertIsArray($test_result['issues']); $this->assertArrayHasKey('is_valid', $test_result); $this->assertArrayHasKey('endpoints', $test_result); $this->assertArrayHasKey('encryption', $test_result); } /** * Test token manager encryption validation */ public function testTokenManagerEncryptionValidation() { $validation = $this->token_manager->validate_encryption(); $this->assertArrayHasKey('is_valid', $validation); $this->assertArrayHasKey('issues', $validation); $this->assertArrayHasKey('cipher', $validation); // Should pass if OpenSSL is available if (extension_loaded('openssl')) { $this->assertTrue($validation['is_valid'], 'Encryption validation failed: ' . implode(', ', $validation['issues'])); } } /** * Test token status information */ public function testTokenStatus() { // Test empty status $status = $this->token_manager->get_token_status(); $this->assertFalse($status['has_access_token']); $this->assertFalse($status['has_refresh_token']); $this->assertFalse($status['is_valid']); // Add tokens $token_data = [ 'access_token' => 'test_token', 'refresh_token' => 'test_refresh', 'expires_in' => 3600, 'scope' => 'read write' ]; $this->token_manager->save_tokens($token_data); $status = $this->token_manager->get_token_status(); $this->assertTrue($status['has_access_token']); $this->assertTrue($status['has_refresh_token']); $this->assertTrue($status['is_valid']); $this->assertEquals('read write', $status['scope']); $this->assertGreaterThan(0, $status['expires_in']); } /** * Test PKCE implementation */ public function testPKCEImplementation() { // Configure OAuth with PKCE enabled $this->oauth->configure($this->test_client_id, $this->test_client_secret, [ 'use_pkce' => true ]); // Generate authorization URL $auth_url = $this->oauth->get_authorization_url('test_state'); // Verify PKCE parameters are included $this->assertStringContainsString('code_challenge=', $auth_url); $this->assertStringContainsString('code_challenge_method=S256', $auth_url); // Verify code verifier is stored in session (would be used in real implementation) $this->assertNotEmpty($this->CI->session->userdata('desk_moloni_code_verifier')); } /** * Test error handling in OAuth flow */ public function testOAuthErrorHandling() { // Configure OAuth $this->oauth->configure($this->test_client_id, $this->test_client_secret); // Test callback with error $this->expectException(Exception::class); $this->expectExceptionMessage('OAuth Error'); // Simulate error callback (this would normally come from Moloni) $_GET['error'] = 'access_denied'; $_GET['error_description'] = 'User denied access'; $this->oauth->handle_callback('', 'test_state'); } /** * Test rate limiting in OAuth requests */ public function testOAuthRateLimiting() { // This test would require mocking HTTP requests // For now, we test that the rate limiting structure is in place $status = $this->oauth->get_status(); $this->assertArrayHasKey('rate_limit', $status); $this->assertArrayHasKey('max_requests', $status['rate_limit']); $this->assertArrayHasKey('current_count', $status['rate_limit']); } /** * Integration test with mock HTTP responses */ public function testIntegrationWithMockResponses() { // This would require a HTTP mocking library like VCR.php or Guzzle Mock // For demonstration, we'll test the structure is correct $this->oauth->configure($this->test_client_id, $this->test_client_secret); // Verify OAuth is ready for integration $this->assertTrue($this->oauth->is_configured()); // Verify we can generate proper authorization URLs $auth_url = $this->oauth->get_authorization_url(); $this->assertStringStartsWith('https://api.moloni.pt/v1/oauth2/authorize', $auth_url); } /** * Test OAuth connection status */ public function testOAuthConnectionStatus() { // Initially not connected $this->assertFalse($this->oauth->is_connected()); // Configure OAuth $this->oauth->configure($this->test_client_id, $this->test_client_secret); $this->assertFalse($this->oauth->is_connected()); // Still no tokens // Add valid tokens $this->token_manager->save_tokens([ 'access_token' => 'valid_token', 'expires_in' => 3600 ]); $this->assertTrue($this->oauth->is_connected()); } /** * Test security features */ public function testSecurityFeatures() { // Test CSRF protection with state parameter $state1 = 'state1'; $state2 = 'state2'; $url1 = $this->oauth->get_authorization_url($state1); $url2 = $this->oauth->get_authorization_url($state2); $this->assertStringContainsString('state=' . $state1, $url1); $this->assertStringContainsString('state=' . $state2, $url2); // Test that different states produce different URLs $this->assertNotEquals($url1, $url2); } }