UpdateCoreTest.php 34.4 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
  protected static $modules = ['update_test', 'update', 'language', 'block'];
25

26
27
28
29
30
  /**
   * {@inheritdoc}
   */
  protected $defaultTheme = 'stark';

31
32
33
34
35
36
37
38
39
40
  /**
   * {@inheritdoc}
   */
  protected $updateTableLocator = 'table.update';

  /**
   * {@inheritdoc}
   */
  protected $updateProject = 'drupal';

41
  protected function setUp(): void {
42
    parent::setUp();
43
    $admin_user = $this->drupalCreateUser(['administer site configuration', 'administer modules', 'administer themes']);
44
    $this->drupalLogin($admin_user);
45
    $this->drupalPlaceBlock('local_actions_block');
46
47
  }

48
49
50
51
52
53
54
  /**
   * Sets the version to x.x.x when no project-specific mapping is defined.
   *
   * @param string $version
   *   The version.
   */
  protected function setSystemInfo($version) {
55
56
    $setting = [
      '#all' => [
57
        'version' => $version,
58
59
      ],
    ];
60
    $this->config('update_test.settings')->set('system_info', $setting)->save();
61
  }
62

63
  /**
64
   * Tests the Update Manager module when no updates are available.
65
66
67
68
69
70
71
   *
   * The XML fixture file 'drupal.1.0.xml' which is one of the XML files this
   * test uses also contains 2 extra releases that are newer than '8.0.1'. These
   * releases will not show as available updates because of the following
   * reasons:
   * - '8.0.2' is an unpublished release.
   * - '8.0.3' is marked as 'Release type' 'Unsupported'.
72
   */
73
  public function testNoUpdatesAvailable() {
74
75
76
    foreach ([0, 1] as $minor_version) {
      foreach ([0, 1] as $patch_version) {
        foreach (['-alpha1', '-beta1', ''] as $extra_version) {
77
          $this->setSystemInfo("8.$minor_version.$patch_version" . $extra_version);
78
          $this->refreshUpdateStatus(['drupal' => "$minor_version.$patch_version" . $extra_version]);
79
          $this->standardTests();
80
81
82
83
          // The XML test fixtures for this method all contain the '8.2.0'
          // release but because '8.2.0' is not in a supported branch it will
          // not be in the available updates.
          $this->assertNoRaw('8.2.0');
84
85
86
          $this->assertText(t('Up to date'));
          $this->assertNoText(t('Update available'));
          $this->assertNoText(t('Security update required!'));
87
          $this->assertRaw('check.svg', 'Check icon was found.');
88
89
90
        }
      }
    }
91
92
93
  }

  /**
94
   * Tests the Update Manager module when one normal update is available.
95
   */
96
  public function testNormalUpdateAvailable() {
97
    $this->setSystemInfo('8.0.0');
98
99
100
101
102

    // 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.');

103
104
    foreach ([0, 1] as $minor_version) {
      foreach (['-alpha1', '-beta1', ''] as $extra_version) {
105
        $full_version = "8.$minor_version.1$extra_version";
106
        $this->refreshUpdateStatus(['drupal' => "$minor_version.1" . $extra_version]);
107
        $this->standardTests();
108
109
        $this->drupalGet('admin/reports/updates');
        $this->clickLink(t('Check manually'));
110
        $this->checkForMetaRefresh();
111
        $this->assertNoText(t('Security update required!'));
112
113
114
115
        // The XML test fixtures for this method all contain the '8.2.0' release
        // but because '8.2.0' is not in a supported branch it will not be in
        // the available updates.
        $this->assertNoRaw('8.2.0');
116
117
118
119
120
121
122
        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'));
123
              $this->assertVersionUpdateLinks('Recommended version:', $full_version);
124
              $this->assertNoText(t('Latest version:'));
125
              $this->assertRaw('warning.svg', 'Warning icon was found.');
126
127
128
129
130
131
132
            }
            // 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:'));
133
              $this->assertVersionUpdateLinks('Latest version:', $full_version);
134
              $this->assertRaw('check.svg', 'Check icon was found.');
135
136
137
138
139
140
141
142
            }
            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'));
143
              $this->assertVersionUpdateLinks('Recommended version:', $full_version);
144
              $this->assertNoText(t('Latest version:'));
145
              $this->assertRaw('warning.svg', 'Warning icon was found.');
146
147
148
149
150
151
            }
            // 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'));
