227 lines
6.7 KiB
Python
227 lines
6.7 KiB
Python
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
|