Completed
Pull Request — master (#224)
by Pieter
10:30
created

DrupalContext::assertLoggedInByName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 11
rs 9.4286
cc 2
eloc 5
nc 2
nop 1
1
<?php
2
3
namespace Drupal\DrupalExtension\Context;
4
5
use Behat\Behat\Context\TranslatableContext;
6
use Behat\Mink\Element\Element;
7
8
use Behat\Gherkin\Node\TableNode;
9
10
/**
11
 * Provides pre-built step definitions for interacting with Drupal.
12
 */
13
class DrupalContext extends RawDrupalContext implements TranslatableContext {
14
15
  /**
16
   * Returns list of definition translation resources paths.
17
   *
18
   * @return array
19
   */
20
  public static function getTranslationResources() {
21
    return glob(__DIR__ . '/../../../../i18n/*.xliff');
22
  }
23
24
  /**
25
   * @Given I am an anonymous user
26
   * @Given I am not logged in
27
   */
28
  public function assertAnonymousUser() {
29
    // Verify the user is logged out.
30
    if ($this->loggedIn()) {
31
      $this->logout();
32
    }
33
  }
34
35
  /**
36
   * Creates and authenticates a user with the given role(s).
37
   *
38
   * @Given I am logged in as a user with the :role role(s)
39
   * @Given I am logged in as a/an :role
40
   */
41
  public function assertAuthenticatedByRole($role) {
42
    // Check if a user with this role is already logged in.
43
    if (!$this->loggedInWithRole($role)) {
44
      // Create user (and project)
45
      $user = (object) array(
46
        'name' => $this->getRandom()->name(8),
47
        'pass' => $this->getRandom()->name(16),
48
        'role' => $role,
49
      );
50
      $user->mail = "{$user->name}@example.com";
51
52
      $this->userCreate($user);
53
54
      $roles = explode(',', $role);
55
      $roles = array_map('trim', $roles);
56 View Code Duplication
      foreach ($roles as $role) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
57
        if (!in_array(strtolower($role), array('authenticated', 'authenticated user'))) {
58
          // Only add roles other than 'authenticated user'.
59
          $this->getDriver()->userAddRole($user, $role);
60
        }
61
      }
62
63
      // Login.
64
      $this->login();
65
    }
66
  }
67
68
  /**
69
   * Creates and authenticates a user with the given role(s) and given fields.
70
   * | field_user_name     | John  |
71
   * | field_user_surname  | Smith |
72
   * | ...                 | ...   |
73
   *
74
   * @Given I am logged in as a user with the :role role(s) and I have the following fields:
75
   */
76
  public function assertAuthenticatedByRoleWithGivenFields($role, TableNode $fields) {
77
    // Check if a user with this role is already logged in.
78
    if (!$this->loggedInWithRole($role)) {
79
      // Create user (and project)
80
      $user = (object) array(
81
        'name' => $this->getRandom()->name(8),
82
        'pass' => $this->getRandom()->name(16),
83
        'role' => $role,
84
      );
85
      $user->mail = "{$user->name}@example.com";
86
87
      // Assign fields to user before creation.
88
      foreach ($fields->getRowsHash() as $field => $value) {
89
        $user->{$field} = $value;
90
      }
91
92
      $this->userCreate($user);
93
94
      $roles = explode(',', $role);
95
      $roles = array_map('trim', $roles);
96 View Code Duplication
      foreach ($roles as $role) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
97
        if (!in_array(strtolower($role), array('authenticated', 'authenticated user'))) {
98
          // Only add roles other than 'authenticated user'.
99
          $this->getDriver()->userAddRole($user, $role);
100
        }
101
      }
102
103
      // Login.
104
      $this->login();
105
    }
106
  }
107
108
109
  /**
110
   * @Given I am logged in as :name
111
   */
112
  public function assertLoggedInByName($name) {
113
    if (!isset($this->users[$name])) {
114
      throw new \Exception(sprintf('No user with %s name is registered with the driver.', $name));
115
    }
116
117
    // Change internal current user.
118
    $this->user = $this->users[$name];
119
120
    // Login.
121
    $this->login();
122
  }
123
124
  /**
125
   * @Given I am logged in as a user with the :permissions permission(s)
126
   */
