Files
michaelschiemer/tests/Unit/Framework/OAuth/ValueObjects/OAuthTokenTest.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

220 lines
10 KiB
PHP

<?php
declare(strict_types=1);
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\OAuth\ValueObjects\AccessToken;
use App\Framework\OAuth\ValueObjects\OAuthToken;
use App\Framework\OAuth\ValueObjects\RefreshToken;
use App\Framework\OAuth\ValueObjects\TokenScope;
use App\Framework\OAuth\ValueObjects\TokenType;
describe('OAuthToken Composite Value Object', function () {
it('creates complete OAuth token', function () {
$accessToken = AccessToken::create('access_token_1234567890', Timestamp::now()->add(Duration::fromSeconds(3600)));
$refreshToken = RefreshToken::create('refresh_token_1234567890');
$tokenType = TokenType::BEARER;
$scope = TokenScope::fromArray(['read', 'write']);
$oauthToken = new OAuthToken($accessToken, $refreshToken, $tokenType, $scope);
expect($oauthToken->accessToken)->toBe($accessToken);
expect($oauthToken->refreshToken)->toBe($refreshToken);
expect($oauthToken->tokenType)->toBe($tokenType);
expect($oauthToken->scope)->toBe($scope);
});
it('creates from provider response with all fields', function () {
$response = [
'access_token' => 'provider_access_token_1234567890',
'refresh_token' => 'provider_refresh_token_1234567890',
'token_type' => 'Bearer',
'expires_in' => 3600,
'scope' => 'read write admin',
];
$token = OAuthToken::fromProviderResponse($response);
expect($token->accessToken->toString())->toBe('provider_access_token_1234567890');
expect($token->refreshToken->toString())->toBe('provider_refresh_token_1234567890');
expect($token->tokenType)->toBe(TokenType::BEARER);
expect($token->scope->toArray())->toBe(['read', 'write', 'admin']);
});
it('creates from provider response with minimal fields', function () {
$response = [
'access_token' => 'minimal_access_token_1234567890',
];
$token = OAuthToken::fromProviderResponse($response);
expect($token->accessToken->toString())->toBe('minimal_access_token_1234567890');
expect($token->refreshToken)->toBeNull();
expect($token->tokenType)->toBe(TokenType::BEARER); // Default
expect($token->scope)->toBeNull();
});
it('uses default expires_in when not provided', function () {
$response = [
'access_token' => 'token_1234567890',
];
$token = OAuthToken::fromProviderResponse($response);
// Default is 3600 seconds (1 hour)
$secondsLeft = $token->accessToken->getSecondsUntilExpiration();
expect($secondsLeft)->toBeGreaterThan(3500);
expect($secondsLeft)->toBeLessThanOrEqual(3600);
});
it('rejects provider response without access_token', function () {
OAuthToken::fromProviderResponse([]);
})->throws(\InvalidArgumentException::class, 'Provider response missing access_token');
it('detects expired token', function () {
$expiredAccessToken = AccessToken::create('expired_1234567890', Timestamp::now()->subtract(Duration::fromSeconds(100)));
$token = new OAuthToken($expiredAccessToken, null, TokenType::BEARER, null);
expect($token->isExpired())->toBeTrue();
});
it('detects valid non-expired token', function () {
$validAccessToken = AccessToken::create('valid_1234567890', Timestamp::now()->add(Duration::fromSeconds(3600)));
$token = new OAuthToken($validAccessToken, null, TokenType::BEARER, null);
expect($token->isExpired())->toBeFalse();
});
it('checks if token can refresh when refresh token exists', function () {
$accessToken = AccessToken::create('access_1234567890', Timestamp::now()->add(Duration::fromSeconds(3600)));
$refreshToken = RefreshToken::create('refresh_1234567890');
$token = new OAuthToken($accessToken, $refreshToken, TokenType::BEARER, null);
expect($token->canRefresh())->toBeTrue();
});
it('checks if token cannot refresh without refresh token', function () {
$accessToken = AccessToken::create('access_1234567890', Timestamp::now()->add(Duration::fromSeconds(3600)));
$token = new OAuthToken($accessToken, null, TokenType::BEARER, null);
expect($token->canRefresh())->toBeFalse();
});
it('creates new token with refreshed access token immutably', function () {
$originalAccessToken = AccessToken::create('original_access_1234567890', Timestamp::now()->add(Duration::fromSeconds(3600)));
$originalRefreshToken = RefreshToken::create('original_refresh_1234567890');
$originalToken = new OAuthToken($originalAccessToken, $originalRefreshToken, TokenType::BEARER, null);
$newAccessToken = AccessToken::create('new_access_1234567890', Timestamp::now()->add(Duration::fromSeconds(7200)));
$newRefreshToken = RefreshToken::create('new_refresh_1234567890');
$refreshedToken = $originalToken->withRefreshedAccessToken($newAccessToken, $newRefreshToken);
// Original unchanged
expect($originalToken->accessToken->toString())->toBe('original_access_1234567890');
expect($originalToken->refreshToken->toString())->toBe('original_refresh_1234567890');
// New instance updated
expect($refreshedToken->accessToken->toString())->toBe('new_access_1234567890');
expect($refreshedToken->refreshToken->toString())->toBe('new_refresh_1234567890');
expect($refreshedToken->tokenType)->toBe(TokenType::BEARER);
});
it('refreshes access token while keeping original refresh token', function () {
$originalAccessToken = AccessToken::create('original_access_1234567890', Timestamp::now()->add(Duration::fromSeconds(3600)));
$originalRefreshToken = RefreshToken::create('original_refresh_1234567890');
$originalToken = new OAuthToken($originalAccessToken, $originalRefreshToken, TokenType::BEARER, null);
$newAccessToken = AccessToken::create('new_access_1234567890', Timestamp::now()->add(Duration::fromSeconds(7200)));
$refreshedToken = $originalToken->withRefreshedAccessToken($newAccessToken);
// Original refresh token preserved
expect($refreshedToken->refreshToken->toString())->toBe('original_refresh_1234567890');
expect($refreshedToken->accessToken->toString())->toBe('new_access_1234567890');
});
it('converts to array correctly', function () {
$accessToken = AccessToken::create('access_1234567890', Timestamp::now()->add(Duration::fromSeconds(3600)));
$refreshToken = RefreshToken::create('refresh_1234567890');
$scope = TokenScope::fromArray(['read', 'write']);
$token = new OAuthToken($accessToken, $refreshToken, TokenType::BEARER, $scope);
$array = $token->toArray();
expect($array['access_token'])->toBe('access_1234567890');
expect($array['refresh_token'])->toBe('refresh_1234567890');
expect($array['token_type'])->toBe('Bearer');
expect($array['scope'])->toBe('read write');
expect($array)->toHaveKey('expires_at');
});
it('converts to array with masked tokens', function () {
$accessToken = AccessToken::create('access_1234567890', Timestamp::now()->add(Duration::fromSeconds(3600)));
$refreshToken = RefreshToken::create('refresh_1234567890');
$token = new OAuthToken($accessToken, $refreshToken, TokenType::BEARER, null);
$masked = $token->toArrayMasked();
expect($masked['access_token'])->toContain('*');
expect($masked['refresh_token'])->toContain('*');
expect($masked['access_token'] !== 'access_1234567890')->toBeTrue();
expect($masked['refresh_token'] !== 'refresh_1234567890')->toBeTrue();
});
it('handles null refresh token in array conversion', function () {
$accessToken = AccessToken::create('access_1234567890', Timestamp::now()->add(Duration::fromSeconds(3600)));
$token = new OAuthToken($accessToken, null, TokenType::BEARER, null);
$array = $token->toArray();
expect($array)->toHaveKey('access_token');
expect(array_key_exists('refresh_token', $array))->toBeFalse();
});
it('handles null scope in array conversion', function () {
$accessToken = AccessToken::create('access_1234567890', Timestamp::now()->add(Duration::fromSeconds(3600)));
$token = new OAuthToken($accessToken, null, TokenType::BEARER, null);
$array = $token->toArray();
expect($array)->toHaveKey('access_token');
expect(array_key_exists('scope', $array))->toBeFalse();
});
it('creates authorization header with Bearer type', function () {
$accessToken = AccessToken::create('access_1234567890', Timestamp::now()->add(Duration::fromSeconds(3600)));
$token = new OAuthToken($accessToken, null, TokenType::BEARER, null);
$header = $token->getAuthorizationHeader();
expect($header)->toBe('Bearer access_1234567890');
});
it('creates authorization header with MAC type', function () {
$accessToken = AccessToken::create('access_1234567890', Timestamp::now()->add(Duration::fromSeconds(3600)));
$token = new OAuthToken($accessToken, null, TokenType::MAC, null);
$header = $token->getAuthorizationHeader();
expect($header)->toBe('MAC access_1234567890');
});
it('creates from array correctly', function () {
$expiresAt = Timestamp::now()->add(Duration::fromSeconds(3600));
$array = [
'access_token' => 'access_1234567890',
'refresh_token' => 'refresh_1234567890',
'token_type' => 'Bearer',
'expires_at' => $expiresAt->format('Y-m-d H:i:s'),
'scope' => 'read write',
];
$token = OAuthToken::fromArray($array);
expect($token->accessToken->toString())->toBe('access_1234567890');
expect($token->refreshToken->toString())->toBe('refresh_1234567890');
expect($token->tokenType)->toBe(TokenType::BEARER);
expect($token->scope->toArray())->toBe(['read', 'write']);
});
});