iXserverLoopEnv/scenarios/s8-human-chain.ps1

104 lines
5.5 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.

<#
S8:人工链路本身(不经第三方 chatbot)。
SCRM 开工(SetSetting IsEnable=false + UpdateCSAvailable 3)→ 客服上线(UserReg + 额度池)
→ 新 IG 用户发消息 → 人工工单,SCRM 在 WebSocket 上收到推送 → 指派 → 打开(验证信息 + 已读)
→ 以客服身份回复 → IG 用户收到 → 结束工单 → SCRM 下班(切回机器人、在线 0),不影响 S1。
前提:.\loop.ps1 up 各角色都健康。每次用新的 IGSID,保证建出新工单。
通过退出 0,失败退出 1 并打印记录;两种情况都在 run\reports\ 生成场景报告(观察台在跑时)。
用法:.\scenarios\s8-human-chain.ps1 [-TimeoutSec 60] [-KeepShift] [-NoReport]
#>
param([int]$TimeoutSec = 60, [switch]$KeepShift, [switch]$NoReport)
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
$LoopNoReport = [bool]$NoReport
. (Join-Path $PSScriptRoot '_lib.ps1')
$Cfg = $LoopCfg
$ig = "http://127.0.0.1:$($Cfg.Ports.IgMock)"
$sc = "http://127.0.0.1:$($Cfg.Ports.ScrmMock)"
function Post([string]$url, $body) {
$bytes = [Text.Encoding]::UTF8.GetBytes(($body | ConvertTo-Json -Compress -Depth 6))
try { return Invoke-RestMethod $url -Method Post -ContentType 'application/json; charset=utf-8' -Body $bytes }
catch {
# 动作失败时模拟方回 502,正文里带着每一步的记录
$resp = $_.Exception.Response
if (-not $resp) { throw }
$reader = New-Object IO.StreamReader($resp.GetResponseStream(), [Text.Encoding]::UTF8)
return ($reader.ReadToEnd() | ConvertFrom-Json)
}
}
function Fail([string]$why) {
Write-Host "S8 失败:$why" -ForegroundColor Red
Write-Host '--- SCRM 模拟记录(最近 15 条)'
(Invoke-RestMethod "$sc/_api/state").events | Select-Object -First 15 | ForEach-Object { ($_ | ConvertTo-Json -Depth 8 -Compress) } | Out-Host
Write-Host '--- IG 模拟记录'
(Invoke-RestMethod "$ig/_api/state").events | ForEach-Object { "{0} {1} {2}" -f $_.kind, $_.igsid, $_.summary } | Out-Host
Add-LoopStep $why $false
if (-not $KeepShift) { Post "$sc/_api/shift" @{ on = $false } | Out-Null }
Save-LoopReport -Passed $false -Reason $why
exit 1
}
function Step([string]$name, [string]$action, $body) {
$r = Post "$sc/_api/$action" $body
if (-not $r.ok) { Fail "$name($action)未成功:$($r.error) $((@($r.calls) | ForEach-Object { $_.api + ' → ' + ($_.response.body.msg) }) -join ';')" }
Add-LoopStep $name $true ((@($r.calls) | ForEach-Object { $_.api }) -join ' + ')
return $r
}
function Wait-For([string]$what, [scriptblock]$probe) {
$deadline = (Get-Date).AddSeconds($TimeoutSec)
while ((Get-Date) -lt $deadline) {
$hit = & $probe
if ($hit) { return $hit }
Start-Sleep -Milliseconds 500
}
Fail "$TimeoutSec 秒内没等到:$what"
}
Start-LoopRun 'S8' 'SCRM 人工链路'
$ws = (Invoke-RestMethod "$sc/_api/state").state.ws
if ($ws.status -ne 'logged-in') { Fail "SCRM 模拟的 WebSocket 没登录($($ws.status) $($ws.lastError))" }
Invoke-RestMethod "$sc/_api/clear" -Method Post | Out-Null
Invoke-RestMethod "$ig/_api/clear" -Method Post | Out-Null
Step 'SCRM 开工(切人工、在线 3)' 'shift' @{ on = $true } | Out-Null
Step '客服上线' 'agent-online' @{} | Out-Null
$igsid = 'loop-s8-' + [guid]::NewGuid().ToString('N').Substring(0, 8)
# 人工状态下条款同样先拦第一句话;同意那一句就建出了人工工单,下面按 IGSID 认的还是这张
$err = Complete-LoopTnC $igsid $TimeoutSec
if ($err) { Fail $err }
$sent = Post "$ig/_api/send" @{ kind = 'text'; igsid = $igsid; text = 'S8 想找人工' }
if ($sent.event.response.status -ne 200) { Fail "iXserver 收 webhook 返回 $($sent.event.response.status)" }
Add-LoopStep '新 IG 用户发消息' $true "$igsid HTTP 200"
# 推送报文没有统一类型字段,按 enduser_openid = IGSID 认出这张新工单
$ticketId = Wait-For 'SCRM 收到这位 IG 用户的工单推送' {
$push = @((Invoke-RestMethod "$sc/_api/state").events | Where-Object { $_.kind -eq 'push' -and (($_.parsed | ConvertTo-Json -Depth 8 -Compress) -match [regex]::Escape($igsid)) }) | Select-Object -First 1
if ($push) { $push.ticketIds | Select-Object -First 1 }
}
Set-LoopTicket $ticketId
Add-LoopStep 'SCRM 收到工单推送' $true "ticket $ticketId"
Step '指派给客服' 'assign' @{ ticket_id = $ticketId } | Out-Null
Step '打开工单(验证信息 + 已读)' 'open' @{ ticket_id = $ticketId } | Out-Null
$replyText = 'S8 客服回复 ' + [guid]::NewGuid().ToString('N').Substring(0, 6)
Step '客服回复' 'reply' @{ ticket_id = $ticketId; text = $replyText } | Out-Null
$out = Wait-For "IG 用户收到「$replyText」" {
@((Invoke-RestMethod "$ig/_api/state").events | Where-Object { $_.kind -eq 'outbound' -and $_.igsid -eq $igsid -and $_.summary -eq $replyText }) | Select-Object -First 1
}
if (@($out.issues).Count) { Fail "出站请求有问题:$($out.issues -join ';')" }
if ($out.response.status -ne 200) { Fail "Graph 应答 $($out.response.status)(IG 模拟档位 $($out.response.mode)),用户实际没收到" }
Add-LoopStep 'IG 用户收到客服回复' $true $replyText
Step '结束工单' 'complete' @{ ticket_id = $ticketId } | Out-Null
if (-not $KeepShift) { Step 'SCRM 下班(切回机器人、在线 0)' 'shift' @{ on = $false } | Out-Null }
Write-Host "S8 通过:$igsid → 人工工单 $ticketId → SCRM 收到推送、指派、回复 → IG 收到「$replyText」→ 已结束" -ForegroundColor Green
Save-LoopReport -Passed $true
exit 0