152
153
              $this->assertVersionUpdateLinks('Recommended version:', '8.1.0');
              $this->assertVersionUpdateLinks('Latest version:', $full_version);
154
              $this->assertRaw('warning.svg', 'Warning icon was found.');
155
156
157
158
159
160
161
162
163
164
            }
            break;
        }
      }
    }
  }

  /**
   * Tests the Update Manager module when a major update is available.
   */
165
  public function testMajorUpdateAvailable() {
166
167
168
    foreach ([0, 1] as $minor_version) {
      foreach ([0, 1] as $patch_version) {
        foreach (['-alpha1', '-beta1', ''] as $extra_version) {
169
          $this->setSystemInfo("8.$minor_version.$patch_version" . $extra_version);
170
          $this->refreshUpdateStatus(['drupal' => '9']);
171
          $this->standardTests();
172
173
          $this->drupalGet('admin/reports/updates');
          $this->clickLink(t('Check manually'));
174
          $this->checkForMetaRefresh();
175
          $this->assertNoText(t('Security update required!'));
176
177
178
          $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.');
179
180
181
182
          $this->assertNoText(t('Up to date'));
          $this->assertText(t('Not supported!'));
          $this->assertText(t('Recommended version:'));
          $this->assertNoText(t('Latest version:'));
183
          $this->assertRaw('error.svg', 'Error icon was found.');
184
185
186
        }
      }
    }
187
188
189
  }

  /**
190
   * Tests the Update Manager module when a security update is available.
191
192
193
194
195
   *
   * @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.
196
197
   * @param string $expected_update_message_type
   *   The type of update message expected.
198
199
200
201
   * @param string $fixture
   *   The test fixture that contains the test XML.
   *
   * @dataProvider securityUpdateAvailabilityProvider
202
   */
