PegaProx_project-pegaprox/tests/test_update_install_method.py
MrMasterbay 18d03f0476 feat(update): install-method-aware self-update (apt / docker / source)
The in-app updater and update.sh are a git-tree + pip file update that only fits
the source/deploy.sh layout. On an apt/dpkg install the correct path is apt
upgrade (deps are system python3-* packages; a pip run diverges from dpkg and
can't lift the dpkg-owned crypto libs -> fail-closed TLS on restart); on Docker
a file update is discarded at the next image pull.

- settings.py: _detect_install_method() (docker via /.dockerenv + cgroup, apt via
  dpkg -S, else source); perform_pegaprox_update refuses on apt/docker with the
  right guidance + copy-paste command (409, allow_managed override); check-update
  reports install_method / in_app_update_supported / managed_update_{hint,command}.
- update.sh: same detection + guard before touching anything (--force override).
- settings_modal.js: the Install button becomes a package-manager / image hint
  with a copyable command on apt/docker; performUpdate handles the 409.
- tests: 5 guard/reporting tests.
2026-08-11 19:02:44 +02:00

78 lines
3.3 KiB
Python

# The in-app updater is a git-tree + pip file update and only fits the source/deploy.sh layout.
# On an apt/dpkg or Docker install it would diverge from the package manager and (for apt) can't
# lift the dpkg-owned crypto libs -> fail-closed TLS brick on restart (the bbailey 1.0.1 case).
# These guard tests pin that perform_pegaprox_update refuses on apt/docker and still runs on source.
import pegaprox.api.settings as settings
import requests
def _admin(seed):
return seed.user('root', role='admin', tenant_id='default')
def _kill_network(monkeypatch):
# make both version fetches fail fast so a guard-passing request returns the 503 network
# error quickly instead of hanging on real GitHub/mirror I/O.
def _boom(*a, **k):
raise requests.exceptions.ConnectionError('blocked in test')
monkeypatch.setattr(settings.requests, 'get', _boom)
def test_update_blocked_on_apt(api, seed, monkeypatch):
admin = _admin(seed)
monkeypatch.setattr(settings, '_detect_install_method', lambda d: 'apt')
r = api.as_user(admin).post('/api/pegaprox/update', json={})
assert r.status_code == 409, r.get_data(as_text=True)
body = r.get_json()
assert body['error'] == 'in_app_update_not_supported'
assert body['install_method'] == 'apt'
assert 'apt' in body['message'].lower()
def test_update_blocked_on_docker(api, seed, monkeypatch):
admin = _admin(seed)
monkeypatch.setattr(settings, '_detect_install_method', lambda d: 'docker')
r = api.as_user(admin).post('/api/pegaprox/update', json={})
assert r.status_code == 409, r.get_data(as_text=True)
body = r.get_json()
assert body['error'] == 'in_app_update_not_supported'
assert body['install_method'] == 'docker'
assert 'image' in body['message'].lower() or 'container' in body['message'].lower()
def test_update_source_passes_guard(api, seed, monkeypatch):
# source install: guard must NOT fire; request reaches the (killed) network step -> 503.
admin = _admin(seed)
monkeypatch.setattr(settings, '_detect_install_method', lambda d: 'source')
_kill_network(monkeypatch)
r = api.as_user(admin).post('/api/pegaprox/update', json={})
assert r.status_code == 503, r.get_data(as_text=True)
def test_update_apt_override_with_allow_managed(api, seed, monkeypatch):
# explicit power-user override bypasses the guard even on apt -> reaches the network step.
admin = _admin(seed)
monkeypatch.setattr(settings, '_detect_install_method', lambda d: 'apt')
_kill_network(monkeypatch)
r = api.as_user(admin).post('/api/pegaprox/update', json={'allow_managed': True})
assert r.status_code == 503, r.get_data(as_text=True)
def test_check_update_reports_install_method(api, seed, monkeypatch):
admin = _admin(seed)
monkeypatch.setattr(settings, '_detect_install_method', lambda d: 'apt')
class _Resp:
status_code = 200
def json(self):
return {'version': '99.0', 'build': '2099.01.01', 'release_date': '2099-01-01', 'changelog': []}
monkeypatch.setattr(settings.requests, 'get', lambda *a, **k: _Resp())
r = api.as_user(admin).get('/api/pegaprox/check-update')
assert r.status_code == 200, r.get_data(as_text=True)
body = r.get_json()
assert body['install_method'] == 'apt'
assert body['in_app_update_supported'] is False
assert 'apt' in body['managed_update_hint'].lower()