Files
wehub-resource-sync 7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

312 lines
10 KiB
Python

"""
Integration tests for the complete authentication system.
"""
import os
import shutil
import tempfile
from pathlib import Path
import pytest
from local_deep_research.database.auth_db import (
get_auth_db_session,
init_auth_database,
)
from local_deep_research.database.models.auth import User
from local_deep_research.web.app_factory import create_app
@pytest.fixture
def temp_data_dir():
"""Create a temporary data directory for testing."""
temp_dir = tempfile.mkdtemp()
yield Path(temp_dir)
shutil.rmtree(temp_dir)
@pytest.fixture
def app(temp_data_dir, monkeypatch):
"""Create a Flask app configured for testing."""
monkeypatch.setenv("LDR_DATA_DIR", str(temp_data_dir))
# Clear database manager state before creating app
from local_deep_research.database.encrypted_db import db_manager
db_manager.close_all_databases()
# Reset db_manager's data directory to temp directory
db_manager.data_dir = temp_data_dir / "encrypted_databases"
db_manager.data_dir.mkdir(parents=True, exist_ok=True)
# Remove any existing user databases in the temp directory
encrypted_db_dir = temp_data_dir / "encrypted_databases"
if encrypted_db_dir.exists():
shutil.rmtree(encrypted_db_dir)
encrypted_db_dir.mkdir(parents=True, exist_ok=True)
# Initialize fresh auth database
init_auth_database()
# Clean up any existing test users before starting
auth_db = get_auth_db_session()
auth_db.query(User).filter(User.username.like("integrationtest%")).delete()
auth_db.query(User).filter(User.username.like("testuser%")).delete()
auth_db.commit()
auth_db.close()
app, _ = create_app()
app.config["TESTING"] = True
app.config["WTF_CSRF_ENABLED"] = False
app.config["SESSION_COOKIE_SECURE"] = False
yield app
# Cleanup after test
db_manager.close_all_databases()
# Clean up test users from auth database
auth_db = get_auth_db_session()
auth_db.query(User).filter(User.username.like("integrationtest%")).delete()
auth_db.query(User).filter(User.username.like("testuser%")).delete()
auth_db.commit()
auth_db.close()
@pytest.fixture
def client(app):
"""Create a test client."""
return app.test_client()
class TestAuthIntegration:
"""Integration tests for authentication system."""
def test_full_user_lifecycle(self, client):
"""Test complete user lifecycle: register, login, use app, logout."""
# 1. Start unauthenticated - should redirect to login
response = client.get("/")
assert response.status_code == 302
assert "/auth/login" in response.location
# 2. Register new user
response = client.post(
"/auth/register",
data={
"username": "integrationtest",
"password": "TestPass123", # pragma: allowlist secret
"confirm_password": "TestPass123", # pragma: allowlist secret
"acknowledge": "true",
},
follow_redirects=True,
)
assert response.status_code == 200
# 3. Should be logged in automatically after registration
response = client.get("/")
assert response.status_code == 200
# The main page should show we're logged in (username might be displayed)
# 4. Access protected routes
response = client.get("/history")
assert response.status_code == 200
response = client.get("/settings")
assert response.status_code == 200
# 5. Check auth status
response = client.get("/auth/check")
assert response.status_code == 200
data = response.get_json()
assert data["authenticated"] is True
assert data["username"] == "integrationtest"
# 6. Logout
response = client.post("/auth/logout", follow_redirects=True)
assert response.status_code == 200
assert b"You have been logged out successfully" in response.data
# 7. Should not be able to access protected routes
response = client.get("/history")
assert response.status_code == 302
assert "/auth/login" in response.location
# 8. Login again
response = client.post(
"/auth/login",
data={"username": "integrationtest", "password": "TestPass123"},
follow_redirects=True,
)
assert response.status_code == 200
# 9. Should be able to access protected routes again
response = client.get("/history")
assert response.status_code == 200
def test_multiple_users(self, client):
"""Test that multiple users can register and have separate sessions."""
# Register first user
client.post(
"/auth/register",
data={
"username": "user1",
"password": "Password1X",
"confirm_password": "Password1X",
"acknowledge": "true",
},
)
# Check logged in as user1
response = client.get("/auth/check")
data = response.get_json()
assert data["username"] == "user1"
# Logout
client.post("/auth/logout")
# Register second user
client.post(
"/auth/register",
data={
"username": "user2",
"password": "Password2X",
"confirm_password": "Password2X",
"acknowledge": "true",
},
)
# Check logged in as user2
response = client.get("/auth/check")
data = response.get_json()
assert data["username"] == "user2"
# Verify both users exist in auth database
auth_db = get_auth_db_session()
users = auth_db.query(User).all()
assert len(users) == 2
usernames = [user.username for user in users]
assert "user1" in usernames
assert "user2" in usernames
auth_db.close()
def test_session_persistence(self, client):
"""Test that sessions persist across requests."""
# Register and login
client.post(
"/auth/register",
data={
"username": "sessiontest",
"password": "TestPass123", # pragma: allowlist secret
"confirm_password": "TestPass123", # pragma: allowlist secret
"acknowledge": "true",
},
)
# Make multiple requests
for _ in range(5):
response = client.get("/history")
assert response.status_code == 200
response = client.get("/auth/check")
data = response.get_json()
assert data["username"] == "sessiontest"
def test_protected_api_endpoints(self, client):
"""Test that API endpoints require authentication."""
# Test various API endpoints without auth
endpoints = [
("/api/start_research", "POST"),
("/api/history", "GET"),
("/settings/api", "GET"),
("/history/api", "GET"),
("/metrics/api/metrics", "GET"),
]
for endpoint, method in endpoints:
if method == "GET":
response = client.get(endpoint)
else:
response = client.post(endpoint, json={})
# All these endpoints contain the slash-bounded `api` segment
# (either /api/ in the middle or trailing /api), so the auth
# decorator returns JSON 401 for unauthenticated callers.
assert response.status_code == 401
assert response.get_json()["error"] == "Authentication required"
# Register and try again
client.post(
"/auth/register",
data={
"username": "apitest",
"password": "TestPass123", # pragma: allowlist secret
"confirm_password": "TestPass123", # pragma: allowlist secret
"acknowledge": "true",
},
)
# Now endpoints should be accessible (may return errors but not redirects)
for endpoint, method in endpoints:
if method == "GET":
response = client.get(endpoint)
else:
response = client.post(endpoint, json={})
assert response.status_code != 302 # Not a redirect
@pytest.mark.skipif(
os.environ.get("CI") == "true"
or os.environ.get("GITHUB_ACTIONS") == "true",
reason="Password change with encrypted DB re-keying is complex to test in CI",
)
def test_password_change_flow(self, client):
"""Test the complete password change flow."""
# Register user
client.post(
"/auth/register",
data={
"username": "changetest",
"password": "OldPass123",
"confirm_password": "OldPass123",
"acknowledge": "true",
},
)
# Change password
response = client.post(
"/auth/change-password",
data={
"current_password": "OldPass123",
"new_password": "NewPass456", # pragma: allowlist secret
"confirm_password": "NewPass456",
},
follow_redirects=True,
)
assert b"Password changed successfully" in response.data
assert b"Please login with your new password" in response.data
# Should be logged out
response = client.get("/auth/check")
assert response.status_code == 401
# Old password should fail
response = client.post(
"/auth/login",
data={"username": "changetest", "password": "OldPass123"},
)
assert response.status_code == 401
# New password should work
response = client.post(
"/auth/login",
data={"username": "changetest", "password": "NewPass456"},
follow_redirects=True,
)
assert response.status_code == 200
# Verify logged in
response = client.get("/auth/check")
data = response.get_json()
assert data["username"] == "changetest"