forked from s3lph/matemat
Merge branch 'webserver-impl' into DO-NOT-MERGE-horrible-webapp
This commit is contained in:
commit
0aa790dae8
2 changed files with 82 additions and 18 deletions
|
@ -8,7 +8,7 @@ It provides a touch-input-friendly user interface (as most input happens through
|
||||||
soda machine's touch screen).
|
soda machine's touch screen).
|
||||||
|
|
||||||
This project intends to provide a well-tested and maintainable alternative to
|
This project intends to provide a well-tested and maintainable alternative to
|
||||||
[TODO][todo] (discontinued).
|
[ckruse/matemat][oldapp] (last commit 2013-07-09).
|
||||||
|
|
||||||
## Further Documentation
|
## Further Documentation
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ This project intends to provide a well-tested and maintainable alternative to
|
||||||
- Python 3 (>=3.6)
|
- Python 3 (>=3.6)
|
||||||
- Python dependencies:
|
- Python dependencies:
|
||||||
- apsw
|
- apsw
|
||||||
- bcrypt
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
@ -37,6 +36,7 @@ python -m matemat
|
||||||
[MIT License][mit-license]
|
[MIT License][mit-license]
|
||||||
|
|
||||||
|
|
||||||
|
[oldapp]: https://github.com/ckruse/matemat
|
||||||
[mit-license]: https://gitlab.com/s3lph/matemat/blob/master/LICENSE
|
[mit-license]: https://gitlab.com/s3lph/matemat/blob/master/LICENSE
|
||||||
[master]: https://gitlab.com/s3lph/matemat/commits/master
|
[master]: https://gitlab.com/s3lph/matemat/commits/master
|
||||||
[wiki]: https://gitlab.com/s3lph/matemat/wikis/home
|
[wiki]: https://gitlab.com/s3lph/matemat/wikis/home
|
|
@ -7,16 +7,25 @@ from matemat.webserver.util import parse_args
|
||||||
class TestParseRequest(unittest.TestCase):
|
class TestParseRequest(unittest.TestCase):
|
||||||
|
|
||||||
def test_parse_get_root(self):
|
def test_parse_get_root(self):
|
||||||
|
"""
|
||||||
|
Test that the simple root path is parsed correctly ('/' path, no args).
|
||||||
|
"""
|
||||||
path, args = parse_args('/')
|
path, args = parse_args('/')
|
||||||
self.assertEqual('/', path)
|
self.assertEqual('/', path)
|
||||||
self.assertEqual(0, len(args))
|
self.assertEqual(0, len(args))
|
||||||
|
|
||||||
def test_parse_get_no_args(self):
|
def test_parse_get_no_args(self):
|
||||||
|
"""
|
||||||
|
Test that a GET request without arguments is parsed correctly.
|
||||||
|
"""
|
||||||
path, args = parse_args('/index.html')
|
path, args = parse_args('/index.html')
|
||||||
self.assertEqual('/index.html', path)
|
self.assertEqual('/index.html', path)
|
||||||
self.assertEqual(0, len(args))
|
self.assertEqual(0, len(args))
|
||||||
|
|
||||||
def test_parse_get_root_getargs(self):
|
def test_parse_get_root_getargs(self):
|
||||||
|
"""
|
||||||
|
Test that a GET request for '/' with scalar arguments is parsed correctly.
|
||||||
|
"""
|
||||||
path, args = parse_args('/?foo=42&bar=1337&baz=Hello,%20World!')
|
path, args = parse_args('/?foo=42&bar=1337&baz=Hello,%20World!')
|
||||||
self.assertEqual('/', path)
|
self.assertEqual('/', path)
|
||||||
self.assertEqual(3, len(args))
|
self.assertEqual(3, len(args))
|
||||||
|
@ -34,6 +43,9 @@ class TestParseRequest(unittest.TestCase):
|
||||||
self.assertEqual('Hello, World!', args['baz'].get_str())
|
self.assertEqual('Hello, World!', args['baz'].get_str())
|
||||||
|
|
||||||
def test_parse_get_getargs(self):
|
def test_parse_get_getargs(self):
|
||||||
|
"""
|
||||||
|
Test that a GET request for an arbitrary path with scalar arguments is parsed correctly.
|
||||||
|
"""
|
||||||
path, args = parse_args('/abc/def?foo=42&bar=1337&baz=Hello,%20World!')
|
path, args = parse_args('/abc/def?foo=42&bar=1337&baz=Hello,%20World!')
|
||||||
self.assertEqual('/abc/def', path)
|
self.assertEqual('/abc/def', path)
|
||||||
self.assertEqual(3, len(args))
|
self.assertEqual(3, len(args))
|
||||||
|
@ -51,6 +63,9 @@ class TestParseRequest(unittest.TestCase):
|
||||||
self.assertEqual('Hello, World!', args['baz'].get_str())
|
self.assertEqual('Hello, World!', args['baz'].get_str())
|
||||||
|
|
||||||
def test_parse_get_getarray(self):
|
def test_parse_get_getarray(self):
|
||||||
|
"""
|
||||||
|
Test that a GET request with mixed scalar and array arguments is parsed correctly.
|
||||||
|
"""
|
||||||
path, args = parse_args('/abc/def?foo=42&foo=1337&baz=Hello,%20World!')
|
path, args = parse_args('/abc/def?foo=42&foo=1337&baz=Hello,%20World!')
|
||||||
self.assertEqual('/abc/def', path)
|
self.assertEqual('/abc/def', path)
|
||||||
self.assertEqual(2, len(args))
|
self.assertEqual(2, len(args))
|
||||||
|
@ -63,6 +78,9 @@ class TestParseRequest(unittest.TestCase):
|
||||||
self.assertEqual('1337', args['foo'].get_str(1))
|
self.assertEqual('1337', args['foo'].get_str(1))
|
||||||
|
|
||||||
def test_parse_get_zero_arg(self):
|
def test_parse_get_zero_arg(self):
|
||||||
|
"""
|
||||||
|
Test that a GET request with an empty argument is parsed correctly.
|
||||||
|
"""
|
||||||
path, args = parse_args('/abc/def?foo=&bar=42')
|
path, args = parse_args('/abc/def?foo=&bar=42')
|
||||||
self.assertEqual(2, len(args))
|
self.assertEqual(2, len(args))
|
||||||
self.assertIn('foo', args)
|
self.assertIn('foo', args)
|
||||||
|
@ -74,10 +92,16 @@ class TestParseRequest(unittest.TestCase):
|
||||||
self.assertEqual('42', args['bar'].get_str())
|
self.assertEqual('42', args['bar'].get_str())
|
||||||
|
|
||||||
def test_parse_get_urlencoded_encoding_fail(self):
|
def test_parse_get_urlencoded_encoding_fail(self):
|
||||||
|
"""
|
||||||
|
Test that a GET request with non-decodable escape sequences fails.
|
||||||
|
"""
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
parse_args('/?foo=42&bar=%80&baz=Hello,%20World!')
|
parse_args('/?foo=42&bar=%80&baz=Hello,%20World!')
|
||||||
|
|
||||||
def test_parse_post_urlencoded(self):
|
def test_parse_post_urlencoded(self):
|
||||||
|
"""
|
||||||
|
Test that a urlencoded POST request with scalar arguments is parsed correctly.
|
||||||
|
"""
|
||||||
path, args = parse_args('/',
|
path, args = parse_args('/',
|
||||||
postbody=b'foo=42&bar=1337&baz=Hello,%20World!',
|
postbody=b'foo=42&bar=1337&baz=Hello,%20World!',
|
||||||
enctype='application/x-www-form-urlencoded')
|
enctype='application/x-www-form-urlencoded')
|
||||||
|
@ -97,6 +121,9 @@ class TestParseRequest(unittest.TestCase):
|
||||||
self.assertEqual('Hello, World!', args['baz'].get_str())
|
self.assertEqual('Hello, World!', args['baz'].get_str())
|
||||||
|
|
||||||
def test_parse_post_urlencoded_array(self):
|
def test_parse_post_urlencoded_array(self):
|
||||||
|
"""
|
||||||
|
Test that a urlencoded POST request with mixed scalar and array arguments is parsed correctly.
|
||||||
|
"""
|
||||||
path, args = parse_args('/',
|
path, args = parse_args('/',
|
||||||
postbody=b'foo=42&foo=1337&baz=Hello,%20World!',
|
postbody=b'foo=42&foo=1337&baz=Hello,%20World!',
|
||||||
enctype='application/x-www-form-urlencoded')
|
enctype='application/x-www-form-urlencoded')
|
||||||
|
@ -111,6 +138,9 @@ class TestParseRequest(unittest.TestCase):
|
||||||
self.assertEqual('1337', args['foo'].get_str(1))
|
self.assertEqual('1337', args['foo'].get_str(1))
|
||||||
|
|
||||||
def test_parse_post_urlencoded_zero_arg(self):
|
def test_parse_post_urlencoded_zero_arg(self):
|
||||||
|
"""
|
||||||
|
Test that a urlencoded POST request with an empty argument is parsed correctly.
|
||||||
|
"""
|
||||||
path, args = parse_args('/abc/def', postbody=b'foo=&bar=42', enctype='application/x-www-form-urlencoded')
|
path, args = parse_args('/abc/def', postbody=b'foo=&bar=42', enctype='application/x-www-form-urlencoded')
|
||||||
self.assertEqual(2, len(args))
|
self.assertEqual(2, len(args))
|
||||||
self.assertIn('foo', args)
|
self.assertIn('foo', args)
|
||||||
|
@ -122,12 +152,18 @@ class TestParseRequest(unittest.TestCase):
|
||||||
self.assertEqual('42', args['bar'].get_str())
|
self.assertEqual('42', args['bar'].get_str())
|
||||||
|
|
||||||
def test_parse_post_urlencoded_encoding_fail(self):
|
def test_parse_post_urlencoded_encoding_fail(self):
|
||||||
|
"""
|
||||||
|
Test that a urlencoded POST request with a non-decodable escape sequence fails.
|
||||||
|
"""
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
parse_args('/',
|
parse_args('/',
|
||||||
postbody=b'foo=42&bar=%80&baz=Hello,%20World!',
|
postbody=b'foo=42&bar=%80&baz=Hello,%20World!',
|
||||||
enctype='application/x-www-form-urlencoded')
|
enctype='application/x-www-form-urlencoded')
|
||||||
|
|
||||||
def test_parse_post_multipart_no_args(self):
|
def test_parse_post_multipart_no_args(self):
|
||||||
|
"""
|
||||||
|
Test that a multipart POST request with no arguments is parsed correctly.
|
||||||
|
"""
|
||||||
path, args = parse_args('/',
|
path, args = parse_args('/',
|
||||||
postbody=b'--testBoundary1337--\r\n',
|
postbody=b'--testBoundary1337--\r\n',
|
||||||
enctype='multipart/form-data; boundary=testBoundary1337')
|
enctype='multipart/form-data; boundary=testBoundary1337')
|
||||||
|
@ -135,6 +171,9 @@ class TestParseRequest(unittest.TestCase):
|
||||||
self.assertEqual(0, len(args))
|
self.assertEqual(0, len(args))
|
||||||
|
|
||||||
def test_parse_post_multipart(self):
|
def test_parse_post_multipart(self):
|
||||||
|
"""
|
||||||
|
Test that a multipart POST request with scalar arguments is parsed correctly.
|
||||||
|
"""
|
||||||
path, args = parse_args('/',
|
path, args = parse_args('/',
|
||||||
postbody=b'--testBoundary1337\r\n'
|
postbody=b'--testBoundary1337\r\n'
|
||||||
b'Content-Disposition: form-data; name="foo"\r\n'
|
b'Content-Disposition: form-data; name="foo"\r\n'
|
||||||
|
@ -166,6 +205,9 @@ class TestParseRequest(unittest.TestCase):
|
||||||
self.assertEqual('Hello, World!', args['baz'].get_str())
|
self.assertEqual('Hello, World!', args['baz'].get_str())
|
||||||
|
|
||||||
def test_parse_post_multipart_zero_arg(self):
|
def test_parse_post_multipart_zero_arg(self):
|
||||||
|
"""
|
||||||
|
Test that a multipart POST request with an empty argument is parsed correctly.
|
||||||
|
"""
|
||||||
path, args = parse_args('/abc/def',
|
path, args = parse_args('/abc/def',
|
||||||
postbody=b'--testBoundary1337\r\n'
|
postbody=b'--testBoundary1337\r\n'
|
||||||
b'Content-Disposition: form-data; name="foo"\r\n'
|
b'Content-Disposition: form-data; name="foo"\r\n'
|
||||||
|
@ -185,7 +227,39 @@ class TestParseRequest(unittest.TestCase):
|
||||||
self.assertEqual('', args['foo'].get_str())
|
self.assertEqual('', args['foo'].get_str())
|
||||||
self.assertEqual('42', args['bar'].get_str())
|
self.assertEqual('42', args['bar'].get_str())
|
||||||
|
|
||||||
|
def test_parse_post_multipart_no_contenttype(self):
|
||||||
|
"""
|
||||||
|
Test that the Content-Type is set to 'application/octet-stream' if it is absent from the multipart header.
|
||||||
|
"""
|
||||||
|
path, args = parse_args('/',
|
||||||
|
postbody=b'--testBoundary1337\r\n'
|
||||||
|
b'Content-Disposition: form-data; name="foo"\r\n'
|
||||||
|
b'Content-Type: text/plain\r\n\r\n'
|
||||||
|
b'42\r\n'
|
||||||
|
b'--testBoundary1337\r\n'
|
||||||
|
b'Content-Disposition: form-data; name="bar"; filename="bar.bin"\r\n'
|
||||||
|
b'Content-Type: application/octet-stream\r\n\r\n'
|
||||||
|
b'1337\r\n'
|
||||||
|
b'--testBoundary1337\r\n'
|
||||||
|
b'Content-Disposition: form-data; name="baz"\r\n\r\n'
|
||||||
|
b'Hello, World!\r\n'
|
||||||
|
b'--testBoundary1337--\r\n',
|
||||||
|
enctype='multipart/form-data; boundary=testBoundary1337')
|
||||||
|
self.assertEqual('/', path)
|
||||||
|
self.assertEqual(3, len(args))
|
||||||
|
self.assertIn('foo', args)
|
||||||
|
self.assertIn('bar', args)
|
||||||
|
self.assertIn('baz', args)
|
||||||
|
self.assertTrue(args['foo'].is_scalar)
|
||||||
|
self.assertTrue(args['bar'].is_scalar)
|
||||||
|
self.assertTrue(args['baz'].is_scalar)
|
||||||
|
self.assertEqual('application/octet-stream', args['baz'].get_content_type())
|
||||||
|
self.assertEqual('Hello, World!', args['baz'].get_str())
|
||||||
|
|
||||||
def test_parse_post_multipart_broken_boundaries(self):
|
def test_parse_post_multipart_broken_boundaries(self):
|
||||||
|
"""
|
||||||
|
Test that multiple cases with broken multipart boundaries fail.
|
||||||
|
"""
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
# Boundary not defined in Content-Type
|
# Boundary not defined in Content-Type
|
||||||
parse_args('/',
|
parse_args('/',
|
||||||
|
@ -237,22 +311,6 @@ class TestParseRequest(unittest.TestCase):
|
||||||
b'Hello, World!\r\n'
|
b'Hello, World!\r\n'
|
||||||
b'--testBoundary1337\r\n',
|
b'--testBoundary1337\r\n',
|
||||||
enctype='multipart/form-data; boundary=testBoundary1337')
|
enctype='multipart/form-data; boundary=testBoundary1337')
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
# Missing Content-Type header in one part
|
|
||||||
parse_args('/',
|
|
||||||
postbody=b'--testBoundary1337\r\n'
|
|
||||||
b'Content-Disposition: form-data; name="foo"\r\n'
|
|
||||||
b'Content-Type: text/plain\r\n\r\n'
|
|
||||||
b'42\r\n'
|
|
||||||
b'--testBoundary1337\r\n'
|
|
||||||
b'Content-Disposition: form-data; name="bar"; filename="bar.bin"\r\n'
|
|
||||||
b'Content-Type: application/octet-stream\r\n\r\n'
|
|
||||||
b'1337\r\n'
|
|
||||||
b'--testBoundary1337\r\n'
|
|
||||||
b'Content-Disposition: form-data; name="baz"\r\n\r\n'
|
|
||||||
b'Hello, World!\r\n'
|
|
||||||
b'--testBoundary1337--\r\n',
|
|
||||||
enctype='multipart/form-data; boundary=testBoundary1337')
|
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
# Missing Content-Disposition header in one part
|
# Missing Content-Disposition header in one part
|
||||||
parse_args('/',
|
parse_args('/',
|
||||||
|
@ -305,6 +363,9 @@ class TestParseRequest(unittest.TestCase):
|
||||||
enctype='multipart/form-data; boundary=testBoundary1337')
|
enctype='multipart/form-data; boundary=testBoundary1337')
|
||||||
|
|
||||||
def test_get_post_precedence_urlencoded(self):
|
def test_get_post_precedence_urlencoded(self):
|
||||||
|
"""
|
||||||
|
Test the precedence of urlencoded POST arguments over GET arguments.
|
||||||
|
"""
|
||||||
path, args = parse_args('/foo?foo=thisshouldnotbethere&bar=isurvived',
|
path, args = parse_args('/foo?foo=thisshouldnotbethere&bar=isurvived',
|
||||||
postbody=b'foo=42&foo=1337&baz=Hello,%20World!',
|
postbody=b'foo=42&foo=1337&baz=Hello,%20World!',
|
||||||
enctype='application/x-www-form-urlencoded')
|
enctype='application/x-www-form-urlencoded')
|
||||||
|
@ -320,6 +381,9 @@ class TestParseRequest(unittest.TestCase):
|
||||||
self.assertEqual('Hello, World!', args['baz'].get_str())
|
self.assertEqual('Hello, World!', args['baz'].get_str())
|
||||||
|
|
||||||
def test_get_post_precedence_multipart(self):
|
def test_get_post_precedence_multipart(self):
|
||||||
|
"""
|
||||||
|
Test the precedence of multipart POST arguments over GET arguments.
|
||||||
|
"""
|
||||||
path, args = parse_args('/foo?foo=thisshouldnotbethere&bar=isurvived',
|
path, args = parse_args('/foo?foo=thisshouldnotbethere&bar=isurvived',
|
||||||
postbody=b'--testBoundary1337\r\n'
|
postbody=b'--testBoundary1337\r\n'
|
||||||
b'Content-Disposition: form-data; name="foo"\r\n'
|
b'Content-Disposition: form-data; name="foo"\r\n'
|
||||||
|
|
Loading…
Reference in a new issue