Files
gavindiazandClaude Code 3a005ddcc6
Docs / Validate documentation (push) Canceled after 0s
Docs / Deploy GitHub Pages (push) Canceled after 0s
CI Final Summary (AI) / AI Final Summary (push) Canceled after 0s
CI Final Summary (AI) / Push/PR event feed (push) Canceled after 0s
CI / CI Integrity and Change Classification (push) Canceled after 0s
CI / Code Quality & Tests (push) Canceled after 0s
Dependency Lock & Migration Safety / Dependency drift (lock vs pyproject) (push) Canceled after 0s
Dependency Lock & Migration Safety / Migration safety (fail-loud + version postconditions) (push) Canceled after 0s
JS Tests / Frontend JS Unit Tests (push) Canceled after 0s
Security / CodeQL Analysis (push) Canceled after 0s
Security / Trivy Vulnerability Scan (push) Canceled after 0s
Tests (OS Matrix) / Py Tests (macos-latest) (push) Canceled after 0s
Tests (OS Matrix) / Py Tests (windows-latest) (push) Canceled after 0s
CI / Extended Validation (Tier 2) (push) Canceled after 0s
CI / Heavy CI (database-provider) (push) Canceled after 0s
CI / Heavy CI (e2e) (push) Canceled after 0s
CI / Heavy CI (integration) (push) Canceled after 0s
CI / Heavy CI (model-validation) (push) Canceled after 0s
CI / Heavy CI (research-backtest) (push) Canceled after 0s
CI / Aggregate CI Results (push) Canceled after 0s
chore(repo): initial import of Nexus Trading Forex Bot into self-hosted hosting
Full project import for the self-hosted code hosting
(https://code.aifunny.ltd/gavindiaz/my-NexusTradingForexBot.git).

Includes the live-freshness + scaler-hash fixes for the permanent
STALE dashboard (tick_pipeline now_utc stamps; infer_probabilities
computes model_input_hash on the actual scaled 70D tensor), the
data-fetch default-path fix, the g29 web-auth test fixes, and a
Chinese user manual (使用手册.md) for the web dashboard.

Scratch diagnostic scripts (check*.py, contain a live web-auth token)
and the .kilo/ agent-worktree directory are gitignored, not committed.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 23:24:54 +08:00

366 lines
14 KiB
Python

"""Venv transaction stress + failure-injection suite (TASK: lifecycle assurance).
OFFLINE: pure local fixtures, no network. Every test proves one lifecycle
invariant of the deferred venv transaction (pending-backup marker):
V1 marker written when old venv parked; stale sweep respects it
V2 missing backup + present marker -> marker self-heals (dropped)
V3 marker + corrupt backup name -> marker dropped, no crash
V4 backup exists but marker missing -> sweep may collect it (documented)
V5 failure injection: marker deleted between stages -> stale sweep keeps
nothing inconsistent; next install healthy-detects and rebuilds
V6 ledger truthfulness: a failed stage writes ok=false into install.json
V7 zip-slip assault remains blocked (regression guard, local fixtures)
"""
from __future__ import annotations
import json
import os
import subprocess
import sys
import zipfile
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parents[2]
INSTALLER = REPO_ROOT / "installer" / "install.ps1"
pytestmark = pytest.mark.skipif(
sys.platform != "win32" or not INSTALLER.exists(),
reason="Windows + installer required",
)
def _ps() -> str:
import shutil
for c in ("pwsh.exe", "powershell.exe"):
p = shutil.which(c)
if p:
return p
pytest.skip("no PowerShell")
return ""
PS = _ps()
def run_ps(expr: str, timeout: int = 120) -> subprocess.CompletedProcess:
return subprocess.run(
[PS, "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", expr],
capture_output=True,
text=True,
timeout=timeout,
encoding="utf-8",
errors="replace",
check=False,
)
def dot(installer: Path) -> str:
return '. "' + installer.as_posix() + '"; '
class TestVenvTransaction:
def _setup_home(self, tmp_path: Path) -> Path:
home = tmp_path / "NexusHome"
(home / "state").mkdir(parents=True)
return home
def test_marker_written_and_sweep_respects_pending(self, tmp_path):
"""V1: parked tree + marker -> stale sweep must NOT delete the backup."""
home = self._setup_home(tmp_path)
backup = home / "venv.stale.20260901-120000-abc"
backup.mkdir()
(backup / "pyvenv.cfg").write_text("home = X", encoding="utf-8")
marker = home / "state" / "venv.pending-backup"
marker.write_text(backup.name, encoding="utf-8")
expr = (
dot(INSTALLER)
+ f"$NexusHome = '{home.as_posix()}'; "
+ "Remove-StaleVenvBackups -AgeMinutes 0; "
+ "'BACKUP_ALIVE=' + (Test-Path -LiteralPath '"
+ backup.as_posix()
+ "')"
)
r = run_ps(expr)
assert "BACKUP_ALIVE=True" in r.stdout, r.stdout + r.stderr
def test_missing_backup_marker_self_heals(self, tmp_path):
"""V2: marker pointing at a missing tree is dropped, no crash."""
home = self._setup_home(tmp_path)
marker = home / "state" / "venv.pending-backup"
marker.write_text("venv.stale.gone", encoding="utf-8")
expr = (
dot(INSTALLER)
+ f"$NexusHome = '{home.as_posix()}'; "
+ "$b = Get-PendingVenvBackup; "
+ "'MARKER_GONE=' + (-not (Test-Path -LiteralPath '"
+ marker.as_posix()
+ "')); 'BACKUP=' + $b"
)
r = run_ps(expr)
assert "MARKER_GONE=True" in r.stdout, r.stdout + r.stderr
assert "BACKUP=" in r.stdout
def test_marker_with_corrupt_backup_name_dropped(self, tmp_path):
"""V3: marker content names a nonexistent/corrupt tree -> dropped."""
home = self._setup_home(tmp_path)
marker = home / "state" / "venv.pending-backup"
marker.write_text("../evil", encoding="utf-8")
expr = (
dot(INSTALLER)
+ f"$NexusHome = '{home.as_posix()}'; "
+ "$b = Get-PendingVenvBackup; "
+ "'BACKUP_IS_NULL=' + ($null -eq $b)"
)
r = run_ps(expr)
assert "BACKUP_IS_NULL=True" in r.stdout, r.stdout + r.stderr
def test_crash_between_stages_recovers_on_next_run(self, tmp_path):
"""V5: simulate crash after venv park but before deps commit - marker
+ backup exist. The next venv-stage invocation must healthy-detect the
RESTORED venv (or rebuild) and reach a consistent end state."""
home = self._setup_home(tmp_path)
# Simulate the crash state: marker + parked backup, plus a live venv.
backup = home / "venv.stale.20260901-130000-def"
backup.mkdir()
(backup / "pyvenv.cfg").write_text("home = X", encoding="utf-8")
(home / "state" / "venv.pending-backup").write_text(backup.name, encoding="utf-8")
# The restore path (Restore-VenvBackup) must bring the backup back as venv.
expr = (
dot(INSTALLER)
+ f"$NexusHome = '{home.as_posix()}'; "
+ "Restore-VenvBackup; "
+ "'VENV_RESTORED=' + (Test-Path -LiteralPath '"
+ (home / "venv").as_posix()
+ "'); 'MARKER_CLEARED=' + (-not (Test-Path -LiteralPath '"
+ (home / "state" / "venv.pending-backup").as_posix()
+ "'))"
)
r = run_ps(expr)
assert "VENV_RESTORED=True" in r.stdout, r.stdout + r.stderr
assert "MARKER_CLEARED=True" in r.stdout, r.stdout + r.stderr
def test_marker_written_when_old_venv_parked(self, tmp_path):
"""V1b: Install-Venv transactional recreate parks the old venv and
writes the pending-backup marker. Uses a STUB uv (no network): the
stub creates a REAL venv via the repo interpreter's venv module so
health verification passes offline."""
home = self._setup_home(tmp_path)
# an existing (unhealthy) venv forces the recreate path
venv = home / "venv"
venv.mkdir()
(venv / "pyvenv.cfg").write_text("home = broken", encoding="utf-8")
repo_py = REPO_ROOT / ".venv" / "Scripts" / "python.exe"
if not repo_py.exists():
pytest.skip("repo .venv python missing for stub venv creation")
stub_dir = tmp_path / "stubbin"
stub_dir.mkdir()
stub = stub_dir / "uv.cmd"
lines = [
"@echo off",
'set "TARGETDIR=%~2"',
'if /i "%~1"=="venv" "' + str(repo_py) + '" -m venv "%TARGETDIR%" >nul 2>&1',
'if /i "%~1"=="--version" (echo uv 9.9.9-stub & exit /b 0)',
"exit /b 0",
"",
]
stub.write_bytes("\r\n".join(lines).encode("utf-8"))
expr = (
dot(INSTALLER)
+ f"$NexusHome = '{home.as_posix()}'; "
+ f"$Script:UvCmd = '{stub.as_posix()}'; "
+ "try { Install-Venv; 'MARKER=' + (Test-Path -LiteralPath '"
+ (home / "state" / "venv.pending-backup").as_posix()
+ "' -PathType Leaf); 'PARKED=' + ((Get-ChildItem '"
+ home.as_posix()
+ "' -Directory -Filter 'venv.stale.*').Count -gt 0) } catch { 'VENV_ERR=' + $_.Exception.Message }"
)
r = run_ps(expr, timeout=180)
out = r.stdout
assert "VENV_ERR=" not in out, out + r.stderr
assert "MARKER=True" in out, out + r.stderr
assert "PARKED=True" in out, out + r.stderr
class TestHealthyDetectResolvesPending:
def test_healthy_venv_consumes_stale_pending_marker(self, tmp_path):
"""Regression (process-kill smoke GATE 08): a run killed after the
venv replacement completed but before the dependencies commit left
marker + parked rollback tree forever - healthy-detect now resolves
the transaction. Injected post-kill state: healthy venv + marker +
parked tree."""
home = tmp_path / "NexusHome"
(home / "state").mkdir(parents=True)
marker = home / "state" / "venv.pending-backup"
parked = home / "venv.stale.20260902-010101-aa"
parked.mkdir()
(parked / "pyvenv.cfg").write_text("home = previous", encoding="utf-8")
marker.write_text(parked.name, encoding="utf-8")
# Seed a genuinely HEALTHY replacement venv (post-kill state: creation
# finished, transaction never committed).
repo_py = REPO_ROOT / ".venv" / "Scripts" / "python.exe"
if not repo_py.exists():
pytest.skip("repo .venv python missing for healthy-venv seeding")
subprocess.run(
[str(repo_py), "-m", "venv", str(home / "venv")],
capture_output=True,
timeout=180,
check=True,
)
# Real (trivial) uv.cmd override: Install-Venv resolves uv BEFORE the
# healthy-detect branch, so the override must exist for the run to
# reach the transaction-resolution path under test.
stub_dir = tmp_path / "stubbin"
stub_dir.mkdir()
stub = stub_dir / "uv.cmd"
stub.write_bytes(b"@echo off\r\nexit /b 0\r\n")
expr = (
dot(INSTALLER)
+ f"$NexusHome = '{home.as_posix()}'; "
+ f"$env:NEXUS_INSTALLER_UV_OVERRIDE = '{stub.as_posix()}'; "
+ "try { Install-Venv; 'MARKER_GONE=' + (-not (Test-Path -LiteralPath '"
+ marker.as_posix()
+ "')); 'HEALTHY=' + (Test-VenvHealthy -VenvPython '"
+ (home / "venv" / "Scripts" / "python.exe").as_posix()
+ "') } catch { 'VENV_ERR=' + $_.Exception.Message }"
)
r = run_ps(expr, timeout=300)
out = r.stdout
assert "VENV_ERR=" not in out, out + r.stderr
assert "HEALTHY=True" in out, out + r.stderr
assert "MARKER_GONE=True" in out, out + r.stderr
class TestLedgerTruthfulness:
def test_failed_stage_records_ok_false_in_ledger(self, tmp_path):
"""V6: a failed stage must appear in install.json with ok=false."""
home = tmp_path / "NexusHome"
engine = tmp_path / "engine-src"
r = subprocess.run(
[
PS,
"-NoProfile",
"-NonInteractive",
"-ExecutionPolicy",
"Bypass",
"-File",
str(INSTALLER),
"-Stage",
"dependencies",
"-NexusHome",
str(home),
"-InstallDir",
str(engine),
"-Json",
],
capture_output=True,
text=True,
timeout=180,
encoding="utf-8",
errors="replace",
check=False,
)
frame = json.loads(r.stdout.strip())
assert frame["ok"] is False
state_path = home / "state" / "install.json"
assert state_path.exists(), "failed stage must still write a truthful ledger"
state = json.loads(state_path.read_text(encoding="utf-8"))
rec = state.get("stages", {}).get("dependencies", {})
assert rec.get("ok") is False, f"ledger must record the failure: {state}"
assert state.get("last_successful_stage") != "dependencies"
def test_stage_ledger_survives_process_death_shape(self, tmp_path):
"""Ledger is valid JSON with atomic-replace marker absent (no .tmp)."""
home = tmp_path / "NexusHome"
engine = tmp_path / "engine-src"
subprocess.run(
[
PS,
"-NoProfile",
"-NonInteractive",
"-ExecutionPolicy",
"Bypass",
"-File",
str(INSTALLER),
"-Stage",
"environment",
"-NexusHome",
str(home),
"-InstallDir",
str(engine),
"-Json",
],
capture_output=True,
text=True,
timeout=120,
encoding="utf-8",
errors="replace",
check=False,
)
state_path = home / "state" / "install.json"
assert state_path.exists()
state = json.loads(state_path.read_text(encoding="utf-8"))
assert state["stages"]["environment"]["ok"] is True
leftovers = list((home / "state").glob("install.json.tmp-*"))
assert not leftovers, f"atomic replace left temp files: {leftovers}"
class TestZipAssault:
def _zip(self, tmp_path: Path, entries: dict) -> Path:
z = tmp_path / "assault.zip"
with zipfile.ZipFile(z, "w") as zf:
for name, content in entries.items():
zf.writestr(name, content)
return z
def _extract(self, tmp_path: Path, zip_path: Path, dest: str):
expr = (
dot(INSTALLER)
+ "try { Expand-NexusZipSafe -ZipPath '"
+ zip_path.as_posix()
+ "' -Destination '"
+ (tmp_path / dest).as_posix()
+ "'; 'EXTRACTED' } catch { 'BLOCKED' }"
)
return run_ps(expr)
def test_traversal_blocked(self, tmp_path):
z = self._zip(tmp_path, {"../../evil.txt": "x"})
r = self._extract(tmp_path, z, "out1")
assert "BLOCKED" in r.stdout
def test_absolute_windows_blocked(self, tmp_path):
z = self._zip(tmp_path, {"C:/evil.txt": "x"})
r = self._extract(tmp_path, z, "out2")
assert "BLOCKED" in r.stdout
def test_unc_blocked(self, tmp_path):
z = self._zip(tmp_path, {"//server/share/evil.txt": "x"})
r = self._extract(tmp_path, z, "out3")
assert "BLOCKED" in r.stdout
def test_valid_zip_extracts(self, tmp_path):
z = self._zip(tmp_path, {"repo/pyproject.toml": "[project]\n"})
r = self._extract(tmp_path, z, "out4")
assert "EXTRACTED" in r.stdout, r.stdout + r.stderr
assert (tmp_path / "out4" / "repo" / "pyproject.toml").exists()
def test_failed_extract_leaves_no_dir_when_fresh(self, tmp_path):
z = self._zip(tmp_path, {"../escape.txt": "x"})
dest = tmp_path / "out5"
r = self._extract(tmp_path, z, "out5")
assert "BLOCKED" in r.stdout
assert not dest.exists(), "failed extraction must leave no fresh-dir residue"