132 lines
7.8 KiB
PowerShell
132 lines
7.8 KiB
PowerShell
<#
|
||
场景脚本公用:计时、记下每一步断言,结束时(通过或失败)请闭环观察台生成场景报告。
|
||
报告是观察台截取这段时间窗里的每一跳、状态变化、iXserver 日志,连同断言与部署版本存成的单文件 HTML,
|
||
放在 run\reports\(不进 git),列表在 http://127.0.0.1:<Ports.Console>/reports/。
|
||
|
||
用法(在场景脚本里点源):
|
||
. (Join-Path $PSScriptRoot '_lib.ps1')
|
||
Start-LoopRun 'S1' 'IG 文本 → 第三方 echo → IG'
|
||
Add-LoopStep 'iXserver 收 webhook' $true 'HTTP 200'
|
||
Set-LoopTicket 1973133303
|
||
Save-LoopReport -Passed $true # 或 Save-LoopReport -Passed $false -Reason '…'
|
||
脚本带 -NoReport 时设 $LoopNoReport = $true 就只断言不出报告(省掉等日志落盘的几秒)。
|
||
#>
|
||
|
||
$LoopCfg = Import-PowerShellDataFile (Join-Path $PSScriptRoot '..\config\loop.psd1')
|
||
$script:LoopRun = $null
|
||
|
||
function Start-LoopRun([string]$Scenario, [string]$Title = '') {
|
||
$script:LoopRun = [ordered]@{
|
||
scenario = $Scenario
|
||
title = $Title
|
||
startedAt = (Get-Date).ToUniversalTime().ToString('o')
|
||
steps = New-Object System.Collections.ArrayList
|
||
ticketId = ''
|
||
}
|
||
}
|
||
|
||
function Add-LoopStep([string]$Name, [bool]$Ok, [string]$Detail = '') {
|
||
if (-not $script:LoopRun) { return }
|
||
[void]$script:LoopRun.steps.Add([ordered]@{ name = $Name; ok = $Ok; detail = $Detail })
|
||
}
|
||
|
||
function Set-LoopTicket($TicketId) {
|
||
if ($script:LoopRun -and $TicketId) { $script:LoopRun.ticketId = [string]$TicketId }
|
||
}
|
||
|
||
function Save-LoopReport([bool]$Passed, [string]$Reason = '') {
|
||
$run = $script:LoopRun
|
||
if (-not $run -or $LoopNoReport) { return }
|
||
$run.endedAt = (Get-Date).ToUniversalTime().ToString('o')
|
||
$run.passed = $Passed
|
||
$run.reason = $Reason
|
||
$console = "http://127.0.0.1:$($LoopCfg.Ports.Console)"
|
||
try {
|
||
$body = [Text.Encoding]::UTF8.GetBytes(($run | ConvertTo-Json -Depth 6 -Compress))
|
||
# 观察台要等 iXserver 日志落盘、库轮询跟上才截取,这一步会停几秒
|
||
$r = Invoke-RestMethod "$console/_api/report" -Method Post -ContentType 'application/json; charset=utf-8' -Body $body -TimeoutSec 60
|
||
Write-Host "报告:$($r.file)($($r.hops) 跳 / $($r.logs) 条日志;观察台里打开 $console$($r.url))"
|
||
}
|
||
catch {
|
||
# 报告是附带的:观察台没在跑不影响场景本身的结果
|
||
Write-Host "没有生成报告(观察台没在跑?.\loop.ps1 up -Only console):$($_.Exception.Message)" -ForegroundColor Yellow
|
||
}
|
||
}
|
||
|
||
# ============================================================================ 条款同意
|
||
|
||
# 以这位用户身份发一条入站;iXserver 没收下返回 $null
|
||
function Send-LoopIg([string]$Igsid, [hashtable]$Body) {
|
||
$Body.igsid = $Igsid
|
||
$bytes = [Text.Encoding]::UTF8.GetBytes(($Body | ConvertTo-Json -Compress))
|
||
try { $r = Invoke-RestMethod "http://127.0.0.1:$($LoopCfg.Ports.IgMock)/_api/send" -Method Post -ContentType 'application/json; charset=utf-8' -Body $bytes }
|
||
catch { return $null } # IG 模拟投递失败回 502
|
||
if ($r.event -and $r.event.response.status -eq 200) { return $r.event }
|
||
return $null
|
||
}
|
||
|
||
# 这位用户在某个入站事件之后收到的消息(IG 模拟的事件 id 全局递增)
|
||
function Get-LoopOutbound([string]$Igsid, [int]$AfterId) {
|
||
@((Invoke-RestMethod "http://127.0.0.1:$($LoopCfg.Ports.IgMock)/_api/state").events |
|
||
Where-Object { $_.kind -eq 'outbound' -and $_.igsid -eq $Igsid -and $_.id -gt $AfterId } | Sort-Object id)
|
||
}
|
||
|
||
function Wait-LoopOutbound([string]$Igsid, [int]$AfterId, [scriptblock]$Match, [int]$TimeoutSec) {
|
||
$deadline = (Get-Date).AddSeconds($TimeoutSec)
|
||
while ((Get-Date) -lt $deadline) {
|
||
$hit = @(Get-LoopOutbound $Igsid $AfterId | Where-Object -FilterScript $Match) | Select-Object -First 1
|
||
if ($hit) { return $hit }
|
||
Start-Sleep -Milliseconds 300
|
||
}
|
||
return $null
|
||
}
|
||
|
||
# 一条出站消息上用户能点的:按钮模板 / 卡片的 postback 按钮,或 quick reply
|
||
function Get-LoopChoices($Outbound) {
|
||
$m = $Outbound.body.message
|
||
if ($m.attachment -and $m.attachment.type -eq 'template') {
|
||
$p = $m.attachment.payload
|
||
$buttons = if ($p.buttons) { @($p.buttons) } else { @($p.elements | ForEach-Object { $_.buttons }) }
|
||
return @($buttons | Where-Object { $_ -and $_.type -eq 'postback' } | ForEach-Object { @{ kind = 'postback'; title = $_.title; payload = $_.payload } })
|
||
}
|
||
return @($m.quick_replies | Where-Object { $_ } | ForEach-Object { @{ kind = 'quick_reply'; title = $_.title; payload = $_.payload } })
|
||
}
|
||
|
||
<#
|
||
epro_ig_demo 常开条款流程(db\ixserver\loop_patch.sql 第 3 节):新 IG 用户的第一句话被条款拦下,
|
||
不进第三方也不进人工对话。场景先走完「发一句 → 收条款 → 同意 → 选语言 → 收问候」,再测自己的链路。
|
||
条款里有 Agree 按钮就照按钮的形态点(闭环 ABP 已补上,db\abp\loop_patch.sql 第 3 节);
|
||
没有时(库是 staging 原样、没打那一节)退回发裸 payload 的 postback(InstagramChatChannel.TnCFlow.IsTnCPayload 放行)。
|
||
每步记 Add-LoopStep。成功返回 $null,失败返回原因——收尾(下班、打印记录)各场景不同,交给调用方的 Fail:
|
||
$err = Complete-LoopTnC $igsid $TimeoutSec; if ($err) { Fail $err }
|
||
#>
|
||
# 默认选 English:没有条款流程时端用户语言就是英文,S5 的问句按钮(-YesTitle Yes)跟着模板语言走
|
||
function Complete-LoopTnC([string]$Igsid, [int]$TimeoutSec = 60, [string]$Language = 'en-us') {
|
||
$text = 'loop-tnc ' + [guid]::NewGuid().ToString('N').Substring(0, 6)
|
||
$e = Send-LoopIg $Igsid @{ kind = 'text'; text = $text }
|
||
if (-not $e) { return "条款前的第一句「$text」没送进 iXserver" }
|
||
# 条款第一条带 4 个外链,标题里认得出来
|
||
$tnc = Wait-LoopOutbound $Igsid $e.id { $_.summary -like '*Terms and Conditions*' } $TimeoutSec
|
||
if (-not $tnc) { return "$TimeoutSec 秒内没收到条款(T_and_C_Setting 没开?见 db\ixserver\loop_patch.sql 第 3 节;或 ABP 模板的 agreementMessages 为空)" }
|
||
Add-LoopStep '新用户第一句话被条款拦下' $true "$Igsid「$text」→ $($tnc.summary)"
|
||
|
||
Start-Sleep -Milliseconds 1500 # 条款组可能不止一条,等它发完再找 Agree 按钮
|
||
$agree = @(Get-LoopOutbound $Igsid $e.id | ForEach-Object { Get-LoopChoices $_ } | Where-Object { $_.payload -eq 'Agree' }) | Select-Object -First 1
|
||
$how = if ($agree) { "点模板里的「$($agree.title)」($($agree.kind))" } else { '条款里没有 Agree 按钮(ABP 没打 loop_patch 第 3 节?),发 postback Agree' }
|
||
if (-not $agree) { $agree = @{ kind = 'postback'; title = 'Agree'; payload = 'Agree' } }
|
||
$e = Send-LoopIg $Igsid @{ kind = $agree.kind; title = $agree.title; payload = $agree.payload; text = $agree.title }
|
||
if (-not $e) { return '同意没送进 iXserver' }
|
||
$switchPayload = "switch $Language"
|
||
$menu = Wait-LoopOutbound $Igsid $e.id { @(Get-LoopChoices $_ | Where-Object { $_.payload -eq $switchPayload }).Count } $TimeoutSec
|
||
if (-not $menu) { return "$TimeoutSec 秒内同意之后没收到带「$switchPayload」的选语言按钮" }
|
||
Add-LoopStep '同意条款,收到选语言' $true $how
|
||
|
||
$choice = @(Get-LoopChoices $menu | Where-Object { $_.payload -eq $switchPayload })[0]
|
||
$e = Send-LoopIg $Igsid @{ kind = $choice.kind; title = $choice.title; payload = $choice.payload; text = $choice.title }
|
||
if (-not $e) { return '选语言没送进 iXserver' }
|
||
$greeting = Wait-LoopOutbound $Igsid $e.id { $true } $TimeoutSec
|
||
if (-not $greeting) { return "$TimeoutSec 秒内选完语言没收到问候(条款模板第四组)" }
|
||
Add-LoopStep "选「$($choice.title)」,收到问候" $true $greeting.summary
|
||
return $null
|
||
}
|