param( [string]$SupabaseExe = 'supabase', [string]$ProjectUrl = 'http://127.0.0.1:55321' ) $ErrorActionPreference = 'Stop' $supabaseProjectDirectory = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) $previousErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Continue' $statusEnv = & $SupabaseExe status --workdir $supabaseProjectDirectory -o env 2>$null $statusExitCode = $LASTEXITCODE $ErrorActionPreference = $previousErrorActionPreference if ($statusExitCode -ne 0) { throw 'D3RO local Supabase stack is not running.' } function Get-StatusValue { param([string]$Name) $line = $statusEnv | Where-Object { $_ -like ($Name + '=*') } if (-not $line) { throw "Supabase status did not provide $Name" } return (($line -split '=', 2)[1]).Trim('"') } $anonKey = Get-StatusValue 'ANON_KEY' $serviceRoleKey = Get-StatusValue 'SERVICE_ROLE_KEY' $testPassword = 'D3ro-Content-Report-E2E-2026!' $createdUserId = $null $createdReportId = $null $passCount = 0 function Assert-Equal { param([string]$Name, [object]$Actual, [object]$Expected) if ($Actual -ne $Expected) { throw "FAIL $Name expected=[$Expected] actual=[$Actual]" } $script:passCount += 1 Write-Host "PASS $Name" } function Assert-True { param([string]$Name, [bool]$Condition) if (-not $Condition) { throw "FAIL $Name" } $script:passCount += 1 Write-Host "PASS $Name" } function Invoke-JsonRequest { param( [ValidateSet('Get', 'Post', 'Delete')][string]$Method, [string]$Path, [string]$ApiKey, [string]$Bearer, [object]$Body, [hashtable]$AdditionalHeaders = @{}, [string]$ContentType = 'application/json' ) $headers = @{ apikey = $ApiKey; Authorization = "Bearer $Bearer" } foreach ($entry in $AdditionalHeaders.GetEnumerator()) { $headers[$entry.Key] = $entry.Value } $parameters = @{ Uri = $ProjectUrl + $Path Method = $Method Headers = $headers TimeoutSec = 30 SkipHttpErrorCheck = $true } if ($null -ne $Body) { $parameters.ContentType = $ContentType $parameters.Body = $Body | ConvertTo-Json -Depth 8 -Compress } return Invoke-WebRequest @parameters } try { $email = "content-report-$([guid]::NewGuid().ToString('N'))@example.test" $signup = Invoke-JsonRequest -Method Post -Path '/auth/v1/signup' ` -ApiKey $anonKey -Bearer $anonKey -Body @{ email = $email; password = $testPassword } Assert-Equal 'signup-status' ([int]$signup.StatusCode) 200 $session = $signup.Content | ConvertFrom-Json $createdUserId = [string]$session.user.id $accessToken = [string]$session.access_token $unauthenticated = Invoke-JsonRequest -Method Post -Path '/functions/v1/content-report' ` -ApiKey $anonKey -Bearer $anonKey -Body @{} Assert-Equal 'unauthenticated-denied' ([int]$unauthenticated.StatusCode) 401 Assert-Equal 'unauthenticated-no-store' ([string]$unauthenticated.Headers['Cache-Control']) 'no-store' $wrongContentType = Invoke-JsonRequest -Method Post -Path '/functions/v1/content-report' ` -ApiKey $anonKey -Bearer $accessToken -Body @{} -ContentType 'application/jsonp' ` -AdditionalHeaders @{ 'Idempotency-Key' = [guid]::NewGuid().ToString() } Assert-Equal 'wrong-content-type-denied' ([int]$wrongContentType.StatusCode) 415 $receiptResponse = Invoke-JsonRequest -Method Post ` -Path '/rest/v1/rpc/issue_content_generation_receipt_v1' ` -ApiKey $serviceRoleKey -Bearer $serviceRoleKey ` -Body @{ p_actor_id = $createdUserId p_purpose = 'talk_response' p_model = 'edge-integration-fixture' } Assert-Equal 'service-receipt-status' ([int]$receiptResponse.StatusCode) 200 $generationId = [string](($receiptResponse.Content | ConvertFrom-Json).generationId) Assert-True 'service-receipt-id' (-not [string]::IsNullOrWhiteSpace($generationId)) $idempotencyKey = [guid]::NewGuid().ToString() $payload = @{ kind = 'ai_output' source = @{ type = 'talk_response'; generationId = $generationId } reason = 'privacy' comment = 'HTTP integration review' snapshot = 'Reporter-selected generated response evidence' } $first = Invoke-JsonRequest -Method Post -Path '/functions/v1/content-report' ` -ApiKey $anonKey -Bearer $accessToken -Body $payload ` -AdditionalHeaders @{ 'Idempotency-Key' = $idempotencyKey } Assert-Equal 'first-submit-status' ([int]$first.StatusCode) 201 Assert-Equal 'first-submit-no-store' ([string]$first.Headers['Cache-Control']) 'no-store' $firstBody = $first.Content | ConvertFrom-Json $createdReportId = [string]$firstBody.reportId Assert-Equal 'first-submit-idempotent' ([bool]$firstBody.idempotent) $false $replay = Invoke-JsonRequest -Method Post -Path '/functions/v1/content-report' ` -ApiKey $anonKey -Bearer $accessToken -Body $payload ` -AdditionalHeaders @{ 'Idempotency-Key' = $idempotencyKey } $replayBody = $replay.Content | ConvertFrom-Json Assert-Equal 'replay-status' ([int]$replay.StatusCode) 200 Assert-Equal 'replay-idempotent' ([bool]$replayBody.idempotent) $true Assert-Equal 'replay-same-report' ([string]$replayBody.reportId) $createdReportId $changedPayload = $payload.Clone() $changedPayload.snapshot = 'Changed evidence' $conflict = Invoke-JsonRequest -Method Post -Path '/functions/v1/content-report' ` -ApiKey $anonKey -Bearer $accessToken -Body $changedPayload ` -AdditionalHeaders @{ 'Idempotency-Key' = $idempotencyKey } Assert-Equal 'idempotency-conflict-status' ([int]$conflict.StatusCode) 409 Assert-True 'idempotency-conflict-code' $conflict.Content.Contains('idempotency_conflict') $directRead = Invoke-JsonRequest -Method Get -Path '/rest/v1/content_reports?select=id' ` -ApiKey $anonKey -Bearer $accessToken -Body $null Assert-Equal 'direct-table-read-denied' ([int]$directRead.StatusCode) 403 Write-Host "content-reporting edge integration: $passCount assertions passed" } finally { if (-not [string]::IsNullOrWhiteSpace($createdReportId)) { $null = Invoke-JsonRequest -Method Delete ` -Path "/rest/v1/content_reports?id=eq.$createdReportId" ` -ApiKey $serviceRoleKey -Bearer $serviceRoleKey -Body $null } if (-not [string]::IsNullOrWhiteSpace($createdUserId)) { $null = Invoke-JsonRequest -Method Delete ` -Path "/auth/v1/admin/users/$createdUserId" ` -ApiKey $serviceRoleKey -Bearer $serviceRoleKey -Body $null } }