removed old UI
This commit is contained in:
parent
4433d7d2c4
commit
f71841e922
62 changed files with 411 additions and 5815 deletions
|
|
@ -1,36 +1,29 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.auth.security import PasswordService
|
||||
|
||||
CSRF_TOKEN_PATTERN = re.compile(r'name="csrf_token" value="([^"]+)"')
|
||||
|
||||
|
||||
def extract_csrf_token(html: str) -> str:
|
||||
match = CSRF_TOKEN_PATTERN.search(html)
|
||||
assert match is not None
|
||||
return match.group(1)
|
||||
|
||||
|
||||
def login_user(client: TestClient, *, email: str, password: str) -> None:
|
||||
login_page = client.get("/login")
|
||||
csrf_token = extract_csrf_token(login_page.text)
|
||||
bootstrap_response = client.get("/api/v1/auth/csrf")
|
||||
assert bootstrap_response.status_code == 200
|
||||
csrf_token = bootstrap_response.json()["data"]["csrf_token"]
|
||||
assert isinstance(csrf_token, str) and csrf_token
|
||||
|
||||
response = client.post(
|
||||
"/login",
|
||||
data={
|
||||
"/api/v1/auth/login",
|
||||
json={
|
||||
"email": email,
|
||||
"password": password,
|
||||
"csrf_token": csrf_token,
|
||||
},
|
||||
headers={"X-CSRF-Token": csrf_token},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert response.status_code == 303
|
||||
assert response.headers["location"] == "/"
|
||||
assert response.status_code == 200
|
||||
payload = response.json()
|
||||
assert payload["data"]["authenticated"] is True
|
||||
|
||||
|
||||
async def insert_user(
|
||||
|
|
@ -60,4 +53,3 @@ async def insert_user(
|
|||
user_id = int(result.scalar_one())
|
||||
await db_session.commit()
|
||||
return user_id
|
||||
|
||||
|
|
|
|||
|
|
@ -1,200 +0,0 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.main import app
|
||||
from tests.integration.helpers import extract_csrf_token, insert_user, login_user
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.db
|
||||
@pytest.mark.asyncio
|
||||
async def test_users_page_is_admin_only(db_session: AsyncSession) -> None:
|
||||
await insert_user(
|
||||
db_session,
|
||||
email="member@example.com",
|
||||
password="member-pass",
|
||||
is_admin=False,
|
||||
)
|
||||
|
||||
client = TestClient(app)
|
||||
login_user(client, email="member@example.com", password="member-pass")
|
||||
|
||||
response = client.get("/users")
|
||||
|
||||
assert response.status_code == 403
|
||||
assert response.json()["detail"] == "Admin access required."
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.db
|
||||
@pytest.mark.asyncio
|
||||
async def test_non_admin_dashboard_hides_users_nav(db_session: AsyncSession) -> None:
|
||||
await insert_user(
|
||||
db_session,
|
||||
email="member@example.com",
|
||||
password="member-pass",
|
||||
is_admin=False,
|
||||
)
|
||||
|
||||
client = TestClient(app)
|
||||
login_user(client, email="member@example.com", password="member-pass")
|
||||
dashboard = client.get("/")
|
||||
|
||||
assert dashboard.status_code == 200
|
||||
assert ">Users<" not in dashboard.text
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.db
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_dashboard_shows_users_nav(db_session: AsyncSession) -> None:
|
||||
await insert_user(
|
||||
db_session,
|
||||
email="admin@example.com",
|
||||
password="admin-pass",
|
||||
is_admin=True,
|
||||
)
|
||||
|
||||
client = TestClient(app)
|
||||
login_user(client, email="admin@example.com", password="admin-pass")
|
||||
dashboard = client.get("/")
|
||||
|
||||
assert dashboard.status_code == 200
|
||||
assert ">Users<" in dashboard.text
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.db
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_can_create_and_deactivate_user(db_session: AsyncSession) -> None:
|
||||
await insert_user(
|
||||
db_session,
|
||||
email="admin@example.com",
|
||||
password="admin-pass",
|
||||
is_admin=True,
|
||||
)
|
||||
|
||||
client = TestClient(app)
|
||||
login_user(client, email="admin@example.com", password="admin-pass")
|
||||
|
||||
users_page = client.get("/users")
|
||||
csrf_token = extract_csrf_token(users_page.text)
|
||||
create_response = client.post(
|
||||
"/users",
|
||||
data={
|
||||
"email": "new-user@example.com",
|
||||
"password": "new-user-pass",
|
||||
"csrf_token": csrf_token,
|
||||
},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert create_response.status_code == 303
|
||||
assert create_response.headers["location"].startswith("/users")
|
||||
|
||||
created_user_id_result = await db_session.execute(
|
||||
text("SELECT id FROM users WHERE email = 'new-user@example.com'")
|
||||
)
|
||||
created_user_id = int(created_user_id_result.scalar_one())
|
||||
|
||||
users_page_after_create = client.get("/users")
|
||||
csrf_after_create = extract_csrf_token(users_page_after_create.text)
|
||||
toggle_response = client.post(
|
||||
f"/users/{created_user_id}/toggle-active",
|
||||
data={"csrf_token": csrf_after_create},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert toggle_response.status_code == 303
|
||||
|
||||
status_result = await db_session.execute(
|
||||
text("SELECT is_active FROM users WHERE id = :user_id"),
|
||||
{"user_id": created_user_id},
|
||||
)
|
||||
assert status_result.scalar_one() is False
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.db
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_can_reset_user_password(db_session: AsyncSession) -> None:
|
||||
target_user_id = await insert_user(
|
||||
db_session,
|
||||
email="target@example.com",
|
||||
password="old-password",
|
||||
is_admin=False,
|
||||
)
|
||||
await insert_user(
|
||||
db_session,
|
||||
email="admin@example.com",
|
||||
password="admin-pass",
|
||||
is_admin=True,
|
||||
)
|
||||
|
||||
client = TestClient(app)
|
||||
login_user(client, email="admin@example.com", password="admin-pass")
|
||||
|
||||
users_page = client.get("/users")
|
||||
csrf_token = extract_csrf_token(users_page.text)
|
||||
reset_response = client.post(
|
||||
f"/users/{target_user_id}/reset-password",
|
||||
data={
|
||||
"new_password": "new-password",
|
||||
"csrf_token": csrf_token,
|
||||
},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert reset_response.status_code == 303
|
||||
|
||||
users_page_after_reset = client.get("/users")
|
||||
logout_csrf = extract_csrf_token(users_page_after_reset.text)
|
||||
client.post(
|
||||
"/logout",
|
||||
data={"csrf_token": logout_csrf},
|
||||
follow_redirects=False,
|
||||
)
|
||||
|
||||
failed_login_page = client.get("/login")
|
||||
failed_login_csrf = extract_csrf_token(failed_login_page.text)
|
||||
failed_login = client.post(
|
||||
"/login",
|
||||
data={
|
||||
"email": "target@example.com",
|
||||
"password": "old-password",
|
||||
"csrf_token": failed_login_csrf,
|
||||
},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert failed_login.status_code == 401
|
||||
|
||||
login_user(client, email="target@example.com", password="new-password")
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.db
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_cannot_deactivate_self(db_session: AsyncSession) -> None:
|
||||
admin_user_id = await insert_user(
|
||||
db_session,
|
||||
email="admin@example.com",
|
||||
password="admin-pass",
|
||||
is_admin=True,
|
||||
)
|
||||
client = TestClient(app)
|
||||
login_user(client, email="admin@example.com", password="admin-pass")
|
||||
|
||||
users_page = client.get("/users")
|
||||
csrf_token = extract_csrf_token(users_page.text)
|
||||
response = client.post(
|
||||
f"/users/{admin_user_id}/toggle-active",
|
||||
data={"csrf_token": csrf_token},
|
||||
follow_redirects=False,
|
||||
)
|
||||
|
||||
assert response.status_code == 303
|
||||
assert response.headers["location"].startswith("/users")
|
||||
result = await db_session.execute(
|
||||
text("SELECT is_active FROM users WHERE id = :user_id"),
|
||||
{"user_id": admin_user_id},
|
||||
)
|
||||
assert result.scalar_one() is True
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
import re
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.auth.security import PasswordService
|
||||
from app.main import app
|
||||
|
||||
CSRF_TOKEN_PATTERN = re.compile(r'name="csrf_token" value="([^"]+)"')
|
||||
|
||||
|
||||
def _extract_csrf_token(html: str) -> str:
|
||||
match = CSRF_TOKEN_PATTERN.search(html)
|
||||
assert match is not None
|
||||
return match.group(1)
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
def test_dashboard_requires_authentication() -> None:
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get("/", follow_redirects=False)
|
||||
|
||||
assert response.status_code == 303
|
||||
assert response.headers["location"] == "/login"
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.db
|
||||
@pytest.mark.asyncio
|
||||
async def test_login_with_valid_credentials_allows_access(db_session: AsyncSession) -> None:
|
||||
password_service = PasswordService()
|
||||
await db_session.execute(
|
||||
text(
|
||||
"""
|
||||
INSERT INTO users (email, password_hash, is_active, is_admin)
|
||||
VALUES (:email, :password_hash, :is_active, :is_admin)
|
||||
"""
|
||||
),
|
||||
{
|
||||
"email": "reader@example.com",
|
||||
"password_hash": password_service.hash_password("correct-password"),
|
||||
"is_active": True,
|
||||
"is_admin": False,
|
||||
},
|
||||
)
|
||||
await db_session.commit()
|
||||
|
||||
client = TestClient(app)
|
||||
login_page = client.get("/login")
|
||||
csrf_token = _extract_csrf_token(login_page.text)
|
||||
login_response = client.post(
|
||||
"/login",
|
||||
data={
|
||||
"email": "reader@example.com",
|
||||
"password": "correct-password",
|
||||
"csrf_token": csrf_token,
|
||||
},
|
||||
follow_redirects=False,
|
||||
)
|
||||
|
||||
assert login_response.status_code == 303
|
||||
assert login_response.headers["location"] == "/"
|
||||
|
||||
dashboard_response = client.get("/")
|
||||
assert dashboard_response.status_code == 200
|
||||
assert "reader@example.com" in dashboard_response.text
|
||||
assert 'data-test="home-hero"' in dashboard_response.text
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.db
|
||||
@pytest.mark.asyncio
|
||||
async def test_login_rejects_inactive_user(db_session: AsyncSession) -> None:
|
||||
password_service = PasswordService()
|
||||
await db_session.execute(
|
||||
text(
|
||||
"""
|
||||
INSERT INTO users (email, password_hash, is_active, is_admin)
|
||||
VALUES (:email, :password_hash, :is_active, :is_admin)
|
||||
"""
|
||||
),
|
||||
{
|
||||
"email": "inactive@example.com",
|
||||
"password_hash": password_service.hash_password("correct-password"),
|
||||
"is_active": False,
|
||||
"is_admin": False,
|
||||
},
|
||||
)
|
||||
await db_session.commit()
|
||||
|
||||
client = TestClient(app)
|
||||
login_page = client.get("/login")
|
||||
csrf_token = _extract_csrf_token(login_page.text)
|
||||
login_response = client.post(
|
||||
"/login",
|
||||
data={
|
||||
"email": "inactive@example.com",
|
||||
"password": "correct-password",
|
||||
"csrf_token": csrf_token,
|
||||
},
|
||||
follow_redirects=False,
|
||||
)
|
||||
|
||||
assert login_response.status_code == 401
|
||||
assert "Invalid email or password." in login_response.text
|
||||
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,227 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.main import app
|
||||
from tests.integration.helpers import extract_csrf_token, insert_user, login_user
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.db
|
||||
@pytest.mark.asyncio
|
||||
async def test_runs_page_queue_actions_lifecycle(db_session: AsyncSession) -> None:
|
||||
user_id = await insert_user(
|
||||
db_session,
|
||||
email="queue-ui@example.com",
|
||||
password="queue-ui-password",
|
||||
)
|
||||
scholar_result = await db_session.execute(
|
||||
text(
|
||||
"""
|
||||
INSERT INTO scholar_profiles (user_id, scholar_id, display_name, is_enabled)
|
||||
VALUES (:user_id, :scholar_id, :display_name, true)
|
||||
RETURNING id
|
||||
"""
|
||||
),
|
||||
{
|
||||
"user_id": user_id,
|
||||
"scholar_id": "abcDEF123456",
|
||||
"display_name": "Queue UI Scholar",
|
||||
},
|
||||
)
|
||||
scholar_profile_id = int(scholar_result.scalar_one())
|
||||
|
||||
queue_result = await db_session.execute(
|
||||
text(
|
||||
"""
|
||||
INSERT INTO ingestion_queue_items (
|
||||
user_id,
|
||||
scholar_profile_id,
|
||||
resume_cstart,
|
||||
reason,
|
||||
status,
|
||||
attempt_count,
|
||||
next_attempt_dt,
|
||||
last_error,
|
||||
dropped_reason,
|
||||
dropped_at
|
||||
)
|
||||
VALUES (
|
||||
:user_id,
|
||||
:scholar_profile_id,
|
||||
200,
|
||||
'dropped',
|
||||
'dropped',
|
||||
3,
|
||||
NOW() + INTERVAL '30 minutes',
|
||||
'captcha challenge',
|
||||
'max_attempts_after_run',
|
||||
NOW() - INTERVAL '1 minute'
|
||||
)
|
||||
RETURNING id
|
||||
"""
|
||||
),
|
||||
{
|
||||
"user_id": user_id,
|
||||
"scholar_profile_id": scholar_profile_id,
|
||||
},
|
||||
)
|
||||
queue_item_id = int(queue_result.scalar_one())
|
||||
await db_session.commit()
|
||||
|
||||
client = TestClient(app)
|
||||
login_user(client, email="queue-ui@example.com", password="queue-ui-password")
|
||||
|
||||
runs_page = client.get("/runs")
|
||||
assert runs_page.status_code == 200
|
||||
assert "Continuation Queue" in runs_page.text
|
||||
assert "Queue UI Scholar" in runs_page.text
|
||||
assert "dropped" in runs_page.text
|
||||
assert "max_attempts_after_run" in runs_page.text
|
||||
|
||||
csrf_retry = extract_csrf_token(runs_page.text)
|
||||
retry_response = client.post(
|
||||
f"/runs/queue/{queue_item_id}/retry",
|
||||
data={"csrf_token": csrf_retry},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert retry_response.status_code == 303
|
||||
|
||||
queue_after_retry = await db_session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT status, reason, attempt_count
|
||||
FROM ingestion_queue_items
|
||||
WHERE id = :queue_item_id
|
||||
"""
|
||||
),
|
||||
{"queue_item_id": queue_item_id},
|
||||
)
|
||||
assert queue_after_retry.one() == ("queued", "manual_retry", 0)
|
||||
|
||||
runs_page_after_retry = client.get("/runs")
|
||||
csrf_drop = extract_csrf_token(runs_page_after_retry.text)
|
||||
drop_response = client.post(
|
||||
f"/runs/queue/{queue_item_id}/drop",
|
||||
data={"csrf_token": csrf_drop},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert drop_response.status_code == 303
|
||||
|
||||
queue_after_drop = await db_session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT status, reason, dropped_reason
|
||||
FROM ingestion_queue_items
|
||||
WHERE id = :queue_item_id
|
||||
"""
|
||||
),
|
||||
{"queue_item_id": queue_item_id},
|
||||
)
|
||||
assert queue_after_drop.one() == ("dropped", "dropped", "manual_drop")
|
||||
|
||||
runs_page_after_drop = client.get("/runs")
|
||||
csrf_clear = extract_csrf_token(runs_page_after_drop.text)
|
||||
clear_response = client.post(
|
||||
f"/runs/queue/{queue_item_id}/clear",
|
||||
data={"csrf_token": csrf_clear},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert clear_response.status_code == 303
|
||||
|
||||
queue_after_clear = await db_session.execute(
|
||||
text("SELECT COUNT(*) FROM ingestion_queue_items WHERE id = :queue_item_id"),
|
||||
{"queue_item_id": queue_item_id},
|
||||
)
|
||||
assert queue_after_clear.scalar_one() == 0
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.db
|
||||
@pytest.mark.asyncio
|
||||
async def test_runs_queue_actions_are_tenant_scoped(db_session: AsyncSession) -> None:
|
||||
user_a_id = await insert_user(
|
||||
db_session,
|
||||
email="queue-owner-a@example.com",
|
||||
password="queue-owner-a-password",
|
||||
)
|
||||
user_b_id = await insert_user(
|
||||
db_session,
|
||||
email="queue-owner-b@example.com",
|
||||
password="queue-owner-b-password",
|
||||
)
|
||||
|
||||
scholar_result = await db_session.execute(
|
||||
text(
|
||||
"""
|
||||
INSERT INTO scholar_profiles (user_id, scholar_id, display_name, is_enabled)
|
||||
VALUES (:user_id, :scholar_id, :display_name, true)
|
||||
RETURNING id
|
||||
"""
|
||||
),
|
||||
{
|
||||
"user_id": user_b_id,
|
||||
"scholar_id": "zxyWVU654321",
|
||||
"display_name": "Owner B Queue Scholar",
|
||||
},
|
||||
)
|
||||
scholar_profile_id = int(scholar_result.scalar_one())
|
||||
queue_result = await db_session.execute(
|
||||
text(
|
||||
"""
|
||||
INSERT INTO ingestion_queue_items (
|
||||
user_id,
|
||||
scholar_profile_id,
|
||||
resume_cstart,
|
||||
reason,
|
||||
status,
|
||||
attempt_count,
|
||||
next_attempt_dt
|
||||
)
|
||||
VALUES (
|
||||
:user_id,
|
||||
:scholar_profile_id,
|
||||
0,
|
||||
'max_pages_reached',
|
||||
'queued',
|
||||
0,
|
||||
NOW()
|
||||
)
|
||||
RETURNING id
|
||||
"""
|
||||
),
|
||||
{
|
||||
"user_id": user_b_id,
|
||||
"scholar_profile_id": scholar_profile_id,
|
||||
},
|
||||
)
|
||||
queue_item_id = int(queue_result.scalar_one())
|
||||
await db_session.commit()
|
||||
|
||||
client = TestClient(app)
|
||||
login_user(client, email="queue-owner-a@example.com", password="queue-owner-a-password")
|
||||
|
||||
runs_page = client.get("/runs")
|
||||
csrf_token = extract_csrf_token(runs_page.text)
|
||||
forbidden_retry = client.post(
|
||||
f"/runs/queue/{queue_item_id}/retry",
|
||||
data={"csrf_token": csrf_token},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert forbidden_retry.status_code == 404
|
||||
|
||||
unchanged = await db_session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT status, reason, user_id
|
||||
FROM ingestion_queue_items
|
||||
WHERE id = :queue_item_id
|
||||
"""
|
||||
),
|
||||
{"queue_item_id": queue_item_id},
|
||||
)
|
||||
assert unchanged.one() == ("queued", "max_pages_reached", user_b_id)
|
||||
assert user_a_id != user_b_id
|
||||
|
|
@ -1,197 +0,0 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.main import app
|
||||
from tests.integration.helpers import extract_csrf_token, insert_user, login_user
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.db
|
||||
@pytest.mark.asyncio
|
||||
async def test_user_can_change_own_password(db_session: AsyncSession) -> None:
|
||||
await insert_user(
|
||||
db_session,
|
||||
email="reader@example.com",
|
||||
password="old-password",
|
||||
)
|
||||
|
||||
client = TestClient(app)
|
||||
login_user(client, email="reader@example.com", password="old-password")
|
||||
|
||||
account_page = client.get("/account/password")
|
||||
csrf_token = extract_csrf_token(account_page.text)
|
||||
wrong_current = client.post(
|
||||
"/account/password",
|
||||
data={
|
||||
"current_password": "wrong-password",
|
||||
"new_password": "new-password",
|
||||
"confirm_password": "new-password",
|
||||
"csrf_token": csrf_token,
|
||||
},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert wrong_current.status_code == 303
|
||||
assert wrong_current.headers["location"].startswith("/account/password")
|
||||
|
||||
account_page_retry = client.get("/account/password")
|
||||
retry_csrf = extract_csrf_token(account_page_retry.text)
|
||||
success = client.post(
|
||||
"/account/password",
|
||||
data={
|
||||
"current_password": "old-password",
|
||||
"new_password": "new-password",
|
||||
"confirm_password": "new-password",
|
||||
"csrf_token": retry_csrf,
|
||||
},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert success.status_code == 303
|
||||
|
||||
account_page_after = client.get("/account/password")
|
||||
logout_csrf = extract_csrf_token(account_page_after.text)
|
||||
client.post("/logout", data={"csrf_token": logout_csrf}, follow_redirects=False)
|
||||
|
||||
failed_login_page = client.get("/login")
|
||||
failed_login_csrf = extract_csrf_token(failed_login_page.text)
|
||||
failed_login = client.post(
|
||||
"/login",
|
||||
data={
|
||||
"email": "reader@example.com",
|
||||
"password": "old-password",
|
||||
"csrf_token": failed_login_csrf,
|
||||
},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert failed_login.status_code == 401
|
||||
|
||||
login_user(client, email="reader@example.com", password="new-password")
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.db
|
||||
@pytest.mark.asyncio
|
||||
async def test_scholar_routes_are_tenant_scoped(db_session: AsyncSession) -> None:
|
||||
user_a_id = await insert_user(
|
||||
db_session,
|
||||
email="owner-a@example.com",
|
||||
password="owner-a-password",
|
||||
)
|
||||
user_b_id = await insert_user(
|
||||
db_session,
|
||||
email="owner-b@example.com",
|
||||
password="owner-b-password",
|
||||
)
|
||||
|
||||
client = TestClient(app)
|
||||
login_user(client, email="owner-a@example.com", password="owner-a-password")
|
||||
|
||||
scholars_page = client.get("/scholars")
|
||||
csrf_token = extract_csrf_token(scholars_page.text)
|
||||
create_response = client.post(
|
||||
"/scholars",
|
||||
data={
|
||||
"scholar_id": "abcDEF123456",
|
||||
"display_name": "Owner A Scholar",
|
||||
"csrf_token": csrf_token,
|
||||
},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert create_response.status_code == 303
|
||||
|
||||
owner_a_row = await db_session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT user_id
|
||||
FROM scholar_profiles
|
||||
WHERE scholar_id = 'abcDEF123456'
|
||||
"""
|
||||
)
|
||||
)
|
||||
assert owner_a_row.scalar_one() == user_a_id
|
||||
|
||||
owner_b_profile = await db_session.execute(
|
||||
text(
|
||||
"""
|
||||
INSERT INTO scholar_profiles (user_id, scholar_id, display_name, is_enabled)
|
||||
VALUES (:user_id, :scholar_id, :display_name, :is_enabled)
|
||||
RETURNING id
|
||||
"""
|
||||
),
|
||||
{
|
||||
"user_id": user_b_id,
|
||||
"scholar_id": "zxcvbn654321",
|
||||
"display_name": "Owner B Scholar",
|
||||
"is_enabled": True,
|
||||
},
|
||||
)
|
||||
owner_b_profile_id = int(owner_b_profile.scalar_one())
|
||||
await db_session.commit()
|
||||
|
||||
scholars_page_after_insert = client.get("/scholars")
|
||||
csrf_after_insert = extract_csrf_token(scholars_page_after_insert.text)
|
||||
forbidden_toggle = client.post(
|
||||
f"/scholars/{owner_b_profile_id}/toggle",
|
||||
data={"csrf_token": csrf_after_insert},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert forbidden_toggle.status_code == 404
|
||||
|
||||
owner_b_status = await db_session.execute(
|
||||
text("SELECT is_enabled FROM scholar_profiles WHERE id = :profile_id"),
|
||||
{"profile_id": owner_b_profile_id},
|
||||
)
|
||||
assert owner_b_status.scalar_one() is True
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.db
|
||||
@pytest.mark.asyncio
|
||||
async def test_settings_updates_are_user_scoped(db_session: AsyncSession) -> None:
|
||||
user_a_id = await insert_user(
|
||||
db_session,
|
||||
email="settings-a@example.com",
|
||||
password="settings-a-password",
|
||||
)
|
||||
user_b_id = await insert_user(
|
||||
db_session,
|
||||
email="settings-b@example.com",
|
||||
password="settings-b-password",
|
||||
)
|
||||
|
||||
client = TestClient(app)
|
||||
login_user(client, email="settings-a@example.com", password="settings-a-password")
|
||||
|
||||
settings_page = client.get("/settings")
|
||||
csrf_token = extract_csrf_token(settings_page.text)
|
||||
response = client.post(
|
||||
"/settings",
|
||||
data={
|
||||
"auto_run_enabled": "on",
|
||||
"run_interval_minutes": "45",
|
||||
"request_delay_seconds": "7",
|
||||
"csrf_token": csrf_token,
|
||||
},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert response.status_code == 303
|
||||
|
||||
user_a_settings = await db_session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT auto_run_enabled, run_interval_minutes, request_delay_seconds
|
||||
FROM user_settings
|
||||
WHERE user_id = :user_id
|
||||
"""
|
||||
),
|
||||
{"user_id": user_a_id},
|
||||
)
|
||||
assert user_a_settings.one() == (True, 45, 7)
|
||||
|
||||
user_b_settings = await db_session.execute(
|
||||
text("SELECT COUNT(*) FROM user_settings WHERE user_id = :user_id"),
|
||||
{"user_id": user_b_id},
|
||||
)
|
||||
assert user_b_settings.scalar_one() == 0
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue