toBeNull(); }); it('returns a Google provider correctly', function () { $params = [ 'clientId' => 'id', 'clientSecret' => 'secret', 'redirectUri' => 'uri' ]; $provider = CactiOAuth::getProvider('google', $params); expect($provider)->toBeInstanceOf(Google::class); }); it('returns a Keycloak provider correctly', function () { $params = [ 'clientId' => 'id', 'clientSecret' => 'secret', 'redirectUri' => 'uri', 'authServerUrl' => 'http://localhost:8080/auth', 'realm' => 'master' ]; $provider = CactiOAuth::getProvider('keycloak', $params); expect($provider)->toBeInstanceOf(\Stevenmaguire\OAuth2\Client\Provider\Keycloak::class); }); it('returns default options for known providers', function () { $options = CactiOAuth::getDefaultOptions('google'); expect($options)->toHaveKey('scope'); expect($options['scope'])->toContain('https://mail.google.com/'); }); it('returns empty options for unknown providers', function () { expect(CactiOAuth::getDefaultOptions('unknown'))->toBeEmpty(); }); it('returns Microsoft, Azure, Yahoo providers', function () { $params = ['clientId' => 'id', 'clientSecret' => 'secret', 'redirectUri' => 'uri']; expect(CactiOAuth::getProvider('microsoft', $params)) ->toBeInstanceOf(\Stevenmaguire\OAuth2\Client\Provider\Microsoft::class); expect(CactiOAuth::getProvider('azure', $params + ['tenantId' => 't'])) ->toBeInstanceOf(\Greew\OAuth2\Client\Provider\Azure::class); expect(CactiOAuth::getProvider('yahoo', $params)) ->toBeInstanceOf(\Hayageek\OAuth2\Client\Provider\Yahoo::class); }); it('returns expected default scopes per provider', function () { expect(CactiOAuth::getDefaultOptions('microsoft')['scope'])->toContain('wl.imap'); expect(CactiOAuth::getDefaultOptions('azure')['scope'])->toContain('offline_access'); expect(CactiOAuth::getDefaultOptions('keycloak'))->toBe([]); }); it('returns null when provider name has wrong case', function () { $params = ['clientId' => 'id', 'clientSecret' => 'secret', 'redirectUri' => 'uri']; expect(CactiOAuth::getProvider('GOOGLE', $params))->toBeNull(); }); it('returns null for an empty provider name', function () { expect(CactiOAuth::getProvider('', []))->toBeNull(); }); it('returns the full Google scope set in defaults', function () { expect(CactiOAuth::getDefaultOptions('google')['scope'])->toContain('https://mail.google.com/'); }); it('returns both Microsoft scope entries in defaults', function () { $scope = CactiOAuth::getDefaultOptions('microsoft')['scope']; expect($scope)->toContain('wl.imap'); expect($scope)->toContain('wl.offline_access'); }); it('returns both Azure scope entries in defaults', function () { $scope = CactiOAuth::getDefaultOptions('azure')['scope']; expect($scope)->toContain('https://outlook.office.com/SMTP.Send'); expect($scope)->toContain('offline_access'); }); it('returns empty defaults for yahoo', function () { expect(CactiOAuth::getDefaultOptions('yahoo'))->toBe([]); }); it('returns empty defaults for keycloak', function () { expect(CactiOAuth::getDefaultOptions('keycloak'))->toBe([]); }); it('returns empty defaults for an unknown provider', function () { expect(CactiOAuth::getDefaultOptions('unknown'))->toBe([]); }); it('returns empty defaults for an empty provider name', function () { expect(CactiOAuth::getDefaultOptions(''))->toBe([]); }); it('runtime OAuth entrypoints load the flat CactiOAuth helper before use', function () { $oauth2 = file_get_contents(dirname(__DIR__, 4) . '/oauth2.php'); $functions = file_get_contents(dirname(__DIR__, 4) . '/lib/functions.php'); expect($oauth2)->toContain("require_once(CACTI_PATH_LIBRARY . '/CactiOAuth.php')") ->and($oauth2)->toContain('CactiOAuth::getProvider') ->and($functions)->toContain("require_once(CACTI_PATH_LIBRARY . '/CactiOAuth.php')") ->and($functions)->toContain('CactiOAuth::getProvider'); }); it('OAuth callbacks fail closed when the session state is absent or mismatched', function () { $oauth2 = file_get_contents(dirname(__DIR__, 4) . '/oauth2.php'); expect($oauth2)->toContain("empty(\$_SESSION['oauth2state'])") ->and($oauth2)->toContain('hash_equals((string) $_SESSION[\'oauth2state\'], (string) grv(\'state\'))') ->and($oauth2)->toContain("unset(\$_SESSION['oauth2state']);"); });