Files
desk-moloni/modules/desk_moloni/tests/OAuthIntegrationTest.php
Emanuel Almeida 8c4f68576f chore: add spec-kit and standardize signatures
- Added GitHub spec-kit for development workflow
- Standardized file signatures to Descomplicar® format
- Updated development configuration

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-12 01:27:37 +01:00

451 lines
15 KiB
PHP

/**
* Descomplicar® Crescimento Digital
* https://descomplicar.pt
*/
<?php
defined('BASEPATH') or exit('No direct script access allowed');
/**
* OAuth Integration Tests
*
* Comprehensive tests for OAuth 2.0 flow with Moloni API
*
* @package DeskMoloni
* @author Descomplicar®
* @copyright 2025 Descomplicar
* @version 3.0.0
*/
class OAuthIntegrationTest extends PHPUnit\Framework\TestCase
{
private $CI;
private $oauth;
private $token_manager;
private $test_client_id;
private $test_client_secret;
private $test_redirect_uri;
protected function setUp(): void
{
// Get CodeIgniter instance
$this->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);
}
}