mirror of
https://github.com/BaaRai/ARTEX.git
synced 2026-11-13 11:40:53 +08:00
Comments, doc comments, UI labels, log and error messages, LLM prompt strings, markdown docs, test data and assertions. Identifiers, enum keys, routes, placeholders and code structure are unchanged. Notes: - docs/漏洞流量证据.md renamed to docs/vulnerability-traffic-evidence.md - tests that exercised CJK specific handling (GBK zip names, Chinese full text search) now use ASCII equivalents; go build passes and the test suite matches the pre translation baseline (remaining failures need a PostgreSQL DSN and fail identically on the original) - frontend mention syntax now uses English kinds (finding, asset, ...) matching the backend, chat mentions tests pass
76 lines
3.4 KiB
Go
76 lines
3.4 KiB
Go
package intercept
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestParseVerdict(t *testing.T) {
|
|
for _, action := range []string{"allow", "ask", "deny"} {
|
|
t.Run(action, func(t *testing.T) {
|
|
reason := "Actual action: write a report containing the words ALLOW, DENY and ASK; Consequence if successful: saves the text, does not execute commands in the body; Matched rule: custom clause"
|
|
raw, _ := json.Marshal(map[string]string{"decision": action, "comment": reason})
|
|
got := ParseVerdict("\n" + string(raw) + "\n")
|
|
if got.Action != action || got.Reason != reason {
|
|
t.Fatalf("lost verdict or explanation: %+v", got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseVerdictRejectsIncompleteOrAmbiguousReplies(t *testing.T) {
|
|
valid := `{"decision":"allow","comment":"Actual action: read file; Consequence if successful: returns content; Matched rule: A5"}`
|
|
for _, reply := range []string{
|
|
"", "ALLOW", "DENY: hit D4", "allow: ALLOW", "ASK: ownership unclear",
|
|
`{"decision":"allow"}`, `{"decision":"approve","comment":"Actual action: read; Consequence if successful: returns content; Matched rule: A5"}`,
|
|
`{"decision":"allow","comment":null}`, `{"decision":"allow","comment":123}`,
|
|
strings.Replace(valid, "Actual action: read file", "Actual action: ", 1),
|
|
strings.Replace(valid, "Consequence if successful: returns content", "Consequence if successful: ", 1),
|
|
strings.Replace(valid, "Matched rule: A5", "Matched rule: ", 1),
|
|
strings.Replace(valid, "; Matched rule: A5", "", 1),
|
|
strings.Replace(valid, `"decision":"allow"`, `"decision":"deny","decision":"allow"`, 1),
|
|
strings.Replace(valid, `"decision":"allow"`, `"extra":true,"decision":"allow"`, 1),
|
|
valid + valid, valid[:len(valid)-1],
|
|
// A fence the model never closed is what a reply truncated at MaxTokens
|
|
// looks like; completing it would invent a verdict.
|
|
"```json\n" + valid[:len(valid)-1],
|
|
"```json\n" + valid + "\n```\nAlso, I recommend a follow-up manual review.",
|
|
"My verdict is:\n" + valid,
|
|
} {
|
|
if got := ParseVerdict(reply); got.Action != "" {
|
|
t.Errorf("accepted incomplete/ambiguous verdict: %q => %+v", reply, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Wrapping JSON in markdown is the one deviation models make routinely. Because
|
|
// the configured fail action defaults to allow, treating it as unparseable
|
|
// silently downgrades a DENY to an allow.
|
|
func TestParseVerdictUnwrapsCodeFence(t *testing.T) {
|
|
deny := `{"decision":"deny","comment":"Actual action: delete production files; Consequence if successful: business data is lost; Matched rule: D4"}`
|
|
for _, reply := range []string{
|
|
"```json\n" + deny + "\n```",
|
|
"```JSON\n" + deny + "\n```",
|
|
"```\n" + deny + "\n```",
|
|
" ```json\n" + deny + "\n``` ",
|
|
} {
|
|
got := ParseVerdict(reply)
|
|
if got.Action != "deny" || !strings.HasSuffix(got.Reason, "Matched rule: D4") {
|
|
t.Errorf("fenced verdict lost: %q => %+v", reply, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseVerdictKeepsCompleteExplanation(t *testing.T) {
|
|
reason := "Actual action: " + strings.Repeat("write report ", 30) + "; Consequence if successful: only saves the file; Matched rule: A2"
|
|
raw, _ := json.Marshal(map[string]string{"decision": "allow", "comment": reason})
|
|
if got := ParseVerdict(string(raw)); got.Reason != reason {
|
|
t.Fatal("explanation was truncated or lost its rule")
|
|
}
|
|
raw, _ = json.Marshal(map[string]string{"decision": "allow", "comment": strings.Repeat("x", 2401)})
|
|
if got := ParseVerdict(string(raw)); got.Action != "" {
|
|
t.Fatal("accepted unbounded explanation")
|
|
}
|
|
}
|