127
  public function assertLoggedInWithPermissions($permissions) {
128
    // Create user.
129
    $user = (object) array(
130
      'name' => $this->getRandom()->name(8),
131
      'pass' => $this->getRandom()->name(16),
132
    );
133
    $user->mail = "{$user->name}@example.com";
134
    $this->userCreate($user);
135
136
    // Create and assign a temporary role with given permissions.
137
    $permissions = explode(',', $permissions);
138
    $rid = $this->getDriver()->roleCreate($permissions);
139
    $this->getDriver()->userAddRole($user, $rid);
140
    $this->roles[] = $rid;
141
142
    // Login.
143
    $this->login();
144
  }
145
146
  /**
147
   * Retrieve a table row containing specified text from a given element.
148
   *
149
   * @param \Behat\Mink\Element\Element
150
   * @param string
151
   *   The text to search for in the table row.
152
   *
153
   * @return \Behat\Mink\Element\NodeElement
154
   *
155
   * @throws \Exception
156
   */
157
  public function getTableRow(Element $element, $search) {
158
    $rows = $element->findAll('css', 'tr');
159
    if (empty($rows)) {
160
      throw new \Exception(sprintf('No rows found on the page %s', $this->getSession()->getCurrentUrl()));
161
    }
162
    foreach ($rows as $row) {
163
      if (strpos($row->getText(), $search) !== FALSE) {
164
        return $row;
165
      }
166
    }
167
    throw new \Exception(sprintf('Failed to find a row containing "%s" on the page %s', $search, $this->getSession()->getCurrentUrl()));
168
  }
169
170
  /**
171
   * Find text in a table row containing given text.
172
   *
173
   * @Then I should see (the text ):text in the ":rowText" row
174
   */
175
  public function assertTextInTableRow($text, $rowText) {
176
    $row = $this->getTableRow($this->getSession()->getPage(), $rowText);
177
    if (strpos($row->getText(), $text) === FALSE) {
178
      throw new \Exception(sprintf('Found a row containing "%s", but it did not contain the text "%s".', $rowText, $text));
179
    }
180
  }
181
182
  /**
183
   * Attempts to find a link in a table row containing giving text. This is for
184
   * administrative pages such as the administer content types screen found at
185
   * `admin/structure/types`.
186
   *
187
   * @Given I click :link in the :rowText row
188
   * @Then I (should )see the :link in the :rowText row
189
   */
190
  public function assertClickInTableRow($link, $rowText) {
191
    $page = $this->getSession()->getPage();
192
    if ($link = $this->getTableRow($page, $rowText)->findLink($link)) {
193
      // Click the link and return.
194
      $link->click();
195
      return;
196
    }
197
    throw new \Exception(sprintf('Found a row containing "%s", but no "%s" link on the page %s', $rowText, $link, $this->getSession()->getCurrentUrl()));
198
  }
199
200
  /**
201
   * @Given the cache has been cleared
202
   */
203
  public function assertCacheClear() {
204
    $this->getDriver()->clearCache();
205
  }
206
207
  /**
208
   * @Given I run cron
209
   */
210
  public function assertCron() {
211
    $this->getDriver()->runCron();
212
  }
213
214
  /**
215
   * Creates content of the given type.
216
   *
217
   * @Given I am viewing a/an :type (content )with the title :title
218
   * @Given a/an :type (content )with the title :title
219
   */
220 View Code Duplication
  public function createNode($type, $title) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
221
    // @todo make this easily extensible.
222
    $node = (object) array(
223
      'title' => $title,
224
      'type' => $type,
225
      'body' => $this->getRandom()->name(255),
226
    );
227
    $saved = $this->nodeCreate($node);
228
    // Set internal page on the new node.
229
    $this->getSession()->visit($this->locatePath('/node/' . $saved->nid));
230
  }
231
232
  /**
233
   * Creates content authored by the current user.
234
   *
235
   * @Given I am viewing my :type (content )with the title :title
236
   */
237
  public function createMyNode($type, $title) {
238
    if (!isset($this->user->uid)) {
239
      throw new \Exception(sprintf('There is no current logged in user to create a node for.'));
240
    }
241
242
    $node = (object) array(
243
      'title' => $title,
244
      'type' => $type,
245
      'body' => $this->getRandom()->name(255),
246
      'uid' => $this->user->uid,
247
    );
248
    $saved = $this->nodeCreate($node);
249
250
    // Set internal page on the new node.
251
    $this->getSession()->visit($this->locatePath('/node/' . $saved->nid));
252
  }
