Files
LT_Janitorial_Quality_Control/tests/test_scoped_pages_smoke.py
T

278 lines
11 KiB
Python

"""
Smoke coverage for the pages whose queries were rewritten during the database
optimization pass: the dashboard, the issues list, and the reports overview.
These routes had no request-level tests, so a query rewrite that produced
invalid SQL or a broken scope would only have surfaced in production. Each test
here drives the real route through the test client for a role that exercises a
DIFFERENT branch of the scoping code:
admin -- unscoped branch
inspector -- InspectorAssignment / get_inspector_scope() branch
customer -- CustomerAssignment / get_customer_scope() branch
The assertions are deliberately about behaviour that must hold (status code,
scope isolation), not about markup.
"""
from datetime import timedelta
import pytest
from app import db as _db
from app.models.issue import Issue
from app.models.project import Project, CustomerAssignment
from app.models.inspector_assignment import InspectorAssignment
from app.utils.time_utils import now_eastern
@pytest.fixture
def make_issue(db):
def _make(facility=None, area=None, severity='high', status='open',
hours_ago=1, **kw):
issue = Issue(
facility_id=facility.id if facility else None,
area_id=area.id if area else None,
severity=severity,
description=kw.pop('description', 'test issue'),
status=status,
reported_at=now_eastern() - timedelta(hours=hours_ago),
)
for k, v in kw.items():
setattr(issue, k, v)
db.session.add(issue)
db.session.commit()
return issue
return _make
@pytest.fixture
def project(db):
proj = Project(name='Contract A', active=True)
db.session.add(proj)
db.session.commit()
return proj
@pytest.fixture
def other_project(db):
proj = Project(name='Contract B', active=True)
db.session.add(proj)
db.session.commit()
return proj
# ── Dashboard ────────────────────────────────────────────────────────────────
@pytest.mark.parametrize('role', ['admin', 'director', 'project_manager', 'auditor'])
def test_dashboard_renders_for_staff_roles(client, login, make_user,
make_facility, make_issue,
project, role):
"""The card queries (severity / handler / SLA splits) build and run."""
facility = make_facility(project=project)
make_issue(facility=facility, severity='critical', hours_ago=100)
make_issue(facility=facility, severity='low', status='in_progress')
make_issue(facility=facility, status='resolved',
resolved_at=now_eastern())
login(make_user(role=role))
assert client.get('/').status_code == 200
def test_dashboard_renders_for_scoped_inspector(client, login, make_user, db,
make_facility, make_area,
make_issue, project):
inspector = make_user(role='inspector')
db.session.add(InspectorAssignment(user_id=inspector.id,
project_id=project.id))
db.session.commit()
facility = make_facility(project=project)
area = make_area(facility)
make_issue(area=area, severity='high', hours_ago=50) # reached via area
make_issue(facility=facility, severity='medium') # reached directly
login(inspector)
assert client.get('/').status_code == 200
def test_dashboard_renders_for_inspector_with_no_assignments(client, login,
make_user):
"""Strict scoping: no assignments must render an empty dashboard, not 500."""
login(make_user(role='inspector'))
assert client.get('/').status_code == 200
def test_dashboard_renders_for_customer(client, login, make_user, db,
make_facility, make_issue, project):
customer = make_user(role='customer')
facility = make_facility(project=project)
db.session.add(CustomerAssignment(user_id=customer.id,
project_id=project.id))
db.session.commit()
make_issue(facility=facility)
login(customer)
assert client.get('/').status_code == 200
# ── Issues list ──────────────────────────────────────────────────────────────
def test_issues_list_renders(client, login, make_user, make_facility,
make_issue, project):
facility = make_facility(project=project)
make_issue(facility=facility)
login(make_user(role='admin'))
assert client.get('/issues/').status_code == 200
@pytest.mark.parametrize('query', [
'?facility_id=abc', # non-numeric -- used to raise ValueError -> 500
'?facility_id=',
'?facility_id=999999', # numeric but nonexistent
'?contract_id=abc',
'?issue_id=abc',
'?severity=high&status=open',
'?handler_type=vendor',
'?unassigned=1',
'?sla=breached',
'?date_from=notadate&date_to=alsonot',
])
def test_issues_list_survives_malformed_filters(client, login, make_user,
make_facility, make_issue,
project, query):
"""A hand-edited or stale query string must never 500 the list."""
facility = make_facility(project=project)
make_issue(facility=facility)
login(make_user(role='admin'))
assert client.get('/issues/' + query).status_code == 200
def test_issues_list_scopes_a_customer_to_their_own_facilities(
client, login, make_user, db, make_facility, make_issue,
project, other_project):
"""The scope filter still isolates customers after the query rewrite."""
customer = make_user(role='customer')
mine = make_facility(project=project, name='Mine')
theirs = make_facility(project=other_project, name='Theirs')
db.session.add(CustomerAssignment(user_id=customer.id,
project_id=project.id))
db.session.commit()
make_issue(facility=mine, description='visible to me')
make_issue(facility=theirs, description='other customer only')
login(customer)
body = client.get('/issues/').get_data(as_text=True)
assert 'visible to me' in body
assert 'other customer only' not in body
# ── Issue detail scope gate ──────────────────────────────────────────────────
# issues.view() had two inline scope blocks; they were collapsed into
# _issue_readable_by() so the linked-issues panel could reuse the same rule.
# These pin the behaviour that gate must keep.
def test_issue_view_allows_an_inspector_inside_their_contract(
client, login, make_user, db, make_facility, make_issue, project):
inspector = make_user(role='inspector')
db.session.add(InspectorAssignment(user_id=inspector.id,
project_id=project.id))
db.session.commit()
issue = make_issue(facility=make_facility(project=project))
login(inspector)
assert client.get(f'/issues/{issue.id}').status_code == 200
def test_issue_view_denies_an_inspector_outside_their_contract(
client, login, make_user, db, make_facility, make_issue,
project, other_project):
inspector = make_user(role='inspector')
db.session.add(InspectorAssignment(user_id=inspector.id,
project_id=project.id))
db.session.commit()
issue = make_issue(facility=make_facility(project=other_project),
description='not yours')
login(inspector)
res = client.get(f'/issues/{issue.id}', follow_redirects=True)
assert 'not yours' not in res.get_data(as_text=True)
def test_issue_view_denies_an_inspector_with_no_assignments(
client, login, make_user, make_facility, make_issue, project):
"""Strict scoping: no assignments means no access, not full access."""
issue = make_issue(facility=make_facility(project=project),
description='strictly scoped')
login(make_user(role='inspector'))
res = client.get(f'/issues/{issue.id}', follow_redirects=True)
assert 'strictly scoped' not in res.get_data(as_text=True)
def test_issue_view_denies_a_customer_outside_their_contract(
client, login, make_user, db, make_facility, make_issue,
project, other_project):
customer = make_user(role='customer')
db.session.add(CustomerAssignment(user_id=customer.id,
project_id=project.id))
db.session.commit()
issue = make_issue(facility=make_facility(project=other_project),
description='other customer only')
login(customer)
res = client.get(f'/issues/{issue.id}', follow_redirects=True)
assert 'other customer only' not in res.get_data(as_text=True)
def test_issue_view_allows_a_customer_inside_their_contract(
client, login, make_user, db, make_facility, make_issue, project):
customer = make_user(role='customer')
db.session.add(CustomerAssignment(user_id=customer.id,
project_id=project.id))
db.session.commit()
issue = make_issue(facility=make_facility(project=project),
description='mine to read')
login(customer)
res = client.get(f'/issues/{issue.id}')
assert res.status_code == 200
assert 'mine to read' in res.get_data(as_text=True)
# ── Reports overview ─────────────────────────────────────────────────────────
def test_reports_overview_renders_for_admin(client, login, make_user,
make_facility, make_template,
make_inspection, project):
admin = make_user(role='admin')
inspector = make_user(role='inspector')
facility = make_facility(project=project)
make_inspection(facility, inspector, make_template())
login(admin)
assert client.get('/reports/').status_code == 200
def test_reports_overview_inspector_scope_uses_subquery(
client, login, make_user, db, make_facility, make_template,
make_inspection, make_issue, project):
"""_scope_issue() now filters via a subquery instead of an id list."""
inspector = make_user(role='inspector')
db.session.add(InspectorAssignment(user_id=inspector.id,
project_id=project.id))
db.session.commit()
facility = make_facility(project=project)
insp = make_inspection(facility, inspector, make_template())
make_issue(facility=facility, inspection_id=insp.id)
login(inspector)
assert client.get('/reports/').status_code == 200
def test_reports_overview_inspector_with_no_inspections(client, login,
make_user):
"""IN (empty subquery) must match nothing rather than error."""
login(make_user(role='inspector'))
assert client.get('/reports/').status_code == 200