diff --git a/core/modules/node/config/schema/node.source.schema.yml b/core/modules/node/config/schema/node.source.schema.yml
index a9d88e8..987f9b0 100644
--- a/core/modules/node/config/schema/node.source.schema.yml
+++ b/core/modules/node/config/schema/node.source.schema.yml
@@ -40,3 +40,7 @@ migrate.source.d6_node_revision:
       # array of node type IDs, so there is no way to consistently parse this.
       type: ignore
       label: 'Node type'
+
+migrate.source.d7_node_type:
+  type: migrate_source_sql
+  label: 'Drupal 7 node type'
diff --git a/core/modules/node/migration_templates/d7_node.yml b/core/modules/node/migration_templates/d7_node.yml
new file mode 100644
index 0000000..9dbe11a
--- /dev/null
+++ b/core/modules/node/migration_templates/d7_node.yml
@@ -0,0 +1,43 @@
+id: d7_node
+label: Drupal 7 nodes
+migration_tags:
+  - Drupal 7
+builder:
+  plugin: d7_node
+source:
+  plugin: d7_node
+process:
+  nid: nid
+  vid: vid
+  type: type
+  langcode:
+    plugin: default_value
+    source: language
+    default_value: "und"
+  title: title
+  uid: node_uid
+# Core migrations are designed for replacing the upgrade path and therefore
+# all node and user ids are preserved. For that reason we do not need to look-up
+# the user id for the node. If you're writing a custom migration, user ids will
+# vary from the source site and a lookup as shown below will be required.
+#    plugin: migration
+#    migration: d6_user
+#    source: node_uid
+  status: status
+  created: created
+  changed: changed
+  promote: promote
+  sticky: sticky
+  revision_uid: revision_uid
+  revision_log: log
+  revision_timestamp: timestamp
+destination:
+  plugin: entity:node
+dependencies:
+  module:
+    - node
+  config:
+    - migrate.migration.d7_node_type
+migration_dependencies:
+  required:
+    - d7_node_type
diff --git a/core/modules/node/migration_templates/d7_node_settings.yml b/core/modules/node/migration_templates/d7_node_settings.yml
new file mode 100644
index 0000000..a4c4b53
--- /dev/null
+++ b/core/modules/node/migration_templates/d7_node_settings.yml
@@ -0,0 +1,16 @@
+id: d7_node_settings
+label: Drupal 7 node configuration
+migration_tags:
+  - Drupal 7
+source:
+  plugin: variable
+  variables:
+    - node_admin_theme
+process:
+  use_admin_theme: node_admin_theme
+destination:
+  plugin: config
+  config_name: node.settings
+dependencies:
+  module:
+    - node
diff --git a/core/modules/node/migration_templates/d7_node_title_label.yml b/core/modules/node/migration_templates/d7_node_title_label.yml
new file mode 100644
index 0000000..2024949
--- /dev/null
+++ b/core/modules/node/migration_templates/d7_node_title_label.yml
@@ -0,0 +1,19 @@
+id: d7_node_title_label
+label: Drupal 7 node title label
+migration_tags:
+  - Drupal 7
+source:
+  plugin: d7_node_type
+  constants:
+    entity_type: node
+    field_name: title
+process:
+  entity_type: 'constants/entity_type'
+  bundle: type
+  field_name: 'constants/field_name'
+  label: title_label
+destination:
+  plugin: entity:base_field_override
+migration_dependencies:
+  required:
+    - d7_node_type
diff --git a/core/modules/node/migration_templates/d7_node_type.yml b/core/modules/node/migration_templates/d7_node_type.yml
new file mode 100644
index 0000000..270b89c
--- /dev/null
+++ b/core/modules/node/migration_templates/d7_node_type.yml
@@ -0,0 +1,27 @@
+id: d7_node_type
+label: Drupal 7 node type configuration
+migration_tags:
+  - Drupal 7
+source:
+  plugin: d7_node_type
+  constants:
+    preview: 1 # DRUPAL_OPTIONAL
+    has_body: 1 # DRUPAL_OPTION
+process:
+  type: type
+  name: name
+  description: description
+  help: help
+  title_label: title_label
+  preview_mode: 'constants/preview'
+  display_submitted: display_submitted
+  new_revision: 'options/revision'
+  # I don't know what this is, but it does not appear to be a thing.
+  # 'settings/node/options': options
+  create_body: 'constants/has_body'
+  create_body_label: body_label
+destination:
+  plugin: entity:node_type
+dependencies:
+  module:
+    - node
diff --git a/core/modules/node/src/Plugin/migrate/source/d7/Node.php b/core/modules/node/src/Plugin/migrate/source/d7/Node.php
new file mode 100644
index 0000000..cc5b3c0
--- /dev/null
+++ b/core/modules/node/src/Plugin/migrate/source/d7/Node.php
@@ -0,0 +1,101 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Plugin\migrate\source\d7\Node.
+ */
+
+namespace Drupal\node\Plugin\migrate\source\d7;
+
+use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
+
+/**
+ * Drupal 7 node source from database.
+ *
+ * @MigrateSource(
+ *   id = "d7_node"
+ * )
+ */
+class Node extends DrupalSqlBase {
+
+  /**
+   * The join options between the node and the node_revisions table.
+   */
+  const JOIN = 'n.vid = nr.vid';
+
+  /**
+   * The default filter format.
+   *
+   * @var string
+   */
+  protected $filterDefaultFormat;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function query() {
+    // Select node in its last revision.
+    $query = $this->select('node_revision', 'nr')
+      ->fields('n', array(
+        'nid',
+        'type',
+        'language',
+        'status',
+        'created',
+        'changed',
+        'comment',
+        'promote',
+        'sticky',
+        'tnid',
+        'translate',
+      ))
+      ->fields('nr', array(
+        'vid',
+        'title',
+        'log',
+        'timestamp',
+      ));
+    $query->addField('n', 'uid', 'node_uid');
+    $query->addField('nr', 'uid', 'revision_uid');
+    $query->innerJoin('node', 'n', static::JOIN);
+
+    if (isset($this->configuration['node_type'])) {
+      $query->condition('type', $this->configuration['node_type']);
+    }
+
+    return $query;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fields() {
+    $fields = array(
+      'nid' => $this->t('Node ID'),
+      'type' => $this->t('Type'),
+      'title' => $this->t('Title'),
+      'node_uid' => $this->t('Node authored by (uid)'),
+      'revision_uid' => $this->t('Revision authored by (uid)'),
+      'created' => $this->t('Created timestamp'),
+      'changed' => $this->t('Modified timestamp'),
+      'status' => $this->t('Published'),
+      'promote' => $this->t('Promoted to front page'),
+      'sticky' => $this->t('Sticky at top of lists'),
+      'revision' => $this->t('Create new revision'),
+      'language' => $this->t('Language (fr, en, ...)'),
+      'tnid' => $this->t('The translation set id for this node'),
+      'timestamp' => $this->t('The timestamp the latest revision of this node was created.'),
+    );
+    return $fields;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIds() {
+    $ids['nid']['type'] = 'integer';
+    $ids['nid']['alias'] = 'n';
+    return $ids;
+  }
+
+}
diff --git a/core/modules/node/src/Plugin/migrate/source/d7/NodeType.php b/core/modules/node/src/Plugin/migrate/source/d7/NodeType.php
new file mode 100644
index 0000000..5b820b5
--- /dev/null
+++ b/core/modules/node/src/Plugin/migrate/source/d7/NodeType.php
@@ -0,0 +1,127 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Plugin\migrate\source\d7\NodeType.
+ */
+
+namespace Drupal\node\Plugin\migrate\source\d7;
+
+use Drupal\migrate\Row;
+use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
+
+/**
+ * Drupal 7 Node types source from database.
+ *
+ * @MigrateSource(
+ *   id = "d7_node_type"
+ * )
+ */
+class NodeType extends DrupalSqlBase {
+
+  /**
+   * The teaser length
+   *
+   * @var int
+   */
+  protected $teaserLength;
+
+  /**
+   * Node preview optional / required.
+   *
+   * @var int
+   */
+  protected $nodePreview;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function query() {
+    return $this->select('node_type', 't')
+      ->fields('t', array(
+        'type',
+        'name',
+        'base',
+        'description',
+        'help',
+        'title_label',
+        'disabled',
+        'custom',
+        'modified',
+        'locked',
+        'orig_type',
+      ))
+      ->orderBy('t.type');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fields() {
+    return array(
+      'type' => $this->t('Machine name of the node type.'),
+      'name' => $this->t('Human name of the node type.'),
+      'description' => $this->t('Description of the node type.'),
+      'help' => $this->t('Help text for the node type.'),
+      'title_label' => $this->t('Title label.'),
+      'disabled' => $this->t('Flag indicating the node type is enable'),
+      'base' => $this->t('base node.'),
+      'custom' => $this->t('Flag.'),
+      'modified' => $this->t('Flag.'),
+      'locked' => $this->t('Flag.'),
+      'orig_type' => $this->t('The original type.'),
+      'teaser_length' => $this->t('Teaser length'),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function runQuery() {
+    $this->teaserLength = $this->variableGet('teaser_length', 600);
+    $this->nodePreview = $this->variableGet('node_preview', 0);
+    return parent::runQuery();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareRow(Row $row) {
+    $row->setSourceProperty('teaser_length', $this->teaserLength);
+    $row->setSourceProperty('node_preview', $this->nodePreview);
+
+    $type = $row->getSourceProperty('type');
+    $source_options = $this->variableGet('node_options_' . $type, array('promote', 'sticky'));
+    $options = array();
+    foreach (array('promote', 'sticky', 'status', 'revision') as $item) {
+      $options[$item] = in_array($item, $source_options);
+    }
+    $row->setSourceProperty('options', $options);
+
+    // Find body field for this node type.
+    $query = $this->select('field_config_instance', 'fci')
+      ->fields('fci', array('data'))
+      ->condition('entity_type', 'node')
+      ->condition('bundle', $row->getSourceProperty('type'))
+      ->condition('field_name', 'body')
+      ->execute()
+      ->fetchAssoc();
+
+    $body_data = unserialize($query['data']);
+    $body_label = $body_data['label'];
+    $row->setSourceProperty('body_label', $body_label);
+
+    $row->setSourceProperty('display_submitted', $this->variableGet('node_submitted_' . $type, TRUE));
+
+    return parent::prepareRow($row);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIds() {
+    $ids['type']['type'] = 'string';
+    return $ids;
+  }
+
+}
diff --git a/core/modules/node/src/Tests/Migrate/d7/MigrateNodeSettingsTest.php b/core/modules/node/src/Tests/Migrate/d7/MigrateNodeSettingsTest.php
new file mode 100644
index 0000000..21a4761
--- /dev/null
+++ b/core/modules/node/src/Tests/Migrate/d7/MigrateNodeSettingsTest.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Tests\Migrate\d7\MigrateNodeSettingsTest.
+ */
+
+namespace Drupal\node\Tests\Migrate\d7;
+
+use Drupal\config\Tests\SchemaCheckTestTrait;
+use Drupal\migrate\MigrateExecutable;
+use Drupal\migrate_drupal\Tests\d7\MigrateDrupal7TestBase;
+
+/**
+ * Upgrade variables to node.settings config object.
+ *
+ * @group node
+ * @dumpFile d7/Variable.php
+ * @migration d7_node_settings
+ */
+class MigrateNodeSettingsTest extends MigrateDrupal7TestBase {
+
+  use SchemaCheckTestTrait;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['node'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $migration = entity_load('migration', 'd7_node_settings');
+    $executable = new MigrateExecutable($migration, $this);
+    $executable->import();
+  }
+
+  /**
+   * Tests migration of node variables to node.settings config object.
+   */
+  public function testAggregatorSettings() {
+    $config = $this->config('node.settings');
+    $this->assertEqual(1, $config->get('use_admin_theme'));
+  }
+
+}
diff --git a/core/modules/node/src/Tests/Migrate/d7/MigrateNodeTest.php b/core/modules/node/src/Tests/Migrate/d7/MigrateNodeTest.php
new file mode 100644
index 0000000..a94815e
--- /dev/null
+++ b/core/modules/node/src/Tests/Migrate/d7/MigrateNodeTest.php
@@ -0,0 +1,124 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Tests\Migrate\d7\MigrateNodeTest.
+ */
+
+namespace Drupal\node\Tests\Migrate\d7;
+
+use Drupal\migrate\MigrateExecutable;
+use Drupal\migrate_drupal\Tests\d7\MigrateDrupal7TestBase;
+use Drupal\Core\Database\Database;
+use Drupal\node\Entity\Node;
+use Drupal\node\NodeInterface;
+use Drupal\user\Entity\User;
+
+/**
+ * Node content migration.
+ *
+ * @group node
+ * @dumpFile d7/FieldConfig.php
+ * @dumpFile d7/FieldConfigInstance.php
+ * @dumpFile d7/NodeType.php
+ * @dumpFile d7/Node.php
+ * @dumpFile d7/NodeRevision.php
+ * @migration d7_node_type
+ * @migration d7_node
+ */
+class MigrateNodeTest extends MigrateDrupal7TestBase {
+
+  static $modules = array('node', 'text', 'filter', 'entity_reference');
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installEntitySchema('node');
+    $this->installConfig(['node']);
+    $this->installSchema('node', ['node_access']);
+    $this->installSchema('system', ['sequences']);
+
+    User::create([
+      'uid' => 1,
+      'name' => $this->randomMachineName(),
+      'status' => 1,
+    ])->enforceIsNew(TRUE)->save();
+
+    $migration = entity_load('migration', 'd7_node_type');
+    $executable = new MigrateExecutable($migration, $this);
+    $executable->import();
+
+    /** @var \Drupal\migrate\entity\Migration $migration */
+
+    $migration = entity_load('migration', 'd7_node');
+    $executable = new MigrateExecutable($migration, $this);
+    $executable->import();
+
+    // This is required for the second import below.
+    db_truncate($migration->getIdMap()->mapTableName())->execute();
+    $this->standalone = TRUE;
+  }
+
+  protected function assertEntity($id, $type, $langcode, $title, $uid, $status, $created, $changed, $promoted, $sticky) {
+    /** @var \Drupal\node\NodeInterface $node */
+    $node = Node::load($id);
+    $this->assertTrue($node instanceof NodeInterface);
+    $this->assertIdentical($type, $node->getType());
+    $this->assertIdentical($langcode, $node->langcode->value);
+    $this->assertIdentical($title, $node->getTitle());
+    $this->assertIdentical($uid, $node->getOwnerId());
+    $this->assertIdentical($status, $node->isPublished());
+    $this->assertIdentical($created, $node->getCreatedTime());
+    if (isset($changed)) {
+      $this->assertIdentical($changed, $node->getChangedTime());
+    }
+    $this->assertIdentical($promoted, $node->isPromoted());
+    $this->assertIdentical($sticky, $node->isSticky());
+  }
+
+  protected function assertRevision($id, $title, $uid, $log, $timestamp) {
+    $revision = node_revision_load($id);
+    $this->assertTrue($revision instanceof NodeInterface);
+    $this->assertIdentical($title, $revision->getTitle());
+    $this->assertIdentical($uid, $revision->getRevisionAuthor()->id());
+    $this->assertIdentical($log, $revision->revision_log->value);
+    $this->assertIdentical($timestamp, $revision->getRevisionCreationTime());
+  }
+
+  /**
+   * Test node migration from Drupal 7 to 8.
+   */
+  public function testNode() {
+    $this->assertEntity(1, 'test_content_type', 'en', 'A Node', '1', TRUE, '1421727515', '1421727515', TRUE, FALSE);
+    $this->assertRevision(1, 'A Node', '1', '', '1421727515');
+
+    // It is pointless to run the second half from MigrateDrupal6Test.
+    if (empty($this->standalone)) {
+      return;
+    }
+
+    // Change the revision title, then re-import. The revision should be
+    // updated in place.
+    Database::getConnection('default', 'migrate')
+      ->update('node_revision')
+      ->fields(array(
+        'title' => 'New node title',
+      ))
+      ->condition('vid', 1)
+      ->execute();
+
+    /** @var \Drupal\migrate\entity\Migration $migration */
+    $migration = entity_load('migration', 'd7_node');
+    $executable = new MigrateExecutable($migration, $this);
+    $executable->import();
+
+    // Don't test the node's changed time, since it will have been updated by
+    // the import we just did.
+    $this->assertEntity(1, 'test_content_type', 'en', 'New node title', '1', TRUE, '1421727515', NULL, TRUE, FALSE);
+    $this->assertRevision(1, 'New node title', '1', '', '1421727515');
+  }
+
+}
diff --git a/core/modules/node/src/Tests/Migrate/d7/MigrateNodeTitleLabelTest.php b/core/modules/node/src/Tests/Migrate/d7/MigrateNodeTitleLabelTest.php
new file mode 100644
index 0000000..d602abb
--- /dev/null
+++ b/core/modules/node/src/Tests/Migrate/d7/MigrateNodeTitleLabelTest.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Tests\Migrate\d7\MigrateNodeTitleLabelTest.
+ */
+
+namespace Drupal\node\Tests\Migrate\d7;
+
+use Drupal\Core\Field\Entity\BaseFieldOverride;
+use Drupal\migrate_drupal\Tests\d7\MigrateDrupal7TestBase;
+
+/**
+ * @group node
+ */
+class MigrateNodeTitleLabelTest extends MigrateDrupal7TestBase {
+
+  public static $modules = ['node', 'text'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->installConfig(static::$modules);
+    $this->installEntitySchema('node');
+    $this->executeMigration('d7_node_type');
+    $this->executeMigration('d7_node_title_label');
+  }
+
+  /**
+   * Asserts various aspects of a base_field_override entity.
+   *
+   * @param string $id
+   *   The override ID.
+   * @param string $label
+   *   The label's expected (overridden) value.
+   */
+  protected function assertEntity($id, $label) {
+    $override = BaseFieldOverride::load($id);
+    $this->assertTrue($override instanceof BaseFieldOverride);
+    /** @var \Drupal\Core\Field\Entity\BaseFieldOverride $override */
+    $this->assertIdentical($label, $override->getLabel());
+  }
+
+  /**
+   * Tests migration of node title field overrides.
+   */
+  public function testMigration() {
+    $this->assertEntity('node.article.title', 'Title');
+    $this->assertEntity('node.blog.title', 'Title');
+    $this->assertEntity('node.book.title', 'Title');
+    $this->assertEntity('node.forum.title', 'Subject');
+    $this->assertEntity('node.page.title', 'Title');
+    $this->assertEntity('node.test_content_type.title', 'Title');
+  }
+
+}
diff --git a/core/modules/node/src/Tests/Migrate/d7/MigrateNodeTypeTest.php b/core/modules/node/src/Tests/Migrate/d7/MigrateNodeTypeTest.php
new file mode 100644
index 0000000..400b527
--- /dev/null
+++ b/core/modules/node/src/Tests/Migrate/d7/MigrateNodeTypeTest.php
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Tests\Migrate\d7\MigrateNodeTypeTest.
+ */
+
+namespace Drupal\node\Tests\Migrate\d7;
+
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\FieldConfigInterface;
+use Drupal\migrate_drupal\Tests\d7\MigrateDrupal7TestBase;
+use Drupal\node\Entity\NodeType;
+use Drupal\node\NodeTypeInterface;
+
+/**
+ * Upgrade node types to node.type.*.yml.
+ *
+ * @group node
+ */
+class MigrateNodeTypeTest extends MigrateDrupal7TestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('node', 'text', 'filter');
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->installConfig(array('node'));
+    $this->executeMigration('d7_node_type');
+  }
+
+  /**
+   * Tests a single node type.
+   *
+   * @dataProvider testNodeTypeDataProvider
+   * @param string $id
+   *  The node type ID.
+   * @param string $label
+   *  The expected label.
+   * @param string $description
+   *  The expected node type description.
+   * @param string $help
+   *  The expected help text.
+   */
+  protected function assertEntity($id, $label, $description, $help, $display_submitted, $new_revision, $body_label = NULL) {
+    /** @var \Drupal\node\NodeTypeInterface $entity */
+    $entity = NodeType::load($id);
+    $this->assertTrue($entity instanceof NodeTypeInterface);
+    $this->assertIdentical($label, $entity->label());
+    $this->assertIdentical($description, $entity->getDescription());
+    $this->assertIdentical($help, $entity->getHelp());
+
+    $this->assertIdentical($display_submitted, $entity->displaySubmitted());
+    $this->assertIdentical($new_revision, $entity->isNewRevision());
+
+    if ($body_label) {
+      /** @var \Drupal\field\FieldConfigInterface $body */
+      $body = FieldConfig::load('node.' . $id . '.body');
+      $this->assertTrue($body instanceof FieldConfigInterface);
+      $this->assertIdentical($body_label, $body->label());
+    }
+  }
+
+  /**
+   * Tests Drupal 7 node type to Drupal 8 migration.
+   */
+  public function testNodeType() {
+    $this->assertEntity('article', 'Article', 'Use <em>articles</em> for time-sensitive content like news, press releases or blog posts.', 'Help text for articles', TRUE, FALSE, "Body");
+    $this->assertEntity('blog', 'Blog entry', 'Use for multi-user blogs. Every user gets a personal blog.', 'Blog away, good sir!', TRUE, FALSE, 'Body');
+    // book's display_submitted flag is not set, so it will default to TRUE.
+    $this->assertEntity('book', 'Book page', '<em>Books</em> have a built-in hierarchical navigation. Use for handbooks or tutorials.', '', TRUE, TRUE, "Body");
+    $this->assertEntity('forum', 'Forum topic', 'A <em>forum topic</em> starts a new discussion thread within a forum.', 'No name-calling, no flame wars. Be nice.', TRUE, FALSE, 'Body');
+    $this->assertEntity('page', 'Basic page', "Use <em>basic pages</em> for your static content, such as an 'About us' page.", 'Help text for basic pages', FALSE, FALSE, "Body");
+    // This node type does not carry a body field.
+    $this->assertEntity('test_content_type', 'Test content type', 'This is the description of the test content type.', 'Help text for test content type', FALSE, TRUE);
+  }
+
+}
diff --git a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d7/NodeTest.php b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d7/NodeTest.php
new file mode 100644
index 0000000..5c43228
--- /dev/null
+++ b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d7/NodeTest.php
@@ -0,0 +1,117 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\node\Unit\Plugin\migrate\source\d7\NodeTest.
+ */
+
+namespace Drupal\Tests\node\Unit\Plugin\migrate\source\d7;
+
+use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
+
+/**
+ * Tests D7 node source plugin.
+ *
+ * @group node
+ */
+class NodeTest extends MigrateSqlSourceTestCase {
+
+  const PLUGIN_CLASS = 'Drupal\node\Plugin\migrate\source\d7\Node';
+
+  protected $migrationConfiguration = array(
+    'id' => 'test',
+    'source' => array(
+      'plugin' => 'd7_node',
+    ),
+  );
+
+  protected $expectedResults = array(
+    array(
+      // Node fields.
+      'nid' => 1,
+      'vid' => 1,
+      'type' => 'page',
+      'language' => 'en',
+      'title' => 'node title 1',
+      'uid' => 1,
+      'status' => 1,
+      'created' => 1279051598,
+      'changed' => 1279051598,
+      'comment' => 2,
+      'promote' => 1,
+      'sticky' => 0,
+      'tnid' => 0,
+      'translate' => 0,
+      'log' => '',
+      'timestamp' => 1279051598,
+    ),
+    array(
+      // Node fields.
+      'nid' => 2,
+      'vid' => 2,
+      'type' => 'page',
+      'language' => 'en',
+      'title' => 'node title 2',
+      'uid' => 1,
+      'status' => 1,
+      'created' => 1279290908,
+      'changed' => 1279308993,
+      'comment' => 0,
+      'promote' => 1,
+      'sticky' => 0,
+      'tnid' => 0,
+      'translate' => 0,
+      'log' => '',
+      'timestamp' => 1279308993,
+    ),
+    array(
+      // Node fields.
+      'nid' => 5,
+      'vid' => 5,
+      'type' => 'article',
+      'language' => 'en',
+      'title' => 'node title 5',
+      'uid' => 1,
+      'status' => 1,
+      'created' => 1279290908,
+      'changed' => 1279308993,
+      'comment' => 0,
+      'promote' => 1,
+      'sticky' => 0,
+      'tnid' => 0,
+      'translate' => 0,
+      'log' => '',
+      'timestamp' => 1279308993,
+    ),
+  );
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    foreach ($this->expectedResults as $k => $row) {
+      foreach (array('nid', 'vid', 'title', 'uid', 'timestamp', 'log') as $field) {
+        $this->databaseContents['node_revision'][$k][$field] = $row[$field];
+        switch ($field) {
+          case 'nid': case 'vid':
+            break;
+          case 'uid':
+            $this->databaseContents['node_revision'][$k]['uid']++;
+            break;
+          default:
+            unset($row[$field]);
+            break;
+        }
+      }
+      $this->databaseContents['node'][$k] = $row;
+    }
+    array_walk($this->expectedResults, function (&$row) {
+      $row['node_uid'] = $row['uid'];
+      $row['revision_uid'] = $row['uid'] + 1;
+      unset($row['uid']);
+    });
+
+    parent::setUp();
+  }
+
+}
diff --git a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d7/NodeTypeTest.php b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d7/NodeTypeTest.php
new file mode 100644
index 0000000..5f444b0
--- /dev/null
+++ b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d7/NodeTypeTest.php
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\node\Unit\Plugin\migrate\source\d7\NodeTypeTest.
+ */
+
+namespace Drupal\Tests\node\Unit\Plugin\migrate\source\d7;
+
+use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
+
+/**
+ * Tests D7 node type source plugin.
+ *
+ * @group node
+ */
+class NodeTypeTest extends MigrateSqlSourceTestCase {
+
+  const PLUGIN_CLASS = 'Drupal\node\Plugin\migrate\source\d7\NodeType';
+
+  protected $migrationConfiguration = array(
+    'id' => 'test_nodetypes',
+    'source' => array(
+      'plugin' => 'd7_node_type',
+    ),
+  );
+
+  protected $expectedResults = array(
+    array(
+      'type' => 'page',
+      'name' => 'Page',
+      'base' => 'node',
+      'description' => 'A <em>page</em>, similar in form to a <em>story</em>, is a simple method for creating and displaying information that rarely changes, such as an "About us" section of a website. By default, a <em>page</em> entry does not allow visitor comments and is not featured on the site\'s initial home page.',
+      'help' => '',
+      'title_label' => 'Title',
+      'custom' => 1,
+      'modified' => 0,
+      'locked' => 0,
+      'disabled' => 0,
+      'orig_type' => 'page',
+    ),
+    array(
+      'type' => 'story',
+      'name' => 'Story',
+      'base' => 'node',
+      'description' => 'A <em>story</em>, similar in form to a <em>page</em>, is ideal for creating and displaying content that informs or engages website visitors. Press releases, site announcements, and informal blog-like entries may all be created with a <em>story</em> entry. By default, a <em>story</em> entry is automatically featured on the site\'s initial home page, and provides the ability to post comments.',
+      'help' => '',
+      'title_label' => 'Title',
+      'custom' => 1,
+      'modified' => 0,
+      'locked' => 0,
+      'disabled' => 0,
+      'orig_type' => 'story',
+    ),
+  );
+
+  /**
+   * Prepopulate contents with results.
+   */
+  protected function setUp() {
+    $this->databaseContents['node_type'] = $this->expectedResults;
+    $this->databaseContents['variable'] = array(
+      array(
+        'name' => 'node_options_page',
+        'value' => 'a:1:{i:0;s:6:"status";}',
+      ),
+      array(
+        'name' => 'node_options_story',
+        'value' => 'a:1:{i:0;s:6:"status";}',
+      ),
+    );
+    $this->databaseContents['field_config_instance'] = array(
+      array(
+        'entity_type' => 'node',
+        'bundle' => 'page',
+        'field_name' => 'body',
+        'data' => 'a:1:{s:5:"label";s:4:"Body";}',
+      ),
+      array(
+        'entity_type' => 'node',
+        'bundle' => 'story',
+        'field_name' => 'body',
+        'data' => 'a:1:{s:5:"label";s:4:"Body";}',
+      )
+    );
+    parent::setUp();
+  }
+
+}
