UpdateCoreTest.php 21.3 KB
Newer Older
1
2
<?php

3
namespace Drupal\Tests\update\Functional;
4

5
use Drupal\Core\Link;
6
use Drupal\Core\Url;
7
use Drupal\Tests\Traits\Core\CronRunTrait;
8

9
/**
10
11
12
13
 * Tests the Update Manager module through a series of functional tests using
 * mock XML data.
 *
 * @group update
14
 */
15
16
class UpdateCoreTest extends UpdateTestBase {

17
18
  use CronRunTrait;

19
20
21
22
23
  /**
   * Modules to enable.
   *
   * @var array
   */
24
  public static $modules = ['update_test', 'update', 'language', 'block'];
25

26
  protected function setUp() {
27
    parent::setUp();
28
    $admin_user = $this->drupalCreateUser(['administer site configuration', 'administer modules', 'administer themes']);
29
    $this->drupalLogin($admin_user);
30
    $this->drupalPlaceBlock('local_actions_block');
31
32
  }

33
34
35
36
37
38
39
  /**
   * Sets the version to x.x.x when no project-specific mapping is defined.
   *
   * @param string $version
   *   The version.
   */
  protected function setSystemInfo($version) {
40
41
    $setting = [
      '#all' => [
42
        'version' => $version,
43
44
      ],
    ];
45
    $this->config('update_test.settings')->set('system_info', $setting)->save();
46
  }
47

48
  /**
49
   * Tests the Update Manager module when no updates are available.
50
   */
51
  public function testNoUpdatesAvailable() {
52
53
54
    foreach ([0, 1] as $minor_version) {
      foreach ([0, 1] as $patch_version) {
        foreach (['-alpha1', '-beta1', ''] as $extra_version) {
55
          $this->setSystemInfo("8.$minor_version.$patch_version" . $extra_version);
56
          $this->refreshUpdateStatus(['drupal' => "$minor_version.$patch_version" . $extra_version]);
57
58
59
60
          $this->standardTests();
          $this->assertText(t('Up to date'));
          $this->assertNoText(t('Update available'));
          $this->assertNoText(t('Security update required!'));
61
          $this->assertRaw('check.svg', 'Check icon was found.');
62
63
64
        }
      }
    }
65
66
67
  }

  /**
68
   * Tests the Update Manager module when one normal update is available.
69
   */
70
  public function testNormalUpdateAvailable() {
71
    $this->setSystemInfo('8.0.0');
72
73
74
75
76

    // Ensure that the update check requires a token.
    $this->drupalGet('admin/reports/updates/check');
    $this->assertResponse(403, 'Accessing admin/reports/updates/check without a CSRF token results in access denied.');

77
78
79
    foreach ([0, 1] as $minor_version) {
      foreach (['-alpha1', '-beta1', ''] as $extra_version) {
        $this->refreshUpdateStatus(['drupal' => "$minor_version.1" . $extra_version]);
80
        $this->standardTests();
81
82
        $this->drupalGet('admin/reports/updates');
        $this->clickLink(t('Check manually'));
83
        $this->checkForMetaRefresh();
84
        $this->assertNoText(t('Security update required!'));
85
86
87
        $this->assertRaw(Link::fromTextAndUrl("8.$minor_version.1" . $extra_version, Url::fromUri("http://example.com/drupal-8-$minor_version-1$extra_version-release"))->toString(), 'Link to release appears.');
        $this->assertRaw(Link::fromTextAndUrl(t('Download'), Url::fromUri("http://example.com/drupal-8-$minor_version-1$extra_version.tar.gz"))->toString(), 'Link to download appears.');
        $this->assertRaw(Link::fromTextAndUrl(t('Release notes'), Url::fromUri("http://example.com/drupal-8-$minor_version-1$extra_version-release"))->toString(), 'Link to release notes appears.');
88
89
90
91
92
93
94
95
96
97

        switch ($minor_version) {
          case 0:
            // Both stable and unstable releases are available.
            // A stable release is the latest.
            if ($extra_version == '') {
              $this->assertNoText(t('Up to date'));
              $this->assertText(t('Update available'));
              $this->assertText(t('Recommended version:'));
              $this->assertNoText(t('Latest version:'));
98
              $this->assertRaw('warning.svg', 'Warning icon was found.');
99
100
101
102
103
104
105
106
            }
            // Only unstable releases are available.
            // An unstable release is the latest.
            else {
              $this->assertText(t('Up to date'));
              $this->assertNoText(t('Update available'));
              $this->assertNoText(t('Recommended version:'));
              $this->assertText(t('Latest version:'));
107
              $this->assertRaw('check.svg', 'Check icon was found.');
108
109
110
111
112
113
114
115
116
117
            }
            break;
          case 1:
            // Both stable and unstable releases are available.
            // A stable release is the latest.
            if ($extra_version == '') {
              $this->assertNoText(t('Up to date'));
              $this->assertText(t('Update available'));
              $this->assertText(t('Recommended version:'));
              $this->assertNoText(t('Latest version:'));
118
              $this->assertRaw('warning.svg', 'Warning icon was found.');
119
120
121
122
123
124
125
126
            }
            // Both stable and unstable releases are available.
            // An unstable release is the latest.
            else {
              $this->assertNoText(t('Up to date'));
              $this->assertText(t('Update available'));
              $this->assertText(t('Recommended version:'));
              $this->assertText(t('Latest version:'));
127
              $this->assertRaw('warning.svg', 'Warning icon was found.');
128
129
130
131
132
133
134
135
136
137
            }
            break;
        }
      }
    }
  }

