19146: Add can_write/can_manage to users#list, fix select=can_*.
[arvados.git] / services / api / test / functional / arvados / v1 / users_controller_test.rb
index 0501da1673ebdff87c5c9900b205dfdde96dce42..6a7b00a00573f9413a61856d66fd6e6eb3900c65 100644 (file)
@@ -13,6 +13,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     @initial_link_count = Link.count
     @vm_uuid = virtual_machines(:testvm).uuid
     ActionMailer::Base.deliveries = []
     @initial_link_count = Link.count
     @vm_uuid = virtual_machines(:testvm).uuid
     ActionMailer::Base.deliveries = []
+    Rails.configuration.Users.ActivatedUsersAreVisibleToOthers = false
   end
 
   test "activate a user after signing UA" do
   end
 
   test "activate a user after signing UA" do
@@ -88,13 +89,44 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_nil created['identity_url'], 'expected no identity_url'
   end
 
     assert_nil created['identity_url'], 'expected no identity_url'
   end
 
+  test "create new user with empty username" do
+    authorize_with :admin
+    post :create, params: {
+      user: {
+        first_name: "test_first_name",
+        last_name: "test_last_name",
+        username: ""
+      }
+    }
+    assert_response :success
+    created = JSON.parse(@response.body)
+    assert_equal 'test_first_name', created['first_name']
+    assert_not_nil created['uuid'], 'expected uuid for the newly created user'
+    assert_nil created['email'], 'expected no email'
+    assert_nil created['username'], 'expected no username'
+  end
+
+  test "update user with empty username" do
+    authorize_with :admin
+    user = users('spectator')
+    assert_not_nil user['username']
+    put :update, params: {
+      id: users('spectator')['uuid'],
+      user: {
+        username: ""
+      }
+    }
+    assert_response :success
+    updated = JSON.parse(@response.body)
+    assert_nil updated['username'], 'expected no username'
+  end
+
   test "create user with user, vm and repo as input" do
     authorize_with :admin
     repo_name = 'usertestrepo'
 
     post :setup, params: {
       repo_name: repo_name,
   test "create user with user, vm and repo as input" do
     authorize_with :admin
     repo_name = 'usertestrepo'
 
     post :setup, params: {
       repo_name: repo_name,
-      openid_prefix: 'https://www.google.com/accounts/o8/id',
       user: {
         uuid: 'zzzzz-tpzed-abcdefghijklmno',
         first_name: "in_create_test_first_name",
       user: {
         uuid: 'zzzzz-tpzed-abcdefghijklmno',
         first_name: "in_create_test_first_name",
@@ -113,11 +145,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'
 
     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'
 
     verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
         "foo/#{repo_name}", created['uuid'], 'arvados#repository', true, 'Repository'
@@ -152,7 +181,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
       user: {uuid: 'bogus_uuid'},
       repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid,
       user: {uuid: 'bogus_uuid'},
       repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid,
-      openid_prefix: 'https://www.google.com/accounts/o8/id'
     }
     response_body = JSON.parse(@response.body)
     response_errors = response_body['errors']
     }
     response_body = JSON.parse(@response.body)
     response_errors = response_body['errors']
@@ -167,7 +195,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     post :setup, params: {
       repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid,
     post :setup, params: {
       repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid,
-      openid_prefix: 'https://www.google.com/accounts/o8/id'
     }
     response_body = JSON.parse(@response.body)
     response_errors = response_body['errors']
     }
     response_body = JSON.parse(@response.body)
     response_errors = response_body['errors']
@@ -183,7 +210,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
       user: {},
       repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid,
       user: {},
       repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid,
-      openid_prefix: 'https://www.google.com/accounts/o8/id'
     }
     response_body = JSON.parse(@response.body)
     response_errors = response_body['errors']
     }
     response_body = JSON.parse(@response.body)
     response_errors = response_body['errors']
@@ -246,7 +272,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     post :setup, params: {
       repo_name: 'usertestrepo',
       user: {email: 'foo@example.com'},
     post :setup, params: {
       repo_name: 'usertestrepo',
       user: {email: 'foo@example.com'},
-      openid_prefix: 'https://www.google.com/accounts/o8/id'
     }
 
     assert_response :success
     }
 
     assert_response :success
@@ -255,8 +280,8 @@ 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'
 
     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
   end
 
   test "setup user with fake vm and expect error" do
@@ -266,7 +291,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
       repo_name: 'usertestrepo',
       vm_uuid: 'no_such_vm',
       user: {email: 'foo@example.com'},
       repo_name: 'usertestrepo',
       vm_uuid: 'no_such_vm',
       user: {email: 'foo@example.com'},
-      openid_prefix: 'https://www.google.com/accounts/o8/id'
     }
 
     response_body = JSON.parse(@response.body)
     }
 
     response_body = JSON.parse(@response.body)
