diff --git a/matemat/webserver/requestargs.py b/matemat/webserver/requestargs.py index f69a53d..1a56aeb 100644 --- a/matemat/webserver/requestargs.py +++ b/matemat/webserver/requestargs.py @@ -250,7 +250,7 @@ class RequestArgument(object): if index == 0: # Return an immutable view of the single scalar value return _View(self.__name, self.__value) - raise ValueError('Scalar RequestArgument only indexable with 0') + raise IndexError('Scalar RequestArgument only indexable with 0') # Pass the index or slice through to the array, packing the result in an immutable view return _View(self.__name, self.__value[index]) diff --git a/matemat/webserver/test/test_requestargs.py b/matemat/webserver/test/test_requestargs.py index dcdde14..133ea4a 100644 --- a/matemat/webserver/test/test_requestargs.py +++ b/matemat/webserver/test/test_requestargs.py @@ -9,196 +9,336 @@ from matemat.webserver.requestargs import _View class TestRequestArguments(unittest.TestCase): + """ + Test cases for the RequestArgument class. + """ def test_create_default(self): + """ + Test creation of an empty RequestArgument + """ ra = RequestArgument('foo') + # Name must be set to 1st argument self.assertEqual('foo', ra.name) + # Must be a 0-length array self.assertEqual(0, len(ra)) self.assertFalse(ra.is_scalar) self.assertTrue(ra.is_array) + # Must not be a view self.assertFalse(ra.is_view) def test_create_str_scalar(self): + """ + Test creation of a scalar RequestArgument with string value. + """ ra = RequestArgument('foo', ('text/plain', 'bar')) + # Name must be set to 1st argument self.assertEqual('foo', ra.name) + # Must be a scalar, length 1 self.assertEqual(1, len(ra)) self.assertTrue(ra.is_scalar) self.assertFalse(ra.is_array) + # Scalar value must be representable both as str and bytes self.assertEqual('bar', ra.get_str()) self.assertEqual(b'bar', ra.get_bytes()) + # Content-Type must be set correctly self.assertEqual('text/plain', ra.get_content_type()) + # Using indices must result in an error with self.assertRaises(ValueError): self.assertEqual('bar', ra.get_str(0)) with self.assertRaises(ValueError): self.assertEqual('bar', ra.get_bytes(0)) with self.assertRaises(ValueError): self.assertEqual('bar', ra.get_content_type(0)) + # Must not be a view self.assertFalse(ra.is_view) def test_create_str_scalar_array(self): + """ + Test creation of a scalar RequestArgument with string value, passing an array instead of a single tuple. + """ ra = RequestArgument('foo', [('text/plain', 'bar')]) + # Name must be set to 1st argument self.assertEqual('foo', ra.name) + # Must be a scalar, length 1 self.assertEqual(1, len(ra)) self.assertTrue(ra.is_scalar) self.assertFalse(ra.is_array) + # Scalar value must be representable both as str and bytes self.assertEqual('bar', ra.get_str()) self.assertEqual(b'bar', ra.get_bytes()) + # Content-Type must be set correctly self.assertEqual('text/plain', ra.get_content_type()) + # Using indices must result in an error with self.assertRaises(ValueError): self.assertEqual('bar', ra.get_str(0)) with self.assertRaises(ValueError): self.assertEqual('bar', ra.get_bytes(0)) with self.assertRaises(ValueError): self.assertEqual('bar', ra.get_content_type(0)) + # Must not be a view self.assertFalse(ra.is_view) def test_create_bytes_scalar(self): + """ + Test creation of a scalar RequestArgument with bytes value. + """ ra = RequestArgument('foo', ('application/octet-stream', b'\x00\x80\xff\xfe')) + # Name must be set to 1st argument self.assertEqual('foo', ra.name) + # Must be a scalar, length 1 self.assertEqual(1, len(ra)) self.assertTrue(ra.is_scalar) self.assertFalse(ra.is_array) + # Conversion to UTF-8 string must fail; bytes representation must work with self.assertRaises(UnicodeDecodeError): ra.get_str() self.assertEqual(b'\x00\x80\xff\xfe', ra.get_bytes()) + # Content-Type must be set correctly self.assertEqual('application/octet-stream', ra.get_content_type()) + # Using indices must result in an error with self.assertRaises(ValueError): self.assertEqual('bar', ra.get_str(0)) with self.assertRaises(ValueError): self.assertEqual('bar', ra.get_bytes(0)) with self.assertRaises(ValueError): self.assertEqual('bar', ra.get_content_type(0)) + # Must not be a view self.assertFalse(ra.is_view) def test_create_array(self): + """ + Test creation of an array RequestArgument with mixed str and bytes initial value. + """ ra = RequestArgument('foo', [ ('text/plain', 'bar'), ('application/octet-stream', b'\x00\x80\xff\xfe') ]) + # Name must be set to 1st argument self.assertEqual('foo', ra.name) + # Must be an array, length 2 self.assertEqual(2, len(ra)) self.assertFalse(ra.is_scalar) self.assertTrue(ra.is_array) + # Retrieving values without an index must fail with self.assertRaises(ValueError): ra.get_str() with self.assertRaises(ValueError): ra.get_bytes() with self.assertRaises(ValueError): ra.get_content_type() + # The first value must be representable both as str and bytes, and have ctype text/plain self.assertEqual('bar', ra.get_str(0)) self.assertEqual(b'bar', ra.get_bytes(0)) self.assertEqual('text/plain', ra.get_content_type(0)) + # Conversion of the second value to UTF-8 string must fail; bytes representation must work with self.assertRaises(UnicodeDecodeError): ra.get_str(1) self.assertEqual(b'\x00\x80\xff\xfe', ra.get_bytes(1)) + # The second value's ctype must be correct self.assertEqual('application/octet-stream', ra.get_content_type(1)) + # Must not be a view self.assertFalse(ra.is_view) def test_append_empty_str(self): + """ + Test appending a str value to an empty RequestArgument. + """ + # Initialize the empty RequestArgument ra = RequestArgument('foo') self.assertEqual(0, len(ra)) self.assertFalse(ra.is_scalar) + # Append a string value ra.append('text/plain', 'bar') + # New length must be 1, empty array must be converted to scalar self.assertEqual(1, len(ra)) self.assertTrue(ra.is_scalar) + # Retrieval of the new value must work both in str and bytes representation self.assertEqual('bar', ra.get_str()) self.assertEqual(b'bar', ra.get_bytes()) + # Content type of the new value must be correct self.assertEqual('text/plain', ra.get_content_type()) + # Must not be a view self.assertFalse(ra.is_view) def test_append_empty_bytes(self): + """ + Test appending a bytes value to an empty RequestArgument. + """ + # Initialize the empty RequestArgument ra = RequestArgument('foo') self.assertEqual(0, len(ra)) self.assertFalse(ra.is_scalar) + # Append a bytes value ra.append('application/octet-stream', b'\x00\x80\xff\xfe') + # New length must be 1, empty array must be converted to scalar self.assertEqual(1, len(ra)) self.assertTrue(ra.is_scalar) + # Conversion of the new value to UTF-8 string must fail; bytes representation must work with self.assertRaises(UnicodeDecodeError): ra.get_str() self.assertEqual(b'\x00\x80\xff\xfe', ra.get_bytes()) + # Content type of the new value must be correct self.assertEqual('application/octet-stream', ra.get_content_type()) + # Must not be a view self.assertFalse(ra.is_view) def test_append_multiple(self): + """ + Test appending multiple values to an empty RequestArgument. + """ + # Initialize the empty RequestArgument ra = RequestArgument('foo') self.assertEqual(0, len(ra)) self.assertFalse(ra.is_scalar) + # Append a first value ra.append('text/plain', 'bar') + # New length must be 1, empty array must be converted to scalar self.assertEqual(1, len(ra)) self.assertTrue(ra.is_scalar) + self.assertEqual(b'bar', ra.get_bytes()) + # Append a second value ra.append('application/octet-stream', b'\x00\x80\xff\xfe') + # New length must be 2, scalar must be converted to array self.assertEqual(2, len(ra)) self.assertFalse(ra.is_scalar) + self.assertEqual(b'bar', ra.get_bytes(0)) + self.assertEqual(b'\x00\x80\xff\xfe', ra.get_bytes(1)) + # Append a third value ra.append('text/plain', 'Hello, World!') + # New length must be 3, array must remain array self.assertEqual(3, len(ra)) self.assertFalse(ra.is_scalar) + self.assertEqual(b'bar', ra.get_bytes(0)) + self.assertEqual(b'\x00\x80\xff\xfe', ra.get_bytes(1)) + self.assertEqual(b'Hello, World!', ra.get_bytes(2)) def test_iterate_empty(self): + """ + Test iterating an empty RequestArgument. + """ ra = RequestArgument('foo') self.assertEqual(0, len(ra)) + # No value must be yielded from iterating an empty instance for _ in ra: self.fail() def test_iterate_scalar(self): + """ + Test iterating a scalar RequestArgument. + """ ra = RequestArgument('foo', ('text/plain', 'bar')) self.assertTrue(ra.is_scalar) + # Counter for the number of iterations count: int = 0 for it in ra: + # Make sure the yielded value is a scalar view and has the same name as the original instance self.assertIsInstance(it, _View) - self.assertEqual('foo', it.name) self.assertTrue(it.is_view) + self.assertEqual('foo', it.name) self.assertTrue(it.is_scalar) count += 1 + # Only one value must be yielded from iterating a scalar instance self.assertEqual(1, count) def test_iterate_array(self): + """ + Test iterating an array RequestArgument. + """ ra = RequestArgument('foo', [('text/plain', 'bar'), ('abc', b'def'), ('xyz', '1337')]) self.assertFalse(ra.is_scalar) + # Container to put the iterated ctypes into items: List[str] = list() for it in ra: + # Make sure the yielded values are scalar views and have the same name as the original instance self.assertIsInstance(it, _View) self.assertTrue(it.is_view) self.assertTrue(it.is_scalar) + # Collect the value's ctype items.append(it.get_content_type()) + # Compare collected ctypes with expected result self.assertEqual(['text/plain', 'abc', 'xyz'], items) - def test_iterate_sliced(self): + def test_slice(self): + """ + Test slicing an array RequestArgument. + """ ra = RequestArgument('foo', [('a', 'b'), ('c', 'd'), ('e', 'f'), ('g', 'h'), ('i', 'j'), ('k', 'l')]) - self.assertFalse(ra.is_scalar) + # Create the sliced view + sliced = ra[1:4:2] + # Make sure the sliced value is a view + self.assertIsInstance(sliced, _View) + self.assertTrue(sliced.is_view) + # Make sure the slice has the same name + self.assertEqual('foo', sliced.name) + # Make sure the slice has the expected shape (array of the 2nd and 4th scalar in the original) + self.assertTrue(sliced.is_array) + self.assertEqual(2, len(sliced)) + self.assertEqual('d', sliced.get_str(0)) + self.assertEqual('h', sliced.get_str(1)) + + def test_iterate_sliced(self): + """ + Test iterating a sliced array RequestArgument. + """ + ra = RequestArgument('foo', [('a', 'b'), ('c', 'd'), ('e', 'f'), ('g', 'h'), ('i', 'j'), ('k', 'l')]) + # Container to put the iterated ctypes into items: List[str] = list() + # Iterate the sliced view for it in ra[1:4:2]: + # Make sure the yielded values are scalar views and have the same name as the original instance self.assertIsInstance(it, _View) self.assertTrue(it.is_view) + self.assertEqual('foo', it.name) self.assertTrue(it.is_scalar) items.append(it.get_content_type()) + # Make sure the expected values are collected (array of the 2nd and 4th scalar in the original) self.assertEqual(['c', 'g'], items) def test_index_scalar(self): + """ + Test indexing of a scalar RequestArgument. + """ ra = RequestArgument('foo', ('bar', 'baz')) + # Index the scalar RequestArgument instance, obtaining an immutable view it = ra[0] + # Make sure the value is a scalar view with the same properties as the original instance self.assertIsInstance(it, _View) + self.assertTrue(it.is_scalar) self.assertEqual('foo', it.name) self.assertEqual('bar', it.get_content_type()) self.assertEqual('baz', it.get_str()) - with self.assertRaises(ValueError): + # Make sure other indices don't work + with self.assertRaises(IndexError): _ = ra[1] def test_index_array(self): + """ + Test indexing of an array RequestArgument. + """ ra = RequestArgument('foo', [('a', 'b'), ('c', 'd')]) + # Index the array RequestArgument instance, obtaining an immutable view it = ra[1] + # Make sure the value is a scalar view with the same properties as the value in the original instance self.assertIsInstance(it, _View) self.assertEqual('foo', it.name) self.assertEqual('c', it.get_content_type()) self.assertEqual('d', it.get_str()) def test_view_immutable(self): + """ + Test immutability of views. + """ ra = RequestArgument('foo', ('bar', 'baz')) + # Index the scalar RequestArgument instance, obtaining an immutable view it = ra[0] + # Make sure the returned value is a view self.assertIsInstance(it, _View) + # Make sure the returned value is immutable with self.assertRaises(TypeError): it.append('foo', 'bar')