374 lines
12 KiB
Python
374 lines
12 KiB
Python
import pytest
|
|
|
|
from app.crud import role as role_crud
|
|
from app.models.attachment_point import AttachmentPoint
|
|
from app.models.attachment_point_name import AttachmentPointName
|
|
from app.models.augmented_part import AugmentedPart, AugmentedPartNote
|
|
from app.models.fit import Fit, FitLabel, FitNote
|
|
from app.models.iso_thread import IsoThread
|
|
from app.models.part_type import PartType
|
|
from app.models.scope_info import ScopeInfo
|
|
|
|
|
|
async def _seed_part_with_fit(db):
|
|
apn = AttachmentPointName(name="Objective")
|
|
scope_t = PartType(name="Scope")
|
|
cover_t = PartType(name="Flip Cover")
|
|
db.add_all([apn, scope_t, cover_t])
|
|
await db.flush()
|
|
|
|
scope = AugmentedPart(
|
|
description="Alpha 3-18x50",
|
|
sku="ALP-318",
|
|
colour_id=1,
|
|
manufacturer_id=1,
|
|
part_type_id=scope_t.id,
|
|
)
|
|
cover = AugmentedPart(
|
|
description="Flip cover 50mm",
|
|
colour_id=1,
|
|
manufacturer_id=1,
|
|
part_type_id=cover_t.id,
|
|
)
|
|
db.add_all([scope, cover])
|
|
await db.flush()
|
|
|
|
db.add(
|
|
ScopeInfo(
|
|
augmented_part_id=scope.id,
|
|
magnification_min=3,
|
|
magnification_max=18,
|
|
)
|
|
)
|
|
ap_s = AttachmentPoint(augmented_part_id=scope.id, attachment_point_name_id=apn.id)
|
|
ap_c = AttachmentPoint(augmented_part_id=cover.id, attachment_point_name_id=apn.id)
|
|
db.add_all([ap_s, ap_c])
|
|
await db.flush()
|
|
|
|
db.add(
|
|
Fit(
|
|
attachment_point_a_id=ap_s.id,
|
|
attachment_point_b_id=ap_c.id,
|
|
augmented_part_a_id=scope.id,
|
|
augmented_part_b_id=cover.id,
|
|
label=FitLabel.POSSIBLE,
|
|
)
|
|
)
|
|
await db.commit()
|
|
return {"scope_id": scope.id, "cover_id": cover.id}
|
|
|
|
|
|
async def _web_login(client, email, password="secret123"):
|
|
resp = await client.post("/login", data={"email": email, "password": password})
|
|
assert resp.status_code == 303
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_detail_shows_attributes_and_fits_readonly(
|
|
client, seeded, make_user, db_sessionmaker
|
|
):
|
|
async with db_sessionmaker() as db:
|
|
ids = await _seed_part_with_fit(db)
|
|
# The default "user" role has augmented_parts:read + fits:read, NOT fits:write.
|
|
await make_user("reader@example.com", role_ids=[seeded["roles"]["user"].id])
|
|
await _web_login(client, "reader@example.com")
|
|
|
|
resp = await client.get(f"/admin/augmented-parts/{ids['scope_id']}")
|
|
assert resp.status_code == 200
|
|
body = resp.text
|
|
# Attributes
|
|
assert "Alpha 3-18x50" in body
|
|
assert "ALP-318" in body
|
|
assert "Objective" in body # attachment point
|
|
# Fits are shown (the other part in the fit)
|
|
assert "Flip cover 50mm" in body
|
|
# ...but write controls are hidden for a read-only user
|
|
assert "Add fit" not in body
|
|
assert "Mark Exact" not in body
|
|
assert "Recompute possible fits" not in body
|
|
assert "Add note" not in body
|
|
# ...and attachment-point add/edit/delete controls are hidden too
|
|
assert 'name="attachment_point_name_id"' not in body
|
|
assert "Delete this attachment point?" not in body
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_detail_write_controls_for_attachment_point_editor(
|
|
client, seeded, make_user, db_sessionmaker
|
|
):
|
|
async with db_sessionmaker() as db:
|
|
ids = await _seed_part_with_fit(db)
|
|
perms = seeded["perms"]
|
|
editor = await role_crud.create(
|
|
db,
|
|
name="ap-editor",
|
|
permission_ids=[
|
|
perms["augmented_parts:read"].id,
|
|
perms["attachment_points:read"].id,
|
|
perms["attachment_points:write"].id,
|
|
],
|
|
)
|
|
editor_id = editor.id
|
|
await make_user("apeditor@example.com", role_ids=[editor_id])
|
|
await _web_login(client, "apeditor@example.com")
|
|
|
|
resp = await client.get(f"/admin/augmented-parts/{ids['scope_id']}")
|
|
assert resp.status_code == 200
|
|
body = resp.text
|
|
assert 'name="attachment_point_name_id"' in body
|
|
assert "Delete this attachment point?" in body
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_detail_write_controls_for_fit_editor(
|
|
client, seeded, make_user, db_sessionmaker
|
|
):
|
|
async with db_sessionmaker() as db:
|
|
ids = await _seed_part_with_fit(db)
|
|
perms = seeded["perms"]
|
|
editor = await role_crud.create(
|
|
db,
|
|
name="fit-editor",
|
|
permission_ids=[
|
|
perms["augmented_parts:read"].id,
|
|
perms["fits:read"].id,
|
|
perms["fits:write"].id,
|
|
],
|
|
)
|
|
editor_id = editor.id
|
|
await make_user("editor@example.com", role_ids=[editor_id])
|
|
await _web_login(client, "editor@example.com")
|
|
|
|
resp = await client.get(f"/admin/augmented-parts/{ids['scope_id']}")
|
|
assert resp.status_code == 200
|
|
body = resp.text
|
|
assert "Add fit" in body
|
|
assert "Mark Exact" in body
|
|
assert "Recompute possible fits" in body
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_fit_auto_creates_matching_attachment_point(
|
|
client, seeded, make_user, db_sessionmaker
|
|
):
|
|
async with db_sessionmaker() as db:
|
|
apn = AttachmentPointName(name="Objective")
|
|
scope_t = PartType(name="Scope")
|
|
cover_t = PartType(name="Flip Cover")
|
|
db.add_all([apn, scope_t, cover_t])
|
|
await db.flush()
|
|
|
|
thread = IsoThread(diameter_mm=42.0, pitch_mm=0.75)
|
|
db.add(thread)
|
|
await db.flush()
|
|
|
|
scope = AugmentedPart(
|
|
description="Alpha 3-18x50",
|
|
sku="ALP-318",
|
|
colour_id=1,
|
|
manufacturer_id=1,
|
|
part_type_id=scope_t.id,
|
|
)
|
|
cover = AugmentedPart(
|
|
description="Flip cover 50mm",
|
|
colour_id=1,
|
|
manufacturer_id=1,
|
|
part_type_id=cover_t.id,
|
|
)
|
|
db.add_all([scope, cover])
|
|
await db.flush()
|
|
|
|
own_ap = AttachmentPoint(
|
|
augmented_part_id=scope.id,
|
|
attachment_point_name_id=apn.id,
|
|
iso_thread_id=thread.id,
|
|
)
|
|
db.add(own_ap)
|
|
await db.commit()
|
|
|
|
scope_id, cover_id, own_ap_id = scope.id, cover.id, own_ap.id
|
|
|
|
perms = seeded["perms"]
|
|
editor = await role_crud.create(
|
|
db,
|
|
name="fit-editor2",
|
|
permission_ids=[
|
|
perms["augmented_parts:read"].id,
|
|
perms["fits:read"].id,
|
|
perms["fits:write"].id,
|
|
],
|
|
)
|
|
editor_id = editor.id
|
|
await make_user("editor2@example.com", role_ids=[editor_id])
|
|
await _web_login(client, "editor2@example.com")
|
|
|
|
resp = await client.post(
|
|
f"/admin/augmented-parts/{scope_id}/fits",
|
|
data={
|
|
"own_attachment_point_id": own_ap_id,
|
|
"other_augmented_part_id": cover_id,
|
|
"other_attachment_point_id": "",
|
|
"label": "possible",
|
|
},
|
|
)
|
|
assert resp.status_code == 200
|
|
body = resp.text
|
|
assert "already exists" not in body
|
|
assert "not found" not in body
|
|
|
|
async with db_sessionmaker() as db:
|
|
cover_points = [
|
|
ap
|
|
for ap in (await db.execute(AttachmentPoint.__table__.select())).all()
|
|
if ap.augmented_part_id == cover_id
|
|
]
|
|
assert len(cover_points) == 1
|
|
assert cover_points[0].iso_thread_id == thread.id
|
|
assert cover_points[0].attachment_point_name_id == apn.id
|
|
|
|
fits = (await db.execute(Fit.__table__.select())).all()
|
|
assert len(fits) == 1
|
|
assert fits[0].label == FitLabel.POSSIBLE
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_fit_with_note_creates_fit_note(
|
|
client, seeded, make_user, db_sessionmaker
|
|
):
|
|
async with db_sessionmaker() as db:
|
|
apn = AttachmentPointName(name="Objective")
|
|
scope_t = PartType(name="Scope")
|
|
cover_t = PartType(name="Flip Cover")
|
|
db.add_all([apn, scope_t, cover_t])
|
|
await db.flush()
|
|
|
|
scope = AugmentedPart(
|
|
description="Alpha 3-18x50",
|
|
sku="ALP-318",
|
|
colour_id=1,
|
|
manufacturer_id=1,
|
|
part_type_id=scope_t.id,
|
|
)
|
|
cover = AugmentedPart(
|
|
description="Flip cover 50mm",
|
|
colour_id=1,
|
|
manufacturer_id=1,
|
|
part_type_id=cover_t.id,
|
|
)
|
|
db.add_all([scope, cover])
|
|
await db.flush()
|
|
|
|
own_ap = AttachmentPoint(
|
|
augmented_part_id=scope.id, attachment_point_name_id=apn.id
|
|
)
|
|
other_ap = AttachmentPoint(
|
|
augmented_part_id=cover.id, attachment_point_name_id=apn.id
|
|
)
|
|
db.add_all([own_ap, other_ap])
|
|
await db.commit()
|
|
|
|
scope_id, own_ap_id, other_ap_id = scope.id, own_ap.id, other_ap.id
|
|
|
|
perms = seeded["perms"]
|
|
editor = await role_crud.create(
|
|
db,
|
|
name="fit-editor3",
|
|
permission_ids=[
|
|
perms["augmented_parts:read"].id,
|
|
perms["fits:read"].id,
|
|
perms["fits:write"].id,
|
|
],
|
|
)
|
|
editor_id = editor.id
|
|
await make_user("editor3@example.com", role_ids=[editor_id])
|
|
await _web_login(client, "editor3@example.com")
|
|
|
|
resp = await client.post(
|
|
f"/admin/augmented-parts/{scope_id}/fits",
|
|
data={
|
|
"own_attachment_point_id": own_ap_id,
|
|
"other_attachment_point_id": other_ap_id,
|
|
"label": "exact",
|
|
"note": "Confirmed fit on the bench.",
|
|
},
|
|
)
|
|
assert resp.status_code == 200
|
|
body = resp.text
|
|
assert "Confirmed fit on the bench." in body
|
|
|
|
async with db_sessionmaker() as db:
|
|
fits = (await db.execute(Fit.__table__.select())).all()
|
|
assert len(fits) == 1
|
|
notes = (await db.execute(FitNote.__table__.select())).all()
|
|
assert len(notes) == 1
|
|
assert notes[0].fit_id == fits[0].id
|
|
assert notes[0].body == "Confirmed fit on the bench."
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_augmented_part_note(client, seeded, make_user, db_sessionmaker):
|
|
async with db_sessionmaker() as db:
|
|
ids = await _seed_part_with_fit(db)
|
|
perms = seeded["perms"]
|
|
editor = await role_crud.create(
|
|
db,
|
|
name="ap-note-editor",
|
|
permission_ids=[
|
|
perms["augmented_parts:read"].id,
|
|
perms["augmented_parts:write"].id,
|
|
],
|
|
)
|
|
editor_id = editor.id
|
|
await make_user("noteeditor@example.com", role_ids=[editor_id])
|
|
await _web_login(client, "noteeditor@example.com")
|
|
|
|
resp = await client.get(f"/admin/augmented-parts/{ids['scope_id']}")
|
|
assert resp.status_code == 200
|
|
assert "Add note" in resp.text
|
|
|
|
resp = await client.post(
|
|
f"/admin/augmented-parts/{ids['scope_id']}/notes",
|
|
data={"body": "Verified thread depth on the bench."},
|
|
)
|
|
assert resp.status_code == 200
|
|
assert "Verified thread depth on the bench." in resp.text
|
|
|
|
async with db_sessionmaker() as db:
|
|
notes = (await db.execute(AugmentedPartNote.__table__.select())).all()
|
|
assert len(notes) == 1
|
|
assert notes[0].augmented_part_id == ids["scope_id"]
|
|
assert notes[0].body == "Verified thread depth on the bench."
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_detail_404_for_missing_part(client, seeded, make_user):
|
|
await make_user("reader2@example.com", role_ids=[seeded["roles"]["user"].id])
|
|
await _web_login(client, "reader2@example.com")
|
|
resp = await client.get("/admin/augmented-parts/99999")
|
|
assert resp.status_code == 404
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_detail_requires_read_permission(
|
|
client, seeded, make_user, db_sessionmaker
|
|
):
|
|
async with db_sessionmaker() as db:
|
|
ids = await _seed_part_with_fit(db)
|
|
await make_user("plain@example.com") # no roles, not superuser
|
|
await _web_login(client, "plain@example.com")
|
|
resp = await client.get(f"/admin/augmented-parts/{ids['scope_id']}")
|
|
assert resp.status_code == 403
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_augmented_parts_list_links_to_detail(
|
|
client, seeded, make_user, db_sessionmaker
|
|
):
|
|
async with db_sessionmaker() as db:
|
|
ids = await _seed_part_with_fit(db)
|
|
await make_user("reader3@example.com", role_ids=[seeded["roles"]["user"].id])
|
|
await _web_login(client, "reader3@example.com")
|
|
resp = await client.get("/admin/augmented-parts")
|
|
assert resp.status_code == 200
|
|
assert f'href="/admin/augmented-parts/{ids["scope_id"]}"' in resp.text
|