A multipurpose python flask API server and administration SPA
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

127 lines
3.7 KiB

  1. import base64
  2. import os
  3. import random
  4. import string
  5. import tempfile
  6. from typing import Tuple, Any
  7. import pytest
  8. from flask import Flask
  9. from flask.testing import FlaskClient, FlaskCliRunner
  10. from werkzeug.test import Client
  11. from atheneum import create_app, init_db, register_blueprints
  12. from atheneum.model import User
  13. from atheneum.service import user_service
  14. def add_test_user() -> Tuple[str, str]:
  15. test_username = 'test_' + ''.join(
  16. random.choices(string.ascii_letters + string.digits, k=17)).strip()
  17. test_password = ''.join(
  18. random.choices(string.ascii_letters + string.digits, k=32)).strip()
  19. user_service.register(test_username, test_password, User.ROLE_ADMIN)
  20. return test_username, test_password
  21. @pytest.fixture
  22. def app() -> Flask:
  23. """Create and configure a new app instance for each test."""
  24. # create a temporary file to isolate the database for each test
  25. db_fd, db_path = tempfile.mkstemp()
  26. # create the app with common test config
  27. app = create_app({
  28. 'TESTING': True,
  29. 'DATABASE': db_path,
  30. })
  31. register_blueprints(app)
  32. # create the database and load test data
  33. with app.app_context():
  34. init_db()
  35. test_username, test_password = add_test_user()
  36. app.config['test_username'] = test_username
  37. app.config['test_password'] = test_password
  38. # get_db().executescript(_data_sql)
  39. yield app
  40. # close and remove the temporary database
  41. os.close(db_fd)
  42. os.unlink(db_path)
  43. @pytest.fixture
  44. def client(app: Flask) -> FlaskClient:
  45. """A test client for the app."""
  46. return app.test_client()
  47. @pytest.fixture
  48. def runner(app: Flask) -> FlaskCliRunner:
  49. """A test runner for the app's Click commands."""
  50. return app.test_cli_runner()
  51. class AuthActions(object):
  52. def __init__(self,
  53. client: Client,
  54. username: str = "",
  55. password: str = "") -> None:
  56. self._client = client
  57. self.username: str = username
  58. self.password: str = password
  59. self.token: str = ""
  60. def configure(self, username: str, password: str) -> Any:
  61. self.username = username
  62. self.password = password
  63. return self
  64. def login(self) -> Any:
  65. auth_header = self.get_authorization_header_basic()
  66. result = self._client.post(
  67. '/auth/login',
  68. headers={
  69. auth_header[0]: auth_header[1]
  70. }
  71. )
  72. self.token = result.json['token']
  73. return result
  74. def bump(self) -> Any:
  75. auth_header = self.get_authorization_header_token()
  76. return self._client.post(
  77. '/auth/bump',
  78. headers={
  79. auth_header[0]: auth_header[1]
  80. }
  81. )
  82. def logout(self) -> Any:
  83. auth_header = self.get_authorization_header_token()
  84. return self._client.post(
  85. '/auth/logout',
  86. headers={
  87. auth_header[0]: auth_header[1]
  88. }
  89. )
  90. def get_authorization_header_basic(self) -> Tuple[str, str]:
  91. credentials = base64.b64encode(
  92. '{}:{}'.format(self.username, self.password).encode('utf8')) \
  93. .decode('utf8').strip()
  94. return 'Authorization', 'Basic {}'.format(credentials)
  95. def get_authorization_header_token(self) -> Tuple[str, str]:
  96. credentials = base64.b64encode(
  97. '{}:{}'.format(self.username, self.token).encode('utf8')) \
  98. .decode('utf8').strip()
  99. return 'Authorization', 'Token {}'.format(credentials)
  100. @pytest.fixture
  101. def auth(client: Client) -> AuthActions:
  102. return AuthActions(client,
  103. client.application.config.get('test_username'),
  104. client.application.config.get('test_password'))