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 @@ + + + SVG Logo + Designed for the SVG Logo Contest in 2006 by Harvey Rayner, and adopted by W3C in 2009. It is available under the Creative Commons license for those who have an SVG product or who are using SVG on their site. + + + + + SVG Logo + 14-08-2009 + + W3C + Harvey Rayner, designer + + See document description + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + Tove + Jani + Reminder + Don't forget me this weekend! + diff --git a/test/uweb3tests/templates/404.html b/test/uweb3tests/templates/404.html new file mode 100644 index 00000000..47713207 --- /dev/null +++ b/test/uweb3tests/templates/404.html @@ -0,0 +1,15 @@ + + + + + µWeb3 project scaffold + + + +

This is not the page you're looking for (HTTP 404)

+

+ 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 @@ + + + + + qwe project test + + + +

Hello µWeb3.

+ + + diff --git a/test/uweb3tests/templates/sparse.html b/test/uweb3tests/templates/sparse.html new file mode 100644 index 00000000..9cb3007e --- /dev/null +++ b/test/uweb3tests/templates/sparse.html @@ -0,0 +1,6 @@ + + + + To sparse target + + diff --git a/test/uweb3tests/templates/sparse_target.html b/test/uweb3tests/templates/sparse_target.html new file mode 100644 index 00000000..c92eae8e --- /dev/null +++ b/test/uweb3tests/templates/sparse_target.html @@ -0,0 +1,11 @@ + + + + To sparse tests +
+ + + +
+ + diff --git a/test/uweb3tests/templates/test.html b/test/uweb3tests/templates/test.html new file mode 100644 index 00000000..505cf93a --- /dev/null +++ b/test/uweb3tests/templates/test.html @@ -0,0 +1,4 @@ +[header:part]
+[header:1:test:2]
+[test]
+[footer] diff --git a/uweb3/logger.py b/uweb3/logger.py index 01d954e3..2f94d1fe 100644 --- a/uweb3/logger.py +++ b/uweb3/logger.py @@ -1,7 +1,6 @@ import logging from typing import NamedTuple - from uweb3.request import IndexedFieldStorage DEBUG_FORMAT = ( diff --git a/uweb3/pagemaker/__init__.py b/uweb3/pagemaker/__init__.py index cebff008..907525e1 100644 --- a/uweb3/pagemaker/__init__.py +++ b/uweb3/pagemaker/__init__.py @@ -18,12 +18,12 @@ import uweb3 from uweb3.logger import ( + DebuggingDetails, + UwebDebuggingAdapter, + default_data_scrubber, setup_debug_logger, setup_debug_stream_logger, setup_error_logger, - UwebDebuggingAdapter, - DebuggingDetails, - default_data_scrubber, ) from uweb3.request import IndexedFieldStorage