Compare commits
	
		
			4 Commits
		
	
	
		
			4cf11798aa
			...
			56b00934be
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 56b00934be | |||
| aa67a1e1b2 | |||
| 3fb8254c15 | |||
| 8a4794a344 | 
| @ -96,21 +96,28 @@ def create_app(): | |||||||
|  |  | ||||||
|   app.config['HUB_URL'] = config.get("HUB_URL", app.config['BASE_URL']) |   app.config['HUB_URL'] = config.get("HUB_URL", app.config['BASE_URL']) | ||||||
|  |  | ||||||
|  |   log_filters = { | ||||||
|  |     'setLogLevelToDebugForHeartbeatRelatedMessages': { | ||||||
|  |         '()': SetLogLevelToDebugForHeartbeatRelatedMessagesFilter, | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   if app.config['TESTING'] != False: | ||||||
|  |     log_filters['captureLogOutputDuringTests'] = { | ||||||
|  |         '()': CaptureLogOutputDuringTestsFilter, | ||||||
|  |     } | ||||||
|  |    | ||||||
|  |  | ||||||
|   logging_dict_config({ |   logging_dict_config({ | ||||||
|     'version': 1, |     'version': 1, | ||||||
|     'formatters': {'default': { |     'formatters': {'default': { | ||||||
|       'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s', |       'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s', | ||||||
|     }}, |     }}, | ||||||
|     'filters': { |     'filters': log_filters, | ||||||
|       'setLogLevelToDebugForHeartbeatRelatedMessages': { |  | ||||||
|           '()': SetLogLevelToDebugForHeartbeatRelatedMessagesFilter, |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     'handlers': {'wsgi': { |     'handlers': {'wsgi': { | ||||||
|       'class': 'logging.StreamHandler', |       'class': 'logging.StreamHandler', | ||||||
|       'stream': 'ext://flask.logging.wsgi_errors_stream', |       'stream': 'ext://flask.logging.wsgi_errors_stream', | ||||||
|       'formatter': 'default', |       'formatter': 'default', | ||||||
|       'filters': ['setLogLevelToDebugForHeartbeatRelatedMessages'] |       'filters': list(log_filters.keys()) | ||||||
|     }}, |     }}, | ||||||
|     'root': { |     'root': { | ||||||
|       'level': app.config['LOG_LEVEL'], |       'level': app.config['LOG_LEVEL'], | ||||||
| @ -270,3 +277,15 @@ class SetLogLevelToDebugForHeartbeatRelatedMessagesFilter(logging.Filter): | |||||||
|         return False |         return False | ||||||
|  |  | ||||||
|     return True |     return True | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class CaptureLogOutputDuringTestsFilter(logging.Filter): | ||||||
|  |   def filter(self, record): | ||||||
|  |     file_object = open('unittest-output.log', 'a') | ||||||
|  |     file_object.write("%s" % record.msg) | ||||||
|  |     for arg in record.args: | ||||||
|  |       file_object.write("%s" % arg) | ||||||
|  |  | ||||||
|  |     file_object.write("\n") | ||||||
|  |     file_object.close() | ||||||
|  |     return True | ||||||
|  | |||||||
| @ -192,6 +192,11 @@ def detail(id): | |||||||
| @bp.route("/create", methods=("GET", "POST")) | @bp.route("/create", methods=("GET", "POST")) | ||||||
| @account_required | @account_required | ||||||
| def create(): | def create(): | ||||||
|  |  | ||||||
|  |   raise "console.create()!" | ||||||
|  |  | ||||||
|  |   current_app.logger.error("console.create()!") | ||||||
|  |  | ||||||
|   vm_sizes = get_model().vm_sizes_dict() |   vm_sizes = get_model().vm_sizes_dict() | ||||||
|   operating_systems = get_model().operating_systems_dict() |   operating_systems = get_model().operating_systems_dict() | ||||||
|   public_keys_for_account = get_model().list_ssh_public_keys_for_account(session["account"]) |   public_keys_for_account = get_model().list_ssh_public_keys_for_account(session["account"]) | ||||||
|  | |||||||
| @ -114,7 +114,7 @@ def init_app(app, is_running_server): | |||||||
|    |    | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_model(): | def get_model() -> DBModel: | ||||||
|     if 'db_model' not in g: |     if 'db_model' not in g: | ||||||
|         connection = current_app.config['PSYCOPG2_CONNECTION_POOL'].getconn() |         connection = current_app.config['PSYCOPG2_CONNECTION_POOL'].getconn() | ||||||
|         cursor = connection.cursor() |         cursor = connection.cursor() | ||||||
|  | |||||||
| @ -381,7 +381,7 @@ class DBModel: | |||||||
|   def list_all_operations(self):  |   def list_all_operations(self):  | ||||||
|     self.cursor.execute(""" |     self.cursor.execute(""" | ||||||
|     SELECT operations.id, operations.email, operations.created, operations.payload, |     SELECT operations.id, operations.email, operations.created, operations.payload, | ||||||
|            host_operation.host host_operation.assignment_status, host_operation.assigned,  |            host_operation.host, host_operation.assignment_status, host_operation.assigned,  | ||||||
|            host_operation.completed, host_operation.results FROM operations |            host_operation.completed, host_operation.results FROM operations | ||||||
|     JOIN host_operation ON host_operation.operation = operations.id |     JOIN host_operation ON host_operation.operation = operations.id | ||||||
|     """) |     """) | ||||||
|  | |||||||
| @ -196,7 +196,7 @@ class CapsulFlaskHub(VirtualizationInterface): | |||||||
|   def create(self, email: str, id: str, os: str, size: str, template_image_file_name: str, vcpus: int, memory_mb: int, ssh_authorized_keys: list): |   def create(self, email: str, id: str, os: str, size: str, template_image_file_name: str, vcpus: int, memory_mb: int, ssh_authorized_keys: list): | ||||||
|     validate_capsul_id(id) |     validate_capsul_id(id) | ||||||
|     online_hosts = get_model().get_online_hosts() |     online_hosts = get_model().get_online_hosts() | ||||||
|     #current_app.logger.debug(f"hub_model.create(): ${len(online_hosts)} hosts") |     current_app.logger.debug(f"hub_model.create(): ${len(online_hosts)} hosts") | ||||||
|     payload = json.dumps(dict( |     payload = json.dumps(dict( | ||||||
|       type="create",  |       type="create",  | ||||||
|       email=email,  |       email=email,  | ||||||
|  | |||||||
| @ -1,3 +1,5 @@ | |||||||
|  | import logging | ||||||
|  |  | ||||||
| from copy import deepcopy | from copy import deepcopy | ||||||
|  |  | ||||||
| from unittest.mock import patch | from unittest.mock import patch | ||||||
| @ -6,7 +8,7 @@ from flask import url_for | |||||||
|  |  | ||||||
| from capsulflask.db import get_model | from capsulflask.db import get_model | ||||||
| from capsulflask.tests_base import BaseTestCase | from capsulflask.tests_base import BaseTestCase | ||||||
| from capsulflask.hub_model import MockHub | from capsulflask.spoke_model import MockSpoke | ||||||
|  |  | ||||||
|  |  | ||||||
| class ConsoleTests(BaseTestCase): | class ConsoleTests(BaseTestCase): | ||||||
| @ -65,9 +67,11 @@ class ConsoleTests(BaseTestCase): | |||||||
|             get_model().create_payment_session('fake', 'test', 'test@example.com', 20) |             get_model().create_payment_session('fake', 'test', 'test@example.com', 20) | ||||||
|             get_model().consume_payment_session('fake', 'test', 20) |             get_model().consume_payment_session('fake', 'test', 20) | ||||||
|  |  | ||||||
|             with patch.object(MockHub, 'capacity_avaliable', return_value=False) as mock_method: |             with patch.object(MockSpoke, 'capacity_avaliable', return_value=False) as mock_method: | ||||||
|                 response = client.post(url_for("console.create"), data=data) |                 response = client.post(url_for("console.create"), data=data) | ||||||
|  |  | ||||||
|  |             self.assert200(response) | ||||||
|  |  | ||||||
|             mock_method.assert_called() |             mock_method.assert_called() | ||||||
|  |  | ||||||
|             capacity_message = \ |             capacity_message = \ | ||||||
| @ -79,6 +83,10 @@ class ConsoleTests(BaseTestCase): | |||||||
|                 0 |                 0 | ||||||
|             ) |             ) | ||||||
|              |              | ||||||
|  |             file_object = open('unittest-output.log', 'a') | ||||||
|  |             file_object.write(f"{self.id()} captured output:\n{self.logs_from_test.getvalue()}\n") | ||||||
|  |             file_object.close() | ||||||
|  |  | ||||||
|     def test_create_fails_invalid(self): |     def test_create_fails_invalid(self): | ||||||
|         with self.client as client: |         with self.client as client: | ||||||
|             client.get(url_for("console.create")) |             client.get(url_for("console.create")) | ||||||
| @ -112,6 +120,14 @@ class ConsoleTests(BaseTestCase): | |||||||
|  |  | ||||||
|             response = client.post(url_for("console.create"), data=data) |             response = client.post(url_for("console.create"), data=data) | ||||||
|  |  | ||||||
|  |          | ||||||
|  |  | ||||||
|  |            | ||||||
|  |             self.assertEqual( | ||||||
|  |                 len(get_model().list_all_operations()), | ||||||
|  |                 1  | ||||||
|  |             ) | ||||||
|  |  | ||||||
|             vms = get_model().list_vms_for_account('test@example.com') |             vms = get_model().list_vms_for_account('test@example.com') | ||||||
|             self.assertEqual( |             self.assertEqual( | ||||||
|                 len(vms), |                 len(vms), | ||||||
| @ -125,6 +141,7 @@ class ConsoleTests(BaseTestCase): | |||||||
|                 url_for("console.index") + f'?created={vm_id}' |                 url_for("console.index") + f'?created={vm_id}' | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|  |  | ||||||
|     def test_keys_loads(self): |     def test_keys_loads(self): | ||||||
|         self._login('test@example.com') |         self._login('test@example.com') | ||||||
|         with self.client as client: |         with self.client as client: | ||||||
| @ -179,10 +196,12 @@ class ConsoleTests(BaseTestCase): | |||||||
|             ) |             ) | ||||||
|  |  | ||||||
|     def setUp(self): |     def setUp(self): | ||||||
|  |         super().setUp() | ||||||
|         self._login('test@example.com') |         self._login('test@example.com') | ||||||
|         get_model().create_ssh_public_key('test@example.com', 'key', 'foo')  |         get_model().create_ssh_public_key('test@example.com', 'key', 'foo')  | ||||||
|  |  | ||||||
|     def tearDown(self): |     def tearDown(self): | ||||||
|  |         super().tearDown() | ||||||
|         get_model().cursor.execute("DELETE FROM ssh_public_keys") |         get_model().cursor.execute("DELETE FROM ssh_public_keys") | ||||||
|         get_model().cursor.execute("DELETE FROM login_tokens") |         get_model().cursor.execute("DELETE FROM login_tokens") | ||||||
|         get_model().cursor.execute("DELETE FROM vms") |         get_model().cursor.execute("DELETE FROM vms") | ||||||
|  | |||||||
| @ -1,7 +1,11 @@ | |||||||
|  | from io import StringIO | ||||||
|  | import logging | ||||||
|  | import unittest | ||||||
| import os | import os | ||||||
| from nanoid import generate | from nanoid import generate | ||||||
|  |  | ||||||
| from flask_testing import TestCase | from flask_testing import TestCase | ||||||
|  | from flask import current_app | ||||||
|  |  | ||||||
| from capsulflask import create_app | from capsulflask import create_app | ||||||
| from capsulflask.db import get_model | from capsulflask.db import get_model | ||||||
| @ -11,9 +15,11 @@ class BaseTestCase(TestCase): | |||||||
|         # Use default connection paramaters |         # Use default connection paramaters | ||||||
|         os.environ['POSTGRES_CONNECTION_PARAMETERS'] = "host=localhost port=5432 user=postgres password=dev dbname=capsulflask_test" |         os.environ['POSTGRES_CONNECTION_PARAMETERS'] = "host=localhost port=5432 user=postgres password=dev dbname=capsulflask_test" | ||||||
|         os.environ['TESTING'] = '1' |         os.environ['TESTING'] = '1' | ||||||
|  |         os.environ['LOG_LEVEL'] = 'DEBUG' | ||||||
|         os.environ['SPOKE_MODEL'] = 'mock' |         os.environ['SPOKE_MODEL'] = 'mock' | ||||||
|         os.environ['HUB_MODEL'] = 'mock' |         os.environ['HUB_MODEL'] = 'capsul-flask' | ||||||
|         return create_app() |         self.app = create_app() | ||||||
|  |         return self.app | ||||||
|  |  | ||||||
|     def setUp(self): |     def setUp(self): | ||||||
|       pass |       pass | ||||||
| @ -26,3 +32,4 @@ class BaseTestCase(TestCase): | |||||||
|         with self.client.session_transaction() as session: |         with self.client.session_transaction() as session: | ||||||
|             session['account'] = user_email |             session['account'] = user_email | ||||||
|             session['csrf-token'] = generate() |             session['csrf-token'] = generate() | ||||||
|  |  | ||||||
|  | |||||||
| @ -4,9 +4,9 @@ Automated tests could make it safer to contribute code, easier to review new cod | |||||||
|  |  | ||||||
| To run tests:  | To run tests:  | ||||||
| 1. create a Postgres database called `capsulflask_test` | 1. create a Postgres database called `capsulflask_test` | ||||||
|   - e.g.: `docker exec -it d1702306f409 psql -U postgres createdb -O postgres capsulflask_test;` |   - e.g.: `docker exec -it 98e1ddfbbffb createdb -U postgres -O postgres capsulflask_test` | ||||||
|     - (`d1702306f409` is the docker container ID of the postgres container) |     - (`98e1ddfbbffb` is the docker container ID of the postgres container) | ||||||
| 2. run `python -m unittest` | 2. run `python3 -m unittest` | ||||||
|  |  | ||||||
| ### Architecture | ### Architecture | ||||||
|  |  | ||||||
| @ -19,13 +19,13 @@ One outstanding question is how to initialise/reinitialise the test database. | |||||||
| Currently, the tests rely on the existence of a capsulflask_test database on localhost, accessible by the postgres user with password dev. | Currently, the tests rely on the existence of a capsulflask_test database on localhost, accessible by the postgres user with password dev. | ||||||
|  |  | ||||||
| I create this manually using: | I create this manually using: | ||||||
| `docker exec -it d1702306f409 psql -U postgres createdb -O postgres capsulflask_test;` | `docker exec -it 98e1ddfbbffb createdb -U postgres -O postgres capsulflask_test` | ||||||
|  |  | ||||||
| where `d1702306f409` is the docker container ID of the postgres container.  | where `98e1ddfbbffb` is the docker container ID of the postgres container.  | ||||||
|  |  | ||||||
| In between test runs, you can either drop and recreate that database, or manually clear data using: | In between test runs, you can either drop and recreate that database, or manually clear data using: | ||||||
|  |  | ||||||
| `docker exec -it d1702306f409 psql -U postgres capsulflask_test -c "DELETE FROM vms; DELETE FROM login_tokens; DELETE FROM ssh_public_keys; DELETE FROM api_tokens; DELETE FROM accounts;` | `docker exec -it 98e1ddfbbffb psql -U postgres capsulflask_test -c "DELETE FROM vm_ssh_authorized_key; DELETE FROM vm_ssh_host_key; DELETE FROM vms; DELETE FROM login_tokens; DELETE FROM ssh_public_keys; DELETE FROM unresolved_btcpay_invoices; DELETE FROM payments; DELETE FROM payment_sessions; DELETE FROM host_operation; DELETE FROM operations;  DELETE FROM api_tokens; DELETE FROM accounts;" ` | ||||||
|  |  | ||||||
| ### Test coverage | ### Test coverage | ||||||
|  |  | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user