iXserverLoopEnv/scenarios/_lib.ps1

132 lines
7.8 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<#
场景脚本公用:计时、记下每一步断言,结束时(通过或失败)请闭环观察台生成场景报告。
报告是观察台截取这段时间窗里的每一跳、状态变化、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
}