Remediate the 2026-09-03 feature-test findings
This commit is contained in:
@@ -488,3 +488,159 @@ def test_asset_presentation_route_token():
|
||||
entries = MeasuringToolsPlugin().get_asset_presentation()
|
||||
entry = next(e for e in entries if e['assettype'] == 'measuring_tool')
|
||||
assert entry['route'] == '/measuringtools/by-asset/{assetid}'
|
||||
|
||||
|
||||
# --- a date that is not a date -----------------------------------------------
|
||||
#
|
||||
# _parse_date used to swallow the ValueError and return None, so a typo in the
|
||||
# calibration date answered 200 "Measuring tool updated", stored NULL, flipped
|
||||
# the tool to 'unknown', and dropped it out of the calibration report and the
|
||||
# map overlay. Clearing a date and mistyping one are different intents and now
|
||||
# get different answers.
|
||||
|
||||
def test_a_mistyped_calibration_date_is_refused_not_stored_as_null(
|
||||
client, auth_headers):
|
||||
created = client.post('/api/measuringtools', headers=auth_headers, json={
|
||||
'assetnumber': 'MT-DATE-1',
|
||||
'name': 'Date guard caliper',
|
||||
'statusid': _status_id(client),
|
||||
'nextcalibrationdate': '2027-01-01',
|
||||
})
|
||||
assert created.status_code == 201, created.get_json()
|
||||
toolid = created.get_json()['data']['measuringtool']['measuringtoolid']
|
||||
|
||||
refused = client.put(f'/api/measuringtools/{toolid}', headers=auth_headers,
|
||||
json={'nextcalibrationdate': 'not-a-date'})
|
||||
assert refused.status_code == 400, refused.get_json()
|
||||
assert 'nextcalibrationdate' in \
|
||||
refused.get_json()['data']['error']['message']
|
||||
|
||||
# The stored date is untouched - the whole point of refusing.
|
||||
after = client.get(f'/api/measuringtools/{toolid}',
|
||||
headers=auth_headers).get_json()['data']
|
||||
assert after['measuringtool']['nextcalibrationdate'] == '2027-01-01'
|
||||
assert after['measuringtool']['calibrationstatus'] != 'unknown'
|
||||
|
||||
|
||||
def test_an_explicitly_empty_calibration_date_still_clears_it(client,
|
||||
auth_headers):
|
||||
"""Clearing is a real intent and must keep working, or the fix above just
|
||||
trades one broken behaviour for another."""
|
||||
created = client.post('/api/measuringtools', headers=auth_headers, json={
|
||||
'assetnumber': 'MT-DATE-2',
|
||||
'name': 'Clearable caliper',
|
||||
'statusid': _status_id(client),
|
||||
'nextcalibrationdate': '2027-01-01',
|
||||
})
|
||||
toolid = created.get_json()['data']['measuringtool']['measuringtoolid']
|
||||
|
||||
cleared = client.put(f'/api/measuringtools/{toolid}', headers=auth_headers,
|
||||
json={'nextcalibrationdate': ''})
|
||||
assert cleared.status_code == 200, cleared.get_json()
|
||||
after = client.get(f'/api/measuringtools/{toolid}',
|
||||
headers=auth_headers).get_json()['data']
|
||||
assert after['measuringtool']['nextcalibrationdate'] is None
|
||||
|
||||
|
||||
def test_a_mistyped_date_is_refused_at_create_too(client, auth_headers):
|
||||
refused = client.post('/api/measuringtools', headers=auth_headers, json={
|
||||
'assetnumber': 'MT-DATE-3',
|
||||
'name': 'Never created',
|
||||
'statusid': _status_id(client),
|
||||
'lastcalibrationdate': '01/02/2026',
|
||||
})
|
||||
assert refused.status_code == 400, refused.get_json()
|
||||
listed = client.get('/api/measuringtools?search=MT-DATE-3',
|
||||
headers=auth_headers).get_json()['data']
|
||||
assert listed == [], 'nothing may be created by a refused request'
|
||||
|
||||
|
||||
# --- an audited field holding a date must not kill the request ---------------
|
||||
#
|
||||
# PUT /api/machines/<id> returned 500 and rolled the whole edit back for any
|
||||
# machine with a maintenance date: the route builds
|
||||
# {'old': getattr(row, field)} straight off the model, and json.dumps raises on
|
||||
# a date. A rename that never touched the date failed too. The audit log
|
||||
# refusing to record a change must not be what stops the change.
|
||||
|
||||
def test_an_edit_is_saved_even_when_an_audited_field_holds_a_date(
|
||||
client, auth_headers):
|
||||
created = client.post('/api/measuringtools', headers=auth_headers, json={
|
||||
'assetnumber': 'MT-AUDIT-1',
|
||||
'name': 'Before rename',
|
||||
'statusid': _status_id(client),
|
||||
'lastcalibrationdate': '2026-01-15',
|
||||
'nextcalibrationdate': '2027-01-15',
|
||||
})
|
||||
assert created.status_code == 201, created.get_json()
|
||||
toolid = created.get_json()['data']['measuringtool']['measuringtoolid']
|
||||
|
||||
# A rename that does not touch the dates. The date is only in the CHANGES
|
||||
# dict because the interval moves with it.
|
||||
renamed = client.put(f'/api/measuringtools/{toolid}', headers=auth_headers,
|
||||
json={'name': 'After rename',
|
||||
'nextcalibrationdate': '2028-01-15'})
|
||||
assert renamed.status_code == 200, renamed.get_json()
|
||||
|
||||
after = client.get(f'/api/measuringtools/{toolid}',
|
||||
headers=auth_headers).get_json()['data']
|
||||
assert after['name'] == 'After rename'
|
||||
assert after['measuringtool']['nextcalibrationdate'] == '2028-01-15'
|
||||
|
||||
|
||||
# --- a derived filter must page over the MATCHES ----------------------------
|
||||
#
|
||||
# calibrationstatus was applied to the rows of the current page, so a request
|
||||
# for 'overdue' returned whatever matched on that page while the total counted
|
||||
# every tool. With one overdue tool in sixteen: page 1 empty, total 16, eight
|
||||
# pages, and the row itself on page 3. The filter hid matching rows and
|
||||
# miscounted the rest.
|
||||
|
||||
def test_a_calibration_filter_pages_over_the_matches_not_the_page(
|
||||
client, auth_headers):
|
||||
from datetime import date, timedelta
|
||||
|
||||
overdue = (date.today() - timedelta(days=5)).isoformat()
|
||||
current = (date.today() + timedelta(days=400)).isoformat()
|
||||
|
||||
# One overdue tool, well past the first page of a small page size.
|
||||
for index in range(6):
|
||||
created = client.post('/api/measuringtools', headers=auth_headers, json={
|
||||
'assetnumber': 'MT-PAGE-%02d' % index,
|
||||
'name': 'Paged tool %d' % index,
|
||||
'statusid': _status_id(client),
|
||||
'nextcalibrationdate': overdue if index == 5 else current,
|
||||
})
|
||||
assert created.status_code == 201, created.get_json()
|
||||
|
||||
# Scoped with search as well, so earlier tests in this module cannot
|
||||
# change the expected set - the point is paging over matches, not how many
|
||||
# overdue tools the whole fixture happens to hold.
|
||||
response = client.get(
|
||||
'/api/measuringtools?calibrationstatus=overdue&search=MT-PAGE&perpage=2',
|
||||
headers=auth_headers)
|
||||
assert response.status_code == 200, response.get_json()
|
||||
body = response.get_json()
|
||||
rows = body['data']
|
||||
pagination = body['meta']['pagination']
|
||||
|
||||
assert [row['assetnumber'] for row in rows] == ['MT-PAGE-05'], \
|
||||
'the overdue tool must be on page 1 of a filtered list'
|
||||
assert pagination['total'] == 1, \
|
||||
'the total counts the MATCHES, not every tool'
|
||||
assert pagination['totalpages'] == 1
|
||||
|
||||
|
||||
def test_an_unfiltered_list_still_pages_normally(client, auth_headers):
|
||||
for index in range(3):
|
||||
client.post('/api/measuringtools', headers=auth_headers, json={
|
||||
'assetnumber': 'MT-PLAIN-%02d' % index,
|
||||
'name': 'Plain tool %d' % index,
|
||||
'statusid': _status_id(client),
|
||||
})
|
||||
|
||||
response = client.get('/api/measuringtools?perpage=2', headers=auth_headers)
|
||||
assert response.status_code == 200
|
||||
pagination = response.get_json()['meta']['pagination']
|
||||
assert pagination['perpage'] == 2
|
||||
assert pagination['total'] >= 3
|
||||
|
||||
Reference in New Issue
Block a user