Files
shaguabijia-app-server/scripts/invite_reward_e2e.ps1
T
liujiahui 2229dfaf04 feat(invite): 好友列表已比价状态 + DEV 造虚拟好友接口; fix(deploy): 每日兑换定时器锁北京时区 (#104)
## 改动概述

本 PR 含两个主题(关联 7-1 邀请页 / 兑换时区工作)。

### 1. 邀请好友列表「已比价」状态
- `get_invitees` 返回新增 `is_compared` 字段(= `compare_reward_granted`)。
  客户端据此区分好友列表「去提醒 / 邀请成功」、在途列表只取未比价、算在途好友数与在途收益。
- 新增开发接口 `POST/DELETE /invite/seed-fake`(仅非生产, `is_prod` 挡 404):
  造未比价 + 已比价虚拟好友, 已比价者走真实入账发 2 元邀请奖励金,
  方便真机联调在途 / 好友列表 UI; 清理时原路退回, 不留脏账。

### 2. 每日兑换定时器时区修复
- `deploy/daily-exchange.service` / `.timer` 锁定 `Asia/Shanghai`,
  避免按服务器本地时区在错误时刻触发每日兑换(此前隐患: worker 已用 cn_today,
  但 systemd timer 仍按服务器本地时区)。
- 新增 `test_daily_exchange_timezone` 覆盖同一北京日内幂等。

## 测试
- 邀请 + 兑换时区相关用例全过(`28 passed`)。
- 说明: 仓库预存的 3 个 proxy 转发测试(`test_compare_proxy` / `test_coupon_proxy`)失败,
  已在干净 HEAD 上复现确认与本改动无关, 不在本 PR 处理范围。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: no_gen_mu <liujianhishen@gmail.com>
Reviewed-on: #104
Co-authored-by: liujiahui <liujiahui@wonderable.ai>
Co-committed-by: liujiahui <liujiahui@wonderable.ai>
2026-07-03 14:42:50 +08:00

142 lines
6.9 KiB
PowerShell

# 邀请发奖真环境验证脚本(方案①:纯后端)—— PowerShell 版 ·【192 局域网 debug 环境】
# ==============================================================================
# 证明:好友 B 实际下单(POST /api/v1/order/report)→ 邀请人 A 到账 2 元(invite_cash),
# 且「只绑定不下单不发」「重复不重发」「无关系不发」;并证明固定测试号 B 经
# dev-reset 后可【反复】当新用户,再次触发发奖。
# 不用真下单 / 不用付钱 / 不用无障碍 / 不用第二台设备。
#
# 打的是本地局域网 debug 环境(默认 http://192.168.0.144:8770 = 本机 LAN IP)。
# 两个前提:① SMS_MOCK=true(默认) → 任意 6 位验证码通过;② .env 配了
# TEST_ACCOUNT_PHONE=11111111111 并【重启过】app-server(B 靠它免验证码 + dev-reset)。
#
# 用法(PowerShell):
# .\invite_reward_e2e.ps1 # 默认 192.168.0.144:8770
# .\invite_reward_e2e.ps1 -BaseUrl http://127.0.0.1:8770
# ==============================================================================
param(
# 目标环境:本地局域网 debug :8770(默认本机 LAN IP)
[string]$BaseUrl = "http://192.168.0.144:8770"
)
$ErrorActionPreference = "Stop"
$BaseUrl = $BaseUrl.TrimEnd('/')
# B 固定用测试号(免验证码 + 可 dev-reset 反复当新用户)。必须与 .env 的 TEST_ACCOUNT_PHONE 一致。
$phoneB = "11111111111"
# A(邀请人)/ X(无关系)用带时间戳的新号。
$stamp = [int][double]::Parse((Get-Date -UFormat %s))
$suffix = ($stamp % 100000000)
$phoneX = "139" + ("{0:D8}" -f $suffix)
$expectCents = 200 # = INVITE_COMPARE_REWARD_CENTS(2 元)
Write-Host "======================================================================"
Write-Host " 邀请发奖·192 局域网 debug 验证 BASE_URL=$BaseUrl"
Write-Host " B(固定测试号·被邀请人)=$phoneB X(无关系·下单)=$phoneX"
Write-Host "======================================================================"
# ---- HTTP 小工具(Invoke-RestMethod 自动解析 JSON)---------------------------
function Invoke-Api {
param([string]$Method, [string]$Path, $Body, [string]$Token)
$headers = @{ "Content-Type" = "application/json; charset=utf-8" }
if ($Token) { $headers["Authorization"] = "Bearer $Token" }
$req = @{ Method = $Method; Uri = "$BaseUrl$Path"; Headers = $headers }
if ($null -ne $Body) {
# 用 UTF8 字节发,保证「美团」等中文不乱码
$json = ($Body | ConvertTo-Json -Compress)
$req["Body"] = [System.Text.Encoding]::UTF8.GetBytes($json)
}
return Invoke-RestMethod @req
}
# 登录:发码(mock 不真发)→ 用 123456 登录 → 回 access_token
# 唯一 device_id 避开「device_id+IP 每小时 5 次」限流(测试号 B 本不受限,统一带上无害)。
function Login {
param([string]$Phone)
$dev = "e2e-$Phone-$stamp"
Invoke-Api POST "/api/v1/auth/sms/send" @{ phone = $Phone; device_id = $dev } | Out-Null
$r = Invoke-Api POST "/api/v1/auth/sms/login" @{ phone = $Phone; code = "123456"; device_id = $dev }
if (-not $r.access_token) { throw "登录失败 phone=$Phone" }
return $r.access_token
}
# 把固定测试号 B 打回出厂态(清绑定/发奖/savings + 刷 created_at 骗过 72h 闸)。无需鉴权。
function Reset-TestAccount {
try {
$r = Invoke-Api POST "/api/v1/invite/dev-reset" @{}
} catch {
throw "dev-reset 失败:$($_.Exception.Message) (检查 app-server 是否用最新代码重启?.env 是否配了 TEST_ACCOUNT_PHONE 并重启?)"
}
if ($r.status -ne "ok") { throw "dev-reset 返回非 ok:$($r | ConvertTo-Json -Compress)" }
}
function Get-Reward { param([string]$Tok) (Invoke-Api GET "/api/v1/invite/me" $null $Tok).reward_balance_cents }
function Get-MyCode { param([string]$Tok) (Invoke-Api GET "/api/v1/invite/me" $null $Tok).invite_code }
function Bind-To { param([string]$Tok,[string]$Code) Invoke-Api POST "/api/v1/invite/bind" @{ invite_code = $Code } $Tok }
function Report-Order {
param([string]$Tok,[string]$EventId)
Invoke-Api POST "/api/v1/order/report" @{
client_event_id = $EventId
platform = "美团"
pay_channel = "wechat"
compared_price_cents = 2016
paid_amount_cents = 2016
} $Tok | Out-Null
}
# ---- 断言 -------------------------------------------------------------------
$script:pass = 0; $script:fail = 0
function Assert-Eq {
param($Expected, $Actual, [string]$Desc)
if ("$Expected" -eq "$Actual") { Write-Host "$Desc (=$Actual)"; $script:pass++ }
else { Write-Host "$Desc 期望=$Expected 实得=$Actual" -ForegroundColor Red; $script:fail++ }
}
# ---- 一轮完整发奖验证:reset B → 新 A 绑定 → B 下单 → A 到账 -----------------
function Invoke-RewardCycle {
param([int]$Tag)
$phoneA = "138" + ("{0:D8}" -f (($suffix + $Tag) % 100000000))
Write-Host "`n===== 第 $Tag 轮:A=$phoneA 邀请固定号 B=$phoneB ====="
Reset-TestAccount # 把 B 打回新用户态
$tokA = Login $phoneA
$tokB = Login $phoneB
$codeA = Get-MyCode $tokA
Write-Host " A 邀请码=$codeA;B 已 dev-reset 回新用户"
Assert-Eq 0 (Get-Reward $tokA) "[$Tag] 发奖前 A.invite_cash"
$bind = Bind-To $tokB $codeA
Write-Host " bind → status=$($bind.status)(期望 success)"
Assert-Eq 0 (Get-Reward $tokA) "[$Tag] 仅绑定后 A.invite_cash(绑定不发)"
Report-Order $tokB "evt-$stamp-$Tag-1"
Assert-Eq $expectCents (Get-Reward $tokA) "[$Tag] B 首单后 A.invite_cash(下单才发)"
Report-Order $tokB "evt-$stamp-$Tag-1" # 重复同 event
Report-Order $tokB "evt-$stamp-$Tag-2" # 不同 event 第二笔
Assert-Eq $expectCents (Get-Reward $tokA) "[$Tag] B 重复/多次上报后 A.invite_cash(只发一次)"
}
# ============================== 开跑 =========================================
# 第 1、2 轮:同一个固定号 B 反复当新用户各拿一次发奖(证明 dev-reset 让它可反复用)。
Invoke-RewardCycle 1
Invoke-RewardCycle 2
# 无关系用户下单 → 不发奖
Write-Host "`n===== 无关系校验:X 没绑定任何人,下单不应发奖 ====="
$tokX = Login $phoneX
Report-Order $tokX "evt-$stamp-x"
Assert-Eq 0 (Get-Reward $tokX) "无邀请关系 X 下单后 X.invite_cash(不发)"
# ============================== 结果 =========================================
Write-Host "`n======================================================================"
if ($script:fail -eq 0) {
Write-Host " ✅ 全部通过($script:pass 项):下单才发奖 / 绑定不发 / 重复不重发 / 无关系不发" -ForegroundColor Green
Write-Host " 且固定号 B 经 dev-reset 后【反复】当新用户两轮均发奖成功。" -ForegroundColor Green
exit 0
} else {
Write-Host " ❌ 有 $($script:fail) 项未通过(通过 $($script:pass) 项)。看上面 ✗ 行。" -ForegroundColor Red
exit 1
}