"""Check download presets and install locked packages from an isolated local index."""
import hashlib
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import zipfile
import pytest
from tests.unit.test_windows_bootstrap import run_bootstrap
ROOT = Path(__file__).resolve().parents[2]
pytestmark = pytest.mark.skipif(
sys.platform != "win32" or not shutil.which("uv"), reason="Windows and uv required"
)
def powershell(tmp_path, script, **env):
path = tmp_path / "test.ps1"
path.write_text(
"$ErrorActionPreference = 'Stop'\n"
"[Console]::OutputEncoding = New-Object Text.UTF8Encoding($false)\n"
". $env:DOWNLOAD_SCRIPT\n" + script,
encoding="ascii",
)
clean_env = {
key: value
for key, value in os.environ.items()
if not key.upper().startswith(("UV_", "NPM_CONFIG_"))
}
return subprocess.run(
["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", str(path)],
env={
**clean_env,
"DOWNLOAD_SCRIPT": str(ROOT / "scripts/download_config.ps1"),
"TEST_ROOT": str(tmp_path),
**env,
},
capture_output=True,
encoding="utf-8",
timeout=45,
)
def test_presets_overrides_and_process_environment(tmp_path):
(tmp_path / "config").mkdir()
config = json.loads((ROOT / "config/downloads.json").read_text())
config["mode"] = "China"
config["overrides"]["node_mirror"] = "https://intranet.example/node"
(tmp_path / "config/downloads.json").write_text(json.dumps(config))
result = powershell(
tmp_path,
"""
Initialize-ArtemisDownloads -ProjectRoot $env:TEST_ROOT
if ($ArtemisDownloads.mode -ne 'China') { throw 'Preset not selected' }
if ($ArtemisDownloads.node_mirror -ne 'https://intranet.example/node') { throw 'Override ignored' }
if ($ArtemisDownloads.python_index -ne 'https://intranet.example/simple') { throw 'Environment ignored' }
if ($env:npm_config_registry -ne 'https://registry.npmmirror.com') { throw 'npm mirror missing' }
if ($env:UV_HTTP_TIMEOUT -ne '180') { throw 'Timeout missing' }
if (([Uri]$ArtemisDownloads.scrcpy_url).Host -eq 'github.com') { throw 'China scrcpy still uses GitHub' }
if ($ArtemisDownloads.scrcpy_urls.Count -lt 2) { throw 'China scrcpy has no fallback' }
if ($ArtemisDownloads.scrcpy_sha256 -notmatch '^[a-f0-9]{64}$') { throw 'scrcpy digest missing' }
Remove-Item Env:npm_config_registry
Remove-Item Env:UV_PYTHON_INSTALL_MIRROR
Initialize-ArtemisDownloads -ProjectRoot $env:TEST_ROOT -Mode Official
if ($ArtemisDownloads.mode -ne 'Official') { throw 'CLI mode ignored' }
if ($env:npm_config_registry) { throw 'Official mode selected a China registry' }
""",
UV_DEFAULT_INDEX="https://intranet.example/simple",
)
assert result.returncode == 0, result.stdout + result.stderr
@pytest.mark.parametrize("first_failure", ["http", "timeout", "hash", "all"])
def test_scrcpy_source_failover_checks_hash_before_installing(tmp_path, first_failure):
archive = tmp_path / "fixture.zip"
with zipfile.ZipFile(archive, "w") as package:
package.writestr("scrcpy-win64-v4.1/scrcpy.exe", b"verified executable")
package.writestr("scrcpy-win64-v4.1/scrcpy-server", b"verified server")
digest = hashlib.sha256(archive.read_bytes()).hexdigest()
install_dir = tmp_path / "user/Programs/scrcpy"
install_dir.mkdir(parents=True)
(install_dir / "keep.txt").write_text("user file")
temp_dir = tmp_path / "temporary"
temp_dir.mkdir()
result = powershell(
tmp_path,
"""
$tokens = $null; $errors = $null
$ast = [System.Management.Automation.Language.Parser]::ParseFile($env:LAUNCH_SCRIPT, [ref]$tokens, [ref]$errors)
if ($errors) { throw 'Launcher parse error' }
$installer = $ast.Find({ param($node) $node -is [System.Management.Automation.Language.FunctionDefinitionAst] -and $node.Name -eq 'Install-PortableScrcpy' }, $true)
. ([scriptblock]::Create($installer.Extent.Text))
function Get-LaunchText { param($English, $Chinese); return $English }
$script:attempts = @()
function Invoke-DownloadFile {
param($Uri, $OutFile)
$script:attempts += $Uri
if ($Uri -eq 'https://first.example/scrcpy.zip' -or $env:FIRST_FAILURE -eq 'all') {
Set-Content -LiteralPath $OutFile -Value 'rejected content'
if ($env:FIRST_FAILURE -eq 'hash') { return $true }
$script:ArtemisDownloadError = if ($env:FIRST_FAILURE -eq 'timeout') { 'Download timed out' } else { 'HTTP 404' }
return $false
}
Copy-Item -LiteralPath (Join-Path $env:TEST_ROOT 'fixture.zip') -Destination $OutFile -Force
return $true
}
$script:ArtemisDownloads = [pscustomobject]@{
scrcpy_urls = @('https://first.example/scrcpy.zip', 'https://second.example/scrcpy.zip')
scrcpy_sha256 = $env:FIXTURE_HASH
}
$installed = Install-PortableScrcpy
Write-Host "INSTALLED=$installed"
Write-Host ('ATTEMPTS=' + ($script:attempts -join ','))
""",
LOCALAPPDATA=str(tmp_path / "user"),
TEMP=str(temp_dir),
TMP=str(temp_dir),
LAUNCH_SCRIPT=str(ROOT / "scripts/start.ps1"),
FIRST_FAILURE=first_failure,
FIXTURE_HASH=digest,
)
assert result.returncode == 0, result.stdout + result.stderr
assert result.stderr == ""
assert "ATTEMPTS=https://first.example/scrcpy.zip,https://second.example/scrcpy.zip" in (
result.stdout
)
assert (install_dir / "keep.txt").read_text() == "user file"
assert not list(temp_dir.iterdir()), "Installer must clean its temporary download directory"
if first_failure == "all":
assert "INSTALLED=False" in result.stdout
assert "HTTP 404" in result.stdout
assert not (install_dir / "scrcpy.exe").exists()
else:
assert "INSTALLED=True" in result.stdout
assert (install_dir / "scrcpy.exe").read_bytes() == b"verified executable"
assert (install_dir / "scrcpy-server").read_bytes() == b"verified server"
expected_error = {
"http": "HTTP 404",
"timeout": "Download timed out",
"hash": "SHA256 verification failed",
}[first_failure]
assert expected_error in result.stdout
@pytest.mark.parametrize("curl_exit", [22, 28])
def test_download_reports_http_or_timeout_without_repeating_request(tmp_path, curl_exit):
result = powershell(
tmp_path,
"""
function curl.exe {
$target = $args[[Array]::IndexOf($args, '-o') + 1]
Set-Content -LiteralPath $target -Value 'partial download'
$global:LASTEXITCODE = [int]$env:CURL_EXIT
return '404'
}
function Invoke-WebRequest { throw 'HTTP rejection/timeout must not be retried with a second client' }
$destination = Join-Path $env:TEST_ROOT 'download.zip'
$downloaded = Invoke-DownloadFile -Uri 'https://missing.example/file.zip' -OutFile $destination -TimeoutSec 10
Write-Host "DOWNLOADED=$downloaded"
Write-Host "REASON=$script:ArtemisDownloadError"
if (Test-Path -LiteralPath $destination) { throw 'Partial download was left behind' }
""",
CURL_EXIT=str(curl_exit),
)
assert result.returncode == 0, result.stdout + result.stderr
assert "DOWNLOADED=False" in result.stdout
assert ("HTTP 404" if curl_exit == 22 else "timed out after 10 seconds") in result.stdout
@pytest.mark.parametrize(
"fallbacks,valid",
[
(["https://backup.example/scrcpy.zip"], True),
([], True),
(["http://insecure.example/scrcpy.zip"], False),
("https://not-an-array.example/scrcpy.zip", False),
([None], False),
],
)
def test_scrcpy_custom_sources_validate_urls_and_preserve_order(tmp_path, fallbacks, valid):
config = json.loads((ROOT / "config/downloads.json").read_text())
config["overrides"]["scrcpy_url"] = "https://custom.example/scrcpy.zip"
config["overrides"]["scrcpy_fallback_urls"] = fallbacks
(tmp_path / "config").mkdir()
(tmp_path / "config/downloads.json").write_text(json.dumps(config))
result = powershell(
tmp_path,
"""
Initialize-ArtemisDownloads -ProjectRoot $env:TEST_ROOT -Mode China
Write-Host ('SOURCES=' + ($ArtemisDownloads.scrcpy_urls -join ','))
""",
)
if valid:
assert result.returncode == 0, result.stdout + result.stderr
assert "SOURCES=" + ",".join(["https://custom.example/scrcpy.zip", *fallbacks]) in (
result.stdout
)
else:
assert result.returncode != 0
assert "HTTPS URLs" in result.stderr
def test_china_sources_never_fall_back_to_direct_github(tmp_path):
config = json.loads((ROOT / "config/downloads.json").read_text())
config["overrides"]["scrcpy_fallback_urls"] = [
"https://github.com/Genymobile/scrcpy/releases/download/v4.1/scrcpy-win64-v4.1.zip",
config["china"]["scrcpy_url"], # Duplicate should be downloaded only once.
]
(tmp_path / "config").mkdir()
(tmp_path / "config/downloads.json").write_text(json.dumps(config))
result = powershell(
tmp_path,
"""
Initialize-ArtemisDownloads -ProjectRoot $env:TEST_ROOT -Mode China
if ($ArtemisDownloads.scrcpy_urls.Count -ne 1) { throw 'Invalid China sources' }
if (([Uri]$ArtemisDownloads.scrcpy_urls[0]).Host -eq 'github.com') { throw 'Direct GitHub fallback' }
""",
)
assert result.returncode == 0, result.stdout + result.stderr
@pytest.mark.parametrize("corrupt", [False, True])
def test_uv_mirror_bootstrap_verifies_wheel_without_python(tmp_path, corrupt):
with zipfile.ZipFile(tmp_path / "fixture.zip", "w") as wheel:
wheel.writestr("uv-1.2.3.data/scripts/uv.exe", b"standalone fixture")
digest = hashlib.sha256((tmp_path / "fixture.zip").read_bytes()).hexdigest()
if corrupt:
digest = "0" * 64
(tmp_path / "index.html").write_text(
f'uv'
)
result = powershell(
tmp_path,
"""
function Invoke-DownloadFile {
param($Uri, $OutFile)
if ($Uri -eq 'https://mirror.example/simple/uv/') {
Copy-Item -LiteralPath (Join-Path $env:TEST_ROOT 'index.html') -Destination $OutFile
} elseif ($Uri -eq 'https://mirror.example/packages/uv-1.2.3-py3-none-win_amd64.whl') {
Copy-Item -LiteralPath (Join-Path $env:TEST_ROOT 'fixture.zip') -Destination $OutFile
} else { throw "Unexpected URL: $Uri" }
return $true
}
Install-ArtemisUvFromIndex -IndexUrl 'https://mirror.example/simple' -InstallDir (Join-Path $env:TEST_ROOT 'bin')
""",
PROCESSOR_ARCHITECTURE="AMD64",
PROCESSOR_ARCHITEW6432="AMD64",
)
if corrupt:
assert result.returncode != 0
assert "SHA256 verification" in result.stderr
assert not (tmp_path / "bin/uv.exe").exists()
else:
assert result.returncode == 0, result.stdout + result.stderr
assert (tmp_path / "bin/uv.exe").read_bytes() == b"standalone fixture"
def test_mirror_installs_locked_version_and_reuses_it_without_changing_lock(tmp_path):
project = tmp_path / "project"
project.mkdir()
package_dir = tmp_path / "simple/mirror-fixture"
package_dir.mkdir(parents=True)
wheel = package_dir / "mirror_fixture-1.0.0-py3-none-any.whl"
with zipfile.ZipFile(wheel, "w") as archive:
archive.writestr("mirror_fixture.py", 'VERSION = "1.0.0"\n')
archive.writestr(
"mirror_fixture-1.0.0.dist-info/METADATA",
"Metadata-Version: 2.1\nName: mirror-fixture\nVersion: 1.0.0\n",
)
archive.writestr(
"mirror_fixture-1.0.0.dist-info/WHEEL",
"Wheel-Version: 1.0\nRoot-Is-Purelib: true\nTag: py3-none-any\n",
)
archive.writestr("mirror_fixture-1.0.0.dist-info/RECORD", "")
digest = hashlib.sha256(wheel.read_bytes()).hexdigest()
(package_dir / "index.html").write_text(
f'{wheel.name}'
)
(project / "pyproject.toml").write_text(
'[project]\nname="bootstrap-fixture"\nversion="0.1.0"\n'
'requires-python=">=3.12"\ndependencies=["mirror-fixture==1.0.0"]\n'
)
for module in ("artemis", "uvicorn", "fastapi", "langchain_openai"):
(project / f"{module}.py").write_text("")
lock = f"""version = 1
revision = 3
requires-python = ">=3.12"
[[package]]
name = "bootstrap-fixture"
version = "0.1.0"
source = {{ virtual = "." }}
dependencies = [{{ name = "mirror-fixture" }}]
[package.metadata]
requires-dist = [{{ name = "mirror-fixture", specifier = "==1.0.0" }}]
[[package]]
name = "mirror-fixture"
version = "1.0.0"
source = {{ registry = "https://pypi.org/simple" }}
wheels = [{{ url = "https://unreachable.invalid/{wheel.name}", hash = "sha256:{digest}", size = {wheel.stat().st_size} }}]
"""
(project / "uv.lock").write_text(lock)
index = (tmp_path / "simple").as_uri()
first = run_bootstrap(project, mirror_index=index)
assert first.returncode == 0, first.stdout + first.stderr
assert (project / ".venv/Lib/site-packages/mirror_fixture.py").exists()
assert (project / "uv.lock").read_text() == lock
second = run_bootstrap(project, mirror_index=index)
assert second.returncode == 0, second.stdout + second.stderr
assert "dependencies are already ready" in second.stdout
assert (project / "uv.lock").read_text() == lock