without type is a submit button")
def test_button_value_if_submitted(self):
form = self.callFUT()
submit = form['submit']
self.assertEqual(
submit.value_if_submitted(), '',
"submit default value is ''")
button = form['button']
self.assertEqual(
button.value_if_submitted(), '',
"submit default value is ''")
def test_force_select(self):
form = self.callFUT()
form['select'].force_value('notavalue')
form['select'].value__set('value3')
self.assertTrue(
form['select']._forced_value is NoValue,
"Setting a value after having forced a value should keep a forced"
" state")
self.assertEqual(
form['select'].value, 'value3',
"the value should the the one set by value__set")
self.assertEqual(
form['select'].selectedIndex, 2,
"the value index should be the one set by value__set")
def test_form_select(self):
form = self.callFUT()
form.select('select', 'value1')
self.assertEqual(
form['select'].value, 'value1',
"when using form.select, the input selected value should be "
"changed")
def test_get_field_by_index(self):
form = self.callFUT()
self.assertEqual(form['select'],
form.get('select', index=0))
def test_get_unknown_field(self):
form = self.callFUT()
self.assertEqual(form['unknown'].value, '')
form['unknown'].value = '1'
self.assertEqual(form['unknown'].value, '1')
def test_get_non_exist_fields(self):
form = self.callFUT()
self.assertRaises(AssertionError, form.get, 'nonfield')
def test_get_non_exist_fields_with_default(self):
form = self.callFUT()
value = form.get('nonfield', default=1)
self.assertEqual(value, 1)
def test_upload_fields(self):
form = self.callFUT()
fu = webtest.Upload(__file__)
form['file'] = fu
self.assertEqual(form.upload_fields(),
[['file', __file__]])
def test_repr(self):
form = self.callFUT()
self.assertTrue(repr(form).startswith('')
res = app.get('/form.html')
self.assertRaises(TypeError, lambda: res.form)
class TestInput(unittest.TestCase):
def callFUT(self, filename='form_inputs.html'):
dirname = os.path.join(os.path.dirname(__file__), 'html')
app = DebugApp(form=os.path.join(dirname, filename), show_form=True)
return webtest.TestApp(app)
def test_input(self):
app = self.callFUT()
res = app.get('/form.html')
self.assertEqual(res.status_int, 200)
self.assertTrue(res.content_type.startswith('text/html'))
form = res.forms['text_input_form']
self.assertEqual(form['foo'].value, 'bar')
self.assertEqual(form.submit_fields(), [('foo', 'bar')])
form = res.forms['radio_input_form']
self.assertEqual(form['foo'].selectedIndex, 1)
self.assertEqual(form['foo'].value, 'baz')
self.assertEqual(form.submit_fields(), [('foo', 'baz')])
form = res.forms['checkbox_input_form']
self.assertEqual(form['foo'].value, 'bar')
self.assertEqual(form.submit_fields(), [('foo', 'bar')])
form = res.forms['password_input_form']
self.assertEqual(form['foo'].value, 'bar')
self.assertEqual(form.submit_fields(), [('foo', 'bar')])
def test_force_radio_input(self):
app = self.callFUT()
res = app.get('/form.html')
form = res.forms['radio_input_form']
form['foo'].force_value('fido')
self.assertEqual(form['foo'].value, 'fido')
self.assertEqual(form.submit_fields(), [('foo', 'fido')])
def test_radio_input_order(self):
app = self.callFUT()
res = app.get('/form.html')
self.assertEqual(res.status_int, 200)
self.assertTrue(res.content_type.startswith('text/html'))
form = res.forms['complex_radio_input_form']
form['foo'].value = 'true'
self.assertEqual(form['foo'].value, 'true')
self.assertEqual(form['foo'].selectedIndex, 0)
self.assertEqual(form.submit_fields(), [
('__start__', 'item:mapping'),
('foo', 'true'),
('__end__', 'item:mapping'),
('__start__', 'item:mapping'),
('__end__', 'item:mapping')])
res = app.get('/form.html')
form = res.forms['complex_radio_input_form']
self.assertEqual(form['foo'].value, 'true')
self.assertEqual(form['foo'].selectedIndex, 1)
self.assertEqual(form.submit_fields(), [
('__start__', 'item:mapping'),
('__end__', 'item:mapping'),
('__start__', 'item:mapping'),
('foo', 'true'),
('__end__', 'item:mapping')])
def test_input_unicode(self):
app = self.callFUT('form_unicode_inputs.html')
res = app.get('/form.html')
self.assertEqual(res.status_int, 200)
self.assertTrue(res.content_type.startswith('text/html'))
self.assertEqual(res.charset.lower(), 'utf-8')
form = res.forms['text_input_form']
self.assertEqual(form['foo'].value, u('Хармс'))
self.assertEqual(form.submit_fields(), [('foo', u('Хармс'))])
form = res.forms['radio_input_form']
self.assertEqual(form['foo'].selectedIndex, 1)
self.assertEqual(form['foo'].value, u('Блок'))
self.assertEqual(form.submit_fields(), [('foo', u('Блок'))])
form = res.forms['checkbox_input_form']
self.assertEqual(form['foo'].value, u('Хармс'))
self.assertEqual(form.submit_fields(), [('foo', u('Хармс'))])
form = res.forms['password_input_form']
self.assertEqual(form['foo'].value, u('Хармс'))
self.assertEqual(form.submit_fields(), [('foo', u('Хармс'))])
def test_input_no_default(self):
app = self.callFUT('form_inputs_with_defaults.html')
res = app.get('/form.html')
self.assertEqual(res.status_int, 200)
self.assertTrue(res.content_type.startswith('text/html'))
form = res.forms['text_input_form']
self.assertEqual(form['foo'].value, '')
self.assertEqual(form.submit_fields(), [('foo', '')])
form = res.forms['radio_input_form']
self.assertTrue(form['foo'].value is None)
self.assertEqual(form.submit_fields(), [])
form = res.forms['checkbox_input_form']
self.assertTrue(form['foo'].value is None)
self.assertEqual(form.submit_fields(), [])
form = res.forms['password_input_form']
self.assertEqual(form['foo'].value, '')
self.assertEqual(form.submit_fields(), [('foo', '')])
def test_textarea_entities(self):
app = self.callFUT()
res = app.get('/form.html')
form = res.forms.get("textarea_input_form")
self.assertEqual(form.get("textarea").value, "'foo&bar'")
self.assertEqual(form.submit_fields(), [('textarea', "'foo&bar'")])
def test_textarea_emptyfirstline(self):
app = self.callFUT()
res = app.get('/form.html')
form = res.forms.get("textarea_emptyline_form")
self.assertEqual(form.get("textarea").value, "aaa")
self.assertEqual(form.submit_fields(), [('textarea', "aaa")])
class TestFormLint(unittest.TestCase):
def test_form_lint(self):
form = webtest.Form(None, '''''')
self.assertRaises(AttributeError, form.lint)
form = webtest.Form(None, '''''')
self.assertRaises(AttributeError, form.lint)
form = webtest.Form(None, '''''')
form.lint()
form = webtest.Form(None, '''''')
form.lint()
def select_app(environ, start_response):
req = Request(environ)
status = b"200 OK"
if req.method == "GET":
body = to_bytes("""
form page
""")
else:
select_type = req.POST.get("button")
if select_type == "single":
selection = req.POST.get("single")
elif select_type == "multiple":
selection = ", ".join(req.POST.getall("multiple"))
body = to_bytes("""
display page
You submitted the %(select_type)s
You selected %(selection)s
""" % dict(selection=selection, select_type=select_type))
headers = [
('Content-Type', 'text/html; charset=utf-8'),
('Content-Length', str(len(body)))]
# PEP 3333 requires native strings:
headers = [(str(k), str(v)) for k, v in headers]
start_response(status, headers)
return [body]
def select_app_without_values(environ, start_response):
req = Request(environ)
status = b"200 OK"
if req.method == "GET":
body = to_bytes("""
form page
""")
else:
select_type = req.POST.get("button")
if select_type == "single":
selection = req.POST.get("single")
elif select_type == "multiple":
selection = ", ".join(req.POST.getall("multiple"))
body = to_bytes("""
display page
You submitted the %(select_type)s
You selected %(selection)s
""" % dict(selection=selection, select_type=select_type))
headers = [
('Content-Type', 'text/html; charset=utf-8'),
('Content-Length', str(len(body)))]
# PEP 3333 requires native strings:
headers = [(str(k), str(v)) for k, v in headers]
start_response(status, headers)
return [body]
def select_app_without_default(environ, start_response):
req = Request(environ)
status = b"200 OK"
if req.method == "GET":
body = to_bytes("""
form page
""")
else:
select_type = req.POST.get("button")
if select_type == "single":
selection = req.POST.get("single")
elif select_type == "multiple":
selection = ", ".join(req.POST.getall("multiple"))
body = to_bytes("""
display page
You submitted the %(select_type)s
You selected %(selection)s
""" % dict(selection=selection, select_type=select_type))
headers = [
('Content-Type', 'text/html; charset=utf-8'),
('Content-Length', str(len(body)))]
# PEP 3333 requires native strings:
headers = [(str(k), str(v)) for k, v in headers]
start_response(status, headers)
return [body]
def select_app_unicode(environ, start_response):
req = Request(environ)
status = b"200 OK"
if req.method == "GET":
body = u("""
form page
""").encode('utf8')
else:
select_type = req.POST.get("button")
if select_type == "single":
selection = req.POST.get("single")
elif select_type == "multiple":
selection = ", ".join(req.POST.getall("multiple"))
body = (u("""
display page
You submitted the %(select_type)s
You selected %(selection)s
""") % dict(selection=selection, select_type=select_type)).encode('utf8')
headers = [
('Content-Type', 'text/html; charset=utf-8'),
('Content-Length', str(len(body)))]
# PEP 3333 requires native strings:
headers = [(str(k), str(v)) for k, v in headers]
start_response(status, headers)
if not isinstance(body, bytes):
raise AssertionError('Body is not %s' % bytes)
return [body]
class TestSelect(unittest.TestCase):
def test_unicode_select(self):
app = webtest.TestApp(select_app_unicode)
res = app.get('/')
single_form = res.forms["single_select_form"]
self.assertEqual(single_form["single"].value, u("МСК"))
display = single_form.submit("button")
self.assertIn(u("You selected МСК
"), display, display)
res = app.get('/')
single_form = res.forms["single_select_form"]
self.assertEqual(single_form["single"].value, u("МСК"))
single_form.set("single", u("СПБ"))
self.assertEqual(single_form["single"].value, u("СПБ"))
display = single_form.submit("button")
self.assertIn(u("You selected СПБ
"), display, display)
def test_single_select(self):
app = webtest.TestApp(select_app)
res = app.get('/')
self.assertEqual(res.status_int, 200)
self.assertEqual(res.headers['content-type'],
'text/html; charset=utf-8')
self.assertEqual(res.content_type, 'text/html')
single_form = res.forms["single_select_form"]
self.assertEqual(single_form["single"].value, "5")
display = single_form.submit("button")
self.assertIn("You selected 5
", display, display)
res = app.get('/')
self.assertEqual(res.status_int, 200)
self.assertEqual(res.headers['content-type'],
'text/html; charset=utf-8')
self.assertEqual(res.content_type, 'text/html')
single_form = res.forms["single_select_form"]
self.assertEqual(single_form["single"].value, "5")
single_form.set("single", "6")
self.assertEqual(single_form["single"].value, "6")
display = single_form.submit("button")
self.assertIn("You selected 6
", display, display)
res = app.get('/')
single_form = res.forms["single_select_form"]
self.assertRaises(ValueError, single_form.select, "single", "5",
text="Five")
self.assertRaises(ValueError, single_form.select, "single",
text="Three")
single_form.select("single", text="Seven")
self.assertEqual(single_form["single"].value, "7")
display = single_form.submit("button")
self.assertIn("You selected 7
", display, display)
def test_single_select_forced_value(self):
app = webtest.TestApp(select_app)
res = app.get('/')
self.assertEqual(res.status_int, 200)
self.assertEqual(res.headers['content-type'],
'text/html; charset=utf-8')
self.assertEqual(res.content_type, 'text/html')
single_form = res.forms["single_select_form"]
self.assertEqual(single_form["single"].value, "5")
self.assertRaises(ValueError, single_form.set, "single", "984")
single_form["single"].force_value("984")
self.assertEqual(single_form["single"].value, "984")
display = single_form.submit("button")
self.assertIn("You selected 984
", display, display)
def test_single_select_no_default(self):
app = webtest.TestApp(select_app_without_default)
res = app.get('/')
self.assertEqual(res.status_int, 200)
self.assertEqual(res.headers['content-type'],
'text/html; charset=utf-8')
self.assertEqual(res.content_type, 'text/html')
single_form = res.forms["single_select_form"]
self.assertEqual(single_form["single"].value, "4")
display = single_form.submit("button")
self.assertIn("You selected 4
", display, display)
res = app.get('/')
self.assertEqual(res.status_int, 200)
self.assertEqual(res.headers['content-type'],
'text/html; charset=utf-8')
self.assertEqual(res.content_type, 'text/html')
single_form = res.forms["single_select_form"]
self.assertEqual(single_form["single"].value, "4")
single_form.set("single", 6)
self.assertEqual(single_form["single"].value, "6")
display = single_form.submit("button")
self.assertIn("You selected 6
", display, display)
def test_multiple_select(self):
app = webtest.TestApp(select_app)
res = app.get('/')
self.assertEqual(res.status_int, 200)
self.assertEqual(res.headers['content-type'],
'text/html; charset=utf-8')
self.assertEqual(res.content_type, 'text/html')
multiple_form = res.forms["multiple_select_form"]
self.assertEqual(multiple_form["multiple"].value, ['8', '11'],
multiple_form["multiple"].value)
display = multiple_form.submit("button")
self.assertIn("You selected 8, 11
", display, display)
res = app.get('/')
self.assertEqual(res.status_int, 200)
self.assertEqual(res.headers['content-type'],
'text/html; charset=utf-8')
self.assertEqual(res.content_type, 'text/html')
multiple_form = res.forms["multiple_select_form"]
self.assertEqual(multiple_form["multiple"].value, ["8", "11"],
multiple_form["multiple"].value)
multiple_form.set("multiple", ["9"])
self.assertEqual(multiple_form["multiple"].value, ["9"],
multiple_form["multiple"].value)
display = multiple_form.submit("button")
self.assertIn("You selected 9
", display, display)
res = app.get('/')
multiple_form = res.forms["multiple_select_form"]
self.assertRaises(ValueError, multiple_form.select_multiple,
"multiple",
["8", "10"], texts=["Eight", "Ten"])
self.assertRaises(ValueError, multiple_form.select_multiple,
"multiple", texts=["Twelve"])
multiple_form.select_multiple("multiple",
texts=["Eight", "Nine", "Ten"])
display = multiple_form.submit("button")
self.assertIn("You selected 8, 9, 10
", display, display)
def test_multiple_select_forced_values(self):
app = webtest.TestApp(select_app)
res = app.get('/')
self.assertEqual(res.status_int, 200)
self.assertEqual(res.headers['content-type'],
'text/html; charset=utf-8')
self.assertEqual(res.content_type, 'text/html')
multiple_form = res.forms["multiple_select_form"]
self.assertEqual(multiple_form["multiple"].value, ["8", "11"],
multiple_form["multiple"].value)
self.assertRaises(ValueError, multiple_form.set,
"multiple", ["24", "88"])
multiple_form["multiple"].force_value(["24", "88"])
self.assertEqual(multiple_form["multiple"].value, ["24", "88"],
multiple_form["multiple"].value)
display = multiple_form.submit("button")
self.assertIn("You selected 24, 88
", display, display)
def test_multiple_select_no_default(self):
app = webtest.TestApp(select_app_without_default)
res = app.get('/')
self.assertEqual(res.status_int, 200)
self.assertEqual(res.headers['content-type'],
'text/html; charset=utf-8')
self.assertEqual(res.content_type, 'text/html')
multiple_form = res.forms["multiple_select_form"]
self.assertTrue(multiple_form["multiple"].value is None,
repr(multiple_form["multiple"].value))
display = multiple_form.submit("button")
self.assertIn("You selected
", display, display)
res = app.get('/')
self.assertEqual(res.status_int, 200)
self.assertEqual(res.headers['content-type'],
'text/html; charset=utf-8')
self.assertEqual(res.content_type, 'text/html')
multiple_form = res.forms["multiple_select_form"]
self.assertTrue(multiple_form["multiple"].value is None,
multiple_form["multiple"].value)
multiple_form.set("multiple", ["9"])
self.assertEqual(multiple_form["multiple"].value, ["9"],
multiple_form["multiple"].value)
display = multiple_form.submit("button")
self.assertIn("You selected 9
", display, display)
def test_select_no_value(self):
app = webtest.TestApp(select_app_without_values)
res = app.get('/')
self.assertEqual(res.status_int, 200)
self.assertEqual(res.headers['content-type'],
'text/html; charset=utf-8')
self.assertEqual(res.content_type, 'text/html')
single_form = res.forms["single_select_form"]
self.assertEqual(single_form["single"].value, "Four")
display = single_form.submit("button")
self.assertIn("You selected Four
", display, display)
res = app.get('/')
self.assertEqual(res.status_int, 200)
self.assertEqual(res.headers['content-type'],
'text/html; charset=utf-8')
self.assertEqual(res.content_type, 'text/html')
single_form = res.forms["single_select_form"]
self.assertEqual(single_form["single"].value, "Four")
single_form.set("single", "Six")
self.assertEqual(single_form["single"].value, "Six")
display = single_form.submit("button")
self.assertIn("You selected Six
", display, display)
def test_multiple_select_no_value(self):
app = webtest.TestApp(select_app_without_values)
res = app.get('/')
self.assertEqual(res.status_int, 200)
self.assertEqual(res.headers['content-type'],
'text/html; charset=utf-8')
self.assertEqual(res.content_type, 'text/html')
multiple_form = res.forms["multiple_select_form"]
self.assertEqual(multiple_form["multiple"].value, ["Nine", "Eleven"])
display = multiple_form.submit("button")
self.assertIn("You selected Nine, Eleven
", display, display)
res = app.get('/')
self.assertEqual(res.status_int, 200)
self.assertEqual(res.headers['content-type'],
'text/html; charset=utf-8')
self.assertEqual(res.content_type, 'text/html')
multiple_form = res.forms["multiple_select_form"]
self.assertEqual(multiple_form["multiple"].value, ["Nine", "Eleven"])
multiple_form.set("multiple", ["Nine", "Ten"])
self.assertEqual(multiple_form["multiple"].value, ["Nine", "Ten"])
display = multiple_form.submit("button")
self.assertIn("You selected Nine, Ten
", display, display)
def test_multiple_select_reset_value(self):
app = webtest.TestApp(select_app_without_values)
res = app.get('/')
self.assertEqual(res.status_int, 200)
self.assertEqual(res.headers['content-type'],
'text/html; charset=utf-8')
self.assertEqual(res.content_type, 'text/html')
multiple_form = res.forms["multiple_select_form"]
self.assertEqual(multiple_form["multiple"].value, ["Nine", "Eleven"])
# reset with value
multiple_form["multiple"].value = []
self.assertIsNone(multiple_form["multiple"].value)
# re-set a value
multiple_form["multiple"].value = ['Nine']
assert multiple_form["multiple"].value == ['Nine']
# reset with force_value
multiple_form["multiple"].force_value(None)
self.assertIsNone(multiple_form["multiple"].value)
display = multiple_form.submit("button")
self.assertIn("You selected
", display, display)
class SingleUploadFileApp:
body = b"""
form page
"""
def __call__(self, environ, start_response):
req = Request(environ)
status = b"200 OK"
if req.method == "GET":
body = self.body
else:
body = b"""
display page
""" + self.get_files_page(req) + b"""
"""
headers = [
('Content-Type', 'text/html; charset=utf-8'),
('Content-Length', str(len(body)))]
# PEP 3333 requires native strings:
headers = [(str(k), str(v)) for k, v in headers]
start_response(status, headers)
assert(isinstance(body, bytes))
return [body]
def get_files_page(self, req):
file_parts = []
uploaded_files = [(k, v) for k, v in req.POST.items() if 'file' in k]
for name, uploaded_file in uploaded_files:
if isinstance(uploaded_file, cgi.FieldStorage):
filename = to_bytes(uploaded_file.filename)
value = to_bytes(uploaded_file.value, 'ascii')
content_type = to_bytes(uploaded_file.type, 'ascii')
else:
filename = value = content_type = b''
file_parts.append(b"""
You selected '""" + filename + b"""'
with contents: '""" + value + b"""'
with content type: '""" + content_type + b"""'
""")
return b''.join(file_parts)
class UploadBinaryApp(SingleUploadFileApp):
def get_files_page(self, req):
uploaded_files = [(k, v) for k, v in req.POST.items() if 'file' in k]
data = uploaded_files[0][1].value
data = struct.unpack(b'255h', data[:510])
return b','.join([to_bytes(str(i)) for i in data])
class SeveralSingleFileApp(SingleUploadFileApp):
body = b"""
form page
"""
class MultipleUploadFileApp(SingleUploadFileApp):
body = b"""
form page
"""
class TestFileUpload(unittest.TestCase):
def assertFile(self, name, contents, display, content_type=None):
if isinstance(name, bytes):
text_name = name.decode('ascii')
else:
text_name = name
self.assertIn("You selected '" + text_name + "'
",
display, display)
if isinstance(contents, bytes):
text_contents = contents.decode('ascii')
else:
text_contents = contents
self.assertIn("with contents: '" + text_contents + "'
",
display, display)
if content_type:
self.assertIn("with content type: '" + content_type + "'
",
display, display)
def test_no_uploads_error(self):
app = webtest.TestApp(SingleUploadFileApp())
app.get('/').forms["file_upload_form"].upload_fields()
def test_upload_without_file(self):
app = webtest.TestApp(SingleUploadFileApp())
upload_form = app.get('/').forms["file_upload_form"]
upload_form.submit()
def test_file_upload_with_filename_only(self):
uploaded_file_name = os.path.join(os.path.dirname(__file__),
"__init__.py")
uploaded_file_contents = open(uploaded_file_name).read()
uploaded_file_contents = to_bytes(uploaded_file_contents)
app = webtest.TestApp(SingleUploadFileApp())
res = app.get('/')
self.assertEqual(res.status_int, 200)
self.assertEqual(res.headers['content-type'],
'text/html; charset=utf-8')
self.assertEqual(res.content_type, 'text/html')
self.assertEqual(res.charset, 'utf-8')
single_form = res.forms["file_upload_form"]
single_form.set("file-field", (uploaded_file_name,))
display = single_form.submit("button")
self.assertFile(uploaded_file_name, uploaded_file_contents, display)
def test_file_upload_with_filename_and_contents(self):
uploaded_file_name = os.path.join(os.path.dirname(__file__),
"__init__.py")
uploaded_file_contents = open(uploaded_file_name).read()
uploaded_file_contents = to_bytes(uploaded_file_contents)
app = webtest.TestApp(SingleUploadFileApp())
res = app.get('/')
self.assertEqual(res.status_int, 200)
self.assertEqual(res.headers['content-type'],
'text/html; charset=utf-8')
self.assertEqual(res.content_type, 'text/html')
single_form = res.forms["file_upload_form"]
single_form.set("file-field",
(uploaded_file_name, uploaded_file_contents))
display = single_form.submit("button")
self.assertFile(uploaded_file_name, uploaded_file_contents, display)
def test_file_upload_with_content_type(self):
uploaded_file_name = os.path.join(os.path.dirname(__file__),
"__init__.py")
with open(uploaded_file_name, 'rb') as f:
uploaded_file_contents = f.read()
app = webtest.TestApp(SingleUploadFileApp())
res = app.get('/')
single_form = res.forms["file_upload_form"]
single_form["file-field"].value = Upload(uploaded_file_name,
uploaded_file_contents,
'text/x-custom-type')
display = single_form.submit("button")
self.assertFile(uploaded_file_name, uploaded_file_contents, display,
content_type='text/x-custom-type')
def test_file_upload_binary(self):
binary_data = struct.pack('255h', *range(0, 255))
app = webtest.TestApp(UploadBinaryApp())
res = app.get('/')
single_form = res.forms["file_upload_form"]
single_form.set("file-field", ('my_file.dat', binary_data))
display = single_form.submit("button")
self.assertIn(','.join([str(n) for n in range(0, 255)]), display)
def test_several_file_uploads_with_filename_and_contents(self):
uploaded_file1_name = os.path.join(os.path.dirname(__file__),
"__init__.py")
uploaded_file1_contents = open(uploaded_file1_name).read()
uploaded_file1_contents = to_bytes(uploaded_file1_contents)
uploaded_file2_name = __file__
uploaded_file2_name = os.path.join(os.path.dirname(__file__), 'html',
"404.html")
uploaded_file2_contents = open(uploaded_file2_name).read()
uploaded_file2_contents = to_bytes(uploaded_file2_contents)
app = webtest.TestApp(SeveralSingleFileApp())
res = app.get('/')
self.assertEqual(res.status_int, 200)
self.assertEqual(res.headers['content-type'],
'text/html; charset=utf-8')
self.assertEqual(res.content_type, 'text/html')
single_form = res.forms["file_upload_form"]
single_form.set("file-field-1",
(uploaded_file1_name, uploaded_file1_contents))
single_form.set("file-field-2",
(uploaded_file2_name, uploaded_file2_contents))
display = single_form.submit("button")
self.assertFile(uploaded_file1_name, uploaded_file1_contents, display)
self.assertFile(uploaded_file1_name, uploaded_file1_contents, display)
def test_post_int(self):
binary_data = struct.pack('255h', *range(0, 255))
app = webtest.TestApp(SingleUploadFileApp())
res = app.get('/')
single_form = res.forms["file_upload_form"]
single_form.set("file-field", ('my_file.dat', binary_data))
single_form.set("int-field", 100)
# just check it does not raise
single_form.submit("button")
def test_invalid_types(self):
binary_data = struct.pack('255h', *range(0, 255))
app = webtest.TestApp(SingleUploadFileApp())
res = app.get('/')
single_form = res.forms["file_upload_form"]
single_form.set("file-field", ('my_file.dat', binary_data))
single_form.set("int-field", SingleUploadFileApp())
self.assertRaises(ValueError, single_form.submit, "button")
def test_upload_invalid_content(self):
app = webtest.TestApp(SingleUploadFileApp())
res = app.get('/')
single_form = res.forms["file_upload_form"]
single_form.set("file-field", ('my_file.dat', 1))
try:
single_form.submit("button")
except ValueError:
e = sys.exc_info()[1]
self.assertEqual(
str(e),
u('File content must be %s not %s' % (bytes, int))
)
def test_invalid_uploadfiles(self):
app = webtest.TestApp(SingleUploadFileApp())
self.assertRaises(ValueError, app.post, '/', upload_files=[()])
self.assertRaises(
ValueError,
app.post, '/',
upload_files=[('name', 'filename', 'content', 'extra')]
)
def test_goto_upload_files(self):
app = webtest.TestApp(SingleUploadFileApp())
resp = app.get('/')
resp = resp.goto(
'/',
method='post',
upload_files=[('file', 'filename', b'content')]
)
resp.mustcontain("You selected 'filename'
",
"with contents: 'content'
")
def test_post_upload_files(self):
app = webtest.TestApp(SingleUploadFileApp())
resp = app.post(
'/',
upload_files=[('file', 'filename', b'content')]
)
resp.mustcontain("You selected 'filename'
",
"with contents: 'content'
")
def test_post_upload_empty_files(self):
app = webtest.TestApp(SingleUploadFileApp())
resp = app.post(
'/',
upload_files=[('file', 'filename', b'')]
)
resp.mustcontain("You selected 'filename'
",
"with contents: ''
")
resp = app.get('/')
form = resp.form
form['file-field'] = Upload('filename', b'', 'text/plain')
resp = form.submit()
resp.mustcontain("You selected 'filename'
",
"with contents: ''
")
def test_multiple_file(self):
uploaded_file_name_1 = os.path.join(os.path.dirname(__file__),
"__init__.py")
uploaded_file_contents_1 = open(uploaded_file_name_1).read()
uploaded_file_contents_1 = to_bytes(uploaded_file_contents_1)
uploaded_file_name_2 = os.path.join(os.path.dirname(__file__),
os.path.basename(__file__))
uploaded_file_contents_2 = open(uploaded_file_name_1).read()
uploaded_file_contents_2 = to_bytes(uploaded_file_contents_2)
app = webtest.TestApp(MultipleUploadFileApp())
res = app.get('/')
self.assertEqual(res.status_int, 200)
self.assertEqual(res.headers['content-type'],
'text/html; charset=utf-8')
self.assertEqual(res.content_type, 'text/html')
single_form = res.forms["file_upload_form"]
single_form.set("files-field", [
(uploaded_file_name_1, uploaded_file_contents_1),
(uploaded_file_name_2, uploaded_file_contents_2),
])
display = single_form.submit("button")
self.assertFile(uploaded_file_name_1, uploaded_file_contents_1, display)
self.assertFile(uploaded_file_name_2, uploaded_file_contents_2, display)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1738577344.0
webtest-3.0.4/tests/test_http.py 0000644 0001750 0001750 00000004114 14750112700 015742 0 ustar 00gawel gawel from tests.compat import unittest
from webob import Request
from webtest.debugapp import debug_app
from webtest import http
class TestServer(unittest.TestCase):
def setUp(self):
self.s = http.StopableWSGIServer.create(debug_app)
def test_server(self):
s = self.s
s.wait()
self.assertEqual(200,
http.check_server(s.adj.host, s.adj.port,
'/__application__'))
self.assertEqual(200,
http.check_server(s.adj.host, s.adj.port,
'/__file__?__file__=' + __file__))
self.assertEqual(404,
http.check_server(s.adj.host, s.adj.port,
'/__file__?__file__=XXX'))
self.assertEqual(304,
http.check_server(s.adj.host, s.adj.port,
'/?status=304'))
def test_wsgi_wrapper(self):
s = self.s
s.wait()
req = Request.blank('/__application__')
resp = req.get_response(s.wrapper)
self.assertEqual(resp.status_int, 200)
req = Request.blank('/__file__?__file__=' + __file__)
resp = req.get_response(s.wrapper)
self.assertEqual(resp.status_int, 200)
req = Request.blank('/__file__?__file__=XXX')
resp = req.get_response(s.wrapper)
self.assertEqual(resp.status_int, 404)
req = Request.blank('/?status=304')
resp = req.get_response(s.wrapper)
self.assertEqual(resp.status_int, 304)
def tearDown(self):
self.s.shutdown()
class TestBrokenServer(unittest.TestCase):
def test_shutdown_non_running(self):
host, port = http.get_free_port()
s = http.StopableWSGIServer(debug_app, host=host, port=port)
self.assertFalse(s.wait(retries=-1))
self.assertTrue(s.shutdown())
class TestClient(unittest.TestCase):
def test_no_server(self):
host, port = http.get_free_port()
self.assertEqual(0, http.check_server(host, port, retries=2))
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1738577344.0
webtest-3.0.4/tests/test_lint.py 0000644 0001750 0001750 00000025352 14750112700 015740 0 ustar 00gawel gawel import sys
from tests.compat import unittest
from webob import Request, Response
import warnings
from unittest import mock
from io import StringIO
from webtest import TestApp
from webtest.compat import to_bytes
from webtest.lint import check_headers
from webtest.lint import check_content_type
from webtest.lint import check_environ
from webtest.lint import IteratorWrapper
from webtest.lint import WriteWrapper
from webtest.lint import ErrorWrapper
from webtest.lint import InputWrapper
from webtest.lint import to_string
from webtest.lint import middleware
from webtest.lint import _assert_latin1_str
from io import BytesIO
def application(environ, start_response):
req = Request(environ)
resp = Response()
env_input = environ['wsgi.input']
len_body = len(req.body)
env_input.input.seek(0)
if req.path_info == '/read':
resp.body = env_input.read(len_body)
elif req.path_info == '/read_line':
resp.body = env_input.readline(len_body)
elif req.path_info == '/read_lines':
resp.body = b'-'.join(env_input.readlines(len_body))
elif req.path_info == '/close':
resp.body = env_input.close()
return resp(environ, start_response)
class TestLatin1Assertion(unittest.TestCase):
def test_valid_type(self):
value = "useful-inførmation-5"
assert value == _assert_latin1_str(value, "fail")
def test_invalid_type(self):
value = b"useful-information-5"
self.assertRaises(AssertionError, _assert_latin1_str, value, "fail")
class TestToString(unittest.TestCase):
def test_to_string(self):
self.assertEqual(to_string('foo'), 'foo')
self.assertEqual(to_string(b'foo'), 'foo')
class TestMiddleware(unittest.TestCase):
@unittest.skipIf(sys.flags.optimize > 0, "skip assert tests if optimize is enabled")
def test_lint_too_few_args(self):
linter = middleware(application)
with self.assertRaisesRegex(AssertionError, "Two arguments required"):
linter()
with self.assertRaisesRegex(AssertionError, "Two arguments required"):
linter({})
@unittest.skipIf(sys.flags.optimize > 0, "skip assert tests if optimize is enabled")
def test_lint_no_keyword_args(self):
linter = middleware(application)
with self.assertRaisesRegex(AssertionError, "No keyword arguments "
"allowed"):
linter({}, 'foo', baz='baz')
# TODO: test start_response_wrapper
@mock.patch.multiple('webtest.lint',
check_environ=lambda x: True, # don't block too early
InputWrapper=lambda x: True)
def test_lint_iterator_returned(self):
linter = middleware(lambda x, y: None) # None is not an iterator
msg = "The application must return an iterator, if only an empty list"
with self.assertRaisesRegex(AssertionError, msg):
linter({'wsgi.input': 'foo', 'wsgi.errors': 'foo'}, 'foo')
class TestInputWrapper(unittest.TestCase):
def test_read(self):
app = TestApp(application)
resp = app.post('/read', 'hello')
self.assertEqual(resp.body, b'hello')
def test_readline(self):
app = TestApp(application)
resp = app.post('/read_line', 'hello\n')
self.assertEqual(resp.body, b'hello\n')
def test_readlines(self):
app = TestApp(application)
resp = app.post('/read_lines', 'hello\nt\n')
self.assertEqual(resp.body, b'hello\n-t\n')
def test_close(self):
input_wrapper = InputWrapper(None)
self.assertRaises(AssertionError, input_wrapper.close)
def test_iter(self):
data = to_bytes("A line\nAnother line\nA final line\n")
input_wrapper = InputWrapper(BytesIO(data))
self.assertEqual(to_bytes("").join(input_wrapper), data, '')
def test_seek(self):
data = to_bytes("A line\nAnother line\nA final line\n")
input_wrapper = InputWrapper(BytesIO(data))
input_wrapper.seek(0)
self.assertEqual(to_bytes("").join(input_wrapper), data, '')
class TestMiddleware2(unittest.TestCase):
def test_exc_info(self):
def application_exc_info(environ, start_response):
body = to_bytes('body stuff')
headers = [
('Content-Type', 'text/plain; charset=utf-8'),
('Content-Length', str(len(body)))]
# PEP 3333 requires native strings:
headers = [(str(k), str(v)) for k, v in headers]
start_response(to_bytes('200 OK'), headers, ('stuff',))
return [body]
app = TestApp(application_exc_info)
app.get('/')
# don't know what to assert here... a bit cheating, just covers code
class TestCheckContentType(unittest.TestCase):
def test_no_content(self):
status = "204 No Content"
headers = [
('Content-Type', 'text/plain; charset=utf-8'),
('Content-Length', '4')
]
self.assertRaises(AssertionError, check_content_type, status, headers)
def test_no_content_type(self):
status = "200 OK"
headers = [
('Content-Length', '4')
]
self.assertRaises(AssertionError, check_content_type, status, headers)
class TestCheckHeaders(unittest.TestCase):
def test_header_bytes_name(self):
headers = [(b'X-Price', '100')]
self.assertRaises(AssertionError, check_headers, headers)
def test_header_bytes_value(self):
headers = [('X-Price', b'100')]
self.assertRaises(AssertionError, check_headers, headers)
def test_header_non_latin1_value(self):
headers = [('X-Price', '100€')]
self.assertRaises(AssertionError, check_headers, headers)
def test_header_non_latin1_name(self):
headers = [('X-€', 'foo')]
self.assertRaises(AssertionError, check_headers, headers)
class TestCheckEnviron(unittest.TestCase):
def test_no_query_string(self):
environ = {
'REQUEST_METHOD': 'GET',
'SERVER_NAME': 'localhost',
'SERVER_PORT': '80',
'wsgi.version': (1, 0, 1),
'wsgi.input': StringIO('test'),
'wsgi.errors': StringIO(),
'wsgi.multithread': None,
'wsgi.multiprocess': None,
'wsgi.run_once': None,
'wsgi.url_scheme': 'http',
'PATH_INFO': '/',
}
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
check_environ(environ)
self.assertEqual(len(w), 1, "We should have only one warning")
self.assertTrue(
"QUERY_STRING" in str(w[-1].message),
"The warning message should say something about QUERY_STRING")
def test_no_valid_request(self):
environ = {
'REQUEST_METHOD': 'PROPFIND',
'SERVER_NAME': 'localhost',
'SERVER_PORT': '80',
'wsgi.version': (1, 0, 1),
'wsgi.input': StringIO('test'),
'wsgi.errors': StringIO(),
'wsgi.multithread': None,
'wsgi.multiprocess': None,
'wsgi.run_once': None,
'wsgi.url_scheme': 'http',
'PATH_INFO': '/',
'QUERY_STRING': '',
}
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
check_environ(environ)
self.assertEqual(len(w), 1, "We should have only one warning")
self.assertTrue(
"REQUEST_METHOD" in str(w[-1].message),
"The warning message should say something "
"about REQUEST_METHOD")
def test_handles_native_strings_in_variables(self):
path = '/umläut'
environ = {
'REQUEST_METHOD': 'GET',
'SERVER_NAME': 'localhost',
'SERVER_PORT': '80',
'wsgi.version': (1, 0, 1),
'wsgi.input': StringIO('test'),
'wsgi.errors': StringIO(),
'wsgi.multithread': None,
'wsgi.multiprocess': None,
'wsgi.run_once': None,
'wsgi.url_scheme': 'http',
'PATH_INFO': path,
'QUERY_STRING': '',
}
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
check_environ(environ)
self.assertEqual(0, len(w), "We should have no warning")
class TestIteratorWrapper(unittest.TestCase):
def test_close(self):
class MockIterator:
def __init__(self):
self.closed = False
def __iter__(self):
return self
def __next__(self):
return None
next = __next__
def close(self):
self.closed = True
mock = MockIterator()
wrapper = IteratorWrapper(mock, None)
wrapper.close()
self.assertTrue(mock.closed, "Original iterator has not been closed")
class TestWriteWrapper(unittest.TestCase):
@unittest.skipIf(sys.flags.optimize > 0, "skip assert tests if optimize is enabled")
def test_wrong_type(self):
write_wrapper = WriteWrapper(None)
self.assertRaises(AssertionError, write_wrapper, 'not a binary')
def test_normal(self):
class MockWriter:
def __init__(self):
self.written = []
def __call__(self, s):
self.written.append(s)
data = to_bytes('foo')
mock = MockWriter()
write_wrapper = WriteWrapper(mock)
write_wrapper(data)
self.assertEqual(
mock.written, [data],
"WriterWrapper should call original writer when data is binary "
"type")
class TestErrorWrapper(unittest.TestCase):
def test_dont_close(self):
error_wrapper = ErrorWrapper(None)
self.assertRaises(AssertionError, error_wrapper.close)
class FakeError:
def __init__(self):
self.written = []
self.flushed = False
def write(self, s):
self.written.append(s)
def writelines(self, lines):
for line in lines:
self.write(line)
def flush(self):
self.flushed = True
def test_writelines(self):
fake_error = self.FakeError()
error_wrapper = ErrorWrapper(fake_error)
data = [to_bytes('a line'), to_bytes('another line')]
error_wrapper.writelines(data)
self.assertEqual(fake_error.written, data,
"ErrorWrapper should call original writer")
def test_flush(self):
fake_error = self.FakeError()
error_wrapper = ErrorWrapper(fake_error)
error_wrapper.flush()
self.assertTrue(
fake_error.flushed,
"ErrorWrapper should have called original wsgi_errors's flush")
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1738577344.0
webtest-3.0.4/tests/test_response.py 0000644 0001750 0001750 00000037404 14750112700 016631 0 ustar 00gawel gawel import webtest
from webtest.debugapp import debug_app
from webob import Request
from webob.response import gzip_app_iter
from tests.compat import unittest
import webbrowser
def links_app(environ, start_response):
req = Request(environ)
status = "200 OK"
responses = {
'/': """
page with links
Foo
Bar
Baz
Baz
Baz
Click me!
Click me!
Button
Button
Button
""",
'/foo/': (
'This is foo. Bar '
''
),
'/foo/bar': 'This is foobar.',
'/bar': 'This is bar.',
'/baz/': 'This is baz.',
'/spam/': 'This is spam.',
'/egg/': 'Just eggs.',
'/utf8/': """
Тестовая страница
Менделеев
Пушкин
""",
'/no_form/': """
Page without form
This is not the form you are looking for
""",
'/one_forms/': """
Page without form
""",
'/many_forms/': """
Page without form
""",
'/html_in_anchor/': """
Page with HTML in an anchor tag
Foo BarQuz
""",
'/json/': '{"foo": "bar"}',
}
utf8_paths = ['/utf8/']
body = responses[req.path_info]
body = body.encode('utf8')
headers = [
('Content-Type', 'text/html'),
('Content-Length', str(len(body)))
]
if req.path_info in utf8_paths:
headers[0] = ('Content-Type', 'text/html; charset=utf-8')
# PEP 3333 requires native strings:
headers = [(str(k), str(v)) for k, v in headers]
start_response(str(status), headers)
return [body]
def svg_application(env, start_response):
start_response('200 OK', [('Content-Type', 'image/svg+xml')])
return [
b""""""
b""""""
b""" """
]
def gzipped_app(environ, start_response):
status = "200 OK"
encoded_body = list(gzip_app_iter([b'test']))
headers = [
('Content-Type', 'text/html'),
('Content-Encoding', 'gzip'),
]
# PEP 3333 requires native strings:
headers = [(str(k), str(v)) for k, v in headers]
start_response(str(status), headers)
return encoded_body
class TestResponse(unittest.TestCase):
def test_repr(self):
def _repr(v):
br = repr(v)
if len(br) > 18:
br = br[:10] + '...' + br[-5:]
br += '/%s' % len(v)
return br
app = webtest.TestApp(debug_app)
res = app.post('/')
self.assertEqual(
repr(res),
'<200 OK text/plain body=%s>' % _repr(res.body)
)
res.content_type = None
self.assertEqual(
repr(res),
'<200 OK body=%s>' % _repr(res.body)
)
res.location = 'http://pylons.org'
self.assertEqual(
repr(res),
'<200 OK location: http://pylons.org body=%s>' % _repr(res.body)
)
res.body = b''
self.assertEqual(
repr(res),
'<200 OK location: http://pylons.org no body>'
)
def test_mustcontains(self):
app = webtest.TestApp(debug_app)
res = app.post('/', params='foobar')
res.mustcontain('foobar')
self.assertRaises(IndexError, res.mustcontain, 'not found')
res.mustcontain('foobar', no='not found')
res.mustcontain('foobar', no=['not found', 'not found either'])
self.assertRaises(IndexError, res.mustcontain, no='foobar')
self.assertRaises(
TypeError,
res.mustcontain, invalid_param='foobar'
)
def test_click(self):
app = webtest.TestApp(links_app)
self.assertIn('This is foo.', app.get('/').click('Foo'))
self.assertIn(
'This is foobar.',
app.get('/').click('Foo').click('Bar')
)
self.assertIn('This is bar.', app.get('/').click('Bar'))
# should skip non-clickable links
self.assertIn(
'This is baz.',
app.get('/').click('Baz')
)
self.assertIn('This is baz.', app.get('/').click(linkid='id_baz'))
self.assertIn('This is baz.', app.get('/').click(href='baz/'))
self.assertIn(
'This is spam.',
app.get('/').click('Click me!', index=0)
)
self.assertIn(
'Just eggs.',
app.get('/').click('Click me!', index=1)
)
self.assertIn(
'This is foo.',
app.get('/html_in_anchor/').click('baz qux')
)
def dont_match_anchor_tag():
app.get('/html_in_anchor/').click('href')
self.assertRaises(IndexError, dont_match_anchor_tag)
def multiple_links():
app.get('/').click('Click me!')
self.assertRaises(IndexError, multiple_links)
def invalid_index():
app.get('/').click('Click me!', index=2)
self.assertRaises(IndexError, invalid_index)
def no_links_found():
app.get('/').click('Ham')
self.assertRaises(IndexError, no_links_found)
def tag_inside_script():
app.get('/').click('Boo')
self.assertRaises(IndexError, tag_inside_script)
def test_click_utf8(self):
app = webtest.TestApp(links_app, use_unicode=False)
resp = app.get('/utf8/')
self.assertEqual(resp.charset, 'utf-8')
def test_click_u(self):
app = webtest.TestApp(links_app)
resp = app.get('/utf8/')
self.assertIn("Тестовая страница", resp)
self.assertIn('This is foo.', resp.click('Менделеев'))
def test_clickbutton(self):
app = webtest.TestApp(links_app)
self.assertIn(
'This is foo.',
app.get('/').clickbutton(buttonid='button1', verbose=True)
)
self.assertIn(
'This is foo.',
app.get('/').clickbutton(buttonid='button3', onclick=r".*href='(.*?)'", verbose=True)
)
self.assertRaises(
IndexError,
app.get('/').clickbutton, buttonid='button2'
)
self.assertRaises(
IndexError,
app.get('/').clickbutton, buttonid='button3'
)
def test_referer(self):
app = webtest.TestApp(links_app)
resp = app.get('/').click('Foo')
self.assertIn('Referer', resp.request.headers)
self.assertEqual(resp.request.headers['Referer'], 'http://localhost/')
resp = app.get('/').clickbutton(buttonid='button1')
self.assertIn('Referer', resp.request.headers)
self.assertEqual(resp.request.headers['Referer'], 'http://localhost/')
resp = app.get('/one_forms/').form.submit()
self.assertIn('Referer', resp.request.headers)
self.assertEqual(resp.request.headers['Referer'],
'http://localhost/one_forms/')
def test_xml_attribute(self):
app = webtest.TestApp(links_app)
resp = app.get('/no_form/')
self.assertRaises(
AttributeError,
getattr,
resp, 'xml'
)
resp.content_type = 'text/xml'
resp.xml
def test_lxml_attribute(self):
app = webtest.TestApp(links_app)
resp = app.post('/')
resp.content_type = 'text/xml'
print(resp.body)
print(resp.lxml)
def test_lxml_attribute_with_encoding_declaration(self):
app = webtest.TestApp(svg_application)
resp = app.get('/')
print(resp.body)
print(resp.lxml)
def test_pyquery(self):
app = webtest.TestApp(svg_application)
resp = app.get('/')
self.assertRaises(ValueError, lambda: resp.pyquery)
pq = resp.PyQuery(parser='xml', remove_namespaces=True)
assert len(pq('svg')) == 1
pq = resp.PyQuery(parser='xml')
assert len(pq('svg')) == 0
def test_html_attribute(self):
app = webtest.TestApp(links_app)
res = app.post('/')
res.content_type = 'text/plain'
self.assertRaises(
AttributeError,
getattr, res, 'html'
)
def test_no_form(self):
app = webtest.TestApp(links_app)
resp = app.get('/no_form/')
self.assertRaises(
TypeError,
getattr,
resp, 'form'
)
def test_one_forms(self):
app = webtest.TestApp(links_app)
resp = app.get('/one_forms/')
self.assertEqual(resp.form.id, 'first_form')
def test_too_many_forms(self):
app = webtest.TestApp(links_app)
resp = app.get('/many_forms/')
self.assertRaises(
TypeError,
getattr,
resp, 'form'
)
def test_showbrowser(self):
def open_new(f):
self.filename = f
webbrowser.open_new = open_new
app = webtest.TestApp(debug_app)
res = app.post('/')
res.showbrowser()
def test_unicode_normal_body(self):
app = webtest.TestApp(debug_app)
res = app.post('/')
self.assertRaises(
AttributeError,
getattr, res, 'unicode_normal_body'
)
res.charset = 'latin1'
res.body = 'été'.encode('latin1')
self.assertEqual(res.unicode_normal_body, 'été')
def test_testbody(self):
app = webtest.TestApp(debug_app)
res = app.post('/')
res.charset = 'utf8'
res.body = 'été'.encode('latin1')
res.testbody
def test_xml(self):
app = webtest.TestApp(links_app)
resp = app.get('/no_form/')
self.assertRaises(
AttributeError,
getattr,
resp, 'xml'
)
resp.content_type = 'text/xml'
resp.xml
def test_json(self):
app = webtest.TestApp(links_app)
resp = app.get('/json/')
with self.assertRaises(AttributeError):
resp.json
resp.content_type = 'text/json'
self.assertIn('foo', resp.json)
resp.content_type = 'application/json'
self.assertIn('foo', resp.json)
resp.content_type = 'application/vnd.webtest+json'
self.assertIn('foo', resp.json)
def test_unicode(self):
app = webtest.TestApp(links_app)
resp = app.get('/')
print(resp.__unicode__())
def test_content_dezips(self):
app = webtest.TestApp(gzipped_app)
resp = app.get('/')
self.assertEqual(resp.body, b'test')
class TestFollow(unittest.TestCase):
def get_redirects_app(self, count=1, locations=None):
"""Return an app that issues a redirect ``count`` times"""
remaining_redirects = [count] # this means "nonlocal"
if locations is None:
locations = ['/'] * count
def app(environ, start_response):
headers = [('Content-Type', 'text/html')]
if remaining_redirects[0] == 0:
status = "200 OK"
body = b"done"
else:
status = "302 Found"
body = b''
nextloc = str(locations.pop(0))
headers.append(('location', nextloc))
remaining_redirects[0] -= 1
headers.append(('Content-Length', str(len(body))))
# PEP 3333 requires native strings:
headers = [(str(k), str(v)) for k, v in headers]
start_response(str(status), headers)
return [body]
return webtest.TestApp(app)
def test_follow_with_cookie(self):
app = webtest.TestApp(debug_app)
app.get('/?header-set-cookie=foo=bar')
self.assertEqual(app.cookies['foo'], 'bar')
resp = app.get('/?status=302%20Found&header-location=/')
resp = resp.follow()
resp.mustcontain('HTTP_COOKIE: foo=bar')
def test_follow(self):
app = self.get_redirects_app(1)
resp = app.get('/')
self.assertEqual(resp.status_int, 302)
resp = resp.follow()
self.assertEqual(resp.body, b'done')
# can't follow non-redirect
self.assertRaises(AssertionError, resp.follow)
def test_follow_relative(self):
app = self.get_redirects_app(2, ['hello/foo/', 'bar'])
resp = app.get('/')
self.assertEqual(resp.status_int, 302)
resp = resp.follow()
self.assertEqual(resp.status_int, 302)
resp = resp.follow()
self.assertEqual(resp.body, b'done')
self.assertEqual(resp.request.url, 'http://localhost/hello/foo/bar')
def test_follow_twice(self):
app = self.get_redirects_app(2)
resp = app.get('/').follow()
self.assertEqual(resp.status_int, 302)
resp = resp.follow()
self.assertEqual(resp.status_int, 200)
def test_maybe_follow_200(self):
app = self.get_redirects_app(0)
resp = app.get('/').maybe_follow()
self.assertEqual(resp.body, b'done')
def test_maybe_follow_once(self):
app = self.get_redirects_app(1)
resp = app.get('/').maybe_follow()
self.assertEqual(resp.body, b'done')
def test_maybe_follow_twice(self):
app = self.get_redirects_app(2)
resp = app.get('/').maybe_follow()
self.assertEqual(resp.body, b'done')
def test_maybe_follow_infinite(self):
app = self.get_redirects_app(100000)
self.assertRaises(AssertionError, app.get('/').maybe_follow)
def test_pytest_collection_disabled(self):
self.assertFalse(webtest.TestResponse.__test__)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1738577344.0
webtest-3.0.4/tests/test_sel.py 0000644 0001750 0001750 00000000347 14750112700 015552 0 ustar 00gawel gawel from .compat import unittest
from webtest import sel
class TestSelenium(unittest.TestCase):
def test_raises(self):
self.assertRaises(ImportError, sel.SeleniumApp)
self.assertRaises(ImportError, sel.selenium)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1738577344.0
webtest-3.0.4/tests/test_utils.py 0000644 0001750 0001750 00000010540 14750112700 016123 0 ustar 00gawel gawel import re
import json
import sys
from .compat import unittest
from webtest import utils
class NoDefaultTest(unittest.TestCase):
def test_nodefault(self):
from webtest.utils import NoDefault
self.assertEqual(repr(NoDefault), '')
class encode_paramsTest(unittest.TestCase):
def test_encode_params_None(self):
self.assertEqual(utils.encode_params(None, None), None)
def test_encode_params_NoDefault(self):
self.assertEqual(utils.encode_params(utils.NoDefault, None), '')
def test_encode_params_dict_or_list(self):
self.assertEqual(utils.encode_params({'foo': 'bar'}, None),
utils.encode_params([('foo', 'bar')], None))
def test_encode_params_no_charset(self):
# no content_type at all
self.assertEqual(utils.encode_params({'foo': 'bar'}, None), 'foo=bar')
# content_type without "charset=xxxx"
self.assertEqual(utils.encode_params({'foo': 'bar'}, 'ba'), 'foo=bar')
def test_encode_params_charset_utf8(self):
# charset is using inconsistent casing on purpose, it should still work
self.assertEqual(utils.encode_params({'f': '€'}, ' CHARset=uTF-8; '),
'f=%E2%82%AC')
class make_patternTest(unittest.TestCase):
def call_FUT(self, obj):
from webtest.utils import make_pattern
return make_pattern(obj)
def test_make_pattern_None(self):
self.assertEqual(self.call_FUT(None), None)
def test_make_pattern_regex(self):
regex = re.compile(r'foobar')
self.assertEqual(self.call_FUT(regex), regex.search)
def test_make_pattern_function(self):
func = lambda x: x
self.assertEqual(self.call_FUT(func), func)
def test_make_pattern_bytes(self):
# if we pass a string, it will get compiled into a regex
# that we can later call and match a string
self.assertEqual(self.call_FUT('a')('a').string, 'a')
def test_make_pattern_invalid(self):
self.assertRaises(ValueError, self.call_FUT, 0)
class stringifyTest(unittest.TestCase):
def test_stringify_text(self):
self.assertEqual(utils.stringify("foo"), "foo")
def test_stringify_binary(self):
self.assertEqual(utils.stringify(b"foo"), "foo")
def test_stringify_other(self):
self.assertEqual(utils.stringify(123), "123")
class json_methodTest(unittest.TestCase):
class MockTestApp:
"""Mock TestApp used to test the json_object decorator."""
from webtest.utils import json_method
JSONEncoder = json.JSONEncoder
foo_json = json_method('FOO')
def _gen_request(self, method, url, **kw):
return (method, url, kw)
mock = MockTestApp()
def test_json_method_request_calls(self):
from webtest.utils import NoDefault
# no params
self.assertEqual(self.mock.foo_json('url', params=NoDefault, c='c'),
('FOO', 'url', {'content_type': 'application/json',
'c': 'c',
'params': NoDefault,
'upload_files': None}))
# params dumped to json
self.assertEqual(self.mock.foo_json('url', params={'a': 'b'}, c='c'),
('FOO', 'url', {'content_type': 'application/json',
'c': 'c',
'params': json.dumps({'a': 'b'}),
'upload_files': None}))
def test_json_method_request_respects_content_type_argument(self):
self.assertEqual(self.mock.foo_json('url', params={'a': 'b'}, c='c', content_type='application/vnd.api+json;charset=utf-8'),
('FOO', 'url', {'content_type': 'application/vnd.api+json;charset=utf-8',
'c': 'c',
'params': json.dumps({'a': 'b'}),
'upload_files': None}))
@unittest.skipIf(sys.flags.optimize == 2, "no docstring if PYTHONOPTIMIZE=2")
def test_json_method_doc(self):
self.assertIn('FOO request', self.mock.foo_json.__doc__)
self.assertIn('TestApp.foo', self.mock.foo_json.__doc__)
def test_json_method_name(self):
self.assertEqual(self.mock.foo_json.__name__, 'foo_json')
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1738577344.0
webtest-3.0.4/tox.ini 0000644 0001750 0001750 00000001400 14750112700 013516 0 ustar 00gawel gawel [tox]
skip_missing_interpreters = true
envlist =
py39,py310,py311,py312
coverage,
docs
[testenv]
setenv =
LC_ALL=C
LANG=C
COVERAGE_FILE=.coverage.{envname}
extras =
tests
commands =
python --version
pip freeze
pytest --cov {posargs:}
[testenv:coverage]
skip_install = true
deps =
coverage
setenv =
COVERAGE_FILE=.coverage
commands =
coverage combine
coverage xml
# We want to get this to 100, but for now we compromise.
# See https://github.com/Pylons/webtest/pull/231#issuecomment-729574898
coverage report --show-missing --fail-under=96
[testenv:docs]
basepython = python3.12
allowlist_externals =
make
commands =
make -C docs html BUILDDIR={envdir} "SPHINXOPTS=-W -E"
extras =
docs
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1738577345.5096748
webtest-3.0.4/webtest/ 0000755 0001750 0001750 00000000000 14750112702 013667 5 ustar 00gawel gawel ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1738577344.0
webtest-3.0.4/webtest/__init__.py 0000644 0001750 0001750 00000001260 14750112700 015775 0 ustar 00gawel gawel # (c) 2005 Ian Bicking and contributors; written for Paste
# (http://pythonpaste.org)
# Licensed under the MIT license:
# http://www.opensource.org/licenses/mit-license.php
"""
Routines for testing WSGI applications.
"""
from webtest.app import TestApp
from webtest.app import TestRequest
from webtest.app import TestResponse
from webtest.app import AppError
from webtest.forms import Form
from webtest.forms import Field
from webtest.forms import Select
from webtest.forms import Radio
from webtest.forms import Checkbox
from webtest.forms import Text
from webtest.forms import Textarea
from webtest.forms import Hidden
from webtest.forms import Submit
from webtest.forms import Upload
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1738577344.0
webtest-3.0.4/webtest/app.py 0000644 0001750 0001750 00000071033 14750112700 015023 0 ustar 00gawel gawel # (c) 2005 Ian Bicking and contributors; written for Paste
# (http://pythonpaste.org)
# Licensed under the MIT license:
# http://www.opensource.org/licenses/mit-license.php
"""
Routines for testing WSGI applications.
Most interesting is TestApp
"""
import os
import re
import json
import random
import fnmatch
import mimetypes
from base64 import b64encode
from http import cookiejar as http_cookiejar
from io import BytesIO, StringIO
from webtest.compat import urlparse
from webtest.compat import to_bytes
from webtest.compat import escape_cookie_value
from webtest.response import TestResponse
from webtest import forms
from webtest import lint
from webtest import utils
import webob
__all__ = ['TestApp', 'TestRequest']
class AppError(Exception):
def __init__(self, message, *args):
if isinstance(message, bytes):
message = message.decode('utf8')
str_args = ()
for arg in args:
if isinstance(arg, webob.Response):
body = arg.body
if isinstance(body, bytes):
if arg.charset:
arg = body.decode(arg.charset)
else:
arg = repr(body)
elif isinstance(arg, bytes):
try:
arg = arg.decode('utf8')
except UnicodeDecodeError:
arg = repr(arg)
str_args += (arg,)
message = message % str_args
Exception.__init__(self, message)
class CookiePolicy(http_cookiejar.DefaultCookiePolicy):
"""A subclass of DefaultCookiePolicy to allow cookie set for
Domain=localhost."""
def return_ok_domain(self, cookie, request):
if cookie.domain == '.localhost':
return True
return http_cookiejar.DefaultCookiePolicy.return_ok_domain(
self, cookie, request)
def set_ok_domain(self, cookie, request):
if cookie.domain == '.localhost':
return True
return http_cookiejar.DefaultCookiePolicy.set_ok_domain(
self, cookie, request)
class TestRequest(webob.BaseRequest):
"""A subclass of webob.Request"""
# Tell pytest not to collect this class as tests
__test__ = False
ResponseClass = TestResponse
class TestApp:
"""
Wraps a WSGI application in a more convenient interface for
testing. It uses extended version of :class:`webob.BaseRequest`
and :class:`webob.Response`.
:param app:
May be an WSGI application or Paste Deploy app,
like ``'config:filename.ini#test'``.
.. versionadded:: 2.0
It can also be an actual full URL to an http server and webtest
will proxy requests with `WSGIProxy2
`_.
:type app:
WSGI application
:param extra_environ:
A dictionary of values that should go
into the environment for each request. These can provide a
communication channel with the application.
:type extra_environ:
dict
:param relative_to:
A directory used for file
uploads are calculated relative to this. Also ``config:``
URIs that aren't absolute.
:type relative_to:
string
:param cookiejar:
:class:`cookielib.CookieJar` alike API that keeps cookies
across requests.
:type cookiejar:
CookieJar instance
.. attribute:: cookies
A convenient shortcut for a dict of all cookies in
``cookiejar``.
:param parser_features:
Passed to BeautifulSoup when parsing responses.
:type parser_features:
string or list
:param json_encoder:
Passed to json.dumps when encoding json
:type json_encoder:
A subclass of json.JSONEncoder
:param lint:
If True (default) then check that the application is WSGI compliant
:type lint:
A boolean
"""
RequestClass = TestRequest
# Tell pytest not to collect this class as tests
__test__ = False
def __init__(self, app, extra_environ=None, relative_to=None,
use_unicode=True, cookiejar=None, parser_features=None,
json_encoder=None, lint=True):
if 'WEBTEST_TARGET_URL' in os.environ:
app = os.environ['WEBTEST_TARGET_URL']
if isinstance(app, str):
if app.startswith('http'):
try:
from wsgiproxy import HostProxy
except ImportError: # pragma: no cover
raise ImportError(
'Using webtest with a real url requires WSGIProxy2. '
'Please install it with: '
'pip install WSGIProxy2')
if '#' not in app:
app += '#httplib'
url, client = app.split('#', 1)
app = HostProxy(url, client=client)
else:
from paste.deploy import loadapp
# @@: Should pick up relative_to from calling module's
# __file__
app = loadapp(app, relative_to=relative_to)
self.app = app
self.lint = lint
self.relative_to = relative_to
if extra_environ is None:
extra_environ = {}
self.extra_environ = extra_environ
self.use_unicode = use_unicode
if cookiejar is None:
cookiejar = http_cookiejar.CookieJar(policy=CookiePolicy())
self.cookiejar = cookiejar
if parser_features is None:
parser_features = 'html.parser'
self.RequestClass.ResponseClass.parser_features = parser_features
if json_encoder is None:
json_encoder = json.JSONEncoder
self.JSONEncoder = json_encoder
def get_authorization(self):
"""Allow to set the HTTP_AUTHORIZATION environ key. Value should look
like one of the following:
* ``('Basic', ('user', 'password'))``
* ``('Bearer', 'mytoken')``
* ``('JWT', 'myjwt')``
If value is None the the HTTP_AUTHORIZATION is removed
"""
return self.authorization_value
def set_authorization(self, value):
self.authorization_value = value
if value is not None:
invalid_value = (
"You should use a value like ('Basic', ('user', 'password'))"
" OR ('Bearer', 'token') OR ('JWT', 'token')"
)
if isinstance(value, (list, tuple)) and len(value) == 2:
authtype, val = value
if authtype == 'Basic' and val and \
isinstance(val, (list, tuple)):
val = ':'.join(list(val))
val = b64encode(to_bytes(val)).strip()
val = val.decode('latin1')
elif authtype in ('Bearer', 'JWT') and val and \
isinstance(val, (str, str)):
val = val.strip()
else:
raise ValueError(invalid_value)
value = str(f'{authtype} {val}')
else:
raise ValueError(invalid_value)
self.extra_environ.update({
'HTTP_AUTHORIZATION': value,
})
else:
if 'HTTP_AUTHORIZATION' in self.extra_environ:
del self.extra_environ['HTTP_AUTHORIZATION']
authorization = property(get_authorization, set_authorization)
@property
def cookies(self):
return {cookie.name: cookie.value for cookie in self.cookiejar}
def set_cookie(self, name, value):
"""
Sets a cookie to be passed through with requests.
"""
cookie_domain = self.extra_environ.get('HTTP_HOST', '.localhost')
cookie_domain = cookie_domain.split(':', 1)[0]
if '.' not in cookie_domain:
cookie_domain = "%s.local" % cookie_domain
value = escape_cookie_value(value)
cookie = http_cookiejar.Cookie(
version=0,
name=name,
value=value,
port=None,
port_specified=False,
domain=cookie_domain,
domain_specified=True,
domain_initial_dot=False,
path='/',
path_specified=True,
secure=False,
expires=None,
discard=False,
comment=None,
comment_url=None,
rest=None
)
self.cookiejar.set_cookie(cookie)
def reset(self):
"""
Resets the state of the application; currently just clears
saved cookies.
"""
self.cookiejar.clear()
def set_parser_features(self, parser_features):
"""
Changes the parser used by BeautifulSoup. See its documentation to
know the supported parsers.
"""
self.RequestClass.ResponseClass.parser_features = parser_features
def get(self, url, params=None, headers=None, extra_environ=None,
status=None, expect_errors=False, xhr=False):
"""
Do a GET request given the url path.
:param params:
A query string, or a dictionary that will be encoded
into a query string. You may also include a URL query
string on the ``url``.
:param headers:
Extra headers to send.
:type headers:
dictionary
:param extra_environ:
Environmental variables that should be added to the request.
:type extra_environ:
dictionary
:param status:
The HTTP status code you expect in response (if not 200 or 3xx).
You can also use a wildcard, like ``'3*'`` or ``'*'``.
:type status:
integer or string
:param expect_errors:
If this is False, then if anything is written to
environ ``wsgi.errors`` it will be an error.
If it is True, then non-200/3xx responses are also okay.
:type expect_errors:
boolean
:param xhr:
If this is true, then marks response as ajax. The same as
headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }
:type xhr:
boolean
:returns: :class:`webtest.TestResponse` instance.
"""
environ = self._make_environ(extra_environ)
url = str(url)
url = self._remove_fragment(url)
if params:
url = utils.build_params(url, params)
if '?' in url:
url, environ['QUERY_STRING'] = url.split('?', 1)
else:
environ['QUERY_STRING'] = ''
req = self.RequestClass.blank(url, environ)
if xhr:
headers = self._add_xhr_header(headers)
if headers:
req.headers.update(headers)
return self.do_request(req, status=status,
expect_errors=expect_errors)
def post(self, url, params='', headers=None, extra_environ=None,
status=None, upload_files=None, expect_errors=False,
content_type=None, xhr=False):
"""
Do a POST request. Similar to :meth:`~webtest.TestApp.get`.
:param params:
Are put in the body of the request. If params is an
iterator, it will be urlencoded. If it is a string, it will not
be encoded, but placed in the body directly.
Can be a :class:`python:collections.OrderedDict` with
:class:`webtest.forms.Upload` fields included::
app.post('/myurl', collections.OrderedDict([
('textfield1', 'value1'),
('uploadfield', webapp.Upload('filename.txt', 'contents'),
('textfield2', 'value2')])))
:param upload_files:
It should be a list of ``(fieldname, filename, file_content)``.
You can also use just ``(fieldname, filename)`` and the file
contents will be read from disk.
:type upload_files:
list
:param content_type:
HTTP content type, for example `application/json`.
:type content_type:
string
:param xhr:
If this is true, then marks response as ajax. The same as
headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }
:type xhr:
boolean
:returns: :class:`webtest.TestResponse` instance.
"""
if xhr:
headers = self._add_xhr_header(headers)
return self._gen_request('POST', url, params=params, headers=headers,
extra_environ=extra_environ, status=status,
upload_files=upload_files,
expect_errors=expect_errors,
content_type=content_type)
def put(self, url, params='', headers=None, extra_environ=None,
status=None, upload_files=None, expect_errors=False,
content_type=None, xhr=False):
"""
Do a PUT request. Similar to :meth:`~webtest.TestApp.post`.
:returns: :class:`webtest.TestResponse` instance.
"""
if xhr:
headers = self._add_xhr_header(headers)
return self._gen_request('PUT', url, params=params, headers=headers,
extra_environ=extra_environ, status=status,
upload_files=upload_files,
expect_errors=expect_errors,
content_type=content_type,
)
def patch(self, url, params='', headers=None, extra_environ=None,
status=None, upload_files=None, expect_errors=False,
content_type=None, xhr=False):
"""
Do a PATCH request. Similar to :meth:`~webtest.TestApp.post`.
:returns: :class:`webtest.TestResponse` instance.
"""
if xhr:
headers = self._add_xhr_header(headers)
return self._gen_request('PATCH', url, params=params, headers=headers,
extra_environ=extra_environ, status=status,
upload_files=upload_files,
expect_errors=expect_errors,
content_type=content_type)
def delete(self, url, params='', headers=None,
extra_environ=None, status=None, expect_errors=False,
content_type=None, xhr=False):
"""
Do a DELETE request. Similar to :meth:`~webtest.TestApp.get`.
:returns: :class:`webtest.TestResponse` instance.
"""
if xhr:
headers = self._add_xhr_header(headers)
return self._gen_request('DELETE', url, params=params, headers=headers,
extra_environ=extra_environ, status=status,
upload_files=None,
expect_errors=expect_errors,
content_type=content_type)
def options(self, url, headers=None, extra_environ=None,
status=None, expect_errors=False, xhr=False):
"""
Do a OPTIONS request. Similar to :meth:`~webtest.TestApp.get`.
:returns: :class:`webtest.TestResponse` instance.
"""
if xhr:
headers = self._add_xhr_header(headers)
return self._gen_request('OPTIONS', url, headers=headers,
extra_environ=extra_environ, status=status,
upload_files=None,
expect_errors=expect_errors)
def head(self, url, params=None, headers=None, extra_environ=None,
status=None, expect_errors=False, xhr=False):
"""
Do a HEAD request. Similar to :meth:`~webtest.TestApp.get`.
:returns: :class:`webtest.TestResponse` instance.
"""
if params:
url = utils.build_params(url, params)
if xhr:
headers = self._add_xhr_header(headers)
return self._gen_request('HEAD', url, headers=headers,
extra_environ=extra_environ, status=status,
upload_files=None,
expect_errors=expect_errors)
post_json = utils.json_method('POST')
put_json = utils.json_method('PUT')
patch_json = utils.json_method('PATCH')
delete_json = utils.json_method('DELETE')
def encode_multipart(self, params, files):
"""
Encodes a set of parameters (typically a name/value list) and
a set of files (a list of (name, filename, file_body, mimetype)) into a
typical POST body, returning the (content_type, body).
"""
boundary = to_bytes(str(random.random()))[2:]
boundary = b'----------a_BoUnDaRy' + boundary + b'$'
lines = []
def _append_file(file_info):
key, filename, value, fcontent = self._get_file_info(file_info)
if isinstance(key, str):
try:
key = key.encode('ascii')
except: # pragma: no cover
raise # file name must be ascii
if isinstance(filename, str):
try:
filename = filename.encode('utf8')
except: # pragma: no cover
raise # file name must be ascii or utf8
if not fcontent:
fcontent = mimetypes.guess_type(filename.decode('utf8'))[0]
fcontent = to_bytes(fcontent)
fcontent = fcontent or b'application/octet-stream'
lines.extend([
b'--' + boundary,
b'Content-Disposition: form-data; ' +
b'name="' + key + b'"; filename="' + filename + b'"',
b'Content-Type: ' + fcontent, b'', value])
for key, value in params:
if isinstance(key, str):
try:
key = key.encode('ascii')
except: # pragma: no cover
raise # field name are always ascii
if isinstance(value, forms.File):
if "multiple" in value.attrs:
for file in value.value:
_append_file([key] + list(file))
elif value.value:
_append_file([key] + list(value.value))
else:
# If no file was uploaded simulate an empty file with no
# name like real browsers do:
_append_file([key, b'', b''])
elif isinstance(value, forms.Upload):
file_info = [key, value.filename]
if value.content is not None:
file_info.append(value.content)
if value.content_type is not None:
file_info.append(value.content_type)
_append_file(file_info)
else:
if isinstance(value, int):
value = str(value).encode('utf8')
elif isinstance(value, str):
value = value.encode('utf8')
elif not isinstance(value, (bytes, str)):
raise ValueError((
'Value for field {} is a {} ({}). '
'It must be str, bytes or an int'
).format(key, type(value), value))
lines.extend([
b'--' + boundary,
b'Content-Disposition: form-data; name="' + key + b'"',
b'', value])
for file_info in files:
_append_file(file_info)
lines.extend([b'--' + boundary + b'--', b''])
body = b'\r\n'.join(lines)
boundary = boundary.decode('ascii')
content_type = 'multipart/form-data; boundary=%s' % boundary
return content_type, body
def request(self, url_or_req, status=None, expect_errors=False,
**req_params):
"""
Creates and executes a request. You may either pass in an
instantiated :class:`TestRequest` object, or you may pass in a
URL and keyword arguments to be passed to
:meth:`TestRequest.blank`.
You can use this to run a request without the intermediary
functioning of :meth:`TestApp.get` etc. For instance, to
test a WebDAV method::
resp = app.request('/new-col', method='MKCOL')
Note that the request won't have a body unless you specify it,
like::
resp = app.request('/test.txt', method='PUT', body='test')
You can use :class:`webtest.TestRequest`::
req = webtest.TestRequest.blank('/url/', method='GET')
resp = app.do_request(req)
"""
if isinstance(url_or_req, str):
url_or_req = str(url_or_req)
for (k, v) in req_params.items():
if isinstance(v, str):
req_params[k] = str(v)
if isinstance(url_or_req, str):
req = self.RequestClass.blank(url_or_req, **req_params)
else:
req = url_or_req.copy()
for name, value in req_params.items():
setattr(req, name, value)
req.environ['paste.throw_errors'] = True
for name, value in self.extra_environ.items():
req.environ.setdefault(name, value)
return self.do_request(req,
status=status,
expect_errors=expect_errors,
)
def do_request(self, req, status=None, expect_errors=None):
"""
Executes the given webob Request (``req``), with the expected
``status``. Generally :meth:`~webtest.TestApp.get` and
:meth:`~webtest.TestApp.post` are used instead.
To use this::
req = webtest.TestRequest.blank('url', ...args...)
resp = app.do_request(req)
.. note::
You can pass any keyword arguments to
``TestRequest.blank()``, which will be set on the request.
These can be arguments like ``content_type``, ``accept``, etc.
"""
errors = StringIO()
req.environ['wsgi.errors'] = errors
script_name = req.environ.get('SCRIPT_NAME', '')
if script_name and req.path_info.startswith(script_name):
req.path_info = req.path_info[len(script_name):]
# set framework hooks
req.environ['paste.testing'] = True
req.environ['paste.testing_variables'] = {}
# set request cookies
self.cookiejar.add_cookie_header(utils._RequestCookieAdapter(req))
# verify wsgi compatibility
app = lint.middleware(self.app) if self.lint else self.app
# FIXME: should it be an option to not catch exc_info?
res = req.get_response(app, catch_exc_info=True)
# be sure to decode the content
res.decode_content()
# set a few handy attributes
res._use_unicode = self.use_unicode
res.request = req
res.app = app
res.test_app = self
# We do this to make sure the app_iter is exhausted:
try:
res.body
except TypeError: # pragma: no cover
pass
res.errors = errors.getvalue()
for name, value in req.environ['paste.testing_variables'].items():
if hasattr(res, name):
raise ValueError(
"paste.testing_variables contains the variable %r, but "
"the response object already has an attribute by that "
"name" % name)
setattr(res, name, value)
if not expect_errors:
self._check_status(status, res)
self._check_errors(res)
# merge cookies back in
self.cookiejar.extract_cookies(utils._ResponseCookieAdapter(res),
utils._RequestCookieAdapter(req))
return res
def _check_status(self, status, res):
if status == '*':
return
res_status = res.status
if (isinstance(status, str) and '*' in status):
if re.match(fnmatch.translate(status), res_status, re.I):
return
if isinstance(status, str):
if status == res_status:
return
if isinstance(status, (list, tuple)):
if res.status_int not in status:
raise AppError(
"Bad response: %s (not one of %s for %s)\n%s",
res_status, ', '.join(map(str, status)),
res.request.url, res)
return
if status is None:
if res.status_int >= 200 and res.status_int < 400:
return
raise AppError(
"Bad response: %s (not 200 OK or 3xx redirect for %s)\n%s",
res_status, res.request.url,
res)
if status != res.status_int:
raise AppError(
"Bad response: %s (not %s)\n%s", res_status, status, res)
def _check_errors(self, res):
errors = res.errors
if errors:
raise AppError(
"Application had errors logged:\n%s", errors)
def _make_environ(self, extra_environ=None):
environ = self.extra_environ.copy()
environ['paste.throw_errors'] = True
if extra_environ:
environ.update(extra_environ)
return environ
def _remove_fragment(self, url):
scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
return urlparse.urlunsplit((scheme, netloc, path, query, ""))
def _gen_request(self, method, url, params=utils.NoDefault,
headers=None, extra_environ=None, status=None,
upload_files=None, expect_errors=False,
content_type=None):
"""
Do a generic request.
"""
environ = self._make_environ(extra_environ)
inline_uploads = []
# this supports OrderedDict
if isinstance(params, dict) or hasattr(params, 'items'):
params = list(params.items())
if isinstance(params, (list, tuple)):
inline_uploads = [v for (k, v) in params
if isinstance(v, (forms.File, forms.Upload))]
if len(inline_uploads) > 0:
content_type, params = self.encode_multipart(
params, upload_files or ())
environ['CONTENT_TYPE'] = content_type
else:
params = utils.encode_params(params, content_type)
if upload_files or \
(content_type and
to_bytes(content_type).startswith(b'multipart')):
params = urlparse.parse_qsl(params, keep_blank_values=True)
content_type, params = self.encode_multipart(
params, upload_files or ())
environ['CONTENT_TYPE'] = content_type
elif params:
environ.setdefault('CONTENT_TYPE',
'application/x-www-form-urlencoded')
if content_type is not None:
environ['CONTENT_TYPE'] = content_type
environ['REQUEST_METHOD'] = str(method)
url = str(url)
url = self._remove_fragment(url)
req = self.RequestClass.blank(url, environ)
if isinstance(params, str):
params = params.encode(req.charset or 'utf8')
req.environ['wsgi.input'] = BytesIO(params)
req.content_length = len(params)
if headers:
req.headers.update(headers)
return self.do_request(req, status=status,
expect_errors=expect_errors)
def _get_file_info(self, file_info):
if len(file_info) == 2:
# It only has a filename
filename = file_info[1]
if self.relative_to:
filename = os.path.join(self.relative_to, filename)
f = open(filename, 'rb')
content = f.read()
f.close()
return (file_info[0], filename, content, None)
elif 3 <= len(file_info) <= 4:
content = file_info[2]
if not isinstance(content, bytes):
raise ValueError('File content must be %s not %s'
% (bytes, type(content)))
if len(file_info) == 3:
return tuple(file_info) + (None,)
else:
return file_info
else:
raise ValueError(
"upload_files need to be a list of tuples of (fieldname, "
"filename, filecontent, mimetype) or (fieldname, "
"filename, filecontent) or (fieldname, filename); "
"you gave: %r"
% repr(file_info)[:100])
@staticmethod
def _add_xhr_header(headers):
headers = headers or {}
# if remove str we will be have an error in lint.middleware
headers.update({'X-REQUESTED-WITH': 'XMLHttpRequest'})
return headers
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1738577344.0
webtest-3.0.4/webtest/compat.py 0000644 0001750 0001750 00000010171 14750112700 015522 0 ustar 00gawel gawel import sys
from http import cookies
SimpleCookie = cookies.SimpleCookie
CookieError = cookies.CookieError
def to_bytes(value, charset='latin1'):
if isinstance(value, str):
return value.encode(charset)
return value
from html.entities import name2codepoint
from urllib.parse import urlencode
import urllib.parse as urlparse
from collections.abc import Iterable # noqa
def print_stderr(value):
print(value, file=sys.stderr)
def escape_cookie_value(value):
"""
Escapes a value so that it can be safely stored in a cookie.
"""
return '"' + ''.join(
COOKIE_ESCAPE_CHAR_MAP.get(x, x) for x in value
) + '"'
# A list of illegal characters in a cookie and the escaped equivalent.
# Taken from Python's cookie module.
COOKIE_ESCAPE_CHAR_MAP = {
'\000' : '\\000', '\001' : '\\001', '\002' : '\\002',
'\003' : '\\003', '\004' : '\\004', '\005' : '\\005',
'\006' : '\\006', '\007' : '\\007', '\010' : '\\010',
'\011' : '\\011', '\012' : '\\012', '\013' : '\\013',
'\014' : '\\014', '\015' : '\\015', '\016' : '\\016',
'\017' : '\\017', '\020' : '\\020', '\021' : '\\021',
'\022' : '\\022', '\023' : '\\023', '\024' : '\\024',
'\025' : '\\025', '\026' : '\\026', '\027' : '\\027',
'\030' : '\\030', '\031' : '\\031', '\032' : '\\032',
'\033' : '\\033', '\034' : '\\034', '\035' : '\\035',
'\036' : '\\036', '\037' : '\\037',
# Because of the way browsers really handle cookies (as opposed
# to what the RFC says) we also encode , and ;
',' : '\\054', ';' : '\\073',
'"' : '\\"', '\\' : '\\\\',
'\177' : '\\177', '\200' : '\\200', '\201' : '\\201',
'\202' : '\\202', '\203' : '\\203', '\204' : '\\204',
'\205' : '\\205', '\206' : '\\206', '\207' : '\\207',
'\210' : '\\210', '\211' : '\\211', '\212' : '\\212',
'\213' : '\\213', '\214' : '\\214', '\215' : '\\215',
'\216' : '\\216', '\217' : '\\217', '\220' : '\\220',
'\221' : '\\221', '\222' : '\\222', '\223' : '\\223',
'\224' : '\\224', '\225' : '\\225', '\226' : '\\226',
'\227' : '\\227', '\230' : '\\230', '\231' : '\\231',
'\232' : '\\232', '\233' : '\\233', '\234' : '\\234',
'\235' : '\\235', '\236' : '\\236', '\237' : '\\237',
'\240' : '\\240', '\241' : '\\241', '\242' : '\\242',
'\243' : '\\243', '\244' : '\\244', '\245' : '\\245',
'\246' : '\\246', '\247' : '\\247', '\250' : '\\250',
'\251' : '\\251', '\252' : '\\252', '\253' : '\\253',
'\254' : '\\254', '\255' : '\\255', '\256' : '\\256',
'\257' : '\\257', '\260' : '\\260', '\261' : '\\261',
'\262' : '\\262', '\263' : '\\263', '\264' : '\\264',
'\265' : '\\265', '\266' : '\\266', '\267' : '\\267',
'\270' : '\\270', '\271' : '\\271', '\272' : '\\272',
'\273' : '\\273', '\274' : '\\274', '\275' : '\\275',
'\276' : '\\276', '\277' : '\\277', '\300' : '\\300',
'\301' : '\\301', '\302' : '\\302', '\303' : '\\303',
'\304' : '\\304', '\305' : '\\305', '\306' : '\\306',
'\307' : '\\307', '\310' : '\\310', '\311' : '\\311',
'\312' : '\\312', '\313' : '\\313', '\314' : '\\314',
'\315' : '\\315', '\316' : '\\316', '\317' : '\\317',
'\320' : '\\320', '\321' : '\\321', '\322' : '\\322',
'\323' : '\\323', '\324' : '\\324', '\325' : '\\325',
'\326' : '\\326', '\327' : '\\327', '\330' : '\\330',
'\331' : '\\331', '\332' : '\\332', '\333' : '\\333',
'\334' : '\\334', '\335' : '\\335', '\336' : '\\336',
'\337' : '\\337', '\340' : '\\340', '\341' : '\\341',
'\342' : '\\342', '\343' : '\\343', '\344' : '\\344',
'\345' : '\\345', '\346' : '\\346', '\347' : '\\347',
'\350' : '\\350', '\351' : '\\351', '\352' : '\\352',
'\353' : '\\353', '\354' : '\\354', '\355' : '\\355',
'\356' : '\\356', '\357' : '\\357', '\360' : '\\360',
'\361' : '\\361', '\362' : '\\362', '\363' : '\\363',
'\364' : '\\364', '\365' : '\\365', '\366' : '\\366',
'\367' : '\\367', '\370' : '\\370', '\371' : '\\371',
'\372' : '\\372', '\373' : '\\373', '\374' : '\\374',
'\375' : '\\375', '\376' : '\\376', '\377' : '\\377'
}
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1738577344.0
webtest-3.0.4/webtest/debugapp.py 0000644 0001750 0001750 00000005542 14750112700 016034 0 ustar 00gawel gawel import os
import webob
__all__ = ['DebugApp', 'make_debug_app']
class DebugApp:
"""The WSGI application used for testing"""
def __init__(self, form=None, show_form=False):
if form and os.path.isfile(form):
fd = open(form, 'rb')
self.form = fd.read()
fd.close()
else:
self.form = form
self.show_form = show_form
def __call__(self, environ, start_response):
req = webob.Request(environ)
if req.path_info == '/form.html' and req.method == 'GET':
resp = webob.Response(content_type='text/html')
resp.body = self.form
return resp(environ, start_response)
if 'error' in req.GET:
raise Exception('Exception requested')
if 'errorlog' in req.GET:
log = req.GET['errorlog']
req.environ['wsgi.errors'].write(log)
status = str(req.GET.get('status', '200 OK'))
parts = []
if not self.show_form:
for name, value in sorted(environ.items()):
if name.upper() != name:
value = repr(value)
parts.append(f'{name}: {value}\n')
body = ''.join(parts)
if not isinstance(body, bytes):
body = body.encode('latin1')
if req.content_length:
body += b'-- Body ----------\n'
body += req.body
else:
body = ''
for name, value in req.POST.items():
body += f'{name}={value}\n'
if status[:3] in ('204', '304') and not req.content_length:
body = ''
headers = [
('Content-Type', 'text/plain'),
('Content-Length', str(len(body)))]
if not self.show_form:
for name, value in req.GET.items():
if name.startswith('header-'):
header_name = name[len('header-'):]
if isinstance(header_name, str):
header_name = str(header_name)
header_name = header_name.title()
headers.append((header_name, str(value)))
resp = webob.Response()
resp.status = status
resp.headers.update(headers)
if req.method != 'HEAD':
if isinstance(body, str):
resp.body = body.encode('utf8')
else:
resp.body = body
return resp(environ, start_response)
debug_app = DebugApp(form=b'''
''')
def make_debug_app(global_conf, **local_conf):
"""An application that displays the request environment, and does
nothing else (useful for debugging and test purposes).
"""
return DebugApp(**local_conf)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1738577344.0
webtest-3.0.4/webtest/ext.py 0000644 0001750 0001750 00000000213 14750112700 015033 0 ustar 00gawel gawel __doc__ = 'webtest.ext is now in a separate package name webtest-casperjs'
def casperjs(*args, **kwargs):
raise ImportError(__doc__)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1738577344.0
webtest-3.0.4/webtest/forms.py 0000644 0001750 0001750 00000053034 14750112700 015372 0 ustar 00gawel gawel """Helpers to fill and submit forms."""
import operator
import re
from bs4 import BeautifulSoup
from collections import OrderedDict
from webtest import utils
class NoValue:
pass
class Upload:
"""
A file to upload::
>>> Upload('filename.txt', 'data', 'application/octet-stream')
>>> Upload('filename.txt', 'data')
>>> Upload("README.txt")
:param filename: Name of the file to upload.
:param content: Contents of the file.
:param content_type: MIME type of the file.
"""
def __init__(self, filename, content=None, content_type=None):
self.filename = filename
self.content = content
self.content_type = content_type
def __iter__(self):
yield self.filename
if self.content is not None:
yield self.content
yield self.content_type
# TODO: do we handle the case when we need to get
# contents ourselves?
def __repr__(self):
return '' % self.filename
class Field:
"""Base class for all Field objects.
.. attribute:: classes
Dictionary of field types (select, radio, etc)
.. attribute:: value
Set/get value of the field.
"""
classes = {}
def __init__(self, form, tag, name, pos,
value=None, id=None, **attrs):
self.form = form
self.tag = tag
self.name = name
self.pos = pos
self._value = value
self.id = id
self.attrs = attrs
def value__get(self):
if self._value is None:
return ''
else:
return self._value
def value__set(self, value):
self._value = value
value = property(value__get, value__set)
def force_value(self, value):
"""Like setting a value, except forces it (even for, say, hidden
fields).
"""
self._value = value
def __repr__(self):
value = f'<{self.__class__.__name__} name="{self.name}"'
if self.id:
value += ' id="%s"' % self.id
return value + '>'
class Select(Field):
"""Field representing `` `` form element."""
def __init__(self, *args, **attrs):
super().__init__(*args, **attrs)
self.options = []
self.optionPositions = []
# Undetermined yet:
self.selectedIndex = None
# we have no forced value
self._forced_value = NoValue
def force_value(self, value):
"""Like setting a value, except forces it (even for, say, hidden
fields).
"""
try:
self.value = value
self._forced_value = NoValue
except ValueError:
self.selectedIndex = None
self._forced_value = value
def select(self, value=None, text=None):
if value is not None and text is not None:
raise ValueError("Specify only one of value and text.")
if text is not None:
value = self._get_value_for_text(text)
self.value = value
def _get_value_for_text(self, text):
for i, (option_value, checked, option_text) in enumerate(self.options):
if option_text == utils.stringify(text):
return option_value
raise ValueError("Option with text %r not found (from %s)"
% (text, ', '.join(
[repr(t) for o, c, t in self.options])))
def value__set(self, value):
if self._forced_value is not NoValue:
self._forced_value = NoValue
for i, (option, checked, text) in enumerate(self.options):
if option == utils.stringify(value):
self.selectedIndex = i
break
else:
raise ValueError(
"Option %r not found (from %s)"
% (value, ', '.join([repr(o) for o, c, t in self.options])))
def value__get(self):
if self._forced_value is not NoValue:
return self._forced_value
elif self.selectedIndex is not None:
return self.options[self.selectedIndex][0]
else:
for option, checked, text in self.options:
if checked:
return option
else:
if self.options:
return self.options[0][0]
value = property(value__get, value__set)
class MultipleSelect(Field):
"""Field representing ````"""
def __init__(self, *args, **attrs):
super().__init__(*args, **attrs)
self.options = []
# Undetermined yet:
self.selectedIndices = []
self._forced_values = NoValue
def force_value(self, values):
"""Like setting a value, except forces it (even for, say, hidden
fields).
"""
self._forced_values = values
self.selectedIndices = []
def select_multiple(self, value=None, texts=None):
if value is not None and texts is not None:
raise ValueError("Specify only one of value and texts.")
if texts is not None:
value = self._get_value_for_texts(texts)
self.value = value
def _get_value_for_texts(self, texts):
str_texts = [utils.stringify(text) for text in texts]
value = []
for i, (option, checked, text) in enumerate(self.options):
if text in str_texts:
value.append(option)
str_texts.remove(text)
if str_texts:
raise ValueError(
"Option(s) %r not found (from %s)"
% (', '.join(str_texts),
', '.join([repr(t) for o, c, t in self.options])))
return value
def value__set(self, values):
if not values:
self._forced_values = None
elif self._forced_values is not NoValue:
self._forced_values = NoValue
str_values = [utils.stringify(value) for value in values]
self.selectedIndices = []
for i, (option, checked, text) in enumerate(self.options):
if option in str_values:
self.selectedIndices.append(i)
str_values.remove(option)
if str_values:
raise ValueError(
"Option(s) %r not found (from %s)"
% (', '.join(str_values),
', '.join([repr(o) for o, c, t in self.options])))
def value__get(self):
if self._forced_values is not NoValue:
return self._forced_values
elif self.selectedIndices:
return [self.options[i][0] for i in self.selectedIndices]
else:
selected_values = []
for option, checked, text in self.options:
if checked:
selected_values.append(option)
return selected_values if selected_values else None
value = property(value__get, value__set)
class Radio(Select):
"""Field representing `` ``"""
def value__get(self):
if self._forced_value is not NoValue:
return self._forced_value
elif self.selectedIndex is not None:
return self.options[self.selectedIndex][0]
else:
for option, checked, text in self.options:
if checked:
return option
else:
return None
value = property(value__get, Select.value__set)
class Checkbox(Field):
"""Field representing `` ``
.. attribute:: checked
Returns True if checkbox is checked.
"""
def __init__(self, *args, **attrs):
super().__init__(*args, **attrs)
self._checked = 'checked' in attrs
def value__set(self, value):
self._checked = not not value
def value__get(self):
if self._checked:
if self._value is None:
return 'on'
else:
return self._value
else:
return None
value = property(value__get, value__set)
def checked__get(self):
return bool(self._checked)
def checked__set(self, value):
self._checked = not not value
checked = property(checked__get, checked__set)
class Text(Field):
"""Field representing `` ``"""
class Email(Field):
"""Field representing `` ``"""
class File(Field):
"""Field representing `` ``"""
# TODO: This doesn't actually handle file uploads and enctype
def value__get(self):
if self._value is None:
return ''
else:
return self._value
value = property(value__get, Field.value__set)
class Textarea(Text):
"""Field representing ``