<# .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