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 ['in', 'not in'].each do |operator|
56 test "error message for int64 overflow ('#{operator}' filter)" do
57 @controller = Arvados::V1::ContainerRequestsController.new
58 authorize_with :active
60 filters: [['priority', operator, [9, 123412341234123412341234]]],
63 assert_match(/Invalid element .* integer attribute/, json_response['errors'].join(' '))
67 test 'error message for invalid boolean operand' do
68 @controller = Arvados::V1::GroupsController.new
69 authorize_with :active
71 filters: [['is_trashed', '=', 'fourty']],
74 assert_match(/Invalid operand .* boolean attribute/, json_response['errors'].join(' '))
77 test 'api responses provide timestamps with nanoseconds' do
78 @controller = Arvados::V1::CollectionsController.new
79 authorize_with :active
81 assert_response :success
82 assert_not_empty json_response['items']
83 json_response['items'].each do |item|
84 %w(created_at modified_at).each do |attr|
85 # Pass fixtures with null timestamps.
86 next if item[attr].nil?
87 assert_match(/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d.\d{9}Z$/, item[attr])
92 %w(< > <= >= =).each do |operator|
93 test "timestamp #{operator} filters work with nanosecond precision" do
94 # Python clients like Node Manager rely on this exact format.
95 # If you must change this format for some reason, make sure you
96 # coordinate the change with them.
97 expect_match = !!operator.index('=')
98 mine = act_as_user users(:active) do
99 Collection.create!(manifest_text: '')
101 timestamp = mine.modified_at.strftime('%Y-%m-%dT%H:%M:%S.%NZ')
102 @controller = Arvados::V1::CollectionsController.new
103 authorize_with :active
104 get :index, params: {
105 filters: [['modified_at', operator, timestamp],
106 ['uuid', '=', mine.uuid]],
108 assert_response :success
109 uuids = json_response['items'].map { |item| item['uuid'] }
111 assert_includes uuids, mine.uuid
113 assert_not_includes uuids, mine.uuid
118 [['prop1', '=', 'value1', [:collection_with_prop1_value1], [:collection_with_prop1_value2, :collection_with_prop2_1]],
119 ['prop1', '!=', 'value1', [:collection_with_prop1_value2, :collection_with_prop2_1], [:collection_with_prop1_value1]],
120 ['prop1', 'exists', true, [:collection_with_prop1_value1, :collection_with_prop1_value2, :collection_with_prop1_value3, :collection_with_prop1_other1], [:collection_with_prop2_1]],
121 ['prop1', 'exists', false, [:collection_with_prop2_1], [:collection_with_prop1_value1, :collection_with_prop1_value2, :collection_with_prop1_value3, :collection_with_prop1_other1]],
122 ['prop1', 'in', ['value1', 'value2'], [:collection_with_prop1_value1, :collection_with_prop1_value2], [:collection_with_prop1_value3, :collection_with_prop2_1]],
123 ['prop1', 'in', ['value1', 'valueX'], [:collection_with_prop1_value1], [:collection_with_prop1_value3, :collection_with_prop2_1]],
124 ['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]],
125 ['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]],
126 ['prop1', '>', 'value2', [:collection_with_prop1_value3], [:collection_with_prop1_other1, :collection_with_prop1_value1]],
127 ['prop1', '<', 'value2', [:collection_with_prop1_other1, :collection_with_prop1_value1], [:collection_with_prop1_value2, :collection_with_prop1_value2]],
128 ['prop1', '<=', 'value2', [:collection_with_prop1_other1, :collection_with_prop1_value1, :collection_with_prop1_value2], [:collection_with_prop1_value3]],
129 ['prop1', '>=', 'value2', [:collection_with_prop1_value2, :collection_with_prop1_value3], [:collection_with_prop1_other1, :collection_with_prop1_value1]],
130 ['prop1', 'like', 'value%', [:collection_with_prop1_value1, :collection_with_prop1_value2, :collection_with_prop1_value3], [:collection_with_prop1_other1]],
131 ['prop1', 'like', '%1', [:collection_with_prop1_value1, :collection_with_prop1_other1], [:collection_with_prop1_value2, :collection_with_prop1_value3]],
132 ['prop1', 'ilike', 'VALUE%', [:collection_with_prop1_value1, :collection_with_prop1_value2, :collection_with_prop1_value3], [:collection_with_prop1_other1]],
133 ['prop2', '>', 1, [:collection_with_prop2_5], [:collection_with_prop2_1]],
134 ['prop2', '<', 5, [:collection_with_prop2_1], [:collection_with_prop2_5]],
135 ['prop2', '<=', 5, [:collection_with_prop2_1, :collection_with_prop2_5], []],
136 ['prop2', '>=', 1, [:collection_with_prop2_1, :collection_with_prop2_5], []],
137 ['<http://schema.org/example>', '=', "value1", [:collection_with_uri_prop], []],
138 ['listprop', 'contains', 'elem1', [:collection_with_list_prop_odd, :collection_with_listprop_elem1], [:collection_with_list_prop_even]],
139 ['listprop', '=', 'elem1', [:collection_with_listprop_elem1], [:collection_with_list_prop_odd]],
140 ['listprop', 'contains', 5, [:collection_with_list_prop_odd], [:collection_with_list_prop_even, :collection_with_listprop_elem1]],
141 ['listprop', 'contains', 'elem2', [:collection_with_list_prop_even], [:collection_with_list_prop_odd, :collection_with_listprop_elem1]],
142 ['listprop', 'contains', 'ELEM2', [], [:collection_with_list_prop_even]],
143 ['listprop', 'contains', 'elem8', [], [:collection_with_list_prop_even]],
144 ['listprop', 'contains', 4, [:collection_with_list_prop_even], [:collection_with_list_prop_odd, :collection_with_listprop_elem1]],
145 ].each do |prop, op, opr, inc, ex|
146 test "jsonb filter properties.#{prop} #{op} #{opr})" do
147 @controller = Arvados::V1::CollectionsController.new
148 authorize_with :admin
149 get :index, params: {
150 filters: SafeJSON.dump([ ["properties.#{prop}", op, opr] ]),
153 assert_response :success
154 found = assigns(:objects).collect(&:uuid)
157 assert_includes(found, collections(i).uuid)
161 assert_not_includes(found, collections(e).uuid)
166 test "jsonb hash 'exists' and '!=' filter" do
167 @controller = Arvados::V1::CollectionsController.new
168 authorize_with :admin
169 get :index, params: {
170 filters: [ ['properties.prop1', 'exists', true], ['properties.prop1', '!=', 'value1'] ]
172 assert_response :success
173 found = assigns(:objects).collect(&:uuid)
174 assert_equal found.length, 3
175 assert_not_includes(found, collections(:collection_with_prop1_value1).uuid)
176 assert_includes(found, collections(:collection_with_prop1_value2).uuid)
177 assert_includes(found, collections(:collection_with_prop1_value3).uuid)
178 assert_includes(found, collections(:collection_with_prop1_other1).uuid)
181 test "jsonb array 'exists'" do
182 @controller = Arvados::V1::CollectionsController.new
183 authorize_with :admin
184 get :index, params: {
185 filters: [ ['storage_classes_confirmed.default', 'exists', true] ]
187 assert_response :success
188 found = assigns(:objects).collect(&:uuid)
189 assert_equal 2, found.length
190 assert_not_includes(found,
191 collections(:storage_classes_desired_default_unconfirmed).uuid)
192 assert_includes(found,
193 collections(:storage_classes_desired_default_confirmed_default).uuid)
194 assert_includes(found,
195 collections(:storage_classes_desired_archive_confirmed_default).uuid)
198 test "jsonb hash alternate form 'exists' and '!=' filter" do
199 @controller = Arvados::V1::CollectionsController.new
200 authorize_with :admin
201 get :index, params: {
202 filters: [ ['properties', 'exists', 'prop1'], ['properties.prop1', '!=', 'value1'] ]
204 assert_response :success
205 found = assigns(:objects).collect(&:uuid)
206 assert_equal found.length, 3
207 assert_not_includes(found, collections(:collection_with_prop1_value1).uuid)
208 assert_includes(found, collections(:collection_with_prop1_value2).uuid)
209 assert_includes(found, collections(:collection_with_prop1_value3).uuid)
210 assert_includes(found, collections(:collection_with_prop1_other1).uuid)
213 test "jsonb array alternate form 'exists' filter" do
214 @controller = Arvados::V1::CollectionsController.new
215 authorize_with :admin
216 get :index, params: {
217 filters: [ ['storage_classes_confirmed', 'exists', 'default'] ]
219 assert_response :success
220 found = assigns(:objects).collect(&:uuid)
221 assert_equal 2, found.length
222 assert_not_includes(found,
223 collections(:storage_classes_desired_default_unconfirmed).uuid)
224 assert_includes(found,
225 collections(:storage_classes_desired_default_confirmed_default).uuid)
226 assert_includes(found,
227 collections(:storage_classes_desired_archive_confirmed_default).uuid)
230 test "jsonb 'exists' must be boolean" do
231 @controller = Arvados::V1::CollectionsController.new
232 authorize_with :admin
233 get :index, params: {
234 filters: [ ['properties.prop1', 'exists', nil] ]
237 assert_match(/Invalid operand '' for 'exists' must be true or false/,
238 json_response['errors'].join(' '))
241 test "jsonb checks column exists" do
242 @controller = Arvados::V1::CollectionsController.new
243 authorize_with :admin
244 get :index, params: {
245 filters: [ ['puppies.prop1', '=', 'value1'] ]
248 assert_match(/Invalid attribute 'puppies' for subproperty filter/,
249 json_response['errors'].join(' '))
252 test "jsonb checks column is valid" do
253 @controller = Arvados::V1::CollectionsController.new
254 authorize_with :admin
255 get :index, params: {
256 filters: [ ['name.prop1', '=', 'value1'] ]
259 assert_match(/Invalid attribute 'name' for subproperty filter/,
260 json_response['errors'].join(' '))
263 test "jsonb invalid operator" do
264 @controller = Arvados::V1::CollectionsController.new
265 authorize_with :admin
266 get :index, params: {
267 filters: [ ['properties.prop1', '###', 'value1'] ]
270 assert_match(/Invalid operator for subproperty search '###'/,
271 json_response['errors'].join(' '))
274 test "groups contents with properties filter succeeds on objects with properties field" do
275 @controller = Arvados::V1::GroupsController.new
276 authorize_with :admin
277 get :contents, params: {
279 ['properties', 'exists', 'foo'],
280 ['uuid', 'is_a', ["arvados#group","arvados#collection","arvados#containerRequest"]],
284 assert json_response['items'].length == 0
288 test "groups contents with properties filter succeeds on some objects with properties field" do
289 @controller = Arvados::V1::GroupsController.new
290 authorize_with :admin
291 get :contents, params: {
293 ['properties', 'exists', 'foo'],
294 ['uuid', 'is_a', ["arvados#group","arvados#workflow"]],
298 assert json_response['items'].length == 0
302 test "groups contents with properties filter fails on objects without properties field" do
303 @controller = Arvados::V1::GroupsController.new
304 authorize_with :admin
305 get :contents, params: {
307 ['properties', 'exists', 'foo'],
308 ['uuid', 'is_a', ["arvados#workflow"]],
312 assert_match(/Invalid attribute 'properties' for operator 'exists'.*on object type Workflow/, json_response['errors'].join(' '))
315 test "groups contents without filters and limit=0, count=none" do
316 @controller = Arvados::V1::GroupsController.new
317 authorize_with :admin
318 get :contents, params: {
323 assert json_response['items'].length == 0
326 test "replication_desired = 2" do
327 @controller = Arvados::V1::CollectionsController.new
328 authorize_with :admin
329 get :index, params: {
330 filters: SafeJSON.dump([ ['replication_desired', '=', 2] ])
332 assert_response :success
333 found = assigns(:objects).collect(&:uuid)
334 assert_includes(found, collections(:replication_desired_2_unconfirmed).uuid)
335 assert_includes(found, collections(:replication_desired_2_confirmed_2).uuid)
348 [nil, [{"foo" => "bar"}]],
349 [nil, {"foo" => "bar"}],
350 ].each do |results, operand|
351 test "storage_classes_desired contains #{operand.inspect}" do
352 @controller = Arvados::V1::CollectionsController.new
353 authorize_with(:active)
354 c = Collection.create!(
356 storage_classes_desired: ["foo", "bar", "baz"])
357 get :index, params: {
358 filters: [["storage_classes_desired", "contains", operand]],
364 assert_response :success
365 assert_equal results, json_response["items"].length
367 assert_equal c.uuid, json_response["items"][0]["uuid"]
372 test "collections properties contains top level key" do
373 @controller = Arvados::V1::CollectionsController.new
374 authorize_with(:active)
375 get :index, params: {
376 filters: [["properties", "contains", "prop1"]],
378 assert_response :success
379 assert_not_empty json_response["items"]
380 json_response["items"].each do |c|
381 assert c["properties"].has_key?("prop1")