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'])
 | 
			
		||||
 | 
			
		||||
  log_filters = {
 | 
			
		||||
    'setLogLevelToDebugForHeartbeatRelatedMessages': {
 | 
			
		||||
        '()': SetLogLevelToDebugForHeartbeatRelatedMessagesFilter,
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if app.config['TESTING'] != False:
 | 
			
		||||
    log_filters['captureLogOutputDuringTests'] = {
 | 
			
		||||
        '()': CaptureLogOutputDuringTestsFilter,
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  logging_dict_config({
 | 
			
		||||
    'version': 1,
 | 
			
		||||
    'formatters': {'default': {
 | 
			
		||||
      'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s',
 | 
			
		||||
    }},
 | 
			
		||||
    'filters': {
 | 
			
		||||
      'setLogLevelToDebugForHeartbeatRelatedMessages': {
 | 
			
		||||
          '()': SetLogLevelToDebugForHeartbeatRelatedMessagesFilter,
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    'filters': log_filters,
 | 
			
		||||
    'handlers': {'wsgi': {
 | 
			
		||||
      'class': 'logging.StreamHandler',
 | 
			
		||||
      'stream': 'ext://flask.logging.wsgi_errors_stream',
 | 
			
		||||
      'formatter': 'default',
 | 
			
		||||
      'filters': ['setLogLevelToDebugForHeartbeatRelatedMessages']
 | 
			
		||||
      'filters': list(log_filters.keys())
 | 
			
		||||
    }},
 | 
			
		||||
    'root': {
 | 
			
		||||
      'level': app.config['LOG_LEVEL'],
 | 
			
		||||
@ -270,3 +277,15 @@ class SetLogLevelToDebugForHeartbeatRelatedMessagesFilter(logging.Filter):
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    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"))
 | 
			
		||||
@account_required
 | 
			
		||||
def create():
 | 
			
		||||
 | 
			
		||||
  raise "console.create()!"
 | 
			
		||||
 | 
			
		||||
  current_app.logger.error("console.create()!")
 | 
			
		||||
 | 
			
		||||
  vm_sizes = get_model().vm_sizes_dict()
 | 
			
		||||
  operating_systems = get_model().operating_systems_dict()
 | 
			
		||||
  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:
 | 
			
		||||
        connection = current_app.config['PSYCOPG2_CONNECTION_POOL'].getconn()
 | 
			
		||||
        cursor = connection.cursor()
 | 
			
		||||
 | 
			
		||||
@ -381,7 +381,7 @@ class DBModel:
 | 
			
		||||
  def list_all_operations(self): 
 | 
			
		||||
    self.cursor.execute("""
 | 
			
		||||
    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
 | 
			
		||||
    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):
 | 
			
		||||
    validate_capsul_id(id)
 | 
			
		||||
    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(
 | 
			
		||||
      type="create", 
 | 
			
		||||
      email=email, 
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from copy import deepcopy
 | 
			
		||||
 | 
			
		||||
from unittest.mock import patch
 | 
			
		||||
@ -6,7 +8,7 @@ from flask import url_for
 | 
			
		||||
 | 
			
		||||
from capsulflask.db import get_model
 | 
			
		||||
from capsulflask.tests_base import BaseTestCase
 | 
			
		||||
from capsulflask.hub_model import MockHub
 | 
			
		||||
from capsulflask.spoke_model import MockSpoke
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ConsoleTests(BaseTestCase):
 | 
			
		||||
@ -65,9 +67,11 @@ class ConsoleTests(BaseTestCase):
 | 
			
		||||
            get_model().create_payment_session('fake', 'test', 'test@example.com', 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)
 | 
			
		||||
 | 
			
		||||
            self.assert200(response)
 | 
			
		||||
 | 
			
		||||
            mock_method.assert_called()
 | 
			
		||||
 | 
			
		||||
            capacity_message = \
 | 
			
		||||
@ -78,6 +82,10 @@ class ConsoleTests(BaseTestCase):
 | 
			
		||||
                len(get_model().list_vms_for_account('test@example.com')),
 | 
			
		||||
                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):
 | 
			
		||||
        with self.client as client:
 | 
			
		||||
@ -112,6 +120,14 @@ class ConsoleTests(BaseTestCase):
 | 
			
		||||
 | 
			
		||||
            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')
 | 
			
		||||
            self.assertEqual(
 | 
			
		||||
                len(vms),
 | 
			
		||||
@ -119,12 +135,13 @@ class ConsoleTests(BaseTestCase):
 | 
			
		||||
            )
 | 
			
		||||
            
 | 
			
		||||
            vm_id = vms[0]['id']
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            self.assertRedirects(
 | 
			
		||||
                response, 
 | 
			
		||||
                url_for("console.index") + f'?created={vm_id}'
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def test_keys_loads(self):
 | 
			
		||||
        self._login('test@example.com')
 | 
			
		||||
        with self.client as client:
 | 
			
		||||
@ -179,10 +196,12 @@ class ConsoleTests(BaseTestCase):
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        self._login('test@example.com')
 | 
			
		||||
        get_model().create_ssh_public_key('test@example.com', 'key', 'foo') 
 | 
			
		||||
 | 
			
		||||
    def tearDown(self):
 | 
			
		||||
        super().tearDown()
 | 
			
		||||
        get_model().cursor.execute("DELETE FROM ssh_public_keys")
 | 
			
		||||
        get_model().cursor.execute("DELETE FROM login_tokens")
 | 
			
		||||
        get_model().cursor.execute("DELETE FROM vms")
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,11 @@
 | 
			
		||||
from io import StringIO
 | 
			
		||||
import logging
 | 
			
		||||
import unittest
 | 
			
		||||
import os
 | 
			
		||||
from nanoid import generate
 | 
			
		||||
 | 
			
		||||
from flask_testing import TestCase
 | 
			
		||||
from flask import current_app
 | 
			
		||||
 | 
			
		||||
from capsulflask import create_app
 | 
			
		||||
from capsulflask.db import get_model
 | 
			
		||||
@ -11,18 +15,21 @@ class BaseTestCase(TestCase):
 | 
			
		||||
        # Use default connection paramaters
 | 
			
		||||
        os.environ['POSTGRES_CONNECTION_PARAMETERS'] = "host=localhost port=5432 user=postgres password=dev dbname=capsulflask_test"
 | 
			
		||||
        os.environ['TESTING'] = '1'
 | 
			
		||||
        os.environ['LOG_LEVEL'] = 'DEBUG'
 | 
			
		||||
        os.environ['SPOKE_MODEL'] = 'mock'
 | 
			
		||||
        os.environ['HUB_MODEL'] = 'mock'
 | 
			
		||||
        return create_app()
 | 
			
		||||
        os.environ['HUB_MODEL'] = 'capsul-flask'
 | 
			
		||||
        self.app = create_app()
 | 
			
		||||
        return self.app
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        pass
 | 
			
		||||
      pass
 | 
			
		||||
 | 
			
		||||
    def tearDown(self):
 | 
			
		||||
        pass
 | 
			
		||||
       pass
 | 
			
		||||
 | 
			
		||||
    def _login(self, user_email):
 | 
			
		||||
        get_model().login(user_email)
 | 
			
		||||
        with self.client.session_transaction() as session:
 | 
			
		||||
            session['account'] = user_email
 | 
			
		||||
            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: 
 | 
			
		||||
1. create a Postgres database called `capsulflask_test`
 | 
			
		||||
  - e.g.: `docker exec -it d1702306f409 psql -U postgres createdb -O postgres capsulflask_test;`
 | 
			
		||||
    - (`d1702306f409` is the docker container ID of the postgres container)
 | 
			
		||||
2. run `python -m unittest`
 | 
			
		||||
  - e.g.: `docker exec -it 98e1ddfbbffb createdb -U postgres -O postgres capsulflask_test`
 | 
			
		||||
    - (`98e1ddfbbffb` is the docker container ID of the postgres container)
 | 
			
		||||
2. run `python3 -m unittest`
 | 
			
		||||
 | 
			
		||||
### 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.
 | 
			
		||||
 | 
			
		||||
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:
 | 
			
		||||
 | 
			
		||||
`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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user