132 lines
4.8 KiB
Python
132 lines
4.8 KiB
Python
"""
|
|
tests/test_followup_ownership.py
|
|
---------------------------------
|
|
phase56 — a follow-up has exactly ONE owner, and every surface must agree.
|
|
|
|
`Inspection.follow_up_owner` (the loaded-row property) and
|
|
`Inspection.follow_up_owned_by()` (the SQL predicate) are two expressions of
|
|
the same rule: the assignee when one is set, the inspection's own inspector
|
|
otherwise. They are declared next to each other because they must not drift.
|
|
|
|
What these guard
|
|
----------------
|
|
The predicate's two arms are mutually exclusive ON PURPOSE. Drop the
|
|
`is_(None)` from the second arm and the original inspector keeps matching a
|
|
follow-up that was handed to somebody else — two people turn up to do the same
|
|
re-inspection, and nothing errors.
|
|
|
|
The three query surfaces (mobile list filter, web dashboard card, iPad stats
|
|
KPI) each used to express ownership by hand, and three of them tested
|
|
AUTHORSHIP: an assignee saw the work in their list while both dashboards read
|
|
0. These tests pin the predicate itself; the surfaces now call it rather than
|
|
re-deriving it.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture
|
|
def client(app):
|
|
with app.app_context():
|
|
from app import db
|
|
db.drop_all()
|
|
db.create_all()
|
|
yield app.test_client()
|
|
db.session.remove()
|
|
|
|
|
|
def _user(username, role='inspector'):
|
|
from app import db
|
|
from app.models.user import User
|
|
u = User(username=username, full_name=username.title(), role=role,
|
|
email=f'{username}@example.com', active=True, password_set=True)
|
|
u.set_password('pw-correct1')
|
|
db.session.add(u)
|
|
db.session.commit()
|
|
return u
|
|
|
|
|
|
def _inspection(inspector, assignee=None, follow_up=True):
|
|
from app import db
|
|
from app.models.facility import Facility
|
|
from app.models.inspection import Inspection, InspectionTemplate
|
|
|
|
fac = Facility.query.first()
|
|
if fac is None:
|
|
fac = Facility(name='Main Office', active=True)
|
|
db.session.add(fac)
|
|
db.session.commit()
|
|
tmpl = InspectionTemplate.query.first()
|
|
if tmpl is None:
|
|
tmpl = InspectionTemplate(name='Restroom Check', active=True, form_schema=[])
|
|
db.session.add(tmpl)
|
|
db.session.commit()
|
|
|
|
insp = Inspection(template_id=tmpl.id, facility_id=fac.id,
|
|
inspector_id=inspector.id, status='completed',
|
|
follow_up_required=follow_up,
|
|
follow_up_assigned_to=(assignee.id if assignee else None))
|
|
db.session.add(insp)
|
|
db.session.commit()
|
|
return insp
|
|
|
|
|
|
def _owned_ids(user):
|
|
from app.models.inspection import Inspection
|
|
return sorted(i.id for i in Inspection.query
|
|
.filter(Inspection.follow_up_owned_by(user.id)).all())
|
|
|
|
|
|
# ── the property ─────────────────────────────────────────────────────────────
|
|
|
|
def test_owner_is_the_inspector_when_unassigned(client):
|
|
ivy = _user('ivy')
|
|
insp = _inspection(ivy)
|
|
assert insp.follow_up_owner.id == ivy.id
|
|
|
|
|
|
def test_owner_is_the_assignee_when_assigned(client):
|
|
ivy, sam = _user('ivy'), _user('sam')
|
|
insp = _inspection(ivy, assignee=sam)
|
|
assert insp.follow_up_owner.id == sam.id
|
|
|
|
|
|
# ── the SQL predicate ────────────────────────────────────────────────────────
|
|
|
|
def test_predicate_matches_unassigned_own_work(client):
|
|
ivy = _user('ivy')
|
|
insp = _inspection(ivy)
|
|
assert _owned_ids(ivy) == [insp.id]
|
|
|
|
|
|
def test_predicate_matches_work_handed_to_me(client):
|
|
ivy, sam = _user('ivy'), _user('sam')
|
|
insp = _inspection(ivy, assignee=sam)
|
|
assert _owned_ids(sam) == [insp.id]
|
|
|
|
|
|
def test_predicate_releases_the_original_inspector_once_assigned(client):
|
|
"""The `is_(None)` on the second arm. Without it Ivy still matches, and two
|
|
people turn up to do the same re-inspection."""
|
|
ivy, sam = _user('ivy'), _user('sam')
|
|
_inspection(ivy, assignee=sam)
|
|
assert _owned_ids(ivy) == []
|
|
|
|
|
|
def test_every_row_has_exactly_one_owner(client):
|
|
"""The property and the predicate must partition the rows, not overlap."""
|
|
from app.models.inspection import Inspection
|
|
|
|
ivy, sam, zoe = _user('ivy'), _user('sam'), _user('zoe')
|
|
_inspection(ivy) # unassigned, Ivy's
|
|
_inspection(ivy, assignee=sam) # Ivy's work, Sam's follow-up
|
|
_inspection(sam, assignee=sam) # Sam's work, explicitly Sam's
|
|
_inspection(zoe) # unassigned, Zoe's
|
|
|
|
everyone = [ivy, sam, zoe]
|
|
for insp in Inspection.query.all():
|
|
owners = [u.id for u in everyone if insp.id in _owned_ids(u)]
|
|
assert owners == [insp.follow_up_owner.id], (
|
|
f'inspection {insp.id}: predicate says {owners}, '
|
|
f'property says {insp.follow_up_owner.id}')
|