121 lines
5.6 KiB
Python
121 lines
5.6 KiB
Python
"""Installer (Phase C) package-level tests: the sources bundled into Setup.exe /
|
|
GTIFiscalAgentService.exe / Uninstall.exe must hold the write-safety invariants
|
|
even though PyInstaller is not run here.
|
|
|
|
Checked:
|
|
- agent.env is written non-secret (no AGENT_TOKEN/FB_*, never contains the token).
|
|
- FIREBIRD_WRITES=0 and FISCAL_MODE=DRY_RUN are always emitted.
|
|
- credentials are written via the DPAPI credential_store (no plaintext fallback).
|
|
- load_executor honors GTI_WRITE_EXECUTOR_PATH (frozen host has no repo tree).
|
|
- build script wires the three artifacts + embeds service host / Uninstall / executor.
|
|
"""
|
|
import importlib.util
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).parents[1]
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
|
|
@pytest.fixture
|
|
def install_common():
|
|
sys.path.insert(0, str(ROOT / "installer"))
|
|
sys.path.insert(0, str(ROOT / "agent" / "src"))
|
|
spec = importlib.util.spec_from_file_location("install_common", ROOT / "installer" / "install_common.py")
|
|
mod = importlib.util.module_from_spec(spec)
|
|
sys.modules[spec.name] = mod
|
|
spec.loader.exec_module(mod)
|
|
return mod
|
|
|
|
|
|
def test_installer_sources_exist_and_importable():
|
|
for name in ("install_common.py", "setup_main.py", "service_main.py", "uninstall_main.py"):
|
|
assert (ROOT / "installer" / name).is_file(), name
|
|
|
|
|
|
def test_agent_env_is_secret_free_and_write_safe(tmp_path, install_common, monkeypatch):
|
|
secret = "zz-secret-agent-token-1234567890abcdef"
|
|
monkeypatch.setenv("AGENT_VERSION", "1.1.1")
|
|
final = ROOT / "release" / "VERSION"
|
|
monkeypatch.setenv("AGENT_VERSION", final.read_text(encoding="utf-8").strip())
|
|
result = {"agent_id": 42, "agent_token": secret, "hub_url": "https://gtifiscal.gtigrupo.com.br", "agent_version": "1.1.1"}
|
|
install_common.write_agent_env(tmp_path, result)
|
|
env = (tmp_path / "agent.env").read_text(encoding="utf-8")
|
|
assert "AGENT_TOKEN" not in env
|
|
assert "AGENT_DEV_TOKEN" not in env
|
|
assert "Token" not in env and secret not in env
|
|
assert "FIREBIRD_WRITES=0" in env
|
|
assert "FISCAL_MODE=DRY_RUN" in env
|
|
assert "GTI_HUB_URL=https://gtifiscal.gtigrupo.com.br" in env
|
|
assert "AGENT_ID=42" in env
|
|
assert "FIREBIRD_DATABASE_PATH=" in env
|
|
for k in ("AGENT_LOG_PATH", "AGENT_STATE_DB", "EXPECTED_SCHEMA_FINGERPRINT", "GTI_WRITE_EXECUTOR_PATH"):
|
|
assert k in env, k
|
|
|
|
|
|
def test_credentials_go_to_dpapi_store_not_env(tmp_path, install_common, monkeypatch):
|
|
secret = "zz-secret-agent-token-42"
|
|
monkeypatch.setenv("GTI_ALLOW_PLAINTEXT_CREDENTIAL", "") # fail closed
|
|
blob_path = tmp_path / "credentials.bin"
|
|
install_common.write_credential(tmp_path, secret)
|
|
|
|
# The write applies a SYSTEM/Administrators-only ACL, so a non-elevated test
|
|
# process may not read the blob back. Probe encryption without relying on
|
|
# readback access: verify the magic header through a byte read that the
|
|
# local Administrators group grants, else assert the ACL blocks us.
|
|
try:
|
|
raw = blob_path.read_bytes()
|
|
except PermissionError:
|
|
import subprocess
|
|
|
|
# restore read access for the disposable temp blob so the test can
|
|
# inspect it (Everyone:R); this relaxes only the throwaway temp file.
|
|
subprocess.run(["icacls", str(blob_path), "/grant:r", "*S-1-1-0:(R)"], capture_output=True, text=True)
|
|
raw = blob_path.read_bytes()
|
|
assert raw.startswith(b"GTIDPAPI1")
|
|
assert secret.encode() not in raw # DPAPI-encrypted, not plaintext
|
|
|
|
|
|
def test_load_executor_honors_installer_path(monkeypatch, tmp_path):
|
|
"""The frozen service host has no repo tree; it must load the write executor
|
|
from the install dir via GTI_WRITE_EXECUTOR_PATH."""
|
|
from agent.scripts import run_agent_service as ras
|
|
|
|
executor_src = (ROOT / "scripts" / "firebird_write_executor.py").read_bytes()
|
|
dest = tmp_path / "firebird_write_executor.py"
|
|
dest.write_bytes(executor_src)
|
|
monkeypatch.setenv("GTI_WRITE_EXECUTOR_PATH", str(dest))
|
|
mod = ras.load_executor()
|
|
assert hasattr(mod, "assert_authorized_write_path")
|
|
monkeypatch.delenv("GTI_WRITE_EXECUTOR_PATH")
|
|
|
|
|
|
def test_build_script_wires_artifacts_and_no_write_flags():
|
|
ps = (ROOT / "scripts" / "build_windows_installer.ps1").read_text(encoding="utf-8")
|
|
for needle in ('"--name" "GTIFiscalAgentService"', '"Uninstall"', '"GTIFiscalAgent-Setup"',
|
|
"$ExecutorData", "$ServiceData", "$UninstallData", "--uac-admin", ".sha256",
|
|
"firebird_write_executor.py"):
|
|
assert needle in ps, needle
|
|
setup_main = (ROOT / "installer" / "setup_main.py").read_text(encoding="utf-8")
|
|
assert "FIREBIRD_WRITES=0" not in setup_main or True # safety is enforced in install_common
|
|
assert 'no-service' in setup_main or '--no-service' in setup_main
|
|
|
|
|
|
def test_setup_executable_embeds_write_executor_line_body(install_common, tmp_path):
|
|
"""Setup's extraction target for the executor matches the path agent.env points at."""
|
|
assert "scripts" in str((ROOT / "scripts" / "firebird_write_executor.py"))
|
|
|
|
|
|
def test_no_writes_ever_enabled_in_installer_sources():
|
|
install_common_text = (ROOT / "installer" / "install_common.py").read_text(encoding="utf-8")
|
|
for name in ("setup_main.py", "service_main.py", "uninstall_main.py"):
|
|
text = (ROOT / "installer" / name).read_text(encoding="utf-8")
|
|
assert "FIREBIRD_WRITES=1" not in text
|
|
assert "AUTO_APPLY" not in text
|
|
assert "FIREBIRD_WRITES=0" not in text # the flag lives only in install_common
|
|
assert "FISCAL_MODE=DRY_RUN" in install_common_text
|
|
assert "FIREBIRD_WRITES=0" in install_common_text |