diff --git a/modules/announcements_feed/announcements_feed.css b/modules/announcements_feed/announcements_feed.css
new file mode 100644
index 0000000000000000000000000000000000000000..01449b8ef20ccbe3bdbb8a7d41e4da022c7ef0b8
--- /dev/null
+++ b/modules/announcements_feed/announcements_feed.css
@@ -0,0 +1,16 @@
+/**
+ * @file
+ * Styles for the announcements feed.
+ */
+
+.announcements {
+  padding-bottom: 1rem;
+}
+.featured-announcements-wrapper .leaf {
+  padding-top: 0;
+}
+.announcements .announcements--view-all {
+  padding-left: 30px;
+  border-top: 1px solid #ccc;
+  padding-top: 10px;
+}
diff --git a/modules/announcements_feed/announcements_feed.inc b/modules/announcements_feed/announcements_feed.inc
new file mode 100644
index 0000000000000000000000000000000000000000..01c649859c59594f54376c5d8e7af710070fadab
--- /dev/null
+++ b/modules/announcements_feed/announcements_feed.inc
@@ -0,0 +1,231 @@
+<?php
+
+/**
+ * @file
+ * Announcements feed helper functions.
+ */
+
+/**
+ * Returns the list of announcements.
+ *
+ * @return array
+ *   A build array with announcements.
+ */
+function announcements_feed_get_announcements() {
+  drupal_set_title('Community announcements');
+  drupal_add_css(drupal_get_path('module', 'announcements_feed')
+    . '/announcements_feed.css', array(
+      'group' => CSS_DEFAULT,
+      'every_page' => TRUE,
+    ));
+  try {
+    $announcements = announcements_feed_get_all_announcements();
+  }
+  catch (Exception $e) {
+    drupal_set_message(t('An error occurred while parsing the announcements feed, check the logs for more information.'), 'error');
+    return array();
+  }
+  $build = array();
+  foreach ($announcements as $announcement) {
+    $key = $announcement['featured'] ? '#featured' : '#standard';
+    $build[$key][] = $announcement;
+  }
+  $build = array_merge($build, array(
+    '#theme' => 'announcements_feed',
+    '#count' => count($announcements),
+    '#feed_link' => variable_get('announcements_feed_link', ANNOUNCEMENTS_FEED_DEFAULT_LINK),
+  ));
+  return $build;
+}
+
+/**
+ * Generate an array of announcements with keys.
+ *
+ * @return array
+ *   An array of announcements.
+ */
+function announcements_feed_get_all_announcements() {
+  $announcements = announcements_feed_fetch();
+  $announcements_feed = array();
+  foreach ($announcements as $announcement) {
+    $announcements_feed[] = array(
+      'id' => $announcement['id'],
+      'title' => $announcement['title'],
+      'link' => $announcement['url'],
+      'date_modified' => $announcement['date_modified'],
+      'date_published' => $announcement['date_published'],
+      'teaser' => $announcement['content_html'],
+      'version' => $announcement['_drupalorg']['version'],
+      'featured' => (bool) $announcement['_drupalorg']['featured'],
+    );
+  }
+
+  return $announcements_feed;
+}
+
+/**
+ * Fetches the feed either from a local cache or fresh remotely.
+ *
+ * The feed follows the "JSON Feed" format:
+ * - https://www.jsonfeed.org/version/1.1/
+ *
+ * The structure of an announcement item in the feed is:
+ *   - id: Id.
+ *   - title: Title of the announcement.
+ *   - content_html: Announcement teaser.
+ *   - url: URL
+ *   - date_modified: Last updated timestamp.
+ *   - date_published: Created timestamp.
+ *   - _drupalorg.featured: 1 if featured, 0 if not featured.
+ *   - _drupalorg.version: Target version of Drupal, as a Composer version.
+ *
+ * @param bool $force
+ *   (optional) Whether to always fetch new items or not. Defaults to FALSE.
+ *
+ * @return array
+ *   An array of announcements from the feed relevant to the Drupal version.
+ *   The array is empty if there were no matching announcements. If an error
+ *   occurred while fetching/decoding the feed, it is thrown as an exception.
+ *
+ * @throws Exception
+ */
+function announcements_feed_fetch($force = FALSE) {
+  $announcements = cache_get('announcements_feed');
+  if (empty($announcements)) {
+    $announcements_feed_json_url = variable_get('announcements_feed_json_url', ANNOUNCEMENTS_FEED_DEFAULT_JSON_URL);
+    $response = drupal_http_request($announcements_feed_json_url);
+    if ($response->code == 200) {
+      $feeds = json_decode($response->data, TRUE);
+      if (!isset($feeds['items'])) {
+        watchdog('announcements_feed', 'The feed format is not valid.', NULL, WATCHDOG_ERROR);
+        throw new Exception('Announcements feed JSON format is invalid');
+      }
+      $announcements = array();
+      if ($feeds['items']) {
+        $announcements = $feeds['items'];
+      }
+      $announcements = array_filter($announcements, 'announcements_feed_filter_announcements');
+      cache_set('announcements_feed', $announcements, 'cache', REQUEST_TIME + variable_get('announcements_feed_max_age', ANNOUNCEMENTS_FEED_DEFAULT_MAX_AGE));
+    }
+    else {
+      watchdog(
+        'announcements_feed',
+        'The feed failed to fetch with an error code: @code, error message: @message.',
+        array('@code' => $response->code, '@message' => $response->error),
+        WATCHDOG_ERROR
+      );
+      throw new Exception($response->error, $response->code);
+    }
+  }
+  else {
+    $announcements = $announcements->data;
+  }
+  // The drupal.org endpoint is sorted by created date in descending order.
+  // We will limit the announcements based on the configuration limit.
+  $announcements_feed_limit = variable_get('announcements_feed_limit', ANNOUNCEMENTS_FEED_DEFAULT_LIMIT);
+  $announcements = array_slice($announcements, 0, $announcements_feed_limit);
+  // For the remaining announcements, put all the featured announcements
+  // before the rest.
+  uasort($announcements, 'announcements_feed_sort_featured');
+
+  return $announcements;
+}
+
+/**
+ * Sort the elements of announcements_feed by values in comparison function.
+ */
+function announcements_feed_sort_featured($a, $b) {
+  $a_value = (int) $a['_drupalorg']['featured'];
+  $b_value = (int) $b['_drupalorg']['featured'];
+  if ($a_value == $b_value) {
+    return 0;
+  }
+
+  return ($a_value < $b_value) ? -1 : 1;
+}
+
+/**
+ * Filter the announcements relevant to the Drupal version used with valid URL controlled by drupal.org.
+ *
+ * @param array $announcement
+ *   Announcement feed array item to check.
+ *
+ * @return bool
+ *   Return TRUE if $announcement is relevant and the URL is valid.
+ */
+function announcements_feed_filter_announcements($announcement) {
+  $announcement_url = '';
+  $announcement_version = '';
+  if (!empty($announcement['url'])) {
+    $announcement_url = $announcement['url'];
+  }
+  if (!empty($announcement['_drupalorg']['version'])) {
+    $announcement_version = $announcement['_drupalorg']['version'];
+  }
+
+  return announcements_feed_validate_url($announcement_url) && announcements_feed_is_relevant_item($announcement_version);
+}
+
+/**
+ * Check whether the version given is relevant to the Drupal version used.
+ *
+ * @param string $version
+ *   Version to check.
+ *
+ * @return bool
+ *   Return TRUE if the version matches Drupal version.
+ */
+function announcements_feed_is_relevant_item($version) {
+  if ($version == '*') {
+    return TRUE;
+  }
+  // Split the version if received in || formats.
+  $version_patterns = '/\|\|/';
+  $all_versions = preg_split($version_patterns, $version);
+  // The operation is optional and defaults to equals.
+  $p_op = '(?P<operation>!=|\^|==|=|<|<=|>|>=|<>)?';
+  $operations = '=';
+  // Extracts major version from version string like 7, 8, 9.
+  $p_major = '(?P<major>\d+)';
+  // Extracts minor version from version string.
+  $p_minor = '(?P<minor>(?:\d+|x)(?:-[A-Za-z]+\d+)?)';
+  foreach ($all_versions as $version) {
+    if (preg_match("/^\s*$p_op\s*$p_major(\.$p_minor)?/", $version, $matches)) {
+      $feed_version = $matches['major'];
+      if (!empty($matches['minor'])) {
+        $feed_version = $matches['major'] . '.' . $matches['minor'];
+      }
+      if (!empty($matches['operation'])) {
+        $operations = $matches['operation'];
+        if ($operations == '^') {
+          $operations = '>=';
+        }
+      }
+      if (isset($operations) && version_compare(VERSION, $feed_version, $operations)) {
+        return TRUE;
+      }
+    }
+  }
+
+  return FALSE;
+}
+
+/**
+ * Check whether a link is controlled by drupal.org.
+ *
+ * @param string $url
+ *   URL to check.
+ *
+ * @return bool
+ *   Return TRUE if the URL is controlled by drupal.org.
+ */
+function announcements_feed_validate_url($url) {
+  if (empty($url)) {
+    return FALSE;
+  }
+  $host = parse_url($url, PHP_URL_HOST);
+
+  // First character can only be a letter or a digit.
+  // @see https://www.rfc-editor.org/rfc/rfc1123#page-13
+  return $host && preg_match('/^([a-zA-Z0-9][a-zA-Z0-9\-_]*\.)?drupal\.org$/', $host);
+}
diff --git a/modules/announcements_feed/announcements_feed.info b/modules/announcements_feed/announcements_feed.info
new file mode 100644
index 0000000000000000000000000000000000000000..ff5edfbdba212561a2effb92b1e56931ce26b239
--- /dev/null
+++ b/modules/announcements_feed/announcements_feed.info
@@ -0,0 +1,5 @@
+name = Announcements
+description =  Displays announcements from the Drupal community.
+package = Core
+core = 7.x
+files[] = tests/announce_feed_test.test
diff --git a/modules/announcements_feed/announcements_feed.install b/modules/announcements_feed/announcements_feed.install
new file mode 100644
index 0000000000000000000000000000000000000000..76308ad90e58433808ac9e6220960a59321d502e
--- /dev/null
+++ b/modules/announcements_feed/announcements_feed.install
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @file
+ * Install, update, and uninstall functions for the announcements_feed module.
+ */
+
+/**
+ * Implements hook_uninstall().
+ */
+function announcements_feed_uninstall() {
+  // Delete the announcements_feed module variables.
+  variable_del('announcements_feed_limit');
+  variable_del('announcements_feed_cron_interval');
+  variable_del('announcements_feed_max_age');
+  variable_del('announcements_feed_json_url');
+  variable_del('announcements_feed_link');
+  variable_del('announcements_feed_last_fetch');
+}
diff --git a/modules/announcements_feed/announcements_feed.module b/modules/announcements_feed/announcements_feed.module
new file mode 100644
index 0000000000000000000000000000000000000000..2cc4dd487402eb7825f24ea0d138c4d756ce9dbe
--- /dev/null
+++ b/modules/announcements_feed/announcements_feed.module
@@ -0,0 +1,146 @@
+<?php
+
+/**
+ * @file
+ * Used to create an announcements feed using the JSON data from drupal.org.
+ */
+
+/**
+ * Default maximum age of cached feed data in seconds.
+ */
+define('ANNOUNCEMENTS_FEED_DEFAULT_MAX_AGE', 86400);
+
+/**
+ * Default cron interval for refreshing feed data in seconds.
+ */
+define('ANNOUNCEMENTS_FEED_DEFAULT_CRON_INTERVAL', 21600);
+
+/**
+ * Default limit for number of feed items to fetch.
+ */
+define('ANNOUNCEMENTS_FEED_DEFAULT_LIMIT', 10);
+
+/**
+ * Default URL for the announcements JSON feed.
+ */
+define('ANNOUNCEMENTS_FEED_DEFAULT_JSON_URL', 'https://www.drupal.org/announcements.json');
+
+/**
+ * Default URL for the announcements feed at drupal.org.
+ */
+define('ANNOUNCEMENTS_FEED_DEFAULT_LINK', 'https://www.drupal.org/about/announcements');
+
+/**
+ * Implements hook_help().
+ */
+function announcements_feed_help($path) {
+  if ($path == 'admin/help#announcements_feed') {
+    $output = '';
+    $output .= '<h3>' . t('About') . '</h3>';
+    $output .= '<p>' . t('The Announcements module displays announcements from the Drupal community. For more information, see the <a href="@documentation">online documentation for the Announcements module</a>.', array('@documentation' => 'https://www.drupal.org/docs/core-modules-and-themes/core-modules/announcements-feed')) . '</p>';
+    $output .= '<h3>' . t('Uses') . '</h3>';
+    $output .= '<dl><dt>' . t('Accessing announcements') . '</dt>';
+    $output .= '<dd>' . t('Users with the "View drupal.org announcements" permission may click on the "Announcements" item in the administration menu, or access <a href="@link">Announcements</a>, to see all announcements relevant to the Drupal version of your site.', array('@link' => url('admin/announcements_feed'))) . '</dd>';
+    $output .= '</dl>';
+
+    return $output;
+  }
+
+}
+
+/**
+ * Implements hook_menu().
+ */
+function announcements_feed_menu() {
+  $items['admin/announcements_feed'] = array(
+    'title' => 'Announcements',
+    'description' => 'Announcements and updates posted by the drupal.org community.',
+    'page callback' => 'announcements_feed_get_announcements',
+    'access arguments' => array('access announcements'),
+    'position' => 'left',
+    'weight' => 2,
+    'file' => 'announcements_feed.inc',
+    'options' => array(
+      'attributes' => array(
+        'class' => array('announcement-menu-class'),
+      ),
+    ),
+  );
+
+  return $items;
+}
+
+/**
+ * Implements hook_permission().
+ */
+function announcements_feed_permission() {
+  return array(
+    'access announcements' => array(
+      'title' => t('View official announcements related to Drupal'),
+    ),
+  );
+}
+
+/**
+ * Implements hook_theme().
+ */
+function announcements_feed_theme() {
+  return array(
+    'announcements_feed' => array(
+      'variables' => array(
+        'featured' => NULL,
+        'standard' => NULL,
+        'feed_link' => NULL,
+        'count' => 0,
+      ),
+      'template' => 'announcements_feed',
+      'path' => drupal_get_path('module', 'announcements_feed'),
+    ),
+  );
+}
+
+/**
+ * Implements hook_page_alter().
+ */
+function announcements_feed_page_alter(&$page) {
+  if (isset($page['page_top']['toolbar'])) {
+    // If the toolbar is available, add a pre-render function to add the class.
+    $page['page_top']['toolbar']['#pre_render'][] = 'announcements_feed_toolbar_pre_render_alter';
+  }
+}
+
+/**
+ * Pre-render function for adding default class to announcement link.
+ */
+function announcements_feed_toolbar_pre_render_alter($toolbar) {
+  $path = drupal_get_path('module', 'announcements_feed');
+  $toolbar['#attached']['css'][] = $path . '/announcements_feed-toolbar.css';
+  module_load_include('inc', 'announcements_feed');
+  foreach ($toolbar['toolbar_menu']['#links'] as &$link) {
+    if ($link['href'] === 'admin/announcements_feed') {
+      $link['attributes']['class'][] = 'announcement-default';
+    }
+  }
+
+  return $toolbar;
+}
+
+/**
+ * Implements hook_cron().
+ */
+function announcements_feed_cron() {
+  module_load_include('inc', 'announcements_feed', 'announcements_feed');
+  $cron_interval = variable_get('announcements_feed_cron_interval', ANNOUNCEMENTS_FEED_DEFAULT_CRON_INTERVAL);
+  $last_check = variable_get('announcements_feed_last_fetch', 0);
+  $time = time();
+  if (($time - $last_check) > $cron_interval) {
+    try {
+      announcements_feed_fetch(TRUE);
+      // Update the last_fetch variable to the current time.
+      variable_set('announcements_feed_last_fetch', $time);
+    }
+    catch (Exception $e) {
+      watchdog('announcements_feed', 'Exception occurred: @message', array('@message' => $e->getMessage()), WATCHDOG_ERROR);
+    }
+  }
+}
diff --git a/modules/announcements_feed/announcements_feed.tpl.php b/modules/announcements_feed/announcements_feed.tpl.php
new file mode 100644
index 0000000000000000000000000000000000000000..c95618a08033dab4562f6c98cc926c88ebedf478
--- /dev/null
+++ b/modules/announcements_feed/announcements_feed.tpl.php
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * @file
+ * Template file for the theming of announcements_feed admin page.
+ *
+ * Available variables:
+ * - $count: Contains the total number of announcements.
+ * - $featured: An array of featured announcements.
+ * - $standard: An array of non-featured announcements.
+ *
+ * Each $announcement in $featured and $standard contain:
+ * - $announcement['id']: Unique id of the announcement.
+ * - $announcement['title']: Title of the announcement.
+ * - $announcement['teaser']: Short description of the announcement.
+ * - $announcement['link']: Learn more link of the announcement.
+ * - $announcement['date_published']: Timestamp of the announcement.
+ *
+ * @see announcements_feed_theme()
+ *
+ * @ingroup themeable
+ */
+?>
+<?php if ($count): ?>
+  <div class="announcements">
+    <ul class="admin-list">
+      <?php if ($featured): ?>
+        <div class="featured-announcements-wrapper">
+          <?php foreach ($featured as $key => $announcement): ?>
+            <li class="leaf">
+              <div class="announcement-title">
+                <h4>
+                  <?php print $announcement['title']; ?>
+                </h4>
+              </div>
+              <div class="announcement-teaser">
+                <?php print strip_tags($announcement['teaser']); ?>
+              </div>
+              <div class="announcement-link">
+                <?php if($announcement['link']): ?>
+                  <a target="_blank" href="<?php print $announcement['link']; ?>">
+                    <span>
+                      <?php print t('Learn More'); ?>
+                    </span>
+                  </a>
+                <?php endif; ?>
+              </div>
+            </li>
+          <?php endforeach; ?>
+        </div>
+      <?php endif; ?>
+      <?php foreach ($standard as $key => $announcement): ?>
+        <li class="leaf">
+          <a target="_blank" href="<?php print $announcement['link']; ?>"><?php print $announcement['title']; ?></a>
+          <div class="description">
+            <?php print format_date(strtotime($announcement['date_published']), 'short'); ?>
+          </div>
+        </li>
+      <?php endforeach; ?>
+    </ul>
+    <?php if ($feed_link): ?>
+      <div class="announcements--view-all">
+        <a target="_blank" href="<?php print $feed_link; ?>"><?php print t('View all announcements'); ?></a>
+      </div>
+    <?php endif; ?>
+  </div>
+<?php else: ?>
+  <div class="no-announcements"><span><?php print t('No announcements available'); ?></span></div>
+<?php endif; ?>
diff --git a/modules/announcements_feed/tests/announce_feed/community-feeds.json b/modules/announcements_feed/tests/announce_feed/community-feeds.json
new file mode 100644
index 0000000000000000000000000000000000000000..ee6672fb38975356d47ea7d97fd2bce55ce93c73
--- /dev/null
+++ b/modules/announcements_feed/tests/announce_feed/community-feeds.json
@@ -0,0 +1,57 @@
+{
+  "version": "https://jsonfeed.org/version/1.1",
+  "title": "Drupal Announcements Feed",
+  "home_page_url": "https://www.drupal.org",
+  "feed_url": "https://www.drupal.org/announcements.json",
+  "favicon": "https://www.drupal.org/favicon.ico",
+  "items": [
+    {
+      "id": "201",
+      "title": "new 9 - 10 Drupal 9.1.3 is available",
+      "content_html": "This release will have a community alert prototype to notify site admins about drupal updates and required information",
+      "url": "https://www.drupal.org/project/announce",
+      "date_modified": "2021-01-19T07:29:38+00:00",
+      "date_published": "2021-01-18T07:29:38+00:00",
+      "_drupalorg": {
+        "featured": false,
+        "version": ">=7.0"
+      }
+    },
+    {
+      "id": "2021",
+      "title": "updated 10 - DrupalCon is here",
+      "content_html": "This release will have a community alert prototype to notify site admins about drupal updates and required information",
+      "url": "https://www.drupal.org/project/announce",
+      "date_modified": "2021-01-19T07:29:38+00:00",
+      "date_published": "2021-01-18T07:29:38+00:00",
+      "_drupalorg":  {
+        "featured": true,
+        "version": ">=7.0"
+      }
+    },
+    {
+      "id": "2031",
+      "title": "new 9 only - Download latest drupal here",
+      "content_html": "This release will have a community alert prototype to notify site admins about drupal updates and required information",
+      "url": "https://www.drupal.org/project/announce",
+      "date_modified": "2021-01-19T07:29:38+00:00",
+      "date_published": "2021-01-18T07:29:38+00:00",
+      "_drupalorg":  {
+        "featured": false,
+        "version": ">=7.0"
+      }
+    },
+    {
+      "id": "2043",
+      "title": "Only 10 - Drupal 106 is available",
+      "content_html": "This release will have a community alert prototype to notify site admins about drupal updates and required information",
+      "url": "https://www.drupal.org/project/announce",
+      "date_modified": "2021-01-19T07:29:39+00:00",
+      "date_published": "2021-01-18T07:29:39+00:00",
+      "_drupalorg":  {
+        "featured": false,
+        "version": ">=7.0"
+      }
+    }
+  ]
+}
diff --git a/modules/announcements_feed/tests/announce_feed/empty.json b/modules/announcements_feed/tests/announce_feed/empty.json
new file mode 100644
index 0000000000000000000000000000000000000000..c49ae3aaed4a15c43c5b9d11ceb491d5543cf6e4
--- /dev/null
+++ b/modules/announcements_feed/tests/announce_feed/empty.json
@@ -0,0 +1,8 @@
+{
+  "version": "https://jsonfeed.org/version/1.1",
+  "title": "Drupal Announcements Feed",
+  "home_page_url": "https://www.drupal.org",
+  "feed_url": "https://www.drupal.org/announcements.json",
+  "favicon": "https://www.drupal.org/favicon.ico",
+  "items": []
+}
diff --git a/modules/announcements_feed/tests/announce_feed/invalid-feeds.json b/modules/announcements_feed/tests/announce_feed/invalid-feeds.json
new file mode 100644
index 0000000000000000000000000000000000000000..46c23d5de9f583eb335253581cb4f889cef486a7
--- /dev/null
+++ b/modules/announcements_feed/tests/announce_feed/invalid-feeds.json
@@ -0,0 +1,33 @@
+{
+  "version": "https://jsonfeed.org/version/1.1",
+  "title" "Drupal Announcements Feed",
+  "home_page_url": "https://www.drupal.org",
+  "feed_url": "https://www.drupal.org/announcements.json",
+  "favicon": "https://www.drupal.org/favicon.ico",
+  "items": [
+    {
+      "id" "201",
+      "title": "new 9 - 10 Drupal 9.1.3 is available",
+      "content_html": "This release will have a community alert prototype to notify site admins about drupal updates and required information",
+      "url": "https://www.drupal.org/project/announce",
+      "date_modified": "2021-01-19T07:29:38+00:00",
+      "date_published": "2021-01-18T07:29:38+00:00",
+      "_drupalorg": {
+        "featured": false,
+        "version": ">=7.0"
+      }
+    }
+    {
+      "id": "2021",
+      "title": "updated 10 - DrupalCon is here"
+      "content_html": "This release will have a community alert prototype to notify site admins about drupal updates and required information",
+      "url": "https://www.drupal.org/project/announce",
+      "date_modified": "2021-01-19T07:29:38+00:00"
+      "date_published": "2021-01-18T07:29:38+00:00",
+      "_drupalorg":  {
+        "featured": true
+        "version": ">=7.0"
+      }
+    }
+  ]
+}
diff --git a/modules/announcements_feed/tests/announce_feed/removed.json b/modules/announcements_feed/tests/announce_feed/removed.json
new file mode 100644
index 0000000000000000000000000000000000000000..7a27be7b9decd9a6c0b5248258832bc0bbbb9069
--- /dev/null
+++ b/modules/announcements_feed/tests/announce_feed/removed.json
@@ -0,0 +1,45 @@
+{
+  "version": "https://jsonfeed.org/version/1.1",
+  "title": "Drupal Announcements Feed",
+  "home_page_url": "https://www.drupal.org",
+  "feed_url": "https://www.drupal.org/announcements.json",
+  "favicon": "https://www.drupal.org/favicon.ico",
+  "items": [
+    {
+      "id": "201",
+      "title": "new 9 - 10 Drupal 9.1.3 is available",
+      "content_html": "This release will have a community alert prototype to notify site admins about drupal updates and required information",
+      "url": "https://www.drupal.org/project/announce",
+      "date_modified": "2021-01-19T07:29:38+00:00",
+      "date_published": "2021-01-18T07:29:38+00:00",
+      "_drupalorg": {
+        "featured": true,
+        "version": "^9 | ^10"
+      }
+    },
+    {
+      "id": "2021",
+      "title": "updated 10 - DrupalCon is here",
+      "content_html": "This release will have a community alert prototype to notify site admins about drupal updates and required information",
+      "url": "https://www.drupal.org/project/announce",
+      "date_modified": "2021-01-19T07:29:38+00:00",
+      "date_published": "2021-01-18T07:29:38+00:00",
+      "_drupalorg":  {
+        "featured": false,
+        "version": "^10"
+      }
+    },
+    {
+      "id": "2031",
+      "title": "new 9 only - Download latest drupal here",
+      "content_html": "This release will have a community alert prototype to notify site admins about drupal updates and required information",
+      "url": "https://www.drupal.org/project/announce",
+      "date_modified": "2021-01-19T07:29:38+00:00",
+      "date_published": "2021-01-18T07:29:38+00:00",
+      "_drupalorg":  {
+        "featured": false,
+        "version": "^9"
+      }
+    }
+  ]
+}
diff --git a/modules/announcements_feed/tests/announce_feed/updated.json b/modules/announcements_feed/tests/announce_feed/updated.json
new file mode 100644
index 0000000000000000000000000000000000000000..4a731845db519e01fd0a9043ec48b9f66a547000
--- /dev/null
+++ b/modules/announcements_feed/tests/announce_feed/updated.json
@@ -0,0 +1,79 @@
+{
+	"version": "https://jsonfeed.org/version/1.1",
+	"title": "Drupal Announcements Feed",
+	"home_page_url": "https://www.drupal.org",
+	"feed_url": "https://www.drupal.org/announcements.json",
+	"favicon": "https://www.drupal.org/favicon.ico",
+	"items": [{
+		"id": "3347976",
+		"title": "Contribute to Drupal and the Open Web",
+		"content_html": "<p>Drupal is an open source project. But what does that mean? How can you as a site-owner participate in the project of building a better web?</p>",
+		"url": "https://www.drupal.org/about/announcements/blog/contribute-to-drupal-and-the-open-web",
+		"date_modified": "2023-03-14T19:43:12+00:00",
+		"date_published": "2023-03-14T19:39:44+00:00",
+		"_drupalorg": {
+			"featured": false,
+			"version": ">=7.0"
+		}
+	},
+        {
+		"id": "3343486",
+		"title": "What to expect from the Announcements feed?",
+		"content_html": "<p>Drupal is introducing a new project Announcements feature so that you can see news and updates about the Drupal project and community directly in your Drupal dashboard. Learn what to expect.</p>",
+		"url": "https://www.drupal.org/about/announcements/blog/what-to-expect-from-the-announcements-feed",
+		"date_modified": "2023-02-21T19:45:53+00:00",
+		"date_published": "2023-02-21T19:45:53+00:00",
+		"_drupalorg": {
+			"featured": true,
+			"version": ">=7.0"
+		}
+	},
+        {
+		"id": "3327047",
+		"title": "Drupal 10.0.0 is available",
+		"content_html": "<p>Thanks to 2129 contributors from 616 organizations resolving 4083 issues in the past two and a half years, Drupal 10.0.0 is available today! This new version sets Drupal up for continued stability and security for the longer term. All new features will be added to Drupal 10 going forward.</p>",
+		"url": "https://www.drupal.org/blog/drupal-10-0-0",
+		"date_modified": "2023-03-14T19:47:33+00:00",
+		"date_published": "2022-12-15T17:43:23+00:00",
+		"_drupalorg": {
+			"featured": false,
+			"version": ">=7.0"
+		}
+	},
+        {
+		"id": "3343429",
+		"title": "New community feed available.",
+		"content_html": "<p>Drupal is introducing a new project Announcements feature so that you can see news and updates about the Drupal project and community directly in your Drupal dashboard. Learn what to expect.</p>",
+		"url": "https://www.drupal.org/about/announcements/blog/what-to-expect-from-the-announcements-feed",
+		"date_modified": "2023-03-21T19:45:53+00:00",
+		"date_published": "2023-03-21T19:45:53+00:00",
+		"_drupalorg": {
+			"featured": true,
+			"version": ">=7.0"
+		}
+	},
+        {
+		"id": "3343431",
+		"title": "Any plan to migrate to Drupal 9/10?",
+		"content_html": "<p>Drupal is introducing a new project Announcements feature so that you can see news and updates about the Drupal project and community directly in your Drupal dashboard. Learn what to expect.</p>",
+		"url": "https://www.drupal.org/about/announcements/blog/what-to-expect-from-the-announcements-feed",
+		"date_modified": "2023-02-25T19:45:53+00:00",
+		"date_published": "2023-02-25T19:45:53+00:00",
+		"_drupalorg": {
+			"featured": true,
+			"version": ">=7.0"
+		}
+	},
+  {
+    "id": "3343440",
+    "title": "Only 9 - Drupal 106 is available and this feed is Updated",
+    "content_html": "<p>Drupal is introducing a new project Announcements feature so that you can see news and updates about the Drupal project and community directly in your Drupal dashboard. Learn what to expect.</p>",
+    "url": "https://www.drupal.org/about/announcements/blog/what-to-expect-from-the-announcements-feed",
+    "date_modified": "2023-01-29T19:45:53+00:00",
+    "date_published": "2023-01-29T19:45:53+00:00",
+    "_drupalorg": {
+      "featured": true,
+      "version": ">=7.0"
+    }
+  }]
+}
diff --git a/modules/announcements_feed/tests/announce_feed_test.info b/modules/announcements_feed/tests/announce_feed_test.info
new file mode 100644
index 0000000000000000000000000000000000000000..5c954bce288fbb64948bf7ffbd8ba5ba8365dfec
--- /dev/null
+++ b/modules/announcements_feed/tests/announce_feed_test.info
@@ -0,0 +1,5 @@
+name = "Announcements feed test"
+description = "Support module for announcements feed testing."
+package = Testing
+core = 7.x
+hidden = TRUE
diff --git a/modules/announcements_feed/tests/announce_feed_test.module b/modules/announcements_feed/tests/announce_feed_test.module
new file mode 100644
index 0000000000000000000000000000000000000000..9f9c70007469229256a791ac124c910c47d47c32
--- /dev/null
+++ b/modules/announcements_feed/tests/announce_feed_test.module
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Helper functions for testing the announcements feed functionality.
+ */
+
+/**
+ * Implements hook_menu().
+ */
+function announce_feed_test_menu() {
+  $items['announcements-feed-json/%'] = array(
+    'title' => 'Announcements feed JSON',
+    'page callback' => 'announce_feed_test_set_feed_config',
+    'page arguments' => array(1),
+    // In unit tests, restrictions are not required.
+    'access callback' => TRUE,
+  );
+
+  return $items;
+}
+
+/**
+ * Helper function to set announcements feed URL.
+ */
+function announce_feed_test_set_feed_config($json_name) {
+  $file = __DIR__ . "/announce_feed/$json_name.json";
+  if (!is_file($file)) {
+    // Return an empty response.
+    drupal_not_found();
+  }
+
+  $contents = file_get_contents($file);
+  drupal_json_output(drupal_json_decode($contents));
+}
diff --git a/modules/announcements_feed/tests/announce_feed_test.test b/modules/announcements_feed/tests/announce_feed_test.test
new file mode 100644
index 0000000000000000000000000000000000000000..c6cef0005fac1b57b9b6db4e09ca4cdbb92fabf8
--- /dev/null
+++ b/modules/announcements_feed/tests/announce_feed_test.test
@@ -0,0 +1,384 @@
+<?php
+
+/**
+ * @file
+ * Contains tests for the announcements_feed module.
+ */
+
+/**
+ * Tests for validating JSON feed with different JSON URLs.
+ */
+class AnnounceFeedTestValidateJsonFeed extends DrupalWebTestCase {
+
+  /**
+   * A user with permission to access toolbar and access announcements.
+   *
+   * @var object
+   */
+  protected $user;
+
+  /**
+   * A test endpoint which contains the community feeds.
+   *
+   * @var string
+   */
+  protected $responseJson;
+
+  /**
+   * A test endpoint which include the new/updated feeds.
+   *
+   * @var string
+   */
+  protected $updatedJson;
+
+  /**
+   * A test endpoint which displays an empty JSON.
+   *
+   * @var string
+   */
+  protected $emptyJson;
+
+  /**
+   * A test endpoint that will have some feeds removed.
+   *
+   * @var string
+   */
+  protected $removed;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'JSON feeds validation / processing',
+      'description' => 'Testing how the code handles multiple types of JSON feeds.',
+      'group' => 'Announcements',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    global $base_url;
+    module_load_include('inc', 'announce_feed_test', 'announce_feed_test');
+    parent::setUp('user', 'toolbar', 'announcements_feed', 'announce_feed_test');
+    $this->user = $this->drupalCreateUser(array(
+      'access toolbar',
+      'access announcements',
+    ));
+    $this->drupalLogin($this->user);
+    $this->responseJson = $base_url . '/announcements-feed-json/community-feeds';
+    $this->updatedJson = $base_url . '/announcements-feed-json/updated';
+    $this->emptyJson = $base_url . '/announcements-feed-json/empty';
+    $this->removed = $base_url . '/announcements-feed-json/removed';
+    variable_set('announcements_feed_json_url', $this->responseJson);
+  }
+
+  /**
+   * Testing the feed with Updated and Removed JSON feeds.
+   */
+  public function testAnnounceFeedUpdatedAndRemoved() {
+    $this->user = $this->drupalCreateUser(array(
+      'access toolbar',
+      'access announcements',
+    ));
+    module_load_include('inc', 'announce_feed_test', 'announce_feed_test');
+    $this->drupalLogin($this->user);
+    $this->drupalGet('');
+    $this->clickLink('Announcements');
+    variable_set('announcements_feed_json_url', $this->updatedJson);
+    cache_clear_all('announcements_feed', 'cache', TRUE);
+    $this->drupalGet('admin/announcements_feed');
+    $this->assertText('Only 9 - Drupal 106 is available and this feed is Updated');
+    $this->drupalLogout();
+
+    // Testing the removed JSON feed.
+    $this->drupalLogin($this->user);
+    $this->drupalGet('');
+    $this->clickLink('Announcements');
+    variable_set('announcements_feed_json_url', $this->removed);
+    cache_clear_all('announcements_feed', 'cache', TRUE);
+    $this->drupalGet('admin/announcements_feed');
+    $this->assertNoText('Only 9 - Drupal 106 is available and this feed is Updated');
+
+    $this->drupalLogout();
+  }
+
+  /**
+   * Check the status of the feed with an empty JSON feed.
+   */
+  public function testAnnounceFeedEmpty() {
+    $this->user = $this->drupalCreateUser(array(
+      'access toolbar',
+      'access announcements',
+    ));
+    // Change the feed URL to empty JSON file.
+    // For resetting temp storage.
+    variable_set('announcements_feed_json_url', $this->emptyJson);
+    cache_clear_all('announcements_feed', 'cache', TRUE);
+    $this->drupalLogin($this->user);
+    // Only no announcements available message should show.
+    $this->clickLink('Announcements');
+    $this->assertText('No announcements available');
+  }
+
+}
+/**
+ * Unit test for validate URL functions.
+ */
+class AnnounceFeedTestValidateUrl extends DrupalUnitTestCase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'JSON feed URLs validation',
+      'description' => 'Unit test to check the validate URL functions.',
+      'group' => 'Announcements',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+    module_load_include('inc', 'announcements_feed', 'announcements_feed');
+  }
+
+  /**
+   * Test for validating the announcements_feed_validate_url function.
+   */
+  public function testValidateUrl() {
+    $urls = array(
+      array('https://www.drupal.org', TRUE),
+      array('https://drupal.org', TRUE),
+      array('https://api.drupal.org', TRUE),
+      array('https://a.drupal.org', TRUE),
+      array('https://123.drupal.org', TRUE),
+      array('https://api-new.drupal.org', TRUE),
+      array('https://api_new.drupal.org', TRUE),
+      array('https://api-.drupal.org', TRUE),
+      array('https://www.example.org', FALSE),
+      array('https://example.org', FALSE),
+      array('https://api.example.org/project/announce', FALSE),
+      array('https://-api.drupal.org', FALSE),
+      array('https://a.example.org/project/announce', FALSE),
+      array('https://test.drupaal.com', FALSE),
+      array('https://api.drupal.org.example.com', FALSE),
+      array('https://example.org/drupal.org', FALSE),
+    );
+    foreach ($urls as $url) {
+      $result = announcements_feed_validate_url($url[0]);
+      $this->assertEqual($url[1], $result, 'Returned ' . ($url[1] ? 'TRUE' : 'FALSE'));
+    }
+  }
+}
+
+/**
+ * Unit test for version compatibility functions.
+ */
+class AnnounceFeedTestRelevantVersion extends DrupalUnitTestCase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Version-specific logic validation',
+      'description' => 'Unit test to check the version-specific logic.',
+      'group' => 'Announcements',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+    module_load_include('inc', 'announcements_feed', 'announcements_feed');
+  }
+
+  /**
+   * Test for validating the announcements_feed_is_relevant_item function.
+   */
+  public function testIsRelevantItem() {
+    $version_strings = array(
+      array('^7', TRUE),
+      // TRUE only if Drupal version is exactly 7.0.
+      array('=7.0', FALSE),
+      array('>=7', TRUE),
+      array('^7 || ^8 || ^9', TRUE),
+      array('>=7.52', TRUE),
+      array('^7.1 || ^8 || ^9', TRUE),
+      // TRUE only if Drupal version is exactly 7.9999.
+      array('=7.9999', FALSE),
+      array('^8 || ^9', FALSE),
+      array('>8', FALSE),
+      array('>=8.1', FALSE),
+      array('^8 || ^9 || ^10', FALSE),
+    );
+    foreach ($version_strings as $strings) {
+      $result = announcements_feed_is_relevant_item($strings[0]);
+      $this->assertEqual($strings[1], $result, 'Returned ' . ($strings[1] ? 'TRUE' : 'FALSE'));
+    }
+  }
+}
+
+/**
+ * Test the Announcements module permissions.
+ */
+class AnnounceFeedTestValidatePermissions extends DrupalWebTestCase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Permissions validation',
+      'description' => 'Tests the module permissions.',
+      'group' => 'Announcements',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    global $base_url;
+    module_load_include('inc', 'announce_feed_test', 'announce_feed_test');
+    parent::setUp('user', 'toolbar', 'announcements_feed', 'announce_feed_test');
+    $response_json = $base_url . '/announcements-feed-json/community-feeds';
+    variable_set('announcements_feed_json_url', $response_json);
+  }
+
+  /**
+   * Testing the announcements page with access announcements permission.
+   */
+  public function testAnnounceWithPermission() {
+    // Create a user with proper permission.
+    $account = $this->drupalCreateUser(array(
+      'access toolbar',
+      'access announcements',
+    ));
+    $this->drupalLogin($account);
+    $this->drupalGet('');
+    $this->drupalGet('admin/announcements_feed');
+    $this->assertText('Announcements');
+    $this->drupalLogout();
+  }
+
+  /**
+   * Testing the announcements page without access announcements permission.
+   */
+  public function testAnnounceWithoutPermission() {
+    $account = $this->drupalCreateUser(array('access toolbar'));
+    $this->drupalLogin($account);
+    $this->drupalGet('admin/announcements_feed');
+    $this->assertResponse(403);
+  }
+}
+
+/**
+ * Tests the announcements feed with invalid JSON URLs.
+ */
+class AnnounceFeedTestInvalidJsonTestCase extends DrupalWebTestCase {
+
+  /**
+   * A user with permission to access toolbar and access announcements.
+   *
+   * @var object
+   */
+  protected $user;
+
+  /**
+   * A test endpoint which contains the community feeds.
+   *
+   * @var string
+   */
+  protected $responseJson;
+
+  /**
+   * A test endpoint which does not exist.
+   *
+   * @var string
+   */
+  protected $unknownJson;
+
+  /**
+   * A test endpoint which returns invalid JSON.
+   *
+   * @var string
+   */
+  protected $invalidJson;
+
+  /**
+   * A test endpoint that will have some feeds removed.
+   *
+   * @var string
+   */
+  protected $removed;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Invalid / unknown JSON feed URL',
+      'description' => 'Testing announcements feed with invalid JSON or non-existing JSON URL.',
+      'group' => 'Announcements',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    global $base_url;
+    module_load_include('inc', 'announce_feed_test', 'announce_feed_test');
+    parent::setUp('user', 'toolbar', 'announcements_feed', 'announce_feed_test');
+    $this->user = $this->drupalCreateUser(array(
+      'access toolbar',
+      'access announcements',
+    ));
+    $this->drupalLogin($this->user);
+    $this->responseJson = $base_url . '/announcements-feed-json/community-feeds';
+    $this->unknownJson = $base_url . '/announcements-feed-json/unknown';
+    $this->invalidJson = $base_url . '/announcements-feed-json/invalid-feeds';
+    variable_set('announcements_feed_json_url', $this->responseJson);
+  }
+
+  /**
+   * Test the announcements feed with invalid JSON or non-existing JSON URL.
+   */
+  public function testInvalidFeedResponse() {
+    // Test when the JSON URL is not found.
+    $this->user = $this->drupalCreateUser(array(
+      'access toolbar',
+      'access announcements',
+    ));
+    module_load_include('inc', 'announce_feed_test', 'announce_feed_test');
+    $this->drupalLogin($this->user);
+    $this->drupalGet('');
+    $this->clickLink('Announcements');
+    variable_set('announcements_feed_json_url', $this->unknownJson);
+    cache_clear_all('announcements_feed', 'cache', TRUE);
+    $this->drupalGet('admin/announcements_feed');
+    $this->assertText('An error occurred while parsing the announcements feed, check the logs for more information.');
+
+    // Test when the JSON feed is invalid.
+    $this->drupalLogout();
+    $this->drupalLogin($this->user);
+    $this->drupalGet('');
+    $this->clickLink('Announcements');
+    variable_set('announcements_feed_json_url', $this->invalidJson);
+    cache_clear_all('announcements_feed', 'cache', TRUE);
+    $this->drupalGet('admin/announcements_feed');
+    $this->assertText('An error occurred while parsing the announcements feed, check the logs for more information.');
+
+    $this->drupalLogout();
+  }
+}
+