diff --git a/app/assets/stylesheets/posts.scss b/app/assets/stylesheets/posts.scss
index 53e70c89896fbc09b611c1a4655549970e5bf677..ed8df6017776e1d827dd7831088bf65cd936f965 100644
--- a/app/assets/stylesheets/posts.scss
+++ b/app/assets/stylesheets/posts.scss
@@ -75,3 +75,12 @@ h1 .badge.is-tag.is-master-tag {
     margin-bottom: 0;
   }
 }
+
+.post--title {
+  display: flex;
+  align-items: center;
+
+  > .badge {
+    margin-left: 0.5em;
+  }
+}
diff --git a/app/controllers/articles_controller.rb b/app/controllers/articles_controller.rb
index 695b0e164bb5aae82c94e0807cf636d962367177..b4253e185eb1beadc400200cd19b652cc29e2b0c 100644
--- a/app/controllers/articles_controller.rb
+++ b/app/controllers/articles_controller.rb
@@ -1,5 +1,6 @@
 class ArticlesController < ApplicationController
   before_action :set_article
+  before_action :check_article
 
   def show
     if @article.deleted?
@@ -21,7 +22,7 @@ class ArticlesController < ApplicationController
     PostHistory.post_edited(@article, current_user, before: @article.body_markdown,
                             after: params[:article][:body_markdown], comment: params[:edit_comment])
     body_rendered = helpers.render_markdown(params[:article][:body_markdown])
