209 lines
9.0 KiB
PowerShell
209 lines
9.0 KiB
PowerShell
#Requires -Version 5.1
|
|
<#
|
|
.SYNOPSIS
|
|
Run the backend functional-demo smoke flow.
|
|
|
|
.DESCRIPTION
|
|
This script exercises only existing demo APIs:
|
|
auth/RBAC, compliance/copy gate, market alert, KYC collection, and templates.
|
|
It uses development data and does not require Neo4j, Ollama, or DeepSeek.
|
|
#>
|
|
param(
|
|
[string]$BaseUrl = "http://127.0.0.1:8000"
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
$BaseUrl = $BaseUrl.TrimEnd("/")
|
|
|
|
function Invoke-DemoJson {
|
|
param(
|
|
[string]$Method,
|
|
[string]$Path,
|
|
[hashtable]$Headers = @{},
|
|
[object]$Body = $null
|
|
)
|
|
|
|
$params = @{
|
|
Method = $Method
|
|
Uri = "$BaseUrl$Path"
|
|
Headers = $Headers
|
|
}
|
|
if ($null -ne $Body) {
|
|
$params.ContentType = "application/json"
|
|
$params.Body = $Body | ConvertTo-Json -Depth 10 -Compress
|
|
}
|
|
return Invoke-RestMethod @params
|
|
}
|
|
|
|
function New-TraceId {
|
|
param([string]$Name)
|
|
return "demo-$Name-$([guid]::NewGuid().ToString('N').Substring(0, 8))"
|
|
}
|
|
|
|
function Get-Sha256Hex {
|
|
param([string]$Text)
|
|
$bytes = [System.Text.Encoding]::UTF8.GetBytes($Text)
|
|
$hash = [System.Security.Cryptography.SHA256]::Create().ComputeHash($bytes)
|
|
return -join ($hash | ForEach-Object { $_.ToString("x2") })
|
|
}
|
|
|
|
function Assert-Equal {
|
|
param(
|
|
[object]$Actual,
|
|
[object]$Expected,
|
|
[string]$Message
|
|
)
|
|
if ($Actual -ne $Expected) {
|
|
throw "$Message. Expected '$Expected', got '$Actual'."
|
|
}
|
|
}
|
|
|
|
Write-Host "== JinRong Agent functional demo smoke =="
|
|
Write-Host "Base URL: $BaseUrl"
|
|
|
|
$health = Invoke-RestMethod "$BaseUrl/health"
|
|
Assert-Equal $health.status "ok" "Health check failed"
|
|
|
|
$login = Invoke-DemoJson -Method "POST" -Path "/api/v1/auth/login" -Body @{
|
|
username = "advisor_test"
|
|
password = "advisor_test"
|
|
}
|
|
Assert-Equal $login.code "00000" "Advisor login failed"
|
|
$advisorToken = $login.data.access_token
|
|
$advisorHeaders = @{ Authorization = "Bearer $advisorToken" }
|
|
|
|
$complianceLogin = Invoke-DemoJson -Method "POST" -Path "/api/v1/auth/login" -Body @{
|
|
username = "compliance_test"
|
|
password = "compliance_test"
|
|
}
|
|
Assert-Equal $complianceLogin.code "00000" "Compliance login failed"
|
|
$complianceHeaders = @{ Authorization = "Bearer $($complianceLogin.data.access_token)" }
|
|
|
|
Write-Host "[1/5] Compliance and copy gate"
|
|
$safeText = "请结合产品说明书、风险揭示和客户风险承受能力进行客观说明。"
|
|
$warnText = "该产品近期波动较大,错过再无机会。"
|
|
$blockText = "这款产品保证赚钱,绝对不会亏。"
|
|
|
|
$safeCheck = Invoke-DemoJson -Method "POST" -Path "/api/v1/compliance/check" `
|
|
-Headers @{ Authorization = "Bearer $advisorToken"; "X-Trace-Id" = (New-TraceId "compliance-safe") } `
|
|
-Body @{ text = $safeText; scene = "demo" }
|
|
$warnCheck = Invoke-DemoJson -Method "POST" -Path "/api/v1/compliance/check" `
|
|
-Headers @{ Authorization = "Bearer $advisorToken"; "X-Trace-Id" = (New-TraceId "compliance-warn") } `
|
|
-Body @{ text = $warnText; scene = "demo" }
|
|
$blockCheck = Invoke-DemoJson -Method "POST" -Path "/api/v1/compliance/check" `
|
|
-Headers @{ Authorization = "Bearer $advisorToken"; "X-Trace-Id" = (New-TraceId "compliance-block") } `
|
|
-Body @{ text = $blockText; scene = "demo" }
|
|
Assert-Equal $safeCheck.data.risk_level "INFO" "Safe text compliance result mismatch"
|
|
Assert-Equal $warnCheck.data.risk_level "WARN" "Warn text compliance result mismatch"
|
|
Assert-Equal $blockCheck.data.risk_level "BLOCK" "Block text compliance result mismatch"
|
|
|
|
$copy = Invoke-DemoJson -Method "POST" -Path "/api/v1/copy/track" `
|
|
-Headers @{ Authorization = "Bearer $advisorToken"; "X-Trace-Id" = (New-TraceId "copy-info") } `
|
|
-Body @{
|
|
content_type = "text"
|
|
content_summary = $safeText.Substring(0, 20)
|
|
content_hash = Get-Sha256Hex $safeText
|
|
source_type = "demo"
|
|
source_id = "safe"
|
|
compliance_check_id = $safeCheck.data.check_id
|
|
compliance_risk_level = "INFO"
|
|
}
|
|
Assert-Equal $copy.code "00000" "INFO copy gate failed"
|
|
|
|
$warnCopy = Invoke-DemoJson -Method "POST" -Path "/api/v1/copy/track" `
|
|
-Headers @{ Authorization = "Bearer $advisorToken"; "X-Trace-Id" = (New-TraceId "copy-warn") } `
|
|
-Body @{
|
|
content_type = "text"
|
|
content_summary = $warnText.Substring(0, 15)
|
|
content_hash = Get-Sha256Hex $warnText
|
|
source_type = "demo"
|
|
source_id = "warn"
|
|
compliance_check_id = $warnCheck.data.check_id
|
|
compliance_risk_level = "WARN"
|
|
warn_confirmed = $true
|
|
}
|
|
Assert-Equal $warnCopy.code "00000" "Confirmed WARN copy gate failed"
|
|
|
|
Write-Host "[2/5] Market data, alert, generation, feedback"
|
|
$quote = Invoke-DemoJson -Method "GET" -Path "/api/v1/market/fund/000001" -Headers $advisorHeaders
|
|
Assert-Equal $quote.data.data_source "jinrong_core" "Core market data source mismatch"
|
|
|
|
$scan = Invoke-DemoJson -Method "POST" -Path "/api/v1/market-alerts/scan" `
|
|
-Headers @{ Authorization = "Bearer $advisorToken"; "X-Trace-Id" = (New-TraceId "market-scan") } `
|
|
-Body @{ nav_date = "2026-09-04"; threshold_pct = 1.0 }
|
|
$alert = $scan.data.items | Where-Object { $_.fund_code -eq "161725" } | Select-Object -First 1
|
|
if ($null -eq $alert) {
|
|
$alerts = Invoke-DemoJson -Method "GET" -Path "/api/v1/market-alerts?date=2026-09-04" -Headers $advisorHeaders
|
|
$alert = $alerts.data.items | Where-Object { $_.fund_code -eq "161725" } | Select-Object -First 1
|
|
}
|
|
if ($null -eq $alert) {
|
|
throw "No demo market alert found for fund 161725."
|
|
}
|
|
|
|
$generated = Invoke-DemoJson -Method "POST" -Path "/api/v1/market-alerts/generate" `
|
|
-Headers @{ Authorization = "Bearer $advisorToken"; "X-Trace-Id" = (New-TraceId "market-generate") } `
|
|
-Body @{ fund_code = $alert.fund_code }
|
|
foreach ($section in @("【异动概述】", "【原因分析】", "【当前建议】", "【风险提示】")) {
|
|
if ($generated.data.generated_text -notlike "*$section*") {
|
|
throw "Generated market text is missing section: $section"
|
|
}
|
|
}
|
|
$feedback = Invoke-DemoJson -Method "PUT" -Path "/api/v1/market-alerts/$($generated.data.alert_id)/feedback" `
|
|
-Headers @{ Authorization = "Bearer $advisorToken"; "X-Trace-Id" = (New-TraceId "market-feedback") } `
|
|
-Body @{ action = "adopt" }
|
|
Assert-Equal $feedback.data.copied $true "Market alert adopt feedback did not copy"
|
|
|
|
Write-Host "[3/5] KYC collection"
|
|
$kyc = Invoke-DemoJson -Method "POST" -Path "/api/v1/kyc/sessions" `
|
|
-Headers @{ Authorization = "Bearer $advisorToken"; "X-Trace-Id" = (New-TraceId "kyc-create") } `
|
|
-Body @{
|
|
customer_id = "CUST-1001"
|
|
session_type = "new_customer"
|
|
customer_display_name = "客户·王**"
|
|
}
|
|
$sessionId = $kyc.data.session_id
|
|
$chat = Invoke-DemoJson -Method "POST" -Path "/api/v1/kyc/sessions/$sessionId/chat" `
|
|
-Headers @{ Authorization = "Bearer $advisorToken"; "X-Trace-Id" = (New-TraceId "kyc-chat") } `
|
|
-Body @{ customer_input = "客户今年28岁,女性,未婚,处于事业起步阶段" }
|
|
if ($chat.data.progress_pct -le 0) {
|
|
throw "KYC progress did not advance."
|
|
}
|
|
$back = Invoke-DemoJson -Method "POST" -Path "/api/v1/kyc/sessions/$sessionId/chat" `
|
|
-Headers @{ Authorization = "Bearer $advisorToken"; "X-Trace-Id" = (New-TraceId "kyc-back") } `
|
|
-Body @{ customer_input = "客户今年30岁"; skip_to = "basic_info" }
|
|
Assert-Equal $back.data.current_node "basic_info" "KYC retrograde node mismatch"
|
|
$completed = Invoke-DemoJson -Method "POST" -Path "/api/v1/kyc/sessions/$sessionId/complete" `
|
|
-Headers @{ Authorization = "Bearer $advisorToken"; "X-Trace-Id" = (New-TraceId "kyc-complete") }
|
|
Assert-Equal $completed.data.status "completed" "KYC completion status mismatch"
|
|
|
|
Write-Host "[4/5] Template approval, search, and use"
|
|
$seedTemplates = Invoke-DemoJson -Method "GET" -Path "/api/v1/templates?keyword=DEV-TOP&page_size=100" `
|
|
-Headers $complianceHeaders
|
|
$template = $seedTemplates.data.items | Where-Object { $_.created_by -eq "seed:template_test_data" } | Select-Object -First 1
|
|
if ($null -eq $template) {
|
|
throw "No DEV-TOP demo template found."
|
|
}
|
|
if (-not $template.is_approved) {
|
|
$approved = Invoke-DemoJson -Method "PUT" -Path "/api/v1/templates/$($template.id)" `
|
|
-Headers $complianceHeaders -Body @{ is_approved = $true }
|
|
Assert-Equal $approved.data.is_approved $true "Template approval failed"
|
|
}
|
|
$search = Invoke-DemoJson -Method "GET" `
|
|
-Path "/api/v1/templates/search?q=$([uri]::EscapeDataString($template.title))" `
|
|
-Headers $advisorHeaders
|
|
if ($search.data.items.Count -lt 1) {
|
|
throw "Approved demo template was not searchable."
|
|
}
|
|
$use = Invoke-DemoJson -Method "POST" -Path "/api/v1/templates/$($template.id)/use" `
|
|
-Headers @{ Authorization = "Bearer $advisorToken"; "X-Trace-Id" = (New-TraceId "template-use") } `
|
|
-Body @{
|
|
is_modified = $true
|
|
modified_content = "$($template.content) 请结合风险揭示文件自主决定。"
|
|
}
|
|
Assert-Equal $use.data.template_id $template.id "Template use returned wrong template"
|
|
|
|
Write-Host "[5/5] Demo smoke passed"
|
|
Write-Host "KYC session: $sessionId"
|
|
Write-Host "Market alert: $($generated.data.alert_id)"
|
|
Write-Host "Template use: $($use.data.use_id)"
|