  /**
   * Tests the Update Manager module when a major update is available.
   */
138
  public function testMajorUpdateAvailable() {
139
140
141
    foreach ([0, 1] as $minor_version) {
      foreach ([0, 1] as $patch_version) {
        foreach (['-alpha1', '-beta1', ''] as $extra_version) {
142
          $this->setSystemInfo("8.$minor_version.$patch_version" . $extra_version);
143
          $this->refreshUpdateStatus(['drupal' => '9']);
144
          $this->standardTests();
145
146
          $this->drupalGet('admin/reports/updates');
          $this->clickLink(t('Check manually'));
147
          $this->checkForMetaRefresh();
148
          $this->assertNoText(t('Security update required!'));
149
150
151
          $this->assertRaw(Link::fromTextAndUrl('9.0.0', Url::fromUri("http://example.com/drupal-9-0-0-release"))->toString(), 'Link to release appears.');
          $this->assertRaw(Link::fromTextAndUrl(t('Download'), Url::fromUri("http://example.com/drupal-9-0-0.tar.gz"))->toString(), 'Link to download appears.');
          $this->assertRaw(Link::fromTextAndUrl(t('Release notes'), Url::fromUri("http://example.com/drupal-9-0-0-release"))->toString(), 'Link to release notes appears.');
152
153
154
155
          $this->assertNoText(t('Up to date'));
          $this->assertText(t('Not supported!'));
          $this->assertText(t('Recommended version:'));
          $this->assertNoText(t('Latest version:'));
156
          $this->assertRaw('error.svg', 'Error icon was found.');
157
158
159
        }
      }
    }
160
161
162
  }

  /**
163
   * Tests the Update Manager module when a security update is available.
164
165
166
167
168
   *
   * @param string $site_patch_version
   *   The patch version to set the site to for testing.
   * @param string[] $expected_security_releases
   *   The security releases, if any, that the status report should recommend.
169
170
   * @param string $expected_update_message_type
   *   The type of update message expected.
171
172
173
174
   * @param string $fixture
   *   The test fixture that contains the test XML.
   *
   * @dataProvider securityUpdateAvailabilityProvider
175
   */
176
  public function testSecurityUpdateAvailability($site_patch_version, array $expected_security_releases, $expected_update_message_type, $fixture) {
177
178
    $this->setSystemInfo("8.$site_patch_version");
    $this->refreshUpdateStatus(['drupal' => $fixture]);
179
    $this->assertSecurityUpdates('drupal-8', $expected_security_releases, $expected_update_message_type, 'table.update');
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
  }

