diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb
index cbe13600ab04f92097eb38e9734ea8064a43234e..846972e6168516a53261d41bee5b6bff02a62420 100644
--- a/app/controllers/posts_controller.rb
+++ b/app/controllers/posts_controller.rb
@@ -213,7 +213,30 @@ class PostsController < ApplicationController
     end
   end
 
-  # TODO: delete, undelete, close, reopen
+  def reopen
+    unless check_your_privilege('flag_close', nil, false)
+      flash[:danger] = helpers.ability_err_msg(:flag_close, 'reopen this post')
+      redirect_to post_path(@post)
+      return
+    end
+
+    unless @post.closed
+      flash[:danger] = 'Cannot reopen an open post.'
+      redirect_to post_path(@post)
+      return
+    end
+
+    if @post.update(closed: false, closed_by: current_user, closed_at: Time.zone.now,
+                    last_activity: DateTime.now, last_activity_by: current_user,
+                    close_reason: nil, duplicate_post: nil)
+      PostHistory.question_reopened(@post, current_user)
+    else
+      flash[:danger] = "Can't reopen this post right now. Try again later."
+    end
+    redirect_to post_path(@post)
+  end
+
+  # TODO: delete, undelete
 
   def document
     @post = Post.unscoped.where(doc_slug: params[:slug], community_id: [RequestContext.community_id, nil]).first
diff --git a/app/controllers/questions_controller.rb b/app/controllers/questions_controller.rb
index 3d1296e7b740f6ef033e9ded7bd97903f1e5ab4d..ae4637bd12b32bc9d14eef8425b6d76c5b774406 100644
--- a/app/controllers/questions_controller.rb
+++ b/app/controllers/questions_controller.rb
@@ -82,27 +82,6 @@ class QuestionsController < ApplicationController
     end
   end
 
-  def reopen
-    unless check_your_privilege('flag_close', nil, false)
-      flash[:danger] = helpers.ability_err_msg(:flag_close, 'reopen this question')
-      redirect_to(question_path(@question)) && return
-    end
-
-    unless @question.closed
-      flash[:danger] = 'Cannot reopen an open question.'
-      redirect_to(question_path(@question)) && return
-    end
-
-    if @question.update(closed: false, closed_by: current_user, closed_at: Time.zone.now,
-                        last_activity: DateTime.now, last_activity_by: current_user,
-                        close_reason: nil, duplicate_post: nil)
-      PostHistory.question_reopened(@question, current_user)
-    else
-      flash[:danger] = "Can't reopen this question right now. Try again later."
-    end
-    redirect_to question_path(@question)
-  end
-
   private
 
   def question_params
diff --git a/test/controllers/posts_controller_test.rb b/test/controllers/posts_controller_test.rb
index 9c3465f1951c2a10651845d7e79f12793a3ef83b..3302409b56a56306789f818a2b093059a4da6781 100644
--- a/test/controllers/posts_controller_test.rb
+++ b/test/controllers/posts_controller_test.rb
@@ -54,6 +54,8 @@ class PostsControllerTest < ActionController::TestCase
                  JSON.parse(response.body)['errors']
   end
 
+  # New
+
   test 'should get new' do
     sign_in users(:moderator)
     get :new, params: { post_type: post_types(:help_doc).id }
@@ -78,6 +80,8 @@ class PostsControllerTest < ActionController::TestCase
     assert_redirected_to new_user_session_path
   end
 
+  # Create
+
   test 'can create help post' do
     sign_in users(:moderator)
     post :create, params: { post_type: post_types(:help_doc).id,
@@ -181,6 +185,8 @@ class PostsControllerTest < ActionController::TestCase
     assert_equal before + 1, after, 'No CommunityUser record was created'
   end
 
+  # Show
+
   test 'anonymous user can get show' do
     get :show, params: { id: posts(:question_one).id }
     assert_response 200
@@ -221,6 +227,8 @@ class PostsControllerTest < ActionController::TestCase
     assert_redirected_to post_path(posts(:answer_one).parent_id)
   end
 
+  # Edit
+
   test 'can get edit' do
     sign_in users(:standard_user)
     get :edit, params: { id: posts(:question_one).id }
@@ -234,6 +242,8 @@ class PostsControllerTest < ActionController::TestCase
     assert_redirected_to new_user_session_path
   end
 
+  # Update
+
   test 'can update post' do
     sign_in users(:standard_user)
     before_history = PostHistory.where(post: posts(:question_one)).count
@@ -303,6 +313,8 @@ class PostsControllerTest < ActionController::TestCase
     assert_equal before_history, after_history, 'PostHistory event incorrectly created on no-change update'
   end
 
+  # Close
+
   test 'can close question' do
     sign_in users(:closer)
     before_history = PostHistory.where(post: posts(:question_one)).count
@@ -378,4 +390,45 @@ class PostsControllerTest < ActionController::TestCase
     end
     assert_equal 'failed', JSON.parse(response.body)['status']
   end
+
+  # Reopen
+
+  test 'can reopen question' do
+    sign_in users(:closer)
+    before_history = PostHistory.where(post: posts(:closed)).count
+    post :reopen, params: { id: posts(:closed).id }
+    after_history = PostHistory.where(post: posts(:closed)).count
+    assert_response 302
+    assert_redirected_to post_path(posts(:closed))
+    assert_nil flash[:danger]
+    assert_equal before_history + 1, after_history, 'PostHistory event not created on reopen'
+  end
+
+  test 'reopen requires authentication' do
+    post :reopen, params: { id: posts(:closed).id }
+    assert_response 302
+    assert_redirected_to new_user_session_path
+  end
+
+  test 'unprivileged user cannot reopen' do
+    sign_in users(:standard_user)
+    before_history = PostHistory.where(post: posts(:closed)).count
+    post :reopen, params: { id: posts(:closed).id }
+    after_history = PostHistory.where(post: posts(:closed)).count
+    assert_response 302
+    assert_redirected_to post_path(posts(:closed))
+    assert_not_nil flash[:danger]
+    assert_equal before_history, after_history, 'PostHistory event incorrectly created on reopen'
+  end
+
+  test 'cannot reopen an open post' do
+    sign_in users(:closer)
+    before_history = PostHistory.where(post: posts(:question_one)).count
+    post :reopen, params: { id: posts(:question_one).id }
+    after_history = PostHistory.where(post: posts(:question_one)).count
+    assert_response 302
+    assert_redirected_to post_path(posts(:question_one))
+    assert_not_nil flash[:danger]
+    assert_equal before_history, after_history, 'PostHistory event incorrectly created on reopen'
+  end
 end