Noor2623's picture
Add Gemini query routing and department-scoped ticket access
eeff6a0
Raw History Blame Contribute Delete
8.01 kB
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool
from app.core.database import Base
from app.core.security import create_access_token, hash_password
from app.dependencies import get_db
from app.models.department import Department
from app.models.query import Query
from app.models.user import User
from app.routes import admin, auth, queries
@pytest.fixture(scope="module")
def password_hash():
return hash_password("test-password-123")
@pytest.fixture
def workspace(password_hash):
engine = create_engine(
"sqlite+pysqlite:///:memory:",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
Base.metadata.create_all(engine)
with sessionmaker(bind=engine)() as db:
departments = [
Department(id=11, name="Examination Department", code="EXAM", keywords="exam"),
Department(id=22, name="Finance Department", code="FIN", keywords="fees"),
Department(id=33, name="Registrar", code="REG", keywords="transcript"),
Department(id=44, name="IT Dept", code="IT", keywords="wifi"),
Department(id=55, name="Instructor", code="INST", keywords="feedback"),
Department(id=66, name="Admin", code="ADMIN", keywords="general"),
]
db.add_all(departments)
db.flush()
users = {
"student": User(email="student@example.com", role="STUDENT"),
"other_student": User(email="other@example.com", role="STUDENT"),
"admin": User(email="admin@example.com", role="ADMIN", is_admin=True),
"unassigned_staff": User(email="unassigned@example.com", role="STAFF"),
}
for department in departments:
users[department.code] = User(
email=f"staff{department.id}@example.com",
role=department.name,
department_id=department.id,
)
for user in users.values():
user.hashed_password = password_hash
db.add_all(users.values())
db.flush()
for query_id, department in enumerate(departments, start=101):
db.add(Query(
id=query_id, student_id=users["student"].id,
sender_email=users["student"].email,
message=f"Question for {department.name}",
department_id=department.id,
))
db.add_all([
Query(id=107, student_id=users["other_student"].id, sender_email="other@example.com",
message="Another student's fees query", department_id=22),
Query(id=108, student_id=users["other_student"].id, sender_email="other@example.com",
message="Unassigned query"),
Query(id=109, student_id=users["FIN"].id, sender_email=users["FIN"].email,
message="Staff's own exam question", department_id=11),
])
db.commit()
app = FastAPI()
for router in (auth.router, queries.router, admin.router):
app.include_router(router)
def test_db():
yield db
app.dependency_overrides[get_db] = test_db
with TestClient(app) as client:
yield client, db, users
engine.dispose()
def headers(user):
return {"Authorization": f"Bearer {create_access_token(str(user.id))}"}
@pytest.mark.parametrize(("account", "expected_ids"), [
("EXAM", {101, 109}), ("FIN", {102, 107}), ("REG", {103}),
("IT", {104}), ("INST", {105}), ("ADMIN", {106}),
])
def test_department_staff_only_receive_assigned_queries(workspace, account, expected_ids):
client, _, users = workspace
response = client.get("/queries/", headers=headers(users[account]))
assert response.status_code == 200
assert {item["id"] for item in response.json()} == expected_ids
@pytest.mark.parametrize("path", ["/queries/", "/admin/queries"])
def test_admin_receives_all_departments_and_unassigned_queries(workspace, path):
client, _, users = workspace
response = client.get(path, headers=headers(users["admin"]))
assert response.status_code == 200
assert {item["id"] for item in response.json()} == set(range(101, 110))
@pytest.mark.parametrize(("account", "expected_ids"), [
("student", {101, 102, 103, 104, 105, 106}), ("other_student", {107, 108}),
])
def test_students_only_receive_their_own_queries(workspace, account, expected_ids):
client, _, users = workspace
response = client.get("/queries/", headers=headers(users[account]))
assert response.status_code == 200
assert {item["id"] for item in response.json()} == expected_ids
def test_student_role_does_not_grant_department_access(workspace):
client, db, users = workspace
users["student"].department_id = 22
db.commit()
response = client.get("/queries/", headers=headers(users["student"]))
assert {item["id"] for item in response.json()} == {101, 102, 103, 104, 105, 106}
def test_staff_without_department_cannot_see_unassigned_queries(workspace):
client, _, users = workspace
response = client.get("/queries/", headers=headers(users["unassigned_staff"]))
assert response.status_code == 200
assert response.json() == []
@pytest.mark.parametrize("account", ["EXAM", "FIN", "REG", "IT", "INST", "ADMIN", "student"])
def test_only_admin_can_use_all_queries_endpoint(workspace, account):
client, _, users = workspace
assert client.get("/admin/queries", headers=headers(users[account])).status_code == 403
@pytest.mark.parametrize("path", ["/queries/", "/admin/queries"])
def test_reading_queries_requires_login(workspace, path):
client, _, _ = workspace
assert client.get(path).status_code == 401
def test_request_parameters_cannot_change_a_staff_members_scope(workspace):
client, _, users = workspace
response = client.get("/queries/?department_id=11&role=Admin&is_admin=true",
headers=headers(users["FIN"]))
assert response.status_code == 200
assert {item["id"] for item in response.json()} == {102, 107}
@pytest.mark.parametrize(("keyword", "department_id", "staff_account"), [
("exam", 11, "EXAM"), ("fees", 22, "FIN"), ("transcript", 33, "REG"),
("wifi", 44, "IT"), ("feedback", 55, "INST"), ("general", 66, "ADMIN"),
])
def test_submitted_query_is_visible_to_its_department_student_and_admin(workspace, keyword, department_id, staff_account):
client, _, users = workspace
response = client.post("/queries/", headers=headers(users["student"]), json={"message": keyword})
assert response.status_code == 201
query_id = response.json()["id"]
assert response.json()["department_id"] == department_id
for account in ("student", staff_account, "admin"):
response = client.get("/queries/", headers=headers(users[account]))
assert query_id in {item["id"] for item in response.json()}
for account in ("EXAM", "FIN", "REG", "IT", "INST", "ADMIN", "other_student"):
if account != staff_account:
response = client.get("/queries/", headers=headers(users[account]))
assert query_id not in {item["id"] for item in response.json()}
def test_login_uses_saved_account_role_for_query_access(workspace):
client, _, users = workspace
response = client.post("/auth/login", json={
"email": users["FIN"].email, "password": "test-password-123",
"selectedRole": "Admin", "is_admin": True,
})
assert response.status_code == 200
assert response.json()["user"]["is_admin"] is False
assert response.json()["user"]["department_id"] == 22
token_headers = {"Authorization": f"Bearer {response.json()['access_token']}"}
response = client.get("/queries/", headers=token_headers)
assert {item["id"] for item in response.json()} == {102, 107}
assert client.get("/admin/queries", headers=token_headers).status_code == 403