read permissions on every GET

This commit is contained in:
2026-08-27 14:26:17 -04:00
parent 4da6c6aa36
commit 0c1fbfcc1e
51 changed files with 521 additions and 224 deletions

View File

@@ -20,9 +20,20 @@ from shopdb import create_app
from shopdb.extensions import db as _db
from shopdb.plugins import plugin_manager
from plugins.measuringtools.models import (
derive_status, DUESOON_WINDOW_DAYS, STATUS_COLORS)
@pytest.fixture(autouse=True)
def _signed_in(client, auth_headers):
"""Reading needs a permission now, and these tests are about what the
payload says rather than about who may see it. Signing the client in once
beats threading headers through every helper in the module."""
client.environ_base['HTTP_AUTHORIZATION'] = auth_headers['Authorization']
yield
client.environ_base.pop('HTTP_AUTHORIZATION', None)
# =============================================================================
# Pure unit tests: derived status (no app needed)
# =============================================================================
@@ -48,7 +59,7 @@ def test_derive_status_duesoon_on_boundary():
assert derive_status(date.today() + timedelta(days=DUESOON_WINDOW_DAYS)) == 'duesoon'
def test_derive_status_duesoon_today():
def test_derive_status_duesoon_today(auth_headers):
"""Due today is not yet overdue - it falls in the due-soon window."""
assert derive_status(date.today()) == 'duesoon'
@@ -120,14 +131,17 @@ def _status_id(client):
# -- Type CRUD + in-use guard -------------------------------------------------
def test_starter_types_seeded(client):
response = client.get('/api/measuringtools/types')
def test_starter_types_seeded(client, auth_headers):
response = client.get('/api/measuringtools/types', headers=auth_headers)
assert response.status_code == 200
names = {t['name'] for t in response.get_json()['data']}
assert {'Caliper', 'Micrometer', 'Thread Gage', 'Other'} <= names
def test_type_create_requires_auth(client):
# This module signs the client in for every test; this one is about what
# happens when nobody is signed in, so it takes that back.
client.environ_base.pop('HTTP_AUTHORIZATION', None)
response = client.post('/api/measuringtools/types', json={'name': 'Nope'})
assert response.status_code == 401
@@ -197,7 +211,7 @@ def test_tool_create_and_get_merged(client, auth_headers):
assert ext['calibrationstatus'] == 'current'
tool_id = ext['measuringtoolid']
fetched = client.get(f'/api/measuringtools/{tool_id}')
fetched = client.get(f'/api/measuringtools/{tool_id}', headers=auth_headers)
assert fetched.status_code == 200
assert fetched.get_json()['data']['measuringtool']['measuringtoolid'] == tool_id
@@ -263,7 +277,7 @@ def test_list_filter_by_calibrationstatus(client, auth_headers):
'assetnumber': 'MT-OVERDUE-1', 'statusid': _status_id(client),
'nextcalibrationdate': str(date.today() - timedelta(days=10)),
})
response = client.get('/api/measuringtools?calibrationstatus=overdue')
response = client.get('/api/measuringtools?calibrationstatus=overdue', headers=auth_headers)
assert response.status_code == 200
rows = response.get_json()['data']
assert rows, 'expected at least one overdue tool'
@@ -341,7 +355,7 @@ def test_search_excludes_tools_when_domain_disabled(mt_app, client, auth_headers
# -- Report shape -------------------------------------------------------------
def test_calibration_report_shape(client, auth_headers):
response = client.get('/api/measuringtools/report/calibration')
response = client.get('/api/measuringtools/report/calibration', headers=auth_headers)
assert response.status_code == 200
data = response.get_json()['data']
assert set(data['counts']) == {'overdue', 'duesoon', 'current', 'unknown'}
@@ -382,7 +396,7 @@ def test_asset_todict_carries_measuringtool_typedata_and_pluginid(mt_app, client
def test_map_lists_measuringtool_subtypes(client, auth_headers):
"""The map filter dropdown carries a Measuring Tool subtype list with color."""
response = client.get('/api/assets/map')
response = client.get('/api/assets/map', headers=auth_headers)
assert response.status_code == 200, response.get_json()
subtypes = response.get_json()['data']['filters']['subtypes']
assert 'Measuring Tool' in subtypes
@@ -404,7 +418,7 @@ def test_map_honors_measuringtool_subtype_filter(client, auth_headers):
'measuringtooltypeid': micrometer_id, 'mapx': 30, 'mapy': 40})
response = client.get(
f'/api/assets/map?assettype=measuring_tool&subtype={caliper_id}')
f'/api/assets/map?assettype=measuring_tool&subtype={caliper_id}', headers=auth_headers)
assert response.status_code == 200, response.get_json()
numbers = {a['assetnumber'] for a in response.get_json()['data']['assets']}
assert 'MT-MAP-CAL' in numbers
@@ -416,7 +430,7 @@ def test_map_item_carries_measuringtool_typedata(client, auth_headers):
client.post('/api/measuringtools', headers=auth_headers, json={
'assetnumber': 'MT-MAP-TD', 'statusid': _status_id(client),
'measuringtooltypeid': _caliper_id(client), 'mapx': 55, 'mapy': 66})
response = client.get('/api/assets/map?assettype=measuring_tool')
response = client.get('/api/assets/map?assettype=measuring_tool', headers=auth_headers)
item = next(a for a in response.get_json()['data']['assets']
if a['assetnumber'] == 'MT-MAP-TD')
assert item['typedata']['measuringtooltypename'] == 'Caliper'
@@ -444,7 +458,7 @@ def test_map_overlay_shape_and_derivation(client, auth_headers):
'nextcalibrationdate': str(date.today() - timedelta(days=3))}) # overdue
assetid = created.get_json()['data']['assetid']
response = client.get('/api/measuringtools/map-overlay')
response = client.get('/api/measuringtools/map-overlay', headers=auth_headers)
assert response.status_code == 200, response.get_json()
rows = response.get_json()['data']
# ADR-010 overlay contract: [{assetid, color, label}], only due/overdue tools.