from pathlib import Path import shutil import subprocess import pytest from app.ota.diagnostics import DiagnosticLog, MAX_FAILURE_LOG_BYTES from scripts import ota_components as component def test_directory_loss_restores_recent_diagnostics(tmp_path): path = tmp_path / 'run/log' log = DiagnosticLog(path) log.reset();log.write('before stop') shutil.rmtree(path.parent) log.write('after stop') assert 'before stop' in path.read_text(encoding='utf-8') assert 'after stop' in path.read_text(encoding='utf-8') def test_unwritable_log_does_not_mask_command_failure_or_prevent_persistence(tmp_path, monkeypatch, capsys): path = tmp_path / 'run/log' log = DiagnosticLog(path) log.reset() original = Path.open def broken(self, *args, **kwargs): if self == path: raise PermissionError('do not print confidential OS details') return original(self, *args, **kwargs) monkeypatch.setattr(Path, 'open', broken) monkeypatch.setattr(subprocess, 'run', lambda *a, **k: subprocess.CompletedProcess(a, 9, b'original failure')) with pytest.raises(RuntimeError, match='original failure'): log.run(['example']) log.write('rollback executed') target = log.persist(tmp_path/'data') assert 'rollback executed' in target.read_text(encoding='utf-8') assert 'confidential' not in capsys.readouterr().err def test_fallback_buffer_is_bounded(tmp_path): log = DiagnosticLog(tmp_path/'log') log.write('x' * (MAX_FAILURE_LOG_BYTES * 2)) assert len(log._recent) <= MAX_FAILURE_LOG_BYTES def test_runtime_guard_verified_and_does_not_overwrite_user_configuration(tmp_path, monkeypatch): path = tmp_path/'dropin/guard.conf' monkeypatch.setattr(component, 'RUNTIME_GUARD', path) monkeypatch.setattr(component, 'sync_directory', lambda p: None) commands=[] def run(args, **kwargs): commands.append(args) return subprocess.CompletedProcess(args, 0, b'yes\n') monkeypatch.setattr(component, 'run', run) component.protect_runtime() assert path.read_bytes() == component.RUNTIME_GUARD_BODY assert commands[-1][1] == 'show' component.release_runtime_guard() assert not path.exists() path.parent.mkdir();path.write_bytes(b'user setting') with pytest.raises(RuntimeError, match='overwrite'): component.protect_runtime() with pytest.raises(RuntimeError, match='modified'): component.release_runtime_guard() assert path.read_bytes() == b'user setting' def test_ineffective_guard_refuses_to_proceed(tmp_path, monkeypatch): monkeypatch.setattr(component, 'RUNTIME_GUARD', tmp_path/'guard') monkeypatch.setattr(component, 'sync_directory', lambda p: None) monkeypatch.setattr(component, 'run', lambda a, **k: subprocess.CompletedProcess(a,0,b'restart\n')) with pytest.raises(RuntimeError, match='refusing to stop'): component.protect_runtime() def test_component_validation_accepts_current_transaction_runtime_guard(tmp_path, monkeypatch): import hashlib import json import os import sys from types import SimpleNamespace source = tmp_path/'source' (source/'systemd').mkdir(parents=True) (source/'systemd/matrix-screen-frpc.service').write_bytes(b'unit') (source/'UPGRADE_POLICY.json').write_text(json.dumps({'checkpoints':[ {'version':'1.1.0','components':{'frpc':{'version':'0.71.0', 'sha256':hashlib.sha256(b'binary').hexdigest()}}}]}),encoding='utf-8') files={name:tmp_path/name for name in ('frpc','frpc-unit','frpc-dropin')} for name,body in [('frpc',b'binary'),('frpc-unit',b'unit'), ('frpc-dropin',b'[Service]\nUser=maintainer\nGroup=maintainer\n')]: files[name].write_bytes(body) data=tmp_path/'data';(data/'frp/profiles').mkdir(parents=True) guard=tmp_path/'guard';guard.write_bytes(component.RUNTIME_GUARD_BODY) monkeypatch.setattr(component,'FILES',files) monkeypatch.setattr(component,'DATA',data) monkeypatch.setattr(component,'RUNTIME_GUARD',guard) monkeypatch.setattr(component,'account',lambda:'maintainer') monkeypatch.setitem(sys.modules,'pwd',SimpleNamespace(getpwnam=lambda u:SimpleNamespace(pw_gid=1000))) monkeypatch.setitem(sys.modules,'grp',SimpleNamespace(getgrgid=lambda g:SimpleNamespace(gr_name='maintainer'))) monkeypatch.setattr(component,'run',lambda a,**k:subprocess.CompletedProcess(a,0,b'0.71.0\n')) monkeypatch.setattr(component,'metadata',lambda p:{'mode':0o755 if p==files['frpc'] else 0o2750 if p.is_relative_to(data) else 0o644, 'uid':0,'gid':1000 if p.is_relative_to(data) else 0}) original=Path.stat def normalized_stat(self,*a,**k): value=original(self,*a,**k) fields=list(value) if self==data:fields[0]=(fields[0]&~0o7777)|0o711 if self==files['frpc']:fields[4]=fields[5]=0 return os.stat_result(fields) monkeypatch.setattr(Path,'stat',normalized_stat) component.check_installed(source,'1.1.0') assert guard.read_bytes()==component.RUNTIME_GUARD_BODY