  /**
   * Data provider method for testSecurityUpdateAvailability().
   *
   * These test cases rely on the following fixtures containing the following
   * releases:
   * - drupal.sec.0.1_0.2.xml
   *   - 8.0.2 Security update
   *   - 8.0.1 Security update, Insecure
   *   - 8.0.0 Insecure
   * - drupal.sec.0.2.xml
   *   - 8.0.2 Security update
   *   - 8.0.1 Insecure
   *   - 8.0.0 Insecure
   * - drupal.sec.0.2-rc2.xml
   *   - 8.2.0-rc2 Security update
   *   - 8.2.0-rc1 Insecure
   *   - 8.2.0-beta2 Insecure
   *   - 8.2.0-beta1 Insecure
   *   - 8.2.0-alpha2 Insecure
   *   - 8.2.0-alpha1 Insecure
   *   - 8.1.2 Security update
   *   - 8.1.1 Insecure
   *   - 8.1.0 Insecure
   *   - 8.0.2 Security update
   *   - 8.0.1 Insecure
   *   - 8.0.0 Insecure
   * - drupal.sec.1.2.xml
   *   - 8.1.2 Security update
   *   - 8.1.1 Insecure
   *   - 8.1.0 Insecure
   *   - 8.0.2
   *   - 8.0.1
   *   - 8.0.0
   * - drupal.sec.1.2_insecure.xml
   *   - 8.1.2 Security update
   *   - 8.1.1 Insecure
   *   - 8.1.0 Insecure
   *   - 8.0.2 Insecure
   *   - 8.0.1 Insecure
   *   - 8.0.0 Insecure
   * - drupal.sec.0.2-rc2-b.xml
   *   - 8.2.0-rc2
   *   - 8.2.0-rc1
   *   - 8.2.0-beta2
   *   - 8.2.0-beta1
   *   - 8.2.0-alpha2
   *   - 8.2.0-alpha1
   *   - 8.1.2 Security update
   *   - 8.1.1 Insecure
   *   - 8.1.0 Insecure
   *   - 8.0.2 Security update
   *   - 8.0.1 Insecure
   *   - 8.0.0 Insecure
   */
  public function securityUpdateAvailabilityProvider() {
    $test_cases = [
      // Security release available for site minor release 0.
      // No releases for next minor.
      '0.0, 0.2' => [
        'site_patch_version' => '0.0',
        'expected_security_releases' => ['0.2'],
243
        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
244
245
        'fixture' => 'sec.0.2',
      ],
246
247
248
249
250
251
252
253
254
      // Site on latest security release available for site minor release 0.
      // Minor release 1 also has a security release, and the current release
      // is marked as insecure.
      '0.2, 0.2' => [
        'site_patch_version' => '0.2',
        'expected_security_release' => ['1.2', '2.0-rc2'],
        'expected_update_message_type' => static::UPDATE_AVAILABLE,
        'fixture' => 'sec.0.2-rc2',
      ],
255
256
257
258
259
260
      // Two security releases available for site minor release 0.
      // 0.1 security release marked as insecure.
      // No releases for next minor.
      '0.0, 0.1 0.2' => [
        'site_patch_version' => '0.0',
        'expected_security_releases' => ['0.2'],
261
        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
262
263
264
265
266
267
268
        'fixture' => 'sec.0.1_0.2',
      ],
      // Security release available for site minor release 1.
      // No releases for next minor.
      '1.0, 1.2' => [
        'site_patch_version' => '1.0',
        'expected_security_releases' => ['1.2'],
269
        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
270
271
272
273
274
275
276
        'fixture' => 'sec.1.2',
      ],
      // Security release available for site minor release 0.
      // Security release also available for next minor.
      '0.0, 0.2 1.2' => [
        'site_patch_version' => '0.0',
        'expected_security_releases' => ['0.2', '1.2', '2.0-rc2'],
277
        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
278
279
280
281
282
283
284
        'fixture' => 'sec.0.2-rc2',
      ],
      // No newer security release for site minor 1.
      // Previous minor has security release.
      '1.2, 0.2 1.2' => [
        'site_patch_version' => '1.2',
        'expected_security_releases' => [],
285
        'expected_update_message_type' => static::UPDATE_NONE,
286
        'fixture' => 'sec.0.2-rc2',
287
288
289
290
291
292
      ],
      // No security release available for site minor release 0.
      // Security release available for next minor.
      '0.0, 1.2, insecure' => [
        'site_patch_version' => '0.0',
        'expected_security_releases' => ['1.2'],
293
        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
294
295
        'fixture' => 'sec.1.2_insecure',
      ],
296
297
298
299
300
301
302
303
304
305
306
307
308
309
      // All releases for minor 0 are secure.
      // Security release available for next minor.
      '0.0, 1.2, secure' => [
        'site_patch_version' => '0.0',
        'expected_security_release' => ['1.2'],
        'expected_update_message_type' => static::UPDATE_AVAILABLE,
        'fixture' => 'sec.1.2',
      ],
      '0.2, 1.2, secure' => [
        'site_patch_version' => '0.2',
        'expected_security_release' => ['1.2'],
        'expected_update_message_type' => static::UPDATE_AVAILABLE,
        'fixture' => 'sec.1.2',
      ],
310
311
312
313
      // Site on 2.0-rc2 which is a security release.
      '2.0-rc2, 0.2 1.2' => [
        'site_patch_version' => '2.0-rc2',
        'expected_security_releases' => [],
314
        'expected_update_message_type' => static::UPDATE_NONE,
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
        'fixture' => 'sec.0.2-rc2',
      ],
    ];
    $pre_releases = [
      '2.0-alpha1',
      '2.0-alpha2',
      '2.0-beta1',
      '2.0-beta2',
      '2.0-rc1',
      '2.0-rc2',
    ];

    // If the site is on an alpha/beta/RC of an upcoming minor and none of the
    // alpha/beta/RC versions are marked insecure, no security update should be
    // required.
    foreach ($pre_releases as $pre_release) {
      $test_cases["Pre-release:$pre_release, no security update"] = [
        'site_patch_version' => $pre_release,
        'expected_security_releases' => [],
334
        'expected_update_message_type' => $pre_release === '2.0-rc2' ? static::UPDATE_NONE : static::UPDATE_AVAILABLE,
335
336
        'fixture' => 'sec.0.2-rc2-b',
      ];
337
    }
338
339
340
341
342
343
344
345

    // @todo In https://www.drupal.org/node/2865920 add test cases:
    //   - For all pre-releases for 8.2.0 except 8.2.0-rc2 using the
    //     'sec.0.2-rc2' fixture to ensure that 8.2.0-rc2 is the only security
    //     update.
    //   - For 8.1.0 using fixture 'sec.0.2-rc2' to ensure that only security
    //     updates are 8.1.2 and 8.2.0-rc2.
    return $test_cases;
346
347
348
  }