@@ -281,7 +305,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
 
     post :setup, params: {
       repo_name: 'usertestrepo',
 
     post :setup, params: {
       repo_name: 'usertestrepo',
-      openid_prefix: 'https://www.google.com/accounts/o8/id',
       vm_uuid: @vm_uuid,
       user: {email: 'foo@example.com'}
     }
       vm_uuid: @vm_uuid,
       user: {email: 'foo@example.com'}
     }
@@ -292,8 +315,8 @@ 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'
 
     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
   end
 
   test "setup user with valid email, no vm and no repo as input" do
@@ -301,7 +324,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
 
     post :setup, params: {
       user: {email: 'foo@example.com'},
 
     post :setup, params: {
       user: {email: 'foo@example.com'},
-      openid_prefix: 'https://www.google.com/accounts/o8/id'
     }
 
     assert_response :success
     }
 
     assert_response :success
@@ -310,11 +332,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'
 
     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'
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_read',
         'All users', response_object['uuid'], 'arvados#group', true, 'Group'
@@ -330,7 +349,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     authorize_with :admin
 
     post :setup, params: {
     authorize_with :admin
 
     post :setup, params: {
-      openid_prefix: 'https://www.google.com/accounts/o8/id',
       repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid,
       user: {
       repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid,
       user: {
@@ -347,8 +365,8 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_equal 'test_first_name', response_object['first_name'],
         'expecting first name'
 
     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
   end
 
   test "setup user with an existing user email and check different object is created" do
@@ -356,7 +374,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     inactive_user = users(:inactive)
 
     post :setup, params: {
     inactive_user = users(:inactive)
 
     post :setup, params: {
-      openid_prefix: 'https://www.google.com/accounts/o8/id',
       repo_name: 'usertestrepo',
       user: {
         email: inactive_user['email']
       repo_name: 'usertestrepo',
       user: {
         email: inactive_user['email']
@@ -370,8 +387,8 @@ 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'
     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
   end
 
   test "setup user with openid prefix" do
@@ -379,7 +396,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
 
     post :setup, params: {
       repo_name: 'usertestrepo',
 
     post :setup, params: {
       repo_name: 'usertestrepo',
-      openid_prefix: 'http://www.example.com/account',
       user: {
         first_name: "in_create_test_first_name",
         last_name: "test_last_name",
       user: {
         first_name: "in_create_test_first_name",
         last_name: "test_last_name",
@@ -398,11 +414,8 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_nil created['identity_url'], 'expected no identity_url'
 
     # verify links
     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'
 
     verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
         'foo/usertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
@@ -414,25 +427,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
         nil, created['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
   end
 
         nil, created['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
   end
 
-  test "invoke setup with no openid prefix, expect error" do
-    authorize_with :admin
-
-    post :setup, params: {
-      repo_name: 'usertestrepo',
-      user: {
-        first_name: "in_create_test_first_name",
-        last_name: "test_last_name",
-        email: "foo@example.com"
-      }
-    }
-
-    response_body = JSON.parse(@response.body)
-    response_errors = response_body['errors']
-    assert_not_nil response_errors, 'Expected error in response'
-    assert (response_errors.first.include? 'openid_prefix parameter is missing'),
-        'Expected ArgumentError'
-  end
-
   test "setup user with user, vm and repo and verify links" do
     authorize_with :admin
 
   test "setup user with user, vm and repo and verify links" do
     authorize_with :admin
 
@@ -444,7 +438,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
       },
       vm_uuid: @vm_uuid,
       repo_name: 'usertestrepo',
       },
       vm_uuid: @vm_uuid,
       repo_name: 'usertestrepo',
-      openid_prefix: 'https://www.google.com/accounts/o8/id'
     }
 
     assert_response :success
     }
 
     assert_response :success
@@ -457,12 +450,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'
 
     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'
 
     verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
         'foo/usertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
@@ -492,7 +483,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     authorize_with :active
 
     post :setup, params: {
     authorize_with :active
 
     post :setup, params: {
-      openid_prefix: 'https://www.google.com/accounts/o8/id',
       user: {email: 'foo@example.com'}
     }
 
       user: {email: 'foo@example.com'}
     }
 
@@ -601,7 +591,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     authorize_with :admin
 
     post :setup, params: {
     authorize_with :admin
 
     post :setup, params: {
-      openid_prefix: 'http://www.example.com/account',
       send_notification_email: 'false',
       user: {
         email: "foo@example.com"
       send_notification_email: 'false',
       user: {
         email: "foo@example.com"
@@ -621,8 +610,24 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
   test "setup user with send notification param true and verify email" do
     authorize_with :admin
 
   test "setup user with send notification param true and verify email" do
     authorize_with :admin
 
+    Rails.configuration.Users.UserSetupMailText = %{
+<% if not @user.full_name.empty? -%>
+<%= @user.full_name %>,
+<% else -%>
+Hi there,
+<% end -%>
+
+Your Arvados shell account has been set up. Please visit the virtual machines page <% if Rails.configuration.Services.Workbench1.ExternalURL %>at
+
+<%= Rails.configuration.Services.Workbench1.ExternalURL %><%= "/" if !Rails.configuration.Services.Workbench1.ExternalURL.to_s.end_with?("/") %>users/<%= @user.uuid%>/virtual_machines <% else %><% end %>
+
+for connection instructions.
+
+Thanks,
+The Arvados team.
+}
+
     post :setup, params: {
     post :setup, params: {
-      openid_prefix: 'http://www.example.com/account',
       send_notification_email: 'true',
       user: {
         email: "foo@example.com"
       send_notification_email: 'true',
       user: {
         email: "foo@example.com"
@@ -640,7 +645,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
 
     assert_equal Rails.configuration.Users.UserNotifierEmailFrom, setup_email.from[0]
     assert_equal 'foo@example.com', setup_email.to[0]
 
     assert_equal Rails.configuration.Users.UserNotifierEmailFrom, setup_email.from[0]
     assert_equal 'foo@example.com', setup_email.to[0]
-    assert_equal 'Welcome to Arvados - shell account enabled', setup_email.subject
+    assert_equal 'Welcome to Arvados - 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.Services.Workbench1.ExternalURL}users/#{created['uuid']}/virtual_machines"), 'Expected virtual machines url in email body'
     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.Services.Workbench1.ExternalURL}users/#{created['uuid']}/virtual_machines"), 'Expected virtual machines url in email body'
@@ -669,11 +674,17 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     get(:index)
     check_non_admin_index
     check_readable_users_index [:spectator], [:inactive, :active]
     get(:index)
     check_non_admin_index
     check_readable_users_index [:spectator], [:inactive, :active]
+    json_response["items"].each do |u|
+      if u["uuid"] == users(:spectator).uuid
+        assert_equal true, u["can_write"]
+        assert_equal true, u["can_manage"]
+      end
+    end
   end
 
   test "non-admin user gets only safe attributes from users#show" do
     g = act_as_system_user do
   end
 
   test "non-admin user gets only safe attributes from users#show" do
     g = act_as_system_user do
-      create :group
+      create :group, group_class: "role"
     end
     users = create_list :active_user, 2, join_groups: [g]
     token = create :token, user: users[0]
     end
     users = create_list :active_user, 2, join_groups: [g]
     token = create :token, user: users[0]
@@ -685,7 +696,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
   [2, 4].each do |limit|
     test "non-admin user can limit index to #{limit}" do
       g = act_as_system_user do
   [2, 4].each do |limit|
     test "non-admin user can limit index to #{limit}" do
       g = act_as_system_user do
-        create :group
+        create :group, group_class: "role"
       end
       users = create_list :active_user, 4, join_groups: [g]
       token = create :token, user: users[0]
       end
       users = create_list :active_user, 4, join_groups: [g]
       token = create :token, user: users[0]
@@ -796,35 +807,46 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
                     "user's writable_by should include its owner_uuid")
   end
 
                     "user's writable_by should include its owner_uuid")
   end
 
-  [
-    [:admin, true],
-    [:active, false],
-  ].each do |auth_user, expect_success|
-    test "update_uuid as #{auth_user}" do
-      authorize_with auth_user
-      orig_uuid = users(:active).uuid
-      post :update_uuid, params: {
-             id: orig_uuid,
-             new_uuid: 'zbbbb-tpzed-abcde12345abcde',
-           }
-      if expect_success
-        assert_response :success
-        assert_empty User.where(uuid: orig_uuid)
-      else
-        assert_response 403
-        assert_not_empty User.where(uuid: orig_uuid)
-      end
-    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
     authorize_with :project_viewer_trustedclient
+    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,
          }
     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
   end
 
   test "refuse to merge user into self" do
@@ -927,7 +949,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
            redirect_to_new_user: true,
          })
     assert_response(:success)
            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)
 
     auth = ApiClientAuthorization.validate(token: api_client_authorizations(:project_viewer).api_token)
     assert_not_nil(auth)
@@ -935,8 +957,135 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_equal(users(:active).uuid, auth.user.uuid)
   end
 
     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'
+    unchanginguuid = 'remot-tpzed-nochangingattrs'
+    act_as_system_user do
+      User.create!(uuid: existinguuid, email: 'root@existing.example.com')
+      User.create!(uuid: unchanginguuid, email: 'root@unchanging.example.com', prefs: {'foo' => {'bar' => 'baz'}})
+    end
+    assert_equal(1, Log.where(object_uuid: unchanginguuid).count)
+
+    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',
+                'username' => '',
+              },
+              unchanginguuid => {
+                'email' => 'root@unchanging.example.com',
+                'prefs' => {'foo' => {'bar' => 'baz'}},
+              },
+            }})
+    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)
+
+    assert_equal(1, Log.where(object_uuid: unchanginguuid).count)
+  end
+
   NON_ADMIN_USER_DATA = ["uuid", "kind", "is_active", "email", "first_name",
   NON_ADMIN_USER_DATA = ["uuid", "kind", "is_active", "email", "first_name",
-                         "last_name", "username"].sort
+                         "last_name", "username", "can_write", "can_manage"].sort
 
   def check_non_admin_index
     assert_response :success
 
   def check_non_admin_index
     assert_response :success