203
  public function testSecurityUpdateAvailability($site_patch_version, array $expected_security_releases, $expected_update_message_type, $fixture) {
204
205
    $this->setSystemInfo("8.$site_patch_version");
    $this->refreshUpdateStatus(['drupal' => $fixture]);
206
    $this->assertSecurityUpdates('drupal-8', $expected_security_releases, $expected_update_message_type, 'table.update');
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
243
244
245
246
247
248
  }

  /**
   * 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
249
250
251
252
253
   * - drupal.sec.1.2_insecure-unsupported
   *   This file has the exact releases as drupal.sec.1.2_insecure.xml. It has a
   *   different value for 'supported_branches' that does not contain '8.0.'.
   *   It is used to ensure that the "Security update required!" is displayed
   *   even if the currently installed version is in an unsupported branch.
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
   * - 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'],
275
        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
276
277
        'fixture' => 'sec.0.2',
      ],
278
279
280
281
282
283
284
285
286
      // 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',
      ],
287
288
289
290
291
292
      // 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'],
293
        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
294
295
296
297
298
299
300
        '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'],
301
        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
302
303
304
305
306
307
308
        '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'],
309
        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
310
311
312
313
314
315
316
        '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' => [],
317
        'expected_update_message_type' => static::UPDATE_NONE,
318
        'fixture' => 'sec.0.2-rc2',
319
320
321
322
323
324
      ],
      // 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'],
325
        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
326
327
        'fixture' => 'sec.1.2_insecure',
      ],
328
329
330
331
332
333
334
335
336
      // No security release available for site minor release 0.
      // Site minor is not a supported branch.
      // Security release available for next minor.
      '0.0, 1.2, insecure-unsupported' => [
        'site_patch_version' => '0.0',
        'expected_security_releases' => ['1.2'],
        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
        'fixture' => 'sec.1.2_insecure-unsupported',
      ],
337
338
339
340
341
342
343
344
345
346
347
348
349
350
      // 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',
      ],
351
352
353
354
      // 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' => [],
355
        'expected_update_message_type' => static::UPDATE_NONE,
356
357
        'fixture' => 'sec.0.2-rc2',
      ],
358
359
360
361
362
363
364
365
      // Ensure that 8.0.2 security release is not shown because it is earlier
      // version than 1.0.
      '1.0, 0.2 1.2' => [
        'site_patch_version' => '1.0',
        'expected_security_releases' => ['1.2', '2.0-rc2'],
        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
        'fixture' => 'sec.0.2-rc2',
      ],
366
367
368
369
370
371
372
373
374
375
376
    ];
    $pre_releases = [
      '2.0-alpha1',
      '2.0-alpha2',
      '2.0-beta1',
      '2.0-beta2',
      '2.0-rc1',
      '2.0-rc2',
    ];

    foreach ($pre_releases as $pre_release) {
377
378
379
      // 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.
380
381
382
      $test_cases["Pre-release:$pre_release, no security update"] = [
        'site_patch_version' => $pre_release,
        'expected_security_releases' => [],
383
        'expected_update_message_type' => $pre_release === '2.0-rc2' ? static::UPDATE_NONE : static::UPDATE_AVAILABLE,
384
385
        'fixture' => 'sec.0.2-rc2-b',
      ];
386
387
388
389
390
391
392
393
      // If the site is on an alpha/beta/RC of an upcoming minor and there is
      // an RC version with a security update, it should be recommended.
      $test_cases["Pre-release:$pre_release, security update"] = [
        'site_patch_version' => $pre_release,
        'expected_security_releases' => $pre_release === '2.0-rc2' ? [] : ['2.0-rc2'],
        'expected_update_message_type' => $pre_release === '2.0-rc2' ? static::UPDATE_NONE : static::SECURITY_UPDATE_REQUIRED,
        'fixture' => 'sec.0.2-rc2',
      ];
394
    }
395
    return $test_cases;
396
397
  }

398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
  /**
   * Tests the security coverage messages for Drupal core versions.
   *
   * @param string $installed_version
   *   The installed Drupal version to test.
   * @param string $fixture
   *   The test fixture that contains the test XML.
   * @param string $requirements_section_heading
   *   The requirements section heading.
   * @param string $message
   *   The expected coverage message.
   * @param string $mock_date
   *   The mock date to use if needed in the format CCYY-MM-DD. If an empty
   *   string is provided, no mock date will be used.
   *
   * @dataProvider securityCoverageMessageProvider
   */
  public function testSecurityCoverageMessage($installed_version, $fixture, $requirements_section_heading, $message, $mock_date) {
    \Drupal::state()->set('update_test.mock_date', $mock_date);
    $this->setSystemInfo($installed_version);
    $this->refreshUpdateStatus(['drupal' => $fixture]);
    $this->drupalGet('admin/reports/status');

    if (empty($requirements_section_heading)) {
      $this->assertSession()->pageTextNotContains('Drupal core security coverage');
      return;
    }

    $all_requirements_details = $this->getSession()->getPage()->findAll(
      'css',
      'details.system-status-report__entry:contains("Drupal core security coverage")'
    );
    // Ensure we only have 1 security message section.
    $this->assertCount(1, $all_requirements_details);
    $requirements_details = $all_requirements_details[0];
    // Ensure that messages are under the correct heading which could be
    // 'Checked', 'Warnings found', or 'Errors found'.
    $requirements_section_element = $requirements_details->getParent();
    $this->assertCount(1, $requirements_section_element->findAll('css', "h3:contains('$requirements_section_heading')"));
437
    $actual_message = $requirements_details->find('css', 'div.system-status-report__entry__value')->getText();
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
    $this->assertNotEmpty($actual_message);
    $this->assertEquals($message, $actual_message);
  }

  /**
   * Dataprovider for testSecurityCoverageMessage().
   *
   * These test cases rely on the following fixtures containing the following
   * releases:
   * - drupal.sec.2.0_3.0-rc1.xml
   *   - 8.2.0
   *   - 8.3.0-rc1
   * - drupal.sec.2.0.xml
   *   - 8.2.0
   * - drupal.sec.2.0_9.0.0.xml
   *   - 8.2.0
   *   - 9.0.0
   * - drupal.sec.9.0.xml
   *   - 8.9.0
   * - drupal.sec.9.9.0.xml
   *   - 9.9.0
   */
  public function securityCoverageMessageProvider() {
    $release_coverage_message = 'Visit the release cycle overview for more information on supported releases.';
462
    $coverage_ended_message = 'Coverage has ended';
463
464
465
466
467
468
469
    $update_asap_message = 'Update to a supported minor as soon as possible to continue receiving security updates.';
    $update_soon_message = 'Update to a supported minor version soon to continue receiving security updates.';
    $test_cases = [
      '8.0.0, unsupported' => [
        'installed_version' => '8.0.0',
        'fixture' => 'sec.2.0_3.0-rc1',
        'requirements_section_heading' => 'Errors found',
470
        'message' => "$coverage_ended_message $update_asap_message $release_coverage_message",
471
472
473
474
475
476
        'mock_date' => '',
      ],
      '8.1.0, supported with 3rc' => [
        'installed_version' => '8.1.0',
        'fixture' => 'sec.2.0_3.0-rc1',
        'requirements_section_heading' => 'Warnings found',
477
        'message' => "Covered until 8.3.0 Update to 8.2 or higher soon to continue receiving security updates. $release_coverage_message",
478
479
480
481
482
483
        'mock_date' => '',
      ],
      '8.1.0, supported' => [
        'installed_version' => '8.1.0',
        'fixture' => 'sec.2.0',
        'requirements_section_heading' => 'Warnings found',
484
        'message' => "Covered until 8.3.0 Update to 8.2 or higher soon to continue receiving security updates. $release_coverage_message",
485
486
487
488
489
490
        'mock_date' => '',
      ],
      '8.2.0, supported with 3rc' => [
        'installed_version' => '8.2.0',
        'fixture' => 'sec.2.0_3.0-rc1',
        'requirements_section_heading' => 'Checked',
491
        'message' => "Covered until 8.4.0 $release_coverage_message",
492
493
494
495
496
497
        'mock_date' => '',
      ],
      '8.2.0, supported' => [
        'installed_version' => '8.2.0',
        'fixture' => 'sec.2.0',
        'requirements_section_heading' => 'Checked',
498
        'message' => "Covered until 8.4.0 $release_coverage_message",
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
        'mock_date' => '',
      ],
      // Ensure we don't show messages for pre-release or dev versions.
      '8.2.0-beta2, no message' => [
        'installed_version' => '8.2.0-beta2',
        'fixture' => 'sec.2.0_3.0-rc1',
        'requirements_section_heading' => '',
        'message' => '',
        'mock_date' => '',
      ],
      '8.1.0-dev, no message' => [
        'installed_version' => '8.1.0-dev',
        'fixture' => 'sec.2.0_3.0-rc1',
        'requirements_section_heading' => '',
        'message' => '',
        'mock_date' => '',
      ],
      // Ensures the message is correct if the next major version has been
      // released and the additional minors indicated by
      // CORE_MINORS_WITH_SECURITY_COVERAGE minors have been released.
      '8.0.0, 9 unsupported' => [
        'installed_version' => '8.0.0',
        'fixture' => 'sec.2.0_9.0.0',
        'requirements_section_heading' => 'Errors found',
523
        'message' => "$coverage_ended_message $update_asap_message $release_coverage_message",
524
525
526
527
528
529
530
531
532
        'mock_date' => '',
      ],
      // Ensures the message is correct if the next major version has been
      // released and the additional minors indicated by
      // CORE_MINORS_WITH_SECURITY_COVERAGE minors have not been released.
      '8.2.0, 9 warning' => [
        'installed_version' => '8.2.0',
        'fixture' => 'sec.2.0_9.0.0',
        'requirements_section_heading' => 'Warnings found',
533
        'message' => "Covered until 8.4.0 Update to 8.3 or higher soon to continue receiving security updates. $release_coverage_message",
534
535
536
537
538
539
540
541
542
543
544
        'mock_date' => '',
      ],
    ];

    // Drupal 8.8.x test cases.
    $test_cases += [
      // Ensure that a message is displayed during 8.8's active support.
      '8.8.0, supported' => [
        'installed_version' => '8.8.0',
        'fixture' => 'sec.9.0',
        'requirements_section_heading' => 'Checked',
545
        'message' => "Covered until 2020-Dec-02 $release_coverage_message",
546
547
548
549
550
551
552
553
        'mock_date' => '2020-06-01',
      ],
      // Ensure a warning is displayed if less than six months remain until the
      // end of 8.8's security coverage.
      '8.8.0, supported, 6 months warn' => [
        'installed_version' => '8.8.0',
        'fixture' => 'sec.9.0',
        'requirements_section_heading' => 'Warnings found',
554
        'message' => "Covered until 2020-Dec-02 $update_soon_message $release_coverage_message",
555
556
557
558
559
560
561
562
563
564
565
566
567
568
        'mock_date' => '2020-06-02',
      ],
    ];
    // Ensure that the message does not change, including on the last day of
    // security coverage.
    $test_cases['8.8.0, supported, last day warn'] = $test_cases['8.8.0, supported, 6 months warn'];
    $test_cases['8.8.0, supported, last day warn']['mock_date'] = '2020-12-01';

    // Ensure that if the 8.8 support window is finished a message is
    // displayed.
    $test_cases['8.8.0, support over'] = [
      'installed_version' => '8.8.0',
      'fixture' => 'sec.9.0',
      'requirements_section_heading' => 'Errors found',
569
      'message' => "$coverage_ended_message $update_asap_message $release_coverage_message",
570
571
572
573
574
575
576
577
      'mock_date' => '2020-12-02',
    ];

    // Drupal 8.9 LTS test cases.
    $test_cases['8.9.0, lts supported'] = [
      'installed_version' => '8.9.0',
      'fixture' => 'sec.9.0',
      'requirements_section_heading' => 'Checked',
578
      'message' => "Covered until 2021-Nov $release_coverage_message",
579
580
581
582
583
584
585
586
587
588
589
590
      'mock_date' => '2021-01-01',
    ];
    // Ensure that the message does not change, including on the last day of
    // security coverage.
    $test_cases['8.9.0, lts supported, last day'] = $test_cases['8.9.0, lts supported'];
    $test_cases['8.9.0, lts supported, last day']['mock_date'] = '2021-10-31';

    // Ensure that if LTS support window is finished a message is displayed.
    $test_cases['8.9.0, lts support over'] = [
      'installed_version' => '8.9.0',
      'fixture' => 'sec.9.0',
      'requirements_section_heading' => 'Errors found',
591
      'message' => "$coverage_ended_message $update_asap_message $release_coverage_message",
592
593
594
595
596
597
598
599
600
601
      'mock_date' => '2021-11-01',
    ];

    // Drupal 9 test cases.
    $test_cases += [
      // Ensure the end dates for 8.8 and 8.9 only apply to major version 8.
      '9.9.0' => [
        'installed_version' => '9.9.0',
        'fixture' => 'sec.9.9.0',
        'requirements_section_heading' => 'Checked',
602
        'message' => "Covered until 9.11.0 $release_coverage_message",
603
604
605
606
607
608
        'mock_date' => '',
      ],
      '9.8.0' => [
        'installed_version' => '9.8.0',
        'fixture' => 'sec.9.9.0',
        'requirements_section_heading' => 'Warnings found',
609
        'message' => "Covered until 9.10.0 Update to 9.9 or higher soon to continue receiving security updates. $release_coverage_message",
610
611
612
613
614
615
616
        'mock_date' => '',
      ],
    ];
    return $test_cases;

  }