  /**
349
   * Ensures proper results where there are date mismatches among modules.
350
   */
351
  public function testDatestampMismatch() {
352
353
    $system_info = [
      '#all' => [
354
        // We need to think we're running a -dev snapshot to see dates.
355
        'version' => '8.1.0-dev',
356
        'datestamp' => time(),
357
358
      ],
      'block' => [
359
360
        // This is 2001-09-09 01:46:40 GMT, so test for "2001-Sep-".
        'datestamp' => '1000000000',
361
362
      ],
    ];
363
    $this->config('update_test.settings')->set('system_info', $system_info)->save();
364
    $this->refreshUpdateStatus(['drupal' => 'dev']);
365
366
367
368
369
370
371
    $this->assertNoText(t('2001-Sep-'));
    $this->assertText(t('Up to date'));
    $this->assertNoText(t('Update available'));
    $this->assertNoText(t('Security update required!'));
  }

  /**
372
   * Checks that running cron updates the list of available updates.
373
   */
374
  public function testModulePageRunCron() {
375
    $this->setSystemInfo('8.0.0');
376
    $this->config('update.settings')
377
      ->set('fetch.url', Url::fromRoute('update_test.update_test')->setAbsolute()->toString())
378
      ->save();
379
    $this->config('update_test.settings')
380
      ->set('xml_map', ['drupal' => '0.0'])
381
      ->save();
382
383
384
385
386
387

    $this->cronRun();
    $this->drupalGet('admin/modules');
    $this->assertNoText(t('No update information available.'));
  }

388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
  /**
   * Checks that clearing the disk cache works.
   */
  public function testClearDiskCache() {
    $directories = [
      _update_manager_cache_directory(FALSE),
      _update_manager_extract_directory(FALSE),
    ];
    // Check that update directories does not exists.
    foreach ($directories as $directory) {
      $this->assertDirectoryNotExists($directory);
    }

    // Method must not fail if update directories do not exists.
    update_clear_update_disk_cache();
  }

405
  /**
406
   * Checks the messages at admin/modules when the site is up to date.
407
   */
408
  public function testModulePageUpToDate() {
409
    $this->setSystemInfo('8.0.0');
410
    // Instead of using refreshUpdateStatus(), set these manually.
411
    $this->config('update.settings')
412
      ->set('fetch.url', Url::fromRoute('update_test.update_test')->setAbsolute()->toString())
413
      ->save();
414
    $this->config('update_test.settings')
415
      ->set('xml_map', ['drupal' => '0.0'])
416
      ->save();
417
418
419

    $this->drupalGet('admin/reports/updates');
    $this->clickLink(t('Check manually'));
420
    $this->checkForMetaRefresh();
421
422
423
424
425
426
427
    $this->assertText(t('Checked available update data for one project.'));
    $this->drupalGet('admin/modules');
    $this->assertNoText(t('There are updates available for your version of Drupal.'));
    $this->assertNoText(t('There is a security update available for your version of Drupal.'));
  }

