closes #6057
[arvados.git] / apps / workbench / test / controllers / projects_controller_test.rb
1 require 'test_helper'
2 require 'helpers/share_object_helper'
3
4 class ProjectsControllerTest < ActionController::TestCase
5   include ShareObjectHelper
6
7   test "invited user is asked to sign user agreements on front page" do
8     get :index, {}, session_for(:inactive)
9     assert_response :redirect
10     assert_match(/^#{Regexp.escape(user_agreements_url)}\b/,
11                  @response.redirect_url,
12                  "Inactive user was not redirected to user_agreements page")
13   end
14
15   test "uninvited user is asked to wait for activation" do
16     get :index, {}, session_for(:inactive_uninvited)
17     assert_response :redirect
18     assert_match(/^#{Regexp.escape(inactive_users_url)}\b/,
19                  @response.redirect_url,
20                  "Uninvited user was not redirected to inactive user page")
21   end
22
23   [[:active, true],
24    [:project_viewer, false]].each do |which_user, should_show|
25     test "create subproject button #{'not ' unless should_show} shown to #{which_user}" do
26       readonly_project_uuid = api_fixture('groups')['aproject']['uuid']
27       get :show, {
28         id: readonly_project_uuid
29       }, session_for(which_user)
30       buttons = css_select('[data-method=post]').select do |el|
31         el.attributes['data-remote-href'].match /project.*owner_uuid.*#{readonly_project_uuid}/
32       end
33       if should_show
34         assert_not_empty(buttons, "did not offer to create a subproject")
35       else
36         assert_empty(buttons.collect(&:to_s),
37                      "offered to create a subproject in a non-writable project")
38       end
39     end
40   end
41
42   test "sharing a project with a user and group" do
43     uuid_list = [api_fixture("groups")["future_project_viewing_group"]["uuid"],
44                  api_fixture("users")["future_project_user"]["uuid"]]
45     post(:share_with, {
46            id: api_fixture("groups")["asubproject"]["uuid"],
47            uuids: uuid_list,
48            format: "json"},
49          session_for(:active))
50     assert_response :success
51     assert_equal(uuid_list, json_response["success"])
52   end
53
54   test "user with project read permission can't add permissions" do
55     share_uuid = api_fixture("users")["spectator"]["uuid"]
56     post(:share_with, {
57            id: api_fixture("groups")["aproject"]["uuid"],
58            uuids: [share_uuid],
59            format: "json"},
60          session_for(:project_viewer))
61     assert_response 422
62     assert(json_response["errors"].andand.
63              any? { |msg| msg.start_with?("#{share_uuid}: ") },
64            "JSON response missing properly formatted sharing error")
65   end
66
67   test "admin can_manage aproject" do
68     assert user_can_manage(:admin, api_fixture("groups")["aproject"])
69   end
70
71   test "owner can_manage aproject" do
72     assert user_can_manage(:active, api_fixture("groups")["aproject"])
73   end
74
75   test "owner can_manage asubproject" do
76     assert user_can_manage(:active, api_fixture("groups")["asubproject"])
77   end
78
79   test "viewer can't manage aproject" do
80     refute user_can_manage(:project_viewer, api_fixture("groups")["aproject"])
81   end
82
83   test "viewer can't manage asubproject" do
84     refute user_can_manage(:project_viewer, api_fixture("groups")["asubproject"])
85   end
86
87   test "subproject_admin can_manage asubproject" do
88     assert user_can_manage(:subproject_admin, api_fixture("groups")["asubproject"])
89   end
90
91   test "detect ownership loop in project breadcrumbs" do
92     # This test has an arbitrary time limit -- otherwise we'd just sit
93     # here forever instead of reporting that the loop was not
94     # detected. The test passes quickly, but fails slowly.
95     Timeout::timeout 10 do
96       get(:show,
97           { id: api_fixture("groups")["project_owns_itself"]["uuid"] },
98           session_for(:admin))
99     end
100     assert_response :success
101   end
102
103   test "project admin can remove collections from the project" do
104     # Deleting an object that supports 'expires_at' should make it
105     # completely inaccessible to API queries, not simply moved out of the project.
106     coll_key = "collection_to_remove_from_subproject"
107     coll_uuid = api_fixture("collections")[coll_key]["uuid"]
108     delete(:remove_item,
109            { id: api_fixture("groups")["asubproject"]["uuid"],
110              item_uuid: coll_uuid,
111              format: "js" },
112            session_for(:subproject_admin))
113     assert_response :success
114     assert_match(/\b#{coll_uuid}\b/, @response.body,
115                  "removed object not named in response")
116
117     use_token :subproject_admin
118     assert_raise ArvadosApiClient::NotFoundException do
119       Collection.find(coll_uuid)
120     end
121   end
122
123   test "project admin can remove items from project other than collections" do
124     # An object which does not have an expired_at field (e.g. Specimen)
125     # should be implicitly moved to the user's Home project when removed.
126     specimen_uuid = api_fixture('specimens', 'in_asubproject')['uuid']
127     delete(:remove_item,
128            { id: api_fixture('groups', 'asubproject')['uuid'],
129              item_uuid: specimen_uuid,
130              format: 'js' },
131            session_for(:subproject_admin))
132     assert_response :success
133     assert_match(/\b#{specimen_uuid}\b/, @response.body,
134                  "removed object not named in response")
135
136     use_token :subproject_admin
137     new_specimen = Specimen.find(specimen_uuid)
138     assert_equal api_fixture('users', 'subproject_admin')['uuid'], new_specimen.owner_uuid
139   end
140
141   # An object which does not offer an expired_at field but has a xx_owner_uuid_name_unique constraint
142   # will be renamed when removed and another object with the same name exists in user's home project.
143   [
144     ['groups', 'subproject_in_asubproject_with_same_name_as_one_in_active_user_home'],
145     ['pipeline_templates', 'template_in_asubproject_with_same_name_as_one_in_active_user_home'],
146   ].each do |dm, fixture|
147     test "removing #{dm} from a subproject results in renaming it when there is another such object with same name in home project" do
148       object = api_fixture(dm, fixture)
149       delete(:remove_item,
150              { id: api_fixture('groups', 'asubproject')['uuid'],
151                item_uuid: object['uuid'],
152                format: 'js' },
153              session_for(:active))
154       assert_response :success
155       assert_match(/\b#{object['uuid']}\b/, @response.body,
156                    "removed object not named in response")
157       use_token :active
158       if dm.eql?('groups')
159         found = Group.find(object['uuid'])
160       else
161         found = PipelineTemplate.find(object['uuid'])
162       end
163       assert_equal api_fixture('users', 'active')['uuid'], found.owner_uuid
164       assert_equal true, found.name.include?(object['name'] + ' removed from ')
165     end
166   end
167
168   test 'projects#show tab infinite scroll partial obeys limit' do
169     get_contents_rows(limit: 1, filters: [['uuid','is_a',['arvados#job']]])
170     assert_response :success
171     assert_equal(1, json_response['content'].scan('<tr').count,
172                  "Did not get exactly one row")
173   end
174
175   ['', ' asc', ' desc'].each do |direction|
176     test "projects#show tab partial orders correctly by #{direction}" do
177       _test_tab_content_order direction
178     end
179   end
180
181   def _test_tab_content_order direction
182     get_contents_rows(limit: 100,
183                       order: "created_at#{direction}",
184                       filters: [['uuid','is_a',['arvados#job',
185                                                 'arvados#pipelineInstance']]])
186     assert_response :success
187     not_grouped_by_kind = nil
188     last_timestamp = nil
189     last_kind = nil
190     found_kind = {}
191     json_response['content'].scan /<tr[^>]+>/ do |tr_tag|
192       found_timestamps = 0
193       tr_tag.scan(/\ data-object-created-at=\"(.*?)\"/).each do |t,|
194         if last_timestamp
195           correct_operator = / desc$/ =~ direction ? :>= : :<=
196           assert_operator(last_timestamp, correct_operator, t,
197                           "Rows are not sorted by created_at#{direction}")
198         end
199         last_timestamp = t
200         found_timestamps += 1
201       end
202       assert_equal(1, found_timestamps,
203                    "Content row did not have exactly one timestamp")
204
205       # Confirm that the test for timestamp ordering couldn't have
206       # passed merely because the test fixtures have convenient
207       # timestamps (e.g., there is only one pipeline and one job in
208       # the project being tested, or there are no pipelines at all in
209       # the project being tested):
210       tr_tag.scan /\ data-kind=\"(.*?)\"/ do |kind|
211         if last_kind and last_kind != kind and found_kind[kind]
212           # We saw this kind before, then a different kind, then
213           # this kind again. That means objects are not grouped by
214           # kind.
215           not_grouped_by_kind = true
216         end
217         found_kind[kind] ||= 0
218         found_kind[kind] += 1
219         last_kind = kind
220       end
221     end
222     assert_equal(true, not_grouped_by_kind,
223                  "Could not confirm that results are not grouped by kind")
224   end
225
226   def get_contents_rows params
227     params = {
228       id: api_fixture('users')['active']['uuid'],
229       partial: :contents_rows,
230       format: :json,
231     }.merge(params)
232     encoded_params = Hash[params.map { |k,v|
233                             [k, (v.is_a?(Array) || v.is_a?(Hash)) ? v.to_json : v]
234                           }]
235     get :show, encoded_params, session_for(:active)
236   end
237
238   test "visit non-public project as anonymous when anonymous browsing is enabled and expect page not found" do
239     Rails.configuration.anonymous_user_token = api_fixture('api_client_authorizations')['anonymous']['api_token']
240     get(:show, {id: api_fixture('groups')['aproject']['uuid']})
241     assert_response 404
242     assert_includes @response.inspect, 'you are not logged in'
243   end
244
245   test "visit home page as anonymous when anonymous browsing is enabled and expect login" do
246     Rails.configuration.anonymous_user_token = api_fixture('api_client_authorizations')['anonymous']['api_token']
247     get(:index)
248     assert_response :redirect
249     assert_match /\/users\/welcome/, @response.redirect_url
250   end
251
252   [
253     nil,
254     :active,
255   ].each do |user|
256     test "visit public projects page when anon config is enabled, as user #{user}, and expect page" do
257       Rails.configuration.anonymous_user_token = api_fixture('api_client_authorizations')['anonymous']['api_token']
258
259       if user
260         get :public, {}, session_for(user)
261       else
262         get :public
263       end
264
265       assert_response :success
266       assert_not_nil assigns(:objects)
267       project_names = assigns(:objects).collect(&:name)
268       assert_includes project_names, 'Unrestricted public data'
269       assert_not_includes project_names, 'A Project'
270     end
271   end
272
273   test "visit public projects page when anon config is not enabled as active user and expect 404" do
274     get :public, {}, session_for(:active)
275     assert_response 404
276   end
277
278   test "visit public projects page when anon config is not enabled as anonymous and expect login page" do
279     get :public
280     assert_response :redirect
281     assert_match /\/users\/welcome/, @response.redirect_url
282   end
283 end