param( [int]$MockPort = 18080, [int]$AppPort = 18081, [switch]$SkipGoFmt ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" . (Join-Path $PSScriptRoot "dev-tools.ps1") Use-LocalTooling $repoRoot = Get-RepoRoot $tmpRoot = Join-Path (Get-LocalRoot) ("selftest-" + [guid]::NewGuid().ToString("N")) Ensure-Directory -Path $tmpRoot $mockProcess = $null $appProcess = $null $appStdoutTask = $null $appStderrTask = $null function Stop-TrackedProcess { param($Process) if ($null -ne $Process -and -not $Process.HasExited) { Stop-Process -Id $Process.Id -Force $Process.WaitForExit() } } try { if (-not $SkipGoFmt) { Write-Step "gofmt" Invoke-CheckedCommand -FilePath "gofmt" -Arguments @( "-w", (Join-Path $repoRoot "backend\main.go"), (Join-Path $repoRoot "backend\handlers\api.go"), (Join-Path $repoRoot "backend\models\db.go"), (Join-Path $repoRoot "backend\services\cse.go"), (Join-Path $repoRoot "backend\services\cse_test.go"), (Join-Path $repoRoot "backend\services\search_collectors.go"), (Join-Path $repoRoot "backend\services\ranker.go"), (Join-Path $repoRoot "backend\services\gemini.go"), (Join-Path $repoRoot "backend\services\gemini_test.go") ) } Write-Step "python syntax" Invoke-CheckedCommand -FilePath (Resolve-VenvPythonExe) -Arguments @( "-m", "py_compile", (Join-Path $repoRoot "worker\downloader.py"), (Join-Path $repoRoot "scripts\mock_searxng.py") ) Write-Step "go test" Invoke-CheckedCommand -FilePath "go" -Arguments @("test", "./...") Write-Step "frontend syntax" Invoke-CheckedCommand -FilePath "node" -Arguments @("--check", (Join-Path $repoRoot "frontend\app.js")) Write-Step "go build" $binaryPath = Join-Path $tmpRoot "ai-media-hub.exe" Invoke-CheckedCommand -FilePath "go" -Arguments @("build", "-o", $binaryPath, "./backend") Write-Step "start mock searxng" $mockLog = Join-Path $tmpRoot "mock-searxng.stdout.log" $mockErrLog = Join-Path $tmpRoot "mock-searxng.stderr.log" $mockProcess = Start-Process -FilePath (Resolve-VenvPythonExe) ` -ArgumentList @((Join-Path $repoRoot "scripts\mock_searxng.py"), "--port", $MockPort) ` -RedirectStandardOutput $mockLog ` -RedirectStandardError $mockErrLog ` -PassThru ` -WindowStyle Hidden Write-Step "start app" $downloadsDir = Join-Path $tmpRoot "downloads" Ensure-Directory -Path $downloadsDir $appLog = Join-Path $tmpRoot "app.log" $envMap = Get-AppEnvironment ` -WorkspaceRoot $repoRoot ` -DownloadsDir $downloadsDir ` -SqlitePath (Join-Path $tmpRoot "media.db") ` -AppAddr ("127.0.0.1:{0}" -f $AppPort) ` -SearxUrl ("http://127.0.0.1:{0}" -f $MockPort) $appStartInfo = New-Object System.Diagnostics.ProcessStartInfo $appStartInfo.FileName = $binaryPath $appStartInfo.WorkingDirectory = $repoRoot $appStartInfo.UseShellExecute = $false $appStartInfo.RedirectStandardOutput = $true $appStartInfo.RedirectStandardError = $true foreach ($entry in $envMap.GetEnumerator()) { $appStartInfo.Environment[$entry.Key] = [string]$entry.Value } $appProcess = New-Object System.Diagnostics.Process $appProcess.StartInfo = $appStartInfo $null = $appProcess.Start() $appStdoutTask = $appProcess.StandardOutput.ReadToEndAsync() $appStderrTask = $appProcess.StandardError.ReadToEndAsync() $healthUrl = "http://127.0.0.1:$AppPort/healthz" $healthy = $false for ($i = 0; $i -lt 30; $i++) { try { $health = Invoke-RestMethod -Uri $healthUrl -TimeoutSec 2 if ($health.status -eq "ok") { $healthy = $true break } } catch { Start-Sleep -Seconds 1 } } if (-not $healthy) { throw "/healthz 확인 실패" } Write-Step "verify search" $searchPayload = @{ query = "city rain" platforms = @("envato", "artgrid", "google video") } | ConvertTo-Json $search = Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:$AppPort/api/search" -ContentType "application/json" -Body $searchPayload $searchResults = @($search.results) if ($null -eq $search.results -or $searchResults.Count -lt 2) { throw "검색 결과가 너무 적습니다." } if (@($searchResults | Where-Object { -not $_.link }).Count -gt 0) { throw "검색 결과에 link가 비어 있습니다." } Write-Step "verify upload" $sampleFile = Join-Path $tmpRoot "sample.txt" Set-Content -LiteralPath $sampleFile -Value "selftest upload" -Encoding UTF8 $form = @{ file = Get-Item -LiteralPath $sampleFile } $upload = Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:$AppPort/api/upload" -Form $form if (-not $upload.filename) { throw "업로드 응답에 filename이 없습니다." } $uploadedPath = Join-Path $downloadsDir $upload.filename if (-not (Test-Path -LiteralPath $uploadedPath)) { throw "업로드된 파일이 downloads에 존재하지 않습니다." } Write-Step "selftest ok" } finally { Stop-TrackedProcess -Process $appProcess Stop-TrackedProcess -Process $mockProcess if ($appStdoutTask) { $appStdoutTask.Wait() $appStdoutTask.Result | Set-Content -LiteralPath (Join-Path $tmpRoot "app.log") -Encoding UTF8 } if ($appStderrTask) { $appStderrTask.Wait() $appStderrTask.Result | Add-Content -LiteralPath (Join-Path $tmpRoot "app.log") -Encoding UTF8 } }