Refactored Database management to allow isolated integration tests
This commit is contained in:
@@ -11,69 +11,117 @@ from pathlib import Path
|
||||
|
||||
from components.datagrid.DataGrid import reset_instances
|
||||
from playwright_config import BROWSER_CONFIG, BASE_URL
|
||||
from tests.fixtures.test_database import TestDatabaseManager
|
||||
from tests.fixtures.test_users import TestUsers
|
||||
|
||||
USER_EMAIL = "test@mail.com"
|
||||
USER_ID = "test_user"
|
||||
APP_PORT = 5002
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def app_server():
|
||||
"""Start the application server for end-to-end tests"""
|
||||
# Use the same Python executable that's running pytest
|
||||
python_executable = sys.executable
|
||||
|
||||
# Get the absolute path to the src directory
|
||||
project_root = Path(__file__).parent.parent
|
||||
src_path = project_root / "src"
|
||||
|
||||
# Start the application server
|
||||
print(f"Starting server on url {BASE_URL}...")
|
||||
port = BASE_URL.split(':')[-1].split('/')[0] if ':' in BASE_URL else APP_PORT
|
||||
print(f"Using port {port}")
|
||||
|
||||
server_process = subprocess.Popen(
|
||||
[python_executable, "main.py", "--port", "5002"],
|
||||
cwd=str(src_path), # Change to src directory where main.py is located
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
env=os.environ.copy() # Inherit environment variables
|
||||
)
|
||||
|
||||
# Wait for the server to start
|
||||
max_retries = 10 # Wait up to 30 seconds
|
||||
for i in range(max_retries):
|
||||
try:
|
||||
print(f"Waiting retry {i}/{max_retries}")
|
||||
response = requests.get(BASE_URL, timeout=1)
|
||||
if response.status_code in [200, 302, 404]: # Server is responding
|
||||
print(f"Server started successfully after {i + 1} attempts")
|
||||
break
|
||||
except requests.exceptions.RequestException:
|
||||
time.sleep(1)
|
||||
else:
|
||||
# If we get here, the server didn't start in time
|
||||
print(f"Failed to start after {max_retries} attempts.")
|
||||
server_process.kill()
|
||||
stdout, stderr = server_process.communicate()
|
||||
raise RuntimeError(
|
||||
f"Server failed to start within {max_retries} seconds.\n"
|
||||
f"STDOUT: {stdout.decode()}\n"
|
||||
f"STDERR: {stderr.decode()}"
|
||||
def test_database():
|
||||
"""Configure temporary database for tests"""
|
||||
# Create temporary DB
|
||||
temp_db_path = TestDatabaseManager.create_temp_db_path()
|
||||
|
||||
# Save original environment
|
||||
original_env = {
|
||||
"DB_PATH": os.environ.get("DB_PATH"),
|
||||
"ADMIN_EMAIL": os.environ.get("ADMIN_EMAIL"),
|
||||
"ADMIN_PASSWORD": os.environ.get("ADMIN_PASSWORD"),
|
||||
"SECRET_KEY": os.environ.get("SECRET_KEY")
|
||||
}
|
||||
|
||||
# Configure test environment
|
||||
TestDatabaseManager.setup_test_environment(temp_db_path)
|
||||
|
||||
print(f"Test database created at: {temp_db_path}")
|
||||
|
||||
yield temp_db_path
|
||||
|
||||
# Cleanup: restore environment and clean up DB
|
||||
TestDatabaseManager.restore_original_environment(original_env)
|
||||
TestDatabaseManager.cleanup_test_db(temp_db_path)
|
||||
print(f"Test database cleaned up: {temp_db_path}")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def test_users():
|
||||
"""Define available test users"""
|
||||
return {
|
||||
"admin": TestUsers.ADMIN,
|
||||
"regular_user": TestUsers.REGULAR_USER,
|
||||
"invalid_cases": TestUsers.INVALID_CREDENTIALS,
|
||||
"protected_urls": TestUsers.PROTECTED_URLS
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def app_server(test_database, test_users):
|
||||
"""Start application server with test database"""
|
||||
# Use the same Python executable that's running pytest
|
||||
python_executable = sys.executable
|
||||
|
||||
# Get the absolute path to the src directory
|
||||
project_root = Path(__file__).parent.parent
|
||||
src_path = project_root / "src"
|
||||
|
||||
# Create test environment
|
||||
test_env = os.environ.copy()
|
||||
test_env["DB_PATH"] = test_database
|
||||
test_env["ADMIN_EMAIL"] = test_users["admin"]["email"]
|
||||
test_env["ADMIN_PASSWORD"] = test_users["admin"]["password"]
|
||||
test_env["SECRET_KEY"] = "test-secret-key-for-e2e-tests"
|
||||
|
||||
# Start the application server
|
||||
print(f"Starting server on url {BASE_URL} with test database...")
|
||||
port = BASE_URL.split(':')[-1].split('/')[0] if ':' in BASE_URL else APP_PORT
|
||||
print(f"Using port {port}")
|
||||
print(f"Test DB path: {test_database}")
|
||||
|
||||
server_process = subprocess.Popen(
|
||||
[python_executable, "main.py", "--port", "5002"],
|
||||
cwd=str(src_path), # Change to src directory where main.py is located
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
env=test_env # Use test environment
|
||||
)
|
||||
|
||||
# Yield control to the tests
|
||||
print('Server started !')
|
||||
yield server_process
|
||||
|
||||
# Cleanup: terminate the server after tests
|
||||
server_process.terminate()
|
||||
try:
|
||||
server_process.wait(timeout=5)
|
||||
print('Server stopped.')
|
||||
except subprocess.TimeoutExpired:
|
||||
server_process.kill()
|
||||
server_process.wait()
|
||||
print('Server killed !')
|
||||
|
||||
# Wait for the server to start
|
||||
max_retries = 10 # Wait up to 30 seconds
|
||||
for i in range(max_retries):
|
||||
try:
|
||||
print(f"Waiting retry {i}/{max_retries}")
|
||||
response = requests.get(BASE_URL, timeout=1)
|
||||
if response.status_code in [200, 302, 404]: # Server is responding
|
||||
print(f"Server started successfully after {i + 1} attempts")
|
||||
break
|
||||
except requests.exceptions.RequestException:
|
||||
time.sleep(1)
|
||||
else:
|
||||
# If we get here, the server didn't start in time
|
||||
print(f"Failed to start after {max_retries} attempts.")
|
||||
server_process.kill()
|
||||
stdout, stderr = server_process.communicate()
|
||||
raise RuntimeError(
|
||||
f"Server failed to start within {max_retries} seconds.\n"
|
||||
f"STDOUT: {stdout.decode()}\n"
|
||||
f"STDERR: {stderr.decode()}"
|
||||
)
|
||||
|
||||
# Yield control to the tests
|
||||
print('Test server started with isolated database!')
|
||||
yield server_process
|
||||
|
||||
# Cleanup: terminate the server after tests
|
||||
server_process.terminate()
|
||||
try:
|
||||
server_process.wait(timeout=5)
|
||||
print('Test server stopped.')
|
||||
except subprocess.TimeoutExpired:
|
||||
server_process.kill()
|
||||
server_process.wait()
|
||||
print('Test server killed!')
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
|
||||
0
tests/fixtures/__init__.py
vendored
Normal file
0
tests/fixtures/__init__.py
vendored
Normal file
0
tests/fixtures/app_factory.py
vendored
Normal file
0
tests/fixtures/app_factory.py
vendored
Normal file
56
tests/fixtures/test_database.py
vendored
Normal file
56
tests/fixtures/test_database.py
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
import tempfile
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class TestDatabaseManager:
|
||||
"""Manager for temporary test databases"""
|
||||
|
||||
@staticmethod
|
||||
def create_temp_db_path():
|
||||
"""Create a unique temporary path for test database"""
|
||||
temp_dir = tempfile.mkdtemp(prefix="test_mmt_")
|
||||
return os.path.join(temp_dir, "test_tools.db")
|
||||
|
||||
@staticmethod
|
||||
def setup_test_environment(db_path):
|
||||
"""Configure environment to use test database"""
|
||||
os.environ["DB_PATH"] = db_path
|
||||
os.environ["ADMIN_EMAIL"] = "test.admin@test.com"
|
||||
os.environ["ADMIN_PASSWORD"] = "TestAdmin123"
|
||||
os.environ["SECRET_KEY"] = "test-secret-key-for-e2e-tests"
|
||||
|
||||
@staticmethod
|
||||
def inject_test_database(db_path):
|
||||
"""Inject test database instance into the application"""
|
||||
# Import here to avoid circular imports
|
||||
from src.core.user_database import Database, set_user_db
|
||||
|
||||
# Create test database instance
|
||||
test_db = Database(db_path)
|
||||
|
||||
# Set it as the active instance
|
||||
set_user_db(test_db)
|
||||
|
||||
return test_db
|
||||
|
||||
@staticmethod
|
||||
def cleanup_test_db(db_path):
|
||||
"""Clean up temporary database"""
|
||||
if os.path.exists(db_path):
|
||||
# Clean up the complete temporary directory
|
||||
temp_dir = os.path.dirname(db_path)
|
||||
if os.path.exists(temp_dir):
|
||||
shutil.rmtree(temp_dir, ignore_errors=True)
|
||||
|
||||
@staticmethod
|
||||
def restore_original_environment(original_env):
|
||||
"""Restore original environment"""
|
||||
for key, value in original_env.items():
|
||||
if value is None:
|
||||
# Variable didn't exist before
|
||||
if key in os.environ:
|
||||
del os.environ[key]
|
||||
else:
|
||||
os.environ[key] = value
|
||||
61
tests/fixtures/test_users.py
vendored
Normal file
61
tests/fixtures/test_users.py
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
"""Test user definitions for E2E authentication testing"""
|
||||
|
||||
|
||||
class TestUsers:
|
||||
"""Class containing test user data"""
|
||||
|
||||
# Admin user (automatically created on startup)
|
||||
ADMIN = {
|
||||
"email": "test.admin@test.com",
|
||||
"password": "TestAdmin123"
|
||||
}
|
||||
|
||||
# Regular user to create for certain tests
|
||||
REGULAR_USER = {
|
||||
"username": "testuser",
|
||||
"email": "user@test.com",
|
||||
"password": "TestUser123"
|
||||
}
|
||||
|
||||
# Failed authentication test cases
|
||||
INVALID_CREDENTIALS = [
|
||||
{
|
||||
"name": "wrong_password",
|
||||
"email": "test.admin@test.com",
|
||||
"password": "wrongpass",
|
||||
"expected_error": "Invalid email or password"
|
||||
},
|
||||
{
|
||||
"name": "nonexistent_user",
|
||||
"email": "nonexistent@test.com",
|
||||
"password": "TestAdmin123",
|
||||
"expected_error": "Invalid email or password"
|
||||
},
|
||||
{
|
||||
"name": "invalid_email_format",
|
||||
"email": "invalid.email",
|
||||
"password": "TestAdmin123",
|
||||
"expected_error": "Invalid email or password"
|
||||
},
|
||||
{
|
||||
"name": "empty_email",
|
||||
"email": "",
|
||||
"password": "TestAdmin123",
|
||||
"expected_error": "Email and password are required"
|
||||
},
|
||||
{
|
||||
"name": "empty_password",
|
||||
"email": "test.admin@test.com",
|
||||
"password": "",
|
||||
"expected_error": "Email and password are required"
|
||||
}
|
||||
]
|
||||
|
||||
# Protected URLs to test for unauthorized access
|
||||
PROTECTED_URLS = [
|
||||
"/",
|
||||
"/admin",
|
||||
"/repositories",
|
||||
"/workflows",
|
||||
"/applications"
|
||||
]
|
||||
@@ -52,7 +52,7 @@ class TestAuthentication:
|
||||
assert has_login_content, "Page does not contain login-related content"
|
||||
|
||||
# Option 4: Ensure we're not on a protected page
|
||||
# Check that we don't see protected content like "dashboard", "logout", etc.
|
||||
# Check that we don't see protected content like "dashboard", "logout", "profile", "settings"]
|
||||
protected_content = ["dashboard", "logout", "profile", "settings"]
|
||||
page_text = page.locator("body").inner_text().lower()
|
||||
|
||||
@@ -61,7 +61,7 @@ class TestAuthentication:
|
||||
|
||||
@pytest.mark.e2e
|
||||
@pytest.mark.regression
|
||||
def test_login_page_has_required_elements(self, page: Page):
|
||||
def test_login_page_has_required_elements(self, app_server, page: Page):
|
||||
"""Test that the login page contains all required elements"""
|
||||
page.goto(BASE_URL)
|
||||
page.wait_for_load_state("networkidle")
|
||||
@@ -81,7 +81,7 @@ class TestAuthentication:
|
||||
expect(submit_button.first).to_be_visible()
|
||||
|
||||
@pytest.mark.e2e
|
||||
def test_page_loads_without_errors(self, page: Page):
|
||||
def test_page_loads_without_errors(self, app_server, page: Page):
|
||||
"""Test that the login page loads without console errors"""
|
||||
console_errors = []
|
||||
|
||||
@@ -101,3 +101,23 @@ class TestAuthentication:
|
||||
|
||||
# Check that the page has a title
|
||||
expect(page).to_have_title("My Managing Tools")
|
||||
|
||||
# New test to validate database isolation
|
||||
@pytest.mark.e2e
|
||||
@pytest.mark.smoke
|
||||
def test_test_database_isolation(self, app_server, test_database, test_users):
|
||||
"""Test that application uses isolated test database"""
|
||||
import os
|
||||
|
||||
# Verify test environment variables are configured
|
||||
assert os.environ.get("DB_PATH") == test_database
|
||||
assert os.environ.get("ADMIN_EMAIL") == test_users["admin"]["email"]
|
||||
assert os.environ.get("ADMIN_PASSWORD") == test_users["admin"]["password"]
|
||||
|
||||
# Verify temporary database file exists
|
||||
assert os.path.exists(test_database), f"Test database file not found: {test_database}"
|
||||
|
||||
# Verify path contains 'test_mmt_' to confirm isolation
|
||||
assert "test_mmt_" in test_database, "Database path should contain test prefix"
|
||||
|
||||
print(f"✅ Test database isolation confirmed: {test_database}")
|
||||
|
||||
Reference in New Issue
Block a user