Add curated manifest-entry -> Application link (honest app tracking)
The honest replacement for the backed-out auto-seeding: instead of scraping
manifest labels into duplicate Application rows, an entry can be LINKED to an
existing catalog Application, cross-referencing what shopdb already tracks.
- Model: manifestentries.appid (nullable soft ref to core applications; in the
0001 baseline). It is shopdb METADATA, deliberately NOT a manifest field - it
never appears in the rendered manifest JSON, so enforcement + parity are
unaffected (test asserts it stays out of the preview manifest).
- API: _entry_payload returns appid + resolved appname; create/update accept an
optional appid (validated, unknown id ignored, null unlinks) via _apply_app_link;
GET /geenforce/applications is the picker source (id + name).
- Editor: a "Tracked application (optional)" select in the entry modal, and the
entry summary line notes the linked app ("...; tracked: eDNC").
- Foundation for a future desired-vs-observed compliance view.
889 tests green (incl. the link test + parity/migration unaffected); build +
naming green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -108,6 +108,42 @@ def test_entry_edit_preserves_full_fidelity(client, db, auth_headers):
|
||||
assert proc['GracefulCloseTimeoutSec'] == 15
|
||||
|
||||
|
||||
def test_entry_curated_app_link(client, db, auth_headers):
|
||||
"""An entry can be linked to an existing Application; the link is shopdb
|
||||
metadata and must NOT leak into the manifest JSON (parity/enforcement)."""
|
||||
from shopdb.core.models import Application
|
||||
app_row = Application(appname='eDNC (tracked)')
|
||||
db.session.add(app_row)
|
||||
db.session.commit()
|
||||
appid = app_row.appid
|
||||
|
||||
scopeid = _create_scope(client, auth_headers)
|
||||
created = client.post(f'/api/geenforce/scopes/{scopeid}/entries',
|
||||
json={'Name': 'eDNC install', 'Type': 'MSI',
|
||||
'appid': appid}, headers=auth_headers)
|
||||
assert created.status_code == 201, created.get_json()
|
||||
data = created.get_json()['data']
|
||||
assert data['appid'] == appid
|
||||
assert data['appname'] == 'eDNC (tracked)'
|
||||
|
||||
# the link is NOT in the rendered manifest (would break the engine contract)
|
||||
manifest = client.get(f'/api/geenforce/scopes/{scopeid}/preview',
|
||||
headers=auth_headers).get_json()['data']['manifest']
|
||||
entry_json = manifest['Applications'][0]
|
||||
assert 'appid' not in entry_json and 'appname' not in entry_json
|
||||
|
||||
# unlink
|
||||
updated = client.put(f"/api/geenforce/entries/{data['entryid']}",
|
||||
json={'Name': 'eDNC install', 'Type': 'MSI', 'appid': None},
|
||||
headers=auth_headers)
|
||||
assert updated.get_json()['data']['appid'] is None
|
||||
|
||||
# picker lists the app
|
||||
picker = client.get('/api/geenforce/applications',
|
||||
headers=auth_headers).get_json()['data']
|
||||
assert any(a['appname'] == 'eDNC (tracked)' for a in picker)
|
||||
|
||||
|
||||
def test_invalid_entry_type_rejected(client, db, auth_headers):
|
||||
scopeid = _create_scope(client, auth_headers)
|
||||
resp = client.post(f'/api/geenforce/scopes/{scopeid}/entries',
|
||||
|
||||
Reference in New Issue
Block a user