From 5ede79c00d7447f78d8d077499c0ea2fffc338f7 Mon Sep 17 00:00:00 2001 From: 491034170 <142008960+491034170@users.noreply.github.com> Date: Sat, 6 Jun 2026 18:01:58 +0800 Subject: [PATCH 1/8] Add Windows backend test preflight script --- backend/test-preflight.ps1 | 249 +++++++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 backend/test-preflight.ps1 diff --git a/backend/test-preflight.ps1 b/backend/test-preflight.ps1 new file mode 100644 index 00000000000..f27e6451458 --- /dev/null +++ b/backend/test-preflight.ps1 @@ -0,0 +1,249 @@ +param( + [string]$Python +) + +$ErrorActionPreference = "Stop" + +$RootDir = Split-Path -Parent $MyInvocation.MyCommand.Path +Set-Location $RootDir + +$script:PassCount = 0 +$script:WarnCount = 0 +$script:FailCount = 0 +$script:PythonCommand = @() + +function Write-Ok { + param([string]$Message) + Write-Host " [OK] $Message" -ForegroundColor Green + $script:PassCount += 1 +} + +function Write-Warn { + param([string]$Message) + Write-Host " [WARN] $Message" -ForegroundColor Yellow + $script:WarnCount += 1 +} + +function Write-Bad { + param([string]$Message) + Write-Host " [FAIL] $Message" -ForegroundColor Red + $script:FailCount += 1 +} + +function Set-PythonCommand { + if ($Python) { + $script:PythonCommand = @($Python) + return + } + + if (Get-Command python -ErrorAction SilentlyContinue) { + $script:PythonCommand = @("python") + return + } + + if (Get-Command py -ErrorAction SilentlyContinue) { + $script:PythonCommand = @("py", "-3") + return + } +} + +function Invoke-Python { + param([string[]]$Arguments) + + $exe = $script:PythonCommand[0] + $prefix = @() + if ($script:PythonCommand.Count -gt 1) { + $prefix = $script:PythonCommand[1..($script:PythonCommand.Count - 1)] + } + + $oldErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = "Continue" + try { + & $exe @prefix @Arguments + } finally { + $ErrorActionPreference = $oldErrorActionPreference + } +} + +function Test-PythonImport { + param([string]$ModuleName) + + Invoke-Python @("-c", "import $ModuleName") *> $null + return $LASTEXITCODE -eq 0 +} + +function Get-EnvValue { + param([string]$Name) + return [Environment]::GetEnvironmentVariable($Name) +} + +function Check-Env { + param( + [string]$Name, + [string]$Description + ) + + if (Get-EnvValue $Name) { + Write-Ok "$Name ($Description)" + } else { + Write-Warn "$Name not set ($Description)" + } +} + +Write-Host "Tools:" +Set-PythonCommand + +if ($script:PythonCommand.Count -eq 0) { + Write-Bad "python not found (install Python 3.11 or pass -Python )" +} else { + $pythonVersion = (Invoke-Python @("--version") 2>&1) -join " " + if ($LASTEXITCODE -eq 0) { + if ($pythonVersion -match "Python 3\.11\.") { + Write-Ok $pythonVersion + } else { + Write-Bad "$pythonVersion detected; backend expects Python 3.11" + } + } else { + Write-Bad "python command failed" + } +} + +if ($script:PythonCommand.Count -gt 0) { + $pytestVersion = (Invoke-Python @("-m", "pytest", "--version") 2>&1) -join " " + if ($LASTEXITCODE -eq 0) { + Write-Ok $pytestVersion + } else { + Write-Bad "pytest not installed (pip install pytest)" + } +} + +if (Get-Command black -ErrorAction SilentlyContinue) { + Write-Ok "black (formatter)" +} elseif ($script:PythonCommand.Count -gt 0) { + Invoke-Python @("-m", "black", "--version") *> $null + if ($LASTEXITCODE -eq 0) { + Write-Ok "python -m black (formatter)" + } else { + Write-Warn "black not installed; pre-commit formatting will fail (pip install black)" + } +} else { + Write-Warn "black not checked because python was not found" +} + +Write-Host "" +Write-Host "Python packages:" + +$missingPackages = @() +if ($script:PythonCommand.Count -gt 0) { + foreach ($pkg in @("pydantic", "fastapi", "firebase_admin", "google.cloud.firestore", "redis", "deepgram_sdk", "openpipe")) { + if (Test-PythonImport $pkg) { + Write-Ok $pkg + } else { + $missingPackages += $pkg + Write-Warn "$pkg not importable" + } + } +} + +if ($missingPackages.Count -gt 0) { + Write-Host " -> Run: pip install -r requirements.txt" -ForegroundColor Yellow +} + +Write-Host "" +Write-Host "Env vars (unit tests):" + +if (Get-EnvValue "ENCRYPTION_SECRET") { + Write-Ok "ENCRYPTION_SECRET (set in env)" +} else { + Write-Ok "ENCRYPTION_SECRET (set by test.sh; no action needed)" +} + +Write-Host "" +Write-Host "Env vars (integration - optional):" + +Check-Env "OPENAI_API_KEY" "LLM calls; some integration tests skip without it" +Check-Env "DEEPGRAM_API_KEY" "STT streaming and pre-recorded transcription" +Check-Env "ADMIN_KEY" "admin endpoint tests" +Check-Env "REDIS_DB_HOST" "Redis connection (default: localhost)" +Check-Env "REDIS_DB_PASSWORD" "Redis auth" +Check-Env "GOOGLE_APPLICATION_CREDENTIALS" "Firebase/Firestore integration tests" + +Write-Host "" +Write-Host "Services:" + +$redisCli = Get-Command redis-cli -ErrorAction SilentlyContinue +if ($redisCli) { + $redisHost = Get-EnvValue "REDIS_DB_HOST" + if (-not $redisHost) { + $redisHost = "localhost" + } + $redisPort = Get-EnvValue "REDIS_DB_PORT" + if (-not $redisPort) { + $redisPort = "6379" + } + + $redisArgs = @("-h", $redisHost, "-p", $redisPort) + $redisPassword = Get-EnvValue "REDIS_DB_PASSWORD" + if ($redisPassword) { + $redisArgs += @("-a", $redisPassword) + } + $redisArgs += "ping" + + & $redisCli.Source @redisArgs *> $null + if ($LASTEXITCODE -eq 0) { + Write-Ok "Redis ($redisHost`:$redisPort) connected" + } else { + Write-Warn "Redis ($redisHost`:$redisPort) not reachable (integration tests may fail)" + } +} else { + Write-Warn "redis-cli not installed; cannot check Redis connectivity" +} + +Write-Host "" +Write-Host "Test files:" + +$unitTests = Get-ChildItem -Path "tests/unit" -Filter "test_*.py" -File -ErrorAction SilentlyContinue +if ($unitTests.Count -gt 0) { + Write-Ok "$($unitTests.Count) unit test files found" +} else { + Write-Bad "No unit test files found in tests/unit/" +} + +$missingTests = @() +if (Test-Path "test.sh") { + $testRefs = Get-Content "test.sh" | + Where-Object { $_ -match "^pytest\s+tests/" } | + ForEach-Object { ($_ -replace "^pytest\s+", "") -replace "\s+-v\s*$", "" } + + foreach ($testRef in $testRefs) { + if (-not (Test-Path $testRef)) { + $missingTests += $testRef + } + } + + if ($missingTests.Count -gt 0) { + Write-Bad "test.sh references missing files: $($missingTests -join ', ')" + } else { + Write-Ok "All test.sh references resolve to existing files" + } +} else { + Write-Bad "test.sh not found" +} + +Write-Host "" +Write-Host "----------------------------------------" +$total = $script:PassCount + $script:WarnCount + $script:FailCount +Write-Host " $script:PassCount passed $script:WarnCount warnings $script:FailCount failed ($total checks)" + +if ($script:FailCount -gt 0) { + Write-Host " Fix failures above before running test.sh" -ForegroundColor Red + exit 1 +} + +if ($script:WarnCount -gt 0) { + Write-Host " Warnings are optional; unit tests should still pass" -ForegroundColor Yellow + exit 0 +} + +Write-Host " All clear; ready to run test.sh" -ForegroundColor Green +exit 0 From 0df67a08c4204333129d044d330f20fa92bef732 Mon Sep 17 00:00:00 2001 From: 491034170 <142008960+491034170@users.noreply.github.com> Date: Sat, 6 Jun 2026 18:02:09 +0800 Subject: [PATCH 2/8] Document Windows backend preflight --- backend/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/backend/README.md b/backend/README.md index 31be561b56e..c7e0edbb801 100644 --- a/backend/README.md +++ b/backend/README.md @@ -115,6 +115,24 @@ This README provides a quick setup guide for the Omi backend. For a comprehensiv deactivate ``` +## Running Backend Tests on Windows + +From PowerShell, run the Windows preflight before the backend test suite: + +```powershell +cd backend +powershell -NoProfile -ExecutionPolicy Bypass -File .\test-preflight.ps1 +``` + +If you have multiple Python versions installed, pass the Python 3.11 executable explicitly: + +```powershell +powershell -NoProfile -ExecutionPolicy Bypass -File .\test-preflight.ps1 -Python C:\Path\To\Python311\python.exe +``` + +The preflight checks Python, pytest, formatter availability, key backend imports, optional integration environment +variables, Redis CLI connectivity, and whether every test referenced by `test.sh` exists. + ## Additional Resources - [Full Backend Setup Documentation](https://docs.omi.me/developer/backend/Backend_Setup) From a2696abc479e8d31b22eb8a87d3dc00dcbdc0679 Mon Sep 17 00:00:00 2001 From: 491034170 <142008960+491034170@users.noreply.github.com> Date: Sat, 6 Jun 2026 18:02:18 +0800 Subject: [PATCH 3/8] Mention Windows backend preflight in agent guide --- backend/AGENTS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/AGENTS.md b/backend/AGENTS.md index 4418e891658..6d270fa5780 100644 --- a/backend/AGENTS.md +++ b/backend/AGENTS.md @@ -123,6 +123,8 @@ bash test-preflight.sh # Verify env bash test.sh # Run all tests (CI source of truth) ``` +On Windows, use `powershell -NoProfile -ExecutionPolicy Bypass -File .\test-preflight.ps1` for the preflight check. + **New test files must be added to `test.sh`** or they won't run in CI. Pre-mock heavy deps before importing the module under test. Use `patch.object(target_module, "func")` not string-based `patch("module.func")` — the string form silently patches the wrong reference if the function was already imported. When modules construct objects at import time, use lazy getters to avoid triggering heavy init in tests. From d2efdc9d4d0671d6ba17478c2c207dcb8c2b6c8a Mon Sep 17 00:00:00 2001 From: 491034170 <142008960+491034170@users.noreply.github.com> Date: Sat, 6 Jun 2026 18:05:02 +0800 Subject: [PATCH 4/8] Avoid Redis password argv in Windows preflight --- backend/test-preflight.ps1 | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/backend/test-preflight.ps1 b/backend/test-preflight.ps1 index f27e6451458..30af80447a5 100644 --- a/backend/test-preflight.ps1 +++ b/backend/test-preflight.ps1 @@ -184,12 +184,23 @@ if ($redisCli) { $redisArgs = @("-h", $redisHost, "-p", $redisPort) $redisPassword = Get-EnvValue "REDIS_DB_PASSWORD" + $redisArgs += "ping" + + $previousRedisCliAuth = [Environment]::GetEnvironmentVariable("REDISCLI_AUTH") if ($redisPassword) { - $redisArgs += @("-a", $redisPassword) + $env:REDISCLI_AUTH = $redisPassword + } + + try { + & $redisCli.Source @redisArgs *> $null + } finally { + if ($null -eq $previousRedisCliAuth) { + Remove-Item Env:REDISCLI_AUTH -ErrorAction SilentlyContinue + } else { + $env:REDISCLI_AUTH = $previousRedisCliAuth + } } - $redisArgs += "ping" - & $redisCli.Source @redisArgs *> $null if ($LASTEXITCODE -eq 0) { Write-Ok "Redis ($redisHost`:$redisPort) connected" } else { From 3d942f839a1c22adcc920959e54fdbee94462a03 Mon Sep 17 00:00:00 2001 From: 491034170 <142008960+491034170@users.noreply.github.com> Date: Sat, 6 Jun 2026 18:12:11 +0800 Subject: [PATCH 5/8] Address Windows preflight review feedback --- backend/test-preflight.ps1 | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/backend/test-preflight.ps1 b/backend/test-preflight.ps1 index 30af80447a5..ec513bb1ca1 100644 --- a/backend/test-preflight.ps1 +++ b/backend/test-preflight.ps1 @@ -101,7 +101,7 @@ if ($script:PythonCommand.Count -eq 0) { if ($pythonVersion -match "Python 3\.11\.") { Write-Ok $pythonVersion } else { - Write-Bad "$pythonVersion detected; backend expects Python 3.11" + Write-Warn "$pythonVersion detected; backend expects Python 3.11" } } else { Write-Bad "python command failed" @@ -223,8 +223,12 @@ if ($unitTests.Count -gt 0) { $missingTests = @() if (Test-Path "test.sh") { $testRefs = Get-Content "test.sh" | - Where-Object { $_ -match "^pytest\s+tests/" } | - ForEach-Object { ($_ -replace "^pytest\s+", "") -replace "\s+-v\s*$", "" } + ForEach-Object { + $line = $_.Trim() + if ($line -match "^pytest\s+") { + $line -split "\s+" | Where-Object { $_ -like "tests/*" } + } + } foreach ($testRef in $testRefs) { if (-not (Test-Path $testRef)) { From 3bcced127cea075b92876b5330fde019b7c91a1b Mon Sep 17 00:00:00 2001 From: 491034170 <142008960+491034170@users.noreply.github.com> Date: Sat, 6 Jun 2026 18:45:12 +0800 Subject: [PATCH 6/8] Add Windows backend test runner --- backend/test.ps1 | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 backend/test.ps1 diff --git a/backend/test.ps1 b/backend/test.ps1 new file mode 100644 index 00000000000..934875f10f1 --- /dev/null +++ b/backend/test.ps1 @@ -0,0 +1,59 @@ +param( + [string]$Python = "python", + [switch]$List +) + +$ErrorActionPreference = "Stop" + +$RootDir = Split-Path -Parent $MyInvocation.MyCommand.Path +Set-Location $RootDir + +$env:ENCRYPTION_SECRET = "omi_ZwB2ZNqB2HHpMK6wStk7sTpavJiPTFg7gXUHnc4tFABPU6pZ2c2DKgehtfgi4RZv" +$env:PYTHONUTF8 = "1" + +if (-not (Test-Path "test.sh")) { + Write-Error "test.sh not found" +} + +$pytestCommands = Get-Content "test.sh" | + Where-Object { $_ -match "^pytest\s+tests/unit/" } | + ForEach-Object { $_.Trim() } + +if ($pytestCommands.Count -eq 0) { + Write-Error "No pytest commands found in test.sh" +} + +if ($List) { + $pytestCommands | ForEach-Object { Write-Output $_ } + exit 0 +} + +foreach ($command in $pytestCommands) { + $arguments = $command -split "\s+" + Write-Host "> $Python -m $($arguments -join ' ')" -ForegroundColor Cyan + & $Python -m @arguments + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } +} + +$redisCli = Get-Command redis-cli -ErrorAction SilentlyContinue +if ($redisCli) { + & $redisCli.Source ping *> $null + if ($LASTEXITCODE -eq 0) { + foreach ($integrationTest in @( + "tests/integration/test_fair_use_live.py", + "tests/integration/test_fair_use_api.py" + )) { + Write-Host "> $Python -m pytest $integrationTest -v" -ForegroundColor Cyan + & $Python -m pytest $integrationTest -v + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } + } + } else { + Write-Host "SKIP: fair-use integration tests (Redis not available)" + } +} else { + Write-Host "SKIP: fair-use integration tests (redis-cli not available)" +} From d13ed157e8658204f841ac700315e0be38140bd3 Mon Sep 17 00:00:00 2001 From: 491034170 <142008960+491034170@users.noreply.github.com> Date: Sat, 6 Jun 2026 18:45:27 +0800 Subject: [PATCH 7/8] Document Windows backend test runner --- backend/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/backend/README.md b/backend/README.md index c7e0edbb801..b82cff71fc7 100644 --- a/backend/README.md +++ b/backend/README.md @@ -133,6 +133,16 @@ powershell -NoProfile -ExecutionPolicy Bypass -File .\test-preflight.ps1 -Python The preflight checks Python, pytest, formatter availability, key backend imports, optional integration environment variables, Redis CLI connectivity, and whether every test referenced by `test.sh` exists. +After the preflight passes, run the Windows test wrapper: + +```powershell +powershell -NoProfile -ExecutionPolicy Bypass -File .\test.ps1 +``` + +`test.ps1` reads the pytest commands from `test.sh`, sets `PYTHONUTF8=1` for Windows source reads, and skips the +fair-use integration tests when `redis-cli` is not available. To preview the pytest commands without running them, add +`-List`. + ## Additional Resources - [Full Backend Setup Documentation](https://docs.omi.me/developer/backend/Backend_Setup) From d22cb2a4ce83aca388df15c1c52f444ee8e9c5ce Mon Sep 17 00:00:00 2001 From: 491034170 <142008960+491034170@users.noreply.github.com> Date: Sat, 6 Jun 2026 18:45:37 +0800 Subject: [PATCH 8/8] Mention Windows backend test runner in agent guide --- backend/AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/AGENTS.md b/backend/AGENTS.md index 6d270fa5780..7d6c42d29a1 100644 --- a/backend/AGENTS.md +++ b/backend/AGENTS.md @@ -123,7 +123,7 @@ bash test-preflight.sh # Verify env bash test.sh # Run all tests (CI source of truth) ``` -On Windows, use `powershell -NoProfile -ExecutionPolicy Bypass -File .\test-preflight.ps1` for the preflight check. +On Windows, use `powershell -NoProfile -ExecutionPolicy Bypass -File .\test-preflight.ps1` for the preflight check, then `powershell -NoProfile -ExecutionPolicy Bypass -File .\test.ps1` to run the same pytest list from `test.sh` with `PYTHONUTF8=1`. **New test files must be added to `test.sh`** or they won't run in CI.