[CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$LocalApk, [Parameter(Mandatory = $true)] [string]$FinalApk, [string]$AndroidSerial = 'emulator-5554' ) $ErrorActionPreference = 'Stop' $root = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path $maestro = Join-Path $env:USERPROFILE '.maestro\bin\maestro.bat' $localApkPath = (Resolve-Path $LocalApk).Path $finalApkPath = (Resolve-Path $FinalApk).Path $canonicalBuildOutput = Join-Path $root 'apps\mobile-rn\android\app\build\outputs\apk\e2e\app-e2e.apk' $externalProviderApk = Join-Path $root 'apps\mobile-rn\android\app\build\outputs\apk\androidTest\debug\app-debug-androidTest.apk' if (-not (Test-Path -LiteralPath $externalProviderApk -PathType Leaf)) { throw 'The external provider test APK is unavailable.' } function Invoke-Adb { param([Parameter(Position = 0, ValueFromRemainingArguments = $true)][string[]]$Arguments) & adb -s $AndroidSerial @Arguments if ($LASTEXITCODE -ne 0) { throw "adb failed: $($Arguments -join ' ')" } } function Get-LocalSupabaseConfiguration { $lines = & supabase status --workdir (Join-Path $root 'server') -o env 2>$null $values = @{} foreach ($line in $lines) { if ($line -match '^([A-Z_]+)="(.*)"$') { $values[$matches[1]] = $matches[2] } } if ( $values.API_URL -ne 'http://127.0.0.1:55321' -or $values.SECRET_KEY -notmatch '^sb_secret_[A-Za-z0-9_-]{20,}$' ) { throw 'The disposable local Supabase boundary is unavailable.' } return $values } $config = Get-LocalSupabaseConfiguration $baseUrl = [string]$config.API_URL $serviceSecret = [string]$config.SECRET_KEY $headers = @{ apikey = $serviceSecret Authorization = "Bearer $serviceSecret" 'Content-Type' = 'application/json' } $nonce = [guid]::NewGuid().ToString('N') $testDomain = @('example', 'invalid') -join '.' $email = 'incoming-share-{0}@{1}' -f $nonce, $testDomain $credentialPrefix = 'D3roShare' $password = $credentialPrefix + $nonce + 'A7' $deviceFileName = '지연된 공유 음성.wav' $userId = $null $primaryError = $null try { Write-Output 'stage=fixture-create' $created = Invoke-RestMethod -Method Post -Uri "$baseUrl/auth/v1/admin/users" -Headers $headers -Body (@{ email = $email password = $password email_confirm = $true user_metadata = @{ name = 'Incoming Share E2E'; locale = 'en' } } | ConvertTo-Json -Depth 4) $userId = [string]$created.id if ($userId -notmatch '^[0-9a-f-]{36}$') { throw 'Fixture user ID is invalid.' } $profile = @() for ($attempt = 1; $attempt -le 30; $attempt += 1) { $profile = @(Invoke-RestMethod -Method Get -Uri "$baseUrl/rest/v1/profiles?id=eq.$userId&select=id" -Headers $headers) if ($profile.Count -eq 1) { break } Start-Sleep -Milliseconds 100 } if ($profile.Count -ne 1) { throw 'Profile trigger did not finish.' } $patchHeaders = $headers.Clone() $patchHeaders.Prefer = 'return=minimal' Invoke-RestMethod -Method Patch -Uri "$baseUrl/rest/v1/profiles?id=eq.$userId" -Headers $patchHeaders -Body (@{ tier = 'pro'; locale = 'en' } | ConvertTo-Json) | Out-Null Invoke-RestMethod -Method Patch -Uri "$baseUrl/rest/v1/subscriptions?user_id=eq.$userId" -Headers $patchHeaders -Body (@{ tier = 'pro'; status = 'active'; provider = 'admin'; payment_provider = 'none'; overage_credits = 0 } | ConvertTo-Json) | Out-Null $settingsHeaders = $headers.Clone() $settingsHeaders.Prefer = 'resolution=merge-duplicates,return=minimal' Invoke-RestMethod -Method Post -Uri "$baseUrl/rest/v1/user_settings" -Headers $settingsHeaders -Body (@{ user_id = $userId onboarding_version = 1 locale = 'en' } | ConvertTo-Json) | Out-Null Write-Output 'stage=install-local-e2e' & adb -s $AndroidSerial uninstall com.d3ro.voice.test 2>$null | Out-Null & adb -s $AndroidSerial uninstall com.d3ro.voice 2>$null | Out-Null Invoke-Adb -Arguments @('install', '--no-streaming', $localApkPath) | Out-Null Invoke-Adb -Arguments @('install', '-t', '--no-streaming', $externalProviderApk) | Out-Null $env:MAESTRO_ACTOR_EMAIL = $email $env:MAESTRO_ACTOR_PASSWORD = $password $env:MAESTRO_SHARED_FILE_NAME = $deviceFileName Write-Output 'stage=login' & $maestro test (Join-Path $root 'apps\mobile-rn\.maestro\incoming-share-local-login.yaml') ` --format JUNIT ` --output (Join-Path $root 'apps\mobile-rn\.maestro\incoming-share-local-login.junit.xml') ` --test-output-dir (Join-Path $root 'apps\mobile-rn\.maestro-output\incoming-share-local\login') if ($LASTEXITCODE -ne 0) { throw 'Incoming share login flow failed.' } Write-Output 'stage=external-provider-share' $uri = 'content://com.d3ro.voice.incoming-media-test/slow-audio' $intent = Invoke-Adb -Arguments @( 'shell', 'am', 'start', '-W', '-a', 'android.intent.action.SEND', '-t', 'audio/wav', '--eu', 'android.intent.extra.STREAM', $uri, '--grant-read-uri-permission', '-n', 'com.d3ro.voice/.MainActivity' ) if (($intent -join "`n") -notmatch 'Status: ok') { throw 'ACTION_SEND did not resolve to MainActivity.' } Write-Output 'stage=queue-ui-check' & $maestro test (Join-Path $root 'apps\mobile-rn\.maestro\incoming-share-local-check.yaml') ` --format JUNIT ` --output (Join-Path $root 'apps\mobile-rn\.maestro\incoming-share-local-check.junit.xml') ` --test-output-dir (Join-Path $root 'apps\mobile-rn\.maestro-output\incoming-share-local\check') if ($LASTEXITCODE -ne 0) { throw 'Incoming share queue flow failed.' } Write-Output 'incoming_share_external_e2e=PASS' } catch { $primaryError = $_ Write-Output "incoming_share_external_e2e=FAIL:$($_.Exception.Message)" } finally { Remove-Item Env:MAESTRO_ACTOR_EMAIL, Env:MAESTRO_ACTOR_PASSWORD, Env:MAESTRO_SHARED_FILE_NAME -ErrorAction SilentlyContinue if ($null -ne $userId) { Invoke-RestMethod -Method Delete -Uri "$baseUrl/auth/v1/admin/users/$userId" -Headers $headers | Out-Null } Copy-Item -LiteralPath $finalApkPath -Destination $canonicalBuildOutput -Force & adb -s $AndroidSerial uninstall com.d3ro.voice.test 2>$null | Out-Null & adb -s $AndroidSerial uninstall com.d3ro.voice 2>$null | Out-Null Invoke-Adb -Arguments @('install', '--no-streaming', $finalApkPath) | Out-Null $residual = if ($null -eq $userId) { @() } else { @(Invoke-RestMethod -Method Get -Uri "$baseUrl/rest/v1/profiles?id=eq.$userId&select=id" -Headers $headers) } Write-Output "fixture_residual_profiles=$($residual.Count)" } if ($null -ne $primaryError) { throw $primaryError }