5 use Civi\Test\HttpTestTrait
;
6 use CRM_Authx_ExtensionUtil
as E
;
7 use Civi\Test\EndToEndInterface
;
8 use GuzzleHttp\Cookie\CookieJar
;
9 use GuzzleHttp\Psr7\AppendStream
;
10 use GuzzleHttp\Psr7\Request
;
11 use GuzzleHttp\Psr7\Uri
;
12 use Psr\Http\Message\ResponseInterface
;
13 use function GuzzleHttp\Psr7\stream_for
;
16 * This is a matrix-style test which assesses all supported permutations of
20 class AllFlowsTest
extends \PHPUnit\Framework\TestCase
implements EndToEndInterface
{
25 * Backup copy of the original settings.
29 protected $settingsBackup;
32 * List of CMS-dependent quirks that should be ignored during testing.
35 protected $quirks = [];
37 public static function setUpBeforeClass(): void
{
42 \CRM_Utils_System
::synchronizeUsers();
49 public function setUp(): void
{
51 'Joomla' => ['sendsExcessCookies', 'authErrorShowsForm'],
52 'WordPress' => ['sendsExcessCookies'],
54 $this->quirks
= $quirks[CIVICRM_UF
] ??
[];
57 $this->settingsBackup
= [];
58 foreach (\Civi\Authx\Meta
::getFlowTypes() as $flowType) {
59 foreach (["authx_{$flowType}_cred", "authx_{$flowType}_user", "authx_guards"] as $setting) {
60 $this->settingsBackup
[$setting] = \Civi
::settings()->get($setting);
64 \Civi
::settings()->set('authx_guards', []);
67 public function tearDown(): void
{
68 foreach ($this->settingsBackup
as $setting => $value) {
69 \Civi
::settings()->set($setting, $value);
74 public function getStatelessExamples() {
76 $exs[] = ['pass', 'param'];
77 $exs[] = ['pass', 'header'];
78 $exs[] = ['pass', 'xheader'];
79 $exs[] = ['api_key', 'param'];
80 $exs[] = ['api_key', 'header'];
81 $exs[] = ['api_key', 'xheader'];
82 $exs[] = ['jwt', 'param'];
83 $exs[] = ['jwt', 'header'];
84 $exs[] = ['jwt', 'xheader'];
88 public function getCredTypes() {
96 public function testAnonymous(): void
{
97 $http = $this->createGuzzle(['http_errors' => FALSE]);
99 /** @var \Psr\Http\Message\RequestInterface $request */
100 $request = $this->requestMyContact();
101 $response = $http->send($request);
102 $this->assertAnonymousContact($response);
106 * Send a request using a stateless protocol. Assert that identities are setup correctly.
108 * @param string $credType
109 * The type of credential to put in the `Authorization:` header.
110 * @param string $flowType
111 * The "flow" determines how the credential is added on top of the base-request (e.g. adding a parameter or header).
112 * @throws \CiviCRM_API3_Exception
113 * @throws \GuzzleHttp\Exception\GuzzleException
114 * @dataProvider getStatelessExamples
116 public function testStatelessContactOnly($credType, $flowType): void
{
117 if ($credType === 'pass') {
118 $this->assertTrue(TRUE, 'No need to test password credentials with non-user contacts');
121 $http = $this->createGuzzle(['http_errors' => FALSE]);
123 /** @var \Psr\Http\Message\RequestInterface $request */
124 $request = $this->applyAuth($this->requestMyContact(), $credType, $flowType, $this->getLebowskiCID());
126 // Phase 1: Request fails if this credential type is not enabled
127 \Civi
::settings()->set("authx_{$flowType}_cred", []);
128 $response = $http->send($request);
129 $this->assertFailedDueToProhibition($response);
131 // Phase 2: Request succeeds if this credential type is enabled
132 \Civi
::settings()->set("authx_{$flowType}_cred", [$credType]);
133 $response = $http->send($request);
134 $this->assertMyContact($this->getLebowskiCID(), NULL, $credType, $flowType, $response);
135 if (!in_array('sendsExcessCookies', $this->quirks
)) {
136 $this->assertNoCookies($response);
141 * Send a request using a stateless protocol. Assert that identities are setup correctly.
143 * @param string $credType
144 * The type of credential to put in the `Authorization:` header.
145 * @param string $flowType
146 * The "flow" determines how the credential is added on top of the base-request (e.g. adding a parameter or header).
147 * @throws \CiviCRM_API3_Exception
148 * @throws \GuzzleHttp\Exception\GuzzleException
149 * @dataProvider getStatelessExamples
151 public function testStatelessUserContact($credType, $flowType): void
{
152 $http = $this->createGuzzle(['http_errors' => FALSE]);
154 /** @var \Psr\Http\Message\RequestInterface $request */
155 $request = $this->applyAuth($this->requestMyContact(), $credType, $flowType, $this->getDemoCID());
157 // Phase 1: Request fails if this credential type is not enabled
158 \Civi
::settings()->set("authx_{$flowType}_cred", []);
159 $response = $http->send($request);
160 $this->assertFailedDueToProhibition($response);
162 // Phase 2: Request succeeds if this credential type is enabled
163 \Civi
::settings()->set("authx_{$flowType}_cred", [$credType]);
164 $response = $http->send($request);
165 $this->assertMyContact($this->getDemoCID(), $this->getDemoUID(), $credType, $flowType, $response);
166 if (!in_array('sendsExcessCookies', $this->quirks
)) {
167 $this->assertNoCookies($response);
172 * The setting "authx_guard" may be used to require (or not require) the site_key.
174 * @throws \CiviCRM_API3_Exception
175 * @throws \GuzzleHttp\Exception\GuzzleException
177 public function testStatelessGuardSiteKey() {
178 if (!defined('CIVICRM_SITE_KEY')) {
179 $this->markTestIncomplete("Cannot run test without CIVICRM_SITE_KEY");
182 $addParam = function($request, $key, $value) {
183 $query = $request->getUri()->getQuery();
184 return $request->withUri(
185 $request->getUri()->withQuery($query . '&' . urlencode($key) . '=' . urlencode($value))
189 [$credType, $flowType] = ['pass', 'header'];
190 $http = $this->createGuzzle(['http_errors' => FALSE]);
191 \Civi
::settings()->set("authx_{$flowType}_cred", [$credType]);
193 /** @var \Psr\Http\Message\RequestInterface $request */
194 $request = $this->applyAuth($this->requestMyContact(), $credType, $flowType, $this->getDemoCID());
196 // Request OK. Policy requires site_key, and we have one.
197 \Civi
::settings()->set("authx_guards", ['site_key']);
198 $response = $http->send($request->withHeader('X-Civi-Key', CIVICRM_SITE_KEY
));
199 $this->assertMyContact($this->getDemoCID(), $this->getDemoUID(), $credType, $flowType, $response);
201 // Request OK. Policy does not require site_key, and we do not have one
202 \Civi
::settings()->set("authx_guards", []);
203 $response = $http->send($request);
204 $this->assertMyContact($this->getDemoCID(), $this->getDemoUID(), $credType, $flowType, $response);
206 // Request fails. Policy requires site_key, but we don't have the wrong value.
207 \Civi
::settings()->set("authx_guards", ['site_key']);
208 $response = $http->send($request->withHeader('X-Civi-Key', 'not-the-site-key'));
209 $this->assertFailedDueToProhibition($response);
211 // Request fails. Policy requires site_key, but we don't have one.
212 \Civi
::settings()->set("authx_guards", ['site_key']);
213 $response = $http->send($request);
214 $this->assertFailedDueToProhibition($response);
218 * The login flow allows you use 'civicrm/authx/login' and 'civicrm/authx/logout'
219 * to setup/teardown a session.
221 * @param string $credType
222 * The type of credential to put in the login request.
223 * @throws \CiviCRM_API3_Exception
224 * @throws \GuzzleHttp\Exception\GuzzleException
225 * @dataProvider getCredTypes
227 public function testStatefulLoginAllowed($credType): void
{
229 $credFunc = 'cred' . ucfirst(preg_replace(';[^a-zA-Z0-9];', '', $credType));
231 // Phase 1: Some pages are not accessible.
232 $http = $this->createGuzzle(['http_errors' => FALSE]);
233 $http->get('civicrm/user');
234 $this->assertDashboardUnauthorized();
236 // Phase 2: Request succeeds if this credential type is enabled
237 $cookieJar = new CookieJar();
238 $http = $this->createGuzzle(['http_errors' => FALSE, 'cookies' => $cookieJar]);
239 \Civi
::settings()->set("authx_{$flowType}_cred", [$credType]);
240 $response = $http->post('civicrm/authx/login', [
241 'form_params' => ['_authx' => $this->$credFunc($this->getDemoCID())],
243 $this->assertMyContact($this->getDemoCID(), $this->getDemoUID(), $credType, $flowType, $response);
244 $this->assertHasCookies($response);
246 // Phase 3: We can use cookies to request other pages
247 $response = $http->get('civicrm/authx/id');
248 $this->assertMyContact($this->getDemoCID(), $this->getDemoUID(), $credType, $flowType, $response);
249 $response = $http->get('civicrm/user');
250 $this->assertDashboardOk();
252 // Phase 4: After logout, requests should fail.
253 $oldCookies = clone $cookieJar;
254 $http->get('civicrm/authx/logout');
255 $this->assertStatusCode(200);
256 $http->get('civicrm/user');
257 $this->assertDashboardUnauthorized();
259 $httpHaxor = $this->createGuzzle(['http_errors' => FALSE, 'cookies' => $oldCookies]);
260 $httpHaxor->get('civicrm/user');
261 $this->assertDashboardUnauthorized();
265 * The login flow 'civicrm/authx/login' may be prohibited by policy.
267 * @param string $credType
268 * The type of credential to put in the login request.
269 * @throws \CiviCRM_API3_Exception
270 * @throws \GuzzleHttp\Exception\GuzzleException
271 * @dataProvider getCredTypes
273 public function testStatefulLoginProhibited($credType): void
{
275 $http = $this->createGuzzle(['http_errors' => FALSE]);
276 $credFunc = 'cred' . ucfirst(preg_replace(';[^a-zA-Z0-9];', '', $credType));
278 \Civi
::settings()->set("authx_{$flowType}_cred", []);
279 $response = $http->post('civicrm/authx/login', [
280 'form_params' => ['_authx' => $this->$credFunc($this->getDemoCID())],
282 $this->assertFailedDueToProhibition($response);
286 * The auto-login flow allows you to request a specific page with specific
287 * credentials. The new session is setup, and the page is displayed.
289 * @param string $credType
290 * The type of credential to put in the login request.
291 * @throws \CiviCRM_API3_Exception
292 * @throws \GuzzleHttp\Exception\GuzzleException
293 * @dataProvider getCredTypes
295 public function testStatefulAutoAllowed($credType): void
{
297 $cookieJar = new CookieJar();
298 $http = $this->createGuzzle(['http_errors' => FALSE, 'cookies' => $cookieJar]);
300 /** @var \Psr\Http\Message\RequestInterface $request */
301 $request = $this->applyAuth($this->requestMyContact(), $credType, $flowType, $this->getDemoCID());
303 \Civi
::settings()->set("authx_{$flowType}_cred", [$credType]);
304 $this->assertEquals(0, $cookieJar->count());
305 $response = $http->send($request);
306 $this->assertTrue($cookieJar->count() >= 1);
307 $this->assertMyContact($this->getDemoCID(), $this->getDemoUID(), $credType, $flowType, $response);
309 // FIXME: Assert that re-using cookies yields correct result.
313 * The auto-login flow allows you to request a specific page with specific
314 * credentials. The new session is setup, and the page is displayed.
316 * @param string $credType
317 * The type of credential to put in the login request.
318 * @throws \CiviCRM_API3_Exception
319 * @throws \GuzzleHttp\Exception\GuzzleException
320 * @dataProvider getCredTypes
322 public function testStatefulAutoProhibited($credType): void
{
324 $cookieJar = new CookieJar();
325 $http = $this->createGuzzle(['http_errors' => FALSE, 'cookies' => $cookieJar]);
327 /** @var \Psr\Http\Message\RequestInterface $request */
328 $request = $this->applyAuth($this->requestMyContact(), $credType, $flowType, $this->getDemoCID());
330 \Civi
::settings()->set("authx_{$flowType}_cred", []);
331 $response = $http->send($request);
332 $this->assertFailedDueToProhibition($response);
336 * Create a session for $demoCID. Within the session, make a single
337 * stateless request as $lebowskiCID.
339 * @throws \CiviCRM_API3_Exception
340 * @throws \GuzzleHttp\Exception\GuzzleException
342 public function testStatefulStatelessOverlap(): void
{
343 \Civi
::settings()->set("authx_login_cred", ['api_key']);
344 \Civi
::settings()->set("authx_header_cred", ['api_key']);
346 $cookieJar = new CookieJar();
347 $http = $this->createGuzzle(['http_errors' => FALSE, 'cookies' => $cookieJar]);
349 // Phase 1: Login, create a session.
350 $response = $http->post('civicrm/authx/login', [
351 'form_params' => ['_authx' => $this->credApikey($this->getDemoCID())],
353 $this->assertMyContact($this->getDemoCID(), $this->getDemoUID(), 'api_key', 'login', $response);
354 $this->assertHasCookies($response);
355 $response = $http->get('civicrm/authx/id');
356 $this->assertMyContact($this->getDemoCID(), $this->getDemoUID(), 'api_key', 'login', $response);
358 // Phase 2: Make a single, stateless request with different creds
359 /** @var \Psr\Http\Message\RequestInterface $request */
360 $request = $this->applyAuth($this->requestMyContact(), 'api_key', 'header', $this->getLebowskiCID());
361 $response = $http->send($request);
362 $this->assertFailedDueToProhibition($response);
363 // The following assertion merely identifies current behavior. If you can get it working generally, then huzza.
364 $this->assertBodyRegexp(';Session already active;', $response);
365 // $this->assertMyContact($this->getLebowskiCID(), NULL, $response);
366 // $this->assertNoCookies($response);
368 // Phase 3: Original session is still valid
369 $response = $http->get('civicrm/authx/id');
370 $this->assertMyContact($this->getDemoCID(), $this->getDemoUID(), 'api_key', 'login', $response);
374 * This consumer intends to make stateless requests with a handful of different identities,
375 * but their browser happens to be cookie-enabled. Ensure that identities do not leak between requests.
377 * @throws \CiviCRM_API3_Exception
378 * @throws \GuzzleHttp\Exception\GuzzleException
380 public function testMultipleStateless(): void
{
381 \Civi
::settings()->set("authx_header_cred", ['api_key']);
382 $cookieJar = new CookieJar();
383 $http = $this->createGuzzle(['http_errors' => FALSE, 'cookies' => $cookieJar]);
385 /** @var \Psr\Http\Message\RequestInterface $request */
387 // Alternate calls among (A)nonymous, (D)emo, and (L)ebowski
388 $planSteps = 'LADA LDLD DDLLAA';
391 for ($i = 0; $i < strlen($planSteps); $i++
) {
392 switch ($planSteps[$i]) {
394 $request = $this->applyAuth($this->requestMyContact(), 'api_key', 'header', $this->getLebowskiCID());
395 $response = $http->send($request);
396 $this->assertMyContact($this->getLebowskiCID(), NULL, 'api_key', 'header', $response, 'Expected Lebowski in step #' . $i);
401 $request = $this->requestMyContact();
402 $response = $http->send($request);
403 $this->assertAnonymousContact($response);
408 $request = $this->applyAuth($this->requestMyContact(), 'api_key', 'header', $this->getDemoCID());
409 $response = $http->send($request);
410 $this->assertMyContact($this->getDemoCID(), $this->getDemoUID(), 'api_key', 'header', $response, 'Expected demo in step #' . $i);
419 $this->fail('Unrecognized step #' . $i);
423 $this->assertEquals($actualSteps, $planSteps);
427 * Civi's test suite includes middleware that will add JWT tokens to outgoing requests.
429 * This test tries a few permutations with different principals ("demo", "Lebowski"),
430 * different identifier fields (authx_user, authx_contact_id), and different
431 * flows (param/header/xheader).
433 * @throws \CiviCRM_API3_Exception
434 * @throws \GuzzleHttp\Exception\GuzzleException
436 public function testJwtMiddleware() {
437 // HTTP GET with a specific user. Choose flow automatically.
438 $response = $this->createGuzzle()->get('civicrm/authx/id', [
439 'authx_user' => $GLOBALS['_CV']['DEMO_USER'],
441 $this->assertMyContact($this->getDemoCID(), $this->getDemoUID(), 'jwt', 'param', $response);
443 // HTTP GET with a specific contact. Choose flow automatically.
444 $response = $this->createGuzzle()->get('civicrm/authx/id', [
445 'authx_contact_id' => $this->getDemoCID(),
447 $this->assertMyContact($this->getDemoCID(), $this->getDemoUID(), 'jwt', 'param', $response);
449 // HTTP POST with a specific contact. Per-client default.
450 $response = $this->createGuzzle([
451 'authx_contact_id' => $this->getLebowskiCID(),
452 ])->post('civicrm/authx/id');
453 $this->assertMyContact($this->getLebowskiCID(), NULL, 'jwt', 'param', $response);
455 // Using explicit flow options...
456 foreach (['param', 'xheader', 'header'] as $flowType) {
457 \Civi
::settings()->set("authx_{$flowType}_cred", ['jwt']);
458 $response = $this->createGuzzle()->get('civicrm/authx/id', [
459 'authx_contact_id' => $this->getDemoCID(),
460 'authx_flow' => $flowType,
462 $this->assertMyContact($this->getDemoCID(), $this->getDemoUID(), 'jwt', $flowType, $response);
467 * Filter a request, applying the given authentication options
469 * @param \Psr\Http\Message\RequestInterface $request
470 * @param string $credType
471 * Ex: 'pass', 'jwt', 'api_key'
472 * @param string $flowType
473 * Ex: 'param', 'header', 'xheader'
475 * @return \Psr\Http\Message\RequestInterface
477 protected function applyAuth($request, $credType, $flowType, $cid) {
478 $credFunc = 'cred' . ucfirst(preg_replace(';[^a-zA-Z0-9];', '', $credType));
479 $flowFunc = 'auth' . ucfirst(preg_replace(';[^a-zA-Z0-9];', '', $flowType));
480 return $this->$flowFunc($request, $this->$credFunc($cid));
483 // ------------------------------------------------
484 // Library: Base requests
487 * Make an AJAX request with info about the current contact.
489 * @return \GuzzleHttp\Psr7\Request
491 public function requestMyContact() {
492 $p = (['where' => [['id', '=', 'user_contact_id']]]);
493 $uri = (new Uri('civicrm/authx/id'))
494 ->withQuery('params=' . urlencode(json_encode($p)));
495 $req = new Request('GET', $uri);
500 * Assert the AJAX request provided the expected contact.
503 * The expected contact ID
504 * @param int|null $uid
505 * The expected user ID
506 * @param string $credType
507 * @param string $flow
508 * @param \Psr\Http\Message\ResponseInterface $response
510 public function assertMyContact($cid, $uid, $credType, $flow, ResponseInterface
$response): void
{
511 $this->assertContentType('application/json', $response);
512 $this->assertStatusCode(200, $response);
513 $j = json_decode((string) $response->getBody(), 1);
514 $formattedFailure = $this->formatFailure($response);
515 $this->assertEquals($cid, $j['contact_id'], "Response did not give expected contact ID\n" . $formattedFailure);
516 $this->assertEquals($uid, $j['user_id'], "Response did not give expected user ID\n" . $formattedFailure);
517 if ($flow !== NULL) {
518 $this->assertEquals($flow, $j['flow'], "Response did not give expected flow type\n" . $formattedFailure);
520 if ($credType !== NULL) {
521 $this->assertEquals($credType, $j['cred'], "Response did not give expected cred type\n" . $formattedFailure);
526 * Assert the AJAX request provided empty contact information
528 * @param \Psr\Http\Message\ResponseInterface $response
530 public function assertAnonymousContact(ResponseInterface
$response): void
{
531 $formattedFailure = $this->formatFailure($response);
532 $this->assertContentType('application/json', $response);
533 $this->assertStatusCode(200, $response);
534 $j = json_decode((string) $response->getBody(), 1);
535 if (json_last_error() !== JSON_ERROR_NONE ||
empty($j)) {
536 $this->fail('Malformed JSON' . $formattedFailure);
538 $this->assertTrue(array_key_exists('contact_id', $j) && $j['contact_id'] === NULL, 'contact_id should be null' . $formattedFailure);
539 $this->assertTrue(array_key_exists('user_id', $j) && $j['user_id'] === NULL, 'user_id should be null' . $formattedFailure);
543 * Assert that the $response indicates the user cannot view the dashboard.
545 * @param \Psr\Http\Message\ResponseInterface $response
547 public function assertDashboardUnauthorized($response = NULL): void
{
548 $response = $this->resolveResponse($response);
549 if (!in_array('authErrorShowsForm', $this->quirks
)) {
550 $this->assertStatusCode(403, $response);
553 (bool) preg_match(';crm-dashboard-groups;', (string) $response->getBody()),
554 'Response should not contain a dashboard' . $this->formatFailure($response)
558 public function assertDashboardOk($response = NULL): void
{
559 $response = $this->resolveResponse($response);
560 $this->assertStatusCode(200, $response);
561 $this->assertContentType('text/html', $response);
562 // If the first two assertions pass but the next fails, then... perhaps the
563 // local site permissions are wrong?
565 (bool) preg_match(';crm-dashboard-groups;', (string) $response->getBody()),
566 'Response should contain a dashboard' . $this->formatFailure($response)
570 // ------------------------------------------------
571 // Library: Flow functions
574 * Add query parameter ("&_authx=<CRED>").
576 * @param \GuzzleHttp\Psr7\Request $request
577 * @param string $cred
578 * The credential add to the request (e.g. "Basic ASDF==" or "Bearer FDSA").
579 * @return \GuzzleHttp\Psr7\Request
581 public function authParam(Request
$request, $cred) {
582 $query = $request->getUri()->getQuery();
583 return $request->withUri(
584 $request->getUri()->withQuery($query . '&_authx=' . urlencode($cred))
589 * Add query parameter ("&_authx=<CRED>&_authxSes=1").
591 * @param \GuzzleHttp\Psr7\Request $request
592 * @param string $cred
593 * The credential add to the request (e.g. "Basic ASDF==" or "Bearer FDSA").
594 * @return \GuzzleHttp\Psr7\Request
596 public function authAuto(Request
$request, $cred) {
597 $query = $request->getUri()->getQuery();
598 return $request->withUri(
599 $request->getUri()->withQuery($query . '&_authx=' . urlencode($cred) . '&_authxSes=1')
603 public function authLogin(Request
$request, $cred) {
604 return $request->withMethod('POST')
605 ->withBody(new AppendStream([
606 stream_for('_authx=' . urlencode($cred) . '&'),
611 public function authHeader(Request
$request, $cred) {
612 return $request->withHeader('Authorization', $cred);
615 public function authXHeader(Request
$request, $cred) {
616 return $request->withHeader('X-Civi-Auth', $cred);
619 public function authNone(Request
$request, $cred) {
623 // ------------------------------------------------
624 // Library: Credential functions
629 * The credential add to the request (e.g. "Basic ASDF==" or "Bearer FDSA").
631 public function credPass($cid) {
632 if ($cid === $this->getDemoCID()) {
633 return 'Basic ' . base64_encode($GLOBALS['_CV']['DEMO_USER'] . ':' . $GLOBALS['_CV']['DEMO_PASS']);
636 $this->fail("This test does have the password the requested contact.");
640 public function credApikey($cid) {
641 $api_key = md5(\random_bytes
(16));
642 \
civicrm_api3('Contact', 'create', [
644 'api_key' => $api_key,
646 return 'Bearer ' . $api_key;
649 public function credJwt($cid) {
650 if (empty(\Civi
::service('crypto.registry')->findKeysByTag('SIGN'))) {
651 $this->markTestIncomplete('Cannot test JWT. No CIVICRM_SIGN_KEYS are defined.');
653 $token = \Civi
::service('crypto.jwt')->encode([
654 'exp' => time() +
60 * 60,
658 return 'Bearer ' . $token;
661 public function credNone($cid) {
666 * @param \Psr\Http\Message\ResponseInterface $response
668 private function assertFailedDueToProhibition($response): void
{
669 $this->assertBodyRegexp(';HTTP 401;', $response);
670 $this->assertContentType('text/plain', $response);
671 if (!in_array('sendsExcessCookies', $this->quirks
)) {
672 $this->assertNoCookies($response);
674 $this->assertStatusCode(401, $response);
679 * @param \Psr\Http\Message\ResponseInterface $response
681 private function assertNoCookies($response = NULL) {
682 $response = $this->resolveResponse($response);
684 preg_grep('/Set-Cookie/i', array_keys($response->getHeaders())),
685 'Response should not have cookies' . $this->formatFailure($response)
691 * @param \Psr\Http\Message\ResponseInterface $response
693 private function assertHasCookies($response = NULL) {
694 $response = $this->resolveResponse($response);
695 $this->assertNotEmpty(
696 preg_grep('/Set-Cookie/i', array_keys($response->getHeaders())),
697 'Response should have cookies' . $this->formatFailure($response)
704 * @param \Psr\Http\Message\ResponseInterface $response
706 private function assertBodyRegexp($regexp, $response = NULL) {
707 $response = $this->resolveResponse($response);
708 $this->assertRegexp($regexp, (string) $response->getBody(),
709 'Response body does not match pattern' . $this->formatFailure($response));
715 * @throws \CiviCRM_API3_Exception
717 private function getDemoCID(): int {
718 if (!isset(\Civi
::$statics[__CLASS__
]['demoId'])) {
719 \Civi
::$statics[__CLASS__
]['demoId'] = (int) \
civicrm_api3('Contact', 'getvalue', [
720 'id' => '@user:' . $GLOBALS['_CV']['DEMO_USER'],
724 return \Civi
::$statics[__CLASS__
]['demoId'];
727 private function getDemoUID(): int {
728 return \CRM_Core_Config
::singleton()->userSystem
->getUfId($GLOBALS['_CV']['DEMO_USER']);
731 public function getLebowskiCID() {
732 if (!isset(\Civi
::$statics[__CLASS__
]['lebowskiCID'])) {
733 $contact = \
civicrm_api3('Contact', 'create', [
734 'contact_type' => 'Individual',
735 'first_name' => 'Jeffrey',
736 'last_name' => 'Lebowski',
737 'external_identifier' => __CLASS__
,
739 'match' => 'external_identifier',
742 \Civi
::$statics[__CLASS__
]['lebowskiCID'] = $contact['id'];
744 return \Civi
::$statics[__CLASS__
]['lebowskiCID'];