1
0
forked from BRT/arc
arc/tests/mock_server/setup.ps1
2026-04-18 18:53:00 +07:00

568 lines
21 KiB
PowerShell

<#
.SYNOPSIS
Установка зависимостей, генерация тестовых данных и запуск mock-сервера.
.DESCRIPTION
Скрипт выполняет:
1. Проверяет/устанавливает Python 3.12+
2. Устанавливает Flask (pip install flask)
3. Находит config.ini и БД
4. Проверяет наличие БД и активных данных (devices, bank_profiles, materials)
5. Генерирует default_profile.json и scenarios из БД
6. Переключает config.ini на mock (apiBase=http://127.0.0.1:PORT)
7. Показывает сводку по данным из БД
8. Запускает mock-сервер и открывает отчёт в браузере
.PARAMETER Port
Порт mock-сервера (default: 8888)
.PARAMETER FtPerMaterial
Сколько FETCH_TRANSACTIONS на материал при бутстрапе (default: 3)
.PARAMETER SkipInstall
Пропустить установку Python/Flask (если уже установлены)
.PARAMETER GenerateOnly
Только сгенерировать данные, не запускать сервер
.EXAMPLE
.\setup.ps1
.\setup.ps1 -Port 9999 -FtPerMaterial 5
.\setup.ps1 -SkipInstall
.\setup.ps1 -GenerateOnly
#>
param(
[int]$Port = 8888,
[int]$FtPerMaterial = 3,
[string]$Banks = "",
[string]$Types = "",
[switch]$SkipInstall,
[switch]$GenerateOnly,
[switch]$Clean,
[switch]$Debug
)
$ErrorActionPreference = "Continue"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$Host.UI.RawUI.WindowTitle = "ARC Mock Server Setup"
# ─── Helpers ───────────────────────────────────────────────────────────────
function Write-Step([string]$n, [string]$msg) {
Write-Host "`n=== [$n] $msg ===" -ForegroundColor Cyan
}
function Write-Ok([string]$msg) {
Write-Host " OK: $msg" -ForegroundColor Green
}
function Write-Warn([string]$msg) {
Write-Host " WARN: $msg" -ForegroundColor Yellow
}
function Write-Err([string]$msg) {
Write-Host " ERROR: $msg" -ForegroundColor Red
}
function Exit-WithError([string]$msg) {
Write-Err $msg
Write-Host "`nPress any key to exit..." -ForegroundColor Gray
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
exit 1
}
# ─── 1. Find script and project directories ───────────────────────────────
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
$configPath = $null
$configCandidates = @(
(Join-Path $scriptDir "..\..\cmake-build-debug\config.ini"),
(Join-Path $scriptDir "..\..\config.ini"),
(Join-Path $scriptDir "..\config.ini"),
(Join-Path $scriptDir "config.ini")
)
foreach ($c in $configCandidates) {
$resolved = [System.IO.Path]::GetFullPath($c)
if (Test-Path $resolved) {
$configPath = $resolved
$projectRoot = Split-Path -Parent $resolved
break
}
}
if (-not $configPath) {
Exit-WithError "config.ini not found. Run from project root or Release/tests/mock_server/"
}
Write-Host "Project root : $projectRoot" -ForegroundColor Gray
Write-Host "Config : $configPath" -ForegroundColor Gray
Write-Host "Script dir : $scriptDir" -ForegroundColor Gray
# ─── 2. Python ─────────────────────────────────────────────────────────────
Write-Step "1/6" "Python"
$pythonExe = $null
function Find-Python {
foreach ($cmd in @("python", "python3", "py")) {
try {
$ver = & $cmd --version 2>&1
if ($ver -match "Python\s+3\.(\d+)") {
if ([int]$Matches[1] -ge 9) {
return (Get-Command $cmd).Source
}
}
} catch {}
}
$paths = @(
"$env:LOCALAPPDATA\Programs\Python\Python3*\python.exe",
"$env:LOCALAPPDATA\Programs\Python\Python31*\python.exe",
"C:\Python3*\python.exe"
)
foreach ($pattern in $paths) {
$found = Get-ChildItem -Path $pattern -ErrorAction SilentlyContinue |
Sort-Object Name -Descending | Select-Object -First 1
if ($found) {
$ver = & $found.FullName --version 2>&1
if ($ver -match "Python\s+3\.(\d+)" -and [int]$Matches[1] -ge 9) {
return $found.FullName
}
}
}
return $null
}
if (-not $SkipInstall) {
$pythonExe = Find-Python
if ($pythonExe) {
$ver = & $pythonExe --version 2>&1
Write-Ok "Found: $ver ($pythonExe)"
} else {
Write-Warn "Python not found - installing via winget..."
try {
winget install Python.Python.3.12 --accept-source-agreements --accept-package-agreements --silent 2>&1 | Out-Null
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" +
[System.Environment]::GetEnvironmentVariable("Path", "User")
$pythonExe = Find-Python
if ($pythonExe) {
$ver = & $pythonExe --version 2>&1
Write-Ok "Installed: $ver"
} else {
Exit-WithError "Python installed but not in PATH. Restart terminal and try again."
}
} catch {
Exit-WithError "Failed to install Python. Install manually: https://python.org"
}
}
} else {
$pythonExe = Find-Python
if (-not $pythonExe) {
Exit-WithError "Python not found. Remove -SkipInstall or install manually."
}
Write-Ok "Python: $pythonExe"
}
# ─── Generate helper scripts ──────────────────────────────────────────────
$genFtPs1 = Join-Path $scriptDir "gen_transactions.ps1"
$runServerPs1 = Join-Path $scriptDir "run_server.ps1"
# Shared config-finder block for helper scripts
$configFinderBlock = @'
$_configCandidates = @(
(Join-Path $PSScriptRoot "..\..\cmake-build-debug\config.ini"),
(Join-Path $PSScriptRoot "..\..\config.ini"),
(Join-Path $PSScriptRoot "..\config.ini"),
(Join-Path $PSScriptRoot "config.ini")
)
$_configPath = $null
foreach ($_c in $_configCandidates) {
$_resolved = [System.IO.Path]::GetFullPath($_c)
if (Test-Path $_resolved) { $_configPath = $_resolved; break }
}
if (-not $_configPath) { Write-Host "ERROR: config.ini not found" -ForegroundColor Red; exit 1 }
$_configArg = @("--config", $_configPath)
'@
# gen_transactions.ps1
$genFtContent = @"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$configFinderBlock
Write-Host "Config: `$_configPath" -ForegroundColor Gray
& "$pythonExe" "`$PSScriptRoot\gen_fetch_transactions.py" @_configArg @args
"@
[System.IO.File]::WriteAllText($genFtPs1, $genFtContent, [System.Text.UTF8Encoding]::new($false))
# run_server.ps1
$runServerContent = @"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
& "$pythonExe" "`$PSScriptRoot\mock_server.py" --port 8888 @args
"@
[System.IO.File]::WriteAllText($runServerPs1, $runServerContent, [System.Text.UTF8Encoding]::new($false))
Write-Ok "Created gen_transactions.ps1, run_server.ps1"
# ─── 3. Flask ──────────────────────────────────────────────────────────────
Write-Step "2/6" "Flask"
$flaskInstalled = $false
try {
$pipOut = & $pythonExe -m pip show flask 2>&1
if ($pipOut -match "Name:\s*Flask") {
$flaskVer = ($pipOut | Select-String "Version:\s*(.+)").Matches.Groups[1].Value
Write-Ok "Flask $flaskVer already installed"
$flaskInstalled = $true
}
} catch {}
if (-not $flaskInstalled) {
Write-Host " Installing Flask..." -ForegroundColor Gray
& $pythonExe -m pip install flask --quiet 2>&1
if ($LASTEXITCODE -ne 0) {
Exit-WithError "Failed to install Flask. Try: $pythonExe -m pip install flask"
}
Write-Ok "Flask installed"
}
# ─── 4. Database check ────────────────────────────────────────────────────
Write-Step "3/6" "Database"
$configContent = Get-Content $configPath -Encoding UTF8
$dbMatch = $configContent | Select-String "dbPath\s*=\s*(.+)"
if (-not $dbMatch) {
Exit-WithError "[db] dbPath not found in config.ini"
}
$dbPathRaw = $dbMatch.Matches.Groups[1].Value.Trim()
if ([System.IO.Path]::IsPathRooted($dbPathRaw)) {
$dbPath = $dbPathRaw
} else {
$dbPath = [System.IO.Path]::GetFullPath((Join-Path $projectRoot $dbPathRaw))
}
Write-Host " DB path: $dbPath" -ForegroundColor Gray
if (-not (Test-Path $dbPath)) {
Exit-WithError ("DB not found: $dbPath`n`n" +
"To create the DB:`n" +
" 1. Set apiBase to production server in config.ini`n" +
" 2. Run ARC.exe and log in`n" +
" 3. Devices, profiles and materials will appear in the DB`n" +
" 4. Re-run this script")
}
Write-Ok "DB found: $dbPath"
# Write a temp Python helper that queries DB and outputs structured data
$dbHelperPy = Join-Path $env:TEMP "arc_db_check.py"
@'
import sqlite3, sys, os
sys.stdout.reconfigure(encoding='utf-8')
db_path = sys.argv[1]
mode = sys.argv[2] if len(sys.argv) > 2 else "count"
pool_dir = sys.argv[3] if len(sys.argv) > 3 else ""
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
if mode == "count":
cnt = conn.execute("""
SELECT COUNT(*) FROM devices d
JOIN bank_profiles bp ON bp.device_id = d.id
JOIN materials m ON m.device_id = d.id AND m.app_code = bp.code
WHERE bp.status = 'active' AND m.status = 'active'
AND m.material_id != '' AND bp.bank_profile_id != ''
""").fetchone()[0]
conn.close()
print(cnt)
sys.exit(0 if cnt > 0 else 1)
elif mode == "clean_events":
try:
cnt = conn.execute("SELECT COUNT(*) FROM events").fetchone()[0]
conn.execute("DELETE FROM events")
conn.commit()
print(cnt)
except Exception:
print(0)
conn.close()
sys.exit(0)
elif mode == "summary":
devices = conn.execute("SELECT id, name, api_id, adb_serial FROM devices").fetchall()
profiles = conn.execute("""
SELECT bp.code, bp.full_name, bp.phone, bp.status, d.name AS device
FROM bank_profiles bp JOIN devices d ON d.id = bp.device_id
WHERE bp.status = 'active' AND bp.bank_profile_id != ''
ORDER BY d.name, bp.code
""").fetchall()
materials = conn.execute("""
SELECT m.material_id, m.card_number, m.last_numbers, m.currency, m.status,
m.app_code, d.name AS device
FROM materials m JOIN devices d ON d.id = m.device_id
WHERE m.status = 'active' AND m.material_id != ''
ORDER BY d.name, m.app_code
""").fetchall()
conn.close()
print("DEVICES|" + str(len(devices)))
for d in devices:
serial = d['adb_serial'] or '-'
api = d['api_id'] or '-'
print(f" DEV|{d['name']}|{serial}|{api}")
print("PROFILES|" + str(len(profiles)))
for p in profiles:
name = p['full_name'] or '-'
phone = p['phone'] or '-'
print(f" PRF|{p['device']}|{p['code']}|{name}|{phone}")
print("MATERIALS|" + str(len(materials)))
for m in materials:
card = m['card_number'] or ('****' + m['last_numbers'] if m['last_numbers'] else '-')
print(f" MAT|{m['device']}|{m['app_code']}|{card}|{m['currency']}|{m['material_id'][:12]}")
pool_n = 0
if pool_dir and os.path.isdir(pool_dir):
pool_n = len([f for f in os.listdir(pool_dir) if f.endswith('.json')])
print(f"POOL|{pool_n}")
'@ | Set-Content -Path $dbHelperPy -Encoding UTF8
# Check active materials count
$matCount = & $pythonExe $dbHelperPy $dbPath "count" 2>&1
if ($LASTEXITCODE -ne 0) {
Exit-WithError ("DB exists but no active materials found.`n`n" +
"To populate:`n" +
" 1. Set apiBase to production server`n" +
" 2. Run ARC.exe, log in, add devices and profiles`n" +
" 3. Re-run this script")
}
Write-Ok "Active materials in DB: $matCount"
# Clean events table
$eventsCleared = & $pythonExe $dbHelperPy $dbPath "clean_events" 2>&1
if ($eventsCleared -and [int]$eventsCleared -gt 0) {
Write-Ok "Cleared $eventsCleared events from DB"
} else {
Write-Ok "Events table already empty"
}
# ─── 5. Generate mock data ────────────────────────────────────────────────
Write-Step "4/6" "Generate test data"
# Clean old generated data
$oldProfile = Join-Path $scriptDir "default_profile.json"
if (Test-Path $oldProfile) {
Remove-Item $oldProfile -Force
Write-Host " Cleaned: default_profile.json" -ForegroundColor Gray
}
$scenariosDir = Join-Path $scriptDir "scenarios"
if (Test-Path $scenariosDir) {
$oldScenarios = Get-ChildItem "$scenariosDir\*.json" -ErrorAction SilentlyContinue
if ($oldScenarios) {
$oldScenarios | Remove-Item -Force
Write-Host " Cleaned: scenarios/ ($($oldScenarios.Count) files)" -ForegroundColor Gray
}
}
# -Clean: also wipe the transaction pool
$poolDir = Join-Path $scriptDir "scenarios\pool"
if ($Clean -and (Test-Path $poolDir)) {
$oldPool = Get-ChildItem "$poolDir\*.json" -ErrorAction SilentlyContinue
if ($oldPool) {
$oldPool | Remove-Item -Force
Write-Host " Cleaned: scenarios/pool/ ($($oldPool.Count) files)" -ForegroundColor Gray
}
}
$genScript = Join-Path $scriptDir "gen_mock_data.py"
if (-not (Test-Path $genScript)) {
Exit-WithError "gen_mock_data.py not found in $scriptDir"
}
$genOutput = & $pythonExe $genScript --config $configPath 2>&1
$genOutput | ForEach-Object { Write-Host " $_" }
if ($LASTEXITCODE -ne 0) {
Exit-WithError "Data generation failed"
}
$profileJson = Join-Path $scriptDir "default_profile.json"
if (Test-Path $profileJson) {
Write-Ok "default_profile.json created"
} else {
Exit-WithError "default_profile.json was not created"
}
$poolDir = Join-Path $scriptDir "scenarios\pool"
$poolCount = 0
if (Test-Path $poolDir) {
$poolFiles = Get-ChildItem "$poolDir\*.json" -ErrorAction SilentlyContinue
if ($poolFiles) { $poolCount = $poolFiles.Count }
}
if ($poolCount -gt 0) {
Write-Ok "Transaction pool: $poolCount files"
} else {
Write-Warn "Transaction pool is empty - bootstrap will only create FETCH_PROFILE tasks"
Write-Host " To generate pool run: $pythonExe gen_fetch_transactions.py" -ForegroundColor Gray
}
# ─── 6. Switch config to mock ─────────────────────────────────────────────
Write-Step "5/6" "Switch config.ini to mock"
$mockBase = "http://127.0.0.1:$Port"
$configText = Get-Content $configPath -Raw -Encoding UTF8
# Find the active (non-commented) apiBase line
$configLines = Get-Content $configPath -Encoding UTF8
$activeIdx = -1
$currentBase = ""
for ($i = 0; $i -lt $configLines.Count; $i++) {
if ($configLines[$i] -match "^\s*apiBase\s*=\s*(.+)") {
$activeIdx = $i
$currentBase = $Matches[1].Trim()
break
}
}
if ($activeIdx -eq -1) {
Exit-WithError "apiBase not found in config.ini"
}
if ($currentBase -eq $mockBase) {
Write-Ok "apiBase already points to mock: $mockBase"
} else {
$backupPath = "$configPath.prod_backup"
if (-not (Test-Path $backupPath)) {
Copy-Item $configPath $backupPath
Write-Host " Backup saved: $backupPath" -ForegroundColor Gray
}
# Comment out old line, insert mock line after it
$configLines[$activeIdx] = "#$($configLines[$activeIdx])"
$before = $configLines[0..$activeIdx]
$after = if ($activeIdx + 1 -lt $configLines.Count) { $configLines[($activeIdx + 1)..($configLines.Count - 1)] } else { @() }
$configLines = $before + @("apiBase=$mockBase") + $after
$newContent = ($configLines -join "`r`n") + "`r`n"
[System.IO.File]::WriteAllText($configPath, $newContent, [System.Text.UTF8Encoding]::new($false))
Write-Ok "apiBase switched: $currentBase -> $mockBase"
}
if ($GenerateOnly) {
Write-Host "`n=== Done (generate only) ===" -ForegroundColor Green
Write-Host "To start server: $pythonExe mock_server.py --port $Port" -ForegroundColor Gray
exit 0
}
# ─── 7. Start mock server ─────────────────────────────────────────────────
Write-Step "6/6" "Start mock server"
# Kill old process on this port if any
$portCheck = netstat -ano 2>$null | Select-String ":${Port}\s.*LISTENING"
if ($portCheck) {
$oldPid = ($portCheck -split '\s+')[-1]
if ($oldPid -and $oldPid -ne "0") {
Write-Warn "Port $Port occupied by PID $oldPid - killing..."
taskkill /PID $oldPid /F 2>$null | Out-Null
Start-Sleep -Seconds 1
Write-Ok "Old process killed"
}
}
$mockScript = Join-Path $scriptDir "mock_server.py"
# ─── Summary banner with DB data ──────────────────────────────────────────
$poolDirArg = Join-Path $scriptDir "scenarios\pool"
$bannerLines = & $pythonExe $dbHelperPy $dbPath "summary" $poolDirArg 2>&1
Write-Host ""
Write-Host "================================================================" -ForegroundColor Cyan
Write-Host " ARC Mock Server - Summary " -ForegroundColor Cyan
Write-Host "================================================================" -ForegroundColor Cyan
foreach ($line in $bannerLines) {
$l = $line.ToString().Trim()
if ($l -match "^DEVICES\|(\d+)") {
Write-Host ""
Write-Host " Devices ($($Matches[1])):" -ForegroundColor White
Write-Host " ----------------------------------------------------------" -ForegroundColor DarkGray
Write-Host (" {0,-30} {1,-20} {2}" -f "Name", "ADB Serial", "API ID") -ForegroundColor DarkGray
}
elseif ($l -match "^PROFILES\|(\d+)") {
Write-Host ""
Write-Host " Bank profiles ($($Matches[1])):" -ForegroundColor White
Write-Host " ----------------------------------------------------------" -ForegroundColor DarkGray
Write-Host (" {0,-25} {1,-20} {2,-20} {3}" -f "Device", "Bank", "Name", "Phone") -ForegroundColor DarkGray
}
elseif ($l -match "^MATERIALS\|(\d+)") {
Write-Host ""
Write-Host " Materials ($($Matches[1])):" -ForegroundColor White
Write-Host " ----------------------------------------------------------" -ForegroundColor DarkGray
Write-Host (" {0,-25} {1,-20} {2,-20} {3,-5} {4}" -f "Device", "Bank", "Card", "Cur.", "Material ID") -ForegroundColor DarkGray
}
elseif ($l -match "^POOL\|(\d+)") {
Write-Host ""
if ([int]$Matches[1] -gt 0) {
Write-Host " Transaction pool: $($Matches[1]) tasks" -ForegroundColor Green
} else {
Write-Host " Transaction pool: empty (only FETCH_PROFILE at bootstrap)" -ForegroundColor Yellow
}
}
elseif ($l -match "^\s*DEV\|([^|]+)\|([^|]+)\|(.+)") {
Write-Host (" {0,-30} {1,-20} {2}" -f $Matches[1], $Matches[2], $Matches[3]) -ForegroundColor Gray
}
elseif ($l -match "^\s*PRF\|([^|]+)\|([^|]+)\|([^|]+)\|(.+)") {
Write-Host (" {0,-25} {1,-20} {2,-20} {3}" -f $Matches[1], $Matches[2], $Matches[3], $Matches[4]) -ForegroundColor Gray
}
elseif ($l -match "^\s*MAT\|([^|]+)\|([^|]+)\|([^|]+)\|([^|]+)\|(.+)") {
Write-Host (" {0,-25} {1,-20} {2,-20} {3,-5} {4}" -f $Matches[1], $Matches[2], $Matches[3], $Matches[4], $Matches[5]) -ForegroundColor Gray
}
}
Write-Host ""
Write-Host " ==========================================================" -ForegroundColor Cyan
Write-Host ""
Write-Host " Mock server: " -NoNewline -ForegroundColor White
Write-Host "http://127.0.0.1:$Port" -ForegroundColor Green
Write-Host " HTML report: " -NoNewline -ForegroundColor White
Write-Host "http://127.0.0.1:${Port}/_admin/report/html" -ForegroundColor Green
Write-Host " State: " -NoNewline -ForegroundColor White
Write-Host "http://127.0.0.1:${Port}/_admin/state" -ForegroundColor Green
Write-Host ""
Write-Host " FT/material: $FtPerMaterial" -ForegroundColor Gray
Write-Host " Config: $configPath" -ForegroundColor Gray
Write-Host ""
Write-Host " Ctrl+C to stop the server" -ForegroundColor Yellow
Write-Host " After testing restore: apiBase=http://93.183.75.134:8005" -ForegroundColor Yellow
Write-Host ""
# Cleanup temp helper
Remove-Item $dbHelperPy -ErrorAction SilentlyContinue
# Open report in browser
Start-Process "http://127.0.0.1:${Port}/_admin/report/html"
# Launch server (blocks until Ctrl+C)
$serverArgs = @("--host", "127.0.0.1", "--port", $Port, "--ft-per-material", $FtPerMaterial)
if ($Banks) {
$serverArgs += "--banks"
$serverArgs += ($Banks -split '[,\s]+')
}
if ($Types) {
$serverArgs += "--types"
$serverArgs += ($Types -split '[,\s]+')
}
if ($Debug) {
$serverArgs += "--debug"
}
& $pythonExe $mockScript @serverArgs