-    if @article.update(article_params.merge(tags_cache: params[:article][:tags_cache]&.reject(&:empty?),
+    if @article.update(article_params.merge(tags_cache: params[:article][:tags_cache]&.reject { |e| e.to_s.empty? },
                                             body: body_rendered, last_activity: DateTime.now,
                                             last_activity_by: current_user))
       redirect_to article_path(@article)
@@ -30,10 +31,59 @@ class ArticlesController < ApplicationController
     end
   end
 
+  def destroy
+    unless check_your_privilege('Delete', @article, false)
+      flash[:danger] = 'You must have the Delete privilege to delete posts.'
+      redirect_to article_path(@article) && return
+    end
+
+    if @article.deleted
+      flash[:danger] = "Can't delete a deleted post."
+      redirect_to article_path(@article) && return
+    end
+
+    if @article.update(deleted: true, deleted_at: DateTime.now, deleted_by: current_user,
+                       last_activity: DateTime.now, last_activity_by: current_user)
+      PostHistory.post_deleted(@article, current_user)
+    else
+      flash[:danger] = "Can't delete this post right now. Try again later."
+    end
+    redirect_to article_path(@article)
+  end
+
+  def undelete
+    unless check_your_privilege('Delete', @article, false)
+      flash[:danger] = 'You must have the Delete privilege to undelete posts.'
+      redirect_to article_path(@article) && return
+    end
+
+    unless @article.deleted
+      flash[:danger] = "Can't undelete an undeleted post."
+      redirect_to article_path(@article) && return
+    end
+
+    if @article.update(deleted: false, deleted_at: nil, deleted_by: nil,
+                       last_activity: DateTime.now, last_activity_by: current_user)
+      PostHistory.post_undeleted(@article, current_user)
+    else
+      flash[:danger] = "Can't undelete this article right now. Try again later."
+    end
+    redirect_to article_path(@article)
+  end
+
   private
 
   def set_article
     @article = Article.find params[:id]
+    if @article.deleted && !current_user&.has_post_privilege?('ViewDeleted', @article)
+      not_found
+    end
+  end
+
+  def check_article
+    unless @article.post_type_id == Article.post_type_id
+      not_found
+    end
   end
 
   def article_params
diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb
index ed4091c9e9764c1d3972df3589f7b78442247aa2..0970ba6e38e5a50178b1e1fef08d40d2f31d6173 100644
--- a/app/controllers/posts_controller.rb
+++ b/app/controllers/posts_controller.rb
@@ -15,7 +15,8 @@ class PostsController < ApplicationController
 
   def create
     @category = Category.find(params[:category_id])
-    @post = Post.new(post_params.merge(category: @category, user: current_user, post_type_id: params[:post_type_id],
+    @post = Post.new(post_params.merge(category: @category, user: current_user,
+                                       post_type_id: params[:post][:post_type_id] || params[:post_type_id],
                                        body: helpers.render_markdown(params[:post][:body_markdown])))
 
     if @category.min_trust_level.present? && @category.min_trust_level > current_user.trust_level
@@ -25,7 +26,7 @@ class PostsController < ApplicationController
     end
 
     if @post.save
-      redirect_to question_path(@post)
+      redirect_to helpers.generic_show_link(@post)
     else
       render :new, status: 400
     end
diff --git a/app/controllers/questions_controller.rb b/app/controllers/questions_controller.rb
index cf9ce5ba2a4a78291031710abbdcba12429ff8dc..8d478f8be87405909b732f52a395610939e9860e 100644
--- a/app/controllers/questions_controller.rb
+++ b/app/controllers/questions_controller.rb
@@ -65,7 +65,7 @@ class QuestionsController < ApplicationController
     PostHistory.post_edited(@question, current_user, before: @question.body_markdown,
                             after: params[:question][:body_markdown], comment: params[:edit_comment])
     body_rendered = helpers.render_markdown(params[:question][:body_markdown])
-    if @question.update(question_params.merge(tags_cache: params[:question][:tags_cache]&.reject(&:empty?),
+    if @question.update(question_params.merge(tags_cache: params[:question][:tags_cache]&.reject { |e| e.to_s.empty? },
                                               body: body_rendered, last_activity: DateTime.now,
                                               last_activity_by: current_user))
       redirect_to url_for(controller: :questions, action: :show, id: @question.id)
@@ -191,11 +191,15 @@ class QuestionsController < ApplicationController
   def set_question
     @question = Question.find params[:id]
   rescue
-    if current_user.has_privilege?('ViewDeleted')
+    if current_user&.has_privilege?('ViewDeleted')
       @question ||= Question.unscoped.find params[:id]
     end
     if @question.nil?
-      render template: 'errors/not_found', status: 404
+      not_found
+      return
+    end
+    unless @question.post_type_id == Question.post_type_id
+      not_found
     end
   end
 end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 7f6c1a8ebe13e43ba472f1a97f268cc6da3b9e18..ef7d04e6b1d9e95dcca2a06fffab14eee7bb0402 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -101,4 +101,15 @@ module ApplicationHelper
       '#'
     end
   end
+
+  def generic_show_link(post)
+    case post.post_type_id
+    when Question.post_type_id
+      question_url(post)
+    when Article.post_type_id
+      article_url(post)
+    else
+      '#'
+    end
+  end
 end
diff --git a/app/helpers/post_types_helper.rb b/app/helpers/post_types_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ddd6bc36541b12bdb19f9257161b54300e66f1a4
--- /dev/null
+++ b/app/helpers/post_types_helper.rb
@@ -0,0 +1,12 @@
+module PostTypesHelper
+  def post_type_badge(type)
+    icon_class = {
+      'Question' => 'fas fa-question',
+      'Article' => 'fas fa-newspaper'
+    }[type]
+    tag.span class: 'badge is-tag is-filled is-muted' do
+      tag.i(class: icon_class) + ' ' +
+        tag.span(type)
+    end
+  end
+end
diff --git a/app/models/article.rb b/app/models/article.rb
index 127ebc0aa4157aaff0d886a34038865ad318ad42..f5cb2f4d5fadfb897caa20437a79fb57b7105654 100644
--- a/app/models/article.rb
+++ b/app/models/article.rb
@@ -4,4 +4,4 @@ class Article < Post
   def self.post_type_id
     PostType.mapping['Article']
   end
-end
\ No newline at end of file
+end
diff --git a/app/models/post.rb b/app/models/post.rb
index 036a493110526653c323d90d55d870d74c1d33ae..601c7189efd6148547d5c1dd20f8d25572c1e3ed 100644
--- a/app/models/post.rb
+++ b/app/models/post.rb
@@ -23,15 +23,15 @@ class Post < ApplicationRecord
 
   validates :body, presence: true, length: { minimum: 30, maximum: 30_000 }
   validates :doc_slug, uniqueness: { scope: [:community_id] }, if: -> { doc_slug.present? }
-  validates :title, :body, :tags_cache, presence: true, if: :question?
-  validate :tags_in_tag_set, if: :question?
-  validate :maximum_tags, if: :question?
-  validate :maximum_tag_length, if: :question?
-  validate :no_spaces_in_tags, if: :question?
-  validate :stripped_minimum, if: :question?
+  validates :title, :body, :tags_cache, presence: true, if: -> { question? || article? }
+  validate :tags_in_tag_set, if: -> { question? || article? }
+  validate :maximum_tags, if: -> { question? || article? }
+  validate :maximum_tag_length, if: -> { question? || article? }
+  validate :no_spaces_in_tags, if: -> { question? || article? }
+  validate :stripped_minimum, if: -> { question? || article? }
   validate :category_allows_post_type
   validate :license_available
-  validate :required_tags?, if: -> { post_type_id == Question.post_type_id }
+  validate :required_tags?, if: -> { question? || article? }
 
   scope :undeleted, -> { where(deleted: false) }
   scope :deleted, -> { where(deleted: true) }
@@ -42,8 +42,8 @@ class Post < ApplicationRecord
   after_save :modify_author_reputation
   after_save :copy_last_activity_to_parent
   after_save :break_description_cache
-  after_save :update_category_activity, if: :question?
-  before_validation :update_tag_associations, if: :question?
+  after_save :update_category_activity, if: -> { question? || article? }
+  before_validation :update_tag_associations, if: -> { question? || article? }
   after_create :create_initial_revision
   after_create :add_license_if_nil
 
@@ -53,7 +53,7 @@ class Post < ApplicationRecord
 
   # Double-define: initial definitions are less efficient, so if we have a record of the post type we'll
   # override them later with more efficient methods.
-  ['Question', 'Answer', 'PolicyDoc', 'HelpDoc'].each do |pt|
+  ['Question', 'Answer', 'PolicyDoc', 'HelpDoc', 'Article'].each do |pt|
     define_method "#{pt.underscore}?" do
       post_type_id == pt.constantize.post_type_id
     end
diff --git a/app/views/articles/_form.html.erb b/app/views/articles/_form.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..bb9878bdeecde21fec65feb7a6381cf49f01a893
--- /dev/null
+++ b/app/views/articles/_form.html.erb
@@ -0,0 +1,47 @@
+<%= render 'posts/markdown_script' %>
+
+<% if @article.errors.any? %>
+  <div class="notice is-danger is-filled">
+    The following errors prevented this post from being saved:
+    <ul>
+      <% @article.errors.full_messages.each do |msg| %>
+        <li><%= msg %></li>
+      <% end %>
+    </ul>
+  </div>
+<% end %>
+
+<%= render 'posts/image_upload' %>
+
+<%= form_for @article, url: edit_article_path(@article) do |f| %>
+  <div class="form-group">
+    <%= f.label :title, "Title your post:", class: "form-element" %>
+    <%= f.text_field :title, class: "form-element" %>
+  </div>
+
+  <%= render 'shared/body_field', f: f, field_name: :body_markdown, field_label: 'Body' %>
+
+  <div class="post-preview"></div>
+
+  <div class="form-group">
+    <%= f.label :tags_cache, "Tags", class: "form-element" %>
+    <div class="form-caption">
+      Tags help to categorize posts. Separate them by space. Use hyphens for multiple-word tags.
+    </div>
+    <%= f.select :tags_cache, options_for_select(@article.tags_cache.map { |t| [t, t] }, selected: @article.tags_cache),
+                 { include_blank: true }, multiple: true, class: "form-element js-tag-select",
+                 data: { tag_set: @article.category.tag_set.id } %>
+  </div>
+
+  <div class="form-group">
+    <%= label_tag :edit_comment, 'Edit comment', class: "form-element" %>
+    <div class="form-caption">
+      Describe&mdash;if necessary&mdash;what you are changing and why you are making this edit.
+    </div>
+    <%= text_field_tag :edit_comment, params[:edit_comment], class: 'form-element' %>
+  </div>
+
+  <div class="form-group">
+    <%= f.submit 'Save', class: "button is-filled" %><br/>
+  </div>
+<% end %>
diff --git a/app/views/articles/edit.html.erb b/app/views/articles/edit.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..6727a85a707a9f1bfb920d6498c26629bb9bfc8f
--- /dev/null
+++ b/app/views/articles/edit.html.erb
@@ -0,0 +1 @@
+<%= render 'form', is_edit: true %>
\ No newline at end of file
diff --git a/app/views/posts/_article_list.html.erb b/app/views/posts/_article_list.html.erb
index 63d781a05e3278306fd67a16ed18e57250d717c3..237edc9cf67c596e45d4839992557eb26c84513f 100644
--- a/app/views/posts/_article_list.html.erb
+++ b/app/views/posts/_article_list.html.erb
@@ -12,9 +12,13 @@
       last activity <%= time_ago_in_words(post.last_activity) %> ago by <%= link_to active_user.username, user_path(active_user) %>
     </p>
     <div class="has-padding-top-2">
+      <% category = defined?(@category) ? @category : post.category %>
+      <% if category.display_post_types.reject { |e| e.to_s.empty? }.size > 1 %>
+        <%= post_type_badge(post.post_type.name) %>
+      <% end %>
       <% tag_set = post.tag_set %>
-      <% required_ids = defined?(@category) ? @category&.required_tag_ids : post.category&.required_tag_ids %>
-      <% topic_ids = defined?(@category) ? @category&.topic_tag_ids : post.category&.topic_tag_ids %>
+      <% required_ids = category&.required_tag_ids %>
+      <% topic_ids = category&.topic_tag_ids %>
       <% category_sort_tags(post.tags, required_ids, topic_ids).each do |tag| %>
         <% required = required_ids&.include? tag.id %>
         <% topic = topic_ids&.include? tag.id %>
diff --git a/app/views/posts/_expanded.html.erb b/app/views/posts/_expanded.html.erb
index 9c4156919ed3caf8ddfb3fe6eb0b6bc9002c11e8..f72f888ec46908f23f34fa6d88e5b40362b126d9 100644
--- a/app/views/posts/_expanded.html.erb
+++ b/app/views/posts/_expanded.html.erb
@@ -7,6 +7,10 @@
     <h1 class="post--title has-border-top-width-4 has-border-top-style-solid has-border-color-<%= post.meta? ? 'tertiary' : 'primary' %>-400 has-padding-2">
       <%= post.title %>
       <%= is_question && post.closed ? "[closed]" : "" %>
+      <% category = defined?(@category) ? @category : post.category %>
+      <% if category.display_post_types.reject { |e| e.to_s.nil? }.size > 1 %>
+        <%= post_type_badge(post.post_type.name) %>
+      <% end %>
     </h1>
   <% end %>
 
@@ -128,10 +132,10 @@
             <%= link_to 'reopen', reopen_question_path(post), method: :post, class: 'reopen-question' %> &middot;
           <% end %>
           <% if !post.deleted %>
-            <%= link_to 'delete', url_for(controller: is_question ? :questions : :answers, action: :destroy, id: post.id),
+            <%= link_to 'delete', url_for(controller: post.post_type.name.pluralize.downcase.to_sym, action: :destroy, id: post.id),
                         method: :delete, data: { confirm: 'Are you sure you want to delete this post?' }, class: "is-red" %>
           <% else %>
-            <%= link_to 'undelete', url_for(controller: is_question ? :questions : :answers, action: :undelete, id: post.id),
+            <%= link_to 'undelete', url_for(controller: post.post_type.name.pluralize.downcase.to_sym, action: :undelete, id: post.id),
                         method: :post, data: { confirm: 'Undelete this question, making it visible to regular users?' }, class: "is-red" %>
           <% end %> &middot;
           <a href="#" class="flag-dialog-link">flag</a>
diff --git a/app/views/posts/_form.html.erb b/app/views/posts/_form.html.erb
index 7bc27bc8712a123c252a98e974fc3339c1eaf5c2..6e71b2d99b300b1136b8946fe95abf6a4ee98339 100644
--- a/app/views/posts/_form.html.erb
+++ b/app/views/posts/_form.html.erb
@@ -1,3 +1,5 @@
+<% with_post_type ||= false %>
+
 <% content_for :head do %>
   <%= render 'posts/markdown_script' %>
 <% end %>
@@ -25,7 +27,20 @@
 
 <%= form_for @post, url: submit_path, html: { class: 'has-margin-top-4' } do |f| %>
   <%= f.hidden_field :category_id %>
-  <%= f.hidden_field :post_type_id %>
+
+  <% if with_post_type %>
+    <div class="form-group">
+      <%= f.label :post_type_id, 'Post type', class: 'form-element' %>
+      <span class="form-caption">What kind of post is this? Questions can have answers; articles only have comments.</span>
+      <% ids = @category.display_post_types.reject { |e| e.to_s.empty? } %>
+      <% post_types = PostType.where(id: ids) %>
+      <% opts = post_types.map { |pt| [pt.name, pt.id] } %>
+      <%= f.select :post_type_id, options_for_select(opts, selected: @post.post_type_id),
+                   { include_blank: true }, class: 'form-element' %>
+    </div>
+  <% else %>
+    <%= f.hidden_field :post_type_id %>
+  <% end %>
 
   <%= render 'shared/body_field', f: f, field_name: :body_markdown, field_label: 'Body' %>
 
diff --git a/app/views/posts/_list.html.erb b/app/views/posts/_list.html.erb
index 57312cf0c21a176582f05a21207403a3441e5d0b..e72067a300ca5fd5f66c3bd5541141f336a4ec17 100644
--- a/app/views/posts/_list.html.erb
+++ b/app/views/posts/_list.html.erb
@@ -1,5 +1,5 @@
 <% is_question = post.post_type_id == Question.post_type_id %>
-<% is_meta = (is_question && post.meta?) || (!is_question && post.parent.meta?) %>
+<% is_meta = (is_question && post.meta?) || (!is_question && post.parent&.meta?) %>
 <% active_user = post.last_activity_by || post.user %>
 <div class="item-list--item <%= is_meta ? 'post__meta' : '' %> <%= post.deleted ? 'deleted-content' : '' %>">
   <div class="item-list--number-value">
@@ -25,10 +25,14 @@
       last activity <%= time_ago_in_words(post.last_activity) %> ago by <%= link_to active_user.username, user_path(active_user) %>	
     </p>
     <div class="has-padding-top-2">
+      <% category = defined?(@category) ? @category : post.category %>
+      <% if category.display_post_types.reject { |e| e.to_s.empty? }.size > 1 %>
+        <%= post_type_badge(post.post_type.name) %>
+      <% end %>
       <% if is_question %>
         <% tag_set = post.tag_set %>
-        <% required_ids = defined?(@category) ? @category&.required_tag_ids : post.category&.required_tag_ids %>
-        <% topic_ids = defined?(@category) ? @category&.topic_tag_ids : post.category&.topic_tag_ids %>
+        <% required_ids = category&.required_tag_ids %>
+        <% topic_ids = category&.topic_tag_ids %>
         <% category_sort_tags(post.tags, required_ids, topic_ids).each do |tag| %>
           <% required = required_ids&.include? tag.id %>
           <% topic = topic_ids&.include? tag.id %>
diff --git a/app/views/posts/new.html.erb b/app/views/posts/new.html.erb
index 09a9d727b3074245fefac79f94073a6d083f11c3..1db9ccf3fdc9292ecaef382020eb94e25d15532c 100644
--- a/app/views/posts/new.html.erb
+++ b/app/views/posts/new.html.erb
@@ -1,4 +1,5 @@
 <h1 class="has-margin-bottom-2">New Post in <%= @category.name %></h1>
 <p class="has-color-tertiary-500">Not where you meant to post? See <%= link_to 'Categories', categories_path %></p>
 
-<%= render 'form', submit_path: create_post_path(@category.id) %>
\ No newline at end of file
+<%= render 'form', with_post_type: @category.display_post_types.reject { |e| e.to_s.empty? }.size > 1,
+           submit_path: create_post_path(@category.id) %>
\ No newline at end of file
diff --git a/app/views/questions/tagged.html.erb b/app/views/questions/tagged.html.erb
index 63bfaafc4ba5e91d0b85c2a1557c648fd3844cf3..2aa7fe81eff2e4dc180ee7291349292ada569157 100644
--- a/app/views/questions/tagged.html.erb
+++ b/app/views/questions/tagged.html.erb
@@ -4,7 +4,7 @@
 
 <div class="item-list">
   <% @questions.each do |question| %>
-    <%= render 'posts/list', post: question %>
+    <%= render 'posts/type_agnostic', post: question %>
   <% end %>
 </div><br/>
 
diff --git a/config/routes.rb b/config/routes.rb
index c1d21a04a4dd5de3d4de448c5560a342735b5e62..af9ed46242e21bdaffb45d8ff520427d48fc4666 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -69,7 +69,11 @@ Rails.application.routes.draw do
   post   'questions/:id/reopen',           to: 'questions#reopen', as: :reopen_question
 
   scope 'articles' do
-    get  ':id',                            to: 'articles#show', as: :article
+    get    ':id',                          to: 'articles#show', as: :article
+    get    ':id/edit',                     to: 'articles#edit', as: :edit_article
+    patch  ':id/edit',                     to: 'articles#update', as: :update_article
+    delete ':id/delete',                   to: 'articles#destroy', as: :destroy_article
+    delete ':id/undelete',                 to: 'articles#undelete', as: :undelete_article
   end
 
   get    'posts/:id/history',              to: 'post_history#post', as: :post_history
diff --git a/test/controllers/articles_controller_test.rb b/test/controllers/articles_controller_test.rb
index aae324859ff2a11295456dc800dfb8cc36c6b4eb..f431f0687565af6c18be9b36eaf8053e34b97c2e 100644
--- a/test/controllers/articles_controller_test.rb
+++ b/test/controllers/articles_controller_test.rb
@@ -1,7 +1,57 @@
 require 'test_helper'
 
-class ArticlesControllerTest < ActionDispatch::IntegrationTest
-  # test "the truth" do
-  #   assert true
-  # end
+class ArticlesControllerTest < ActionController::TestCase
+  include Devise::Test::ControllerHelpers
+
+  test 'should get show article page' do
+    get :show, params: { id: posts(:article_one).id }
+    assert_not_nil assigns(:article)
+    assert_response(200)
+  end
+
+  test 'should get show article page with deleted article' do
+    sign_in users(:deleter)
+    get :show, params: { id: posts(:deleted_article).id }
+    assert_not_nil assigns(:article)
+    assert_response(200)
+  end
+
+  test 'should prevent unprivileged user seeing deleted post' do
+    get :show, params: { id: posts(:deleted_article).id }
+    assert_response 404
+  end
+
+  test 'should get edit article page' do
+    sign_in users(:editor)
+    get :edit, params: { id: posts(:article_one).id }
+    assert_not_nil assigns(:article)
+    assert_response(200)
+  end
+
+  test 'should update existing article' do
+    sign_in users(:editor)
+    patch :update, params: { id: posts(:article_one).id, article: { title: 'ABCDEF GHIJKL MNOPQR',
+                                                                    body_markdown: 'ABCDEF GHIJKL MNOPQR STUVWX YZ',
+                                                                    tags_cache: ['discussion', 'support'] } }
+    assert_not_nil assigns(:article)
+    assert_equal ['discussion', 'support'], assigns(:article).tags_cache
+    assert_equal ['discussion', 'support'], assigns(:article).tags.map(&:name)
+    assert_response(302)
+  end
+
+  test 'should mark article deleted' do
+    sign_in users(:deleter)
+    delete :destroy, params: { id: posts(:article_one).id }
+    assert_not_nil assigns(:article)
+    assert_equal true, assigns(:article).deleted
+    assert_response(302)
+  end
+
+  test 'should mark article undeleted' do
+    sign_in users(:deleter)
+    delete :undelete, params: { id: posts(:deleted_article).id }
+    assert_not_nil assigns(:article)
+    assert_equal false, assigns(:article).deleted
+    assert_response(302)
+  end
 end
diff --git a/test/controllers/moderator_controller_test.rb b/test/controllers/moderator_controller_test.rb
index 9350bc885e3a34f0ea2bf6a082dae895f550cd9b..8528ecc9605b2dcb4e8145ca02f546bab84e9c5e 100644
--- a/test/controllers/moderator_controller_test.rb
+++ b/test/controllers/moderator_controller_test.rb
@@ -9,46 +9,6 @@ class ModeratorControllerTest < ActionController::TestCase
     assert_response(200)
   end
 
-  test 'should get recently deleted questions' do
-    sign_in users(:moderator)
-    get :recently_deleted_questions
-    assert_not_nil assigns(:questions)
-    assigns(:questions).each do |question|
-      assert_equal true, question.deleted
-    end
-    assert_response(200)
-  end
-
-  test 'should get recently deleted answers' do
-    sign_in users(:moderator)
-    get :recently_deleted_answers
-    assert_not_nil assigns(:answers)
-    assigns(:answers).each do |answer|
-      assert_equal true, answer.deleted
-    end
-    assert_response(200)
-  end
-
-  test 'should get recently undeleted questions' do
-    sign_in users(:moderator)
-    get :recently_undeleted_questions
-    assert_not_nil assigns(:questions)
-    assigns(:questions).each do |question|
-      assert_equal false, question.deleted
-    end
-    assert_response(200)
-  end
-
-  test 'should get recently undeleted answers' do
-    sign_in users(:moderator)
-    get :recently_undeleted_answers
-    assert_not_nil assigns(:answers)
-    assigns(:answers).each do |answer|
-      assert_equal false, answer.deleted
-    end
-    assert_response(200)
-  end
-
   test 'should require authentication to access pages' do
     sign_out :user
     [:index, :recently_deleted_answers, :recently_deleted_questions, :recently_undeleted_answers,
diff --git a/test/controllers/questions_controller_test.rb b/test/controllers/questions_controller_test.rb
index 32cf644c5eff6a612784503da02b765b3cc6eec7..9ca6af0f0ab5a429b7b5c8bbc0f591e4e29dffc0 100644
--- a/test/controllers/questions_controller_test.rb
+++ b/test/controllers/questions_controller_test.rb
@@ -189,4 +189,10 @@ class QuestionsControllerTest < ActionController::TestCase
     assert_not_nil flash[:danger]
     assert_response(302)
   end
+
+  test 'should prevent using questions routes for articles' do
+    sign_in users(:deleter) # deliberate; catch ViewDeleted using unscoped
+    get :show, params: { id: posts(:article_one).id }
+    assert_response 404
+  end
 end
diff --git a/test/fixtures/categories.yml b/test/fixtures/categories.yml
index 53bae5c6fa99f7cc6f3250afea5b639d9c5ad030..df944e28aa218c38d286359f66d756564a21fe96 100644
--- a/test/fixtures/categories.yml
+++ b/test/fixtures/categories.yml
@@ -6,9 +6,11 @@ main:
   short_wiki: Main Q&A
   display_post_types:
     - <%= Question.post_type_id %>
+    - <%= Article.post_type_id %>
   post_types:
     - question
     - answer
+    - article
   tag_set: main
   license: cc_by_sa
 
diff --git a/test/fixtures/post_types.yml b/test/fixtures/post_types.yml
index 4736a9e20f7916d48956eaff2a7958184c15192d..71c578bf15cd658dd8bb260ec97eaa0f725e874c 100644
--- a/test/fixtures/post_types.yml
+++ b/test/fixtures/post_types.yml
@@ -4,6 +4,9 @@ question:
 answer:
   name: Answer
 
+article:
+  name: Article
+
 policy_doc:
   name: PolicyDoc
 
diff --git a/test/fixtures/posts.yml b/test/fixtures/posts.yml
index e8b53a4dded8c8f450a73c94f8ac39c1983275a0..3039cfa852186945c6a05201a853e27cc5d9a939 100644
--- a/test/fixtures/posts.yml
+++ b/test/fixtures/posts.yml
@@ -147,3 +147,44 @@ help_doc:
   user: admin
   community: sample
   license: cc_by_sa
+
+article_one:
+  post_type: article
+  title: Q1 ABCDEF GHIJKL MNOPQR STUVWX YZ
+  body: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
+  body_markdown: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
+  tags_cache:
+    - discussion
+    - support
+    - bug
+  tags:
+    - discussion
+    - support
+    - bug
+  score: 0
+  user: standard_user
+  community: sample
+  category: main
+  license: cc_by_sa
+
+deleted_article:
+  post_type: article
+  title: Q1 ABCDEF GHIJKL MNOPQR STUVWX YZ
+  body: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
+  body_markdown: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
+  tags_cache:
+    - discussion
+    - support
+    - bug
+  tags:
+    - discussion
+    - support
+    - bug
+  score: 0
+  user: standard_user
+  community: sample
+  category: main
+  license: cc_by_sa
+  deleted: true
+  deleted_at: 2019-01-01T00:00:00.000000Z
+  deleted_by: admin