17994: Allow filtering on storage_classes_desired/confirmed fields.
[arvados.git] / services / api / test / functional / arvados / v1 / collections_controller_test.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 require 'test_helper'
6
7 class Arvados::V1::CollectionsControllerTest < ActionController::TestCase
8   include DbCurrentTime
9
10   PERM_TOKEN_RE = /\+A[[:xdigit:]]+@[[:xdigit:]]{8}\b/
11
12   def permit_unsigned_manifests isok=true
13     # Set security model for the life of a test.
14     Rails.configuration.Collections.BlobSigning = !isok
15   end
16
17   def assert_signed_manifest manifest_text, label='', token: false
18     assert_not_nil manifest_text, "#{label} manifest_text was nil"
19     manifest_text.scan(/ [[:xdigit:]]{32}\S*/) do |tok|
20       assert_match(PERM_TOKEN_RE, tok,
21                    "Locator in #{label} manifest_text was not signed")
22       if token
23         bare = tok.gsub(/\+A[^\+]*/, '').sub(/^ /, '')
24         exp = tok[/\+A[[:xdigit:]]+@([[:xdigit:]]+)/, 1].to_i(16)
25         sig = Blob.sign_locator(
26           bare,
27           key: Rails.configuration.Collections.BlobSigningKey,
28           expire: exp,
29           api_token: token)[/\+A[^\+]*/, 0]
30         assert_includes tok, sig
31       end
32     end
33   end
34
35   def assert_unsigned_manifest resp, label=''
36     txt = resp['unsigned_manifest_text']
37     assert_not_nil(txt, "#{label} unsigned_manifest_text was nil")
38     locs = 0
39     txt.scan(/ [[:xdigit:]]{32}\S*/) do |tok|
40       locs += 1
41       refute_match(PERM_TOKEN_RE, tok,
42                    "Locator in #{label} unsigned_manifest_text was signed: #{tok}")
43     end
44     return locs
45   end
46
47   test "should get index" do
48     authorize_with :active
49     get :index
50     assert_response :success
51     assert(assigns(:objects).andand.any?, "no Collections returned in index")
52     refute(json_response["items"].any? { |c| c.has_key?("manifest_text") },
53            "basic Collections index included manifest_text")
54     refute(json_response["items"].any? { |c| c["uuid"] == collections(:collection_owned_by_active_past_version_1).uuid },
55            "basic Collections index included past version")
56   end
57
58   test "get index with include_old_versions" do
59     authorize_with :active
60     get :index, params: {
61       include_old_versions: true
62     }
63     assert_response :success
64     assert(assigns(:objects).andand.any?, "no Collections returned in index")
65     assert(json_response["items"].any? { |c| c["uuid"] == collections(:collection_owned_by_active_past_version_1).uuid },
66            "past version not included on index")
67   end
68
69   test "collections.get returns signed locators, and no unsigned_manifest_text" do
70     permit_unsigned_manifests
71     authorize_with :active
72     get :show, params: {id: collections(:foo_file).uuid}
73     assert_response :success
74     assert_signed_manifest json_response['manifest_text'], 'foo_file'
75     refute_includes json_response, 'unsigned_manifest_text'
76   end
77
78   ['v1token', 'v2token'].each do |token_method|
79     test "correct signatures are given for #{token_method}" do
80       token = api_client_authorizations(:active).send(token_method)
81       authorize_with_token token
82       get :show, params: {id: collections(:foo_file).uuid}
83       assert_response :success
84       assert_signed_manifest json_response['manifest_text'], 'foo_file', token: token
85     end
86
87     test "signatures with #{token_method} are accepted" do
88       token = api_client_authorizations(:active).send(token_method)
89       signed = Blob.sign_locator(
90         'acbd18db4cc2f85cedef654fccc4a4d8+3',
91         key: Rails.configuration.Collections.BlobSigningKey,
92         api_token: token)
93       authorize_with_token token
94       put :update, params: {
95             id: collections(:collection_owned_by_active).uuid,
96             collection: {
97               manifest_text: ". #{signed} 0:3:foo.txt\n",
98             },
99           }
100       assert_response :success
101       assert_signed_manifest json_response['manifest_text'], 'updated', token: token
102     end
103   end
104
105   test "index with manifest_text selected returns signed locators" do
106     columns = %w(uuid owner_uuid manifest_text)
107     authorize_with :active
108     get :index, params: {select: columns}
109     assert_response :success
110     assert(assigns(:objects).andand.any?,
111            "no Collections returned for index with columns selected")
112     json_response["items"].each do |coll|
113       assert_equal(coll.keys - ['kind'], columns,
114                    "Collections index did not respect selected columns")
115       assert_signed_manifest coll['manifest_text'], coll['uuid']
116     end
117   end
118
119   test "index with unsigned_manifest_text selected returns only unsigned locators" do
120     authorize_with :active
121     get :index, params: {select: ['unsigned_manifest_text']}
122     assert_response :success
123     assert_operator json_response["items"].count, :>, 0
124     locs = 0
125     json_response["items"].each do |coll|
126       assert_equal(coll.keys - ['kind'], ['unsigned_manifest_text'],
127                    "Collections index did not respect selected columns")
128       locs += assert_unsigned_manifest coll, coll['uuid']
129     end
130     assert_operator locs, :>, 0, "no locators found in any manifests"
131   end
132
133   test 'index without select returns everything except manifest' do
134     authorize_with :active
135     get :index
136     assert_response :success
137     assert json_response['items'].any?
138     json_response['items'].each do |coll|
139       assert_includes(coll.keys, 'uuid')
140       assert_includes(coll.keys, 'name')
141       assert_includes(coll.keys, 'created_at')
142       refute_includes(coll.keys, 'manifest_text')
143     end
144   end
145
146   ['', nil, false, 'null'].each do |select|
147     test "index with select=#{select.inspect} returns everything except manifest" do
148       authorize_with :active
149       get :index, params: {select: select}
150       assert_response :success
151       assert json_response['items'].any?
152       json_response['items'].each do |coll|
153         assert_includes(coll.keys, 'uuid')
154         assert_includes(coll.keys, 'name')
155         assert_includes(coll.keys, 'created_at')
156         refute_includes(coll.keys, 'manifest_text')
157       end
158     end
159   end
160
161   [["uuid"],
162    ["uuid", "manifest_text"],
163    '["uuid"]',
164    '["uuid", "manifest_text"]'].each do |select|
165     test "index with select=#{select.inspect} returns no name" do
166       authorize_with :active
167       get :index, params: {select: select}
168       assert_response :success
169       assert json_response['items'].any?
170       json_response['items'].each do |coll|
171         refute_includes(coll.keys, 'name')
172       end
173     end
174   end
175
176   [0,1,2].each do |limit|
177     test "get index with limit=#{limit}" do
178       authorize_with :active
179       get :index, params: {limit: limit}
180       assert_response :success
181       assert_equal limit, assigns(:objects).count
182       resp = JSON.parse(@response.body)
183       assert_equal limit, resp['limit']
184     end
185   end
186
187   test "items.count == items_available" do
188     authorize_with :active
189     get :index, params: {limit: 100000}
190     assert_response :success
191     resp = JSON.parse(@response.body)
192     assert_equal resp['items_available'], assigns(:objects).length
193     assert_equal resp['items_available'], resp['items'].count
194     unique_uuids = resp['items'].collect { |i| i['uuid'] }.compact.uniq
195     assert_equal unique_uuids.count, resp['items'].count
196   end
197
198   test "items.count == items_available with filters" do
199     authorize_with :active
200     get :index, params: {
201       limit: 100,
202       filters: [['uuid','=',collections(:foo_file).uuid]]
203     }
204     assert_response :success
205     assert_equal 1, assigns(:objects).length
206     assert_equal 1, json_response['items_available']
207     assert_equal 1, json_response['items'].count
208   end
209
210   test "get index with limit=2 offset=99999" do
211     # Assume there are not that many test fixtures.
212     authorize_with :active
213     get :index, params: {limit: 2, offset: 99999}
214     assert_response :success
215     assert_equal 0, assigns(:objects).count
216     resp = JSON.parse(@response.body)
217     assert_equal 2, resp['limit']
218     assert_equal 99999, resp['offset']
219   end
220
221   def request_capped_index(params={})
222     authorize_with :user1_with_load
223     coll1 = collections(:collection_1_of_201)
224     Rails.configuration.API.MaxIndexDatabaseRead =
225       yield(coll1.manifest_text.size)
226     get :index, params: {
227       select: %w(uuid manifest_text),
228       filters: [["owner_uuid", "=", coll1.owner_uuid]],
229       limit: 300,
230     }.merge(params)
231   end
232
233   test "index with manifest_text limited by max_index_database_read returns non-empty" do
234     request_capped_index() { |_| 1 }
235     assert_response :success
236     assert_equal(1, json_response["items"].size)
237     assert_equal(1, json_response["limit"])
238     assert_equal(201, json_response["items_available"])
239   end
240
241   test "max_index_database_read size check follows same order as real query" do
242     authorize_with :user1_with_load
243     txt = '.' + ' d41d8cd98f00b204e9800998ecf8427e+0'*1000 + " 0:0:empty.txt\n"
244     c = Collection.create! manifest_text: txt, name: '0000000000000000000'
245     request_capped_index(select: %w(uuid manifest_text name),
246                          order: ['name asc'],
247                          filters: [['name','>=',c.name]]) do |_|
248       txt.length - 1
249     end
250     assert_response :success
251     assert_equal(1, json_response["items"].size)
252     assert_equal(1, json_response["limit"])
253     assert_equal(c.uuid, json_response["items"][0]["uuid"])
254     # The effectiveness of the test depends on >1 item matching the filters.
255     assert_operator(1, :<, json_response["items_available"])
256   end
257
258   test "index with manifest_text limited by max_index_database_read" do
259     request_capped_index() { |size| (size * 3) + 1 }
260     assert_response :success
261     assert_equal(3, json_response["items"].size)
262     assert_equal(3, json_response["limit"])
263     assert_equal(201, json_response["items_available"])
264   end
265
266   test "max_index_database_read does not interfere with limit" do
267     request_capped_index(limit: 5) { |size| size * 20 }
268     assert_response :success
269     assert_equal(5, json_response["items"].size)
270     assert_equal(5, json_response["limit"])
271     assert_equal(201, json_response["items_available"])
272   end
273
274   test "max_index_database_read does not interfere with order" do
275     request_capped_index(select: %w(uuid manifest_text name),
276                          order: "name DESC") { |size| (size * 11) + 1 }
277     assert_response :success
278     assert_equal(11, json_response["items"].size)
279     assert_empty(json_response["items"].reject do |coll|
280                    coll["name"] =~ /^Collection_9/
281                  end)
282     assert_equal(11, json_response["limit"])
283     assert_equal(201, json_response["items_available"])
284   end
285
286   test "admin can create collection with unsigned manifest" do
287     authorize_with :admin
288     test_collection = {
289       manifest_text: <<-EOS
290 . d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt
291 . acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:bar.txt
292 . acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:bar.txt
293 ./baz acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:bar.txt
294 EOS
295     }
296     test_collection[:portable_data_hash] =
297       Digest::MD5.hexdigest(test_collection[:manifest_text]) +
298       '+' +
299       test_collection[:manifest_text].length.to_s
300
301     # post :create will modify test_collection in place, so we save a copy first.
302     # Hash.deep_dup is not sufficient as it preserves references of strings (??!?)
303     post_collection = Marshal.load(Marshal.dump(test_collection))
304     post :create, params: {
305       collection: post_collection
306     }
307
308     assert_response :success
309     assert_nil assigns(:objects)
310
311     response_collection = assigns(:object)
312
313     stored_collection = Collection.select([:uuid, :portable_data_hash, :manifest_text]).
314       where(portable_data_hash: response_collection['portable_data_hash']).first
315
316     assert_equal test_collection[:portable_data_hash], stored_collection['portable_data_hash']
317
318     # The manifest in the response will have had permission hints added.
319     # Remove any permission hints in the response before comparing it to the source.
320     stripped_manifest = stored_collection['manifest_text'].gsub(/\+A[A-Za-z0-9@_-]+/, '')
321     assert_equal test_collection[:manifest_text], stripped_manifest
322
323     # TBD: create action should add permission signatures to manifest_text in the response,
324     # and we need to check those permission signatures here.
325   end
326
327   [:admin, :active].each do |user|
328     test "#{user} can get collection using portable data hash" do
329       authorize_with user
330
331       foo_collection = collections(:foo_file)
332
333       # Get foo_file using its portable data hash
334       get :show, params: {
335         id: foo_collection[:portable_data_hash]
336       }
337       assert_response :success
338       assert_not_nil assigns(:object)
339       resp = assigns(:object)
340       assert_equal foo_collection[:portable_data_hash], resp[:portable_data_hash]
341       assert_signed_manifest resp[:manifest_text]
342
343       # The manifest in the response will have had permission hints added.
344       # Remove any permission hints in the response before comparing it to the source.
345       stripped_manifest = resp[:manifest_text].gsub(/\+A[A-Za-z0-9@_-]+/, '')
346       assert_equal foo_collection[:manifest_text], stripped_manifest
347     end
348   end
349
350   test "create with owner_uuid set to owned group" do
351     permit_unsigned_manifests
352     authorize_with :active
353     manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
354     post :create, params: {
355       collection: {
356         owner_uuid: 'zzzzz-j7d0g-rew6elm53kancon',
357         manifest_text: manifest_text,
358         portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47"
359       }
360     }
361     assert_response :success
362     resp = JSON.parse(@response.body)
363     assert_equal 'zzzzz-j7d0g-rew6elm53kancon', resp['owner_uuid']
364   end
365
366   test "create fails with duplicate name" do
367     permit_unsigned_manifests
368     authorize_with :admin
369     manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
370     post :create, params: {
371       collection: {
372         owner_uuid: 'zzzzz-tpzed-000000000000000',
373         manifest_text: manifest_text,
374         portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47",
375         name: "foo_file"
376       }
377     }
378     assert_response 422
379     response_errors = json_response['errors']
380     assert_not_nil response_errors, 'Expected error in response'
381     assert(response_errors.first.include?('duplicate key'),
382            "Expected 'duplicate key' error in #{response_errors.first}")
383   end
384
385   [false, true].each do |unsigned|
386     test "create with duplicate name, ensure_unique_name, unsigned=#{unsigned}" do
387       permit_unsigned_manifests unsigned
388       authorize_with :active
389       manifest_text = ". acbd18db4cc2f85cedef654fccc4a4d8+3 0:0:foo.txt\n"
390       if !unsigned
391         manifest_text = Collection.sign_manifest manifest_text, api_token(:active)
392       end
393       post :create, params: {
394         collection: {
395           owner_uuid: users(:active).uuid,
396           manifest_text: manifest_text,
397           name: "owned_by_active"
398         },
399         ensure_unique_name: true
400       }
401       assert_response :success
402       assert_match /^owned_by_active \(\d{4}-\d\d-\d\d.*?Z\)$/, json_response['name']
403     end
404   end
405
406   test "create with owner_uuid set to group i can_manage" do
407     permit_unsigned_manifests
408     authorize_with :active
409     manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
410     post :create, params: {
411       collection: {
412         owner_uuid: groups(:active_user_has_can_manage).uuid,
413         manifest_text: manifest_text,
414         portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47"
415       }
416     }
417     assert_response :success
418     resp = JSON.parse(@response.body)
419     assert_equal groups(:active_user_has_can_manage).uuid, resp['owner_uuid']
420   end
421
422   test "create with owner_uuid fails on group with only can_read permission" do
423     permit_unsigned_manifests
424     authorize_with :active
425     manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
426     post :create, params: {
427       collection: {
428         owner_uuid: groups(:all_users).uuid,
429         manifest_text: manifest_text,
430         portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47"
431       }
432     }
433     assert_response 403
434   end
435
436   test "create with owner_uuid fails on group with no permission" do
437     permit_unsigned_manifests
438     authorize_with :active
439     manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
440     post :create, params: {
441       collection: {
442         owner_uuid: groups(:public).uuid,
443         manifest_text: manifest_text,
444         portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47"
445       }
446     }
447     assert_response 422
448   end
449
450   test "admin create with owner_uuid set to group with no permission" do
451     permit_unsigned_manifests
452     authorize_with :admin
453     manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
454     post :create, params: {
455       collection: {
456         owner_uuid: 'zzzzz-j7d0g-it30l961gq3t0oi',
457         manifest_text: manifest_text,
458         portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47"
459       }
460     }
461     assert_response :success
462   end
463
464   test "should create with collection passed as json" do
465     permit_unsigned_manifests
466     authorize_with :active
467     post :create, params: {
468       collection: <<-EOS
469       {
470         "manifest_text":". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n",\
471         "portable_data_hash":"d30fe8ae534397864cb96c544f4cf102+47"\
472       }
473       EOS
474     }
475     assert_response :success
476   end
477
478   test "should fail to create with checksum mismatch" do
479     permit_unsigned_manifests
480     authorize_with :active
481     post :create, params: {
482       collection: <<-EOS
483       {
484         "manifest_text":". d41d8cd98f00b204e9800998ecf8427e 0:0:bar.txt\n",\
485         "portable_data_hash":"d30fe8ae534397864cb96c544f4cf102+47"\
486       }
487       EOS
488     }
489     assert_response 422
490   end
491
492   test "collection UUID is normalized when created" do
493     permit_unsigned_manifests
494     authorize_with :active
495     post :create, params: {
496       collection: {
497         manifest_text: ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n",
498         portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47+Khint+Xhint+Zhint"
499       }
500     }
501     assert_response :success
502     assert_not_nil assigns(:object)
503     resp = JSON.parse(@response.body)
504     assert_equal "d30fe8ae534397864cb96c544f4cf102+47", resp['portable_data_hash']
505   end
506
507   test "get full provenance for baz file" do
508     authorize_with :active
509     get :provenance, params: {id: 'ea10d51bcf88862dbcc36eb292017dfd+45'}
510     assert_response :success
511     resp = JSON.parse(@response.body)
512     assert_not_nil resp['ea10d51bcf88862dbcc36eb292017dfd+45'] # baz
513     assert_not_nil resp['fa7aeb5140e2848d39b416daeef4ffc5+45'] # bar
514     assert_not_nil resp['1f4b0bc7583c2a7f9102c395f4ffc5e3+45'] # foo
515     assert_not_nil resp['zzzzz-8i9sb-cjs4pklxxjykyuq'] # bar->baz
516     assert_not_nil resp['zzzzz-8i9sb-aceg2bnq7jt7kon'] # foo->bar
517   end
518
519   test "get no provenance for foo file" do
520     # spectator user cannot even see baz collection
521     authorize_with :spectator
522     get :provenance, params: {id: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'}
523     assert_response 404
524   end
525
526   test "get partial provenance for baz file" do
527     # spectator user can see bar->baz job, but not foo->bar job
528     authorize_with :spectator
529     get :provenance, params: {id: 'ea10d51bcf88862dbcc36eb292017dfd+45'}
530     assert_response :success
531     resp = JSON.parse(@response.body)
532     assert_not_nil resp['ea10d51bcf88862dbcc36eb292017dfd+45'] # baz
533     assert_not_nil resp['fa7aeb5140e2848d39b416daeef4ffc5+45'] # bar
534     assert_not_nil resp['zzzzz-8i9sb-cjs4pklxxjykyuq']     # bar->baz
535     assert_nil resp['zzzzz-8i9sb-aceg2bnq7jt7kon']         # foo->bar
536     assert_nil resp['1f4b0bc7583c2a7f9102c395f4ffc5e3+45'] # foo
537   end
538
539   test "search collections with 'any' operator" do
540     expect_pdh = collections(:docker_image).portable_data_hash
541     authorize_with :active
542     get :index, params: {
543       where: { any: ['contains', expect_pdh[5..25]] }
544     }
545     assert_response :success
546     found = assigns(:objects)
547     assert_equal 1, found.count
548     assert_equal expect_pdh, found.first.portable_data_hash
549   end
550
551   [false, true].each do |permit_unsigned|
552     test "create collection with signed manifest, permit_unsigned=#{permit_unsigned}" do
553       permit_unsigned_manifests permit_unsigned
554       authorize_with :active
555       locators = %w(
556       d41d8cd98f00b204e9800998ecf8427e+0
557       acbd18db4cc2f85cedef654fccc4a4d8+3
558       ea10d51bcf88862dbcc36eb292017dfd+45)
559
560       unsigned_manifest = locators.map { |loc|
561         ". " + loc + " 0:0:foo.txt\n"
562       }.join()
563       manifest_uuid = Digest::MD5.hexdigest(unsigned_manifest) +
564         '+' +
565         unsigned_manifest.length.to_s
566
567       # Build a manifest with both signed and unsigned locators.
568       signing_opts = {
569         key: Rails.configuration.Collections.BlobSigningKey,
570         api_token: api_token(:active),
571       }
572       signed_locators = locators.collect do |x|
573         Blob.sign_locator x, signing_opts
574       end
575       if permit_unsigned
576         # Leave a non-empty blob unsigned.
577         signed_locators[1] = locators[1]
578       else
579         # Leave the empty blob unsigned. This should still be allowed.
580         signed_locators[0] = locators[0]
581       end
582       signed_manifest =
583         ". " + signed_locators[0] + " 0:0:foo.txt\n" +
584         ". " + signed_locators[1] + " 0:0:foo.txt\n" +
585         ". " + signed_locators[2] + " 0:0:foo.txt\n"
586
587       post :create, params: {
588         collection: {
589           manifest_text: signed_manifest,
590           portable_data_hash: manifest_uuid,
591         }
592       }
593       assert_response :success
594       assert_not_nil assigns(:object)
595       resp = JSON.parse(@response.body)
596       assert_equal manifest_uuid, resp['portable_data_hash']
597       # All of the locators in the output must be signed.
598       resp['manifest_text'].lines.each do |entry|
599         m = /([[:xdigit:]]{32}\+\S+)/.match(entry)
600         if m
601           assert Blob.verify_signature m[0], signing_opts
602         end
603       end
604     end
605   end
606
607   test "create collection with signed manifest and explicit TTL" do
608     authorize_with :active
609     locators = %w(
610       d41d8cd98f00b204e9800998ecf8427e+0
611       acbd18db4cc2f85cedef654fccc4a4d8+3
612       ea10d51bcf88862dbcc36eb292017dfd+45)
613
614     unsigned_manifest = locators.map { |loc|
615       ". " + loc + " 0:0:foo.txt\n"
616     }.join()
617     manifest_uuid = Digest::MD5.hexdigest(unsigned_manifest) +
618       '+' +
619       unsigned_manifest.length.to_s
620
621     # build a manifest with both signed and unsigned locators.
622     # TODO(twp): in phase 4, all locators will need to be signed, so
623     # this test should break and will need to be rewritten. Issue #2755.
624     signing_opts = {
625       key: Rails.configuration.Collections.BlobSigningKey,
626       api_token: api_token(:active),
627       ttl: 3600   # 1 hour
628     }
629     signed_manifest =
630       ". " + locators[0] + " 0:0:foo.txt\n" +
631       ". " + Blob.sign_locator(locators[1], signing_opts) + " 0:0:foo.txt\n" +
632       ". " + Blob.sign_locator(locators[2], signing_opts) + " 0:0:foo.txt\n"
633
634     post :create, params: {
635       collection: {
636         manifest_text: signed_manifest,
637         portable_data_hash: manifest_uuid,
638       }
639     }
640     assert_response :success
641     assert_not_nil assigns(:object)
642     resp = JSON.parse(@response.body)
643     assert_equal manifest_uuid, resp['portable_data_hash']
644     # All of the locators in the output must be signed.
645     resp['manifest_text'].lines.each do |entry|
646       m = /([[:xdigit:]]{32}\+\S+)/.match(entry)
647       if m
648         assert Blob.verify_signature m[0], signing_opts
649       end
650     end
651   end
652
653   test "create fails with invalid signature" do
654     authorize_with :active
655     signing_opts = {
656       key: Rails.configuration.Collections.BlobSigningKey,
657       api_token: api_token(:active),
658     }
659
660     # Generate a locator with a bad signature.
661     unsigned_locator = "acbd18db4cc2f85cedef654fccc4a4d8+3"
662     bad_locator = unsigned_locator + "+Affffffffffffffffffffffffffffffffffffffff@ffffffff"
663     assert !Blob.verify_signature(bad_locator, signing_opts)
664
665     # Creating a collection with this locator should
666     # produce 403 Permission denied.
667     unsigned_manifest = ". #{unsigned_locator} 0:0:foo.txt\n"
668     manifest_uuid = Digest::MD5.hexdigest(unsigned_manifest) +
669       '+' +
670       unsigned_manifest.length.to_s
671
672     bad_manifest = ". #{bad_locator} 0:0:foo.txt\n"
673     post :create, params: {
674       collection: {
675         manifest_text: bad_manifest,
676         portable_data_hash: manifest_uuid
677       }
678     }
679
680     assert_response 403
681   end
682
683   test "create fails with uuid of signed manifest" do
684     authorize_with :active
685     signing_opts = {
686       key: Rails.configuration.Collections.BlobSigningKey,
687       api_token: api_token(:active),
688     }
689
690     unsigned_locator = "d41d8cd98f00b204e9800998ecf8427e+0"
691     signed_locator = Blob.sign_locator(unsigned_locator, signing_opts)
692     signed_manifest = ". #{signed_locator} 0:0:foo.txt\n"
693     manifest_uuid = Digest::MD5.hexdigest(signed_manifest) +
694       '+' +
695       signed_manifest.length.to_s
696
697     post :create, params: {
698       collection: {
699         manifest_text: signed_manifest,
700         portable_data_hash: manifest_uuid
701       }
702     }
703
704     assert_response 422
705   end
706
707   test "reject manifest with unsigned block as stream name" do
708     authorize_with :active
709     post :create, params: {
710       collection: {
711         manifest_text: "00000000000000000000000000000000+1234 d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt\n"
712       }
713     }
714     assert_includes [422, 403], response.code.to_i
715   end
716
717   test "multiple locators per line" do
718     permit_unsigned_manifests
719     authorize_with :active
720     locators = %w(
721       d41d8cd98f00b204e9800998ecf8427e+0
722       acbd18db4cc2f85cedef654fccc4a4d8+3
723       ea10d51bcf88862dbcc36eb292017dfd+45)
724
725     manifest_text = [".", *locators, "0:0:foo.txt\n"].join(" ")
726     manifest_uuid = Digest::MD5.hexdigest(manifest_text) +
727       '+' +
728       manifest_text.length.to_s
729
730     test_collection = {
731       manifest_text: manifest_text,
732       portable_data_hash: manifest_uuid,
733     }
734     post_collection = Marshal.load(Marshal.dump(test_collection))
735     post :create, params: {
736       collection: post_collection
737     }
738     assert_response :success
739     assert_not_nil assigns(:object)
740     resp = JSON.parse(@response.body)
741     assert_equal manifest_uuid, resp['portable_data_hash']
742
743     # The manifest in the response will have had permission hints added.
744     # Remove any permission hints in the response before comparing it to the source.
745     stripped_manifest = resp['manifest_text'].gsub(/\+A[A-Za-z0-9@_-]+/, '')
746     assert_equal manifest_text, stripped_manifest
747   end
748
749   test "multiple signed locators per line" do
750     permit_unsigned_manifests
751     authorize_with :active
752     locators = %w(
753       d41d8cd98f00b204e9800998ecf8427e+0
754       acbd18db4cc2f85cedef654fccc4a4d8+3
755       ea10d51bcf88862dbcc36eb292017dfd+45)
756
757     signing_opts = {
758       key: Rails.configuration.Collections.BlobSigningKey,
759       api_token: api_token(:active),
760     }
761
762     unsigned_manifest = [".", *locators, "0:0:foo.txt\n"].join(" ")
763     manifest_uuid = Digest::MD5.hexdigest(unsigned_manifest) +
764       '+' +
765       unsigned_manifest.length.to_s
766
767     signed_locators = locators.map { |loc| Blob.sign_locator loc, signing_opts }
768     signed_manifest = [".", *signed_locators, "0:0:foo.txt\n"].join(" ")
769
770     post :create, params: {
771       collection: {
772         manifest_text: signed_manifest,
773         portable_data_hash: manifest_uuid,
774       }
775     }
776     assert_response :success
777     assert_not_nil assigns(:object)
778     resp = JSON.parse(@response.body)
779     assert_equal manifest_uuid, resp['portable_data_hash']
780     # All of the locators in the output must be signed.
781     # Each line is of the form "path locator locator ... 0:0:file.txt"
782     # entry.split[1..-2] will yield just the tokens in the middle of the line
783     returned_locator_count = 0
784     resp['manifest_text'].lines.each do |entry|
785       entry.split[1..-2].each do |tok|
786         returned_locator_count += 1
787         assert Blob.verify_signature tok, signing_opts
788       end
789     end
790     assert_equal locators.count, returned_locator_count
791   end
792
793   test 'Reject manifest with unsigned blob' do
794     permit_unsigned_manifests false
795     authorize_with :active
796     unsigned_manifest = ". 0cc175b9c0f1b6a831c399e269772661+1 0:1:a.txt\n"
797     manifest_uuid = Digest::MD5.hexdigest(unsigned_manifest)
798     post :create, params: {
799       collection: {
800         manifest_text: unsigned_manifest,
801         portable_data_hash: manifest_uuid,
802       }
803     }
804     assert_response 403,
805     "Creating a collection with unsigned blobs should respond 403"
806     assert_empty Collection.where('uuid like ?', manifest_uuid+'%'),
807     "Collection should not exist in database after failed create"
808   end
809
810   test 'List expired collection returns empty list' do
811     authorize_with :active
812     get :index, params: {
813       where: {name: 'expired_collection'},
814     }
815     assert_response :success
816     found = assigns(:objects)
817     assert_equal 0, found.count
818   end
819
820   test 'Show expired collection returns 404' do
821     authorize_with :active
822     get :show, params: {
823       id: 'zzzzz-4zz18-mto52zx1s7sn3ih',
824     }
825     assert_response 404
826   end
827
828   test 'Update expired collection returns 404' do
829     authorize_with :active
830     post :update, params: {
831       id: 'zzzzz-4zz18-mto52zx1s7sn3ih',
832       collection: {
833         name: "still expired"
834       }
835     }
836     assert_response 404
837   end
838
839   test 'List collection with future expiration time succeeds' do
840     authorize_with :active
841     get :index, params: {
842       where: {name: 'collection_expires_in_future'},
843     }
844     found = assigns(:objects)
845     assert_equal 1, found.count
846   end
847
848
849   test 'Show collection with future expiration time succeeds' do
850     authorize_with :active
851     get :show, params: {
852       id: 'zzzzz-4zz18-padkqo7yb8d9i3j',
853     }
854     assert_response :success
855   end
856
857   test 'Update collection with future expiration time succeeds' do
858     authorize_with :active
859     post :update, params: {
860       id: 'zzzzz-4zz18-padkqo7yb8d9i3j',
861       collection: {
862         name: "still not expired"
863       }
864     }
865     assert_response :success
866   end
867
868   test "get collection and verify that file_names is not included" do
869     authorize_with :active
870     get :show, params: {id: collections(:foo_file).uuid}
871     assert_response :success
872     assert_equal collections(:foo_file).uuid, json_response['uuid']
873     assert_nil json_response['file_names']
874     assert json_response['manifest_text']
875   end
876
877   [
878     [2**8, :success],
879     [2**18, 422],
880   ].each do |description_size, expected_response|
881     # Descriptions are not part of search indexes. Skip until
882     # full-text search is implemented, at which point replace with a
883     # search in description.
884     skip "create collection with description size #{description_size}
885           and expect response #{expected_response}" do
886       authorize_with :active
887
888       description = 'here is a collection with a very large description'
889       while description.length < description_size
890         description = description + description
891       end
892
893       post :create, params: {
894         collection: {
895           manifest_text: ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt\n",
896           description: description,
897         }
898       }
899
900       assert_response expected_response
901     end
902   end
903
904   [1, 5, nil].each do |ask|
905     test "Set replication_desired=#{ask.inspect}" do
906       Rails.configuration.Collections.DefaultReplication = 2
907       authorize_with :active
908       put :update, params: {
909         id: collections(:replication_undesired_unconfirmed).uuid,
910         collection: {
911           replication_desired: ask,
912         },
913       }
914       assert_response :success
915       assert_equal ask, json_response['replication_desired']
916     end
917   end
918
919   test "get collection with properties" do
920     authorize_with :active
921     get :show, params: {id: collections(:collection_with_one_property).uuid}
922     assert_response :success
923     assert_not_nil json_response['uuid']
924     assert_equal 'value1', json_response['properties']['property1']
925   end
926
927   [
928     {'property_1' => 'value_1'},
929     "{\"property_1\":\"value_1\"}",
930   ].each do |p|
931     test "create collection with valid properties param #{p.inspect}" do
932       authorize_with :active
933       manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
934       post :create, params: {
935         collection: {
936           manifest_text: manifest_text,
937           portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47",
938           properties: p
939         }
940       }
941       assert_response :success
942       assert_not_nil json_response['uuid']
943       assert_equal Hash, json_response['properties'].class, 'Collection properties attribute should be of type hash'
944       assert_equal 'value_1', json_response['properties']['property_1']
945     end
946   end
947
948   [
949     false,
950     [],
951     42,
952     'some string',
953     '["json", "encoded", "array"]',
954   ].each do |p|
955     test "create collection with non-valid properties param #{p.inspect}" do
956       authorize_with :active
957       post :create, params: {
958         collection: {
959           name: "test collection with non-valid properties param '#{p.inspect}'",
960           manifest_text: '',
961           properties: p
962         }
963       }
964       assert_response 422
965       response_errors = json_response['errors']
966       assert_not_nil response_errors, 'Expected error in response'
967     end
968   end
969
970   [
971     [". d41d8cd98f00b204e9800998ecf8427e 0:34:foo.txt\n", 1, 34],
972     [". d41d8cd98f00b204e9800998ecf8427e 0:34:foo.txt 0:30:foo.txt 0:30:foo1.txt 0:30:foo2.txt 0:30:foo3.txt 0:30:foo4.txt\n", 5, 184],
973     [". d41d8cd98f00b204e9800998ecf8427e 0:0:.\n", 0, 0]
974   ].each do |manifest, count, size|
975     test "create collection with valid manifest #{manifest} and expect file stats" do
976       authorize_with :active
977       post :create, params: {
978         collection: {
979           manifest_text: manifest
980         }
981       }
982       assert_response 200
983       assert_equal count, json_response['file_count']
984       assert_equal size, json_response['file_size_total']
985     end
986   end
987
988   test "update collection manifest and expect new file stats" do
989     authorize_with :active
990     post :update, params: {
991       id: collections(:collection_owned_by_active_with_file_stats).uuid,
992       collection: {
993         manifest_text: ". d41d8cd98f00b204e9800998ecf8427e 0:34:foo.txt\n"
994       }
995     }
996     assert_response 200
997     assert_equal 1, json_response['file_count']
998     assert_equal 34, json_response['file_size_total']
999   end
1000
1001   [
1002     ['file_count', 1],
1003     ['file_size_total', 34]
1004   ].each do |attribute, val|
1005     test "create collection with #{attribute} and expect overwrite" do
1006       authorize_with :active
1007       post :create, params: {
1008         collection: {
1009           manifest_text: ". d41d8cd98f00b204e9800998ecf8427e 0:34:foo.txt\n",
1010           "#{attribute}": 10
1011         }
1012       }
1013       assert_response 200
1014       assert_equal val, json_response[attribute]
1015     end
1016   end
1017
1018   [
1019     ['file_count', 1],
1020     ['file_size_total', 3]
1021   ].each do |attribute, val|
1022     test "update collection with #{attribute} and expect ignore" do
1023       authorize_with :active
1024       post :update, params: {
1025         id: collections(:collection_owned_by_active_with_file_stats).uuid,
1026         collection: {
1027           "#{attribute}": 10
1028         }
1029       }
1030       assert_response 200
1031       assert_equal val, json_response[attribute]
1032     end
1033   end
1034
1035   [
1036     ['file_count', 1],
1037     ['file_size_total', 34]
1038   ].each do |attribute, val|
1039     test "update collection with #{attribute} and manifest and expect manifest values" do
1040       authorize_with :active
1041       post :update, params: {
1042         id: collections(:collection_owned_by_active_with_file_stats).uuid,
1043         collection: {
1044           manifest_text: ". d41d8cd98f00b204e9800998ecf8427e 0:34:foo.txt\n",
1045           "#{attribute}": 10
1046         }
1047       }
1048       assert_response 200
1049       assert_equal val, json_response[attribute]
1050     end
1051   end
1052
1053   [
1054     ". 0:0:foo.txt",
1055     ". d41d8cd98f00b204e9800998ecf8427e foo.txt",
1056     "d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt",
1057     ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt",
1058   ].each do |manifest_text|
1059     test "create collection with invalid manifest #{manifest_text} and expect error" do
1060       authorize_with :active
1061       post :create, params: {
1062         collection: {
1063           manifest_text: manifest_text,
1064           portable_data_hash: "d41d8cd98f00b204e9800998ecf8427e+0"
1065         }
1066       }
1067       assert_response 422
1068       response_errors = json_response['errors']
1069       assert_not_nil response_errors, 'Expected error in response'
1070       assert(response_errors.first.include?('Invalid manifest'),
1071              "Expected 'Invalid manifest' error in #{response_errors.first}")
1072     end
1073   end
1074
1075   [
1076     [nil, "d41d8cd98f00b204e9800998ecf8427e+0"],
1077     ["", "d41d8cd98f00b204e9800998ecf8427e+0"],
1078     [". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n", "d30fe8ae534397864cb96c544f4cf102+47"],
1079   ].each do |manifest_text, pdh|
1080     test "create collection with valid manifest #{manifest_text.inspect} and expect success" do
1081       authorize_with :active
1082       post :create, params: {
1083         collection: {
1084           manifest_text: manifest_text,
1085           portable_data_hash: pdh
1086         }
1087       }
1088       assert_response 200
1089     end
1090   end
1091
1092   [
1093     ". 0:0:foo.txt",
1094     ". d41d8cd98f00b204e9800998ecf8427e foo.txt",
1095     "d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt",
1096     ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt",
1097   ].each do |manifest_text|
1098     test "update collection with invalid manifest #{manifest_text} and expect error" do
1099       authorize_with :active
1100       post :update, params: {
1101         id: 'zzzzz-4zz18-bv31uwvy3neko21',
1102         collection: {
1103           manifest_text: manifest_text,
1104         }
1105       }
1106       assert_response 422
1107       response_errors = json_response['errors']
1108       assert_not_nil response_errors, 'Expected error in response'
1109       assert(response_errors.first.include?('Invalid manifest'),
1110              "Expected 'Invalid manifest' error in #{response_errors.first}")
1111     end
1112   end
1113
1114   [
1115     nil,
1116     "",
1117     ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n",
1118   ].each do |manifest_text|
1119     test "update collection with valid manifest #{manifest_text.inspect} and expect success" do
1120       authorize_with :active
1121       post :update, params: {
1122         id: 'zzzzz-4zz18-bv31uwvy3neko21',
1123         collection: {
1124           manifest_text: manifest_text,
1125         }
1126       }
1127       assert_response 200
1128     end
1129   end
1130
1131   [true, false].each do |include_trash|
1132     test "get trashed collection with include_trash=#{include_trash}" do
1133       uuid = 'zzzzz-4zz18-mto52zx1s7sn3ih' # expired_collection
1134       authorize_with :active
1135       get :show, params: {
1136         id: uuid,
1137         include_trash: include_trash,
1138       }
1139       if include_trash
1140         assert_response 200
1141       else
1142         assert_response 404
1143       end
1144     end
1145   end
1146
1147   [:admin, :active].each do |user|
1148     test "get trashed collection via filters and #{user} user without including its past versions" do
1149       uuid = 'zzzzz-4zz18-mto52zx1s7sn3ih' # expired_collection
1150       authorize_with user
1151       get :index, params: {
1152         filters: [["current_version_uuid", "=", uuid]],
1153         include_trash: true,
1154       }
1155       assert_response 200
1156       # Only the current version is returned
1157       assert_equal 1, json_response["items"].size
1158     end
1159   end
1160
1161   [:admin, :active].each do |user|
1162     test "get trashed collection via filters and #{user} user, including its past versions" do
1163       uuid = 'zzzzz-4zz18-mto52zx1s7sn3ih' # expired_collection
1164       authorize_with :admin
1165       get :index, params: {
1166         filters: [["current_version_uuid", "=", uuid]],
1167         include_trash: true,
1168         include_old_versions: true,
1169       }
1170       assert_response 200
1171       # Both current & past version are returned
1172       assert_equal 2, json_response["items"].size
1173     end
1174   end
1175
1176   test "trash collection also trash its past versions" do
1177     uuid = collections(:collection_owned_by_active).uuid
1178     authorize_with :active
1179     versions = Collection.where(current_version_uuid: uuid)
1180     assert_equal 2, versions.size
1181     versions.each do |col|
1182       refute col.is_trashed
1183     end
1184     post :trash, params: {
1185       id: uuid,
1186     }
1187     assert_response 200
1188     versions = Collection.where(current_version_uuid: uuid)
1189     assert_equal 2, versions.size
1190     versions.each do |col|
1191       assert col.is_trashed
1192     end
1193   end
1194
1195   test 'get trashed collection without include_trash' do
1196     uuid = 'zzzzz-4zz18-mto52zx1s7sn3ih' # expired_collection
1197     authorize_with :active
1198     get :show, params: {
1199       id: uuid,
1200     }
1201     assert_response 404
1202   end
1203
1204   test 'trash collection using http DELETE verb' do
1205     uuid = collections(:collection_owned_by_active).uuid
1206     authorize_with :active
1207     delete :destroy, params: {
1208       id: uuid,
1209     }
1210     assert_response 200
1211     c = Collection.find_by_uuid(uuid)
1212     assert_operator c.trash_at, :<, db_current_time
1213     assert_equal c.delete_at, c.trash_at + Rails.configuration.Collections.BlobSigningTTL
1214   end
1215
1216   test 'delete long-trashed collection immediately using http DELETE verb' do
1217     uuid = 'zzzzz-4zz18-mto52zx1s7sn3ih' # expired_collection
1218     authorize_with :active
1219     delete :destroy, params: {
1220       id: uuid,
1221     }
1222     assert_response 200
1223     c = Collection.find_by_uuid(uuid)
1224     assert_operator c.trash_at, :<, db_current_time
1225     assert_operator c.delete_at, :<, db_current_time
1226   end
1227
1228   ['zzzzz-4zz18-mto52zx1s7sn3ih', # expired_collection
1229    :empty_collection_name_in_active_user_home_project,
1230   ].each do |fixture|
1231     test "trash collection #{fixture} via trash action with grace period" do
1232       if fixture.is_a? String
1233         uuid = fixture
1234       else
1235         uuid = collections(fixture).uuid
1236       end
1237       authorize_with :active
1238       time_before_trashing = db_current_time
1239       post :trash, params: {
1240         id: uuid,
1241       }
1242       assert_response 200
1243       c = Collection.find_by_uuid(uuid)
1244       assert_operator c.trash_at, :<, db_current_time
1245       assert_operator c.delete_at, :>=, time_before_trashing + Rails.configuration.Collections.DefaultTrashLifetime
1246     end
1247   end
1248
1249   test 'untrash a trashed collection' do
1250     authorize_with :active
1251     post :untrash, params: {
1252       id: collections(:expired_collection).uuid,
1253     }
1254     assert_response 200
1255     assert_equal false, json_response['is_trashed']
1256     assert_nil json_response['trash_at']
1257   end
1258
1259   test 'untrash error on not trashed collection' do
1260     authorize_with :active
1261     post :untrash, params: {
1262       id: collections(:collection_owned_by_active).uuid,
1263     }
1264     assert_response 422
1265   end
1266
1267   [:active, :admin].each do |user|
1268     test "get trashed collections as #{user}" do
1269       authorize_with user
1270       get :index, params: {
1271         filters: [["is_trashed", "=", true]],
1272         include_trash: true,
1273       }
1274       assert_response :success
1275
1276       items = []
1277       json_response["items"].each do |coll|
1278         items << coll['uuid']
1279       end
1280
1281       assert_includes(items, collections('unique_expired_collection')['uuid'])
1282       if user == :admin
1283         assert_includes(items, collections('unique_expired_collection2')['uuid'])
1284       else
1285         assert_not_includes(items, collections('unique_expired_collection2')['uuid'])
1286       end
1287     end
1288   end
1289
1290   test 'untrash collection with same name as another with no ensure unique name' do
1291     authorize_with :active
1292     post :untrash, params: {
1293       id: collections(:trashed_collection_to_test_name_conflict_on_untrash).uuid,
1294     }
1295     assert_response 422
1296   end
1297
1298   test 'untrash collection with same name as another with ensure unique name' do
1299     authorize_with :active
1300     post :untrash, params: {
1301       id: collections(:trashed_collection_to_test_name_conflict_on_untrash).uuid,
1302       ensure_unique_name: true
1303     }
1304     assert_response 200
1305     assert_equal false, json_response['is_trashed']
1306     assert_nil json_response['trash_at']
1307     assert_nil json_response['delete_at']
1308     assert_match /^same name for trashed and persisted collections \(\d{4}-\d\d-\d\d.*?Z\)$/, json_response['name']
1309   end
1310
1311   test 'cannot show collection in trashed subproject' do
1312     authorize_with :active
1313     get :show, params: {
1314       id: collections(:collection_in_trashed_subproject).uuid,
1315       format: :json
1316     }
1317     assert_response 404
1318   end
1319
1320   test 'can show collection in untrashed subproject' do
1321     authorize_with :active
1322     Group.find_by_uuid(groups(:trashed_project).uuid).update! is_trashed: false
1323     get :show, params: {
1324       id: collections(:collection_in_trashed_subproject).uuid,
1325       format: :json,
1326     }
1327     assert_response :success
1328   end
1329
1330   test 'cannot index collection in trashed subproject' do
1331     authorize_with :active
1332     get :index, params: { limit: 1000 }
1333     assert_response :success
1334     item_uuids = json_response['items'].map do |item|
1335       item['uuid']
1336     end
1337     assert_not_includes(item_uuids, collections(:collection_in_trashed_subproject).uuid)
1338   end
1339
1340   test 'can index collection in untrashed subproject' do
1341     authorize_with :active
1342     Group.find_by_uuid(groups(:trashed_project).uuid).update! is_trashed: false
1343     get :index, params: { limit: 1000 }
1344     assert_response :success
1345     item_uuids = json_response['items'].map do |item|
1346       item['uuid']
1347     end
1348     assert_includes(item_uuids, collections(:collection_in_trashed_subproject).uuid)
1349   end
1350
1351   test 'can index trashed subproject collection with include_trash' do
1352     authorize_with :active
1353     get :index, params: {
1354           include_trash: true,
1355           limit: 1000
1356         }
1357     assert_response :success
1358     item_uuids = json_response['items'].map do |item|
1359       item['uuid']
1360     end
1361     assert_includes(item_uuids, collections(:collection_in_trashed_subproject).uuid)
1362   end
1363
1364   test 'can get collection with past versions' do
1365     authorize_with :active
1366     get :index, params: {
1367       filters: [['current_version_uuid','=',collections(:collection_owned_by_active).uuid]],
1368       include_old_versions: true
1369     }
1370     assert_response :success
1371     assert_equal 2, assigns(:objects).length
1372     assert_equal 2, json_response['items_available']
1373     assert_equal 2, json_response['items'].count
1374     json_response['items'].each do |c|
1375       assert_equal collections(:collection_owned_by_active).uuid,
1376                    c['current_version_uuid'],
1377                    'response includes a version from a different collection'
1378     end
1379   end
1380
1381   test 'can get old version collection by uuid' do
1382     authorize_with :active
1383     get :show, params: {
1384       id: collections(:collection_owned_by_active_past_version_1).uuid,
1385     }
1386     assert_response :success
1387     assert_equal collections(:collection_owned_by_active_past_version_1).name,
1388                   json_response['name']
1389   end
1390
1391   test 'can get old version collection by PDH' do
1392     authorize_with :active
1393     get :show, params: {
1394       id: collections(:collection_owned_by_active_past_version_1).portable_data_hash,
1395     }
1396     assert_response :success
1397     assert_equal collections(:collection_owned_by_active_past_version_1).portable_data_hash,
1398                   json_response['portable_data_hash']
1399   end
1400
1401   test 'version and current_version_uuid are ignored at creation time' do
1402     permit_unsigned_manifests
1403     authorize_with :active
1404     manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
1405     post :create, params: {
1406       collection: {
1407         name: 'Test collection',
1408         version: 42,
1409         current_version_uuid: collections(:collection_owned_by_active).uuid,
1410         manifest_text: manifest_text,
1411       }
1412     }
1413     assert_response :success
1414     resp = JSON.parse(@response.body)
1415     assert_equal 1, resp['version']
1416     assert_equal resp['uuid'], resp['current_version_uuid']
1417   end
1418
1419   test "update collection with versioning enabled" do
1420     Rails.configuration.Collections.CollectionVersioning = true
1421     Rails.configuration.Collections.PreserveVersionIfIdle = 1 # 1 second
1422
1423     col = collections(:collection_owned_by_active)
1424     assert_equal 2, col.version
1425     assert col.modified_at < Time.now - 1.second
1426
1427     token = api_client_authorizations(:active).v2token
1428     signed = Blob.sign_locator(
1429       'acbd18db4cc2f85cedef654fccc4a4d8+3',
1430       key: Rails.configuration.Collections.BlobSigningKey,
1431       api_token: token)
1432     authorize_with_token token
1433     put :update, params: {
1434           id: col.uuid,
1435           collection: {
1436             manifest_text: ". #{signed} 0:3:foo.txt\n",
1437           },
1438         }
1439     assert_response :success
1440     assert_equal 3, json_response['version']
1441   end
1442
1443   test "delete collection with versioning enabled" do
1444     Rails.configuration.Collections.CollectionVersioning = true
1445     Rails.configuration.Collections.PreserveVersionIfIdle = 1 # 1 second
1446
1447     col = collections(:collection_owned_by_active)
1448     assert_equal 2, col.version
1449     assert col.modified_at < Time.now - 1.second
1450
1451     authorize_with(:active)
1452     post :trash, params: {
1453       id: col.uuid,
1454     }
1455     assert_response :success
1456     assert_equal col.version, json_response['version'], 'Trashing a collection should not create a new version'
1457   end
1458
1459   ["storage_classes_desired", "storage_classes_confirmed"].each do |attr|
1460     test "filter collections by #{attr}" do
1461       authorize_with(:active)
1462       get :index, params: {
1463             filters: [[attr, "=", '["default"]']]
1464           }
1465       assert_response :success
1466       assert_not_equal 0, json_response["items"].length
1467       json_response["items"].each do |c|
1468         assert_equal ["default"], c[attr]
1469       end
1470     end
1471   end
1472 end