253
254
  /**
255
   * Creates content of a given type provided in the form:
256
   * | title    | author     | status | created           |
257
   * | My title | Joe Editor | 1      | 2014-10-17 8:00am |
258
   * | ...      | ...        | ...    | ...               |
259
   *
260
   * @Given :type content:
261
   */
262
  public function createNodes($type, TableNode $nodesTable) {
263
    foreach ($nodesTable->getHash() as $nodeHash) {
264
      $node = (object) $nodeHash;
265
      $node->type = $type;
266
      $this->nodeCreate($node);
267
    }
268
  }
269
270
  /**
271
   * Creates content of the given type, provided in the form:
272
   * | title     | My node        |
273
   * | Field One | My field value |
274
   * | author    | Joe Editor     |
275
   * | status    | 1              |
276
   * | ...       | ...            |
277
   *
278
   * @Given I am viewing a/an :type( content):
279
   */
280
  public function assertViewingNode($type, TableNode $fields) {
281
    $node = (object) array(
282
      'type' => $type,
283
    );
284
    foreach ($fields->getRowsHash() as $field => $value) {
285
      $node->{$field} = $value;
286
    }
287
288
    $saved = $this->nodeCreate($node);
289
290
    // Set internal browser on the node.
291
    $this->getSession()->visit($this->locatePath('/node/' . $saved->nid));
292
  }
293
294
  /**
295
   * Asserts that a given content type is editable.
296
   *
297
   * @Then I should be able to edit a/an :type( content)
298
   */
299 View Code Duplication
  public function assertEditNodeOfType($type) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
300
    $node = (object) array('type' => $type);
301
    $saved = $this->nodeCreate($node);
302
303
    // Set internal browser on the node edit page.
304
    $this->getSession()->visit($this->locatePath('/node/' . $saved->nid . '/edit'));
305
306
    // Test status.
307
    $this->assertSession()->statusCodeEquals('200');
308
  }
309
310
311
  /**
312
   * Creates a term on an existing vocabulary.
313
   *
314
   * @Given I am viewing a/an :vocabulary term with the name :name
315
   * @Given a/an :vocabulary term with the name :name
316
   */
317 View Code Duplication
  public function createTerm($vocabulary, $name) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
318
    // @todo make this easily extensible.
319
    $term = (object) array(
320
      'name' => $name,
321
      'vocabulary_machine_name' => $vocabulary,
322
      'description' => $this->getRandom()->name(255),
323
    );
324
    $saved = $this->termCreate($term);
325
326
    // Set internal page on the term.
327
    $this->getSession()->visit($this->locatePath('/taxonomy/term/' . $saved->tid));
328
  }
329
330
  /**
331
   * Creates multiple users.
332
   *
333
   * Provide user data in the following format:
334
   *
335
   * | name     | mail         | roles        |
336
   * | user foo | [email protected]  | role1, role2 |
337
   *
338
   * @Given users:
339
   */
340
  public function createUsers(TableNode $usersTable) {
341
    foreach ($usersTable->getHash() as $userHash) {
342
343
      // Split out roles to process after user is created.
344
      $roles = array();
345
      if (isset($userHash['roles'])) {
346
        $roles = explode(',', $userHash['roles']);
347
        $roles = array_filter(array_map('trim', $roles));
348
        unset($userHash['roles']);
349
      }
350
351
      $user = (object) $userHash;
352
      // Set a password.
353
      if (!isset($user->pass)) {
354
        $user->pass = $this->getRandom()->name();
355
      }
356
      $this->userCreate($user);
357
358
      // Assign roles.
359
      foreach ($roles as $role) {
360
        $this->getDriver()->userAddRole($user, $role);
361
      }
362
    }
363
  }
364
365
  /**
366
   * Creates one or more terms on an existing vocabulary.
367
   *
368
   * @Given :vocabulary terms:
369
   */
370
  public function createTerms($vocabulary, TableNode $termsTable) {
371
    foreach ($termsTable->getHash() as $termsHash) {
372
      $term = (object) $termsHash;
373
      $term->vocabulary_machine_name = $vocabulary;
374
      $this->termCreate($term);
375
    }
376
  }
