"""Tests for resolve_whatsapp_bridge_dir() — read-only install tree handling. Regression coverage for #49561: in the Docker image the install tree (/opt/hermes/scripts/whatsapp-bridge) is read-only, so `npm install` fails with EACCES. The resolver must detect the read-only install dir and mirror the bridge source into a writable HERMES_HOME location instead. """ import importlib from pathlib import Path import pytest from gateway.platforms import whatsapp_common def _seed_install_tree(install_bridge: Path) -> None: """Create a minimal fake bridge source tree.""" install_bridge.mkdir(parents=True, exist_ok=True) (install_bridge / "bridge.js").write_text("// bridge\n") (install_bridge / "package.json").write_text('{"name": "whatsapp-bridge"}\n') def test_readonly_install_mirrors_to_hermes_home(tmp_path, monkeypatch): """A read-only install tree is mirrored into a writable HERMES_HOME.""" install_root = tmp_path / "install" install_bridge = install_root / "scripts" / "whatsapp-bridge" _seed_install_tree(install_bridge) hermes_home = tmp_path / "hermes_home" hermes_home.mkdir() monkeypatch.setattr( whatsapp_common, "__file__", str(install_root / "gateway" / "platforms" / "whatsapp_common.py"), ) monkeypatch.setattr( "hermes_constants.get_hermes_home", lambda: hermes_home ) # Simulate a read-only install tree. chmod(0o555) is unreliable under # root (CI/Docker bypass permission bits), so force the write probe to # fail by raising on the .write_test touch for the install dir only. _real_touch = Path.touch def _fake_touch(self, *a, **kw): if self.name == ".write_test" and install_bridge in self.parents: raise PermissionError("read-only install tree") return _real_touch(self, *a, **kw) monkeypatch.setattr(Path, "touch", _fake_touch) resolved = whatsapp_common.resolve_whatsapp_bridge_dir() expected = hermes_home / "scripts" / "whatsapp-bridge" assert resolved == expected # Source was mirrored, not symlinked. assert (expected / "bridge.js").read_text() == "// bridge\n" assert (expected / "package.json").exists()