diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml
index 2342e35a..2fe299eb 100644
--- a/.github/workflows/python-app.yml
+++ b/.github/workflows/python-app.yml
@@ -20,8 +20,8 @@ jobs:
image: mysql:latest
env:
MYSQL_DATABASE: uweb_test
- MYSQL_USER: stef
- MYSQL_PASSWORD: password
+ MYSQL_USER: uweb3test
+ MYSQL_PASSWORD: uweb3test
MYSQL_ALLOW_EMPTY_PASSWORD: yes
ports:
- 3306:3306
@@ -35,7 +35,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- pip install flake8 pytest
+ pip install flake8 requests
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
@@ -47,3 +47,4 @@ jobs:
python3 -m unittest test.test_model
python3 -m unittest test.test_request
python3 -m unittest test.test_templateparser
+ python3 -m test.uweb3tests.serve & sleep 5 && python3 -m unittest test.test_uweb3tests
diff --git a/aquarium.sqlite b/aquarium.sqlite
new file mode 100644
index 00000000..bfaa6047
Binary files /dev/null and b/aquarium.sqlite differ
diff --git a/setup.py b/setup.py
index 1ae753d0..497d18e2 100644
--- a/setup.py
+++ b/setup.py
@@ -23,7 +23,7 @@ def Version():
"""Returns the version of the library as read from the __init__.py file"""
main_lib = os.path.join(os.path.dirname(__file__), "uweb3", "__init__.py")
with open(main_lib) as v_file:
- return re.match(".*__version__ = \"(.*?)\"", v_file.read(), re.S).group(1)
+ return re.match('.*__version__ = "(.*?)"', v_file.read(), re.S).group(1)
setup(
diff --git a/sqlite.db b/sqlite.db
new file mode 100644
index 00000000..e1c7ce35
Binary files /dev/null and b/sqlite.db differ
diff --git a/test/sqlite.db b/test/sqlite.db
deleted file mode 100644
index e69de29b..00000000
diff --git a/test/test_model.py b/test/test_model.py
index 9c3a9e21..b4622853 100755
--- a/test/test_model.py
+++ b/test/test_model.py
@@ -670,7 +670,11 @@ def testMultipleCommits(self):
def DatabaseConnection():
"""Returns an SQLTalk database connection to 'uWeb3_model_test'."""
return mysql.Connect(
- host="localhost", user="stef", passwd="password", db="uweb_test", charset="utf8"
+ host="localhost",
+ user="uweb3test",
+ passwd="uweb3test",
+ db="uweb_test",
+ charset="utf8",
)
diff --git a/test/test_model_alchemy.py b/test/test_model_alchemy.py
index 69a2cc72..99053307 100755
--- a/test/test_model_alchemy.py
+++ b/test/test_model_alchemy.py
@@ -242,7 +242,7 @@ def DatabaseConnection():
"""Returns an SQLTalk database connection to 'uWeb3_model_test'."""
return create_engine(
"mysql://{user}:{passwd}@{host}/{db}".format(
- host="localhost", user="stef", passwd="24192419", db="uweb_test"
+ host="localhost", user="uweb3test", passwd="uweb3test", db="uweb_test"
)
)
diff --git a/test/test_uweb3tests.py b/test/test_uweb3tests.py
new file mode 100755
index 00000000..9454d7c0
--- /dev/null
+++ b/test/test_uweb3tests.py
@@ -0,0 +1,488 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+"""This script attempts to connect to the webserver running form this same
+directory. It communicates over http and checks the output of each route.
+
+You can start the webserver by issuing: python3 serve.py
+You can test test all the functions by issuing: python3 testrunner.py -v
+"""
+import hashlib
+import os
+import unittest
+
+import requests
+
+baseurl = "http://127.0.0.1:8002/"
+BASE_DIR = os.path.join(os.path.dirname(__file__), "uweb3tests")
+
+
+def escape_html(string):
+ """Quick and very dirty html escaping"""
+ string = string.replace("'", "'")
+ string = string.replace("<", "<")
+ string = string.replace(">", ">")
+ string = string.replace('"', """)
+ return string
+
+
+def HashContent(string):
+ """Helper function for hashing of template files, this is needed to allow
+ users to download raw templates on their own which they know the hash for."""
+ return hashlib.sha256(string.encode("utf-8")).hexdigest()
+
+
+class ConfigTest(unittest.TestCase):
+ """Test if the server is using the config file"""
+
+ def test_port(self):
+ """The config for the test server tells us we are live on 8002"""
+ r = requests.get(baseurl)
+ self.assertEqual(r.status_code, 200)
+
+
+class ContentTests(unittest.TestCase):
+ """Test the content of routes"""
+
+ def test_content(self):
+ """Lets see if the index route returns valid content"""
+ r = requests.get(baseurl)
+ self.assertEqual(r.encoding, "utf-8")
+ with open(os.path.join(BASE_DIR, "templates/index.html"), "r") as template:
+ templatecontent = template.read()
+ self.assertEqual(r.status_code, 200)
+ self.assertMultiLineEqual(templatecontent, r.text)
+
+ def test_decorator_content(self):
+ """Lets see if the template decorated route returns valid content"""
+ url = baseurl + "templatedecorator"
+ r = requests.get(url)
+ self.assertEqual(r.encoding, "utf-8")
+ with open(os.path.join(BASE_DIR, "templates/index.html"), "r") as template:
+ templatecontent = template.read()
+ self.assertEqual(r.status_code, 200)
+ self.assertMultiLineEqual(templatecontent, r.text)
+
+ def test_post(self):
+ """Lets see if the posted data is reflected as json"""
+ url = baseurl + "post"
+ data = {"key": "value"}
+ r = requests.post(url, data)
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.headers["Content-Type"], "application/json; charset=utf-8")
+ self.assertEqual(r.json(), data)
+
+ def test_returnNone(self):
+ """Lets see if a None survives the response"""
+ url = baseurl + "none"
+ r = requests.get(url)
+ response = "None"
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.text, response)
+
+ def test_returnEmptystr(self):
+ """Lets see if an Empty string survives the response"""
+ url = baseurl + "emptystr"
+ r = requests.get(url)
+ response = ""
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.text, response)
+
+ def test_templateparser(self):
+ """This tests if the pagemaker can return dicts to the decorator,
+ It tests if the _PostInit variables are present, and if the sparse list is
+ populated based on the given tagname.
+ It also tests if the content is properly escaped due to not being htmlSafe
+ """
+ url = baseurl + "templateglobals"
+ r = requests.get(url)
+ response = """path test
+sparse path test
+pagemaker return test
+<b>escaped html test</b>"""
+
+ self.assertEqual(r.status_code, 200)
+ self.assertTrue(r.text.startswith(response))
+
+ @unittest.skip("Unknown encoding is broken since conversion to py3.")
+ def test_unknown_encoding(self):
+ """Lets see if our a record is served correctly as 'unknown' encoding"""
+ url = baseurl + "mysql/read/1/unknown"
+ r = requests.get(url)
+ returnvalue = "Tank({'ID': 1, 'name': 'Living Room'})"
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.text, returnvalue)
+ self.assertEqual(r.headers["Content-Type"], "unknown")
+
+
+class HeadersTests(unittest.TestCase):
+ """Test the headers"""
+
+ def test_404(self):
+ """Do we see 404 headers?"""
+ url = baseurl + "nonexistant"
+ r = requests.get(url)
+ self.assertEqual(r.status_code, 404)
+
+ def test_redirect_headers(self):
+ """Do we see redirect headers?"""
+ url = baseurl + "redirect"
+ r = requests.get(url, allow_redirects=False)
+ self.assertEqual(r.status_code, 307)
+
+ def test_redirect_location(self):
+ """Do we see redirect headers?"""
+ url = baseurl + "redirect"
+ r = requests.get(url)
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.url, baseurl)
+
+ def test_post(self):
+ """Lets see if the page returns a json/utf-8 content-type"""
+ url = baseurl + "post"
+ data = {"key": "value"}
+ r = requests.post(url, data)
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.headers["Content-Type"], "application/json; charset=utf-8")
+
+
+class RouteTests(unittest.TestCase):
+ """Test the method routing"""
+
+ def test_text_get_optional_missing(self):
+ """Lets see if the method route with GET returns valid content"""
+ url = baseurl + "text/arguments/1/word"
+ r = requests.get(url)
+ response = """('1', 'word', 'test')"""
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.text, response)
+
+ def test_text_get_optional(self):
+ """Lets see if the method route with GET returns valid content"""
+ url = baseurl + "text/arguments/1/word/optional"
+ r = requests.get(url)
+ response = """('1', 'word', 'optional')"""
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.text, response)
+
+
+class MethodTests(unittest.TestCase):
+ """Test the method routing"""
+
+ def test_get(self):
+ """Lets see if the method route with GET returns valid content"""
+ url = baseurl + "method"
+ r = requests.get(url)
+ self.assertEqual(r.encoding, "utf-8")
+ with open(os.path.join(BASE_DIR, "templates/index.html"), "r") as template:
+ templatecontent = template.read()
+ self.assertEqual(r.status_code, 200)
+ self.assertMultiLineEqual(templatecontent, r.text)
+
+ def test_post(self):
+ """Lets see if the method route with POST returns a json reflection of our
+ input"""
+ url = baseurl + "method"
+ data = {"key": "value"}
+ r = requests.post(url, data)
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.json(), data)
+ self.assertEqual(r.headers["Content-Type"], "application/json; charset=utf-8")
+
+ def test_put(self):
+ """Lets see if the method route with PUT returns the value from our put"""
+ url = baseurl + "method"
+ data = {"key": "value"}
+ r = requests.put(url, data)
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.text, data["key"])
+
+ def test_put_keyerror(self):
+ """Lets see if the method route with PUT returns the failover value from our put"""
+ url = baseurl + "method"
+ data = {"nokey": "value"}
+ r = requests.put(url, data)
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.text, "invalid")
+
+ def test_del(self):
+ """Lets see if the method route with DEL returns a text/plain content-type"""
+ url = baseurl + "method"
+ r = requests.delete(url)
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.text, "delete done")
+ self.assertEqual(r.headers["Content-Type"], "text/plain; charset=utf-8")
+
+
+class CookieTests(unittest.TestCase):
+ """Test the cookies."""
+
+ def test_setcookie(self):
+ """Lets see if cookieset route gives us a cookie."""
+ url = baseurl + "cookie/set"
+ data = '"this is an example cookie value set by uWeb"'
+ r = requests.get(url)
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.cookies["example"], data)
+
+ def test_setsecurecookie(self):
+ """Lets see if cookieset route gives us a cookie with the secure flag on."""
+ url = baseurl + "cookie/set/secure"
+ data = '"this is an example cookie value set by uWeb"'
+ r = requests.get(url)
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.cookies["example"], data)
+ for cookie in r.cookies:
+ self.assertTrue(cookie.secure)
+
+ def test_setcookie_redirect(self):
+ """Lets see if cookieset route gives us a cookie even after a redirect."""
+ url = baseurl + "cookie/set/redirect"
+ data = '"this is an example cookie value set by uWeb"'
+ r = requests.get(url, allow_redirects=False)
+ self.assertEqual(r.status_code, 307)
+ self.assertEqual(r.cookies["example"], data)
+
+ def test_templatedecoratedsetcookie(self):
+ """Lets see if cookieset route gives us a cookie when combined with the template decorator."""
+ url = baseurl + "cookie/set/templatedecorated"
+ data = '"this is an example cookie value set by uWeb"'
+ r = requests.get(url)
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.cookies["example"], data)
+
+ def test_reflectcookie(self):
+ """Lets see if cookies are parsed and the value is reflected
+
+ uWeb stores cookies quote encapsulated. But reads them without the quotes.
+ """
+ url = baseurl + "cookie/reflect"
+ jar = requests.cookies.RequestsCookieJar()
+ data = '"this is an example cookie value set by uWeb"'
+ jar.set("cookie", data)
+ r = requests.get(url, cookies=jar)
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.text, data[1:-1])
+
+ def test_secure_clear(self):
+ """Lets see if we can set a cookie, and not have it reflected when we dont send it in for a second request."""
+
+ url_set = baseurl + "signedcookie/set"
+ requests.get(url_set)
+
+ url_fetch = baseurl + "signedcookie/reflect"
+ r_fetch = requests.get(url_fetch)
+
+ self.assertEqual(r_fetch.text, "")
+ self.assertEqual(r_fetch.cookies.keys(), [])
+ self.assertEqual(r_fetch.status_code, 200)
+
+ def test_secure_cookie(self):
+ """Lets see if secure cookieset reflects our cookie when send ntampered for a second request.
+ This also checks"""
+ session = requests.session()
+
+ url_set = baseurl + "signedcookie/set"
+ r_set = session.get(url_set)
+
+ url_fetch = baseurl + "signedcookie/reflect"
+ r_fetch = session.get(url_fetch)
+
+ content = escape_html("{'key': 'this is an example cookie value set by uWeb'}")
+ self.assertEqual(r_fetch.text, content)
+ self.assertEqual(r_set.cookies.keys(), ["signedExample"])
+ self.assertEqual(r_fetch.status_code, 200)
+
+ def test_secure_cookie_tampered(self):
+ """Lets see if Signed cookieset route gives us a cookie even after a redirect."""
+ jar = requests.cookies.RequestsCookieJar()
+ jar.set("signedExample", "tampered")
+ url_fetch = baseurl + "signedcookie/reflect"
+ r_fetch = requests.get(url_fetch, cookies=jar)
+ self.assertEqual(r_fetch.text, "")
+ self.assertEqual(r_fetch.status_code, 200)
+
+
+class StaticTests(unittest.TestCase):
+ """Test the static handler."""
+
+ def test_textfile(self):
+ """Lets see if our text file is served correctly"""
+ url = baseurl + "static/text.txt"
+ r = requests.get(url)
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.text.rstrip(), "text content")
+ self.assertEqual(r.headers["Content-Type"], "text/plain; charset=utf-8")
+
+ def test_missingfile(self):
+ """Lets see if our text file is served correctly"""
+ url = baseurl + "static/invalid.txt"
+ r = requests.get(url)
+ self.assertEqual(r.status_code, 404)
+
+ def test_image(self):
+ """Lets see if the image is returned"""
+ url = baseurl + "static/favicon.png"
+ r = requests.get(url)
+ with open(os.path.join(BASE_DIR, "static/favicon.png"), "rb") as template:
+ imagecontent = template.read()
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.content, imagecontent)
+ self.assertEqual(r.headers["Content-Type"], "image/png")
+
+ def test_svgfile(self):
+ """Lets see if our SVG file is served correctly"""
+ url = baseurl + "static/SVG_Logo.svg"
+ r = requests.get(url)
+ with open(os.path.join(BASE_DIR, "static/SVG_Logo.svg"), "r") as template:
+ svgcontent = template.read()
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.text, svgcontent)
+ self.assertEqual(r.headers["Content-Type"], "image/svg+xml")
+
+ def test_xmlfile(self):
+ """Lets see if our xml file is served correctly"""
+ url = baseurl + "static/text.xml"
+ r = requests.get(url)
+ with open(os.path.join(BASE_DIR, "static/text.xml"), "r") as template:
+ xmlcontent = template.read()
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.text, xmlcontent)
+ self.assertEqual(r.headers["Content-Type"], "application/xml")
+
+
+class SQLitetests(unittest.TestCase):
+ def test_record(self):
+ """Lets see if our complete record is served correctly"""
+ url = baseurl + "sqlite/read/1"
+ r = requests.get(url)
+ returnvalue = escape_html(
+ """Fish({'ID': 1, 'name': 'sammy', 'species': 'shark', 'tank': Tank({'ID': 1, 'name': 'Living Room sqlite'})})"""
+ )
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.text, returnvalue)
+
+ def test_missingfile(self):
+ """Lets see if our missing record is served correctly"""
+ url = baseurl + "sqlite/read/3"
+ r = requests.get(url)
+ self.assertEqual(r.status_code, 404)
+
+
+class Mysqltests(unittest.TestCase):
+ def test_record(self):
+ """Lets see if our complete record is served correctly"""
+ url = baseurl + "mysql/read/1"
+ r = requests.get(url)
+ returnvalue = escape_html("""Tank({'ID': 1, 'name': 'Living Room'})""")
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.text, returnvalue)
+
+ def test_missingrecord(self):
+ """Lets see if our missing record is served correctly"""
+ url = baseurl + "mysql/read/-1"
+ r = requests.get(url)
+ self.assertEqual(r.status_code, 404)
+
+ def test_post_write(self):
+ """Lets see if the page returns a posted and insterted mysql record"""
+ url = baseurl + "mysql/write"
+ data = {"name": "Squid Tank"}
+ r = requests.post(url, data)
+ self.assertEqual(r.status_code, 200)
+ self.assertTrue(escape_html("""'name': 'Squid Tank'""") in r.text)
+
+
+class EscapingTest(unittest.TestCase):
+ def test_get_optional_missing(self):
+ """Lets see if the method route with GET returns valid content"""
+ url = baseurl + "arguments/1/word"
+ r = requests.get(url)
+ response = escape_html("""('1', 'word', 'test')""")
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.text, response)
+
+ def test_get_optional(self):
+ """Lets see if the method route with GET returns valid content"""
+ url = baseurl + "arguments/1/word/optional"
+ r = requests.get(url)
+ response = escape_html("""('1', 'word', 'optional')""")
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.text, response)
+
+ def test_get_jsonstring(self):
+ """Lets see if a string dumped ot trough the json headers is correctly escaped and encasulated in json"""
+ url = baseurl + "jsonstring"
+ r = requests.get(url)
+ response = '{"message": "Hello, World!"}'
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r.json(), response)
+ self.assertEqual(r.headers["Content-Type"], "application/json; charset=utf-8")
+
+ def test_post_write_html_reflection(self):
+ """Lets see if the page returns a posted and insterted mysql record"""
+ url = baseurl + "mysql/write"
+ data = {"name": "Squid Tank"}
+ r = requests.post(url, data)
+ self.assertEqual(r.status_code, 200)
+ self.assertTrue("Tank" not in r.text)
+
+
+class ErrorTest(unittest.TestCase):
+ def test_500(self):
+ """Lets see if a deliberate error page is returned correctly."""
+ url = baseurl + "error"
+ response = escape_html("name 'test' is not defined")
+ r = requests.get(url)
+ self.assertEqual(r.status_code, 500)
+ self.assertTrue(response in r.text)
+
+
+class PathTraversalTest(unittest.TestCase):
+ def test_500(self):
+ """Lets see if a deliberate invalid template returns an error"""
+ url = baseurl + "templatetraversal"
+ response = escape_html("Could not load template")
+ invalidresponse = escape_html("[development]")
+ r = requests.get(url)
+ self.assertEqual(r.status_code, 500)
+ self.assertTrue(response in r.text)
+ self.assertFalse(invalidresponse in r.text)
+
+ def test_statictraversal(self):
+ """Lets see if our text file is served correctly"""
+ url = baseurl + "static/../config.ini"
+ invalidresponse = escape_html("[development]")
+ r = requests.get(url)
+ self.assertEqual(r.status_code, 404)
+ self.assertFalse(invalidresponse in r.text)
+
+
+class SmartClientTests(unittest.TestCase):
+ """Test the 'smart' client functionality"""
+
+ def test_get(self):
+ """Lets see if the method route with GET returns valid content"""
+ url = baseurl + "templateglobals"
+ r = requests.get(url, headers={"Accept": "application/json"})
+ self.assertEqual(r.encoding, "utf-8")
+
+ with open(os.path.join(BASE_DIR, "templates/test.html"), "r") as template:
+ templatehash = HashContent(template.read())
+
+ returnvalue = (
+ """{"template": "/test.html", "replacements": {"[header:part]": "path test", "[header:1:test:2]": "sparse path test", "[test]": "pagemaker return test", "[footer]": "<b>escaped html test</b>"}, "template_hash": "%s"}"""
+ % templatehash
+ )
+
+ self.assertEqual(r.status_code, 200)
+ self.assertMultiLineEqual(returnvalue, r.text)
+
+ templateurl = baseurl + "template/%s/%s" % (templatehash, "test.html")
+ tr = requests.get(templateurl)
+ self.assertEqual(tr.encoding, "utf-8")
+ self.assertEqual(tr.status_code, 200)
+ self.assertMultiLineEqual(templatehash, HashContent(tr.text))
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/uweb3tests/__init__.py b/test/uweb3tests/__init__.py
new file mode 100644
index 00000000..fec94e09
--- /dev/null
+++ b/test/uweb3tests/__init__.py
@@ -0,0 +1,63 @@
+"""A minimal uWeb3 project scaffold."""
+import os
+
+# Third-party modules
+import uweb3
+
+# Application
+from . import pages
+
+__version__ = "0.1"
+
+
+def main():
+ """Creates a uWeb3 application.
+
+ The application is created from the following components:
+
+ - The presenter class (PageMaker) which implements the request handlers.
+ - The routes iterable, where each 2-tuple defines a url-pattern and the
+ name of a presenter method which should handle it.
+ - The execution path, internally used to find templates etc.
+ """
+ return uweb3.uWeb(
+ pages.PageMaker,
+ [
+ ("/", "Index"),
+ ("/post", "Post"),
+ ("/method", "Index", "GET"),
+ ("/method", "Post", "POST"),
+ ("/method", "Put", "PUT"),
+ ("/method", "Delete", "DELETE"),
+ ("/arguments/(\d+)/(\w+)/?(.*?)", "HtmlArgumentReflect"),
+ ("/text/arguments/(\d+)/(\w+)/?(.*?)", "TextArgumentReflect"),
+ ("/cookie/set", "Cookieset"),
+ ("/cookie/set/secure", "Cookiesetsecure"),
+ ("/cookie/reflect", "Cookiereflect"),
+ ("/cookie/set/redirect", "CookiesetRedirect"),
+ ("/cookie/set/templatedecorated", "TemplateDecoratedCookieset"),
+ ("/signedcookie/set", "SignedCookieset"),
+ ("/signedcookie/reflect", "SignedCookieReflect"),
+ ("/templatedecorator", "TemplateDecorator"),
+ ("/templateglobals", "TemplateGlobals"),
+ ("/templatetraversal", "TemplateTraversal"),
+ ("/sqlite/read/(.*)", "SqliteRead"),
+ ("/mysql/read/(\d+)/?(.*)?", "MysqlRead"),
+ ("/mysql/write", "MysqlWrite", "POST"),
+ ("/jsonapi/read/(.*)", "JsonApiRead"),
+ ("/redirect", "Redirect"),
+ ("/static/(.*)", "Static"),
+ ("/error", "ThrowError"),
+ ("/none", "ReturnNone"),
+ ("/emptystr", "ReturnEmptyStr"),
+ ("/json", "ReturnJson"),
+ ("/jsonstring", "ReturnJsonString"),
+ ("/contenttypedecorator", "ReturnJsonDecorated"),
+ ("/sparsetests", "SparseTests"),
+ ("/sparseteststarget", "SparseTestsTarget"),
+ ("/template/(\w+)/(.*?)", "SparseTemplateProvider"),
+ ("/sparse.js", "SparseRenderedProvider"),
+ ("/(.*)", "FourOhFour"),
+ ],
+ os.path.dirname(__file__),
+ )
diff --git a/test/uweb3tests/config.ini b/test/uweb3tests/config.ini
new file mode 100644
index 00000000..effd14be
--- /dev/null
+++ b/test/uweb3tests/config.ini
@@ -0,0 +1,22 @@
+[development]
+host = 127.0.0.1
+port = 8002
+error_logging = False
+
+[log]
+access_logging = False
+error_logging = False
+
+[sqlite]
+database = aquarium.sqlite
+
+[mysql]
+database = uweb_test
+user = uweb3test
+password = uweb3test
+
+[restfulljson]
+url = https://jsonplaceholder.typicode.com/
+
+[signedCookie]
+secret =
diff --git a/test/uweb3tests/model.py b/test/uweb3tests/model.py
new file mode 100644
index 00000000..cd5f2356
--- /dev/null
+++ b/test/uweb3tests/model.py
@@ -0,0 +1,62 @@
+"""Model for the uweb3 test server"""
+
+from uweb3 import model
+
+
+class SignedExample(model.SecureCookie):
+ """Model for the Singed Cookie example"""
+
+
+class Fish(model.Record):
+ """Model for the Sqlite example"""
+
+ _CONNECTOR = "sqlite"
+
+ @classmethod
+ def create_table(cls, connection):
+ with connection as cursor:
+ cursor.Execute("DROP TABLE IF EXISTS fish")
+ cursor.Execute("DROP TABLE IF EXISTS tank")
+ cursor.Execute(
+ "CREATE TABLE fish(ID INTEGER, name TEXT, species TEXT, tank INTEGER)"
+ )
+ cursor.Execute("CREATE TABLE tank(ID INTEGER, name TEXT)")
+ cursor.Execute(
+ """INSERT INTO tank(ID, name) VALUES (1, "Living Room sqlite")"""
+ )
+ cursor.Execute(
+ """INSERT INTO fish(ID, name, species, tank) VALUES (1, "sammy", "shark", 1)"""
+ )
+
+
+class Tank(model.Record):
+ """Model for the Mysql example"""
+
+ @classmethod
+ def create_table(cls, connection):
+ with connection as cursor:
+ cursor.Execute("DROP TABLE IF EXISTS tank")
+ cursor.Execute(
+ """
+ CREATE TABLE `tank` (
+ `ID` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(45) DEFAULT NULL,
+ PRIMARY KEY (`ID`)
+ )
+ """
+ )
+ cursor.Execute(
+ "INSERT INTO `tank` VALUES (1,'Living Room'),(2,'Squid Tank');"
+ )
+
+
+class Posts(model.Record):
+ """Model for the Mysql example"""
+
+ _CONNECTOR = "restfulljson"
+
+
+class Albums(model.Record):
+ """Model for the Mysql example"""
+
+ _CONNECTOR = "restfulljson"
diff --git a/test/uweb3tests/pages.py b/test/uweb3tests/pages.py
new file mode 100644
index 00000000..42ba80a7
--- /dev/null
+++ b/test/uweb3tests/pages.py
@@ -0,0 +1,188 @@
+#!/usr/bin/python
+"""Request handlers for the uWeb3 project scaffold"""
+# standard modules
+import json
+
+# modules
+import uweb3
+
+# package imports
+from . import model
+
+
+class PageMaker(uweb3.DebuggingPageMaker, uweb3.SparseAsyncPages):
+ """Holds all the request handlers for the application"""
+
+ def _PostInit(self):
+ """Register some vars to be used in the template parser later on"""
+ self.parser.RegisterTag("header:part", "path test")
+ self.parser.RegisterTag("header:1:test:2", "sparse path test")
+ self.parser.RegisterTag("footer", "escaped html test")
+ model.Fish.create_table(self.connection)
+ model.Tank.create_table(self.connection)
+
+ def Index(self):
+ """Returns the index template"""
+ return self.parser.Parse("index.html")
+
+ def Post(self):
+ """Returns the posted data as json"""
+ return uweb3.Response(
+ uweb3.JSONsafestring(json.dumps(self.post, cls=uweb3.JsonEncoder)),
+ content_type="application/json",
+ )
+
+ def Put(self):
+ """Returns the put data as text"""
+ return uweb3.Response(
+ self.put.getfirst("key", "invalid"), content_type="text/plain"
+ )
+
+ def Delete(self):
+ """Returns the delete string as text"""
+ return uweb3.Response("delete done", content_type="text/plain")
+
+ def Cookieset(self):
+ """Returns the index template and sets a cookie
+
+ As cookies should not contain UTF8 we just use ascii."""
+ self.req.AddCookie("example", "this is an example cookie value set by uWeb")
+ return self.parser.Parse("index.html")
+
+ def Cookiesetsecure(self):
+ """Returns the index template and sets a cookie with a secure flag."""
+ self.req.AddCookie(
+ "example", "this is an example cookie value set by uWeb", secure=True
+ )
+ return self.parser.Parse("index.html")
+
+ def Cookiereflect(self):
+ """Returns the cookies value."""
+ return self.cookies["cookie"]
+
+ def CookiesetRedirect(self):
+ """Returns to the index and sets a cookie"""
+ self.req.AddCookie("example", "this is an example cookie value set by uWeb")
+ return self.req.Redirect("/")
+
+ def SignedCookieset(self):
+ """Returns the index template and sets a signed cookie
+
+ As signed cookies should not contain UTF8 we just use ascii."""
+ data = "this is an example cookie value set by uWeb"
+ model.SignedExample.Create(self.connection, {"key": data})
+ return self.parser.Parse("index.html")
+
+ def SignedCookieReflect(self):
+ """Returns signed cookie if valid."""
+ return model.SignedExample(self.connection)
+
+ def HtmlArgumentReflect(self, numeric, string, optional="test"):
+ """Returns the url arguments as parsed by the router."""
+ return (numeric, string, optional)
+
+ def TextArgumentReflect(self, numeric, string, optional="test"):
+ """Returns the url arguments as parsed by the router."""
+ return uweb3.Response((numeric, string, optional), content_type="text/plain")
+
+ @uweb3.decorators.TemplateParser("index.html")
+ def TemplateDecoratedCookieset(self):
+ """Returns the index template and sets a cookie"""
+ self.req.AddCookie("example", "this is an example cookie value set by uWeb")
+
+ @uweb3.decorators.TemplateParser("index.html")
+ def TemplateDecorator(self):
+ """Returns the index template"""
+
+ @uweb3.decorators.TemplateParser("test.html")
+ def TemplateGlobals(self):
+ """Returns the index template"""
+ return {"test": "pagemaker return test"}
+
+ @uweb3.decorators.TemplateParser("sparse.html")
+ def SparseTests(self):
+ """Returns the sparse template"""
+
+ @uweb3.decorators.TemplateParser("sparse_target.html")
+ def SparseTestsTarget(self):
+ """Returns the sparse_target template"""
+
+ @uweb3.decorators.TemplateParser("../config.ini")
+ def TemplateTraversal(self):
+ """Tries to load an invalid template by traversing up the tree"""
+ return {"test": "pagemaker return test"}
+
+ def Redirect(self):
+ """Redirects to the homepage"""
+ return uweb3.Redirect("/")
+
+ def SqliteRead(self, fish=1):
+ """Reads form the SQLite db"""
+ try:
+ return model.Fish.FromPrimary(self.connection, int(fish))
+ except uweb3.model.NotExistError:
+ return uweb3.Response(
+ "No such fish", httpcode=404, content_type="text/plain"
+ )
+
+ def MysqlRead(self, tank=1, contenttype="html"):
+ """Reads form the Mysql db"""
+ try:
+ record = model.Tank.FromPrimary(self.connection, int(tank))
+ if contenttype != "html":
+ return uweb3.Response(record, content_type=contenttype)
+ return record
+ except uweb3.model.NotExistError:
+ return uweb3.Response(
+ "No such tank", httpcode=404, content_type="text/plain"
+ )
+
+ def MysqlWrite(self):
+ """Writes to the Mysql db"""
+ newtank = model.Tank.Create(
+ self.connection, {"name": self.post.getfirst("name", "Empty post")}
+ )
+ return newtank
+
+ def JsonApiRead(self, post=1):
+ """Reads form the Json API"""
+ try:
+ return model.Posts.FromPrimary(self.connection, int(post))
+ except uweb3.model.NotExistError:
+ return uweb3.Response(
+ "No such post", httpcode=404, content_type="text/plain"
+ )
+
+ def ThrowError(self):
+ """The request could not be fulfilled, this returns a 500."""
+ raise Exception("name 'test' is not defined")
+
+ def ReturnNone(self):
+ """returns a Python None."""
+ return None
+
+ def ReturnEmptyStr(self):
+ """returns a Python empty str."""
+ return ""
+
+ def ReturnJsonString(self):
+ """Returns a string, escaped by the json handler to become a json safe tring instead."""
+ return uweb3.Response(
+ '{"message": "Hello, World!"}', content_type="application/json"
+ )
+
+ def ReturnJson(self):
+ """Returns a json page"""
+ return uweb3.Response(
+ {"message": "Hello, World!"}, content_type="application/json"
+ )
+
+ @uweb3.decorators.ContentType("application/json")
+ def ReturnJsonDecorated(self):
+ """Returns a json page"""
+ return {"message": "Hello, World!"}
+
+ def FourOhFour(self, path):
+ """The request could not be fulfilled, this returns a 404."""
+ self.req.response.httpcode = 404
+ return self.parser.Parse("404.html", path=path)
diff --git a/test/uweb3tests/serve.py b/test/uweb3tests/serve.py
new file mode 100644
index 00000000..5aa92517
--- /dev/null
+++ b/test/uweb3tests/serve.py
@@ -0,0 +1,16 @@
+#!/usr/bin/python3
+"""Starts a simple application development server.
+Just execute `./serve.py` or `python3 serve.py`
+"""
+
+# Application
+from test import uweb3tests
+
+
+def main():
+ app = uweb3tests.main()
+ app.serve()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/test/uweb3tests/setup/schema.sql b/test/uweb3tests/setup/schema.sql
new file mode 100644
index 00000000..6d595161
--- /dev/null
+++ b/test/uweb3tests/setup/schema.sql
@@ -0,0 +1,51 @@
+-- MySQL dump 10.13 Distrib 5.7.31, for Linux (x86_64)
+--
+-- Host: localhost Database: aquarium
+-- ------------------------------------------------------
+-- Server version 5.7.31-0ubuntu0.18.04.1
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Table structure for table `tank`
+--
+
+DROP TABLE IF EXISTS `tank`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `tank` (
+ `ID` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(45) DEFAULT NULL,
+ PRIMARY KEY (`ID`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `tank`
+--
+
+LOCK TABLES `tank` WRITE;
+/*!40000 ALTER TABLE `tank` DISABLE KEYS */;
+INSERT INTO `tank` VALUES (1,'Living Room'),(2,'Squid Tank');
+/*!40000 ALTER TABLE `tank` ENABLE KEYS */;
+UNLOCK TABLES;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2020-10-27 16:52:37
diff --git a/test/uweb3tests/static/SVG_Logo.svg b/test/uweb3tests/static/SVG_Logo.svg
new file mode 100644
index 00000000..a05c274f
--- /dev/null
+++ b/test/uweb3tests/static/SVG_Logo.svg
@@ -0,0 +1,56 @@
+
diff --git a/test/uweb3tests/static/favicon.png b/test/uweb3tests/static/favicon.png
new file mode 100644
index 00000000..a1a95277
Binary files /dev/null and b/test/uweb3tests/static/favicon.png differ
diff --git a/test/uweb3tests/static/text.txt b/test/uweb3tests/static/text.txt
new file mode 100644
index 00000000..c182a933
--- /dev/null
+++ b/test/uweb3tests/static/text.txt
@@ -0,0 +1 @@
+text content
diff --git a/test/uweb3tests/static/text.xml b/test/uweb3tests/static/text.xml
new file mode 100644
index 00000000..d3f0eca2
--- /dev/null
+++ b/test/uweb3tests/static/text.xml
@@ -0,0 +1,7 @@
+
+
+ The URL you requested ("[path]") doesn't exist. +
+ + + diff --git a/test/uweb3tests/templates/index.html b/test/uweb3tests/templates/index.html new file mode 100644 index 00000000..3a968c3f --- /dev/null +++ b/test/uweb3tests/templates/index.html @@ -0,0 +1,12 @@ + + + + +