377
378
  /**
379
   * Creates one or more languages.
380
   *
381
   * @Given the/these (following )languages are available:
382
   *
383
   * Provide language data in the following format:
384
   *
385
   * | langcode |
386
   * | en       |
387
   * | fr       |
388
   *
389
   * @param TableNode $langcodesTable
390
   *   The table listing languages by their ISO code.
391
   */
392
  public function createLanguages(TableNode $langcodesTable) {
393
    foreach ($langcodesTable->getHash() as $row) {
394
      $language = (object) array(
395
        'langcode' => $row['languages'],
396
      );
397
      $this->languageCreate($language);
398
    }
399
  }
400
401
  /**
402
   * Installs the given module.
403
   *
404
   * @Given the :module module is enabled/installed
405
   *
406
   * @param string $module
407
   *   The name of the module to install.
408
   */
409
  public function installModule($module) {
410
    $this->installModules(array($module));
411
  }
412
413
  /**
414
   * Installs the given modules.
415
   *
416
   * @Given the following modules are enabled/installed:
417
   *
418
   * Provide the list of modules in the following format:
419
   * | modules |
420
   * | comment |
421
   * | forum   |
422
   *
423
   * @param TableNode $modulesTable
424
   *   The list of modules to install.
425
   */
426 View Code Duplication
  public function installModuleList(TableNode $modulesTable) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
427
    $modules = array();
428
    foreach ($modulesTable->getHash() as $row) {
429
      $modules[] = $row['module'];
430
    }
431
    $this->installModules($modules);
432
  }
433
434
  /**
435
   * Uninstalls the given module.
436
   *
437
   * @Given the :module module is disabled/uninstalled
438
   *
439
   * @param string $module
440
   *   The name of the module to uninstall.
441
   */
442
  public function uninstallModule($module) {
443
    $this->uninstallModules(array($module));
444
  }
445
446
  /**
447
   * Uninstalls the given modules.
448
   *
449
   * @Given the following modules are disabled/uninstalled:
450
   *
451
   * Provide the list of modules in the following format:
452
   * | modules |
453
   * | comment |
454
   * | forum   |
455
   *
456
   * @param TableNode $modulesTable
457
   *   The list of modules to uninstall.
458
   */
459 View Code Duplication
  public function uninstallModuleList(TableNode $modulesTable) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
460
    $modules = array();
461
    foreach ($modulesTable->getHash() as $row) {
462
      $modules[] = $row['module'];
463
    }
464
    $this->uninstallModules($modules);
465
  }
466
467
  /**
468
   * Checks if the given module is active.
469
   *
470
   * @Then the :module module should be enabled/active
471
   *
472
   * @param string $module
473
   *   The name of the module to check.
474
   *
475
   * @throws \Exception
476
   *   Thrown when the module is not active.
477
   */
478
  public function assertModuleActive($module) {
479
    $module_list = $this->getDriver()->getCore()->getModuleList();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Drupal\Driver\DriverInterface as the method getCore() does only exist in the following implementations of said interface: Drupal\Driver\DrupalDriver.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
480
    if (!in_array($module, $module_list)) {
481
      throw new \Exception(sprintf('The %s module is not active.', $module));
482
    }
483
  }
484
485
  /**
486
   * Checks if the given module is not active.
487
   *
488
   * @Then the :module module should not be enabled/active
489
   *
490
   * @param string $module
491
   *   The name of the module to check.
492
   *
493
   * @throws \Exception
494
   *   Thrown when the module is active.
495
   */
496
  public function assertModuleNotActive($module) {
497
    $module_list = $this->getDriver()->getCore()->getModuleList();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Drupal\Driver\DriverInterface as the method getCore() does only exist in the following implementations of said interface: Drupal\Driver\DrupalDriver.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
498
    if (in_array($module, $module_list)) {
499
      throw new \Exception(sprintf('The %s module is active.', $module));
500
    }
501
  }
502
503
  /**
504
   * Pauses the scenario until the user presses a key. Useful when debugging a scenario.
505
   *
506
   * @Then (I )break
507
   */
508
    public function iPutABreakpoint()
509
    {
510
      fwrite(STDOUT, "\033[s \033[93m[Breakpoint] Press \033[1;93m[RETURN]\033[0;93m to continue...\033[0m");
511
      while (fgets(STDIN, 1024) == '') {}
512
      fwrite(STDOUT, "\033[u");
513
      return;
514
    }
515
516
}
517