1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: AGPL-3.0
7 class Arvados::V1::FiltersTest < ActionController::TestCase
8 test '"not in" filter passes null values' do
9 @controller = Arvados::V1::ContainerRequestsController.new
12 filters: [ ['container_uuid', 'not in', ['zzzzz-dz642-queuedcontainer', 'zzzzz-dz642-runningcontainr']] ],
13 controller: 'container_requests',
15 assert_response :success
16 found = assigns(:objects)
17 assert_includes(found.collect(&:container_uuid), nil,
18 "'container_uuid not in [zzzzz-dz642-queuedcontainer, zzzzz-dz642-runningcontainr]' filter should pass null")
21 test 'error message for non-array element in filters array' do
22 @controller = Arvados::V1::CollectionsController.new
23 authorize_with :active
25 filters: [{bogus: 'filter'}],
28 assert_match(/Invalid element in filters array/,
29 json_response['errors'].join(' '))
32 test 'error message for unsupported full text search' do
33 @controller = Arvados::V1::CollectionsController.new
34 authorize_with :active
36 filters: [['uuid', '@@', 'abcdef']],
39 assert_match(/no longer supported/, json_response['errors'].join(' '))
42 test 'error message for int64 overflow' do
43 # some versions of ActiveRecord cast >64-bit ints to postgres
44 # numeric type, but this is never useful because database content
46 @controller = Arvados::V1::LogsController.new
47 authorize_with :active
49 filters: [['id', '=', 123412341234123412341234]],
52 assert_match(/Invalid operand .* integer attribute/, json_response['errors'].join(' '))
55 test 'error message for invalid boolean operand' do
56 @controller = Arvados::V1::GroupsController.new
57 authorize_with :active
59 filters: [['is_trashed', '=', 'fourty']],
62 assert_match(/Invalid operand .* boolean attribute/, json_response['errors'].join(' '))
65 test 'api responses provide timestamps with nanoseconds' do
66 @controller = Arvados::V1::CollectionsController.new
67 authorize_with :active
69 assert_response :success
70 assert_not_empty json_response['items']
71 json_response['items'].each do |item|
72 %w(created_at modified_at).each do |attr|
73 # Pass fixtures with null timestamps.
74 next if item[attr].nil?
75 assert_match(/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d.\d{9}Z$/, item[attr])
80 %w(< > <= >= =).each do |operator|
81 test "timestamp #{operator} filters work with nanosecond precision" do
82 # Python clients like Node Manager rely on this exact format.
83 # If you must change this format for some reason, make sure you
84 # coordinate the change with them.
85 expect_match = !!operator.index('=')
86 mine = act_as_user users(:active) do
87 Collection.create!(manifest_text: '')
89 timestamp = mine.modified_at.strftime('%Y-%m-%dT%H:%M:%S.%NZ')
90 @controller = Arvados::V1::CollectionsController.new
91 authorize_with :active
93 filters: [['modified_at', operator, timestamp],
94 ['uuid', '=', mine.uuid]],
96 assert_response :success
97 uuids = json_response['items'].map { |item| item['uuid'] }
99 assert_includes uuids, mine.uuid
101 assert_not_includes uuids, mine.uuid
106 [['prop1', '=', 'value1', [:collection_with_prop1_value1], [:collection_with_prop1_value2, :collection_with_prop2_1]],
107 ['prop1', '!=', 'value1', [:collection_with_prop1_value2, :collection_with_prop2_1], [:collection_with_prop1_value1]],
108 ['prop1', 'exists', true, [:collection_with_prop1_value1, :collection_with_prop1_value2, :collection_with_prop1_value3, :collection_with_prop1_other1], [:collection_with_prop2_1]],
109 ['prop1', 'exists', false, [:collection_with_prop2_1], [:collection_with_prop1_value1, :collection_with_prop1_value2, :collection_with_prop1_value3, :collection_with_prop1_other1]],
110 ['prop1', 'in', ['value1', 'value2'], [:collection_with_prop1_value1, :collection_with_prop1_value2], [:collection_with_prop1_value3, :collection_with_prop2_1]],
111 ['prop1', 'in', ['value1', 'valueX'], [:collection_with_prop1_value1], [:collection_with_prop1_value3, :collection_with_prop2_1]],
112 ['prop1', 'not in', ['value1', 'value2'], [:collection_with_prop1_value3, :collection_with_prop1_other1, :collection_with_prop2_1], [:collection_with_prop1_value1, :collection_with_prop1_value2]],
113 ['prop1', 'not in', ['value1', 'valueX'], [:collection_with_prop1_value2, :collection_with_prop1_value3, :collection_with_prop1_other1, :collection_with_prop2_1], [:collection_with_prop1_value1]],
114 ['prop1', '>', 'value2', [:collection_with_prop1_value3], [:collection_with_prop1_other1, :collection_with_prop1_value1]],
115 ['prop1', '<', 'value2', [:collection_with_prop1_other1, :collection_with_prop1_value1], [:collection_with_prop1_value2, :collection_with_prop1_value2]],
116 ['prop1', '<=', 'value2', [:collection_with_prop1_other1, :collection_with_prop1_value1, :collection_with_prop1_value2], [:collection_with_prop1_value3]],
117 ['prop1', '>=', 'value2', [:collection_with_prop1_value2, :collection_with_prop1_value3], [:collection_with_prop1_other1, :collection_with_prop1_value1]],
118 ['prop1', 'like', 'value%', [:collection_with_prop1_value1, :collection_with_prop1_value2, :collection_with_prop1_value3], [:collection_with_prop1_other1]],
119 ['prop1', 'like', '%1', [:collection_with_prop1_value1, :collection_with_prop1_other1], [:collection_with_prop1_value2, :collection_with_prop1_value3]],
120 ['prop1', 'ilike', 'VALUE%', [:collection_with_prop1_value1, :collection_with_prop1_value2, :collection_with_prop1_value3], [:collection_with_prop1_other1]],
121 ['prop2', '>', 1, [:collection_with_prop2_5], [:collection_with_prop2_1]],
122 ['prop2', '<', 5, [:collection_with_prop2_1], [:collection_with_prop2_5]],
123 ['prop2', '<=', 5, [:collection_with_prop2_1, :collection_with_prop2_5], []],
124 ['prop2', '>=', 1, [:collection_with_prop2_1, :collection_with_prop2_5], []],
125 ['<http://schema.org/example>', '=', "value1", [:collection_with_uri_prop], []],
126 ['listprop', 'contains', 'elem1', [:collection_with_list_prop_odd, :collection_with_listprop_elem1], [:collection_with_list_prop_even]],
127 ['listprop', '=', 'elem1', [:collection_with_listprop_elem1], [:collection_with_list_prop_odd]],
128 ['listprop', 'contains', 5, [:collection_with_list_prop_odd], [:collection_with_list_prop_even, :collection_with_listprop_elem1]],
129 ['listprop', 'contains', 'elem2', [:collection_with_list_prop_even], [:collection_with_list_prop_odd, :collection_with_listprop_elem1]],
130 ['listprop', 'contains', 'ELEM2', [], [:collection_with_list_prop_even]],
131 ['listprop', 'contains', 'elem8', [], [:collection_with_list_prop_even]],
132 ['listprop', 'contains', 4, [:collection_with_list_prop_even], [:collection_with_list_prop_odd, :collection_with_listprop_elem1]],
133 ].each do |prop, op, opr, inc, ex|
134 test "jsonb filter properties.#{prop} #{op} #{opr})" do
135 @controller = Arvados::V1::CollectionsController.new
136 authorize_with :admin
137 get :index, params: {
138 filters: SafeJSON.dump([ ["properties.#{prop}", op, opr] ]),
141 assert_response :success
142 found = assigns(:objects).collect(&:uuid)
145 assert_includes(found, collections(i).uuid)
149 assert_not_includes(found, collections(e).uuid)
154 test "jsonb hash 'exists' and '!=' filter" do
155 @controller = Arvados::V1::CollectionsController.new
156 authorize_with :admin
157 get :index, params: {
158 filters: [ ['properties.prop1', 'exists', true], ['properties.prop1', '!=', 'value1'] ]
160 assert_response :success
161 found = assigns(:objects).collect(&:uuid)
162 assert_equal found.length, 3
163 assert_not_includes(found, collections(:collection_with_prop1_value1).uuid)
164 assert_includes(found, collections(:collection_with_prop1_value2).uuid)
165 assert_includes(found, collections(:collection_with_prop1_value3).uuid)
166 assert_includes(found, collections(:collection_with_prop1_other1).uuid)
169 test "jsonb array 'exists'" do
170 @controller = Arvados::V1::CollectionsController.new
171 authorize_with :admin
172 get :index, params: {
173 filters: [ ['storage_classes_confirmed.default', 'exists', true] ]
175 assert_response :success
176 found = assigns(:objects).collect(&:uuid)
177 assert_equal 2, found.length
178 assert_not_includes(found,
179 collections(:storage_classes_desired_default_unconfirmed).uuid)
180 assert_includes(found,
181 collections(:storage_classes_desired_default_confirmed_default).uuid)
182 assert_includes(found,
183 collections(:storage_classes_desired_archive_confirmed_default).uuid)
186 test "jsonb hash alternate form 'exists' and '!=' filter" do
187 @controller = Arvados::V1::CollectionsController.new
188 authorize_with :admin
189 get :index, params: {
190 filters: [ ['properties', 'exists', 'prop1'], ['properties.prop1', '!=', 'value1'] ]
192 assert_response :success
193 found = assigns(:objects).collect(&:uuid)
194 assert_equal found.length, 3
195 assert_not_includes(found, collections(:collection_with_prop1_value1).uuid)
196 assert_includes(found, collections(:collection_with_prop1_value2).uuid)
197 assert_includes(found, collections(:collection_with_prop1_value3).uuid)
198 assert_includes(found, collections(:collection_with_prop1_other1).uuid)
201 test "jsonb array alternate form 'exists' filter" do
202 @controller = Arvados::V1::CollectionsController.new
203 authorize_with :admin
204 get :index, params: {
205 filters: [ ['storage_classes_confirmed', 'exists', 'default'] ]
207 assert_response :success
208 found = assigns(:objects).collect(&:uuid)
209 assert_equal 2, found.length
210 assert_not_includes(found,
211 collections(:storage_classes_desired_default_unconfirmed).uuid)
212 assert_includes(found,
213 collections(:storage_classes_desired_default_confirmed_default).uuid)
214 assert_includes(found,
215 collections(:storage_classes_desired_archive_confirmed_default).uuid)
218 test "jsonb 'exists' must be boolean" do
219 @controller = Arvados::V1::CollectionsController.new
220 authorize_with :admin
221 get :index, params: {
222 filters: [ ['properties.prop1', 'exists', nil] ]
225 assert_match(/Invalid operand '' for 'exists' must be true or false/,
226 json_response['errors'].join(' '))
229 test "jsonb checks column exists" do
230 @controller = Arvados::V1::CollectionsController.new
231 authorize_with :admin
232 get :index, params: {
233 filters: [ ['puppies.prop1', '=', 'value1'] ]
236 assert_match(/Invalid attribute 'puppies' for subproperty filter/,
237 json_response['errors'].join(' '))
240 test "jsonb checks column is valid" do
241 @controller = Arvados::V1::CollectionsController.new
242 authorize_with :admin
243 get :index, params: {
244 filters: [ ['name.prop1', '=', 'value1'] ]
247 assert_match(/Invalid attribute 'name' for subproperty filter/,
248 json_response['errors'].join(' '))
251 test "jsonb invalid operator" do
252 @controller = Arvados::V1::CollectionsController.new
253 authorize_with :admin
254 get :index, params: {
255 filters: [ ['properties.prop1', '###', 'value1'] ]
258 assert_match(/Invalid operator for subproperty search '###'/,
259 json_response['errors'].join(' '))
262 test "groups contents with properties filter succeeds on objects with properties field" do
263 @controller = Arvados::V1::GroupsController.new
264 authorize_with :admin
265 get :contents, params: {
267 ['properties', 'exists', 'foo'],
268 ['uuid', 'is_a', ["arvados#group","arvados#collection","arvados#containerRequest"]],
272 assert json_response['items'].length == 0
276 test "groups contents with properties filter succeeds on some objects with properties field" do
277 @controller = Arvados::V1::GroupsController.new
278 authorize_with :admin
279 get :contents, params: {
281 ['properties', 'exists', 'foo'],
282 ['uuid', 'is_a', ["arvados#group","arvados#workflow"]],
286 assert json_response['items'].length == 0
290 test "groups contents with properties filter fails on objects without properties field" do
291 @controller = Arvados::V1::GroupsController.new
292 authorize_with :admin
293 get :contents, params: {
295 ['properties', 'exists', 'foo'],
296 ['uuid', 'is_a', ["arvados#workflow"]],
300 assert_match(/Invalid attribute 'properties' for operator 'exists'.*on object type Workflow/, json_response['errors'].join(' '))
303 test "groups contents without filters and limit=0, count=none" do
304 @controller = Arvados::V1::GroupsController.new
305 authorize_with :admin
306 get :contents, params: {
311 assert json_response['items'].length == 0
314 test "replication_desired = 2" do
315 @controller = Arvados::V1::CollectionsController.new
316 authorize_with :admin
317 get :index, params: {
318 filters: SafeJSON.dump([ ['replication_desired', '=', 2] ])
320 assert_response :success
321 found = assigns(:objects).collect(&:uuid)
322 assert_includes(found, collections(:replication_desired_2_unconfirmed).uuid)
323 assert_includes(found, collections(:replication_desired_2_confirmed_2).uuid)
336 [nil, [{"foo" => "bar"}]],
337 [nil, {"foo" => "bar"}],
338 ].each do |results, operand|
339 test "storage_classes_desired contains #{operand.inspect}" do
340 @controller = Arvados::V1::CollectionsController.new
341 authorize_with(:active)
342 c = Collection.create!(
344 storage_classes_desired: ["foo", "bar", "baz"])
345 get :index, params: {
346 filters: [["storage_classes_desired", "contains", operand]],
352 assert_response :success
353 assert_equal results, json_response["items"].length
355 assert_equal c.uuid, json_response["items"][0]["uuid"]
360 test "collections properties contains top level key" do
361 @controller = Arvados::V1::CollectionsController.new
362 authorize_with(:active)
363 get :index, params: {
364 filters: [["properties", "contains", "prop1"]],
366 assert_response :success
367 assert_not_empty json_response["items"]
368 json_response["items"].each do |c|
369 assert c["properties"].has_key?("prop1")