155 lines
6.2 KiB
PowerShell
155 lines
6.2 KiB
PowerShell
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
|
|
}
|
|
}
|