Files
artex/report/report_localized_test.go
T
dela 97668dab7e
ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
detections / detections (push) Waiting to run
docs / links (push) Waiting to run
web / web (push) Waiting to run
Localize user-facing UI, agent output, and backend messages to Simplified Chinese
2026-10-09 14:08:31 +08:00

181 lines
5.7 KiB
Go

package report
import (
"encoding/json"
"strings"
"testing"
"time"
"unicode"
"github.com/Autumn-27/artex/db"
)
// containsHan 은 문자열에 CJK 한자(중국어)가 섞여 있으면 true 를 돌려준다.
// 한글(unicode.Hangul)·영문·숫자·기호는 한자가 아니므로 걸리지 않는다.
func containsHan(s string) (rune, bool) {
for _, r := range s {
if unicode.Is(unicode.Han, r) {
return r, true
}
}
return 0, false
}
// containsHangul 은 문자열에 한글 음절이 남아 있으면 true 를 돌려준다.
// 중국어로 바뀐 뒤 한글이 되돌아오는 회귀를 잡는다.
func containsHangul(s string) (rune, bool) {
for _, r := range s {
if unicode.Is(unicode.Hangul, r) {
return r, true
}
}
return 0, false
}
// assertChinese 는 s 에 한자가 있고 한글이 없음을 단언한다.
func assertChinese(t *testing.T, label, s string) {
t.Helper()
if r, ok := containsHangul(s); ok {
t.Fatalf("%s: 한글이 남아 있습니다 %q:\n%s", label, string(r), s)
}
if _, ok := containsHan(s); !ok {
t.Fatalf("%s: 한자가 없습니다:\n%s", label, s)
}
}
func sampleFindingNode(t *testing.T) *db.Node {
t.Helper()
payload, err := json.Marshal(map[string]any{
"vulnclass": "SQL Injection",
"name": "登录表单 SQL 注入",
"severity": "high",
"summary": "在登录参数中确认了基于错误的 SQL 注入。",
"evidence": map[string]string{"poc": "' OR '1'='1"},
})
if err != nil {
t.Fatalf("payload 직렬화 실패: %v", err)
}
return &db.Node{ID: 1, Kind: "finding", Payload: payload}
}
func sampleDBFinding() *db.DBFinding {
return &db.DBFinding{
ID: 123,
VulnClass: "SQL Injection",
Name: "登录表单 SQL 注入",
Severity: "high",
Summary: "在登录参数中确认了基于错误的 SQL 注入。",
Evidence: "' OR '1'='1",
Status: "confirmed",
Report: "详细分析正文。",
CreatedAt: time.Date(2026, 10, 4, 2, 30, 0, 0, time.UTC),
EvidenceVersion: 2,
TaskDescription: "演示目标渗透测试",
TrafficBindings: []db.FindingTrafficBinding{
{
ID: 9,
Role: "request",
Note: "注入载荷发送点",
Snapshot: db.TrafficEvidenceSnapshot{
Method: "POST",
URL: "https://sandbox.local/login",
Status: 200,
},
},
},
}
}
// TestReportMarkdownLocalized 는 탐색 그래프 보고서(report.Markdown)의 골격이 한국어이고
// 중국어 한자가 전혀 없음을 검증한다.
func TestReportMarkdownLocalized(t *testing.T) {
out := Markdown(Input{
Title: "演示任务",
Goal: "沙箱全面检查",
GeneratedAt: time.Date(2026, 10, 4, 2, 30, 0, 0, time.UTC),
AssetCounts: map[string]int{"host": 2, "url": 5},
Findings: []*db.Node{sampleFindingNode(t)},
})
assertChinese(t, "보고서", out)
for _, want := range []string{
"# 渗透测试报告: 演示任务",
"- **任务目标**: 沙箱全面检查",
"- **生成时间**:",
"## 摘要",
"- 已确认漏洞: **1** 个",
"## 漏洞",
"**PoC / 证据:**",
} {
if !strings.Contains(out, want) {
t.Errorf("보고서 골격에 %q 가 없다", want)
}
}
}
// TestReportMarkdownEmptyLocalized 는 취약점이 없을 때의 안내 문구가 한국어임을 검증한다.
func TestReportMarkdownEmptyLocalized(t *testing.T) {
out := Markdown(Input{Title: "空任务", GeneratedAt: time.Now()})
assertChinese(t, "빈 보고서", out)
if !strings.Contains(out, "_本次未确认到任何漏洞。_") {
t.Errorf("빈 보고서 안내 문구가 중국어가 아니다:\n%s", out)
}
}
// TestFindingsMarkdownLocalized 는 취약점 요약/상세 Markdown 익스포트 골격과 심각도 라벨이
// 한국어이고 중국어 한자가 없음을 검증한다.
func TestFindingsMarkdownLocalized(t *testing.T) {
f := sampleDBFinding()
out := FindingsMarkdown([]*db.DBFinding{f}, time.Date(2026, 10, 4, 2, 30, 0, 0, time.UTC))
assertChinese(t, "취약점 요약 보고서", out)
for _, want := range []string{
"# 漏洞摘要报告",
"- **生成时间**:",
"- **漏洞总数**: 1 个",
"## 摘要",
"| 严重程度 | 数量 |",
"| 严重 |", "| 高危 |", "| 中危 |", "| 低危 |", // 심각도 라벨(UI status.severity 와 동일)
"## 漏洞详情",
"- **类型**: SQL Injection",
"- **状态**: confirmed",
"- **所属任务**: 演示目标渗透测试",
"- **发现时间**:",
"**证据:**",
"**详细报告:**",
"## 相关流量证据",
"证据版本: 2, 绑定数量: 1.",
} {
if !strings.Contains(out, want) {
t.Errorf("요약 보고서에 %q 가 없다", want)
}
}
single := SingleFindingMarkdown(f, time.Date(2026, 10, 4, 2, 30, 0, 0, time.UTC))
assertChinese(t, "단건 보고서", single)
for _, want := range []string{
"- **严重程度**: high",
"## 概述",
"## 证据",
"## 详细报告",
"[请求消息](evidence/123/9/request.http)",
"[响应消息](evidence/123/9/response.http)",
} {
if !strings.Contains(single, want) {
t.Errorf("단건 보고서에 %q 가 없다", want)
}
}
}
// TestFindingsCSVLocalized 는 CSV 익스포트 헤더가 한국어이고 중국어 한자가 없음을 검증한다.
// (BOM 뒤 헤더 행만 한국어면 된다. 데이터 값은 원본 그대로 둔다.)
func TestFindingsCSVLocalized(t *testing.T) {
raw := FindingsCSV([]*db.DBFinding{sampleDBFinding()})
out := strings.TrimPrefix(string(raw), "\xEF\xBB\xBF")
header := strings.SplitN(out, "\n", 2)[0]
assertChinese(t, "CSV 헤더", header)
for _, col := range []string{"ID", "名称", "类型", "严重程度", "状态", "所属任务", "发现时间", "概述", "流量证据数量", "流量证据 ID"} {
if !strings.Contains(header, col) {
t.Errorf("CSV 헤더에 %q 열이 없다: %s", col, header)
}
}
}