Merge branch '15877-accept-json-in-json'
[arvados.git] / services / api / test / functional / arvados / v1 / users_controller_test.rb
index b01597c05bf0280ea6cc6fa052ba98ff70526994..e3763c389e243a44b0bd891a9809713f481788a1 100644 (file)
@@ -17,7 +17,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
 
   test "activate a user after signing UA" do
     authorize_with :inactive_but_signed_user_agreement
-    post :activate, id: users(:inactive_but_signed_user_agreement).uuid
+    post :activate, params: {id: users(:inactive_but_signed_user_agreement).uuid}
     assert_response :success
     assert_not_nil assigns(:object)
     me = JSON.parse(@response.body)
@@ -49,7 +49,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     authorize_with :inactive
     assert_equal false, users(:inactive).is_active
 
-    post :activate, id: users(:inactive).uuid
+    post :activate, params: {id: users(:inactive).uuid}
     assert_response 403
 
     resp = json_response
@@ -59,7 +59,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
 
   test "activate an already-active user" do
     authorize_with :active
-    post :activate, id: users(:active).uuid
+    post :activate, params: {id: users(:active).uuid}
     assert_response :success
     me = JSON.parse(@response.body)
     assert_equal true, me['is_active']
@@ -73,10 +73,12 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
 
   test "create new user with user as input" do
     authorize_with :admin