  /**
428
   * Checks the messages at admin/modules when an update is missing.
429
   */
430
  public function testModulePageRegularUpdate() {
431
    $this->setSystemInfo('8.0.0');
432
    // Instead of using refreshUpdateStatus(), set these manually.
433
    $this->config('update.settings')
434
      ->set('fetch.url', Url::fromRoute('update_test.update_test')->setAbsolute()->toString())
435
      ->save();
436
    $this->config('update_test.settings')
437
      ->set('xml_map', ['drupal' => '0.1'])
438
      ->save();
439
440
441

    $this->drupalGet('admin/reports/updates');
    $this->clickLink(t('Check manually'));
442
    $this->checkForMetaRefresh();
443
444
445
446
447
448
449
    $this->assertText(t('Checked available update data for one project.'));
    $this->drupalGet('admin/modules');
    $this->assertText(t('There are updates available for your version of Drupal.'));
    $this->assertNoText(t('There is a security update available for your version of Drupal.'));
  }

  /**
450
   * Checks the messages at admin/modules when a security update is missing.
451
   */
452
  public function testModulePageSecurityUpdate() {
453
    $this->setSystemInfo('8.0.0');
454
    // Instead of using refreshUpdateStatus(), set these manually.
455
    $this->config('update.settings')
456
      ->set('fetch.url', Url::fromRoute('update_test.update_test')->setAbsolute()->toString())
457
      ->save();
458
    $this->config('update_test.settings')
459
      ->set('xml_map', ['drupal' => 'sec.0.2'])
460
      ->save();
461
462
463

    $this->drupalGet('admin/reports/updates');
    $this->clickLink(t('Check manually'));
464
    $this->checkForMetaRefresh();
465
466
467
468
469
470
471
472
473
474
475
476
477
    $this->assertText(t('Checked available update data for one project.'));
    $this->drupalGet('admin/modules');
    $this->assertNoText(t('There are updates available for your version of Drupal.'));
    $this->assertText(t('There is a security update available for your version of Drupal.'));

    // Make sure admin/appearance warns you you're missing a security update.
    $this->drupalGet('admin/appearance');
    $this->assertNoText(t('There are updates available for your version of Drupal.'));
    $this->assertText(t('There is a security update available for your version of Drupal.'));

    // Make sure duplicate messages don't appear on Update status pages.
    $this->drupalGet('admin/reports/status');
    // We're expecting "There is a security update..." inside the status report
478
479
480
    // itself, but the message from
    // \Drupal\Core\Messenger\MessengerInterface::addStatus() appears as an li
    // so we can prefix with that and search for the raw HTML.
481
482
483
484
485
486
487
488
489
490
    $this->assertNoRaw('<li>' . t('There is a security update available for your version of Drupal.'));

    $this->drupalGet('admin/reports/updates');
    $this->assertNoText(t('There is a security update available for your version of Drupal.'));

    $this->drupalGet('admin/reports/updates/settings');
    $this->assertNoText(t('There is a security update available for your version of Drupal.'));
  }