617
  /**
618
   * Ensures proper results where there are date mismatches among modules.
619
   */
620
  public function testDatestampMismatch() {
621
622
    $system_info = [
      '#all' => [
623
        // We need to think we're running a -dev snapshot to see dates.
624
        'version' => '8.1.0-dev',
625
        'datestamp' => time(),
626
627
      ],
      'block' => [
628
629
        // This is 2001-09-09 01:46:40 GMT, so test for "2001-Sep-".
        'datestamp' => '1000000000',
630
631
      ],
    ];
632
    $this->config('update_test.settings')->set('system_info', $system_info)->save();
633
    $this->refreshUpdateStatus(['drupal' => 'dev']);
634
635
636
637
638
639
640
    $this->assertNoText(t('2001-Sep-'));
    $this->assertText(t('Up to date'));
    $this->assertNoText(t('Update available'));
    $this->assertNoText(t('Security update required!'));
  }

  /**
641
   * Checks that running cron updates the list of available updates.
642
   */
643
  public function testModulePageRunCron() {
644
    $this->setSystemInfo('8.0.0');
645
    $this->config('update.settings')
646
      ->set('fetch.url', Url::fromRoute('update_test.update_test')->setAbsolute()->toString())
647
      ->save();
648
    $this->config('update_test.settings')
649
      ->set('xml_map', ['drupal' => '0.0'])
650
      ->save();
651
652
653
654
655
656

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

657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
  /**
   * 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();
  }

674
  /**
675
   * Checks the messages at admin/modules when the site is up to date.
676
   */
677
  public function testModulePageUpToDate() {
678
    $this->setSystemInfo('8.0.0');
679
    // Instead of using refreshUpdateStatus(), set these manually.
680
    $this->config('update.settings')
681
      ->set('fetch.url', Url::fromRoute('update_test.update_test')->setAbsolute()->toString())
682
      ->save();
683
    $this->config('update_test.settings')
684
      ->set('xml_map', ['drupal' => '0.0'])
685
      ->save();
686
687
688

    $this->drupalGet('admin/reports/updates');
    $this->clickLink(t('Check manually'));
689
    $this->checkForMetaRefresh();
690
691
692
693
694
695
696
    $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.'));
  }

  /**
697
   * Checks the messages at admin/modules when an update is missing.
698
   */
699
  public function testModulePageRegularUpdate() {
700
    $this->setSystemInfo('8.0.0');
701
    // Instead of using refreshUpdateStatus(), set these manually.
702
    $this->config('update.settings')
703
      ->set('fetch.url', Url::fromRoute('update_test.update_test')->setAbsolute()->toString())
704
      ->save();
705
    $this->config('update_test.settings')
706
      ->set('xml_map', ['drupal' => '0.1'])
707
      ->save();
708
709
710

    $this->drupalGet('admin/reports/updates');
    $this->clickLink(t('Check manually'));
711
    $this->checkForMetaRefresh();
712
713
714
715
716
717
718
    $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.'));
  }

  /**
719
   * Checks the messages at admin/modules when a security update is missing.
720
   */
721
  public function testModulePageSecurityUpdate() {
722
    $this->setSystemInfo('8.0.0');
723
    // Instead of using refreshUpdateStatus(), set these manually.
724
    $this->config('update.settings')
725
      ->set('fetch.url', Url::fromRoute('update_test.update_test')->setAbsolute()->toString())
726
      ->save();
727
    $this->config('update_test.settings')
728
      ->set('xml_map', ['drupal' => 'sec.0.2'])
729
      ->save();
730
731
732

    $this->drupalGet('admin/reports/updates');
    $this->clickLink(t('Check manually'));
733
    $this->checkForMetaRefresh();
734
735
736
737
738
739
740
741
742
743
744
745
746
    $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
747
748
749
    // 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.
750
751
752
753
754
755
756
757
758
759
    $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.'));
  }

  /**
760
   * Tests the Update Manager module when the update server returns 503 errors.
761
   */
762
  public function testServiceUnavailable() {
763
    $this->refreshUpdateStatus([], '503-error');
764
765
766
767
768
769
770
771
    // 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.
   */
772
  public function testFetchTasks() {
773
    $projecta = [
774
      'name' => 'aaa_update_test',
775
776
    ];
    $projectb = [
777
      'name' => 'bbb_update_test',
778
    ];
779
    $queue = \Drupal::queue('update_fetch_tasks');
780
781
782
783
784
785
786
787
788
    $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');

789
790
    // Clear storage and try again.
    update_storage_clear();
791
792
793
794
    update_create_fetch_task($projecta);
    $this->assertEqual($queue->numberOfItems(), 2, 'Queue contains two items');
  }

795
796
797
  /**
   * Checks language module in core package at admin/reports/updates.
   */
798
  public function testLanguageModuleUpdate() {
799
    $this->setSystemInfo('8.0.0');
800
    // Instead of using refreshUpdateStatus(), set these manually.
801
    $this->config('update.settings')
802
      ->set('fetch.url', Url::fromRoute('update_test.update_test')->setAbsolute()->toString())
803
      ->save();
804
    $this->config('update_test.settings')
805
      ->set('xml_map', ['drupal' => '0.1'])
806
      ->save();
807
808
809
810
811

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

812
813
814
815
  /**
   * Ensures that the local actions appear.
   */
  public function testLocalActions() {
816
    $admin_user = $this->drupalCreateUser(['administer site configuration', 'administer modules', 'administer software updates', 'administer themes']);
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
    $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');
  }

832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
  /**
   * Tests messages when a project release is unpublished.
   *
   * This test confirms that revoked messages are displayed regardless of
   * whether the installed version is in a supported branch or not. This test
   * relies on 2 test XML fixtures that are identical except for the
   * 'supported_branches' value:
   * - drupal.1.0.xml
   *    'supported_branches' is '8.0.,8.1.'.
   * - drupal.1.0-unsupported.xml
   *    'supported_branches' is '8.1.'.
   * They both have an '8.0.2' release that is unpublished and an '8.1.0'
   * release that is published and is the expected update.
   */
  public function testRevokedRelease() {
    foreach (['1.0', '1.0-unsupported'] as $fixture) {
      $this->setSystemInfo('8.0.2');
      $this->refreshUpdateStatus([$this->updateProject => $fixture]);
      $this->standardTests();
      $this->confirmRevokedStatus('8.0.2', '8.1.0', 'Recommended version:');
    }
  }

  /**
   * Tests messages when a project release is marked unsupported.
   *
   * This test confirms unsupported messages are displayed regardless of whether
   * the installed version is in a supported branch or not. This test relies on
   * 2 test XML fixtures that are identical except for the 'supported_branches'
   * value:
   * - drupal.1.0.xml
   *    'supported_branches' is '8.0.,8.1.'.
   * - drupal.1.0-unsupported.xml
   *    'supported_branches' is '8.1.'.
866
   * They both have an '8.0.3' release that has the 'Release type' value of
867
868
869
870
871
872
873
874
875
876
877
878
   * 'unsupported' and an '8.1.0' release that has the 'Release type' value of
   * 'supported' and is the expected update.
   */
  public function testUnsupportedRelease() {
    foreach (['1.0', '1.0-unsupported'] as $fixture) {
      $this->setSystemInfo('8.0.3');
      $this->refreshUpdateStatus([$this->updateProject => $fixture]);
      $this->standardTests();
      $this->confirmUnsupportedStatus('8.0.3', '8.1.0', 'Recommended version:');
    }
  }

879
880
881
882
883
884
885
886
887
888
  /**
   * {@inheritdoc}
   */
  protected function assertVersionUpdateLinks($label, $version, $download_version = NULL) {
    // Test XML files for Drupal core use '-' in the version number for the
    // download link.
    $download_version = str_replace('.', '-', $version);
    parent::assertVersionUpdateLinks($label, $version, $download_version);
  }

889
}