1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: Apache-2.0
13 from pathlib import Path
16 from parameterized import parameterized
18 import arvados.commands._util as cmd_util
20 FILE_PATH = Path(__file__)
22 class ValidateFiltersTestCase(unittest.TestCase):
26 ('name', '=', 'tuple'),
27 {'filters': ['name', '=', 'object']},
29 NON_FILTER_TYPES = NON_FIELD_TYPES + ['string']
31 ['owner_uuid', '=', 'zzzzz-tpzed-12345abcde67890'],
32 ['name', 'in', ['foo', 'bar']],
33 '(replication_desired > replication_cofirmed)',
34 '(replication_confirmed>=replication_desired)',
37 @parameterized.expand(itertools.combinations(VALID_FILTERS, 2))
38 def test_valid_filters(self, f1, f2):
40 actual = cmd_util.validate_filters(copy.deepcopy(expected))
41 self.assertEqual(actual, expected)
43 @parameterized.expand([(t,) for t in NON_FILTER_TYPES])
44 def test_filters_wrong_type(self, value):
45 with self.assertRaisesRegex(ValueError, r'^filters are not a list\b'):
46 cmd_util.validate_filters(value)
48 @parameterized.expand([(t,) for t in NON_FIELD_TYPES])
49 def test_single_filter_wrong_type(self, value):
50 with self.assertRaisesRegex(ValueError, r'^filter at index 0 is not a string or list\b'):
51 cmd_util.validate_filters([value])
53 @parameterized.expand([
56 (['owner_uuid', 'zzzzz-tpzed-12345abcde67890'],),
57 (['name', 'not in', 'foo', 'bar'],),
58 (['name', 'in', 'foo', 'bar', 'baz'],),
60 def test_filters_wrong_arity(self, value):
61 with self.assertRaisesRegex(ValueError, r'^filter at index 0 does not have three items\b'):
62 cmd_util.validate_filters([value])
64 @parameterized.expand(itertools.product(
68 def test_filter_definition_wrong_type(self, index, bad_value):
69 value = ['owner_uuid', '=', 'zzzzz-tpzed-12345abcde67890']
70 value[index] = bad_value
71 name = ('field name', 'operator')[index]
72 with self.assertRaisesRegex(ValueError, rf'^filter at index 0 {name} is not a string\b'):
73 cmd_util.validate_filters([value])
75 @parameterized.expand([
76 # Not enclosed in parentheses
80 # Not exactly one operator
83 '(file_count version)',
84 # Invalid field identifiers
87 '(replication.desired <= replication.confirmed)',
89 '(file_count\t=\tversion)',
90 '(file_count >= version\n)',
92 def test_invalid_string_filter(self, value):
93 with self.assertRaisesRegex(ValueError, r'^filter at index 0 has invalid syntax\b'):
94 cmd_util.validate_filters([value])
97 class JSONArgumentTestCase(unittest.TestCase):
104 {'object': True, 'yaml': False},
109 cls.json_file = tempfile.NamedTemporaryFile(
115 cls.parser = cmd_util.JSONArgument()
118 def tearDownClass(cls):
119 cls.json_file.close()
122 self.json_file.seek(0)
123 self.json_file.truncate()
125 @parameterized.expand((obj,) for obj in JSON_OBJECTS)
126 def test_valid_argument_string(self, obj):
127 actual = self.parser(json.dumps(obj))
128 self.assertEqual(actual, obj)
130 @parameterized.expand((obj,) for obj in JSON_OBJECTS)
131 def test_valid_argument_path(self, obj):
132 json.dump(obj, self.json_file)
133 self.json_file.flush()
134 actual = self.parser(self.json_file.name)
135 self.assertEqual(actual, obj)
137 @parameterized.expand([
142 def test_argument_not_json_or_path(self, value):
144 with tempfile.NamedTemporaryFile() as gone_file:
145 value = gone_file.name
146 with self.assertRaisesRegex(ValueError, r'\bnot a valid JSON string or file path\b'):
149 @parameterized.expand([
151 FILE_PATH / 'nonexistent.json',
154 def test_argument_path_unreadable(self, path):
156 bad_file = tempfile.NamedTemporaryFile()
157 os.chmod(bad_file.fileno(), 0o000)
159 @contextlib.contextmanager
164 os.chmod(bad_file.fileno(), 0o600)
166 ctx = contextlib.nullcontext
167 with self.assertRaisesRegex(ValueError, rf'^error reading JSON file path {str(path)!r}: '), ctx():
168 self.parser(str(path))
170 @parameterized.expand([
174 def test_argument_path_not_json(self, path):
176 path = self.json_file.name
177 with self.assertRaisesRegex(ValueError, rf'^error decoding JSON from file {str(path)!r}'):
178 self.parser(str(path))
181 class JSONArgumentValidationTestCase(unittest.TestCase):
182 @parameterized.expand((obj,) for obj in JSONArgumentTestCase.JSON_OBJECTS)
183 def test_object_returned_from_validator(self, value):
184 parser = cmd_util.JSONArgument(lambda _: copy.deepcopy(value))
185 self.assertEqual(parser('{}'), value)
187 @parameterized.expand((obj,) for obj in JSONArgumentTestCase.JSON_OBJECTS)
188 def test_exception_raised_from_validator(self, value):
189 json_value = json.dumps(value)
191 raise ValueError(json_value)
192 parser = cmd_util.JSONArgument(raise_func)
193 with self.assertRaises(ValueError) as exc_check:
195 self.assertEqual(exc_check.exception.args, (json_value,))
198 class TestRangedValue:
199 @pytest.fixture(scope='class')
201 return cmd_util.RangedValue(int, range(-1, 2))
203 @pytest.mark.parametrize('s', ['-1', '0', '1'])
204 def test_valid_values(self, cmpint, s):
205 assert cmpint(s) == int(s)
207 @pytest.mark.parametrize('s', ['foo', '-2', '2', '0.2', '', ' '])
208 def test_invalid_values(self, cmpint, s):
209 with pytest.raises(ValueError):
213 class TestUniqueSplit:
214 @pytest.fixture(scope='class')
216 return cmd_util.UniqueSplit()
218 @pytest.mark.parametrize('arg', [
222 'foo , bar , baz , quux',
224 def test_basic_parse(self, arg, argtype):
225 expected = ['foo', 'bar', 'baz', 'quux'][:arg.count(',') + 1]
226 assert argtype(arg) == expected
228 @pytest.mark.parametrize('arg', [
233 def test_uniqueness(self, arg, argtype):
234 assert argtype(arg) == ['foo', 'bar']