  /**
491
   * Tests the Update Manager module when the update server returns 503 errors.
492
   */
493
  public function testServiceUnavailable() {
494
    $this->refreshUpdateStatus([], '503-error');
495
496
497
498
499
500
501
502
    // Ensure that no "Warning: SimpleXMLElement..." parse errors are found.
    $this->assertNoText('SimpleXMLElement');
    $this->assertUniqueText(t('Failed to get available update data for one project.'));
  }

  /**
   * Tests that exactly one fetch task per project is created and not more.
   */
503
  public function testFetchTasks() {
504
    $projecta = [
505
      'name' => 'aaa_update_test',
506
507
    ];
    $projectb = [
508
      'name' => 'bbb_update_test',
509
    ];
510
    $queue = \Drupal::queue('update_fetch_tasks');
511
512
513
514
515
516
517
518
519
    $this->assertEqual($queue->numberOfItems(), 0, 'Queue is empty');
    update_create_fetch_task($projecta);
    $this->assertEqual($queue->numberOfItems(), 1, 'Queue contains one item');
    update_create_fetch_task($projectb);
    $this->assertEqual($queue->numberOfItems(), 2, 'Queue contains two items');
    // Try to add project a again.
    update_create_fetch_task($projecta);
    $this->assertEqual($queue->numberOfItems(), 2, 'Queue still contains two items');

520
521
    // Clear storage and try again.
    update_storage_clear();
522
523
524
525
    update_create_fetch_task($projecta);
    $this->assertEqual($queue->numberOfItems(), 2, 'Queue contains two items');
  }

526
527
528
  /**
   * Checks language module in core package at admin/reports/updates.
   */
529
  public function testLanguageModuleUpdate() {
530
    $this->setSystemInfo('8.0.0');
531
    // Instead of using refreshUpdateStatus(), set these manually.
532
    $this->config('update.settings')
533
      ->set('fetch.url', Url::fromRoute('update_test.update_test')->setAbsolute()->toString())
534
      ->save();
535
    $this->config('update_test.settings')
536
      ->set('xml_map', ['drupal' => '0.1'])
537
      ->save();
538
539
540
541
542

    $this->drupalGet('admin/reports/updates');
    $this->assertText(t('Language'));
  }

543
544
545
546
  /**
   * Ensures that the local actions appear.
   */
  public function testLocalActions() {
547
    $admin_user = $this->drupalCreateUser(['administer site configuration', 'administer modules', 'administer software updates', 'administer themes']);
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
    $this->drupalLogin($admin_user);

    $this->drupalGet('admin/modules');
    $this->clickLink(t('Install new module'));
    $this->assertUrl('admin/modules/install');

    $this->drupalGet('admin/appearance');
    $this->clickLink(t('Install new theme'));
    $this->assertUrl('admin/theme/install');

    $this->drupalGet('admin/reports/updates');
    $this->clickLink(t('Install new module or theme'));
    $this->assertUrl('admin/reports/updates/install');
  }

563
}