-    post :create, user: {
-      first_name: "test_first_name",
-      last_name: "test_last_name",
-      email: "foo@example.com"
+    post :create, params: {
+      user: {
+        first_name: "test_first_name",
+        last_name: "test_last_name",
+        email: "foo@example.com"
+      }
     }
     assert_response :success
     created = JSON.parse(@response.body)
@@ -90,7 +92,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     authorize_with :admin
     repo_name = 'usertestrepo'
 
-    post :setup, {
+    post :setup, params: {
       repo_name: repo_name,
       openid_prefix: 'https://www.google.com/accounts/o8/id',
       user: {
@@ -111,11 +113,8 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_not_nil created['email'], 'expected non-nil email'
     assert_nil created['identity_url'], 'expected no identity_url'
 
-    # arvados#user, repo link and link add user to 'All users' group
-    verify_links_added 4
-
-    verify_link response_items, 'arvados#user', true, 'permission', 'can_login',
-        created['uuid'], created['email'], 'arvados#user', false, 'User'
+    # repo link and link add user to 'All users' group
+    verify_links_added 3
 
     verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
         "foo/#{repo_name}", created['uuid'], 'arvados#repository', true, 'Repository'
@@ -132,7 +131,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
   test "setup user with bogus uuid and expect error" do
     authorize_with :admin
 
-    post :setup, {
+    post :setup, params: {
       uuid: 'bogus_uuid',
       repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid
@@ -146,7 +145,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
   test "setup user with bogus uuid in user and expect error" do
     authorize_with :admin
 
-    post :setup, {
+    post :setup, params: {
       user: {uuid: 'bogus_uuid'},
       repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid,
@@ -162,7 +161,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
   test "setup user with no uuid and user, expect error" do
     authorize_with :admin
 
-    post :setup, {
+    post :setup, params: {
       repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid,
       openid_prefix: 'https://www.google.com/accounts/o8/id'
@@ -177,7 +176,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
   test "setup user with no uuid and email, expect error" do
     authorize_with :admin
 
-    post :setup, {
+    post :setup, params: {
       user: {},
       repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid,
@@ -194,7 +193,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     authorize_with :admin
     inactive_user = users(:inactive)
 
-    post :setup, {
+    post :setup, params: {
       uuid: users(:inactive).uuid,
       repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid
@@ -222,7 +221,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     authorize_with :admin
     inactive_user = users(:inactive)
 
-    post :setup, {
+    post :setup, params: {
       uuid: inactive_user['uuid'],
       user: {email: 'junk_email'}
     }
@@ -241,7 +240,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
   test "setup user with valid email and repo as input" do
     authorize_with :admin
 
-    post :setup, {
+    post :setup, params: {
       repo_name: 'usertestrepo',
       user: {email: 'foo@example.com'},
       openid_prefix: 'https://www.google.com/accounts/o8/id'
@@ -253,14 +252,14 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_not_nil response_object['uuid'], 'expected uuid for the new user'
     assert_equal response_object['email'], 'foo@example.com', 'expected given email'
 
-    # four extra links; system_group, login, group and repo perms
-    verify_links_added 4
+    # three extra links; system_group, group and repo perms
+    verify_links_added 3
   end
 
   test "setup user with fake vm and expect error" do
     authorize_with :admin
 
-    post :setup, {
+    post :setup, params: {
       repo_name: 'usertestrepo',
       vm_uuid: 'no_such_vm',
       user: {email: 'foo@example.com'},
@@ -277,7 +276,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
   test "setup user with valid email, repo and real vm as input" do
     authorize_with :admin
 
-    post :setup, {
+    post :setup, params: {
       repo_name: 'usertestrepo',
       openid_prefix: 'https://www.google.com/accounts/o8/id',
       vm_uuid: @vm_uuid,
@@ -290,14 +289,14 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_not_nil response_object['uuid'], 'expected uuid for the new user'
     assert_equal response_object['email'], 'foo@example.com', 'expected given email'
 
-    # five extra links; system_group, login, group, vm, repo
-    verify_links_added 5
+    # four extra links; system_group, group, vm, repo
+    verify_links_added 4
   end
 
   test "setup user with valid email, no vm and no repo as input" do
     authorize_with :admin
 
-    post :setup, {
+    post :setup, params: {
       user: {email: 'foo@example.com'},
       openid_prefix: 'https://www.google.com/accounts/o8/id'
     }
@@ -308,11 +307,8 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_not_nil response_object['uuid'], 'expected uuid for new user'
     assert_equal response_object['email'], 'foo@example.com', 'expected given email'
 
-    # three extra links; system_group, login, and group
-    verify_links_added 3
-
-    verify_link response_items, 'arvados#user', true, 'permission', 'can_login',
-        response_object['uuid'], response_object['email'], 'arvados#user', false, 'User'
+    # two extra links; system_group, and group
+    verify_links_added 2
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_read',
         'All users', response_object['uuid'], 'arvados#group', true, 'Group'
@@ -327,7 +323,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
   test "setup user with email, first name, repo name and vm uuid" do
     authorize_with :admin
 
-    post :setup, {
+    post :setup, params: {
       openid_prefix: 'https://www.google.com/accounts/o8/id',
       repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid,
@@ -345,15 +341,15 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_equal 'test_first_name', response_object['first_name'],
         'expecting first name'
 
-    # five extra links; system_group, login, group, repo and vm
-    verify_links_added 5
+    # four extra links; system_group, group, repo and vm
+    verify_links_added 4
   end
 
   test "setup user with an existing user email and check different object is created" do
     authorize_with :admin
     inactive_user = users(:inactive)
 
-    post :setup, {
+    post :setup, params: {
       openid_prefix: 'https://www.google.com/accounts/o8/id',
       repo_name: 'usertestrepo',
       user: {
@@ -368,14 +364,14 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_not_equal response_object['uuid'], inactive_user['uuid'],
         'expected different uuid after create operation'
     assert_equal inactive_user['email'], response_object['email'], 'expected given email'
-    # system_group, openid, group, and repo. No vm link.
-    verify_links_added 4
+    # system_group, group, and repo. No vm link.
+    verify_links_added 3
   end
 
   test "setup user with openid prefix" do
     authorize_with :admin
 
-    post :setup, {
+    post :setup, params: {
       repo_name: 'usertestrepo',
       openid_prefix: 'http://www.example.com/account',
       user: {
@@ -396,11 +392,8 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_nil created['identity_url'], 'expected no identity_url'
 
     # verify links
-    # four new links: system_group, arvados#user, repo, and 'All users' group.
-    verify_links_added 4
-
-    verify_link response_items, 'arvados#user', true, 'permission', 'can_login',
-        created['uuid'], created['email'], 'arvados#user', false, 'User'
+    # three new links: system_group, repo, and 'All users' group.
+    verify_links_added 3
 
     verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
         'foo/usertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
@@ -415,7 +408,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
   test "invoke setup with no openid prefix, expect error" do
     authorize_with :admin
 
-    post :setup, {
+    post :setup, params: {
       repo_name: 'usertestrepo',
       user: {
         first_name: "in_create_test_first_name",
@@ -434,7 +427,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
   test "setup user with user, vm and repo and verify links" do
     authorize_with :admin
 
-    post :setup, {
+    post :setup, params: {
       user: {
         first_name: "in_create_test_first_name",
         last_name: "test_last_name",
@@ -455,12 +448,10 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_not_nil created['email'], 'expected non-nil email'
     assert_nil created['identity_url'], 'expected no identity_url'
 
-    # five new links: system_group, arvados#user, repo, vm and 'All
-    # users' group link
-    verify_links_added 5
+    # four new links: system_group, repo, vm and 'All users' group link
+    verify_links_added 4
 
-    verify_link response_items, 'arvados#user', true, 'permission', 'can_login',
-        created['uuid'], created['email'], 'arvados#user', false, 'User'
+    # system_group isn't part of the response.  See User#add_system_group_permission_link
 
     verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
         'foo/usertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
@@ -475,7 +466,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
   test "create user as non admin user and expect error" do
     authorize_with :active
 
-    post :create, {
+    post :create, params: {
       user: {email: 'foo@example.com'}
     }
 
@@ -489,7 +480,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
   test "setup user as non admin user and expect error" do
     authorize_with :active
 
-    post :setup, {
+    post :setup, params: {
       openid_prefix: 'https://www.google.com/accounts/o8/id',
       user: {email: 'foo@example.com'}
     }
@@ -506,7 +497,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     active_user = users(:active)
 
     # invoke setup with a repository
-    post :setup, {
+    post :setup, params: {
       repo_name: 'usertestrepo',
       uuid: active_user['uuid']
     }
@@ -539,7 +530,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     repo_link_count = repo_link_query.count
 
     # invoke setup with a repository
-    post :setup, {
+    post :setup, params: {
       vm_uuid: @vm_uuid,
       uuid: active_user['uuid'],
       email: 'junk_email'
@@ -574,7 +565,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     authorize_with :admin
 
     # now unsetup this user
-    post :unsetup, id: active_user['uuid']
+    post :unsetup, params: {id: active_user['uuid']}
     assert_response :success
 
     response_user = JSON.parse(@response.body)
@@ -598,7 +589,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
   test "setup user with send notification param false and verify no email" do
     authorize_with :admin
 
-    post :setup, {
+    post :setup, params: {
       openid_prefix: 'http://www.example.com/account',
       send_notification_email: 'false',
       user: {
@@ -619,7 +610,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
   test "setup user with send notification param true and verify email" do
     authorize_with :admin
 
-    post :setup, {
+    post :setup, params: {
       openid_prefix: 'http://www.example.com/account',
       send_notification_email: 'true',
       user: {
@@ -636,12 +627,12 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     setup_email = ActionMailer::Base.deliveries.last
     assert_not_nil setup_email, 'Expected email after setup'
 
-    assert_equal Rails.configuration.user_notifier_email_from, setup_email.from[0]
+    assert_equal Rails.configuration.Users.UserNotifierEmailFrom, setup_email.from[0]
     assert_equal 'foo@example.com', setup_email.to[0]
-    assert_equal 'Welcome to Curoverse - shell account enabled', setup_email.subject
+    assert_equal 'Welcome to Arvados - shell account enabled', setup_email.subject
     assert (setup_email.body.to_s.include? 'Your Arvados shell account has been set up'),
         'Expected Your Arvados shell account has been set up in email body'
-    assert (setup_email.body.to_s.include? "#{Rails.configuration.workbench_address}users/#{created['uuid']}/virtual_machines"), 'Expected virtual machines url in email body'
+    assert (setup_email.body.to_s.include? "#{Rails.configuration.Services.Workbench1.ExternalURL}users/#{created['uuid']}/virtual_machines"), 'Expected virtual machines url in email body'
   end
 
   test "setup inactive user by changing is_active to true" do
@@ -649,7 +640,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     active_user = users(:active)
 
     # invoke setup with a repository
-    put :update, {
+    put :update, params: {
           id: active_user['uuid'],
           user: {
             is_active: true,
@@ -676,7 +667,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     users = create_list :active_user, 2, join_groups: [g]
     token = create :token, user: users[0]
     authorize_with_token token
-    get :show, id: users[1].uuid
+    get :show, params: {id: users[1].uuid}
     check_non_admin_show
   end
 
@@ -689,7 +680,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
       token = create :token, user: users[0]
 
       authorize_with_token token
-      get(:index, limit: limit)
+      get(:index, params: {limit: limit})
       check_non_admin_index
       assert_equal(limit, json_response["items"].size,
                    "non-admin index limit was ineffective")
@@ -708,14 +699,14 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
 
   test "admin can filter on user.is_active" do
     authorize_with :admin
-    get(:index, filters: [["is_active", "=", "true"]])
+    get(:index, params: {filters: [["is_active", "=", "true"]]})
     assert_response :success
     check_readable_users_index [:active, :spectator], [:inactive]
   end
 
   test "admin can search where user.is_active" do
     authorize_with :admin
-    get(:index, where: {is_active: true})
+    get(:index, params: {where: {is_active: true}})
     assert_response :success
     check_readable_users_index [:active, :spectator], [:inactive]
   end
@@ -723,7 +714,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
   test "update active_no_prefs user profile and expect notification email" do
     authorize_with :admin
 
-    put :update, {
+    put :update, params: {
       id: users(:active_no_prefs).uuid,
       user: {
         prefs: {:profile => {'organization' => 'example.com'}}
@@ -747,7 +738,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     user = {}
     user[:prefs] = users(:active_no_prefs_profile_no_getting_started_shown).prefs
     user[:prefs][:profile] = {:profile => {'organization' => 'example.com'}}
-    put :update, {
+    put :update, params: {
       id: users(:active_no_prefs_profile_no_getting_started_shown).uuid,
       user: user
     }
@@ -766,7 +757,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
   test "update active user profile and expect no notification email" do
     authorize_with :admin
 
-    put :update, {
+    put :update, params: {
       id: users(:active).uuid,
       user: {
         prefs: {:profile => {'organization' => 'anotherexample.com'}}
@@ -801,7 +792,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     test "update_uuid as #{auth_user}" do
       authorize_with auth_user
       orig_uuid = users(:active).uuid
-      post :update_uuid, {
+      post :update_uuid, params: {
              id: orig_uuid,
              new_uuid: 'zbbbb-tpzed-abcde12345abcde',
            }
@@ -815,19 +806,51 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     end
   end
 
-  test "refuse to merge with redirect_to_user_uuid=false (not yet supported)" do
+  test "merge with redirect_to_user_uuid=false" do
     authorize_with :project_viewer_trustedclient
-    post :merge, {
+    tok = api_client_authorizations(:project_viewer).api_token
+    post :merge, params: {
            new_user_token: api_client_authorizations(:active_trustedclient).api_token,
            new_owner_uuid: users(:active).uuid,
            redirect_to_new_user: false,
          }
-    assert_response(422)
+    assert_response(:success)
+    assert_nil(User.unscoped.find_by_uuid(users(:project_viewer).uuid).redirect_to_user_uuid)
+
+    # because redirect_to_new_user=false, token owned by
+    # project_viewer should be deleted
+    auth = ApiClientAuthorization.validate(token: tok)
+    assert_nil(auth)
+  end
+
+  test "merge remote to local as admin" do
+    authorize_with :admin
+
+    remoteuser = User.create!(uuid: "zbbbb-tpzed-remotremotremot")
+    tok = ApiClientAuthorization.create!(user: remoteuser, api_client: api_clients(:untrusted)).api_token
+
+    auth = ApiClientAuthorization.validate(token: tok)
+    assert_not_nil(auth)
+    assert_nil(remoteuser.redirect_to_user_uuid)
+
+    post :merge, params: {
+           new_user_uuid: users(:active).uuid,
+           old_user_uuid: remoteuser.uuid,
+           new_owner_uuid: users(:active).uuid,
+           redirect_to_new_user: true,
+         }
+    assert_response(:success)
+    remoteuser.reload
+    assert_equal(users(:active).uuid, remoteuser.redirect_to_user_uuid)
+
+    # token owned by remoteuser should be deleted
+    auth = ApiClientAuthorization.validate(token: tok)
+    assert_nil(auth)
   end
 
   test "refuse to merge user into self" do
     authorize_with(:active_trustedclient)
-    post(:merge, {
+    post(:merge, params: {
            new_user_token: api_client_authorizations(:active_trustedclient).api_token,
            new_owner_uuid: users(:active).uuid,
            redirect_to_new_user: true,
@@ -839,7 +862,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
    [:active_trustedclient, :project_viewer]].each do |src, dst|
     test "refuse to merge with untrusted token (#{src} -> #{dst})" do
       authorize_with(src)
-      post(:merge, {
+      post(:merge, params: {
              new_user_token: api_client_authorizations(dst).api_token,
              new_owner_uuid: api_client_authorizations(dst).user.uuid,
              redirect_to_new_user: true,
@@ -852,7 +875,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
    [:project_viewer_trustedclient, :expired_trustedclient]].each do |src, dst|
     test "refuse to merge with expired token (#{src} -> #{dst})" do
       authorize_with(src)
-      post(:merge, {
+      post(:merge, params: {
              new_user_token: api_client_authorizations(dst).api_token,
              new_owner_uuid: api_client_authorizations(dst).user.uuid,
              redirect_to_new_user: true,
@@ -868,7 +891,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
         api_client_authorizations(auth).update_attributes(scopes: ["GET /", "POST /", "PUT /"])
       end
       authorize_with(:active_trustedclient)
-      post(:merge, {
+      post(:merge, params: {
              new_user_token: api_client_authorizations(:project_viewer_trustedclient).api_token,
              new_owner_uuid: users(:project_viewer).uuid,
              redirect_to_new_user: true,
@@ -879,7 +902,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
 
   test "refuse to merge if new_owner_uuid is not writable" do
     authorize_with(:project_viewer_trustedclient)
-    post(:merge, {
+    post(:merge, params: {
            new_user_token: api_client_authorizations(:active_trustedclient).api_token,
            new_owner_uuid: groups(:anonymously_accessible_project).uuid,
            redirect_to_new_user: true,
@@ -889,7 +912,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
 
   test "refuse to merge if new_owner_uuid is empty" do
     authorize_with(:project_viewer_trustedclient)
-    post(:merge, {
+    post(:merge, params: {
            new_user_token: api_client_authorizations(:active_trustedclient).api_token,
            new_owner_uuid: "",
            redirect_to_new_user: true,
@@ -899,7 +922,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
 
   test "refuse to merge if new_owner_uuid is not provided" do
     authorize_with(:project_viewer_trustedclient)
-    post(:merge, {
+    post(:merge, params: {
            new_user_token: api_client_authorizations(:active_trustedclient).api_token,
            redirect_to_new_user: true,
          })
@@ -908,7 +931,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
 
   test "refuse to update redirect_to_user_uuid directly" do
     authorize_with(:active_trustedclient)
-    patch(:update, {
+    patch(:update, params: {
             id: users(:active).uuid,
             user: {
               redirect_to_user_uuid: users(:active).uuid,
@@ -919,13 +942,13 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
 
   test "merge 'project_viewer' account into 'active' account" do
     authorize_with(:project_viewer_trustedclient)
-    post(:merge, {
+    post(:merge, params: {
            new_user_token: api_client_authorizations(:active_trustedclient).api_token,
            new_owner_uuid: users(:active).uuid,
            redirect_to_new_user: true,
          })
     assert_response(:success)
-    assert_equal(users(:project_viewer).redirect_to_user_uuid, users(:active).uuid)
+    assert_equal(users(:active).uuid, User.unscoped.find_by_uuid(users(:project_viewer).uuid).redirect_to_user_uuid)
 
     auth = ApiClientAuthorization.validate(token: api_client_authorizations(:project_viewer).api_token)
     assert_not_nil(auth)
@@ -933,6 +956,123 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_equal(users(:active).uuid, auth.user.uuid)
   end
 
+
+  test "merge 'project_viewer' account into 'active' account using uuids" do
+    authorize_with(:admin)
+    post(:merge, params: {
+           old_user_uuid: users(:project_viewer).uuid,
+           new_user_uuid: users(:active).uuid,
+           new_owner_uuid: users(:active).uuid,
+           redirect_to_new_user: true,
+         })
+    assert_response(:success)
+    assert_equal(users(:active).uuid, User.unscoped.find_by_uuid(users(:project_viewer).uuid).redirect_to_user_uuid)
+
+    auth = ApiClientAuthorization.validate(token: api_client_authorizations(:project_viewer).api_token)
+    assert_not_nil(auth)
+    assert_not_nil(auth.user)
+    assert_equal(users(:active).uuid, auth.user.uuid)
+  end
+
+  test "merge 'project_viewer' account into 'active' account using uuids denied for non-admin" do
+    authorize_with(:active)
+    post(:merge, params: {
+           old_user_uuid: users(:project_viewer).uuid,
+           new_user_uuid: users(:active).uuid,
+           new_owner_uuid: users(:active).uuid,
+           redirect_to_new_user: true,
+         })
+    assert_response(403)
+    assert_nil(users(:project_viewer).redirect_to_user_uuid)
+  end
+
+  test "merge 'project_viewer' account into 'active' account using uuids denied missing old_user_uuid" do
+    authorize_with(:admin)
+    post(:merge, params: {
+           new_user_uuid: users(:active).uuid,
+           new_owner_uuid: users(:active).uuid,
+           redirect_to_new_user: true,
+         })
+    assert_response(422)
+    assert_nil(users(:project_viewer).redirect_to_user_uuid)
+  end
+
+  test "merge 'project_viewer' account into 'active' account using uuids denied missing new_user_uuid" do
+    authorize_with(:admin)
+    post(:merge, params: {
+           old_user_uuid: users(:project_viewer).uuid,
+           new_owner_uuid: users(:active).uuid,
+           redirect_to_new_user: true,
+         })
+    assert_response(422)
+    assert_nil(users(:project_viewer).redirect_to_user_uuid)
+  end
+
+  test "merge 'project_viewer' account into 'active' account using uuids denied bogus old_user_uuid" do
+    authorize_with(:admin)
+    post(:merge, params: {
+           old_user_uuid: "zzzzz-tpzed-bogusbogusbogus",
+           new_user_uuid: users(:active).uuid,
+           new_owner_uuid: users(:active).uuid,
+           redirect_to_new_user: true,
+         })
+    assert_response(422)
+    assert_nil(users(:project_viewer).redirect_to_user_uuid)
+  end
+
+  test "merge 'project_viewer' account into 'active' account using uuids denied bogus new_user_uuid" do
+    authorize_with(:admin)
+    post(:merge, params: {
+           old_user_uuid: users(:project_viewer).uuid,
+           new_user_uuid: "zzzzz-tpzed-bogusbogusbogus",
+           new_owner_uuid: users(:active).uuid,
+           redirect_to_new_user: true,
+         })
+    assert_response(422)
+    assert_nil(users(:project_viewer).redirect_to_user_uuid)
+  end
+
+  test "batch update fails for non-admin" do
+    authorize_with(:active)
+    patch(:batch_update, params: {updates: {}})
+    assert_response(403)
+  end
+
+  test "batch update" do
+    existinguuid = 'remot-tpzed-foobarbazwazqux'
+    newuuid = 'remot-tpzed-newnarnazwazqux'
+    act_as_system_user do
+      User.create!(uuid: existinguuid, email: 'root@existing.example.com')
+    end
+
+    authorize_with(:admin)
+    patch(:batch_update,
+          params: {
+            updates: {
+              existinguuid => {
+                'first_name' => 'root',
+                'email' => 'root@remot.example.com',
+                'is_active' => true,
+                'is_admin' => true,
+                'prefs' => {'foo' => 'bar'},
+              },
+              newuuid => {
+                'first_name' => 'noot',
+                'email' => 'root@remot.example.com',
+              },
+            }})
+    assert_response(:success)
+
+    assert_equal('root', User.find_by_uuid(existinguuid).first_name)
+    assert_equal('root@remot.example.com', User.find_by_uuid(existinguuid).email)
+    assert_equal(true, User.find_by_uuid(existinguuid).is_active)
+    assert_equal(true, User.find_by_uuid(existinguuid).is_admin)
+    assert_equal({'foo' => 'bar'}, User.find_by_uuid(existinguuid).prefs)
+
+    assert_equal('noot', User.find_by_uuid(newuuid).first_name)
+    assert_equal('root@remot.example.com', User.find_by_uuid(newuuid).email)
+  end
+
   NON_ADMIN_USER_DATA = ["uuid", "kind", "is_active", "email", "first_name",
                          "last_name", "username"].sort
 
@@ -972,7 +1112,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
 
   def check_inactive_user_findable(params={})
     inactive_user = users(:inactive)
-    get(:index, params.merge(filters: [["email", "=", inactive_user.email]]))
+    get(:index, params: params.merge(filters: [["email", "=", inactive_user.email]]))
     assert_response :success
     user_list = json_response["items"]
     assert_equal(1, user_list.andand.count)