Reciprocal controls links, dead chip links, and a resumable legacy import
The machine page now shows the PC that controls it. The endpoint was correct all along and returned both directions; AssetTriageBand discarded every peer whose type was not 'machine' and was mounted only on the PC page. The counterpart type, label and relationship types are now props. Fixed while adopting it more widely: every band chip linked to /assets/<id>, and there is no such route. The resolver that AssetRelationships.vue kept privately is now frontend/src/utils/assetRoute.js, used by both - and its type lookup is case-insensitive, because seed data capitalises the type names while this site's database does not. The legacy import is now safe to resume. The 409-resolve asked for per_page=10000 against a server that clamps to 100, so it silently searched the first 100 rows and dropped links past that on every re-run. The crosswalk is now bound to the database it describes, so one carried across a rebuild is refused rather than attaching rows to the wrong assets. Source database names are resolved from the environment, so the loader can be aimed without editing it. Full suite 2178 passed, frontend 280 passed.
This commit is contained in:
@@ -104,3 +104,50 @@ def test_relationships_row_carries_isdirectional(client, db, auth_headers):
|
||||
ctrl_rows = [r for r in data['outgoing']
|
||||
if r['relationshiptypename'] == 'controls']
|
||||
assert ctrl_rows and all(r['isdirectional'] is True for r in ctrl_rows)
|
||||
|
||||
def test_the_controlled_asset_sees_the_link_from_its_own_page(client, db, auth_headers):
|
||||
"""The target of a directional edge must receive it in `incoming`.
|
||||
|
||||
A PC controls a machine, and the row is stored once, source=PC. The PC's
|
||||
page reads it from `outgoing`; the machine's page can only ever see it in
|
||||
`incoming`. Nothing asserted that second half, and a UI was shipped that
|
||||
rendered the link on the PC page and showed the machine page nothing - the
|
||||
reciprocal-links defect. The endpoint was correct all along, which is
|
||||
exactly why this assertion is worth having: it pins the contract the
|
||||
frontend depends on, so the next such bug cannot be blamed on the API.
|
||||
"""
|
||||
atype_machine = AssetType(assettype='machine')
|
||||
atype_computer = AssetType(assettype='computer')
|
||||
db.session.add_all([atype_machine, atype_computer])
|
||||
db.session.flush()
|
||||
|
||||
controls = RelationshipType(relationshiptype='controls', isdirectional=True)
|
||||
db.session.add(controls)
|
||||
db.session.flush()
|
||||
|
||||
pc = _make_asset('PC-1', atype_computer.assettypeid)
|
||||
machine = _make_asset('M-1', atype_machine.assettypeid)
|
||||
|
||||
# Stored ONCE, in the canonical direction only.
|
||||
db.session.add(AssetRelationship(sourceassetid=pc.assetid,
|
||||
targetassetid=machine.assetid,
|
||||
relationshiptypeid=controls.relationshiptypeid))
|
||||
db.session.commit()
|
||||
|
||||
resp = client.get(f'/api/assets/{machine.assetid}/relationships',
|
||||
headers=auth_headers)
|
||||
assert resp.status_code == 200
|
||||
data = resp.get_json()['data']
|
||||
|
||||
assert data['outgoing'] == [], 'the machine controls nothing'
|
||||
assert len(data['incoming']) == 1, 'the machine must see the PC that controls it'
|
||||
|
||||
row = data['incoming'][0]
|
||||
assert row['relationshiptypename'] == 'controls'
|
||||
# The peer has to arrive with the fields the UI routes on. assetid alone
|
||||
# is not enough: the detail routes key on pluginid, and the type name
|
||||
# chooses the route base.
|
||||
peer = row['sourceasset']
|
||||
assert peer is not None
|
||||
assert peer['assetid'] == pc.assetid
|
||||
assert peer['assettypename'] == 'computer'
|
||||
|
||||
Reference in New Issue
Block a user