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: |         elif 500 == resp.status: | ||||||
|             raise ValueError(resp.data["Message"]) |             raise ValueError(resp.data["Message"]) | ||||||
|         elif 501 == resp.status: |         elif 501 == resp.status: | ||||||
|             raise NotImplementedError(resp.data["Message"]) |             raise NotImplementedError(name) | ||||||
|         elif 502 <= resp.status <= 599: |         elif 502 <= resp.status <= 599: | ||||||
|             raise ApiError(resp.data["Message"]) |             raise ApiError(resp.data["Message"]) | ||||||
|         return resp |         return resp | ||||||
|  | @ -317,22 +317,25 @@ class TestAPI(unittest.TestCase): | ||||||
| 
 | 
 | ||||||
|         for device in devices: |         for device in devices: | ||||||
|             dev = self.c.GetDevice(DeviceName=device.DeviceName) |             dev = self.c.GetDevice(DeviceName=device.DeviceName) | ||||||
|             new = self.c.PutDevice(DeviceName=dev.DeviceName, |             with self.assertRaises(NotImplementedError): | ||||||
|                                    Device={ |                 new = self.c.PutDevice(DeviceName=dev.DeviceName, | ||||||
|                                        "DeviceName": dev.DeviceName, |                                        Device={ | ||||||
|                                        "IPsStr": dev.IPsStr, |                                            "DeviceName": dev.DeviceName, | ||||||
|                                        "PrivateKey": dev.PrivateKey, |                                            "IPsStr": dev.IPsStr, | ||||||
|                                        "Type": "client", |                                            "PrivateKey": dev.PrivateKey, | ||||||
|                                        "PublicKey": dev.PublicKey} |                                            "Type": "client", | ||||||
|                                    ) |                                            "PublicKey": dev.PublicKey} | ||||||
|             new = self.c.PatchDevice(DeviceName=dev.DeviceName, |                                        ) | ||||||
|                                      Device={ |             with self.assertRaises(NotImplementedError): | ||||||
|                                          "DeviceName": dev.DeviceName, |                 new = self.c.PatchDevice(DeviceName=dev.DeviceName, | ||||||
|                                          "IPsStr": dev.IPsStr, |                                          Device={ | ||||||
|                                          "PrivateKey": dev.PrivateKey, |                                              "DeviceName": dev.DeviceName, | ||||||
|                                          "Type": "client", |                                              "IPsStr": dev.IPsStr, | ||||||
|                                          "PublicKey": dev.PublicKey} |                                              "PrivateKey": dev.PrivateKey, | ||||||
|                                      ) |                                              "Type": "client", | ||||||
|  |                                              "PublicKey": dev.PublicKey} | ||||||
|  |                                          ) | ||||||
|  |             break | ||||||
| 
 | 
 | ||||||
|     def easy_peer(self): |     def easy_peer(self): | ||||||
|         data = self.c.PostPeerDeploymentConfig(ProvisioningRequest={"Email": self.user, "Identifier": "debug"}) |         data = self.c.PostPeerDeploymentConfig(ProvisioningRequest={"Email": self.user, "Identifier": "debug"}) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue