mirror of https://github.com/h44z/wg-portal.git
				
				
				
			UI unit tests (#59)
* tests - add pytests for the UI * tests/api - fix NotImplemented * tests - add README Co-authored-by: Markus Koetter <koetter@cispa.de>
This commit is contained in:
		
							parent
							
								
									19c58fb5af
								
							
						
					
					
						commit
						04bc0b7a81
					
				|  | @ -0,0 +1,59 @@ | |||
| # pyswagger unittests for the API & UI | ||||
| 
 | ||||
| ## Requirements | ||||
| 
 | ||||
| ``` | ||||
| wg-quick up conf/wg-example0.conf | ||||
| sudo LOG_LEVEL=debug CONFIG_FILE=conf/config.yml ../dist/wg-portal-amd64  | ||||
| 
 | ||||
| python3 -m venv ~/venv/apitest | ||||
| ~/venv/apitest/bin/pip install pyswagger mechanize requests pytest PyYAML | ||||
| ``` | ||||
| 
 | ||||
| ## Running | ||||
| 
 | ||||
| ### API | ||||
| ``` | ||||
| ~/venv/apitest/bin/python3 -m unittest test_API.TestAPI  | ||||
| ``` | ||||
| 
 | ||||
| ### UI | ||||
| ``` | ||||
| ~/venv/lsl/bin/pytest pytest_UI.py  | ||||
| ``` | ||||
| 
 | ||||
| 
 | ||||
| ## Debugging | ||||
| Debugging for requests http request/response is included for the API unittesting. | ||||
| To use, adjust the log level for "api" logger to DEBUG | ||||
| 
 | ||||
| ```python | ||||
| log.setLevel(logging.DEBUG) | ||||
| <action> | ||||
| log.setLevel(logging.INFO) | ||||
| ``` | ||||
| This will provide: | ||||
| ``` | ||||
| 2021-09-29 14:55:15,585 DEBUG api HTTP | ||||
| ---------------- request ---------------- | ||||
| GET http://localhost:8123/api/v1/provisioning/peers?Email=test%2Bn4gbm7%40example.org | ||||
| User-Agent: python-requests/2.26.0 | ||||
| Accept-Encoding: gzip, deflate | ||||
| Accept: application/json | ||||
| Connection: keep-alive | ||||
| Authorization: Basic d2dAZXhhbXBsZS5vcmc6YWJhZGNob2ljZQ== | ||||
| 
 | ||||
| None | ||||
| ---------------- response ---------------- | ||||
| 200 OK http://localhost:8123/api/v1/provisioning/peers?Email=test%2Bn4gbm7%40example.org | ||||
| Content-Type: application/json; charset=utf-8 | ||||
| Date: Wed, 29 Sep 2021 12:55:15 GMT | ||||
| Content-Length: 285 | ||||
| 
 | ||||
| [{"PublicKey":"hO3pxnft/8QL6nbE+79HN464Z+L4+D/JjUvNE+8LmTs=", | ||||
| "Identifier":"Test User (Default)","Device":"wg-example0","DeviceIdentifier":"example0"}, | ||||
| {"PublicKey":"RVS2gsdRpFjyOpr1nAlEkrs194lQytaPHhaxL5amQxY=", | ||||
| "Identifier":"debug","Device":"wg-example0","DeviceIdentifier":"example0"}] | ||||
| ``` | ||||
| 
 | ||||
| 
 | ||||
|  | @ -0,0 +1,214 @@ | |||
| import logging.config | ||||
| import http.cookiejar | ||||
| import random | ||||
| import string | ||||
| 
 | ||||
| import mechanize | ||||
| import yaml | ||||
| import pytest | ||||
| 
 | ||||
| 
 | ||||
| @pytest.fixture(scope="function") | ||||
| def browser(): | ||||
|     # Fake Cookie Policy to send the Secure cookies via http | ||||
|     class InSecureCookiePolicy(http.cookiejar.DefaultCookiePolicy): | ||||
|         def set_ok(self, cookie, request): | ||||
|             return True | ||||
| 
 | ||||
|         def return_ok(self, cookie, request): | ||||
|             return True | ||||
| 
 | ||||
|         def domain_return_ok(self, domain, request): | ||||
|             return True | ||||
| 
 | ||||
|         def path_return_ok(self, path, request): | ||||
|             return True | ||||
| 
 | ||||
|     b = mechanize.Browser() | ||||
|     b.set_cookiejar(http.cookiejar.CookieJar(InSecureCookiePolicy())) | ||||
|     b.set_handle_robots(False) | ||||
|     b.set_debug_http(True) | ||||
|     return b | ||||
| 
 | ||||
| 
 | ||||
| @pytest.fixture | ||||
| def config(): | ||||
|     cfg = yaml.load(open('conf/config.yml', 'r')) | ||||
|     return cfg | ||||
| 
 | ||||
| 
 | ||||
| @pytest.fixture() | ||||
| def admin(browser, config): | ||||
|     auth = (c := config['core'])['adminUser'], c['adminPass'] | ||||
|     return _login(browser, auth) | ||||
| 
 | ||||
| def _create_user(admin, values): | ||||
|     b = admin | ||||
|     b.follow_link(text="User Management") | ||||
|     b.follow_link(predicate=has_attr('Add a user')) | ||||
| 
 | ||||
|     # FIXME name form | ||||
|     b.select_form(predicate=lambda x: x.method == 'post') | ||||
|     for k, v in values.items(): | ||||
|         b.form.set_value(v, k) | ||||
|     b.submit() | ||||
|     alert = b._factory.root.findall('body/div/div[@role="alert"]') | ||||
|     assert len(alert) == 1 and alert[0].text.strip() == "user created successfully" | ||||
|     return values["email"],values["password"] | ||||
| 
 | ||||
| def _destroy_user(admin, uid): | ||||
|     b = admin | ||||
|     b.follow_link(text="User Management") | ||||
|     for user in b._factory.root.findall('body/div/div/table[@id="userTable"]/tbody/'): | ||||
|         email,*_ = list(map(lambda x: x.text.strip() if x.text else '', list(user))) | ||||
|         if email == uid: | ||||
|             break | ||||
|     else: | ||||
|         assert False | ||||
|     a = user.findall('td/a[@title="Edit user"]') | ||||
|     assert len(a) == 1 | ||||
|     b.follow_link(url=a[0].attrib['href']) | ||||
| 
 | ||||
|     # FIXME name form | ||||
|     b.select_form(predicate=lambda x: x.method == 'post') | ||||
|     disabled = b.find_control("isdisabled") | ||||
|     disabled.set_single("true") | ||||
|     b.submit() | ||||
| 
 | ||||
| def _destroy_peer(admin, uid): | ||||
|     b = admin | ||||
|     b.follow_link(text="Administration") | ||||
|     peers = b._factory.root.findall('body/div/div/table[@id="userTable"]/tbody/tr') | ||||
|     for idx,peer in enumerate(peers): | ||||
|         if idx % 2 == 1: | ||||
|             continue | ||||
|         head, Identifier, PublicKey, EMail, IPs, Handshake, tail = list(map(lambda x: x.text.strip() if x.text else x, list(peer))) | ||||
|         print(Identifier) | ||||
|         if EMail != uid: | ||||
|             continue | ||||
|         peer = peers[idx+1] | ||||
|         a = peer.findall('.//a[@title="Delete peer"]') | ||||
|         assert len(a) == 1 | ||||
|         b.follow_link(url=a[0].attrib['href']) | ||||
| 
 | ||||
| 
 | ||||
| def _list_peers(user): | ||||
|     r = [] | ||||
|     b = user | ||||
|     b.follow_link(predicate=has_attr('User-Profile')) | ||||
|     profiles = b._factory.root.findall('body/div/div/table[@id="userTable"]/tbody/tr') | ||||
|     for idx,profile in enumerate(profiles): | ||||
|         if idx % 2 == 1: | ||||
|             continue | ||||
|         head, Identifier, PublicKey, EMail, IPs, Handshake = list(map(lambda x: x.text.strip() if x.text else x, list(profile))) | ||||
|         profile = profiles[idx+1] | ||||
|         pre = profile.findall('.//pre') | ||||
|         assert len(pre) == 1 | ||||
|         r.append((PublicKey, pre)) | ||||
|     return r | ||||
| 
 | ||||
| 
 | ||||
| @pytest.fixture(scope="session") | ||||
| def user_data(): | ||||
|     values = { | ||||
|         "email": f"test+{randstr()}@example.org", | ||||
|         "password": randstr(12), | ||||
|         "firstname": randstr(8), | ||||
|         "lastname": randstr(12) | ||||
|     } | ||||
|     return values | ||||
| 
 | ||||
| @pytest.fixture | ||||
| def user(admin, user_data, config): | ||||
|     b = admin | ||||
|     auth = _create_user(b, user_data) | ||||
|     _logout(b) | ||||
|     _login(b, auth) | ||||
|     assert b.find_link(predicate=has_attr('User-Profile')) | ||||
|     yield b | ||||
|     _logout(b) | ||||
|     auth = (c := config['core'])['adminUser'], c['adminPass'] | ||||
|     _login(b, auth) | ||||
|     _destroy_user(b, user_data["email"]) | ||||
|     _destroy_peer(b, user_data["email"]) | ||||
| 
 | ||||
| @pytest.fixture | ||||
| def peer(admin, user, user_data): | ||||
|     pass | ||||
| 
 | ||||
| def _login(browser, auth): | ||||
|     b = browser | ||||
|     b.open("http://localhost:8123/") | ||||
| 
 | ||||
|     b.follow_link(text="Login") | ||||
| 
 | ||||
|     b.select_form(name="login") | ||||
|     username, password = auth | ||||
|     b.form.set_value(username, "username") | ||||
|     b.form.set_value(password, "password") | ||||
|     b.submit() | ||||
|     return b | ||||
| 
 | ||||
| def _logout(browser): | ||||
|     browser.follow_link(text="Logout") | ||||
|     return browser | ||||
| 
 | ||||
| def has_attr(value, attr='title'): | ||||
|     def find_attr(x): | ||||
|         return any([a == (attr, value) for a in x.attrs]) | ||||
|     return find_attr | ||||
| 
 | ||||
| 
 | ||||
| def _server(browser, addr): | ||||
|     b = browser | ||||
|     b.follow_link(text="Administration") | ||||
|     b.follow_link(predicate=has_attr('Edit interface settings')) | ||||
|     b.select_form("server") | ||||
| 
 | ||||
|     values = { | ||||
|         "displayname": "example0", | ||||
|         "endpoint": "wg.example.org:51280", | ||||
|         "ip": addr | ||||
|     } | ||||
|     for k, v in values.items(): | ||||
|         b.form.set_value(v, k) | ||||
| 
 | ||||
|     b.submit() | ||||
|     return b | ||||
| 
 | ||||
| @pytest.fixture | ||||
| def server(admin): | ||||
|     return _server(admin, "10.0.0.0/24") | ||||
| 
 | ||||
| def randstr(l=6): | ||||
|     return ''.join([random.choice(string.ascii_lowercase + string.digits) for i in range(l)]) | ||||
| 
 | ||||
| 
 | ||||
| def test_admin_login(admin): | ||||
|     b = admin | ||||
|     b.find_link("Administration") | ||||
| 
 | ||||
| 
 | ||||
| def test_admin_server(admin): | ||||
|     ip = "10.0.0.0/28" | ||||
|     b = _server(admin, ip) | ||||
|     b.select_form("server") | ||||
|     assert ip == b.form.get_value("ip") | ||||
| 
 | ||||
| 
 | ||||
| def test_admin_create_peer(server, user_data): | ||||
|     auth = _create_user(server, user_data) | ||||
| 
 | ||||
| 
 | ||||
| def test_admin_create_user(admin, user_data): | ||||
|     auth = _create_user(admin, user_data) | ||||
| 
 | ||||
| 
 | ||||
| def test_user_login(server, user): | ||||
|     b = user | ||||
|     b.follow_link(predicate=has_attr('User-Profile')) | ||||
| 
 | ||||
| def test_user_config(server, user): | ||||
|     b = user | ||||
|     peers = _list_peers(b) | ||||
|     assert len(peers) >= 1 | ||||
|  | @ -134,7 +134,7 @@ class WGPClient: | |||
|         elif 500 == resp.status: | ||||
|             raise ValueError(resp.data["Message"]) | ||||
|         elif 501 == resp.status: | ||||
|             raise NotImplementedError(resp.data["Message"]) | ||||
|             raise NotImplementedError(name) | ||||
|         elif 502 <= resp.status <= 599: | ||||
|             raise ApiError(resp.data["Message"]) | ||||
|         return resp | ||||
|  | @ -317,6 +317,7 @@ class TestAPI(unittest.TestCase): | |||
| 
 | ||||
|         for device in devices: | ||||
|             dev = self.c.GetDevice(DeviceName=device.DeviceName) | ||||
|             with self.assertRaises(NotImplementedError): | ||||
|                 new = self.c.PutDevice(DeviceName=dev.DeviceName, | ||||
|                                        Device={ | ||||
|                                            "DeviceName": dev.DeviceName, | ||||
|  | @ -325,6 +326,7 @@ class TestAPI(unittest.TestCase): | |||
|                                            "Type": "client", | ||||
|                                            "PublicKey": dev.PublicKey} | ||||
|                                        ) | ||||
|             with self.assertRaises(NotImplementedError): | ||||
|                 new = self.c.PatchDevice(DeviceName=dev.DeviceName, | ||||
|                                          Device={ | ||||
|                                              "DeviceName": dev.DeviceName, | ||||
|  | @ -333,6 +335,7 @@ class TestAPI(unittest.TestCase): | |||
|                                              "Type": "client", | ||||
|                                              "PublicKey": dev.PublicKey} | ||||
|                                          ) | ||||
|             break | ||||
| 
 | ||||
|     def easy_peer(self): | ||||
|         data = self.c.PostPeerDeploymentConfig(ProvisioningRequest={"Email": self.user, "Identifier": "debug"}) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue