# 端到端冒烟测试:真实 HTTP 走一遍主要流程 # 用法: powershell -NoProfile -ExecutionPolicy Bypass -File scripts\smoke.ps1 -Base http://127.0.0.1:18080 param( [string]$Base = 'http://127.0.0.1:18080', [string]$User = 'admin', [string]$Pass = 'daili123' ) $ErrorActionPreference = 'Stop' $script:fail = 0 $session = New-Object Microsoft.PowerShell.Commands.WebRequestSession function Send { param([string]$Method, [string]$Path, $Body) $p = @{ Uri = "$Base$Path" WebSession = $session UseBasicParsing = $true MaximumRedirection = 5 Method = $Method } if ($null -ne $Body) { $p.Body = $Body } try { return Invoke-WebRequest @p } catch [System.Net.WebException] { $resp = $_.Exception.Response if ($null -eq $resp) { throw } $sr = New-Object System.IO.StreamReader($resp.GetResponseStream()) $body = $sr.ReadToEnd() $sr.Close() return [pscustomobject]@{ StatusCode = [int]$resp.StatusCode Content = $body BaseResponse = [pscustomobject]@{ ResponseUri = $resp.ResponseUri } } } } function Check { param([bool]$Cond, [string]$Msg) if ($Cond) { Write-Host (" [ok] " + $Msg) -ForegroundColor Green } else { Write-Host (" [FAIL] " + $Msg) -ForegroundColor Red $script:fail++ } } function Final-Path($resp) { if ($resp.PSObject.Properties.Name -contains 'BaseResponse' -and $resp.BaseResponse -and $resp.BaseResponse.ResponseUri) { return $resp.BaseResponse.ResponseUri.AbsolutePath } return '' } $now = Get-Date $today = $now.ToString('yyyy-MM-dd') $tail = Get-Random -Minimum 1000 -Maximum 9999 Write-Host "`n=== 代打台冒烟测试 @ $Base ($today / 尾号 $tail) ===" -ForegroundColor Cyan # 1. 初始化管理员(或登录) $r = Send 'GET' '/setup' if ($r.Content -match '创建第一个管理员') { $r = Send 'POST' '/setup' @{ username = $User; display_name = '老板'; password = $Pass; password2 = $Pass } Check ($r.StatusCode -eq 200) '创建管理员并自动登录' Check ((Final-Path $r) -like '/packages*') '初始化后跳转到套餐页' } else { $r = Send 'POST' '/login' @{ username = $User; password = $Pass; next = '/calendar' } Check ($r.StatusCode -eq 200 -and (Final-Path $r) -like '/calendar*') '已存在管理员,登录成功' } # 2. 套餐 $r = Send 'GET' '/packages' Check ($r.StatusCode -eq 200) '套餐页可访问' $colors = @('#2F5D50', '#6B4E9B') $pkgs = @( @{ name = '包月日常'; unit = 'day'; default_amount = 30; price = 300; note = '每天日常 + 周常' }, @{ name = '深渊十次'; unit = 'count'; default_amount = 10; price = 180; note = '按次结算' } ) for ($i = 0; $i -lt $pkgs.Count; $i++) { $p = $pkgs[$i] $r = Send 'POST' '/packages' @{ name = $p.name; unit = $p.unit; default_amount = "$($p.default_amount)" price = "$($p.price)"; color = $colors[$i]; note = $p.note; sort_order = "$($i + 1)" } Check ($r.Content -match '套餐已保存|套餐已创建|套餐') "提交套餐 $($p.name)" } $r = Send 'GET' '/packages' Check ($r.Content -match '包月日常' -and $r.Content -match '深渊十次') '套餐列表包含两个套餐' $r = Send 'GET' '/packages/1/edit' Check ($r.StatusCode -eq 200 -and $r.Content -match '包月日常') '套餐编辑页可打开' # 3. 客户 $r = Send 'POST' '/customers' @{ tail = "$tail"; name = '老张'; game_name = '王者荣耀'; game_server = '微信区' contact = 'wx: laozhang'; tags = '老客户,包月'; source = '抖音'; note = '喜欢晚上打' } $custPath = Final-Path $r Check ($custPath -like '/customers/*') "新建客户成功 -> $custPath" $custID = ($custPath -split '/')[-1] $r = Send 'GET' "/customers?q=$tail" Check ($r.Content -match '老张') "尾号 $tail 搜索命中老张" $r = Send 'POST' '/customers' @{ tail = "$tail"; name = '老张' } Check ($r.StatusCode -eq 400 -and $r.Content -match '已存在') '重复客户被拒绝并提示' # 4. 订阅(按日 + 按次) $r = Send 'POST' "/customers/$custID/subscriptions" @{ package_id = '1'; start_date = $today; days = '30'; note = '首单' } Check ($r.Content -match '已开通') '开通按日订阅' $r = Send 'POST' "/customers/$custID/subscriptions" @{ package_id = '1'; start_date = $today; days = '30' } Check ($r.Content -match '追加时长') '同一套餐重复订阅被拦截' $r = Send 'POST' "/customers/$custID/subscriptions" @{ package_id = '2'; start_date = $today; total_count = '10'; end_date = $now.AddDays(10).ToString('yyyy-MM-dd') } Check ($r.Content -match '已开通') '开通按次订阅' $r = Send 'GET' "/customers/$custID" $subIDs = [regex]::Matches($r.Content, 'id="sub-(\d+)"') | ForEach-Object { [int]$_.Groups[1].Value } Check ($subIDs.Count -ge 2) "客户页出现 2 张订阅卡(实际 $($subIDs.Count))" $daySub = $subIDs[0] $cntSub = $subIDs[1] # 5. 追加时长(日历手动选终止日:今天 +45 天) $newEnd = $now.AddDays(45) $oldEnd = $now.AddDays(29) $expectDays = ($newEnd.Date - $oldEnd.Date).Days $r = Send 'POST' "/subscriptions/$daySub/extend" @{ new_end_date = $newEnd.ToString('yyyy-MM-dd'); note = '微信转账续费'; back = "/customers/$custID" } Check ($r.Content -match "已追加 $expectDays 天") "追加时长反算正确(期望 $expectDays 天)" $r = Send 'GET' "/customers/$custID" Check ($r.Content -match '剩 46 天') '客户页剩余天数更新为 46 天' Check ($r.Content -match '微信转账续费') '追加记录已留痕(备注)' Check ($r.Content -match '追加天数') '追加记录标注类型' # 6. 日历 $r = Send 'GET' "/calendar?y=$($now.Year)&m=$($now.Month)&d=$today" Check ($r.StatusCode -eq 200) '日历页可打开' Check ($r.Content -match '老张') '日历当天出现该客户色签' Check ($r.Content -match '当日待打') '日历抽屉存在' # 7. 打卡(按日) $r = Send 'POST' "/subscriptions/$daySub/checkin" @{ date = $today; status = 'done'; note = '打完'; back = "/calendar?d=$today" } Check ($r.Content -match '已记录') '按日打卡成功' # 8. 按次打卡扣次 $r = Send 'POST' "/subscriptions/$cntSub/checkin" @{ date = $today; status = 'done'; back = "/customers/$custID" } Check ($r.Content -match '已记录') '按次打卡成功(应扣 1 次)' $r = Send 'GET' "/customers/$custID" Check ($r.Content -match '剩 9 / 10 次') '按次剩余次数扣减为 9' # 9. 暂停 / 恢复 $r = Send 'POST' "/subscriptions/$daySub/pause" @{ back = "/customers/$custID" } Check ($r.Content -match '已暂停') '暂停订阅' $r = Send 'GET' "/customers/$custID" Check ($r.Content -match '暂停中') '客户页显示暂停中' $r = Send 'POST' "/subscriptions/$daySub/resume" @{ back = "/customers/$custID" } Check ($r.Content -match '已恢复') '恢复订阅' # 9.5 无有效期的按次订阅:靠客户页「今日打卡」扣次 $r = Send 'POST' '/packages' @{ name = '副本五次'; unit = 'count'; default_amount = '5'; price = '120'; color = '#41556B'; note = '无有效期'; sort_order = '3' } Check ($r.Content -match '套餐') '新建第三个套餐(按次、无有效期)' $r = Send 'GET' '/packages' $pkgIDs = [regex]::Matches($r.Content, '/packages/(\d+)/edit') | ForEach-Object { [int]$_.Groups[1].Value } $newPkg = ($pkgIDs | Measure-Object -Maximum).Maximum $r = Send 'POST' "/customers/$custID/subscriptions" @{ package_id = "$newPkg"; start_date = $today; total_count = '5' } Check ($r.Content -match '已开通') '开通无有效期的按次订阅' $r = Send 'GET' "/customers/$custID" Check ($r.Content -match '今日打卡') '订阅卡片出现「今日打卡」按钮' $ids3 = [regex]::Matches($r.Content, 'id="sub-(\d+)"') | ForEach-Object { [int]$_.Groups[1].Value } $thirdSub = $ids3[-1] $r = Send 'POST' "/subscriptions/$thirdSub/checkin" @{ date = $today; status = 'done'; back = "/customers/$custID" } Check ($r.Content -match '已记录') '无有效期按次订阅打卡成功' $r = Send 'GET' "/customers/$custID" Check ($r.Content -match '剩 4 / 5 次') '打卡后次数扣减为 4' # 10. 大屏 $r = Send 'GET' '/screen' Check ($r.StatusCode -eq 200) '大屏可访问' Check ($r.Content -match '即将到期 TOP 10') '大屏含 TOP 榜' Check ($r.Content -match '到期热力') '大屏含热力月历' Check ($r.Content -match "$tail") '大屏显示该客户尾号' # 11. 日志 / 设置 / 导出 $r = Send 'GET' '/logs' Check ($r.Content -match '追加时长') '操作日志含追加记录' $r = Send 'GET' '/settings' Check ($r.StatusCode -eq 200 -and $r.Content -match '基础设置') '设置页可打开' $r = Send 'GET' '/export/customers.csv' Check ($r.StatusCode -eq 200 -and $r.Content -match '手机尾号') '客户 CSV 导出正常' $r = Send 'GET' '/export/subscriptions.csv' Check ($r.StatusCode -eq 200 -and $r.Content -match '剩余') '订阅 CSV 导出正常' $r = Send 'GET' '/export/logs.csv' Check ($r.StatusCode -eq 200 -and $r.Content -match '操作人') '日志 CSV 导出正常' # 12. 静态资源与健康检查 $r = Send 'GET' '/static/app.css' Check ($r.StatusCode -eq 200 -and $r.Content -match 'ticket') '静态样式可访问' $r = Send 'GET' '/healthz' Check ($r.Content -match 'ok') '健康检查正常' Write-Host "" if ($script:fail -eq 0) { Write-Host "全部通过" -ForegroundColor Green exit 0 } else { Write-Host ("失败 $script:fail 项") -ForegroundColor Red exit 1 }