import socket
import pytest
from unit.control import Control
prerequisites = {'modules': {'python': 'any'}}
client = Control()
def try_addr(addr):
return client.conf(
{
"listeners": {addr: {"pass": "routes"}},
"routes": [{"action": {"return": 200}}],
"applications": {},
}
)
def test_json_empty():
assert 'error' in client.conf(''), 'empty'
def test_json_leading_zero():
assert 'error' in client.conf('00'), 'leading zero'
def test_json_unicode():
assert 'success' in client.conf(
"""
{
"ap\u0070": {
"type": "\u0070ython",
"processes": { "spare": 0 },
"path": "\u002Fapp",
"module": "wsgi"
}
}
""",
'applications',
), 'unicode'
assert client.conf_get('applications') == {
"app": {
"type": "python",
"processes": {"spare": 0},
"path": "/app",
"module": "wsgi",
}
}, 'unicode get'
def test_json_unicode_2():
assert 'success' in client.conf(
{
"приложение": {
"type": "python",
"processes": {"spare": 0},
"path": "/app",
"module": "wsgi",
}
},
'applications',
), 'unicode 2'
assert 'приложение' in client.conf_get('applications')
def test_json_unicode_number():
assert 'success' in client.conf(
"""
{
"app": {
"type": "python",
"processes": { "spare": \u0030 },
"path": "/app",
"module": "wsgi"
}
}
""",
'applications',
), 'unicode number'
def test_json_utf8_bom():
assert 'success' in client.conf(
b"""\xEF\xBB\xBF
{
"app": {
"type": "python",
"processes": {"spare": 0},
"path": "/app",
"module": "wsgi"
}
}
""",
'applications',
), 'UTF-8 BOM'
def test_json_comment_single_line():
assert 'success' in client.conf(
b"""
// this is bridge
{
"//app": {
"type": "python", // end line
"processes": {"spare": 0},
// inside of block
"path": "/app",
"module": "wsgi"
}
// double //
}
// end of json \xEF\t
""",
'applications',
), 'single line comments'
def test_json_comment_multi_line():
assert 'success' in client.conf(
b"""
/* this is bridge */
{
"/*app": {
/**
* multiple lines
**/
"type": "python",
"processes": /* inline */ {"spare": 0},
"path": "/app",
"module": "wsgi"
/*
// end of block */
}
/* blah * / blah /* blah */
}
/* end of json \xEF\t\b */
""",
'applications',
), 'multi line comments'
def test_json_comment_invalid():
assert 'error' in client.conf(b'/{}', 'applications'), 'slash'
assert 'error' in client.conf(b'//{}', 'applications'), 'comment'
assert 'error' in client.conf(b'{} /', 'applications'), 'slash end'
assert 'error' in client.conf(b'/*{}', 'applications'), 'slash star'
assert 'error' in client.conf(b'{} /*', 'applications'), 'slash star end'
def test_applications_open_brace():
assert 'error' in client.conf('{', 'applications'), 'open brace'
def test_applications_string():
assert 'error' in client.conf('"{}"', 'applications'), 'string'
@pytest.mark.skip('not yet, unsafe')
def test_applications_type_only():
assert 'error' in client.conf(
{"app": {"type": "python"}}, 'applications'
), 'type only'
def test_applications_miss_quote():
assert 'error' in client.conf(
"""
{
app": {
"type": "python",
"processes": { "spare": 0 },
"path": "/app",
"module": "wsgi"
}
}
""",
'applications',
), 'miss quote'
def test_applications_miss_colon():
assert 'error' in client.conf(
"""
{
"app" {
"type": "python",
"processes": { "spare": 0 },
"path": "/app",
"module": "wsgi"
}
}
""",
'applications',
), 'miss colon'
def test_applications_miss_comma():
assert 'error' in client.conf(
"""
{
"app": {
"type": "python"
"processes": { "spare": 0 },
"path": "/app",
"module": "wsgi"
}
}
""",
'applications',
), 'miss comma'
def test_applications_skip_spaces():
assert 'success' in client.conf(b'{ \n\r\t}', 'applications'), 'skip spaces'
def test_applications_relative_path():
assert 'success' in client.conf(
{
"app": {
"type": "python",
"processes": {"spare": 0},
"path": "../app",
"module": "wsgi",
}
},
'applications',
), 'relative path'
@pytest.mark.skip('not yet, unsafe')
def test_listeners_empty():
assert 'error' in client.conf({"*:8080": {}}, 'listeners'), 'listener empty'
def test_listeners_no_app():
assert 'error' in client.conf(
{"*:8080": {"pass": "applications/app"}}, 'listeners'
), 'listeners no app'
def test_listeners_unix_abstract(system):
if system != 'Linux':
assert 'error' in try_addr("unix:@sock"), 'abstract at'
pytest.skip('not yet')
assert 'error' in try_addr("unix:\0soc"), 'abstract \0'
assert 'error' in try_addr("unix:\u0000soc"), 'abstract \0 unicode'
def test_listeners_addr():
assert 'success' in try_addr("*:8080"), 'wildcard'
assert 'success' in try_addr("127.0.0.1:8081"), 'explicit'
assert 'success' in try_addr("[::1]:8082"), 'explicit ipv6'
def test_listeners_addr_error():
assert 'error' in try_addr("127.0.0.1"), 'no port'
def test_listeners_addr_error_2(skip_alert):
skip_alert(r'bind.*failed', r'failed to apply new conf')
assert 'error' in try_addr("[f607:7403:1e4b:6c66:33b2:843f:2517:da27]:8080")
def test_listeners_port_release():
for _ in range(10):
fail = False
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
client.conf(
{
"listeners": {"127.0.0.1:8080": {"pass": "routes"}},
"routes": [],
}
)
resp = client.conf({"listeners": {}, "applications": {}})
try:
s.bind(('127.0.0.1', 8080))
s.listen()
except OSError:
fail = True
if fail:
pytest.fail('cannot bind or listen to the address')
assert 'success' in resp, 'port release'
def test_json_application_name_large():
name = "X" * 1024 * 1024
assert 'success' in client.conf(
{
"listeners": {"*:8080": {"pass": f"applications/{name}"}},
"applications": {
name: {
"type": "python",
"processes": {"spare": 0},
"path": "/app",
"module": "wsgi",
}
},
}
)
@pytest.mark.skip('not yet')
def test_json_application_many():
apps = 999
conf = {
"applications": {
f"app-{a}": {
"type": "python",
"processes": {"spare": 0},
"path": "/app",
"module": "wsgi",
}
for a in range(apps)
},
"listeners": {
f"*:{(7000 + a)}": {"pass": f"applications/app-{a}"}
for a in range(apps)
},
}
assert 'success' in client.conf(conf)
def test_json_application_python_prefix():
conf = {
"applications": {
"sub-app": {
"type": "python",
"processes": {"spare": 0},
"path": "/app",
"module": "wsgi",
"prefix": "/app",
}
},
"listeners": {"*:8080": {"pass": "routes"}},
"routes": [
{
"match": {"uri": "/app/*"},
"action": {"pass": "applications/sub-app"},
}
],
}
assert 'success' in client.conf(conf)
def test_json_application_prefix_target():
conf = {
"applications": {
"sub-app": {
"type": "python",
"processes": {"spare": 0},
"path": "/app",
"targets": {
"foo": {"module": "foo.wsgi", "prefix": "/app"},
"bar": {
"module": "bar.wsgi",
"callable": "bar",
"prefix": "/api",
},
},
}
},
"listeners": {"*:8080": {"pass": "routes"}},
"routes": [
{
"match": {"uri": "/app/*"},
"action": {"pass": "applications/sub-app/foo"},
},
{
"match": {"uri": "/api/*"},
"action": {"pass": "applications/sub-app/bar"},
},
],
}
assert 'success' in client.conf(conf)
def test_json_application_invalid_python_prefix():
conf = {
"applications": {
"sub-app": {
"type": "python",
"processes": {"spare": 0},
"path": "/app",
"module": "wsgi",
"prefix": "app",
}
},
"listeners": {"*:8080": {"pass": "applications/sub-app"}},
}
assert 'error' in client.conf(conf)
def test_json_application_empty_python_prefix():
conf = {
"applications": {
"sub-app": {
"type": "python",
"processes": {"spare": 0},
"path": "/app",
"module": "wsgi",
"prefix": "",
}
},
"listeners": {"*:8080": {"pass": "applications/sub-app"}},
}
assert 'error' in client.conf(conf)
def test_json_application_many2():
conf = {
"applications": {
f"app-{a}": {
"type": "python",
"processes": {"spare": 0},
"path": "/app",
"module": "wsgi",
}
# Larger number of applications can cause test fail with default
# open files limit due to the lack of file descriptors.
for a in range(100)
},
"listeners": {"*:8080": {"pass": "applications/app-1"}},
}
assert 'success' in client.conf(conf)
def test_unprivileged_user_error(require, skip_alert):
require({'privileged_user': False})
skip_alert(r'cannot set user "root"', r'failed to apply new conf')
assert 'error' in client.conf(
{
"app": {
"type": "external",
"processes": 1,
"executable": "/app",
"user": "root",
}
},
'applications',
), 'setting user'